Skip to content

Commit

Permalink
[nnyeah] Assembly comparator (#14836)
Browse files Browse the repository at this point in the history
This adds code to compare a .NET 5 version of Xamarin.macOS to Microsoft.macOS or Xamarin.iOS to Microsoft.iOS and outputs a huge xml file with incompatible translations or maps from one to the other.
  • Loading branch information
stephen-hawley authored May 2, 2022
1 parent 1dcefc9 commit 1aa3e4d
Show file tree
Hide file tree
Showing 12 changed files with 901 additions and 74 deletions.
106 changes: 106 additions & 0 deletions tools/nnyeah/nnyeah/AssemblyComparator/ComparingVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using Mono.Cecil;

#nullable enable

namespace Microsoft.MaciOS.Nnyeah.AssemblyComparator {
public class ComparingVisitor {
ModuleDefinition EarlierModule, LaterModule;
bool PublicOnly;

public ComparingVisitor (ModuleDefinition earlierModule, ModuleDefinition laterModule, bool publicOnly)
{
EarlierModule = earlierModule;
LaterModule = laterModule;
PublicOnly = publicOnly;
}

public void Visit ()
{
var earlierElements = ModuleElements.Import (EarlierModule, PublicOnly);
if (earlierElements is null)
throw new Exception (Errors.E0007);
var laterElements = ModuleElements.Import (LaterModule, PublicOnly);
if (laterElements is null)
throw new Exception (Errors.E0007);
var reworker = new TypeReworker (EarlierModule);
VisitTypes (reworker, earlierElements, laterElements);
}

void VisitTypes (TypeReworker reworker, ModuleElements earlier, ModuleElements later)
{
foreach (var typeNameAndValue in earlier.Types) {
if (!later.Types.TryGetValue (typeNameAndValue.Key, out var laterElems)) {
TypeEvents.InvokeNotFound (this, typeNameAndValue.Key);
continue;
}

TypeEvents.InvokeFound (this, typeNameAndValue.Key, laterElems.DeclaringType.ToString ());

VisitAllMembers (reworker, typeNameAndValue.Value, laterElems);
}
}

void VisitAllMembers (TypeReworker reworker, TypeElements earlier, TypeElements later)
{
VisitMembers (reworker, earlier.Methods, later.Methods, MethodEvents);
VisitMembers (reworker, earlier.Fields, later.Fields, FieldEvents);
VisitMembers (reworker, earlier.Events, later.Events, EventEvents);
VisitMembers (reworker, earlier.Properties, later.Properties, PropertyEvents);
}

void VisitMembers<T> (TypeReworker reworker, List<TypeElement<T>> earlier,
List<TypeElement<T>> later, ItemEvents<T> events) where T : IMemberDefinition
{
foreach (var earlierElem in earlier) {
VisitMember (reworker, earlierElem, later, events);
}
}

void VisitMember<T> (TypeReworker reworker, TypeElement<T> elem,
List<TypeElement<T>> later, ItemEvents<T> events) where T : IMemberDefinition
{
foreach (var late in later) {
if (elem.Signature == late.Signature) {
events.InvokeFound (this, elem.Signature, late.Signature);
return;
}
}
var remappedSig = RemappedSignature (reworker, elem.Element);
if (remappedSig == elem.Signature) {
events.InvokeNotFound (this, elem.Signature);
return;
}
foreach (var late in later) {
if (remappedSig == late.Signature) {
events.InvokeFound (this, elem.Signature, late.Signature);
return;
}
}
events.InvokeNotFound (this, elem.Signature);
}

static string RemappedSignature<T> (TypeReworker reworker, T elem) where T : IMemberDefinition
{
switch (elem) {
case MethodDefinition method:
return reworker.ReworkMethod (method).ToString ();
case FieldDefinition field:
return reworker.ReworkField (field).ToString ();
case EventDefinition @event:
return reworker.ReworkEvent (@event).ToString ();
case PropertyDefinition property:
return reworker.ReworkProperty (property).ToString ();
default:
throw new ArgumentException (nameof (elem));
}
}

public ItemEvents<TypeDefinition> TypeEvents { get; } = new ();
public ItemEvents<MethodDefinition> MethodEvents { get; } = new ();
public ItemEvents<FieldDefinition> FieldEvents { get; } = new ();
public ItemEvents<EventDefinition> EventEvents { get; } = new ();
public ItemEvents<PropertyDefinition> PropertyEvents { get; } = new ();
}
}
34 changes: 34 additions & 0 deletions tools/nnyeah/nnyeah/AssemblyComparator/ItemFoundEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;

#nullable enable

namespace Microsoft.MaciOS.Nnyeah.AssemblyComparator {
public class ItemNotFoundEventArgs<T> : EventArgs{
public ItemNotFoundEventArgs (string original)
{
Original = original;
}
public string Original { get; init; }
}

public class ItemFoundEventArgs<T> : EventArgs {
public ItemFoundEventArgs (string original, string mapped)
{
Original = original;
Mapped = mapped;
}
public string Original { get; init; }
public string Mapped { get; init; }
}

public class ItemEvents<T> {
public EventHandler<ItemNotFoundEventArgs<T>> NotFound = (s, e) => { };
public EventHandler<ItemFoundEventArgs<T>> Found = (s, e) => { };

internal void InvokeFound (object sender, string original, string mapped) =>
Found.Invoke (sender, new (original, mapped));

internal void InvokeNotFound (object sender, string original) =>
NotFound.Invoke (sender, new (original));
}
}
72 changes: 72 additions & 0 deletions tools/nnyeah/nnyeah/AssemblyComparator/ModuleElements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Mono.Cecil;

#nullable enable

namespace Microsoft.MaciOS.Nnyeah.AssemblyComparator {
public class ModuleElements {
public Dictionary<string, TypeElements> Types { get; init; } = new Dictionary<string, TypeElements> ();
ModuleElements ()
{
}

public static ModuleElements Import (ModuleDefinition module, bool publicOnly)
{
var moduleElements = new ModuleElements ();
var typeStack = new Stack<TypeElements> ();

var visitor = new ModuleVisitor (module, publicOnly);

visitor.TypeVisited += (s, e) => {
if (e.Kind == VisitKind.Start) {
typeStack.Push (new TypeElements (e.Type));
} else if (e.Kind == VisitKind.End) {
var typeElements = typeStack.Pop ();
moduleElements.Types.Add (typeElements.DeclaringType.FullName, typeElements);
} else {
throw new Exception (String.Format(Errors.E0008, e.Kind.ToString()));
}
};

visitor.FieldVisited += (s, e) => {
if (e.Kind == VisitKind.Start) {
var typeElements = typeStack.Peek ();
var member = e.Member;
typeElements.Fields.Add (new TypeElement<FieldDefinition> (member.ToString (), member));
}
};

visitor.EventVisited += (s, e) => {
if (e.Kind == VisitKind.Start) {
var typeElements = typeStack.Peek ();
var member = e.Member;
typeElements.Events.Add (new TypeElement<EventDefinition> (member.ToString (), member));
}
};

visitor.PropertyVisited += (s, e) => {
if (e.Kind == VisitKind.Start) {
var typeElements = typeStack.Peek ();
var member = e.Member;
typeElements.Properties.Add (new TypeElement<PropertyDefinition> (member.ToString (), member));
}
};

visitor.MethodVisited += (s, e) => {
if (e.Kind == VisitKind.Start) {
var typeElements = typeStack.Peek ();
var member = e.Member;
typeElements.Methods.Add (new TypeElement<MethodDefinition> (member.ToString (), member));
}
};

visitor.Visit ();
if (typeStack.Count != 0) {
throw new Exception (Errors.E0009);
}

return moduleElements;
}
}
}
137 changes: 137 additions & 0 deletions tools/nnyeah/nnyeah/AssemblyComparator/ModuleVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System;
using System.IO;
using Mono.Cecil;
using System.Collections.Generic;

#nullable enable

namespace Microsoft.MaciOS.Nnyeah.AssemblyComparator {
public class ModuleVisitor {
ModuleDefinition Module;
bool PublicOnly;
public ModuleVisitor (ModuleDefinition module, bool publicOnly)
{
Module = module;
PublicOnly = publicOnly;
}

public ModuleVisitor (Stream stm, bool publicOnly)
: this (ModuleDefinition.ReadModule (stm), publicOnly)
{
}


public void Visit ()
{
VisitTypes (Module.Types);
}

void VisitTypes (IEnumerable<TypeDefinition> types)
{
foreach (var type in types) {
VisitType (type);
}
}

void VisitType (TypeDefinition type)
{
if (PublicOnly && !type.IsPublic)
return;
TypeVisited.Invoke (this, new TypeVisitedEventArgs (VisitKind.Start, type));

if (type.NestedTypes.Count > 0)
VisitTypes (type.NestedTypes);
VisitFields (type);
VisitEvents (type);
VisitProperties (type);
VisitMethods (type);

TypeVisited.Invoke (this, new TypeVisitedEventArgs (VisitKind.End, type));
}

void VisitFields (TypeDefinition type)
{
foreach (var field in type.Fields)
VisitField (type, field);
}

void VisitField (TypeDefinition type, FieldDefinition field)
{
if (PublicOnly && !field.IsPublic)
return;

FieldVisited.Invoke (this, new MemberVisitedEventArgs<FieldDefinition> (VisitKind.Start, type, field));
FieldVisited.Invoke (this, new MemberVisitedEventArgs<FieldDefinition> (VisitKind.End, type, field));
}

void VisitProperties (TypeDefinition type)
{
foreach (var prop in type.Properties) {
VisitProperty (type, prop);
}
}

void VisitProperty (TypeDefinition type, PropertyDefinition prop)
{
if (PublicOnly && !(PublicGetterExists (prop) || PublicSetterExists (prop)))
return;
PropertyVisited (this, new MemberVisitedEventArgs<PropertyDefinition> (VisitKind.Start, type, prop));
PropertyVisited (this, new MemberVisitedEventArgs<PropertyDefinition> (VisitKind.End, type, prop));
}

bool PublicGetterExists (PropertyDefinition prop)
{
return MethodExistsAndIsPublic (prop.GetMethod);
}

bool PublicSetterExists (PropertyDefinition prop)
{
return MethodExistsAndIsPublic (prop.SetMethod);
}

bool MethodExistsAndIsPublic (MethodDefinition? meth)
{
return meth is not null && meth.IsPublic;
}

void VisitEvents (TypeDefinition type)
{
foreach (var evt in type.Events) {
VisitEvent (type, evt);
}
}

void VisitEvent (TypeDefinition type, EventDefinition evt)
{
var method = evt.AddMethod ?? evt.RemoveMethod ?? evt.InvokeMethod;
if (method is null)
return;
if (PublicOnly && !method.IsPublic)
return;

EventVisited.Invoke (this, new MemberVisitedEventArgs<EventDefinition> (VisitKind.Start, type, evt));
EventVisited.Invoke (this, new MemberVisitedEventArgs<EventDefinition> (VisitKind.End, type, evt));
}

void VisitMethods (TypeDefinition type)
{
foreach (var method in type.Methods) {
VisitMethod (type, method);
}
}

void VisitMethod (TypeDefinition type, MethodDefinition method)
{
if (PublicOnly && !method.IsPublic)
return;
MethodVisited (this, new MemberVisitedEventArgs<MethodDefinition> (VisitKind.Start, type, method));
MethodVisited (this, new MemberVisitedEventArgs<MethodDefinition> (VisitKind.End, type, method));
}

public EventHandler<TypeVisitedEventArgs> TypeVisited = (s, e) => { };
public EventHandler<MemberVisitedEventArgs<MethodDefinition>> MethodVisited = (s, e) => { };
public EventHandler<MemberVisitedEventArgs<FieldDefinition>> FieldVisited = (s, e) => { };
public EventHandler<MemberVisitedEventArgs<EventDefinition>> EventVisited = (s, e) => { };
public EventHandler<MemberVisitedEventArgs<PropertyDefinition>> PropertyVisited = (s, e) => { };
}
}
Loading

5 comments on commit 1aa3e4d

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

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

📋 [CI Build] API Diff 📋

API Current PR diff

✅ API Diff (from PR only) (no change)

View API diff
View dotnet API diff
View dotnet legacy API diff
View dotnet iOS-MacCatalayst API diff

API diff

✅ API Diff from stable

View API diff
View dotnet API diff
View dotnet legacy API diff
View dotnet iOS-MacCatalayst API diff

Generator diff

Generator Diff (no change)

Pipeline on Agent XAMMINI-051.Monterey'
Hash: 1aa3e4d17b4879bce27f7c648134e0b980924b5b

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

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

💻 [CI Build] Tests on macOS M1 - Mac Big Sur (11.5) passed 💻

All tests on macOS M1 - Mac Big Sur (11.5) passed.

Pipeline on Agent
Hash: 1aa3e4d17b4879bce27f7c648134e0b980924b5b

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

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

❌ [CI Build] Tests on macOS Mac Catalina (10.15) failed ❌

Failed tests are:

  • introspection

Pipeline on Agent
Hash: 1aa3e4d17b4879bce27f7c648134e0b980924b5b

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

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

📚 [CI Build] Artifacts 📚

Packages generated

View packages

Pipeline on Agent XAMMINI-052.Monterey'
Hash: 1aa3e4d17b4879bce27f7c648134e0b980924b5b

@vs-mobiletools-engineering-service2
Copy link
Collaborator

Choose a reason for hiding this comment

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

❌ [CI Build] Tests failed on VSTS: simulator tests iOS ❌

Tests failed on VSTS: simulator tests iOS.

Test results

2 tests failed, 232 tests passed.

Failed tests

  • introspection/iOS Unified 64-bits - simulator/Debug (iOS 12.4) [dotnet]: Failed
  • introspection/tvOS - simulator/Debug (tvOS 12.4) [dotnet]: Failed

Pipeline on Agent XAMBOT-1033.Monterey
[nnyeah] Assembly comparator (#14836)

Please sign in to comment.