// 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));
}
}
}