Skip to content

Fix an invalid IL bug with interface sweeping#463

Merged
marek-safar merged 1 commit into
dotnet:masterfrom
Unity-Technologies:master-interface-sweep-bug-fix
Feb 14, 2019
Merged

Fix an invalid IL bug with interface sweeping#463
marek-safar merged 1 commit into
dotnet:masterfrom
Unity-Technologies:master-interface-sweep-bug-fix

Conversation

@mrvoorhe
Copy link
Copy Markdown
Contributor

@mrvoorhe mrvoorhe commented Feb 8, 2019

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

The tests that hit the invalid IL bug were

  • FieldDowncastedToInterface
  • LocalDowncastedToInterface
  • ReturnValueDowncastedToInterface

The test ObjectHardCastedToInterface was passing on master. I think the expected result this test is asserting is OK.

The test LocalDowncastDoesNotCuaseOtherTypesToKeepInterface is to ensure we don't go overboard marking interface implementations.

@mrvoorhe mrvoorhe requested a review from marek-safar as a code owner February 8, 2019 21:09
@mrvoorhe mrvoorhe force-pushed the master-interface-sweep-bug-fix branch 2 times, most recently from 703fea0 to 32cffcb Compare February 8, 2019 21:22
@marek-safar
Copy link
Copy Markdown
Contributor

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
{
}

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

Good catch. I can address that case by collecting the parameter types of all calls.

See any other cases where this falls apart?

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

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.

@marek-safar
Copy link
Copy Markdown
Contributor

what about generics with interface constraint, array operations or Activator calls?

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

what about generics with interface constraint

There were issues with a number of scenarios involving generics. I will update the PR with fixes shortly.

array operations

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?

Activator calls?

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.

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

mrvoorhe commented Feb 11, 2019

Summary of updated changes

  • Fixed a number of cases involving generics
  • Fixed an issue with interfaces on base types not being kept.
  • Added some array tests
  • Pushed as much of this logic into it's own class to help make it easier to follow.
  • Removed stack type collection from TypeMapStep. This is getting rather complex (and non-trivial cost) and I think it's better to do it during MarkMethodBody since that way we only have to pay the price for methods that are used.
  • Copied over the TypeMapStep.MapBaseTypeHierarchy change from WIP : Master base sweeping simplified #455. I needed to get base types so I figured I'd pull that change in rather than copy pasting the loop & resolve code necessary to iterate all the base types.

@mrvoorhe mrvoorhe force-pushed the master-interface-sweep-bug-fix branch from 32cffcb to dd8b93e Compare February 11, 2019 16:13
@marek-safar
Copy link
Copy Markdown
Contributor

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
{
}

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

@marek-safar new C() causes C to be marked as instance required which means interface implementations on C will be marked as soon as the interface implementation type is marked. In your case, I is marked so the interface will remain on C.

This bug was exclusive to reference types that do not have an instance constructor marked.

@mrvoorhe
Copy link
Copy Markdown
Contributor Author

Something to consider. This PR is a bug fix, not a new feature. I think this plugs all of the holes that exist on master today. Maybe it does, or maybe there are still edge cases hiding. Either way, master is better off with this PR than without it.

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.

Comment thread linker/Linker.Steps/MarkStep.cs Outdated

bool IsInterfaceImplementationMarked (TypeDefinition type, TypeDefinition interfaceType)
{
if (type.Implements (@interfaceType, out InterfaceImplementation implementation))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could be simple return type.Implements (@interfaceType, out InterfaceImplementation implementation) && Annotations.IsMarked (implementation);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done


namespace Mono.Linker {
public static class TypeDefinitionExtensions {
public static bool Implements (this TypeDefinition type, TypeDefinition interfaceType, out InterfaceImplementation implementation)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The name is not right, it should be something like HasInterface (type, out iface)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

using Mono.Cecil.Cil;

namespace Mono.Linker {
public static class BodyRequirementsCollector {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's call it MethodBodyScanner instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

and this one GetReferencedInterfaces

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

public static class BodyRequirementsCollector {
public static IEnumerable<InterfaceImplementation> InterfacesNeededByBody (AnnotationStore annotations, MethodBody body)
{
var interfaceImplementations = new HashSet<InterfaceImplementation> ();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Return null instead of empty cases

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

if (possibleStackTypes.Count == 0)
return interfaceImplementations;

var interfaceTypes = possibleStackTypes.Where (t => t.IsInterface).Distinct ().ToArray ();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need Distinct over Hashset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 ();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This loop is redundant, you can fold it to the one bellow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread linker/Linker/Annotations.cs Outdated
return marked_types_with_cctor.Add (type);
}

public void SetBaseHierarchy (TypeDefinition type, List<TypeDefinition> bases)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is for classes only, the name should reflect that (e.g. SetClassHierarchy)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not sure this will cover all cases (will have to experiment with it)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants