-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[nnyeah] Assembly comparator (#14836)
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
1 parent
1dcefc9
commit 1aa3e4d
Showing
12 changed files
with
901 additions
and
74 deletions.
There are no files selected for viewing
106 changes: 106 additions & 0 deletions
106
tools/nnyeah/nnyeah/AssemblyComparator/ComparingVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
34
tools/nnyeah/nnyeah/AssemblyComparator/ItemFoundEventArgs.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
137
tools/nnyeah/nnyeah/AssemblyComparator/ModuleVisitor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) => { }; | ||
} | ||
} |
Oops, something went wrong.
1aa3e4d
There was a problem hiding this comment.
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
1aa3e4d
There was a problem hiding this comment.
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
1aa3e4d
There was a problem hiding this comment.
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:
Pipeline on Agent
Hash: 1aa3e4d17b4879bce27f7c648134e0b980924b5b
1aa3e4d
There was a problem hiding this comment.
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
1aa3e4d
There was a problem hiding this comment.
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
Pipeline on Agent XAMBOT-1033.Monterey
[nnyeah] Assembly comparator (#14836)