5 changed files with 347 additions and 16 deletions
@ -0,0 +1,205 @@ |
|||||||
|
//
|
||||||
|
// ABIComparer.cs
|
||||||
|
//
|
||||||
|
// Author:
|
||||||
|
// Mike Krüger <mkrueger@xamarin.com>
|
||||||
|
//
|
||||||
|
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
namespace ICSharpCode.NRefactory.CSharp |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Used to check the compatibility state of two compilations.
|
||||||
|
/// </summary>
|
||||||
|
public enum AbiCompatibility |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The ABI is equal
|
||||||
|
/// </summary>
|
||||||
|
Equal, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Some items got added, but the ABI remains to be compatible
|
||||||
|
/// </summary>
|
||||||
|
Bigger, |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ABI has changed
|
||||||
|
/// </summary>
|
||||||
|
Incompatible |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Abi comparer checks the public API of two compilation and determines the compatibility state.
|
||||||
|
/// </summary>
|
||||||
|
public class AbiComparer |
||||||
|
{ |
||||||
|
void CheckContstraints(ITypeParameter p1, ITypeParameter p2, ref AbiCompatibility compatibility) |
||||||
|
{ |
||||||
|
if (p1.DirectBaseTypes.Count () != p2.DirectBaseTypes.Count () || |
||||||
|
p1.HasReferenceTypeConstraint != p2.HasReferenceTypeConstraint || |
||||||
|
p1.HasValueTypeConstraint != p2.HasValueTypeConstraint || |
||||||
|
p1.HasDefaultConstructorConstraint != p2.HasDefaultConstructorConstraint) { |
||||||
|
IncompatibilityFound ("Constraints are not compatible."); |
||||||
|
compatibility = AbiCompatibility.Incompatible; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CheckTypes (ITypeDefinition oType, ITypeDefinition nType, ref AbiCompatibility compatibility) |
||||||
|
{ |
||||||
|
int oldMemberCount = 0; |
||||||
|
Predicate<IUnresolvedMember> pred = null; |
||||||
|
if (oType.Kind == TypeKind.Class || oType.Kind == TypeKind.Struct) |
||||||
|
pred = m => (m.IsPublic || m.IsProtected) && !m.IsOverride && !m.IsSynthetic; |
||||||
|
|
||||||
|
for (int i = 0; i < oType.TypeParameterCount; i++) { |
||||||
|
CheckContstraints (oType.TypeParameters[i], nType.TypeParameters[i], ref compatibility); |
||||||
|
if (compatibility == AbiCompatibility.Incompatible) |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var member in oType.GetMembers (pred, GetMemberOptions.IgnoreInheritedMembers)) { |
||||||
|
var newMember = nType.GetMembers (m => member.UnresolvedMember.Name == m.Name && m.IsPublic == member.IsPublic && m.IsProtected == member.IsProtected); |
||||||
|
var equalMember = newMember.FirstOrDefault (m => SignatureComparer.Ordinal.Equals (member, m)); |
||||||
|
if (equalMember == null) { |
||||||
|
compatibility = AbiCompatibility.Incompatible; |
||||||
|
return; |
||||||
|
} |
||||||
|
var om = member as IMethod; |
||||||
|
if (om != null) { |
||||||
|
for (int i = 0; i < om.TypeParameters.Count; i++) { |
||||||
|
CheckContstraints (om.TypeParameters[i], ((IMethod)equalMember).TypeParameters[i], ref compatibility); |
||||||
|
if (compatibility == AbiCompatibility.Incompatible) |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
oldMemberCount++; |
||||||
|
} |
||||||
|
if (compatibility == AbiCompatibility.Bigger && oType.Kind != TypeKind.Interface) |
||||||
|
return; |
||||||
|
if (oldMemberCount != nType.GetMembers (pred, GetMemberOptions.IgnoreInheritedMembers).Count ()) { |
||||||
|
if (oType.Kind == TypeKind.Interface) { |
||||||
|
IncompatibilityFound ("Interface " + oType.FullName + " changed."); |
||||||
|
compatibility = AbiCompatibility.Incompatible; |
||||||
|
} else { |
||||||
|
compatibility = AbiCompatibility.Bigger; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CheckNamespace(INamespace oNs, INamespace nNs, ref AbiCompatibility compatibility) |
||||||
|
{ |
||||||
|
foreach (var type in oNs.Types) { |
||||||
|
if (!type.IsPublic && !type.IsProtected) |
||||||
|
continue; |
||||||
|
var newType = nNs.GetTypeDefinition (type.Name, type.TypeParameterCount); |
||||||
|
if (newType == null) { |
||||||
|
IncompatibilityFound ("Type definition "+ type.FullName +" is missing."); |
||||||
|
compatibility = AbiCompatibility.Incompatible; |
||||||
|
return; |
||||||
|
} |
||||||
|
CheckTypes (type, newType, ref compatibility); |
||||||
|
if (compatibility == AbiCompatibility.Incompatible) |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (compatibility == AbiCompatibility.Bigger) |
||||||
|
return; |
||||||
|
foreach (var type in nNs.Types) { |
||||||
|
if (!type.IsPublic && !type.IsProtected) |
||||||
|
continue; |
||||||
|
if (oNs.GetTypeDefinition (type.Name, type.TypeParameterCount) == null) { |
||||||
|
compatibility = AbiCompatibility.Bigger; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static bool ContainsPublicTypes(INamespace testNs) |
||||||
|
{ |
||||||
|
var stack = new Stack<INamespace> (); |
||||||
|
stack.Push (testNs); |
||||||
|
while (stack.Count > 0) { |
||||||
|
var ns = stack.Pop (); |
||||||
|
if (ns.Types.Any (t => t.IsPublic)) |
||||||
|
return true; |
||||||
|
foreach (var child in ns.ChildNamespaces) |
||||||
|
stack.Push (child); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check the specified oldProject and newProject if they're compatible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldProject">Old project.</param>
|
||||||
|
/// <param name="newProject">New project.</param>
|
||||||
|
public AbiCompatibility Check (ICompilation oldProject, ICompilation newProject) |
||||||
|
{ |
||||||
|
var oldStack = new Stack<INamespace> (); |
||||||
|
var newStack = new Stack<INamespace> (); |
||||||
|
oldStack.Push (oldProject.MainAssembly.RootNamespace); |
||||||
|
newStack.Push (newProject.MainAssembly.RootNamespace); |
||||||
|
|
||||||
|
AbiCompatibility compatibility = AbiCompatibility.Equal; |
||||||
|
while (oldStack.Count > 0) { |
||||||
|
var oNs = oldStack.Pop (); |
||||||
|
var nNs = newStack.Pop (); |
||||||
|
|
||||||
|
CheckNamespace (oNs, nNs, ref compatibility); |
||||||
|
if (compatibility == AbiCompatibility.Incompatible) |
||||||
|
return AbiCompatibility.Incompatible; |
||||||
|
foreach (var child in oNs.ChildNamespaces) { |
||||||
|
var newChild = nNs.GetChildNamespace (child.Name); |
||||||
|
if (newChild == null) { |
||||||
|
IncompatibilityFound ("Namespace "+ child.FullName +" is missing."); |
||||||
|
return AbiCompatibility.Incompatible; |
||||||
|
} |
||||||
|
oldStack.Push (child); |
||||||
|
newStack.Push (newChild); |
||||||
|
} |
||||||
|
|
||||||
|
// check if namespaces are added
|
||||||
|
if (compatibility != AbiCompatibility.Bigger) { |
||||||
|
foreach (var child in nNs.ChildNamespaces) { |
||||||
|
if (oNs.GetChildNamespace (child.Name) == null) { |
||||||
|
if (ContainsPublicTypes (child)) |
||||||
|
compatibility = AbiCompatibility.Bigger; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return compatibility; |
||||||
|
} |
||||||
|
|
||||||
|
void IncompatibilityFound(string str) |
||||||
|
{ |
||||||
|
Console.WriteLine (str); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,108 @@ |
|||||||
|
//
|
||||||
|
// AbiComparerTests.cs
|
||||||
|
//
|
||||||
|
// Author:
|
||||||
|
// Mike Krüger <mkrueger@xamarin.com>
|
||||||
|
//
|
||||||
|
// Copyright (c) 2013 Xamarin Inc. (http://xamarin.com)
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using ICSharpCode.NRefactory.CSharp.Resolver; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||||
|
using NUnit.Framework; |
||||||
|
using ICSharpCode.NRefactory.CSharp.CodeCompletion; |
||||||
|
|
||||||
|
namespace ICSharpCode.NRefactory.CSharp.Analysis |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class AbiComparerTests |
||||||
|
{ |
||||||
|
AbiCompatibility Check (string before, string after) |
||||||
|
{ |
||||||
|
IProjectContent oldPctx, newPctx; |
||||||
|
SyntaxTree tree; |
||||||
|
ICSharpCode.NRefactory.CSharp.TypeSystem.CSharpUnresolvedFile file; |
||||||
|
CodeCompletionBugTests.CreateCompilation (before, out oldPctx, out tree, out file, false); |
||||||
|
CodeCompletionBugTests.CreateCompilation (after, out newPctx, out tree, out file, false); |
||||||
|
return new AbiComparer ().Check (oldPctx.CreateCompilation (), newPctx.CreateCompilation ()); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckEquality() |
||||||
|
{ |
||||||
|
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} public int Bar2 { get; set;} int removed; } class Removed {} }"; |
||||||
|
string a2 = @"namespace Foo { public class Bar { public void FooBar () {} public int Bar2 { get {} set{}} void Added () {} } class Added {} } namespace Added { class Test { } }"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Equal, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckBigger() |
||||||
|
{ |
||||||
|
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} } }"; |
||||||
|
string a2 = @"namespace Foo { public class Bar { public void FooBar () {} public void BarFoo () {} } }"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Bigger, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckIncompatible() |
||||||
|
{ |
||||||
|
string a1 = @"namespace Foo { public class Bar { public void FooBar () {} } }"; |
||||||
|
string a2 = @"namespace Foo { public class Bar { public void FooBar (int bar) {} } }"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckIncompatibleInterfaceChange() |
||||||
|
{ |
||||||
|
string a1 = @"public interface IFoo {}"; |
||||||
|
string a2 = @"public interface IFoo { void Bar (); }"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckTypeConstraintChange() |
||||||
|
{ |
||||||
|
string a1 = @"public class IFoo<T> {}"; |
||||||
|
string a2 = @"public class IFoo<T> where T : System.IDisposable {}"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckTypeConstraintChangeCase2() |
||||||
|
{ |
||||||
|
string a1 = @"public class IFoo<T> {}"; |
||||||
|
string a2 = @"public class IFoo<T> where T : class {}"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void CheckMethodConstraintChange() |
||||||
|
{ |
||||||
|
string a1 = @"public class IFoo { public void Bar<T> () {} }"; |
||||||
|
string a2 = @"public class IFoo { public void Bar<T> () where T : System.IDisposable {} }"; |
||||||
|
Assert.AreEqual (AbiCompatibility.Incompatible, Check (a1, a2)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue