Skip to content

Refactor missing reference errors to allow not throwing for all cases#50437

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
davidwrighton:refactor_reference_load_failure
Mar 31, 2021
Merged

Refactor missing reference errors to allow not throwing for all cases#50437
davidwrighton merged 4 commits into
dotnet:mainfrom
davidwrighton:refactor_reference_load_failure

Conversation

@davidwrighton

Copy link
Copy Markdown
Member
  • Build the concept of a cacheable resolution failure
  • Plumb it through the Ecma type loader and the required public api surfaces
  • Use it within the Mibc parser to avoid throwing

- Build the concept of a cacheable resolution failure
- Plumb it through the Ecma type loader and the required public api surfaces
- Use it within the Mibc parser to avoid throwing
@davidwrighton

Copy link
Copy Markdown
Member Author

@dotnet/crossgen-contrib The current state of pgo data loading was quite difficult to debug due to the excessive amount of exception throwing. This change allows typesystem users to disable all those exceptions as desired.

@trylek trylek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me, thank you!

typeName = fullName.Substring(split + 1);
}
return module.GetType(namespaceName, typeName, throwIfNotFound);
return module.GetType(namespaceName, typeName, throwIfNotFound ? NotFoundBehavior.Throw : NotFoundBehavior.ReturnNull);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to propagate the new enum up the call graph instead of decoding the bool "in the middle"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plausibly, but this change is already quite large enough, and very few components other than the internals of the typesystem actually can do anything useful with ResolutionFailure objects. So I don't think we need to perturb the api of the typesystem more than I already have.


public PropertySignature ParsePropertySignature()
{
// As PropertySignature is a struct, we cannot return null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I have a hard time understanding the exact semantics of this check. Does that mean that we can only use non-Throw signature parser modes when we know upfront that we won't be parsing any property signatures? Why can't we just throw when ParseType returns null?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle is that if one only throws if ParseType returns null, its easy for a bug like using a non-throwing parse mode to be exist and require an obscure test case or worse customer scenario to actually encounter the bug. By forcing the throw eagerly, it becomes easier to find the bug of using the ParsePropertySignature method incorrectly. I didn't adjust the api to allow for this, as there aren't any uses of this function in the current repo, so I don't have a good way to be confident that users will be served well by any api I come up with.

/// or <see cref="MethodSignature"/>).
/// </summary>
public abstract Object GetObject(int token);
public abstract Object GetObject(int token, NotFoundBehavior notFoundBehavior);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give this one a default value (throw) so that we can limit the number of places where this needs to be passed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I always start my refactor without the default param, just to make sure I've made a decision at all points, but as you say, it hits a bunch of stuff which could benefit from a default param. I've put that in.

Comment thread src/coreclr/tools/Common/TypeSystem/Common/ResolutionFailure.cs Outdated
{
public class ResolutionFailure
{
private enum FailureType

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it would be easier to just have a single field in this that is the exception instance.

sealed class ResolutionFailure
{
    private readonly Exception _exception;

    public ResolutionFailure(Exception ex) => _exception = ex;

    public void Throw() => throw _exception;
}

We would need to update ThrowHelper so that we have CreateXXX methods along with ThrowXXX methods on it.

Then the usage is "new ResolutionFailure(ThrowHelper.CreateFileNotFoundException(...));`

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that, but unfortunately, as these ResolutionFailure objects are persistent, it can result in multiple throws with the exact same exception in them. While this does work, it results in stack traces in the exception being somewhat nondeterministic, which I very much disapprove of.

Comment thread src/coreclr/tools/Common/TypeSystem/Ecma/EcmaModule.cs Outdated
Comment thread src/coreclr/tools/Common/TypeSystem/Ecma/EcmaSignatureParser.cs
@davidwrighton davidwrighton merged commit 4581290 into dotnet:main Mar 31, 2021
MichalStrehovsky added a commit that referenced this pull request Apr 7, 2021
This was missed in #50437.

Wonder if we should have just introduced a new overload of GetType that returns `object`. This "return resolution failure that we then need to not forget to check" looks like a potential bug farm.

```csharp
public MetadataType GetType(string nameSpace, string name, bool throwIfNotFound = true)
{
    /* the obvious implementation that calls the virtual method */
}

public abstract object GetType(string nameSpace, string name, NotFoundBehavior notFoundBehavior)
```
MichalStrehovsky added a commit that referenced this pull request Apr 8, 2021
This was missed in #50437.

Wonder if we should have just introduced a new overload of GetType that returns `object`. This "return resolution failure that we then need to not forget to check" looks like a potential bug farm.

```csharp
public MetadataType GetType(string nameSpace, string name, bool throwIfNotFound = true)
{
    /* the obvious implementation that calls the virtual method */
}

public abstract object GetType(string nameSpace, string name, NotFoundBehavior notFoundBehavior)
```
@davidwrighton davidwrighton deleted the refactor_reference_load_failure branch April 20, 2021 17:47
@karelz karelz added this to the 6.0.0 milestone May 20, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jun 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants