public CompareItemsContext(MetadataItemBase oldItem, MetadataItemBase newItem, AssemblyFamily newAssemblyFamily, List <BreakingChangeBase> breakingChanges, MetadataItemBase additionalInfo = null)
 {
     this.OldItem           = oldItem;
     this.NewItem           = newItem;
     this.NewAssemblyFamily = newAssemblyFamily;
     this.BreakingChanges   = breakingChanges;
     this.AdditionalInfo    = additionalInfo;
 }
Example #2
0
        private AssemblyData[] ReadAssemblyDataCollection(int assemblyCount)
        {
            var family = new AssemblyFamily();

            var loadedAssemblies = new AssemblyData[assemblyCount];

            for (int i = 0; i < assemblyCount; i++)
            {
                loadedAssemblies[i] = ReadAssemblyData(family);
            }

            return(loadedAssemblies);
        }
Example #3
0
        private AssemblyData ReadAssemblyData(AssemblyFamily family)
        {
            var fullName  = _reader.ReadString();
            var typeCount = _reader.ReadInt32();

            var assemblyData = new AssemblyData(null, fullName, null, null, null); // TODO_Serialize: pass the names here

            //for (int i = 0; i < typeCount; i++)
            //    assemblyData.AddType(this.ReadTypeDataBase(assemblyData));

            family.Add(assemblyData);
            return(assemblyData);
        }
        public void TypeForwardingTest()
        {
            var foo = typeof(older::TypeForwardingOld.SourceType).Assembly.FullName;

            var oldFamily = new AssemblyFamily {
                AssemblyData.FromAssembly(typeof(older::TypeForwardingOld.SourceType).Assembly)
            };
            var newFamily = new AssemblyFamily
            {
                AssemblyData.FromAssembly(typeof(newer::TypeForwardingOld.SourceType).Assembly),
                AssemblyData.FromAssembly(typeof(newer::TypeForwardingOld.TargetType1).Assembly)
            };
            var breakingChanges = MetadataComparer.CompareAssemblyFamilies(oldFamily, newFamily);

            Assert.AreEqual(0, breakingChanges.Count, "There should be no breaking changes when the removed type is forwarded.");
        }
        public void NamespaceRenamedAttributeTest()
        {
            var oldFamily = new AssemblyFamily {
                AssemblyData.FromAssembly(typeof(NamespaceRenamedAttributeOld.Type).Assembly)
            };
            var newFamily = new AssemblyFamily {
                AssemblyData.FromAssembly(typeof(NamespaceRenamedAttributeNew.Type).Assembly)
            };
            var breakingChanges = MetadataComparer.CompareAssemblyFamilies(oldFamily, newFamily);

            // Verify that the two 'Type' classes match up and that the comparer correctly detects the breaking change between them.
            Assert.AreEqual(1, breakingChanges.Count, "There should be a breaking change for the removed IDisposable interface.");
            Assert.AreEqual(BreakingChangeKind.RemovedImplementedInterface, breakingChanges[0].BreakingChangeKind, "There should be a breaking change for the removed IDisposable interface.");

            // However, when reversing the order, we should detect a removed type since the old assembly doesn't have the rename attribute
            breakingChanges = MetadataComparer.CompareAssemblyFamilies(newFamily, oldFamily);
            Assert.AreEqual(1, breakingChanges.Count, "There should be a breaking change for removing a root type.");
            Assert.AreEqual(BreakingChangeKind.RemovedRootType, breakingChanges[0].BreakingChangeKind, "There should be a breaking change for removing a root type.");
        }
        /// <summary>
        /// Compares two <see cref="AssemblyFamily"/> instances and returns the breaking changes introduced in the newer assemblies if their
        /// associated older versions exist in the <paramref name="oldFamily"/>.
        /// </summary>
        public static List <BreakingChangeBase> CompareAssemblyFamilies(AssemblyFamily oldFamily, AssemblyFamily newFamily)
        {
            var breakingChanges = new List <BreakingChangeBase>();

            foreach (var oldAssembly in oldFamily)
            {
                var newAssembly = newFamily.GetEquivalentAssembly(oldAssembly);
                if (newAssembly == null)
                {
                    breakingChanges.Add(new RemovedAssembly(oldAssembly));
                }
                else
                {
                    breakingChanges.AddRange(CompareAssemblies(oldAssembly, newAssembly, newFamily));
                }
            }

            return(breakingChanges);
        }
