using System; using System.Collections.Immutable; using System.Reflection.Metadata; using Microsoft.CodeAnalysis; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Reflection.Metadata.Ecma335; namespace Semmle.Extraction.CIL.Entities { /// /// A method entity. /// interface IMethod : IMember { } /// /// A method entity. /// abstract class Method : TypeContainer, IMethod { protected Method(GenericContext gc) : base(gc.cx) { this.gc = gc; } public override IEnumerable TypeParameters => gc.TypeParameters.Concat(declaringType.TypeParameters); public override IEnumerable MethodParameters => genericParams == null ? gc.MethodParameters : gc.MethodParameters.Concat(genericParams); public int GenericParameterCount => signature.GenericParameterCount; public virtual Method SourceDeclaration => this; public abstract Type DeclaringType { get; } public abstract string Name { get; } public virtual IList LocalVariables => throw new NotImplementedException(); public IList Parameters { get; private set; } static readonly Id tick = CIL.Id.Create("`"); static readonly Id space = CIL.Id.Create(" "); static readonly Id dot = CIL.Id.Create("."); static readonly Id open = CIL.Id.Create("("); static readonly Id close = CIL.Id.Create(")"); internal protected Id MakeMethodId(Type parent, Id methodName) { var id = signature.ReturnType.MakeId(this) + space + parent.ShortId + dot + methodName; if (signature.GenericParameterCount > 0) { id += tick + signature.GenericParameterCount; } id += open + CIL.Id.CommaSeparatedList(signature.ParameterTypes.Select(p => p.MakeId(this))) + close; return id; } protected MethodTypeParameter[] genericParams; protected Type declaringType; protected GenericContext gc; protected MethodSignature signature; protected Id name; static readonly StringId methodSuffix = new StringId(";cil-method"); public override Id IdSuffix => methodSuffix; protected void PopulateParameters(IEnumerable parameterTypes) { Parameters = MakeParameters(parameterTypes).ToArray(); } protected IEnumerable PopulateFlags { get { if (IsStatic) yield return Tuples.cil_static(this); } } public abstract bool IsStatic { get; } private IEnumerable MakeParameters(IEnumerable parameterTypes) { int i = 0; if (!IsStatic) { yield return cx.Populate(new Parameter(cx, this, i++, DeclaringType)); } foreach (var p in parameterTypes) yield return cx.Populate(new Parameter(cx, this, i++, p)); } } /// /// A method implementation entity. /// interface IMethodImplementation : IExtractedEntity { } /// /// A method implementation entity. /// In the database, the same method could in principle have multiple implementations. /// class MethodImplementation : UnlabelledEntity, IMethodImplementation { readonly Method m; public MethodImplementation(Method m) : base(m.cx) { this.m = m; } public override IEnumerable Contents { get { yield return Tuples.cil_method_implementation(this, m, cx.assembly); } } } /// /// A definition method - a method defined in the current assembly. /// class DefinitionMethod : Method, IMember { readonly Handle handle; readonly MethodDefinition md; readonly PDB.IMethod methodDebugInformation; LocalVariable[] locals; public MethodImplementation Implementation { get; private set; } public override IList LocalVariables => locals; public DefinitionMethod(GenericContext gc, MethodDefinitionHandle handle) : base(gc) { md = cx.mdReader.GetMethodDefinition(handle); this.gc = gc; this.handle = handle; name = cx.GetId(md.Name); declaringType = (Type)cx.CreateGeneric(this, md.GetDeclaringType()); signature = md.DecodeSignature(new SignatureDecoder(), this); ShortId = MakeMethodId(declaringType, name); methodDebugInformation = cx.GetMethodDebugInformation(handle); } public override bool IsStatic => !signature.Header.IsInstance; public override Type DeclaringType => declaringType; public override string Name => cx.ShortName(md.Name); /// /// Holds if this method has bytecode. /// public bool HasBytecode => md.ImplAttributes == MethodImplAttributes.IL && md.RelativeVirtualAddress != 0; public override IEnumerable Contents { get { if (md.GetGenericParameters().Any()) { // We need to perform a 2-phase population because some type parameters can // depend on other type parameters (as a constraint). genericParams = new MethodTypeParameter[md.GetGenericParameters().Count]; for (int i = 0; i < genericParams.Length; ++i) genericParams[i] = cx.Populate(new MethodTypeParameter(this, this, i)); for (int i = 0; i < genericParams.Length; ++i) genericParams[i].PopulateHandle(this, md.GetGenericParameters()[i]); foreach (var p in genericParams) yield return p; } var typeSignature = md.DecodeSignature(cx.TypeSignatureDecoder, this); PopulateParameters(typeSignature.ParameterTypes); foreach (var c in Parameters) yield return c; foreach (var c in PopulateFlags) yield return c; foreach (var p in md.GetParameters().Select(h => cx.mdReader.GetParameter(h)).Where(p => p.SequenceNumber > 0)) { var pe = Parameters[IsStatic ? p.SequenceNumber - 1 : p.SequenceNumber]; if (p.Attributes.HasFlag(ParameterAttributes.Out)) yield return Tuples.cil_parameter_out(pe); if (p.Attributes.HasFlag(ParameterAttributes.In)) yield return Tuples.cil_parameter_in(pe); Attribute.Populate(cx, pe, p.GetCustomAttributes()); } yield return Tuples.metadata_handle(this, cx.assembly, MetadataTokens.GetToken(handle)); yield return Tuples.cil_method(this, Name, declaringType, typeSignature.ReturnType); yield return Tuples.cil_method_source_declaration(this, this); yield return Tuples.cil_method_location(this, cx.assembly); if (HasBytecode) { Implementation = new MethodImplementation(this); yield return Implementation; var body = cx.peReader.GetMethodBody(md.RelativeVirtualAddress); if (!body.LocalSignature.IsNil) { var locals = cx.mdReader.GetStandaloneSignature(body.LocalSignature); var localVariableTypes = locals.DecodeLocalSignature(cx.TypeSignatureDecoder, this); this.locals = new LocalVariable[localVariableTypes.Length]; for (int l = 0; l < this.locals.Length; ++l) { this.locals[l] = cx.Populate(new LocalVariable(cx, Implementation, l, localVariableTypes[l])); yield return this.locals[l]; } } var jump_table = new Dictionary(); foreach (var c in Decode(body.GetILBytes(), jump_table)) yield return c; int filter_index = 0; foreach (var region in body.ExceptionRegions) { yield return new ExceptionRegion(this, Implementation, filter_index++, region, jump_table); } yield return Tuples.cil_method_stack_size(Implementation, body.MaxStack); if (methodDebugInformation != null) { var sourceLocation = cx.CreateSourceLocation(methodDebugInformation.Location); yield return sourceLocation; yield return Tuples.cil_method_location(this, sourceLocation); } } // Flags if (md.Attributes.HasFlag(MethodAttributes.Private)) yield return Tuples.cil_private(this); if (md.Attributes.HasFlag(MethodAttributes.Public)) yield return Tuples.cil_public(this); if (md.Attributes.HasFlag(MethodAttributes.Family)) yield return Tuples.cil_protected(this); if (md.Attributes.HasFlag(MethodAttributes.Final)) yield return Tuples.cil_sealed(this); if (md.Attributes.HasFlag(MethodAttributes.Virtual)) yield return Tuples.cil_virtual(this); if (md.Attributes.HasFlag(MethodAttributes.Abstract)) yield return Tuples.cil_abstract(this); if (md.Attributes.HasFlag(MethodAttributes.HasSecurity)) yield return Tuples.cil_security(this); if (md.Attributes.HasFlag(MethodAttributes.RequireSecObject)) yield return Tuples.cil_requiresecobject(this); if (md.Attributes.HasFlag(MethodAttributes.SpecialName)) yield return Tuples.cil_specialname(this); if (md.Attributes.HasFlag(MethodAttributes.NewSlot)) yield return Tuples.cil_newslot(this); // Populate attributes Attribute.Populate(cx, this, md.GetCustomAttributes()); } } IEnumerable Decode(byte[] ilbytes, Dictionary jump_table) { // Sequence points are stored in order of offset. // We use an enumerator to locate the correct sequence point for each instruction. // The sequence point gives the location of each instruction. // The location of an instruction is given by the sequence point *after* the // instruction. IEnumerator nextSequencePoint = null; PdbSourceLocation instructionLocation = null; if (methodDebugInformation != null) { nextSequencePoint = methodDebugInformation.SequencePoints.GetEnumerator(); if (nextSequencePoint.MoveNext()) { instructionLocation = cx.CreateSourceLocation(nextSequencePoint.Current.Location); yield return instructionLocation; } else { nextSequencePoint = null; } } int child = 0; for (int offset = 0; offset < ilbytes.Length;) { var instruction = new Instruction(cx, this, ilbytes, offset, child++); yield return instruction; if (nextSequencePoint != null && offset >= nextSequencePoint.Current.Offset) { instructionLocation = cx.CreateSourceLocation(nextSequencePoint.Current.Location); yield return instructionLocation; if (!nextSequencePoint.MoveNext()) nextSequencePoint = null; } if (instructionLocation != null) yield return Tuples.cil_instruction_location(instruction, instructionLocation); jump_table.Add(instruction.Offset, instruction); offset += instruction.Width; } foreach (var i in jump_table) { foreach (var t in i.Value.JumpContents(jump_table)) yield return t; } } /// /// Display the instructions in the method in the debugger. /// This is only used for debugging, not in the code itself. /// public IEnumerable DebugInstructions { get { if (md.ImplAttributes == MethodImplAttributes.IL && md.RelativeVirtualAddress != 0) { var body = cx.peReader.GetMethodBody(md.RelativeVirtualAddress); var ilbytes = body.GetILBytes(); int child = 0; for (int offset = 0; offset < ilbytes.Length;) { Instruction decoded; try { decoded = new Instruction(cx, this, ilbytes, offset, child++); offset += decoded.Width; } catch { yield break; } yield return decoded; } } } } } /// /// This is a late-bound reference to a method. /// class MemberReferenceMethod : Method { readonly MemberReference mr; readonly Type declType; readonly GenericContext parent; readonly Method sourceDeclaration; public MemberReferenceMethod(GenericContext gc, MemberReferenceHandle handle) : base(gc) { this.gc = gc; mr = cx.mdReader.GetMemberReference(handle); signature = mr.DecodeMethodSignature(new SignatureDecoder(), gc); parent = (GenericContext)cx.CreateGeneric(gc, mr.Parent); var parentMethod = parent as Method; var nameLabel = cx.GetId(mr.Name); declType = parentMethod == null ? parent as Type : parentMethod.DeclaringType; ShortId = MakeMethodId(declType, nameLabel); var typeSourceDeclaration = declType.SourceDeclaration; sourceDeclaration = typeSourceDeclaration == declType ? (Method)this : typeSourceDeclaration.LookupMethod(mr.Name, mr.Signature); } public override Method SourceDeclaration => sourceDeclaration; public override bool IsStatic => !signature.Header.IsInstance; public override Type DeclaringType => declType; public override string Name => cx.ShortName(mr.Name); public override IEnumerable TypeParameters => parent.TypeParameters.Concat(gc.TypeParameters); public override IEnumerable Contents { get { genericParams = new MethodTypeParameter[signature.GenericParameterCount]; for (int p = 0; p < genericParams.Length; ++p) genericParams[p] = cx.Populate(new MethodTypeParameter(this, this, p)); foreach (var p in genericParams) yield return p; var typeSignature = mr.DecodeMethodSignature(cx.TypeSignatureDecoder, this); PopulateParameters(typeSignature.ParameterTypes); foreach (var p in Parameters) yield return p; foreach (var f in PopulateFlags) yield return f; yield return Tuples.cil_method(this, Name, DeclaringType, typeSignature.ReturnType); if (SourceDeclaration != null) yield return Tuples.cil_method_source_declaration(this, SourceDeclaration); } } } /// /// A constructed method. /// class MethodSpecificationMethod : Method { readonly MethodSpecification ms; readonly Method unboundMethod; readonly ImmutableArray typeParams; public MethodSpecificationMethod(GenericContext gc, MethodSpecificationHandle handle) : base(gc) { ms = cx.mdReader.GetMethodSpecification(handle); typeParams = ms.DecodeSignature(cx.TypeSignatureDecoder, gc); unboundMethod = (Method)cx.CreateGeneric(gc, ms.Method); declaringType = unboundMethod.DeclaringType; ShortId = unboundMethod.ShortId + openAngle + CIL.Id.CommaSeparatedList(typeParams.Select(p => p.ShortId)) + closeAngle; } static readonly Id openAngle = CIL.Id.Create("<"); static readonly Id closeAngle = CIL.Id.Create(">"); public override Method SourceDeclaration => unboundMethod; public override Type DeclaringType => unboundMethod.DeclaringType; public override string Name => unboundMethod.Name; public override bool IsStatic => unboundMethod.IsStatic; public override IEnumerable MethodParameters => typeParams; public override IEnumerable Contents { get { MethodSignature constructedTypeSignature; switch (ms.Method.Kind) { case HandleKind.MemberReference: var mr = cx.mdReader.GetMemberReference((MemberReferenceHandle)ms.Method); constructedTypeSignature = mr.DecodeMethodSignature(cx.TypeSignatureDecoder, this); break; case HandleKind.MethodDefinition: var md = cx.mdReader.GetMethodDefinition((MethodDefinitionHandle)ms.Method); constructedTypeSignature = md.DecodeSignature(cx.TypeSignatureDecoder, this); break; default: throw new InternalError("Unexpected constructed method handle kind {0}", ms.Method.Kind); } PopulateParameters(constructedTypeSignature.ParameterTypes); foreach (var p in Parameters) yield return p; foreach (var f in PopulateFlags) yield return f; yield return Tuples.cil_method(this, Name, DeclaringType, constructedTypeSignature.ReturnType); yield return Tuples.cil_method_source_declaration(this, SourceDeclaration); if (typeParams.Count() != unboundMethod.GenericParameterCount) throw new InternalError("Method type parameter mismatch"); for (int p = 0; p < typeParams.Length; ++p) { yield return Tuples.cil_type_argument(this, p, typeParams[p]); } } } } }