// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using dnlib.IO; namespace dnlib.DotNet { /// /// Searches for a type according to custom attribute search rules: first try the /// current assembly, and if that fails, try mscorlib /// sealed class CAAssemblyRefFinder : IAssemblyRefFinder { readonly ModuleDef module; /// /// Constructor /// /// The module to search first public CAAssemblyRefFinder(ModuleDef module) => this.module = module; /// public AssemblyRef FindAssemblyRef(TypeRef nonNestedTypeRef) { var modAsm = module.Assembly; if (modAsm is not null) { var type = modAsm.Find(nonNestedTypeRef); // If the user added a new type with the same name as a corelib type, don't return it, // only return the type if it is this module's original type. if (type is TypeDefMD td && td.ReaderModule == module) return module.UpdateRowId(new AssemblyRefUser(modAsm)); } else if (module.Find(nonNestedTypeRef) is not null) return AssemblyRef.CurrentAssembly; var corLibAsm = module.Context.AssemblyResolver.Resolve(module.CorLibTypes.AssemblyRef, module); if (corLibAsm is not null) { var type = corLibAsm.Find(nonNestedTypeRef); if (type is not null) return module.CorLibTypes.AssemblyRef; } if (nonNestedTypeRef.Namespace == "System" || nonNestedTypeRef.Namespace.StartsWith("System.")) return module.CorLibTypes.AssemblyRef; if (modAsm is not null) return module.UpdateRowId(new AssemblyRefUser(modAsm)); return AssemblyRef.CurrentAssembly; } } /// /// Thrown by CustomAttributeReader when it fails to parse a custom attribute blob /// [Serializable] public class CABlobParserException : Exception { /// /// Default constructor /// public CABlobParserException() { } /// /// Constructor /// /// Error message public CABlobParserException(string message) : base(message) { } /// /// Constructor /// /// Error message /// Other exception public CABlobParserException(string message, Exception innerException) : base(message, innerException) { } /// /// Constructor /// /// /// protected CABlobParserException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// Reads custom attributes from the #Blob stream /// public struct CustomAttributeReader { readonly ModuleDef module; DataReader reader; readonly uint caBlobOffset; readonly GenericParamContext gpContext; GenericArguments genericArguments; RecursionCounter recursionCounter; bool verifyReadAllBytes; /// /// Reads a custom attribute /// /// Reader module /// Custom attribute constructor /// Offset of custom attribute in the #Blob stream /// A new instance public static CustomAttribute Read(ModuleDefMD readerModule, ICustomAttributeType ctor, uint offset) => Read(readerModule, ctor, offset, new GenericParamContext()); /// /// Reads a custom attribute /// /// Reader module /// Custom attribute constructor /// Offset of custom attribute in the #Blob stream /// Generic parameter context /// A new instance public static CustomAttribute Read(ModuleDefMD readerModule, ICustomAttributeType ctor, uint offset, GenericParamContext gpContext) { var caReader = new CustomAttributeReader(readerModule, offset, gpContext); try { if (ctor is null) return caReader.CreateRaw(ctor); return caReader.Read(ctor); } catch (CABlobParserException) { return caReader.CreateRaw(ctor); } catch (IOException) { return caReader.CreateRaw(ctor); } } CustomAttribute CreateRaw(ICustomAttributeType ctor) => new CustomAttribute(ctor, GetRawBlob()); /// /// Reads a custom attribute /// /// Owner module /// CA blob /// Custom attribute constructor /// A new instance public static CustomAttribute Read(ModuleDef module, byte[] caBlob, ICustomAttributeType ctor) => Read(module, ByteArrayDataReaderFactory.CreateReader(caBlob), ctor, new GenericParamContext()); /// /// Reads a custom attribute /// /// Owner module /// A reader positioned at the the first byte of the CA blob /// Custom attribute constructor /// A new instance public static CustomAttribute Read(ModuleDef module, DataReader reader, ICustomAttributeType ctor) => Read(module, ref reader, ctor, new GenericParamContext()); /// /// Reads a custom attribute /// /// Owner module /// CA blob /// Custom attribute constructor /// Generic parameter context /// A new instance public static CustomAttribute Read(ModuleDef module, byte[] caBlob, ICustomAttributeType ctor, GenericParamContext gpContext) => Read(module, ByteArrayDataReaderFactory.CreateReader(caBlob), ctor, gpContext); /// /// Reads a custom attribute /// /// Owner module /// A stream positioned at the the first byte of the CA blob /// Custom attribute constructor /// Generic parameter context /// A new instance public static CustomAttribute Read(ModuleDef module, DataReader reader, ICustomAttributeType ctor, GenericParamContext gpContext) => Read(module, ref reader, ctor, gpContext); /// /// Reads a custom attribute /// /// Owner module /// A stream positioned at the the first byte of the CA blob /// Custom attribute constructor /// Generic parameter context /// A new instance static CustomAttribute Read(ModuleDef module, ref DataReader reader, ICustomAttributeType ctor, GenericParamContext gpContext) { var caReader = new CustomAttributeReader(module, ref reader, gpContext); CustomAttribute ca; try { if (ctor is null) ca = caReader.CreateRaw(ctor); else ca = caReader.Read(ctor); } catch (CABlobParserException) { ca = caReader.CreateRaw(ctor); } catch (IOException) { ca = caReader.CreateRaw(ctor); } return ca; } /// /// Reads custom attribute named arguments /// /// Owner module /// A reader positioned at the the first byte of the CA blob /// Number of named arguments to read from /// Generic parameter context /// A list of s or null if some error /// occurred. internal static List ReadNamedArguments(ModuleDef module, ref DataReader reader, int numNamedArgs, GenericParamContext gpContext) { try { var caReader = new CustomAttributeReader(module, ref reader, gpContext); var namedArgs = caReader.ReadNamedArguments(numNamedArgs); reader.CurrentOffset = caReader.reader.CurrentOffset; return namedArgs; } catch (CABlobParserException) { return null; } catch (IOException) { return null; } } CustomAttributeReader(ModuleDefMD readerModule, uint offset, GenericParamContext gpContext) { module = readerModule; caBlobOffset = offset; reader = readerModule.BlobStream.CreateReader(offset); genericArguments = null; recursionCounter = new RecursionCounter(); verifyReadAllBytes = false; this.gpContext = gpContext; } CustomAttributeReader(ModuleDef module, ref DataReader reader, GenericParamContext gpContext) { this.module = module; caBlobOffset = 0; this.reader = reader; genericArguments = null; recursionCounter = new RecursionCounter(); verifyReadAllBytes = false; this.gpContext = gpContext; } byte[] GetRawBlob() => reader.ToArray(); CustomAttribute Read(ICustomAttributeType ctor) { var methodSig = ctor?.MethodSig; if (methodSig is null) throw new CABlobParserException("ctor is null or not a method"); if (ctor is MemberRef mrCtor && mrCtor.Class is TypeSpec owner && owner.TypeSig is GenericInstSig gis) { genericArguments = new GenericArguments(); genericArguments.PushTypeArgs(gis.GenericArguments); } var methodSigParams = methodSig.Params; bool isEmpty = methodSigParams.Count == 0 && reader.Position == reader.Length; if (!isEmpty && reader.ReadUInt16() != 1) throw new CABlobParserException("Invalid CA blob prolog"); var ctorArgs = new List(methodSigParams.Count); int count = methodSigParams.Count; for (int i = 0; i < count; i++) ctorArgs.Add(ReadFixedArg(FixTypeSig(methodSigParams[i]))); // Some tools don't write the next ushort if there are no named arguments. int numNamedArgs = reader.Position == reader.Length ? 0 : reader.ReadUInt16(); var namedArgs = ReadNamedArguments(numNamedArgs); // verifyReadAllBytes will be set when we guess the underlying type of an enum. // To make sure we guessed right, verify that we read all bytes. if (verifyReadAllBytes && reader.Position != reader.Length) throw new CABlobParserException("Not all CA blob bytes were read"); return new CustomAttribute(ctor, ctorArgs, namedArgs, caBlobOffset); } List ReadNamedArguments(int numNamedArgs) { if ((uint)numNamedArgs >= 0x4000_0000 || numNamedArgs * 4 > reader.BytesLeft) return null; var namedArgs = new List(numNamedArgs); for (int i = 0; i < numNamedArgs; i++) { if (reader.Position == reader.Length) break; namedArgs.Add(ReadNamedArgument()); } return namedArgs; } TypeSig FixTypeSig(TypeSig type) => SubstituteGenericParameter(type.RemoveModifiers()).RemoveModifiers(); TypeSig SubstituteGenericParameter(TypeSig type) { if (genericArguments is null) return type; return genericArguments.Resolve(type); } CAArgument ReadFixedArg(TypeSig argType) { if (!recursionCounter.Increment()) throw new CABlobParserException("Too much recursion"); if (argType is null) throw new CABlobParserException("null argType"); CAArgument result; if (argType is SZArraySig arrayType) result = ReadArrayArgument(arrayType); else result = ReadElem(argType); recursionCounter.Decrement(); return result; } CAArgument ReadElem(TypeSig argType) { if (argType is null) throw new CABlobParserException("null argType"); var value = ReadValue((SerializationType)argType.ElementType, argType, out var realArgType); if (realArgType is null) throw new CABlobParserException("Invalid arg type"); // One example when this is true is when prop/field type is object and // value type is string[] if (value is CAArgument) return (CAArgument)value; return new CAArgument(realArgType, value); } object ReadValue(SerializationType etype, TypeSig argType, out TypeSig realArgType) { if (!recursionCounter.Increment()) throw new CABlobParserException("Too much recursion"); object result; switch (etype) { case SerializationType.Boolean: realArgType = module.CorLibTypes.Boolean; result = reader.ReadByte() != 0; break; case SerializationType.Char: realArgType = module.CorLibTypes.Char; result = reader.ReadChar(); break; case SerializationType.I1: realArgType = module.CorLibTypes.SByte; result = reader.ReadSByte(); break; case SerializationType.U1: realArgType = module.CorLibTypes.Byte; result = reader.ReadByte(); break; case SerializationType.I2: realArgType = module.CorLibTypes.Int16; result = reader.ReadInt16(); break; case SerializationType.U2: realArgType = module.CorLibTypes.UInt16; result = reader.ReadUInt16(); break; case SerializationType.I4: realArgType = module.CorLibTypes.Int32; result = reader.ReadInt32(); break; case SerializationType.U4: realArgType = module.CorLibTypes.UInt32; result = reader.ReadUInt32(); break; case SerializationType.I8: realArgType = module.CorLibTypes.Int64; result = reader.ReadInt64(); break; case SerializationType.U8: realArgType = module.CorLibTypes.UInt64; result = reader.ReadUInt64(); break; case SerializationType.R4: realArgType = module.CorLibTypes.Single; result = reader.ReadSingle(); break; case SerializationType.R8: realArgType = module.CorLibTypes.Double; result = reader.ReadDouble(); break; case SerializationType.String: realArgType = module.CorLibTypes.String; result = ReadUTF8String(); break; // It's ET.ValueType if it's eg. a ctor enum arg type case (SerializationType)ElementType.ValueType: if (argType is null) throw new CABlobParserException("Invalid element type"); realArgType = argType; result = ReadEnumValue(GetEnumUnderlyingType(argType)); break; // It's ET.Object if it's a ctor object arg type case (SerializationType)ElementType.Object: case SerializationType.TaggedObject: realArgType = ReadFieldOrPropType(); var arraySig = realArgType as SZArraySig; if (arraySig is not null) result = ReadArrayArgument(arraySig); else result = ReadValue((SerializationType)realArgType.ElementType, realArgType, out var tmpType); break; // It's ET.Class if it's eg. a ctor System.Type arg type case (SerializationType)ElementType.Class: var tdr = argType as TypeDefOrRefSig; if (tdr is not null && tdr.DefinitionAssembly.IsCorLib() && tdr.Namespace == "System") { if (tdr.TypeName == "Type") { result = ReadValue(SerializationType.Type, tdr, out realArgType); break; } if (tdr.TypeName == "String") { result = ReadValue(SerializationType.String, tdr, out realArgType); break; } if (tdr.TypeName == "Object") { result = ReadValue(SerializationType.TaggedObject, tdr, out realArgType); break; } } // Assume it's an enum that couldn't be resolved realArgType = argType; result = ReadEnumValue(null); break; case SerializationType.Type: realArgType = argType; result = ReadType(true); break; case SerializationType.Enum: realArgType = ReadType(false); result = ReadEnumValue(GetEnumUnderlyingType(realArgType)); break; default: throw new CABlobParserException("Invalid element type"); } recursionCounter.Decrement(); return result; } object ReadEnumValue(TypeSig underlyingType) { if (underlyingType is not null) { if (underlyingType.ElementType < ElementType.Boolean || underlyingType.ElementType > ElementType.U8) throw new CABlobParserException("Invalid enum underlying type"); return ReadValue((SerializationType)underlyingType.ElementType, underlyingType, out var realArgType); } // We couldn't resolve the type ref. It should be an enum, but we don't know for sure. // Most enums use Int32 as the underlying type. Assume that's true also in this case. // Since we're guessing, verify that we've read all CA blob bytes. If we haven't, then // we probably guessed wrong. verifyReadAllBytes = true; return reader.ReadInt32(); } TypeSig ReadType(bool canReturnNull) { var name = ReadUTF8String(); if (canReturnNull && name is null) return null; var asmRefFinder = new CAAssemblyRefFinder(module); var type = TypeNameParser.ParseAsTypeSigReflection(module, UTF8String.ToSystemStringOrEmpty(name), asmRefFinder, gpContext); if (type is null) throw new CABlobParserException("Could not parse type"); return type; } /// /// Gets the enum's underlying type /// /// An enum type /// The underlying type or null if we couldn't resolve the type ref /// If is not an enum or null static TypeSig GetEnumUnderlyingType(TypeSig type) { if (type is null) throw new CABlobParserException("null enum type"); var td = GetTypeDef(type); if (td is null) return null; if (!td.IsEnum) throw new CABlobParserException("Not an enum"); return td.GetEnumUnderlyingType().RemoveModifiers(); } /// /// Converts to a , possibly resolving /// a /// /// The type /// A or null if we couldn't resolve the /// or if is a type spec static TypeDef GetTypeDef(TypeSig type) { if (type is TypeDefOrRefSig tdr) { var td = tdr.TypeDef; if (td is not null) return td; var tr = tdr.TypeRef; if (tr is not null) return tr.Resolve(); } return null; } CAArgument ReadArrayArgument(SZArraySig arrayType) { if (!recursionCounter.Increment()) throw new CABlobParserException("Too much recursion"); var arg = new CAArgument(arrayType); int arrayCount = reader.ReadInt32(); if (arrayCount == -1) { // -1 if it's null } else if (arrayCount < 0 || arrayCount > reader.BytesLeft) throw new CABlobParserException("Array is too big"); else { var array = new List(arrayCount); arg.Value = array; for (int i = 0; i < arrayCount; i++) array.Add(ReadFixedArg(FixTypeSig(arrayType.Next))); } recursionCounter.Decrement(); return arg; } CANamedArgument ReadNamedArgument() { var isField = (SerializationType)reader.ReadByte() switch { SerializationType.Property => false, SerializationType.Field => true, _ => throw new CABlobParserException("Named argument is not a field/property"), }; var fieldPropType = ReadFieldOrPropType(); var name = ReadUTF8String(); var argument = ReadFixedArg(fieldPropType); return new CANamedArgument(isField, fieldPropType, name, argument); } TypeSig ReadFieldOrPropType() { if (!recursionCounter.Increment()) throw new CABlobParserException("Too much recursion"); var result = (SerializationType)reader.ReadByte() switch { SerializationType.Boolean => module.CorLibTypes.Boolean, SerializationType.Char => module.CorLibTypes.Char, SerializationType.I1 => module.CorLibTypes.SByte, SerializationType.U1 => module.CorLibTypes.Byte, SerializationType.I2 => module.CorLibTypes.Int16, SerializationType.U2 => module.CorLibTypes.UInt16, SerializationType.I4 => module.CorLibTypes.Int32, SerializationType.U4 => module.CorLibTypes.UInt32, SerializationType.I8 => module.CorLibTypes.Int64, SerializationType.U8 => module.CorLibTypes.UInt64, SerializationType.R4 => module.CorLibTypes.Single, SerializationType.R8 => module.CorLibTypes.Double, SerializationType.String => module.CorLibTypes.String, SerializationType.SZArray => new SZArraySig(ReadFieldOrPropType()), SerializationType.Type => new ClassSig(module.CorLibTypes.GetTypeRef("System", "Type")), SerializationType.TaggedObject => module.CorLibTypes.Object, SerializationType.Enum => ReadType(false), _ => throw new CABlobParserException("Invalid type"), }; recursionCounter.Decrement(); return result; } UTF8String ReadUTF8String() { if (reader.ReadByte() == 0xFF) return null; reader.Position--; if (!reader.TryReadCompressedUInt32(out uint len)) throw new CABlobParserException("Could not read compressed UInt32"); if (len == 0) return UTF8String.Empty; return new UTF8String(reader.ReadBytes((int)len)); } } }