Example #7
0
        public void TypeForwardingTest()
        {
            var olderSourceType = typeof(older::TypeForwardingOld.SourceType);

            var olderContext = MetadataResolutionContext.CreateFromTypes(olderSourceType);
            var olderFamily  = new AssemblyFamily
            {
                olderContext.GetAssemblyData(olderSourceType.Assembly)
            };

            var newerSourceType = typeof(newer::TypeForwardingOld.SourceType);
            var newerTargetType = typeof(newer::TypeForwardingOld.TargetType1);
            var newerContext    = MetadataResolutionContext.CreateFromTypes(newerSourceType, newerTargetType);
            var newFamily       = new AssemblyFamily
            {
                newerContext.GetAssemblyData(newerSourceType.Assembly),
                newerContext.GetAssemblyData(newerTargetType.Assembly)
            };
            var breakingChanges = MetadataComparer.CompareAssemblyFamilies(olderFamily, newFamily);

            AssertX.Equal(0, breakingChanges.Count, "There should be no breaking changes when the removed type is forwarded.");
        }
Example #8
0
        static void Main(string[] args)
        {
            //var assemblyData = AssemblyData.GetAssemblyData(typeof(AddedAbstractMemberTests).Assembly);
            //var allAssemblies = AssemblyData.GetAllAssemblies();

            //var family = AssemblyFamily.FromDirectory(@"C:\Work\NetAdvantage\DEV\WinForms\2013.2\Source\Build");

            var family1 = AssemblyFamily.FromDirectory(@"C:\Users\miked\Desktop\FeatureDocuments\Research\BreakingChanges\V1");
            var family2 = AssemblyFamily.FromDirectory(@"C:\Users\miked\Desktop\FeatureDocuments\Research\BreakingChanges\V2");

            //using (var fs = File.Create("data.bin"))
            //	new BinaryItemSerializerV1(fs, family1.GetAllReferencedAssemblies().ToArray());

            //using (var fs = File.OpenRead("data.bin"))
            //{
            //	var d = new BinaryItemDeserializerV1(fs);
            //	var l = d.LoadedAssemblies;
            //}

            var breakingChanges = MetadataComparer.CompareAssemblyFamilies(family1, family2);
            //breakingChanges = breakingChanges.OrderBy(b => b.BreakingChangeKind).ToList();

            var formatter = new BreakingChangeRichTextDocumentFormatter();

            foreach (var breakingChange in breakingChanges)
            {
                breakingChange.FormatMessage(formatter);
                formatter.AppendLine();
            }

            formatter.OnDocumentComplete();

            using (var s = File.Create("Out.docx"))
                formatter.Document.SaveToWord(s);

            Process.Start("Out.docx");
        }
        private static List <BreakingChangeBase> CompareMethods(MethodData oldMethodBase, MethodData newMethodBase, AssemblyFamily newAssemblyFamily)
        {
            var breakingChanges = CompareItems(oldMethodBase, newMethodBase, newAssemblyFamily);

            CompareParameters(oldMethodBase, newMethodBase, newAssemblyFamily, breakingChanges);

            if (oldMethodBase.MetadataItemKind == MetadataItemKinds.Method)
            {
                var oldMethod = oldMethodBase;
                var newMethod = newMethodBase;

                if (oldMethod.GenericParameters.Count == newMethod.GenericParameters.Count)
                {
                    for (int i = 0; i < oldMethod.GenericParameters.Count; i++)
                    {
                        breakingChanges.AddRange(CompareGenericTypeParameters(oldMethod.GenericParameters[i], newMethod.GenericParameters[i], newAssemblyFamily));
                    }
                }
            }
            return(breakingChanges);
        }
