Fix an invalid IL bug with interface sweeping#463
Conversation
703fea0 to
32cffcb
Compare
|
I don't this is going to work. Unfortunately, there are many more places where the implicit reference conversion is done. Consider simple class X
{
public static void Main ()
{
C c = null;
Foo(c);
}
static void Foo (I i)
{
}
}
class C : I
{
}
interface I
{
} |
|
Good catch. I can address that case by collecting the parameter types of all calls. See any other cases where this falls apart? |
|
Looks like I’m already collecting the parameter types. I will take a look at your test case later today when I get in. On the surface it seems like it should be ok. |
|
what about generics with interface constraint, array operations or Activator calls? |
There were issues with a number of scenarios involving generics. I will update the PR with fixes shortly.
I'm adding array tests after fixing the generic issues and I'm not seeing any problems. Do you have a specific case you are concerned with?
What specifically are you concerned with about Activator? In order for Activator to work, one would have to ensure an instance ctor is marked, and once an instance ctor is marked this bug is no longer an issue. |
|
Summary of updated changes
|
32cffcb to
dd8b93e
Compare
|
I'll have to think more about the complex scenarios but what about simple class X
{
public static void Main ()
{
object[] c = new I[2];
c [0] = new C ();
}
}
class C : I
{
}
interface I
{
} |
|
@marek-safar This bug was exclusive to reference types that do not have an instance constructor marked. |
|
Something to consider. This PR is a bug fix, not a new feature. I think this plugs all of the holes that exist on If the approach in this PR seems acceptable, it doesn't introduce new bugs, and you have no concerns about undesirable side effects of the changes, then I'd rather we land this PR and then worry about other bugs than leave the PR open waiting to think of all possible bugs. |
|
|
||
| bool IsInterfaceImplementationMarked (TypeDefinition type, TypeDefinition interfaceType) | ||
| { | ||
| if (type.Implements (@interfaceType, out InterfaceImplementation implementation)) |
There was a problem hiding this comment.
Could be simple return type.Implements (@interfaceType, out InterfaceImplementation implementation) && Annotations.IsMarked (implementation);
|
|
||
| namespace Mono.Linker { | ||
| public static class TypeDefinitionExtensions { | ||
| public static bool Implements (this TypeDefinition type, TypeDefinition interfaceType, out InterfaceImplementation implementation) |
There was a problem hiding this comment.
The name is not right, it should be something like HasInterface (type, out iface)
| using Mono.Cecil.Cil; | ||
|
|
||
| namespace Mono.Linker { | ||
| public static class BodyRequirementsCollector { |
There was a problem hiding this comment.
Let's call it MethodBodyScanner instead
There was a problem hiding this comment.
Will do. I was struggling to come up with a good name.
|
|
||
| namespace Mono.Linker { | ||
| public static class BodyRequirementsCollector { | ||
| public static IEnumerable<InterfaceImplementation> InterfacesNeededByBody (AnnotationStore annotations, MethodBody body) |
There was a problem hiding this comment.
and this one GetReferencedInterfaces
| public static class BodyRequirementsCollector { | ||
| public static IEnumerable<InterfaceImplementation> InterfacesNeededByBody (AnnotationStore annotations, MethodBody body) | ||
| { | ||
| var interfaceImplementations = new HashSet<InterfaceImplementation> (); |
There was a problem hiding this comment.
Return null instead of empty cases
| if (possibleStackTypes.Count == 0) | ||
| return interfaceImplementations; | ||
|
|
||
| var interfaceTypes = possibleStackTypes.Where (t => t.IsInterface).Distinct ().ToArray (); |
There was a problem hiding this comment.
Why do we need Distinct over Hashset?
There was a problem hiding this comment.
Artifact of an earlier version where I was return List from AllPossibleStackTypes. I missed this while cleaning up. Fixed.
| return interfaceImplementations; | ||
|
|
||
| // We only sweep interfaces on classes so that's why we only care about classes | ||
| var classes = possibleStackTypes.Where (t => t.IsClass).Distinct ().ToArray (); |
There was a problem hiding this comment.
This loop is redundant, you can fold it to the one bellow
| return marked_types_with_cctor.Add (type); | ||
| } | ||
|
|
||
| public void SetBaseHierarchy (TypeDefinition type, List<TypeDefinition> bases) |
There was a problem hiding this comment.
This is for classes only, the name should reflect that (e.g. SetClassHierarchy)
| if (classes.Length == 0) | ||
| return interfaceImplementations; | ||
|
|
||
| // If a type could be on the stack in the body and an interface it implements could be on the stack on the body |
There was a problem hiding this comment.
I am not sure this will cover all cases (will have to experiment with it)
There was a problem hiding this comment.
One thing we don't have tests for, and I thought this might not be enough to address is covariance and contravariance.
If there is a bug with covariance and/or contravariance I'd rather address it in a follow on pr. That is an increasingly less common scenario where this interface sweep bug could appear.
If a type can be on the stack then we need to keep the interface implementation on that type even if the type is never instantiated. I think we can get away with only doing this when the using of the type and the interface are within the same body. Once beyond the body, there won't be invalid IL. Casting is similar, see `ObjectHardCastedToInterface`
dd8b93e to
238025e
Compare
If a type can be on the stack then we need to keep the interface implementation on that type even if the type is never instantiated.
I think we can get away with only doing this when the using of the type and the interface are within the same body. Once beyond the body, there won't be invalid IL. Casting is similar, see
ObjectHardCastedToInterfaceThe tests that hit the invalid IL bug were
FieldDowncastedToInterfaceLocalDowncastedToInterfaceReturnValueDowncastedToInterfaceThe test
ObjectHardCastedToInterfacewas passing on master. I think the expected result this test is asserting is OK.The test
LocalDowncastDoesNotCuaseOtherTypesToKeepInterfaceis to ensure we don't go overboard marking interface implementations.