Browse Source

Added ABI comparer.

pull/32/merge
Mike Krüger 13 years ago
parent
commit
84e9a33715
  1. 205
      ICSharpCode.NRefactory.CSharp/Analysis/AbiComparer.cs
  2. 1
      ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
  3. 108
      ICSharpCode.NRefactory.Tests/CSharp/Analysis/AbiComparerTests.cs
  4. 48
      ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs
  5. 1
      ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

205
ICSharpCode.NRefactory.CSharp/Analysis/AbiComparer.cs

@ -0,0 +1,205 @@ @@ -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);
}
}
}

1
ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj

@ -516,6 +516,7 @@ @@ -516,6 +516,7 @@
<Compile Include="Resolver\ReducedExtensionMethod.cs" />
<Compile Include="Refactoring\CodeIssues\SimplifyAnonymousMethodToDelegateIssue.cs" />
<Compile Include="Refactoring\CodeIssues\CompilerErrors\CS0127ReturnMustNotBeFollowedByAnyExpression.cs" />
<Compile Include="Analysis\AbiComparer.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj">

108
ICSharpCode.NRefactory.Tests/CSharp/Analysis/AbiComparerTests.cs

@ -0,0 +1,108 @@ @@ -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));
}
}
}

48
ICSharpCode.NRefactory.Tests/CSharp/CodeCompletion/CodeCompletionBugTests.cs

@ -304,6 +304,30 @@ namespace ICSharpCode.NRefactory.CSharp.CodeCompletion @@ -304,6 +304,30 @@ namespace ICSharpCode.NRefactory.CSharp.CodeCompletion
return loader.LoadAssemblyFile(typeof(System.Linq.Enumerable).Assembly.Location);
});
public static void CreateCompilation (string parsedText, out IProjectContent pctx, out SyntaxTree syntaxTree, out CSharpUnresolvedFile unresolvedFile, bool expectErrors, params IUnresolvedAssembly[] references)
{
pctx = new CSharpProjectContent();
var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value, systemXmlLinq.Value };
if (references != null)
refs.AddRange (references);
pctx = pctx.AddAssemblyReferences(refs);
syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
syntaxTree.Freeze();
if (!expectErrors && syntaxTree.Errors.Count > 0) {
Console.WriteLine ("----");
Console.WriteLine (parsedText);
Console.WriteLine ("----");
foreach (var error in syntaxTree.Errors)
Console.WriteLine (error.Message);
Assert.Fail ("Parse error.");
}
unresolvedFile = syntaxTree.ToTypeSystem();
pctx = pctx.AddOrUpdateFiles(unresolvedFile);
}
public static CSharpCompletionEngine CreateEngine(string text, out int cursorPosition, params IUnresolvedAssembly[] references)
{
string parsedText;
@ -317,26 +341,18 @@ namespace ICSharpCode.NRefactory.CSharp.CodeCompletion @@ -317,26 +341,18 @@ namespace ICSharpCode.NRefactory.CSharp.CodeCompletion
parsedText = editorText = text.Substring(0, cursorPosition) + text.Substring(cursorPosition + 1);
}
} else {
parsedText = text.Substring(0, cursorPosition) + new string(' ', endPos - cursorPosition) + text.Substring(endPos + 1);
editorText = text.Substring(0, cursorPosition) + text.Substring(cursorPosition + 1, endPos - cursorPosition - 1) + text.Substring(endPos + 1);
cursorPosition = endPos - 1;
parsedText = text.Substring(0, cursorPosition) + new string(' ', endPos - cursorPosition) + text.Substring(endPos + 1);
editorText = text.Substring(0, cursorPosition) + text.Substring(cursorPosition + 1, endPos - cursorPosition - 1) + text.Substring(endPos + 1);
cursorPosition = endPos - 1;
}
var doc = new ReadOnlyDocument(editorText);
IProjectContent pctx = new CSharpProjectContent();
var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value, systemXmlLinq.Value };
if (references != null)
refs.AddRange (references);
pctx = pctx.AddAssemblyReferences(refs);
var syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
syntaxTree.Freeze();
var unresolvedFile = syntaxTree.ToTypeSystem();
pctx = pctx.AddOrUpdateFiles(unresolvedFile);
IProjectContent pctx;
SyntaxTree syntaxTree;
CSharpUnresolvedFile unresolvedFile;
CreateCompilation (parsedText, out pctx, out syntaxTree, out unresolvedFile, true, references);
var cmp = pctx.CreateCompilation();
var loc = cursorPosition > 0 ? doc.GetLocation(cursorPosition) : new TextLocation (1, 1);
var rctx = new CSharpTypeResolveContext(cmp.MainAssembly);

1
ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

@ -394,6 +394,7 @@ @@ -394,6 +394,7 @@
<Compile Include="CSharp\CodeIssues\SimplifyAnonymousMethodToDelegateIssueTests.cs" />
<Compile Include="CSharp\CodeIssues\CS0127ReturnMustNotBeFollowedByAnyExpressionTests.cs" />
<Compile Include="CSharp\Analysis\SemanticHighlightingTests.cs" />
<Compile Include="CSharp\Analysis\AbiComparerTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\cecil\Mono.Cecil.csproj">

Loading…
Cancel
Save