Example #10
0
 public IsAssignableFromContext(AssemblyFamily newAssemblyFamily, bool isSourceTypeOld, bool onlyReferenceAndIdentityConversions)
 {
     IsSourceTypeOld   = isSourceTypeOld;
     NewAssemblyFamily = newAssemblyFamily;
     OnlyReferenceAndIdentityConversions = onlyReferenceAndIdentityConversions;
 }
        private static bool IsMemberKindChangeAllowed(MemberDataBase oldMember, TypeDefinitionData newType, AssemblyFamily newAssemblyFamily)
        {
            if (oldMember.MetadataItemKind == MetadataItemKinds.Constant ||
                (oldMember.MetadataItemKind == MetadataItemKinds.Field && ((FieldData)oldMember).IsReadOnly) ||
                (oldMember.MetadataItemKind == MetadataItemKinds.Property && oldMember.CanBeOverridden == false))
            {
                var oldTypedMemer    = (TypedMemberDataBase)oldMember;
                var candidateMembers = newType.GetMembers(oldMember.Name).Where(m =>
                                                                                (
                                                                                    m.MetadataItemKind == MetadataItemKinds.Constant ||
                                                                                    m.MetadataItemKind == MetadataItemKinds.Field ||
                                                                                    m.MetadataItemKind == MetadataItemKinds.Property
                                                                                ) &&
                                                                                oldTypedMemer.Accessibility <= m.Accessibility &&
                                                                                m.IsInstance == oldMember.IsInstance &&
                                                                                oldTypedMemer.Type.IsEquivalentToNew(((ITypedItem)m).Type, newAssemblyFamily)
                                                                                ).ToList();

                if (candidateMembers.Count == 0)
                {
                    return(false);
                }

                Debug.Assert(candidateMembers.Count == 1, "There should only be one member with the same name.");

                var newMember = candidateMembers[0];
                if (oldMember.CanRead() && newMember.CanRead() == false)
                {
                    return(false);
                }

                if (oldMember.CanWrite() && newMember.CanWrite() == false)
                {
                    return(false);
                }

                if (newMember is PropertyData newProperty && newProperty.GetMethodAccessibility < oldMember.Accessibility)
                {
                    return(false);
                }

                return(true);
            }

            return(false);
        }
        private static MemberDataBase FindEquivalentMember(MemberDataBase oldMember, DeclaringTypeData newType, AssemblyFamily newAssemblyFamily, bool allowMatchOnNameOnly = true)
        {
            var newMembers = newType.GetMembers(oldMember.Name);

            if (newMembers.Count == 0)
            {
                return(null);
            }

            var candidateMembers = newMembers.Where(m => m.MetadataItemKind == oldMember.MetadataItemKind).ToList();

            foreach (var newMember in candidateMembers)
            {
                if (oldMember.IsEquivalentToNewMember(newMember, newAssemblyFamily))
                {
                    return(newMember);
                }
            }

            // TODO: This could match up a new item to multiple old items. This might need to be improved.
            // If we didn't find any match and the current member has parameters, we should try to match this method up
            // with any new method that has the same required parameters, but new optional parameters as well.
            if (oldMember is IParameterizedItem oldParameterizedMember)
            {
                foreach (var newParameterizedMember in candidateMembers)
                {
                    if (oldParameterizedMember.IsEquivalentToNewMember(newParameterizedMember, newAssemblyFamily, ignoreNewOptionalParameters: true))
                    {
                        return(newParameterizedMember);
                    }
                }
            }

            // If there is only one member, we may still want to return it (for example, if both types have a single method with the same name but
            // a different number of parameters, they are incompatible for normal comparison, but not since there is only one method in both versions
            // we can safely assume they are the "same" and report errors for the number or types of parameters changing.
            if (allowMatchOnNameOnly &&
                candidateMembers.Count == 1 &&
                oldMember.ContainingType.GetMembers(oldMember.Name).Count == 1)
            {
                switch (oldMember.MetadataItemKind)
                {
                case MetadataItemKinds.Constant:
                case MetadataItemKinds.Constructor:
                case MetadataItemKinds.Event:
                case MetadataItemKinds.Field:
                case MetadataItemKinds.Indexer:
                case MetadataItemKinds.Method:
                case MetadataItemKinds.Operator:
                case MetadataItemKinds.Property:
                case MetadataItemKinds.TypeDefinition:
                    return(candidateMembers[0]);
                }
            }

            return(null);
        }
        private static MemberDataBase FindEquivalentMemberInClassHierarchy(MemberDataBase oldMember, DeclaringTypeData newType, AssemblyFamily newAssemblyFamily)
        {
            var allowMatchOnNameOnly = true;
            var current = newType;

            while (current != null)
            {
                var newMember = FindEquivalentMember(oldMember, current, newAssemblyFamily, allowMatchOnNameOnly);
                if (newMember != null)
                {
                    return(newMember);
                }

                current = current.BaseType;
                allowMatchOnNameOnly = false;
            }

            return(null);
        }
        private static List <BreakingChangeBase> CompareTypes(TypeData oldTypeBase, TypeData newTypeBase, AssemblyFamily newAssemblyFamily)
        {
            var oldType = (TypeDefinitionData)oldTypeBase;
            var newType = (TypeDefinitionData)newTypeBase;

            var breakingChanges = CompareItems(oldType, newType, newAssemblyFamily);

            if (oldType.GenericParameters.Count == newType.GenericParameters.Count)
            {
                for (int i = 0; i < oldType.GenericParameters.Count; i++)
                {
                    breakingChanges.AddRange(CompareGenericTypeParameters(oldType.GenericParameters[i], newType.GenericParameters[i], newAssemblyFamily));
                }
            }

            var hasExternallyVisibleConstructor = oldType.GetMembers(".ctor").Count != 0;

            if (oldType.TypeKind != TypeKind.Delegate)
            {
                foreach (var oldMember in oldType.GetMembers())
                {
                    MemberDataBase newMember;
                    if (oldType.TypeKind == TypeKind.Class && oldMember.IsOverride == false)
                    {
                        newMember = FindEquivalentMemberInClassHierarchy(oldMember, newType, newAssemblyFamily);
                    }
                    else
                    {
                        newMember = FindEquivalentMember(oldMember, newType, newAssemblyFamily);
                    }

                    if (newMember != null)
                    {
                        breakingChanges.AddRange(CompareMembers(oldMember, newMember, newAssemblyFamily));
                        continue;
                    }

                    if (IsMemberKindChangeAllowed(oldMember, newType, newAssemblyFamily))
                    {
                        continue;
                    }

                    if (oldMember.IsOverride)
                    {
                        if (hasExternallyVisibleConstructor)
                        {
                            var baseMember = oldMember.GetBaseMember();
                            if (baseMember.IsAbstract)
                            {
                                breakingChanges.Add(new RemovedOverrideOfAbstractMember(oldMember, newType));
                            }
                        }

                        continue;
                    }

                    breakingChanges.Add(new RemovedMember(oldMember, newType));
                }
            }

            switch (oldType.TypeKind)
            {
            case TypeKind.Class:
                foreach (var newMember in newType.GetMembers())
                {
                    if (newMember.IsAbstract)
                    {
                        var oldMember = FindEquivalentMemberInClassHierarchy(newMember, oldType, newAssemblyFamily);
                        if (oldMember == null)
                        {
                            breakingChanges.Add(new AddedAbstractMember(newMember));
                        }
                    }
                    else if (newType.IsSealed == false && newMember.IsSealed && newMember.IsOverride)
                    {
                        // If the sealed class contains a sealed override member, find the original equivalent member and see if derived
                        // classes would have been able to override the member previously.
                        var oldMember = FindEquivalentMemberInClassHierarchy(newMember, oldType, newAssemblyFamily);
                        if (oldMember != null && oldMember.CanBeOverridden)
                        {
                            breakingChanges.Add(new SealedMember(oldMember, newMember));
                        }
                    }
                }
                break;

            case TypeKind.Interface:
                foreach (var newMember in newType.GetMembers())
                {
                    var oldMember = FindEquivalentMember(newMember, oldType, newAssemblyFamily);
                    if (oldMember == null)
                    {
                        breakingChanges.Add(new AddedInterfaceMember(newMember));
                    }
                }
                break;

            case TypeKind.Delegate:
                CompareParameters(oldType, newType, newAssemblyFamily, breakingChanges);
                break;
            }
            return(breakingChanges);
        }
        private static List <BreakingChangeBase> CompareParameters(ParameterData oldParameter, ParameterData newParameter, AssemblyFamily newAssemblyFamily, MetadataItemBase declaringMember)
        {
            var breakingChanges = CompareItems(oldParameter, newParameter, newAssemblyFamily, declaringMember);

            // If a parameter is changed completely, it will most likely have a new type and name. We should only warn about the type change.
            var typeChanges = new HashSet <ParameterData>(
                breakingChanges.OfType <ChangedParameterType>().Select(b => b.NewParameter)
                );

            breakingChanges.RemoveAll(b => b.BreakingChangeKind == BreakingChangeKind.ChangedParameterName && typeChanges.Contains((ParameterData)b.NewItem));

            return(breakingChanges);
        }
 private static void CompareParameters(IParameterizedItem oldParameterizedItem, IParameterizedItem newParameterizedItem, AssemblyFamily newAssemblyFamily, List <BreakingChangeBase> breakingChanges)
 {
     if (oldParameterizedItem.Parameters.Count == newParameterizedItem.Parameters.Count)
     {
         for (int i = 0; i < oldParameterizedItem.Parameters.Count; i++)
         {
             breakingChanges.AddRange(CompareParameters(oldParameterizedItem.Parameters[i], newParameterizedItem.Parameters[i], newAssemblyFamily, (MetadataItemBase)newParameterizedItem));
         }
     }
 }
        private static IEnumerable <BreakingChangeBase> CompareMembers(MemberDataBase oldMember, MemberDataBase newMember, AssemblyFamily newAssemblyFamily)
        {
            switch (oldMember.MetadataItemKind)
            {
            case MetadataItemKinds.Constant:
            case MetadataItemKinds.Event:
            case MetadataItemKinds.Field:
            case MetadataItemKinds.Property:
                return(CompareItems(oldMember, newMember, newAssemblyFamily));

            case MetadataItemKinds.Indexer:
                return(CompareIndexers((IndexerData)oldMember, (IndexerData)newMember, newAssemblyFamily));

            case MetadataItemKinds.Constructor:
            case MetadataItemKinds.Operator:
            {
                var breakingChanges = CompareItems(oldMember, newMember, newAssemblyFamily);
                CompareParameters((IParameterizedItem)oldMember, (IParameterizedItem)newMember, newAssemblyFamily, breakingChanges);
                return(breakingChanges);
            }

            case MetadataItemKinds.Method:
                return(CompareMethods((MethodData)oldMember, (MethodData)newMember, newAssemblyFamily));

            case MetadataItemKinds.TypeDefinition:
                return(CompareTypes((TypeDefinitionData)oldMember, (TypeDefinitionData)newMember, newAssemblyFamily));

            case MetadataItemKinds.Assembly:
            case MetadataItemKinds.GenericTypeParameter:
            case MetadataItemKinds.Parameter:
                Debug.Fail("This is not a member: " + oldMember.MetadataItemKind);
                break;

            default:
                Debug.Fail("Unknown MetadataItemKinds: " + oldMember.MetadataItemKind);
                break;
            }

            return(_noBreakingChanges);
        }
        private static List <BreakingChangeBase> CompareItems(MetadataItemBase oldItem, MetadataItemBase newItem, AssemblyFamily newAssemblyFamily, MetadataItemBase additionalInfo = null)
        {
            var breakingChanges = new List <BreakingChangeBase>();
            var context         = new CompareItemsContext(oldItem, newItem, newAssemblyFamily, breakingChanges, additionalInfo);

            Debug.Assert(oldItem.MetadataItemKind == newItem.MetadataItemKind, "The items are not the same kind.");
            var definitions = _definitionsByKind[oldItem.MetadataItemKind];

            foreach (var definition in definitions)
            {
                definition.CompareItems(context);
            }

            return(breakingChanges);
        }
        private static List <BreakingChangeBase> CompareIndexers(IndexerData oldIndexer, IndexerData newIndexer, AssemblyFamily newAssemblyFamily)
        {
            var breakingChanges = CompareItems(oldIndexer, newIndexer, newAssemblyFamily);

            CompareParameters(oldIndexer, newIndexer, newAssemblyFamily, breakingChanges);
            return(breakingChanges);
        }
 private static List <BreakingChangeBase> CompareGenericTypeParameters(GenericTypeParameterData oldGenericTypeParameter, GenericTypeParameterData newGenericTypeParameter, AssemblyFamily newAssemblyFamily) =>
 CompareItems(oldGenericTypeParameter, newGenericTypeParameter, newAssemblyFamily);
        private static List <BreakingChangeBase> CompareAssemblies(AssemblyData oldAssembly, AssemblyData newAssembly, AssemblyFamily newFamily)
        {
            var breakingChanges = new List <BreakingChangeBase>();

            foreach (var oldType in oldAssembly.GetNonNestedTypeDefinitions())
            {
                var newType = oldType.GetEquivalentNewType(newFamily);
                if (newType == null)
                {
                    breakingChanges.Add(new RemovedRootType(oldType));
                }
                else
                {
                    breakingChanges.AddRange(CompareTypes(oldType, newType, newFamily));
                }
            }
            return(breakingChanges);
        }