diff --git a/BuildTools/appveyor-install.ps1 b/BuildTools/appveyor-install.ps1 index 9897ab827..db3950f43 100644 --- a/BuildTools/appveyor-install.ps1 +++ b/BuildTools/appveyor-install.ps1 @@ -3,6 +3,7 @@ $ErrorActionPreference = "Stop" $baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463"; $baseCommitRev = 1; +# make sure this list matches artifacts-only branches list in appveyor.yml! $masterBranches = @("master", "3.2.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; diff --git a/BuildTools/update-assemblyinfo.ps1 b/BuildTools/update-assemblyinfo.ps1 index b5fbe0aba..e304522a8 100644 --- a/BuildTools/update-assemblyinfo.ps1 +++ b/BuildTools/update-assemblyinfo.ps1 @@ -3,6 +3,7 @@ $baseCommit = "d779383cb85003d6dabeb976f0845631e07bf463"; $baseCommitRev = 1; +# make sure this list matches artifacts-only branches list in appveyor.yml! $masterBranches = @("master", "3.2.x"); $globalAssemblyInfoTemplateFile = "ILSpy/Properties/AssemblyInfo.template.cs"; diff --git a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs index 936df3910..cb1620dbd 100644 --- a/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs +++ b/ICSharpCode.Decompiler.Tests/TypeSystem/TypeSystemLoaderTests.cs @@ -106,7 +106,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(method.IsVirtual); Assert.IsFalse(method.IsStatic); Assert.AreEqual(0, method.Parameters.Count); - Assert.AreEqual(0, method.Attributes.Count); + Assert.AreEqual(0, method.GetAttributes().Count()); Assert.IsTrue(method.HasBody); Assert.IsNull(method.AccessorOwner); } @@ -117,7 +117,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem ITypeDefinition testClass = GetTypeDefinition(typeof(DynamicTest)); Assert.AreEqual(SpecialType.Dynamic, testClass.Fields.Single(f => f.Name == "DynamicField").ReturnType); Assert.AreEqual(SpecialType.Dynamic, testClass.Properties.Single().ReturnType); - Assert.AreEqual(0, testClass.Properties.Single().Attributes.Count); + Assert.AreEqual(0, testClass.Properties.Single().GetAttributes().Count()); } [Test] @@ -153,7 +153,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem { ITypeDefinition testClass = GetTypeDefinition(typeof(DynamicTest)); IMethod m1 = testClass.Methods.Single(me => me.Name == "DynamicGenerics1"); - Assert.AreEqual(0, m1.Parameters[0].Attributes.Count); + Assert.AreEqual(0, m1.Parameters[0].GetAttributes().Count()); } [Test] @@ -550,7 +550,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem [Test] public void SerializableAttribute() { - IAttribute attr = GetTypeDefinition(typeof(NonCustomAttributes)).Attributes.Single(); + IAttribute attr = GetTypeDefinition(typeof(NonCustomAttributes)).GetAttributes().Single(); Assert.AreEqual("System.SerializableAttribute", attr.AttributeType.FullName); } @@ -558,13 +558,13 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void NonSerializedAttribute() { IField field = GetTypeDefinition(typeof(NonCustomAttributes)).Fields.Single(f => f.Name == "NonSerializedField"); - Assert.AreEqual("System.NonSerializedAttribute", field.Attributes.Single().AttributeType.FullName); + Assert.AreEqual("System.NonSerializedAttribute", field.GetAttributes().Single().AttributeType.FullName); } [Test] public void ExplicitStructLayoutAttribute() { - IAttribute attr = GetTypeDefinition(typeof(ExplicitFieldLayoutStruct)).Attributes.Single(); + IAttribute attr = GetTypeDefinition(typeof(ExplicitFieldLayoutStruct)).GetAttributes().Single(); Assert.AreEqual("System.Runtime.InteropServices.StructLayoutAttribute", attr.AttributeType.FullName); var arg1 = attr.FixedArguments.Single(); Assert.AreEqual("System.Runtime.InteropServices.LayoutKind", arg1.Type.FullName); @@ -585,14 +585,14 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void FieldOffsetAttribute() { IField field = GetTypeDefinition(typeof(ExplicitFieldLayoutStruct)).Fields.Single(f => f.Name == "Field0"); - Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.Attributes.Single().AttributeType.FullName); - var arg = field.Attributes.Single().FixedArguments.Single(); + Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.GetAttributes().Single().AttributeType.FullName); + var arg = field.GetAttributes().Single().FixedArguments.Single(); Assert.AreEqual("System.Int32", arg.Type.FullName); Assert.AreEqual(0, arg.Value); field = GetTypeDefinition(typeof(ExplicitFieldLayoutStruct)).Fields.Single(f => f.Name == "Field100"); - Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.Attributes.Single().AttributeType.FullName); - arg = field.Attributes.Single().FixedArguments.Single(); + Assert.AreEqual("System.Runtime.InteropServices.FieldOffsetAttribute", field.GetAttributes().Single().AttributeType.FullName); + arg = field.GetAttributes().Single().FixedArguments.Single(); Assert.AreEqual("System.Int32", arg.Type.FullName); Assert.AreEqual(100, arg.Value); } @@ -601,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void DllImportAttribute() { IMethod method = GetTypeDefinition(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod"); - IAttribute dllImport = method.Attributes.Single(); + IAttribute dllImport = method.GetAttributes().Single(); Assert.AreEqual("System.Runtime.InteropServices.DllImportAttribute", dllImport.AttributeType.FullName); Assert.AreEqual("unmanaged.dll", dllImport.FixedArguments[0].Value); Assert.AreEqual((int)CharSet.Unicode, dllImport.NamedArguments.Single().Value); @@ -613,16 +613,17 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem IParameter p = GetTypeDefinition(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod").Parameters.Single(); Assert.IsTrue(p.IsRef); Assert.IsFalse(p.IsOut); - Assert.AreEqual(2, p.Attributes.Count); - Assert.AreEqual("System.Runtime.InteropServices.InAttribute", p.Attributes[0].AttributeType.FullName); - Assert.AreEqual("System.Runtime.InteropServices.OutAttribute", p.Attributes[1].AttributeType.FullName); + var attr = p.GetAttributes().ToList(); + Assert.AreEqual(2, attr.Count); + Assert.AreEqual("System.Runtime.InteropServices.InAttribute", attr[0].AttributeType.FullName); + Assert.AreEqual("System.Runtime.InteropServices.OutAttribute", attr[1].AttributeType.FullName); } [Test] public void MarshalAsAttributeOnMethod() { IMethod method = GetTypeDefinition(typeof(NonCustomAttributes)).Methods.Single(m => m.Name == "DllMethod"); - IAttribute marshalAs = method.ReturnTypeAttributes.Single(); + IAttribute marshalAs = method.GetReturnTypeAttributes().Single(); Assert.AreEqual((int)UnmanagedType.Bool, marshalAs.FixedArguments.Single().Value); } @@ -633,7 +634,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsOptional); Assert.IsFalse(p.IsRef); Assert.IsTrue(p.IsOut); - Assert.AreEqual(0, p.Attributes.Count); + Assert.AreEqual(0, p.GetAttributes().Count()); Assert.IsTrue(p.Type.Kind == TypeKind.ByReference); } @@ -645,7 +646,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsRef); Assert.IsFalse(p.IsOut); Assert.IsTrue(p.IsParams); - Assert.AreEqual(0, p.Attributes.Count); + Assert.AreEqual(0, p.GetAttributes().Count()); Assert.IsTrue(p.Type.Kind == TypeKind.Array); } @@ -657,7 +658,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsRef); Assert.IsFalse(p.IsOut); Assert.IsFalse(p.IsParams); - Assert.AreEqual(0, p.Attributes.Count); + Assert.AreEqual(0, p.GetAttributes().Count()); Assert.AreEqual(4, p.ConstantValue); } @@ -670,7 +671,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsOut); Assert.IsFalse(p.IsParams); // explicit optional parameter appears in type system if it's read from C#, but not when read from IL - //Assert.AreEqual(1, p.Attributes.Count); + //Assert.AreEqual(1, p.GetAttributes().Count()); } [Test] @@ -681,7 +682,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsRef); Assert.IsFalse(p.IsOut); Assert.IsFalse(p.IsParams); - Assert.AreEqual(0, p.Attributes.Count); + Assert.AreEqual(0, p.GetAttributes().Count()); Assert.AreEqual((int)StringComparison.OrdinalIgnoreCase, p.ConstantValue); } @@ -693,7 +694,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem Assert.IsFalse(p.IsRef); Assert.IsFalse(p.IsOut); Assert.IsFalse(p.IsParams); - Assert.AreEqual(0, p.Attributes.Count); + Assert.AreEqual(0, p.GetAttributes().Count()); Assert.IsNull(p.ConstantValue); } @@ -781,7 +782,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem { ITypeDefinition type = GetTypeDefinition(typeof(IAssemblyEnum)); // [ComImport] - Assert.AreEqual(1, type.Attributes.Count(a => a.AttributeType.FullName == typeof(ComImportAttribute).FullName)); + Assert.AreEqual(1, type.GetAttributes().Count(a => a.AttributeType.FullName == typeof(ComImportAttribute).FullName)); IMethod m = type.Methods.Single(); Assert.AreEqual("GetNextAssembly", m.Name); @@ -892,7 +893,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem CustomAttributeTypedArgument GetParamsAttributeArgument(int index) { ITypeDefinition type = GetTypeDefinition(typeof(ParamsAttribute)); - var arr = (AttributeArray)type.Attributes.Single().FixedArguments.Single().Value; + var arr = (AttributeArray)type.GetAttributes().Single().FixedArguments.Single().Value; Assert.AreEqual(5, arr.Length); return arr[index]; } @@ -942,7 +943,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem { ITypeDefinition type = GetTypeDefinition(typeof(ParamsAttribute)); IProperty prop = type.Properties.Single(p => p.Name == "Property"); - var attr = prop.Attributes.Single(); + var attr = prop.GetAttributes().Single(); Assert.AreEqual(type, attr.AttributeType); var elements = (AttributeArray)attr.FixedArguments.Single().Value; @@ -959,15 +960,15 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem { ITypeDefinition type = GetTypeDefinition(typeof(ParamsAttribute)); IProperty prop = type.Properties.Single(p => p.Name == "Property"); - Assert.AreEqual(0, prop.Getter.Attributes.Count); - Assert.AreEqual(1, prop.Getter.ReturnTypeAttributes.Count); + Assert.AreEqual(0, prop.Getter.GetAttributes().Count()); + Assert.AreEqual(1, prop.Getter.GetReturnTypeAttributes().Count()); } [Test] public void DoubleAttribute_ImplicitNumericConversion() { ITypeDefinition type = GetTypeDefinition(typeof(DoubleAttribute)); - var arg = type.Attributes.Single().FixedArguments.Single(); + var arg = type.GetAttributes().Single().FixedArguments.Single(); Assert.AreEqual("System.Double", arg.Type.ReflectionName); Assert.AreEqual(1.0, arg.Value); } @@ -1397,7 +1398,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem var f = type.GetFields().Single(x => x.Name == name); Assert.IsTrue(f.IsConst); Assert.AreEqual(expected, f.ConstantValue); - Assert.AreEqual(0, f.Attributes.Count); + Assert.AreEqual(0, f.GetAttributes().Count()); } [Test] @@ -1518,33 +1519,33 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem var type = GetTypeDefinition(typeof(ClassWithAttributesUsingNestedMembers)); var inner = type.GetNestedTypes().Single(t => t.Name == "Inner"); var myAttribute = type.GetNestedTypes().Single(t => t.Name == "MyAttribute"); - var typeTypeTestAttr = type.Attributes.Single(a => a.AttributeType.Name == "TypeTestAttribute"); + var typeTypeTestAttr = type.GetAttributes().Single(a => a.AttributeType.Name == "TypeTestAttribute"); Assert.AreEqual(42, typeTypeTestAttr.FixedArguments[0].Value); Assert.AreEqual(inner, typeTypeTestAttr.FixedArguments[1].Value); - var typeMyAttr = type.Attributes.Single(a => a.AttributeType.Name == "MyAttribute"); + var typeMyAttr = type.GetAttributes().Single(a => a.AttributeType.Name == "MyAttribute"); Assert.AreEqual(myAttribute, typeMyAttr.AttributeType); var prop = type.GetProperties().Single(p => p.Name == "P"); - var propTypeTestAttr = prop.Attributes.Single(a => a.AttributeType.Name == "TypeTestAttribute"); + var propTypeTestAttr = prop.GetAttributes().Single(a => a.AttributeType.Name == "TypeTestAttribute"); Assert.AreEqual(42, propTypeTestAttr.FixedArguments[0].Value); Assert.AreEqual(inner, propTypeTestAttr.FixedArguments[1].Value); - var propMyAttr = prop.Attributes.Single(a => a.AttributeType.Name == "MyAttribute"); + var propMyAttr = prop.GetAttributes().Single(a => a.AttributeType.Name == "MyAttribute"); Assert.AreEqual(myAttribute, propMyAttr.AttributeType); var attributedInner = (ITypeDefinition)type.GetNestedTypes().Single(t => t.Name == "AttributedInner"); - var innerTypeTestAttr = attributedInner.Attributes.Single(a => a.AttributeType.Name == "TypeTestAttribute"); + var innerTypeTestAttr = attributedInner.GetAttributes().Single(a => a.AttributeType.Name == "TypeTestAttribute"); Assert.AreEqual(42, innerTypeTestAttr.FixedArguments[0].Value); Assert.AreEqual(inner, innerTypeTestAttr.FixedArguments[1].Value); - var innerMyAttr = attributedInner.Attributes.Single(a => a.AttributeType.Name == "MyAttribute"); + var innerMyAttr = attributedInner.GetAttributes().Single(a => a.AttributeType.Name == "MyAttribute"); Assert.AreEqual(myAttribute, innerMyAttr.AttributeType); var attributedInner2 = (ITypeDefinition)type.GetNestedTypes().Single(t => t.Name == "AttributedInner2"); var inner2 = attributedInner2.GetNestedTypes().Single(t => t.Name == "Inner"); var myAttribute2 = attributedInner2.GetNestedTypes().Single(t => t.Name == "MyAttribute"); - var inner2TypeTestAttr = attributedInner2.Attributes.Single(a => a.AttributeType.Name == "TypeTestAttribute"); + var inner2TypeTestAttr = attributedInner2.GetAttributes().Single(a => a.AttributeType.Name == "TypeTestAttribute"); Assert.AreEqual(43, inner2TypeTestAttr.FixedArguments[0].Value); Assert.AreEqual(inner2, inner2TypeTestAttr.FixedArguments[1].Value); - var inner2MyAttr = attributedInner2.Attributes.Single(a => a.AttributeType.Name == "MyAttribute"); + var inner2MyAttr = attributedInner2.GetAttributes().Single(a => a.AttributeType.Name == "MyAttribute"); Assert.AreEqual(myAttribute2, inner2MyAttr.AttributeType); } @@ -1552,7 +1553,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void ClassWithAttributeOnTypeParameter() { var tp = GetTypeDefinition(typeof(ClassWithAttributeOnTypeParameter<>)).TypeParameters.Single(); - var attr = tp.Attributes.Single(); + var attr = tp.GetAttributes().Single(); Assert.AreEqual("DoubleAttribute", attr.AttributeType.Name); } @@ -1658,7 +1659,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void Void_SerializableAttribute() { ITypeDefinition c = compilation.FindType(typeof(void)).GetDefinition(); - var attr = c.Attributes.Single(a => a.AttributeType.FullName == "System.SerializableAttribute"); + var attr = c.GetAttributes().Single(a => a.AttributeType.FullName == "System.SerializableAttribute"); Assert.AreEqual(0, attr.Constructor.Parameters.Count); Assert.AreEqual(0, attr.FixedArguments.Length); Assert.AreEqual(0, attr.NamedArguments.Length); @@ -1668,7 +1669,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void Void_StructLayoutAttribute() { ITypeDefinition c = compilation.FindType(typeof(void)).GetDefinition(); - var attr = c.Attributes.Single(a => a.AttributeType.FullName == "System.Runtime.InteropServices.StructLayoutAttribute"); + var attr = c.GetAttributes().Single(a => a.AttributeType.FullName == "System.Runtime.InteropServices.StructLayoutAttribute"); Assert.AreEqual(1, attr.Constructor.Parameters.Count); Assert.AreEqual(1, attr.FixedArguments.Length); Assert.AreEqual(0, attr.FixedArguments[0].Value); @@ -1681,7 +1682,7 @@ namespace ICSharpCode.Decompiler.Tests.TypeSystem public void Void_ComVisibleAttribute() { ITypeDefinition c = compilation.FindType(typeof(void)).GetDefinition(); - var attr = c.Attributes.Single(a => a.AttributeType.FullName == "System.Runtime.InteropServices.ComVisibleAttribute"); + var attr = c.GetAttributes().Single(a => a.AttributeType.FullName == "System.Runtime.InteropServices.ComVisibleAttribute"); Assert.AreEqual(1, attr.Constructor.Parameters.Count); Assert.AreEqual(1, attr.FixedArguments.Length); Assert.AreEqual(true, attr.FixedArguments[0].Value); diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 36ebde47b..24f460238 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -794,7 +794,7 @@ namespace ICSharpCode.Decompiler.CSharp EnumValueDisplayMode DetectBestEnumValueDisplayMode(ITypeDefinition typeDef, PEFile module) { - if (typeDef.GetAttribute(KnownAttribute.Flags) != null) + if (typeDef.HasAttribute(KnownAttribute.Flags, inherit: false)) return EnumValueDisplayMode.All; bool first = true; long firstValue = 0, previousValue = 0; @@ -997,7 +997,7 @@ namespace ICSharpCode.Decompiler.CSharp void AddDefinesForConditionalAttributes(ILFunction function, DecompileRun decompileRun, ITypeResolveContext decompilationContext) { foreach (var call in function.Descendants.OfType()) { - var attr = call.Method.GetAttribute(KnownAttribute.Conditional); + var attr = call.Method.GetAttribute(KnownAttribute.Conditional, inherit: true); var symbolName = attr?.FixedArguments.FirstOrDefault().Value as string; if (symbolName == null || !decompileRun.DefinedSymbols.Add(symbolName)) continue; @@ -1014,12 +1014,12 @@ namespace ICSharpCode.Decompiler.CSharp long initValue = (long)CSharpPrimitiveCast.Cast(TypeCode.Int64, field.ConstantValue, false); enumDec.Initializer = typeSystemAstBuilder.ConvertConstantValue(decompilationContext.CurrentTypeDefinition.EnumUnderlyingType, field.ConstantValue); if (enumDec.Initializer is PrimitiveExpression primitive - && (decompilationContext.CurrentTypeDefinition.Attributes.Any(a => a.AttributeType.FullName == "System.FlagsAttribute") + && (decompilationContext.CurrentTypeDefinition.HasAttribute(KnownAttribute.Flags) || (initValue > 9 && ((initValue & (initValue - 1)) == 0 || (initValue & (initValue + 1)) == 0)))) { primitive.SetValue(initValue, $"0x{initValue:X}"); } - enumDec.Attributes.AddRange(field.Attributes.Select(a => new AttributeSection(typeSystemAstBuilder.ConvertAttribute(a)))); + enumDec.Attributes.AddRange(field.GetAttributes().Select(a => new AttributeSection(typeSystemAstBuilder.ConvertAttribute(a)))); enumDec.AddAnnotation(new MemberResolveResult(null, field)); return enumDec; } diff --git a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs index ca03abb7a..99c9bbb17 100644 --- a/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs +++ b/ICSharpCode.Decompiler/CSharp/RequiredNamespaceCollector.cs @@ -40,10 +40,10 @@ namespace ICSharpCode.Decompiler.CSharp switch (entity) { case ITypeDefinition td: namespaces.Add(td.Namespace); - HandleAttributes(td.Attributes, namespaces); + HandleAttributes(td.GetAttributes(), namespaces); foreach (var typeParam in td.TypeParameters) { - HandleAttributes(typeParam.Attributes, namespaces); + HandleAttributes(typeParam.GetAttributes(), namespaces); } foreach (var baseType in td.DirectBaseTypes) { @@ -71,19 +71,19 @@ namespace ICSharpCode.Decompiler.CSharp } break; case IField field: - HandleAttributes(field.Attributes, namespaces); + HandleAttributes(field.GetAttributes(), namespaces); CollectNamespacesForTypeReference(field.ReturnType, namespaces); break; case IMethod method: - HandleAttributes(method.Attributes, namespaces); - HandleAttributes(method.ReturnTypeAttributes, namespaces); + HandleAttributes(method.GetAttributes(), namespaces); + HandleAttributes(method.GetReturnTypeAttributes(), namespaces); CollectNamespacesForTypeReference(method.ReturnType, namespaces); foreach (var param in method.Parameters) { - HandleAttributes(param.Attributes, namespaces); + HandleAttributes(param.GetAttributes(), namespaces); CollectNamespacesForTypeReference(param.Type, namespaces); } foreach (var typeParam in method.TypeParameters) { - HandleAttributes(typeParam.Attributes, namespaces); + HandleAttributes(typeParam.GetAttributes(), namespaces); } if (!method.MetadataToken.IsNil && method.HasBody) { var reader = typeSystem.ModuleDefinition.Reader; @@ -93,12 +93,12 @@ namespace ICSharpCode.Decompiler.CSharp } break; case IProperty property: - HandleAttributes(property.Attributes, namespaces); + HandleAttributes(property.GetAttributes(), namespaces); CollectNamespaces(property.Getter, typeSystem, namespaces); CollectNamespaces(property.Setter, typeSystem, namespaces); break; case IEvent @event: - HandleAttributes(@event.Attributes, namespaces); + HandleAttributes(@event.GetAttributes(), namespaces); CollectNamespaces(@event.AddAccessor, typeSystem, namespaces); CollectNamespaces(@event.RemoveAccessor, typeSystem, namespaces); break; diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index eba09ec69..066fe2b51 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -150,8 +150,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver get { return SymbolKind.Operator; } } - IReadOnlyList IEntity.Attributes { - get { return EmptyList.Instance; } + IEnumerable IEntity.GetAttributes() + { + return EmptyList.Instance; } Accessibility IEntity.Accessibility { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 1a91fc33d..6642448e9 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -1741,7 +1741,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver void CheckForEnumerableInterface(ResolveResult expression, out IType collectionType, out IType enumeratorType, out IType elementType, out ResolveResult getEnumeratorInvocation) { bool? isGeneric; - elementType = GetElementTypeFromIEnumerable(expression.Type, compilation, false, out isGeneric); + elementType = expression.Type.GetElementTypeFromIEnumerable(compilation, false, out isGeneric); if (isGeneric == true) { ITypeDefinition enumerableOfT = compilation.FindType(KnownTypeCode.IEnumerableOfT).GetDefinition(); if (enumerableOfT != null) @@ -1765,33 +1765,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver getEnumeratorInvocation = ResolveMemberAccess(getEnumeratorInvocation, "GetEnumerator", EmptyList.Instance, NameLookupMode.InvocationTarget); getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, new ResolveResult[0]); } - - static IType GetElementTypeFromIEnumerable(IType collectionType, ICompilation compilation, bool allowIEnumerator, out bool? isGeneric) - { - bool foundNonGenericIEnumerable = false; - foreach (IType baseType in collectionType.GetAllBaseTypes()) { - ITypeDefinition baseTypeDef = baseType.GetDefinition(); - if (baseTypeDef != null) { - KnownTypeCode typeCode = baseTypeDef.KnownTypeCode; - if (typeCode == KnownTypeCode.IEnumerableOfT || (allowIEnumerator && typeCode == KnownTypeCode.IEnumeratorOfT)) { - ParameterizedType pt = baseType as ParameterizedType; - if (pt != null) { - isGeneric = true; - return pt.GetTypeArgument(0); - } - } - if (typeCode == KnownTypeCode.IEnumerable || (allowIEnumerator && typeCode == KnownTypeCode.IEnumerator)) - foundNonGenericIEnumerable = true; - } - } - // System.Collections.IEnumerable found in type hierarchy -> Object is element type. - if (foundNonGenericIEnumerable) { - isGeneric = false; - return compilation.FindType(KnownTypeCode.Object); - } - isGeneric = null; - return SpecialType.UnknownType; - } #endregion #region GetExtensionMethods diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs b/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs index f0a37e07a..7ef4758a5 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/ReducedExtensionMethod.cs @@ -136,12 +136,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region IMethod implementation - public IReadOnlyList ReturnTypeAttributes { - get { - return baseMethod.ReturnTypeAttributes; - } - } - public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; @@ -243,11 +237,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - public IReadOnlyList Attributes { - get { - return baseMethod.Attributes; - } - } + IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); + IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); public bool IsStatic { get { diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 8a5f73eda..796864eb9 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -455,7 +455,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax public Attribute ConvertAttribute(IAttribute attribute) { Attribute attr = new Attribute(); - attr.Type = ConvertType(attribute.AttributeType); + attr.Type = ConvertAttributeType(attribute.AttributeType); SimpleType st = attr.Type as SimpleType; MemberType mt = attr.Type as MemberType; if (st != null && st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) { @@ -482,6 +482,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } return attr; } + + private IEnumerable ConvertAttributes(IEnumerable attibutes) + { + return attibutes.Select(a => new AttributeSection(ConvertAttribute(a))); + } + + private IEnumerable ConvertAttributes(IEnumerable attibutes, string target) + { + return attibutes.Select(a => new AttributeSection(ConvertAttribute(a)) { + AttributeTarget = target + }); + } #endregion #region Convert Attribute Type @@ -495,6 +507,28 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax if (type.Name.Length > 9 && type.Name.EndsWith("Attribute", StringComparison.Ordinal)) { shortName = type.Name.Remove(type.Name.Length - 9); } + if (AlwaysUseShortTypeNames) { + switch (astType) { + case SimpleType st: + st.Identifier = shortName; + break; + case MemberType mt: + mt.MemberName = shortName; + break; + } + } else if (resolver != null) { + ApplyShortAttributeNameIfPossible(type, astType, shortName); + } + + if (AddTypeReferenceAnnotations) + astType.AddAnnotation(type); + if (AddResolveResultAnnotations) + astType.AddAnnotation(new TypeResolveResult(type)); + return astType; + } + + private void ApplyShortAttributeNameIfPossible(IType type, AstType astType, string shortName) + { switch (astType) { case SimpleType st: ResolveResult shortRR = null; @@ -529,12 +563,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } break; } - - if (AddTypeReferenceAnnotations) - astType.AddAnnotation(type); - if (AddResolveResultAnnotations) - astType.AddAnnotation(new TypeResolveResult(type)); - return astType; } private bool IsAttributeType(IType type) @@ -792,8 +820,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax bool IsFlagsEnum(ITypeDefinition type) { - IType flagsAttributeType = type.Compilation.FindType(typeof(System.FlagsAttribute)); - return type.GetAttribute(flagsAttributeType) != null; + return type.HasAttribute(KnownAttribute.Flags, inherit: false); } Expression ConvertEnumValue(IType type, long val) @@ -880,7 +907,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.ParameterModifier = ParameterModifier.Params; } if (ShowAttributes) { - decl.Attributes.AddRange (parameter.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(parameter.GetAttributes())); } if (parameter.Type.Kind == TypeKind.ByReference) { // avoid 'out ref' @@ -997,7 +1024,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.ClassType = classType; decl.Modifiers = modifiers; if (ShowAttributes) { - decl.Attributes.AddRange (typeDefinition.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(typeDefinition.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new TypeResolveResult(typeDefinition)); @@ -1034,7 +1061,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } return decl; } - + DelegateDeclaration ConvertDelegate(IMethod invokeMethod, Modifiers modifiers) { ITypeDefinition d = invokeMethod.DeclaringTypeDefinition; @@ -1042,10 +1069,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax DelegateDeclaration decl = new DelegateDeclaration(); decl.Modifiers = modifiers & ~Modifiers.Sealed; if (ShowAttributes) { - decl.Attributes.AddRange (d.Attributes.Select (a => new AttributeSection (ConvertAttribute (a)))); - decl.Attributes.AddRange (invokeMethod.ReturnTypeAttributes.Select ((a) => new AttributeSection (ConvertAttribute (a)) { - AttributeTarget = "return" - })); + decl.Attributes.AddRange(ConvertAttributes(d.GetAttributes())); + decl.Attributes.AddRange(ConvertAttributes(invokeMethod.GetReturnTypeAttributes(), "return")); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new TypeResolveResult(d)); @@ -1091,7 +1116,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.Modifiers = m; } if (ShowAttributes) { - decl.Attributes.AddRange (field.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(field.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, field)); @@ -1115,7 +1140,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } - Accessor ConvertAccessor(IMethod accessor, Accessibility ownerAccessibility, bool addParamterAttribute) + Accessor ConvertAccessor(IMethod accessor, Accessibility ownerAccessibility, bool addParameterAttribute) { if (accessor == null) return Accessor.Null; @@ -1123,14 +1148,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax if (this.ShowAccessibility && accessor.Accessibility != ownerAccessibility) decl.Modifiers = ModifierFromAccessibility(accessor.Accessibility); if (ShowAttributes) { - decl.Attributes.AddRange (accessor.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); - decl.Attributes.AddRange (accessor.ReturnTypeAttributes.Select ((a) => new AttributeSection (ConvertAttribute (a)) { - AttributeTarget = "return" - })); - if (addParamterAttribute && accessor.Parameters.Count > 0) { - decl.Attributes.AddRange (accessor.Parameters.Last ().Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)) { - AttributeTarget = "param" - })); + decl.Attributes.AddRange(ConvertAttributes(accessor.GetAttributes())); + decl.Attributes.AddRange(ConvertAttributes(accessor.GetReturnTypeAttributes(), "return")); + if (addParameterAttribute && accessor.Parameters.Count > 0) { + decl.Attributes.AddRange(ConvertAttributes(accessor.Parameters.Last().GetAttributes(), "param")); } } if (AddResolveResultAnnotations) { @@ -1145,7 +1166,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax PropertyDeclaration decl = new PropertyDeclaration(); decl.Modifiers = GetMemberModifiers(property); if (ShowAttributes) { - decl.Attributes.AddRange (property.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(property.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, property)); @@ -1163,7 +1184,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax IndexerDeclaration decl = new IndexerDeclaration(); decl.Modifiers = GetMemberModifiers(indexer); if (ShowAttributes) { - decl.Attributes.AddRange (indexer.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(indexer.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, indexer)); @@ -1184,7 +1205,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax CustomEventDeclaration decl = new CustomEventDeclaration(); decl.Modifiers = GetMemberModifiers(ev); if (ShowAttributes) { - decl.Attributes.AddRange (ev.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(ev.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, ev)); @@ -1199,7 +1220,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax EventDeclaration decl = new EventDeclaration(); decl.Modifiers = GetMemberModifiers(ev); if (ShowAttributes) { - decl.Attributes.AddRange (ev.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(ev.GetAttributes())); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, ev)); @@ -1215,10 +1236,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax MethodDeclaration decl = new MethodDeclaration(); decl.Modifiers = GetMemberModifiers(method); if (ShowAttributes) { - decl.Attributes.AddRange (method.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); - decl.Attributes.AddRange (method.ReturnTypeAttributes.Select ((a) => new AttributeSection (ConvertAttribute (a)) { - AttributeTarget = "return" - })); + decl.Attributes.AddRange(ConvertAttributes(method.GetAttributes())); + decl.Attributes.AddRange(ConvertAttributes(method.GetReturnTypeAttributes(), "return")); } if (AddResolveResultAnnotations) { decl.AddAnnotation(new MemberResolveResult(null, method)); @@ -1275,7 +1294,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ConstructorDeclaration decl = new ConstructorDeclaration(); decl.Modifiers = GetMemberModifiers(ctor); if (ShowAttributes) - decl.Attributes.AddRange (ctor.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(ctor.GetAttributes())); if (ctor.DeclaringTypeDefinition != null) decl.Name = ctor.DeclaringTypeDefinition.Name; foreach (IParameter p in ctor.Parameters) { @@ -1369,7 +1388,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax decl.Variance = tp.Variance; decl.Name = tp.Name; if (ShowAttributes) - decl.Attributes.AddRange (tp.Attributes.Select ((a) => new AttributeSection (ConvertAttribute (a)))); + decl.Attributes.AddRange(ConvertAttributes(tp.GetAttributes())); return decl; } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index c188a7366..f2efa6d3c 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -507,17 +507,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms propertyDeclaration.Getter.Body = null; propertyDeclaration.Setter.Body = null; - // Add C# 7.3 attributes on backing field: - var attributes = field.Attributes - .Where(a => !attributeTypesToRemoveFromAutoProperties.Any(t => t == a.AttributeType.FullName)) - .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); - if (attributes.Length > 0) { - var section = new AttributeSection { - AttributeTarget = "field" - }; - section.Attributes.AddRange(attributes); - propertyDeclaration.Attributes.Add(section); - } + // Add C# 7.3 attributes on backing field: + var attributes = field.GetAttributes() + .Where(a => !attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName)) + .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); + if (attributes.Length > 0) { + var section = new AttributeSection { + AttributeTarget = "field" + }; + section.Attributes.AddRange(attributes); + propertyDeclaration.Attributes.Add(section); + } } // Since the property instance is not changed, we can continue in the visitor as usual, so return null return null; @@ -788,8 +788,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms IField field = eventDef.DeclaringType.GetFields(f => f.Name == ev.Name, GetMemberOptions.IgnoreInheritedMembers).SingleOrDefault(); if (field != null) { ed.AddAnnotation(field); - var attributes = field.Attributes - .Where(a => !attributeTypesToRemoveFromAutoEvents.Any(t => t == a.AttributeType.FullName)) + var attributes = field.GetAttributes() + .Where(a => !attributeTypesToRemoveFromAutoEvents.Contains(a.AttributeType.FullName)) .Select(context.TypeSystemAstBuilder.ConvertAttribute).ToArray(); if (attributes.Length > 0) { var section = new AttributeSection { diff --git a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs index 272719a21..74c639267 100644 --- a/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs @@ -21,7 +21,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Resources; using System.Threading.Tasks; using System.Xml; using ICSharpCode.Decompiler.CSharp.OutputVisitor; @@ -30,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using System.Threading; +using System.Text; using System.Reflection.PortableExecutable; using System.Reflection.Metadata; using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions; @@ -340,23 +340,40 @@ namespace ICSharpCode.Decompiler.CSharp Stream stream = r.TryOpenStream(); stream.Position = 0; - IEnumerable entries; if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { - if (GetEntries(stream, out entries) && entries.All(e => e.Value is Stream)) { - foreach (var pair in entries) { - string fileName = Path.Combine(((string)pair.Key).Split('/').Select(p => CleanUpFileName(p)).ToArray()); - string dirName = Path.GetDirectoryName(fileName); - if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { - Directory.CreateDirectory(Path.Combine(targetDirectory, dirName)); + bool decodedIntoIndividualFiles; + var individualResources = new List>(); + try { + var resourcesFile = new ResourcesFile(stream); + if (resourcesFile.AllEntriesAreStreams()) { + foreach (var (name, value) in resourcesFile) { + string fileName = Path.Combine(name.Split('/').Select(p => CleanUpFileName(p)).ToArray()); + string dirName = Path.GetDirectoryName(fileName); + if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { + Directory.CreateDirectory(Path.Combine(targetDirectory, dirName)); + } + Stream entryStream = (Stream)value; + entryStream.Position = 0; + individualResources.AddRange( + WriteResourceToFile(Path.Combine(targetDirectory, fileName), (string)name, entryStream)); } - Stream entryStream = (Stream)pair.Value; - entryStream.Position = 0; - WriteResourceToFile(Path.Combine(targetDirectory, fileName), (string)pair.Key, entryStream); + decodedIntoIndividualFiles = true; + } else { + decodedIntoIndividualFiles = false; + } + } catch (BadImageFormatException) { + decodedIntoIndividualFiles = false; + } + if (decodedIntoIndividualFiles) { + foreach (var entry in individualResources) { + yield return entry; } } else { stream.Position = 0; - string fileName = GetFileNameForResource(Path.ChangeExtension(r.Name, ".resource")); - WriteResourceToFile(fileName, r.Name, stream); + string fileName = Path.ChangeExtension(GetFileNameForResource(r.Name), ".resource"); + foreach (var entry in WriteResourceToFile(fileName, r.Name, stream)) { + yield return entry; + } } } else { string fileName = GetFileNameForResource(r.Name); @@ -391,17 +408,6 @@ namespace ICSharpCode.Decompiler.CSharp } return fileName; } - - bool GetEntries(Stream stream, out IEnumerable entries) - { - try { - entries = new ResourceSet(stream).Cast(); - return true; - } catch (ArgumentException) { - entries = null; - return false; - } - } #endregion /// @@ -416,9 +422,21 @@ namespace ICSharpCode.Decompiler.CSharp if (pos > 0) text = text.Substring(0, pos); text = text.Trim(); - foreach (char c in Path.GetInvalidFileNameChars()) - text = text.Replace(c, '-'); - return text; + // Whitelist allowed characters, replace everything else: + StringBuilder b = new StringBuilder(text.Length); + foreach (var c in text) { + if (char.IsLetterOrDigit(c) || c == '-' || c == '_') + b.Append(c); + else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.') + b.Append('.'); // allow dot, but never two in a row + else + b.Append('-'); + if (b.Length >= 64) + break; // limit to 64 chars + } + if (b.Length == 0) + b.Append('-'); + return b.ToString(); } public static string GetPlatformName(Metadata.PEFile module) diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 8198321d7..0cc240f7c 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -372,6 +372,8 @@ + + diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index df5d85f2d..90bfc182f 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -1116,21 +1116,47 @@ namespace ICSharpCode.Decompiler.IL return inst; default: Warn("Expected native int or pointer, but got " + inst.ResultType); - return inst; + return new Conv(inst, PrimitiveType.I, false, Sign.None); } } ILInstruction PopFieldTarget(IField field) { - return field.DeclaringType.IsReferenceType == true ? Pop(StackType.O) : PopPointer(); + switch (field.DeclaringType.IsReferenceType) { + case true: + return Pop(StackType.O); + case false: + return PopPointer(); + default: + // field in unresolved type + if (PeekStackType() == StackType.O) + return Pop(); + else + return PopPointer(); + } } + /// + /// Like PopFieldTarget, but supports ldfld's special behavior for fields of temporary value types. + /// ILInstruction PopLdFldTarget(IField field) { - if (field.DeclaringType.IsReferenceType == true) - return Pop(StackType.O); - - return PeekStackType() == StackType.O ? new AddressOf(Pop()) : PopPointer(); + switch (field.DeclaringType.IsReferenceType) { + case true: + return Pop(StackType.O); + case false: + // field of value type: ldfld can handle temporaries + if (PeekStackType() == StackType.O) + return new AddressOf(Pop()); + else + return PopPointer(); + default: + // field in unresolved type + if (PeekStackType() == StackType.O) + return Pop(StackType.O); + else + return PopPointer(); + } } private ILInstruction Return() diff --git a/ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs b/ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs index 166484078..87917b8d4 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs @@ -25,7 +25,20 @@ namespace ICSharpCode.Decompiler.IL internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); - Debug.Assert(field.DeclaringType.IsReferenceType == true ? (target.ResultType == StackType.O) : (target.ResultType == StackType.I || target.ResultType == StackType.Ref)); + switch (field.DeclaringType.IsReferenceType) { + case true: + Debug.Assert(target.ResultType == StackType.O, + "Class fields can only be accessed with an object on the stack"); + break; + case false: + Debug.Assert(target.ResultType == StackType.I || target.ResultType == StackType.Ref, + "Struct fields can only be accessed with a pointer on the stack"); + break; + case null: + // field of unresolved type + Debug.Assert(target.ResultType == StackType.O || target.ResultType == StackType.I || target.ResultType == StackType.Ref); + break; + } } } } diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs index 77b1f9f54..142923ed5 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs @@ -23,6 +23,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using ICSharpCode.Decompiler.Util; using LightJson.Serialization; namespace ICSharpCode.Decompiler.Metadata @@ -44,7 +45,7 @@ namespace ICSharpCode.Decompiler.Metadata this.Version = parts[1]; this.Type = type; this.Path = path; - this.RuntimeComponents = runtimeComponents ?? new string[0]; + this.RuntimeComponents = runtimeComponents ?? Empty.Array; } } diff --git a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs index 40da69cc1..3bac328ce 100644 --- a/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs +++ b/ICSharpCode.Decompiler/Metadata/DotNetCorePathFinderExtensions.cs @@ -19,6 +19,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; +using ICSharpCode.Decompiler.TypeSystem.Implementation; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; @@ -26,7 +28,11 @@ namespace ICSharpCode.Decompiler.Metadata { public static class DotNetCorePathFinderExtensions { - public static string DetectTargetFrameworkId(this PEReader assembly) + static readonly string RefPathPattern = + @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" + + @"|(NuGetFallbackFolder[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])"; + + public static string DetectTargetFrameworkId(this PEReader assembly, string assemblyPath = null) { if (assembly == null) throw new ArgumentNullException(nameof(assembly)); @@ -44,6 +50,31 @@ namespace ICSharpCode.Decompiler.Metadata } } + // Optionally try to detect target version through assembly path as a fallback (use case: reference assemblies) + if (assemblyPath != null) { + /* + * Detected path patterns (examples): + * + * - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll + * - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll + * - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll + */ + var pathMatch = Regex.Match(assemblyPath, RefPathPattern, + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + if (pathMatch.Success) { + var type = pathMatch.Groups[1].Value; + var version = pathMatch.Groups[2].Value; + + if (type == ".NETFramework") { + return $".NETFramework,Version=v{version}"; + } else if (type.Contains("netcore")) { + return $".NETCoreApp,Version=v{version}"; + } else if (type.Contains("netstandard")) { + return $".NETStandard,Version=v{version}"; + } + } + } + return string.Empty; } } diff --git a/ICSharpCode.Decompiler/NRExtensions.cs b/ICSharpCode.Decompiler/NRExtensions.cs index a2ff9ba22..0a816ad82 100644 --- a/ICSharpCode.Decompiler/NRExtensions.cs +++ b/ICSharpCode.Decompiler/NRExtensions.cs @@ -44,10 +44,7 @@ namespace ICSharpCode.Decompiler public static bool IsCompilerGenerated(this IEntity entity) { if (entity != null) { - foreach (IAttribute a in entity.Attributes) { - if (a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute") - return true; - } + return entity.HasAttribute(KnownAttribute.CompilerGenerated); } return false; } diff --git a/ICSharpCode.Decompiler/TypeSystem/ComHelper.cs b/ICSharpCode.Decompiler/TypeSystem/ComHelper.cs index 5c646dba2..b0064f746 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ComHelper.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ComHelper.cs @@ -33,7 +33,7 @@ namespace ICSharpCode.Decompiler.TypeSystem { return typeDefinition != null && typeDefinition.Kind == TypeKind.Interface - && typeDefinition.GetAttributes(KnownAttribute.ComImport, inherit: false) != null; + && typeDefinition.HasAttribute(KnownAttribute.ComImport, inherit: false); } /// diff --git a/ICSharpCode.Decompiler/TypeSystem/IEntity.cs b/ICSharpCode.Decompiler/TypeSystem/IEntity.cs index 1d6b9db92..fdc1d83f6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IEntity.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IEntity.cs @@ -123,8 +123,9 @@ namespace ICSharpCode.Decompiler.TypeSystem /// /// Gets the attributes on this entity. + /// Does not include inherited attributes. /// - IReadOnlyList Attributes { get; } + IEnumerable GetAttributes(); /// /// Gets the accessibility of this entity. diff --git a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs index 67f982753..d29803a0f 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IMethod.cs @@ -79,7 +79,10 @@ namespace ICSharpCode.Decompiler.TypeSystem /// /// Gets the attributes associated with the return type. (e.g. [return: MarshalAs(...)]) /// - IReadOnlyList ReturnTypeAttributes { get; } + /// + /// Does not include inherited attributes. + /// + IEnumerable GetReturnTypeAttributes(); /// /// Gets the type parameters of this method; or an empty list if the method is not generic. diff --git a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs index f5fca79a3..8b6acb8b6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/IParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/IParameter.cs @@ -63,9 +63,9 @@ namespace ICSharpCode.Decompiler.TypeSystem public interface IParameter : IVariable { /// - /// Gets the list of attributes. + /// Gets the attributes on this parameter. /// - IReadOnlyList Attributes { get; } + IEnumerable GetAttributes(); /// /// Gets whether this parameter is a C# 'ref' parameter. diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs index 78cc617d7..34c530a7b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ITypeParameter.cs @@ -82,9 +82,9 @@ namespace ICSharpCode.Decompiler.TypeSystem new string Name { get; } /// - /// Gets the list of attributes declared on this type parameter. + /// Gets the attributes declared on this type parameter. /// - IReadOnlyList Attributes { get; } + IEnumerable GetAttributes(); /// /// Gets the variance of this type parameter. diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedEntity.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedEntity.cs index 9c01eb5ff..e7f24cdc7 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedEntity.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedEntity.cs @@ -59,6 +59,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } public IReadOnlyList Attributes { get; protected set; } + IEnumerable IEntity.GetAttributes() => Attributes; public bool IsStatic { get { return unresolved.IsStatic; } } public bool IsAbstract { get { return unresolved.IsAbstract; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs index deaf7f4ad..e6c681745 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/AbstractResolvedTypeParameter.cs @@ -71,8 +71,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public int Index { get { return index; } } - - public abstract IReadOnlyList Attributes { get; } + + public abstract IEnumerable GetAttributes(); public VarianceModifier Variance { get { return variance; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs index 435da9076..28d0873d5 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultParameter.cs @@ -72,9 +72,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation get { return owner; } } - public IReadOnlyList Attributes { - get { return attributes; } - } + public IEnumerable GetAttributes() => attributes; public bool IsRef { get { return isRef; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedMethod.cs index 825fc28a9..e1304b298 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedMethod.cs @@ -170,6 +170,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IReadOnlyList Parameters { get; private set; } public IReadOnlyList ReturnTypeAttributes { get; private set; } + IEnumerable IMethod.GetReturnTypeAttributes() => ReturnTypeAttributes; public IReadOnlyList TypeParameters { get; private set; } public IReadOnlyList TypeArguments { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs index cc4eaa992..a2ca1c902 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeDefinition.cs @@ -105,7 +105,9 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation return LazyInit.GetOrSet(ref this.attributes, result); } } - + + public IEnumerable GetAttributes() => Attributes; + public System.Reflection.Metadata.EntityHandle MetadataToken => parts[0].MetadataToken; public SymbolKind SymbolKind { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeParameter.cs index e6bc15eff..9b7fce095 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultResolvedTypeParameter.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation this.attributes = attributes ?? EmptyList.Instance; } - public override IReadOnlyList Attributes => attributes; + public override IEnumerable GetAttributes() => attributes; public override bool HasValueTypeConstraint => hasValueTypeConstraint; public override bool HasReferenceTypeConstraint => hasReferenceTypeConstraint; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedParameter.cs index 4b7077759..ffe8d0d95 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DefaultUnresolvedParameter.cs @@ -229,6 +229,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IType Type { get; internal set; } public string Name { get; internal set; } public IReadOnlyList Attributes { get; internal set; } + public IEnumerable GetAttributes() => Attributes; public bool IsRef { get; internal set; } public bool IsOut { get; internal set; } public bool IsParams { get; internal set; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/DummyTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/DummyTypeParameter.cs index cb0f2bd71..ed84cb480 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/DummyTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/DummyTypeParameter.cs @@ -141,9 +141,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation get { return index; } } - IReadOnlyList ITypeParameter.Attributes { - get { return EmptyList.Instance; } - } + IEnumerable ITypeParameter.GetAttributes() =>EmptyList.Instance; SymbolKind ITypeParameter.OwnerType { get { return ownerType; } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs index d433148ab..ad3536088 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/FakeMember.cs @@ -58,7 +58,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IAssembly IEntity.ParentAssembly => DeclaringType?.GetDefinition()?.ParentAssembly; - IReadOnlyList IEntity.Attributes => EmptyList.Instance; + IEnumerable IEntity.GetAttributes() => EmptyList.Instance; public Accessibility Accessibility { get; set; } = Accessibility.Public; @@ -130,7 +130,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public override SymbolKind SymbolKind => symbolKind; - IReadOnlyList IMethod.ReturnTypeAttributes => EmptyList.Instance; + IEnumerable IMethod.GetReturnTypeAttributes() => EmptyList.Instance; public IReadOnlyList TypeParameters { get; set; } = EmptyList.Instance; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataEvent.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataEvent.cs index def1c93ad..f7ed937b2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataEvent.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataEvent.cs @@ -38,7 +38,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly string name; // lazy-loaded: - IAttribute[] customAttributes; IType returnType; internal MetadataEvent(MetadataAssembly assembly, EventDefinitionHandle handle) @@ -103,16 +102,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation TypeParameterSubstitution IMember.Substitution => TypeParameterSubstitution.Identity; #region Attributes - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs index 34dc34915..b02f947ca 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataField.cs @@ -43,7 +43,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IType type; bool isVolatile; // initialized together with this.type byte decimalConstant; // 0=no, 1=yes, 2=unknown - IAttribute[] customAttributes; internal MetadataField(MetadataAssembly assembly, FieldDefinitionHandle handle) { @@ -127,17 +126,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IType DeclaringType => DeclaringTypeDefinition; public IAssembly ParentAssembly => assembly; public ICompilation Compilation => assembly.Compilation; - - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs index c5a006633..07101b7e3 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs @@ -45,8 +45,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation // lazy-loaded fields: ITypeDefinition declaringType; string name; - IAttribute[] customAttributes; - IAttribute[] returnTypeAttributes; IParameter[] parameters; IType returnType; @@ -232,15 +230,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public ICompilation Compilation => assembly.Compilation; #region Attributes - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - IType FindInteropType(string name) { return assembly.Compilation.FindType(new TopLevelTypeName( @@ -248,7 +237,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation )); } - IAttribute[] DecodeAttributes() + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); @@ -365,16 +354,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation #endregion #region Return type attributes - public IReadOnlyList ReturnTypeAttributes { - get { - var attr = LazyInit.VolatileRead(ref this.returnTypeAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.returnTypeAttributes, DecodeReturnTypeAttributes()); - } - } - - private IAttribute[] DecodeReturnTypeAttributes() + public IEnumerable GetReturnTypeAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs index dbf523768..7f2123a34 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataParameter.cs @@ -37,7 +37,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation // lazy-loaded: string name; - IAttribute[] customAttributes; internal MetadataParameter(MetadataAssembly assembly, IParameterizedMember owner, IType type, ParameterHandle handle) { @@ -53,16 +52,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public EntityHandle MetadataToken => handle; #region Attributes - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs index 2758bf6b2..d2612bd29 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataProperty.cs @@ -42,7 +42,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly SymbolKind symbolKind; // lazy-loaded: - IAttribute[] customAttributes; volatile Accessibility cachedAccessiblity = InvalidAccessibility; IParameter[] parameters; IType returnType; @@ -144,16 +143,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation TypeParameterSubstitution IMember.Substitution => TypeParameterSubstitution.Identity; #region Attributes - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs index 950cef6c4..43e6de017 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs @@ -51,7 +51,6 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public bool HasExtensionMethods { get; } // lazy-loaded: - IAttribute[] customAttributes; IMember[] members; IField[] fields; IProperty[] properties; @@ -285,16 +284,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IAssembly ParentAssembly => assembly; #region Type Attributes - public IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + public IEnumerable GetAttributes() { var b = new AttributeListBuilder(assembly); var metadata = assembly.metadata; diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs index 56ea796f9..7aad3675e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeParameter.cs @@ -98,16 +98,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public GenericParameterHandle MetadataToken => handle; - public override IReadOnlyList Attributes { - get { - var attr = LazyInit.VolatileRead(ref this.customAttributes); - if (attr != null) - return attr; - return LazyInit.GetOrSet(ref this.customAttributes, DecodeAttributes()); - } - } - - IAttribute[] DecodeAttributes() + public override IEnumerable GetAttributes() { var metadata = assembly.metadata; var gp = metadata.GetGenericParameter(handle); diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs index f80927c44..79472bc5d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs @@ -150,10 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public ITypeDefinition DeclaringTypeDefinition { get { return baseMember.DeclaringTypeDefinition; } } - - public IReadOnlyList Attributes { - get { return baseMember.Attributes; } - } + + IEnumerable IEntity.GetAttributes() => baseMember.GetAttributes(); public IEnumerable ExplicitlyImplementedInterfaceMembers { get { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs index b49fe531a..a215acfe6 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMethod.cs @@ -92,10 +92,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation public IReadOnlyList TypeArguments { get { return this.Substitution.MethodTypeArguments ?? EmptyList.Instance; } } - - public IReadOnlyList ReturnTypeAttributes { - get { return methodDefinition.ReturnTypeAttributes; } - } + + public IEnumerable GetReturnTypeAttributes() => methodDefinition.GetReturnTypeAttributes(); public IReadOnlyList TypeParameters { get { @@ -230,7 +228,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation this.baseTp = baseTp; } - public override IReadOnlyList Attributes => baseTp.Attributes; + public override IEnumerable GetAttributes() => baseTp.GetAttributes(); public override int GetHashCode() { diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs index eee19f7a6..5e6070f0a 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedParameter.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation this.newOwner = newOwner; } - IReadOnlyList IParameter.Attributes => baseParameter.Attributes; + IEnumerable IParameter.GetAttributes() => baseParameter.GetAttributes(); bool IParameter.IsRef => baseParameter.IsRef; bool IParameter.IsOut => baseParameter.IsOut; bool IParameter.IsParams => baseParameter.IsParams; diff --git a/ICSharpCode.Decompiler/TypeSystem/InheritanceHelper.cs b/ICSharpCode.Decompiler/TypeSystem/InheritanceHelper.cs index 602f5be5e..7ee067de9 100644 --- a/ICSharpCode.Decompiler/TypeSystem/InheritanceHelper.cs +++ b/ICSharpCode.Decompiler/TypeSystem/InheritanceHelper.cs @@ -29,7 +29,7 @@ namespace ICSharpCode.Decompiler.TypeSystem { // TODO: maybe these should be extension methods? // or even part of the interface itself? (would allow for easy caching) - + #region GetBaseMember /// /// Gets the base member that has the same signature. @@ -58,16 +58,16 @@ namespace ICSharpCode.Decompiler.TypeSystem yield return member; }*/ } - + // Remove generic specialization var substitution = member.Substitution; member = member.MemberDefinition; - + if (member.DeclaringTypeDefinition == null) { // For global methods, return empty list. (prevent SharpDevelop UDC crash 4524) yield break; } - + IEnumerable allBaseTypes; if (includeImplementedInterfaces) { allBaseTypes = member.DeclaringTypeDefinition.GetAllBaseTypes(); @@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } } #endregion - + #region GetDerivedMember /// /// Finds the member declared in 'derivedType' that has the same signature (could override) 'baseMember'. @@ -104,10 +104,10 @@ namespace ICSharpCode.Decompiler.TypeSystem throw new ArgumentNullException("baseMember"); if (derivedType == null) throw new ArgumentNullException("derivedType"); - + if (baseMember.Compilation != derivedType.Compilation) throw new ArgumentException("baseMember and derivedType must be from the same compilation"); - + baseMember = baseMember.MemberDefinition; bool includeInterfaces = baseMember.DeclaringTypeDefinition.Kind == TypeKind.Interface; IMethod method = baseMember as IMethod; @@ -147,5 +147,34 @@ namespace ICSharpCode.Decompiler.TypeSystem return null; } #endregion + + #region Attributes + internal static IEnumerable GetAttributes(ITypeDefinition typeDef) + { + foreach (var baseType in typeDef.GetNonInterfaceBaseTypes().Reverse()) { + ITypeDefinition baseTypeDef = baseType.GetDefinition(); + if (baseTypeDef == null) + continue; + foreach (var attr in baseTypeDef.GetAttributes()) { + yield return attr; + } + } + } + + internal static IEnumerable GetAttributes(IMember member) + { + HashSet visitedMembers = new HashSet(); + do { + member = member.MemberDefinition; // it's sufficient to look at the definitions + if (!visitedMembers.Add(member)) { + // abort if we seem to be in an infinite loop (cyclic inheritance) + break; + } + foreach (var attr in member.GetAttributes()) { + yield return attr; + } + } while (member.IsOverride && (member = InheritanceHelper.GetBaseMember(member)) != null); + } + #endregion } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index e24a8f910..2d9609490 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -420,181 +420,70 @@ namespace ICSharpCode.Decompiler.TypeSystem return null; } #endregion - - #region ITypeReference.Resolve(ICompilation) - - /// - /// Resolves a type reference in the compilation's main type resolve context. - /// Some type references require a more specific type resolve context and will not resolve using this method. - /// - /// - /// Returns the resolved type. - /// In case of an error, returns . - /// Never returns null. - /// - public static IType Resolve (this ITypeReference reference, ICompilation compilation) - { - if (reference == null) - throw new ArgumentNullException ("reference"); - if (compilation == null) - throw new ArgumentNullException ("compilation"); - return reference.Resolve (compilation.TypeResolveContext); - } - #endregion - #region ITypeDefinition.GetAttribute + #region IEntity.GetAttribute /// - /// Gets the attribute of the specified attribute type (or derived attribute types). + /// Gets whether the entity has an attribute of the specified attribute type (or derived attribute types). /// /// The entity on which the attributes are declared. /// The attribute type to look for. /// - /// Specifies whether attributes inherited from base classes and base members (if the given in an override) - /// should be returned. The default is true. + /// Specifies whether attributes inherited from base classes and base members + /// (if the given in an override) + /// should be returned. /// - /// - /// Returns the attribute that was found; or null if none was found. - /// If inherit is true, an from the entity itself will be returned if possible; - /// and the base entity will only be searched if none exists. - /// - public static IAttribute GetAttribute(this IEntity entity, IType attributeType, bool inherit = true) + public static bool HasAttribute(this IEntity entity, KnownAttribute attrType, bool inherit=false) { - return GetAttributes(entity, attributeType, inherit).FirstOrDefault(); + return GetAttribute(entity, attrType, inherit) != null; } - - /// - /// Gets the attributes of the specified attribute type (or derived attribute types). - /// - /// The entity on which the attributes are declared. - /// The attribute type to look for. - /// - /// Specifies whether attributes inherited from base classes and base members (if the given in an override) - /// should be returned. The default is true. - /// - /// - /// Returns the list of attributes that were found. - /// If inherit is true, attributes from the entity itself are returned first; followed by attributes inherited from the base entity. - /// - public static IEnumerable GetAttributes(this IEntity entity, IType attributeType, bool inherit = true) - { - if (entity == null) - throw new ArgumentNullException("entity"); - if (attributeType == null) - throw new ArgumentNullException("attributeType"); - return GetAttributes(entity, attributeType.Equals, inherit); - } - + /// /// Gets the attribute of the specified attribute type (or derived attribute types). /// /// The entity on which the attributes are declared. /// The attribute type to look for. /// - /// Specifies whether attributes inherited from base classes and base members (if the given in an override) - /// should be returned. The default is true. + /// Specifies whether attributes inherited from base classes and base members + /// (if the given in an override) + /// should be returned. /// /// /// Returns the attribute that was found; or null if none was found. /// If inherit is true, an from the entity itself will be returned if possible; /// and the base entity will only be searched if none exists. /// - public static IAttribute GetAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit = true) - { - return GetAttributes(entity, attributeType, inherit).FirstOrDefault(); - } - - /// - /// Gets the attributes of the specified attribute type (or derived attribute types). - /// - /// The entity on which the attributes are declared. - /// The attribute type to look for. - /// - /// Specifies whether attributes inherited from base classes and base members (if the given in an override) - /// should be returned. The default is true. - /// - /// - /// Returns the list of attributes that were found. - /// If inherit is true, attributes from the entity itself are returned first; followed by attributes inherited from the base entity. - /// - public static IEnumerable GetAttributes(this IEntity entity, KnownAttribute attributeType, bool inherit = true) + public static IAttribute GetAttribute(this IEntity entity, KnownAttribute attributeType, bool inherit=false) { - if (entity == null) - throw new ArgumentNullException("entity"); - return GetAttributes(entity, attrType => { - ITypeDefinition typeDef = attrType.GetDefinition(); - return typeDef != null && typeDef.FullTypeName == attributeType.GetTypeName(); - }, inherit); + return GetAttributes(entity, inherit).FirstOrDefault(a => a.AttributeType.IsKnownType(attributeType)); } /// - /// Gets the attribute of the specified attribute type (or derived attribute types). + /// Gets the attributes on the entity. /// /// The entity on which the attributes are declared. /// - /// Specifies whether attributes inherited from base classes and base members (if the given in an override) - /// should be returned. The default is true. + /// Specifies whether attributes inherited from base classes and base members + /// (if the given in an override) + /// should be returned. /// /// - /// Returns the attribute that was found; or null if none was found. - /// If inherit is true, an from the entity itself will be returned if possible; - /// and the base entity will only be searched if none exists. + /// Returns the list of attributes that were found. + /// If inherit is true, attributes from the entity itself are returned first; + /// followed by attributes inherited from the base entity. /// - public static IEnumerable GetAttributes(this IEntity entity, bool inherit = true) - { - if (entity == null) - throw new ArgumentNullException ("entity"); - return GetAttributes(entity, a => true, inherit); - } - - static IEnumerable GetAttributes(IEntity entity, Predicate attributeTypePredicate, bool inherit) - { - if (!inherit) { - foreach (var attr in entity.Attributes) { - if (attributeTypePredicate(attr.AttributeType)) - yield return attr; + public static IEnumerable GetAttributes(this IEntity entity, bool inherit) + { + if (inherit) { + if (entity is ITypeDefinition td) { + return InheritanceHelper.GetAttributes(td); + } else if (entity is IMember m) { + return InheritanceHelper.GetAttributes(m); + } else { + throw new NotSupportedException("Unknown entity type"); } - yield break; + } else { + return entity.GetAttributes(); } - ITypeDefinition typeDef = entity as ITypeDefinition; - if (typeDef != null) { - foreach (var baseType in typeDef.GetNonInterfaceBaseTypes().Reverse()) { - ITypeDefinition baseTypeDef = baseType.GetDefinition(); - if (baseTypeDef == null) - continue; - foreach (var attr in baseTypeDef.Attributes) { - if (attributeTypePredicate(attr.AttributeType)) - yield return attr; - } - } - yield break; - } - IMember member = entity as IMember; - if (member != null) { - HashSet visitedMembers = new HashSet(); - do { - member = member.MemberDefinition; // it's sufficient to look at the definitions - if (!visitedMembers.Add(member)) { - // abort if we seem to be in an infinite loop (cyclic inheritance) - break; - } - foreach (var attr in member.Attributes) { - if (attributeTypePredicate(attr.AttributeType)) - yield return attr; - } - } while (member.IsOverride && (member = InheritanceHelper.GetBaseMember(member)) != null); - yield break; - } - throw new NotSupportedException("Unknown entity type"); - } - #endregion - - #region IsCompilerGenerated - public static bool IsCompilerGenereated(this IEntity entity) - { - if (entity == null) - throw new ArgumentNullException(nameof(entity)); - - return entity.GetAttribute(KnownAttribute.CompilerGenerated) != null; } #endregion diff --git a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs index 38180810b..96e3375d8 100644 --- a/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs +++ b/ICSharpCode.Decompiler/TypeSystem/VarArgInstanceMethod.cs @@ -109,10 +109,9 @@ namespace ICSharpCode.Decompiler.TypeSystem baseMethod.Specialize(substitution), parameters.Skip(baseMethod.Parameters.Count - 1).Select(p => p.Type.AcceptVisitor(substitution)).ToList()); } - - public IReadOnlyList ReturnTypeAttributes { - get { return baseMethod.ReturnTypeAttributes; } - } + + IEnumerable IEntity.GetAttributes() => baseMethod.GetAttributes(); + IEnumerable IMethod.GetReturnTypeAttributes() => baseMethod.GetReturnTypeAttributes(); public IReadOnlyList TypeParameters { get { return baseMethod.TypeParameters; } @@ -225,10 +224,6 @@ namespace ICSharpCode.Decompiler.TypeSystem get { return baseMethod.ParentAssembly; } } - public IReadOnlyList Attributes { - get { return baseMethod.Attributes; } - } - public bool IsStatic { get { return baseMethod.IsStatic; } } diff --git a/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs new file mode 100644 index 000000000..c600830fe --- /dev/null +++ b/ICSharpCode.Decompiler/Util/ResXResourceWriter.cs @@ -0,0 +1,540 @@ +// Copyright (c) 2018 Daniel Grunwald +// This file is based on the Mono implementation of ResXResourceWriter. +// It is modified to add support for "ResourceSerializedObject" values. +// +// 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. +// +// Copyright (c) 2004-2005 Novell, Inc. +// +// Authors: +// Duncan Mak duncan@ximian.com +// Gonzalo Paniagua Javier gonzalo@ximian.com +// Peter Bartok pbartok@novell.com +// Gary Barnett gary.barnett.mono@gmail.com +// includes code by Mike Krüger and Lluis Sanchez + +using System.ComponentModel; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Xml; +using System; + +namespace ICSharpCode.Decompiler.Util +{ +#if INSIDE_SYSTEM_WEB + internal +#else + public +#endif + class ResXResourceWriter : IDisposable + { + #region Local Variables + private string filename; + private Stream stream; + private TextWriter textwriter; + private XmlTextWriter writer; + private bool written; + private string base_path; + #endregion // Local Variables + + #region Static Fields + public static readonly string BinSerializedObjectMimeType = "application/x-microsoft.net.object.binary.base64"; + public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64"; + public static readonly string DefaultSerializedObjectMimeType = BinSerializedObjectMimeType; + public static readonly string ResMimeType = "text/microsoft-resx"; + public static readonly string ResourceSchema = schema; + public static readonly string SoapSerializedObjectMimeType = "application/x-microsoft.net.object.soap.base64"; + public static readonly string Version = "2.0"; + #endregion // Static Fields + + #region Constructors & Destructor + public ResXResourceWriter(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + if (!stream.CanWrite) + throw new ArgumentException("stream is not writable.", "stream"); + + this.stream = stream; + } + + public ResXResourceWriter(TextWriter textWriter) + { + if (textWriter == null) + throw new ArgumentNullException("textWriter"); + + this.textwriter = textWriter; + } + + public ResXResourceWriter(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + + this.filename = fileName; + } + + ~ResXResourceWriter() + { + Dispose(false); + } + #endregion // Constructors & Destructor + + const string WinFormsAssemblyName = ", System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; + const string ResXNullRefTypeName = "System.Resources.ResXNullRef" + WinFormsAssemblyName; + + void InitWriter() + { + if (filename != null) + stream = File.Open(filename, FileMode.Create); + if (textwriter == null) + textwriter = new StreamWriter(stream, Encoding.UTF8); + + writer = new XmlTextWriter(textwriter); + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("root"); + writer.WriteRaw(schema); + WriteHeader("resmimetype", "text/microsoft-resx"); + WriteHeader("version", "1.3"); + WriteHeader("reader", "System.Resources.ResXResourceReader" + WinFormsAssemblyName); + WriteHeader("writer", "System.Resources.ResXResourceWriter" + WinFormsAssemblyName); + } + + void WriteHeader(string name, string value) + { + writer.WriteStartElement("resheader"); + writer.WriteAttributeString("name", name); + writer.WriteStartElement("value"); + writer.WriteString(value); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + + void WriteNiceBase64(byte[] value, int offset, int length) + { + string b64; + StringBuilder sb; + int pos; + int inc; + string ins; + + b64 = Convert.ToBase64String(value, offset, length); + + // Wild guess; two extra newlines, and one newline/tab pair for every 80 chars + sb = new StringBuilder(b64, b64.Length + ((b64.Length + 160) / 80) * 3); + pos = 0; + inc = 80 + Environment.NewLine.Length + 1; + ins = Environment.NewLine + "\t"; + while (pos < sb.Length) { + sb.Insert(pos, ins); + pos += inc; + } + sb.Insert(sb.Length, Environment.NewLine); + writer.WriteString(sb.ToString()); + } + + void WriteBytes(string name, Type type, byte[] value, int offset, int length, string comment) + { + writer.WriteStartElement("data"); + writer.WriteAttributeString("name", name); + + if (type != null) { + writer.WriteAttributeString("type", type.AssemblyQualifiedName); + // byte[] should never get a mimetype, otherwise MS.NET won't be able + // to parse the data. + if (type != typeof(byte[])) + writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); + writer.WriteStartElement("value"); + WriteNiceBase64(value, offset, length); + } else { + writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType); + writer.WriteStartElement("value"); + writer.WriteBase64(value, offset, length); + } + + writer.WriteEndElement(); + + if (!(comment == null || comment.Equals(String.Empty))) { + writer.WriteStartElement("comment"); + writer.WriteString(comment); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); + } + + void WriteBytes(string name, Type type, byte[] value, string comment) + { + WriteBytes(name, type, value, 0, value.Length, comment); + } + + void WriteString(string name, string value) + { + WriteString(name, value, null); + } + void WriteString(string name, string value, string type) + { + WriteString(name, value, type, String.Empty); + } + void WriteString(string name, string value, string type, string comment) + { + writer.WriteStartElement("data"); + writer.WriteAttributeString("name", name); + if (type != null) + writer.WriteAttributeString("type", type); + writer.WriteStartElement("value"); + writer.WriteString(value); + writer.WriteEndElement(); + if (!(comment == null || comment.Equals(String.Empty))) { + writer.WriteStartElement("comment"); + writer.WriteString(comment); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteWhitespace("\n "); + } + + public void AddResource(string name, byte[] value) + { + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + WriteBytes(name, value.GetType(), value, null); + } + + public void AddResource(string name, object value) + { + AddResource(name, value, String.Empty); + } + + private void AddResource(string name, object value, string comment) + { + if (value is string) { + AddResource(name, (string)value, comment); + return; + } + + if (name == null) + throw new ArgumentNullException("name"); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + if (value is byte[]) { + WriteBytes(name, value.GetType(), (byte[])value, comment); + return; + } + if (value is ResourceSerializedObject rso) { + var bytes = rso.GetBytes(); + WriteBytes(name, null, bytes, 0, bytes.Length, comment); + return; + } + + if (value == null) { + // nulls written as ResXNullRef + WriteString(name, "", ResXNullRefTypeName, comment); + return; + } + + if (value != null && !value.GetType().IsSerializable) + throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name)); + + TypeConverter converter = TypeDescriptor.GetConverter(value); + + if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) { + string str = (string)converter.ConvertToInvariantString(value); + WriteString(name, str, value.GetType().AssemblyQualifiedName, comment); + return; + } + + if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) { + byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[])); + WriteBytes(name, value.GetType(), b, comment); + return; + } + + MemoryStream ms = new MemoryStream(); + BinaryFormatter fmt = new BinaryFormatter(); + try { + fmt.Serialize(ms, value); + } catch (Exception e) { + throw new InvalidOperationException("Cannot add a " + value.GetType() + + "because it cannot be serialized: " + + e.Message); + } + + WriteBytes(name, null, ms.GetBuffer(), 0, (int)ms.Length, comment); + ms.Close(); + } + + public void AddResource(string name, string value) + { + AddResource(name, value, string.Empty); + } + + private void AddResource(string name, string value, string comment) + { + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + WriteString(name, value, null, comment); + } + + public void AddMetadata(string name, string value) + { + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + writer.WriteStartElement("metadata"); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("xml:space", "preserve"); + + writer.WriteElementString("value", value); + + writer.WriteEndElement(); + } + + public void AddMetadata(string name, byte[] value) + { + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + writer.WriteStartElement("metadata"); + writer.WriteAttributeString("name", name); + + writer.WriteAttributeString("type", value.GetType().AssemblyQualifiedName); + + writer.WriteStartElement("value"); + WriteNiceBase64(value, 0, value.Length); + writer.WriteEndElement(); + + writer.WriteEndElement(); + } + + public void AddMetadata(string name, object value) + { + if (value is string) { + AddMetadata(name, (string)value); + return; + } + + if (value is byte[]) { + AddMetadata(name, (byte[])value); + return; + } + + if (name == null) + throw new ArgumentNullException("name"); + + if (value == null) + throw new ArgumentNullException("value"); + + if (!value.GetType().IsSerializable) + throw new InvalidOperationException(String.Format("The element '{0}' of type '{1}' is not serializable.", name, value.GetType().Name)); + + if (written) + throw new InvalidOperationException("The resource is already generated."); + + if (writer == null) + InitWriter(); + + Type type = value.GetType(); + + TypeConverter converter = TypeDescriptor.GetConverter(value); + if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) { + string str = (string)converter.ConvertToInvariantString(value); + writer.WriteStartElement("metadata"); + writer.WriteAttributeString("name", name); + if (type != null) + writer.WriteAttributeString("type", type.AssemblyQualifiedName); + writer.WriteStartElement("value"); + writer.WriteString(str); + writer.WriteEndElement(); + writer.WriteEndElement(); + writer.WriteWhitespace("\n "); + return; + } + + if (converter != null && converter.CanConvertTo(typeof(byte[])) && converter.CanConvertFrom(typeof(byte[]))) { + byte[] b = (byte[])converter.ConvertTo(value, typeof(byte[])); + writer.WriteStartElement("metadata"); + writer.WriteAttributeString("name", name); + + if (type != null) { + writer.WriteAttributeString("type", type.AssemblyQualifiedName); + writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); + writer.WriteStartElement("value"); + WriteNiceBase64(b, 0, b.Length); + } else { + writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType); + writer.WriteStartElement("value"); + writer.WriteBase64(b, 0, b.Length); + } + + writer.WriteEndElement(); + writer.WriteEndElement(); + return; + } + + MemoryStream ms = new MemoryStream(); + BinaryFormatter fmt = new BinaryFormatter(); + try { + fmt.Serialize(ms, value); + } catch (Exception e) { + throw new InvalidOperationException("Cannot add a " + value.GetType() + + "because it cannot be serialized: " + + e.Message); + } + + writer.WriteStartElement("metadata"); + writer.WriteAttributeString("name", name); + + if (type != null) { + writer.WriteAttributeString("type", type.AssemblyQualifiedName); + writer.WriteAttributeString("mimetype", ByteArraySerializedObjectMimeType); + writer.WriteStartElement("value"); + WriteNiceBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length); + } else { + writer.WriteAttributeString("mimetype", BinSerializedObjectMimeType); + writer.WriteStartElement("value"); + writer.WriteBase64(ms.GetBuffer(), 0, ms.GetBuffer().Length); + } + + writer.WriteEndElement(); + writer.WriteEndElement(); + ms.Close(); + } + + public void Close() + { + if (!written) { + Generate(); + } + + if (writer != null) { + writer.Close(); + stream = null; + filename = null; + textwriter = null; + } + } + + public virtual void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Generate() + { + if (written) + throw new InvalidOperationException("The resource is already generated."); + + written = true; + writer.WriteEndElement(); + writer.Flush(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + Close(); + } + + static string schema = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + +".Replace("'", "\"").Replace("\t", " "); + + #region Public Properties + public string BasePath { + get { return base_path; } + set { base_path = value; } + } + #endregion + } +} diff --git a/ICSharpCode.Decompiler/Util/ResourcesFile.cs b/ICSharpCode.Decompiler/Util/ResourcesFile.cs new file mode 100644 index 000000000..85e18bf4d --- /dev/null +++ b/ICSharpCode.Decompiler/Util/ResourcesFile.cs @@ -0,0 +1,526 @@ +// Copyright (c) 2018 Daniel Grunwald +// Based on the .NET Core ResourceReader; make available under the MIT license +// by the .NET Foundation. +// +// 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace ICSharpCode.Decompiler.Util +{ + /// + /// .resources file. + /// + public class ResourcesFile : IEnumerable>, IDisposable + { + sealed class MyBinaryReader : BinaryReader + { + public MyBinaryReader(Stream input, bool leaveOpen) : base(input, Encoding.UTF8, leaveOpen) + { + } + + // upgrade from protected to public visibility + public new int Read7BitEncodedInt() + { + return base.Read7BitEncodedInt(); + } + + public void Seek(long pos, SeekOrigin origin) + { + BaseStream.Seek(pos, origin); + } + } + + enum ResourceTypeCode + { + Null = 0, + String = 1, + Boolean = 2, + Char = 3, + Byte = 4, + SByte = 5, + Int16 = 6, + UInt16 = 7, + Int32 = 8, + UInt32 = 9, + Int64 = 10, + UInt64 = 11, + Single = 12, + Double = 13, + Decimal = 14, + DateTime = 0xF, + TimeSpan = 0x10, + LastPrimitive = 0x10, + ByteArray = 0x20, + Stream = 33, + StartOfUserTypes = 0x40 + } + + /// Holds the number used to identify resource files. + public const int MagicNumber = unchecked((int)0xBEEFCACE); + const int ResourceSetVersion = 2; + + readonly MyBinaryReader reader; + readonly int version; + readonly int numResources; + readonly string[] typeTable; + readonly int[] namePositions; + readonly long fileStartPosition; + readonly long nameSectionPosition; + readonly long dataSectionPosition; + long[] startPositions; + + /// + /// Creates a new ResourcesFile. + /// + /// Input stream. + /// Whether the stream should be help open when the ResourcesFile is disposed. + /// + /// The stream is must be held open while the ResourcesFile is in use. + /// The stream must be seekable; any operation using the ResourcesFile will end up seeking the stream. + /// + public ResourcesFile(Stream stream, bool leaveOpen = true) + { + fileStartPosition = stream.Position; + reader = new MyBinaryReader(stream, leaveOpen); + + const string ResourcesHeaderCorrupted = "Resources header corrupted."; + + // Read ResourceManager header + // Check for magic number + int magicNum = reader.ReadInt32(); + if (magicNum != MagicNumber) + throw new BadImageFormatException("Not a .resources file - invalid magic number"); + // Assuming this is ResourceManager header V1 or greater, hopefully + // after the version number there is a number of bytes to skip + // to bypass the rest of the ResMgr header. For V2 or greater, we + // use this to skip to the end of the header + int resMgrHeaderVersion = reader.ReadInt32(); + int numBytesToSkip = reader.ReadInt32(); + if (numBytesToSkip < 0 || resMgrHeaderVersion < 0) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + if (resMgrHeaderVersion > 1) { + reader.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current); + } else { + // We don't care about numBytesToSkip; read the rest of the header + + // readerType: + reader.ReadString(); + // resourceSetType: + reader.ReadString(); + } + + // Read RuntimeResourceSet header + // Do file version check + version = reader.ReadInt32(); + if (version != ResourceSetVersion && version != 1) + throw new BadImageFormatException($"Unsupported resource set version: {version}"); + + numResources = reader.ReadInt32(); + if (numResources < 0) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + + // Read type positions into type positions array. + // But delay initialize the type table. + int numTypes = reader.ReadInt32(); + if (numTypes < 0) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + typeTable = new string[numTypes]; + for (int i = 0; i < numTypes; i++) { + typeTable[i] = reader.ReadString(); + } + + // Prepare to read in the array of name hashes + // Note that the name hashes array is aligned to 8 bytes so + // we can use pointers into it on 64 bit machines. (4 bytes + // may be sufficient, but let's plan for the future) + // Skip over alignment stuff. All public .resources files + // should be aligned No need to verify the byte values. + long pos = reader.BaseStream.Position - fileStartPosition; + int alignBytes = unchecked((int)pos) & 7; + if (alignBytes != 0) { + for (int i = 0; i < 8 - alignBytes; i++) { + reader.ReadByte(); + } + } + + // Skip over the array of name hashes + try { + reader.Seek(checked(4 * numResources), SeekOrigin.Current); + } catch (OverflowException) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + + // Read in the array of relative positions for all the names. + namePositions = new int[numResources]; + for (int i = 0; i < numResources; i++) { + int namePosition = reader.ReadInt32(); + if (namePosition < 0) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + namePositions[i] = namePosition; + } + + // Read location of data section. + int dataSectionOffset = reader.ReadInt32(); + if (dataSectionOffset < 0) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + + // Store current location as start of name section + nameSectionPosition = reader.BaseStream.Position; + dataSectionPosition = fileStartPosition + dataSectionOffset; + + // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt + if (dataSectionPosition < nameSectionPosition) { + throw new BadImageFormatException(ResourcesHeaderCorrupted); + } + } + + public void Dispose() + { + reader.Dispose(); + } + + public int ResourceCount => numResources; + + public string GetResourceName(int index) + { + return GetResourceName(index, out _); + } + + int GetResourceDataOffset(int index) + { + GetResourceName(index, out int dataOffset); + return dataOffset; + } + + string GetResourceName(int index, out int dataOffset) + { + long pos = nameSectionPosition + namePositions[index]; + byte[] bytes; + lock (reader) { + reader.Seek(pos, SeekOrigin.Begin); + // Can't use reader.ReadString, since it's using UTF-8! + int byteLen = reader.Read7BitEncodedInt(); + if (byteLen < 0) { + throw new BadImageFormatException("Resource name has negative length"); + } + bytes = new byte[byteLen]; + // We must read byteLen bytes, or we have a corrupted file. + // Use a blocking read in case the stream doesn't give us back + // everything immediately. + int count = byteLen; + while (count > 0) { + int n = reader.Read(bytes, byteLen - count, count); + if (n == 0) + throw new BadImageFormatException("End of stream within a resource name"); + count -= n; + } + dataOffset = reader.ReadInt32(); + if (dataOffset < 0) { + throw new BadImageFormatException("Negative data offset"); + } + } + return Encoding.Unicode.GetString(bytes); + } + + internal bool AllEntriesAreStreams() + { + if (version != 2) + return false; + lock (reader) { + for (int i = 0; i < numResources; i++) { + int dataOffset = GetResourceDataOffset(i); + reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin); + var typeCode = (ResourceTypeCode)reader.Read7BitEncodedInt(); + if (typeCode != ResourceTypeCode.Stream) + return false; + } + } + return true; + } + + object LoadObject(int dataOffset) + { + try { + lock (reader) { + if (version == 1) { + return LoadObjectV1(dataOffset); + } else { + return LoadObjectV2(dataOffset); + } + } + } catch (EndOfStreamException e) { + throw new BadImageFormatException("Invalid resource file", e); + } + } + + string FindType(int typeIndex) + { + if (typeIndex < 0 || typeIndex >= typeTable.Length) + throw new BadImageFormatException("Type index out of bounds"); + return typeTable[typeIndex]; + } + + // This takes a virtual offset into the data section and reads an Object + // from that location. + // Anyone who calls LoadObject should make sure they take a lock so + // no one can cause us to do a seek in here. + private object LoadObjectV1(int dataOffset) + { + Debug.Assert(System.Threading.Monitor.IsEntered(reader)); + reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin); + int typeIndex = reader.Read7BitEncodedInt(); + if (typeIndex == -1) + return null; + string typeName = FindType(typeIndex); + int comma = typeName.IndexOf(','); + if (comma > 0) { + // strip assembly name + typeName = typeName.Substring(0, comma); + } + switch (typeName) { + case "System.String": + return reader.ReadString(); + case "System.Byte": + return reader.ReadByte(); + case "System.SByte": + return reader.ReadSByte(); + case "System.Int16": + return reader.ReadInt16(); + case "System.UInt16": + return reader.ReadUInt16(); + case "System.Int32": + return reader.ReadInt32(); + case "System.UInt32": + return reader.ReadUInt32(); + case "System.Int64": + return reader.ReadInt64(); + case "System.UInt64": + return reader.ReadUInt64(); + case "System.Single": + return reader.ReadSingle(); + case "System.Double": + return reader.ReadDouble(); + case "System.DateTime": + // Ideally we should use DateTime's ToBinary & FromBinary, + // but we can't for compatibility reasons. + return new DateTime(reader.ReadInt64()); + case "System.TimeSpan": + return new TimeSpan(reader.ReadInt64()); + case "System.Decimal": + int[] bits = new int[4]; + for (int i = 0; i < bits.Length; i++) + bits[i] = reader.ReadInt32(); + return new decimal(bits); + default: + return new ResourceSerializedObject(FindType(typeIndex), this, reader.BaseStream.Position); + } + } + + private object LoadObjectV2(int dataOffset) + { + Debug.Assert(System.Threading.Monitor.IsEntered(reader)); + reader.Seek(dataSectionPosition + dataOffset, SeekOrigin.Begin); + var typeCode = (ResourceTypeCode)reader.Read7BitEncodedInt(); + switch (typeCode) { + case ResourceTypeCode.Null: + return null; + + case ResourceTypeCode.String: + return reader.ReadString(); + + case ResourceTypeCode.Boolean: + return reader.ReadBoolean(); + + case ResourceTypeCode.Char: + return (char)reader.ReadUInt16(); + + case ResourceTypeCode.Byte: + return reader.ReadByte(); + + case ResourceTypeCode.SByte: + return reader.ReadSByte(); + + case ResourceTypeCode.Int16: + return reader.ReadInt16(); + + case ResourceTypeCode.UInt16: + return reader.ReadUInt16(); + + case ResourceTypeCode.Int32: + return reader.ReadInt32(); + + case ResourceTypeCode.UInt32: + return reader.ReadUInt32(); + + case ResourceTypeCode.Int64: + return reader.ReadInt64(); + + case ResourceTypeCode.UInt64: + return reader.ReadUInt64(); + + case ResourceTypeCode.Single: + return reader.ReadSingle(); + + case ResourceTypeCode.Double: + return reader.ReadDouble(); + + case ResourceTypeCode.Decimal: + return reader.ReadDecimal(); + + case ResourceTypeCode.DateTime: + // Use DateTime's ToBinary & FromBinary. + long data = reader.ReadInt64(); + return DateTime.FromBinary(data); + + case ResourceTypeCode.TimeSpan: + long ticks = reader.ReadInt64(); + return new TimeSpan(ticks); + + // Special types + case ResourceTypeCode.ByteArray: { + int len = reader.ReadInt32(); + if (len < 0) { + throw new BadImageFormatException("Resource with negative length"); + } + return reader.ReadBytes(len); + } + + case ResourceTypeCode.Stream: { + int len = reader.ReadInt32(); + if (len < 0) { + throw new BadImageFormatException("Resource with negative length"); + } + byte[] bytes = reader.ReadBytes(len); + return new MemoryStream(bytes, writable: false); + } + + default: + if (typeCode < ResourceTypeCode.StartOfUserTypes) { + throw new BadImageFormatException("Invalid typeCode"); + } + return new ResourceSerializedObject(FindType(typeCode - ResourceTypeCode.StartOfUserTypes), this, reader.BaseStream.Position); + } + } + + public object GetResourceValue(int index) + { + GetResourceName(index, out int dataOffset); + return LoadObject(dataOffset); + } + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < numResources; i++) { + string name = GetResourceName(i, out int dataOffset); + object val = LoadObject(dataOffset); + yield return new KeyValuePair(name, val); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + long[] GetStartPositions() + { + long[] positions = LazyInit.VolatileRead(ref startPositions); + if (positions != null) + return positions; + lock (reader) { + // double-checked locking + positions = LazyInit.VolatileRead(ref startPositions); + if (positions != null) + return positions; + positions = new long[numResources * 2]; + int outPos = 0; + for (int i = 0; i < numResources; i++) { + positions[outPos++] = nameSectionPosition + namePositions[i]; + positions[outPos++] = dataSectionPosition + GetResourceDataOffset(i); + } + Array.Sort(positions); + return LazyInit.GetOrSet(ref startPositions, positions); + } + } + + internal byte[] GetBytesForSerializedObject(long pos) + { + long[] positions = GetStartPositions(); + int i = Array.BinarySearch(positions, pos); + if (i < 0) { + // 'pos' the the start position of the serialized object data + // This is the position after the type code, so it should not appear in the 'positions' array. + // Set i to the index of the next position after 'pos'. + i = ~i; + // Note: if 'pos' does exist in the array, that means the stream has length 0, + // so we keep the i that we found. + } + lock (reader) { + long endPos; + if (i == positions.Length) { + endPos = reader.BaseStream.Length; + } else { + endPos = positions[i]; + } + int len = (int)(endPos - pos); + reader.Seek(pos, SeekOrigin.Begin); + return reader.ReadBytes(len); + } + } + } + + public class ResourceSerializedObject + { + public string TypeName { get; } + readonly ResourcesFile file; + readonly long position; + + internal ResourceSerializedObject(string typeName, ResourcesFile file, long position) + { + this.TypeName = typeName; + this.file = file; + this.position = position; + } + + /// + /// Gets a stream that starts with the serialized object data. + /// + public Stream GetStream() + { + return new MemoryStream(file.GetBytesForSerializedObject(position), writable: false); + } + + /// + /// Gets the serialized object data. + /// + public byte[] GetBytes() + { + return file.GetBytesForSerializedObject(position); + } + } +} diff --git a/ILSpy.AddIn/AssemblyFileFinder.cs b/ILSpy.AddIn/AssemblyFileFinder.cs new file mode 100644 index 000000000..7ba13bd18 --- /dev/null +++ b/ILSpy.AddIn/AssemblyFileFinder.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.Util; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.AddIn +{ + public class AssemblyFileFinder + { + public static string FindAssemblyFile(AssemblyDefinition assemblyDefinition, string assemblyFile) + { + var assemblyName = assemblyDefinition.Name; + + var detectedTargetFramework = DetectTargetFrameworkId(assemblyDefinition, assemblyFile); + if (string.IsNullOrEmpty(detectedTargetFramework)) { + // Without a target framework id it makes no sense to continue + return null; + } + + var targetFramework = detectedTargetFramework.Split(new[] { ",Version=v" }, StringSplitOptions.None); + string file = null; + switch (targetFramework[0]) { + case ".NETCoreApp": + case ".NETStandard": + if (targetFramework.Length != 2) + return FindAssemblyFromGAC(assemblyDefinition); + var version = targetFramework[1].Length == 3 ? targetFramework[1] + ".0" : targetFramework[1]; + var dotNetCorePathFinder = new DotNetCorePathFinder(assemblyFile, detectedTargetFramework, version); + file = dotNetCorePathFinder.TryResolveDotNetCore(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyName.FullName)); + if (file != null) + return file; + return FindAssemblyFromGAC(assemblyDefinition); + default: + return FindAssemblyFromGAC(assemblyDefinition); + } + } + + static string FindAssemblyFromGAC(AssemblyDefinition assemblyDefinition) + { + return GacInterop.FindAssemblyInNetGac(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDefinition.Name.FullName)); + } + + static readonly string RefPathPattern = @"NuGetFallbackFolder[/\\][^/\\]+[/\\][^/\\]+[/\\]ref[/\\]"; + + public static bool IsReferenceAssembly(AssemblyDefinition assemblyDef, string assemblyFile) + { + if (assemblyDef.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute")) + return true; + + // Try to detect reference assembly through specific path pattern + var refPathMatch = Regex.Match(assemblyFile, RefPathPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + return refPathMatch.Success; + } + + static readonly string DetectTargetFrameworkIdRefPathPattern = + @"(Reference Assemblies[/\\]Microsoft[/\\]Framework[/\\](?<1>.NETFramework)[/\\]v(?<2>[^/\\]+)[/\\])" + + @"|(NuGetFallbackFolder[/\\](?<1>[^/\\]+)\\(?<2>[^/\\]+)([/\\].*)?[/\\]ref[/\\])"; + + public static string DetectTargetFrameworkId(AssemblyDefinition assembly, string assemblyPath = null) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + + const string TargetFrameworkAttributeName = "System.Runtime.Versioning.TargetFrameworkAttribute"; + + foreach (var attribute in assembly.CustomAttributes) { + if (attribute.AttributeType.FullName != TargetFrameworkAttributeName) + continue; + if (attribute.HasConstructorArguments) { + if (attribute.ConstructorArguments[0].Value is string value) + return value; + } + } + + // Optionally try to detect target version through assembly path as a fallback (use case: reference assemblies) + if (assemblyPath != null) { + /* + * Detected path patterns (examples): + * + * - .NETFramework -> C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll + * - .NETCore -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.1.0\ref\netcoreapp2.1\System.Console.dll + * - .NETStandard -> C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll + */ + var pathMatch = Regex.Match(assemblyPath, DetectTargetFrameworkIdRefPathPattern, + RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + if (pathMatch.Success) { + var type = pathMatch.Groups[1].Value; + var version = pathMatch.Groups[2].Value; + + if (type == ".NETFramework") { + return $".NETFramework,Version=v{version}"; + } else if (type.Contains("netcore")) { + return $".NETCoreApp,Version=v{version}"; + } else if (type.Contains("netstandard")) { + return $".NETStandard,Version=v{version}"; + } + } + } + + return string.Empty; + } + } +} diff --git a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs index a80748ce1..958b0e00a 100644 --- a/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs +++ b/ILSpy.AddIn/Commands/OpenCodeItemCommand.cs @@ -106,8 +106,7 @@ namespace ICSharpCode.ILSpy.AddIn.Commands owner.ShowMessage(OLEMSGICON.OLEMSGICON_WARNING, "Symbol can't be opened. This might happen while project is loading.", Environment.NewLine, invalidSymbolReference.AssemblyFile); - } - if (invalidSymbolReference.IsProjectReference) { + } else if (invalidSymbolReference.IsProjectReference) { // Some project references don't have assemblies, maybe not compiled yet? if (owner.ShowMessage( OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_WARNING, diff --git a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs index 2b0e33528..8e8e48193 100644 --- a/ILSpy.AddIn/Commands/OpenILSpyCommand.cs +++ b/ILSpy.AddIn/Commands/OpenILSpyCommand.cs @@ -89,8 +89,8 @@ namespace ICSharpCode.ILSpy.AddIn.Commands foreach (var reference in parentProject.MetadataReferences) { using (var assemblyDef = AssemblyDefinition.ReadAssembly(reference.Display)) { string assemblyName = assemblyDef.Name.Name; - if (IsReferenceAssembly(assemblyDef)) { - string resolvedAssemblyFile = GacInterop.FindAssemblyInNetGac(Decompiler.Metadata.AssemblyNameReference.Parse(assemblyDef.FullName)); + if (AssemblyFileFinder.IsReferenceAssembly(assemblyDef, reference.Display)) { + string resolvedAssemblyFile = AssemblyFileFinder.FindAssemblyFile(assemblyDef, reference.Display); dict.Add(assemblyName, new DetectedReference(assemblyName, resolvedAssemblyFile, false)); } else { @@ -127,11 +127,6 @@ namespace ICSharpCode.ILSpy.AddIn.Commands return null; } - - protected bool IsReferenceAssembly(AssemblyDefinition assemblyDef) - { - return assemblyDef.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.Runtime.CompilerServices.ReferenceAssemblyAttribute"); - } } class OpenILSpyCommand : ILSpyCommand diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj index ae4311f2b..ee784cab3 100644 --- a/ILSpy.AddIn/ILSpy.AddIn.csproj +++ b/ILSpy.AddIn/ILSpy.AddIn.csproj @@ -84,6 +84,7 @@ + diff --git a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs index 927ec4920..1e3b3b217 100644 --- a/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs +++ b/ILSpy.BamlDecompiler.Tests/BamlTestRunner.cs @@ -5,11 +5,11 @@ using System; using System.Collections; using System.IO; using System.Linq; -using System.Resources; using System.Threading; using System.Xml.Linq; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Tests.Helpers; +using ICSharpCode.Decompiler.Util; using NUnit.Framework; namespace ILSpy.BamlDecompiler.Tests @@ -143,15 +143,16 @@ namespace ILSpy.BamlDecompiler.Tests { if (res.ResourceType != ResourceType.Embedded) return null; Stream s = res.TryOpenStream(); + if (s == null) return null; s.Position = 0; - ResourceReader reader; + ResourcesFile resources; try { - reader = new ResourceReader(s); + resources = new ResourcesFile(s); } catch (ArgumentException) { return null; } - foreach (DictionaryEntry entry in reader.Cast().OrderBy(e => e.Key.ToString())) { - if (entry.Key.ToString() == name) { + foreach (var entry in resources.OrderBy(e => e.Key)) { + if (entry.Key == name) { if (entry.Value is Stream) return (Stream)entry.Value; if (entry.Value is byte[]) diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 0e01a6fc9..a75239b11 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -17,12 +17,10 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; using System.Linq; -using System.Resources; using System.Windows; using System.Windows.Controls; using ICSharpCode.Decompiler; @@ -388,11 +386,11 @@ namespace ICSharpCode.ILSpy protected override IEnumerable> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { if (fileName.EndsWith(".resource", StringComparison.OrdinalIgnoreCase)) { - using (ResourceReader reader = new ResourceReader(entryStream)) + fileName = Path.ChangeExtension(fileName, ".resx"); using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write)) using (ResXResourceWriter writer = new ResXResourceWriter(fs)) { - foreach (DictionaryEntry entry in reader) { - writer.AddResource((string)entry.Key, entry.Value); + foreach (var entry in new ResourcesFile(entryStream)) { + writer.AddResource(entry.Key, entry.Value); } } return new[] { Tuple.Create("EmbeddedResource", fileName) }; diff --git a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs index 47a2a029c..264299f45 100644 --- a/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs +++ b/ILSpy/TreeNodes/ResourceNodes/ResourcesFileTreeNode.cs @@ -17,15 +17,12 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.IO; -using System.Linq; -using System.Resources; - using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Controls; using ICSharpCode.ILSpy.TextView; @@ -61,8 +58,7 @@ namespace ICSharpCode.ILSpy.TreeNodes this.LazyLoading = true; } - public override object Icon - { + public override object Icon { get { return Images.ResourceResourcesFile; } } @@ -71,46 +67,42 @@ namespace ICSharpCode.ILSpy.TreeNodes Stream s = Resource.TryOpenStream(); if (s == null) return; s.Position = 0; - ResourceReader reader; try { - reader = new ResourceReader(s); - } - catch (ArgumentException) { - return; - } - foreach (DictionaryEntry entry in reader.Cast().OrderBy(e => e.Key.ToString())) { - ProcessResourceEntry(entry); + foreach (var entry in new ResourcesFile(s)) { + ProcessResourceEntry(entry); + } + } catch (BadImageFormatException) { + // ignore errors } } - private void ProcessResourceEntry(DictionaryEntry entry) + private void ProcessResourceEntry(KeyValuePair entry) { - var keyString = entry.Key.ToString(); - if (entry.Value is String) { - stringTableEntries.Add(new KeyValuePair(keyString, (string)entry.Value)); + stringTableEntries.Add(new KeyValuePair(entry.Key, (string)entry.Value)); return; } if (entry.Value is byte[]) { - Children.Add(ResourceEntryNode.Create(keyString, new MemoryStream((byte[])entry.Value))); + Children.Add(ResourceEntryNode.Create(entry.Key, new MemoryStream((byte[])entry.Value))); return; } - var node = ResourceEntryNode.Create(keyString, entry.Value); + var node = ResourceEntryNode.Create(entry.Key, entry.Value); if (node != null) { Children.Add(node); return; } - string entryType = entry.Value.GetType().FullName; - if (entry.Value is System.Globalization.CultureInfo) { - otherEntries.Add(new SerializedObjectRepresentation(keyString, entryType, ((System.Globalization.CultureInfo)entry.Value).DisplayName)); + if (entry.Value == null) { + otherEntries.Add(new SerializedObjectRepresentation(entry.Key, "null", "")); + } else if (entry.Value is ResourceSerializedObject so) { + otherEntries.Add(new SerializedObjectRepresentation(entry.Key, so.TypeName, "")); } else { - otherEntries.Add(new SerializedObjectRepresentation(keyString, entryType, entry.Value.ToString())); + otherEntries.Add(new SerializedObjectRepresentation(entry.Key, entry.Value.GetType().FullName, entry.Value.ToString())); } } - + public override bool Save(DecompilerTextView textView) { Stream s = Resource.TryOpenStream(); @@ -127,10 +119,9 @@ namespace ICSharpCode.ILSpy.TreeNodes } break; case 2: - var reader = new ResourceReader(s); using (var writer = new ResXResourceWriter(dlg.OpenFile())) { - foreach (DictionaryEntry entry in reader) { - writer.AddResource(entry.Key.ToString(), entry.Value); + foreach (var entry in new ResourcesFile(s)) { + writer.AddResource(entry.Key, entry.Value); } } break; diff --git a/appveyor.yml b/appveyor.yml index fe610097e..69468dc04 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,8 +31,7 @@ for: - branches: only: - master - - 3.0.x - - 3.1.x + - 3.2.x artifacts: - path: ILSpy_binaries.zip name: ILSpy %APPVEYOR_REPO_BRANCH% %ILSPY_VERSION_NUMBER% binaries diff --git a/doc/Resources.txt b/doc/Resources.txt new file mode 100644 index 000000000..a7b2f4df8 --- /dev/null +++ b/doc/Resources.txt @@ -0,0 +1,99 @@ +System.Resources.ResourceReader is unsuitable for deserializing resources in the ILSpy context, +because it tries to deserialize custom resource types, which fails if the types are in a non-GAC assembly. + +So we are instead using our own "class ResourcesFile", which is based on the +.NET Core ResourceReader implementation. + +struct ResourcesFileFormat { + int32 magicNum; [check == ResourceManager.MagicNumber] + + // ResourceManager header: + int32 resMgrHeaderVersion; [check >= 0] + int32 numBytesToSkip; [check >= 0] + if (resMgrHeaderVersion <= 1) { + string readerType; + string resourceSetType; + } else { + byte _[numBytesToSkip]; + } + + // RuntimeResourceSet header: + int32 version; [check in (1, 2)] + int32 numResources; [check >=0] + int32 numTypes; [check >=0] + string typeName[numTypes]; + .align 8; + int32 nameHashes[numResources]; + int32 namePositions[numResources]; [check >= 0] + int32 dataSectionOffset; [check >= current position in file] + byte remainderOfFile[]; +} + +// normal strings in this file format are stored as: +struct string { + compressedint len; + byte value[len]; // interpret as UTF-8 +} + +// NameEntry #i is stored starting at remainderOfFile[namePositions[i]] +// (that is, namePositions is interpreted relative to the start of the remainderOfFile array) +struct NameEntry { + compressedint len; + byte name[len]; // interpret as UTF-16 + int32 dataOffset; [check >= 0] +} + +// Found at position ResourcesFileFormat.dataSectionOffset+NameEntry.dataOffset in the file. +struct ValueEntry { + if (version == 1) { + compressedint typeIndex; + if (typeIndex == -1) { + // no value stored; value is implicitly null + } else { + switch (typeName[typeIndex]) { + case string: + case int, uint, long, ulong, sbyte, byte, short, ushort: + case float: + case double: + T value; // value directly stored + case DateTime: + int64 value; // new DateTime(_store.ReadInt64()) + case TimeSpan: + int64 value; // new TimeSpan(_store.ReadInt64()) + case decimal: + int32 value[4]; + default: + byte data[...]; // BinaryFormatter-serialized data using typeName[typeIndex] + } + } + } else if (version == 2) { + compressedint typeCode; + // note: unlike v1, no lookup into the typeName array! + switch (typeCode) { + case null: + // no value stored; value is implicitly null + case string: + case bool: + case int, uint, long, ulong, sbyte, byte, short, ushort: + T value; + case char: + uint16 value; + case float: + case double: + case decimal: + T value; + case DateTime: + int64 value; // DateTime.FromBinary(_store.ReadInt64()) + case TimeSpan: + int64 value; // new TimeSpan(_store.ReadInt64()) + case ResourceTypeCode.ByteArray: + int32 len; + byte value[len]; + case ResourceTypeCode.Stream: + int32 len; + byte value[len]; + case >= ResourceTypeCode.StartOfUserTypes: + byte data[...]; // BinaryFormatter-serialized data using typeName[typeCode - ResourceTypeCode.StartOfUserTypes] + } + } +} \ No newline at end of file