private static void GetAllExceptions(MethodBase method, HashSet <Type> exceptionTypes, HashSet <MethodBase> visitedMethods, int depth) { var ilReader = new ILReader(method); MethodBase stackTopItem = null; foreach (var instruction in ilReader) { if (instruction is InlineMethodInstruction) { var methodInstruction = (InlineMethodInstruction)instruction; if (methodInstruction.OpCode.StackBehaviourPush == StackBehaviour.Pushref && (methodInstruction.Method.MemberType & MemberTypes.Constructor) != 0) { stackTopItem = methodInstruction.Method; } if (!visitedMethods.Contains(methodInstruction.Method)) { visitedMethods.Add(methodInstruction.Method); GetAllExceptions(methodInstruction.Method, exceptionTypes, visitedMethods, depth + 1); } } else { switch (instruction.OpCode.Value) { case 0x7A: // throw if (stackTopItem == null) { throw new InvalidProgramException(); } exceptionTypes.Add(stackTopItem.DeclaringType); break; } } } }
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options) { base.DecompileMethod(method, output, options); if (!method.HasBody) { return; } var typeSystem = new DecompilerTypeSystem(method.Module); var specializingTypeSystem = typeSystem.GetSpecializingTypeSystem(new SimpleTypeResolveContext(typeSystem.Resolve(method))); var reader = new ILReader(specializingTypeSystem); reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; ILFunction il = reader.ReadIL(method.Body, options.CancellationToken); ILTransformContext context = new ILTransformContext(il, typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken }; context.Stepper.StepLimit = options.StepLimit; context.Stepper.IsDebug = options.IsDebug; try { il.RunTransforms(transforms, context); } catch (StepLimitReachedException) { } catch (Exception ex) { output.WriteLine(ex.ToString()); output.WriteLine(); output.WriteLine("ILAst after the crash:"); } finally { // update stepper even if a transform crashed unexpectedly if (options.StepLimit == int.MaxValue) { Stepper = context.Stepper; OnStepperUpdated(new EventArgs()); } } (output as ISmartTextOutput)?.AddButton(Images.ViewCode, "Show Steps", delegate { DebugSteps.Show(); }); output.WriteLine(); il.WriteTo(output, DebugSteps.Options); }
ILFunction TransformDelegateConstruction(NewObj value, out ILInstruction target) { target = null; if (!IsDelegateConstruction(value)) { return(null); } var targetMethod = ((IInstructionWithMethodOperand)value.Arguments[1]).Method; if (IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) { target = value.Arguments[0]; var methodDefinition = (Mono.Cecil.MethodDefinition)context.TypeSystem.GetCecil(targetMethod); var localTypeSystem = context.TypeSystem.GetSpecializingTypeSystem(new SimpleTypeResolveContext(targetMethod)); var ilReader = new ILReader(localTypeSystem); ilReader.UseDebugSymbols = context.Settings.UseDebugSymbols; var function = ilReader.ReadIL(methodDefinition.Body, context.CancellationToken); function.DelegateType = value.Method.DeclaringType; function.CheckInvariant(ILPhase.Normal); var contextPrefix = targetMethod.Name; foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { v.Name = contextPrefix + v.Name; } var nestedContext = new ILTransformContext(function, localTypeSystem, context.Settings) { CancellationToken = context.CancellationToken }; function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)), nestedContext); function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter))); // handle nested lambdas ((IILTransform) new DelegateConstruction()).Run(function, nestedContext); function.AddILRange(target.ILRange); function.AddILRange(value.Arguments[1].ILRange); return(function); } return(null); }
bool LookaheadExpressionFieldConstant(bool isDup, ref ILReader reader, out MetadataToken?fieldToken) { fieldToken = null; var fork = reader.Clone(); if (//Expression.Constant(typeof(<>__DisplayClass>), c) (isDup || fork.TryGet(OpCodes.Ldloc, 0) != null) && fork.TryGet(OpCodes.Ldtoken) != null && fork.TryGetCall(nameof(Type.GetTypeFromHandle)) != null && fork.TryGetCall(nameof(Expression.Constant)) != null && //Expression.Field( , fieldof(c.a)); fork.TryGet(OpCodes.Ldtoken) is Instruction ldtoken && fork.TryGetCall(nameof(System.Reflection.FieldInfo.GetFieldFromHandle)) != null && fork.TryGetCall(nameof(Expression.Field)) != null) { fieldToken = ((FieldReference)ldtoken.Operand).MetadataToken; reader.Position = fork.Position; return(true); } return(false); }
public void Write(ArraySegment <Byte> segment, String outputPath, UInt16 expectedVersion) { using (MemoryStream sourceFile = new MemoryStream(segment.Array, 0, segment.Count)) using (StreamWriter outputFile = File.CreateText(outputPath)) { ILReader reader = new ILReader(sourceFile, expectedVersion); String[][] lines = reader.ReadLines(); for (var i = 0; i < lines.Length; i++) { outputFile.WriteLine($"{i:D4}:"); String[] entries = lines[i]; foreach (String line in entries) { outputFile.WriteLine(line); } outputFile.WriteLine(); } } }
public static MethodDefinition Wrap(ReflectionNET.MethodInfo methodInfo) { var methodName = new MethodName(methodInfo.Name); var builder = new MethodBuilder(methodName); var generator = builder.GetGenerator(); foreach (var local in methodInfo.GetMethodBody().LocalVariables) { generator.DeclareLocal(local.LocalType); } var instructions = new ILReader(methodInfo).Instructions; var modifyWriter = new StringModifyWriter(generator); foreach (var instr in instructions) { modifyWriter.Write(instr); } //ILInstructionWriter.WriteIL(instructions, generator); return(builder.CreateMethodDefinition()); }
private static MemberInfo MemberOf(Delegate action, params OpCode[] ops) { MemberInfo info = null; if (cache.TryGetValue(action.Method.MethodHandle, out info)) { return(info); } ILReader reader = new ILReader(action.Method); foreach (ILInstruction instruction in reader.Instructions) { if (ops.Any(x => x == instruction.Op)) { info = instruction.Data as MemberInfo; cache[action.Method.MethodHandle] = info; return(info); } } throw new InvalidOperationException("Did not find a member."); }
public void TestStackAnalysis(Type aType, string aMethodName, Type[] aArgs) { if (aArgs is null) { aArgs = Array.Empty <Type>(); } var method = aType.GetMethod(aMethodName, 0, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, aArgs, null); var methodBase = new _MethodInfo(method, 1, _MethodInfo.TypeEnum.Normal, null); var appAssembler = new AppAssembler(null, new VoidTextWriter(), "") { DebugMode = Cosmos.Build.Common.DebugMode.None }; var ilReader = new ILReader(); var opCodes = ilReader.ProcessMethod(method); var mSequence = appAssembler.GenerateDebugSequencePoints(methodBase, Cosmos.Build.Common.DebugMode.None); var iMethod = new ILMethod(opCodes, mSequence); Assert.DoesNotThrow(() => iMethod.Analyse()); }
public unsafe void DeconstructOpcodes3() { var gen = CreateGenerator(); var l1 = gen.DefineLabel(); var l2 = gen.DefineLabel(); gen.Emit(OpCodes.ADD); gen.Emit(OpCodes.LDC_I4_S, 228); gen.Emit(OpCodes.ADD); gen.Emit(OpCodes.JMP_HQ, l2); gen.Emit(OpCodes.LDC_I4_S, 228); gen.Emit(OpCodes.ADD); gen.Emit(OpCodes.LDC_I4_S, 228); gen.Emit(OpCodes.LDC_I4_S, 228); gen.Emit(OpCodes.ADD); gen.Emit(OpCodes.JMP_HQ, l1); gen.UseLabel(l1); gen.Emit(OpCodes.SUB); gen.Emit(OpCodes.SUB); gen.UseLabel(l2); gen.Emit(OpCodes.SUB); gen.Emit(OpCodes.SUB); var offset = 0; var body = gen.BakeByteArray(); var(result, map) = ILReader.Deconstruct(body, &offset, null); var labels = ILReader.DeconstructLabels(body, &offset); var first_label = result[map[labels[0]].pos]; Assert.AreEqual(first_label, OpCodes.SUB.Value); //Assert.AreEqual(second_label, OpCodes.SUB.Value); Assert.AreEqual(OpCodes.ADD.Value, result[0]); Assert.AreEqual(OpCodes.LDC_I4_S.Value, result[1]); Assert.AreEqual((uint)228, result[2]); }
public static HashSet <int> ComputeBranchTargets(this MethodIL methodBody) { HashSet <int> branchTargets = new HashSet <int>(); var reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un) { branchTargets.Add(reader.ReadBranchDestination(opcode)); } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { branchTargets.Add((int)reader.ReadILUInt32() + jmpBase); } } else { reader.Skip(opcode); } } foreach (ILExceptionRegion einfo in methodBody.GetExceptionRegions()) { if (einfo.Kind == ILExceptionRegionKind.Filter) { branchTargets.Add(einfo.FilterOffset); } branchTargets.Add(einfo.HandlerOffset); } return(branchTargets); }
public void TestGenerateGroups(Type aType, string aMethodName, int aExpectedGroups, Type[] aArgs) { if (aArgs is null) { aArgs = Array.Empty <Type>(); } var method = aType.GetMethod(aMethodName, 0, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, aArgs, null); var methodBase = new _MethodInfo(method, 1, _MethodInfo.TypeEnum.Normal, null); var appAssembler = new AppAssembler(null, new VoidTextWriter(), "") { DebugMode = Cosmos.Build.Common.DebugMode.None }; var ilReader = new ILReader(); var opCodes = ilReader.ProcessMethod(method); var mSequence = appAssembler.GenerateDebugSequencePoints(methodBase, Cosmos.Build.Common.DebugMode.None); var iMethod = new ILMethod(opCodes, mSequence); var groups = ILGroup.GenerateGroups(iMethod, mSequence); Assert.AreEqual(aExpectedGroups, groups.Count); }
public static FlowGraph Create(MethodIL il) { HashSet <int> bbStarts = GetBasicBlockStarts(il); List <BasicBlock> bbs = new List <BasicBlock>(); void AddBB(int start, int count) { if (count > 0) { bbs.Add(new BasicBlock(start, count)); } } int prevStart = 0; foreach (int ofs in bbStarts.OrderBy(o => o)) { AddBB(prevStart, ofs - prevStart); prevStart = ofs; } AddBB(prevStart, il.GetILBytes().Length - prevStart); FlowGraph fg = new FlowGraph(bbs); // We know where each basic block starts now. Proceed by linking them together. ILReader reader = new ILReader(il.GetILBytes()); foreach (BasicBlock bb in bbs) { reader.Seek(bb.Start); while (reader.HasNext) { Debug.Assert(fg.Lookup(reader.Offset) == bb); ILOpcode opc = reader.ReadILOpcode(); if (opc.IsBranch()) { int tar = reader.ReadBranchDestination(opc); bb.Targets.Add(fg.Lookup(tar)); if (!opc.IsUnconditionalBranch()) { bb.Targets.Add(fg.Lookup(reader.Offset)); } break; } if (opc == ILOpcode.switch_) { uint numCases = reader.ReadILUInt32(); int jmpBase = reader.Offset + checked ((int)(numCases * 4)); bb.Targets.Add(fg.Lookup(jmpBase)); for (uint i = 0; i < numCases; i++) { int caseOfs = jmpBase + (int)reader.ReadILUInt32(); bb.Targets.Add(fg.Lookup(caseOfs)); } break; } if (opc == ILOpcode.ret || opc == ILOpcode.endfinally || opc == ILOpcode.endfilter || opc == ILOpcode.throw_ || opc == ILOpcode.rethrow) { break; } reader.Skip(opc); // Check fall through if (reader.HasNext) { BasicBlock nextBB = fg.Lookup(reader.Offset); if (nextBB != bb) { // Falling through bb.Targets.Add(nextBB); break; } } } } foreach (BasicBlock bb in bbs) { foreach (BasicBlock tar in bb.Targets) { tar.Sources.Add(bb); } } return(fg); }
public static void GetAllExceptions(MethodBase method, HashSet <Type> exceptionTypes, HashSet <MethodBase> visitedMethods, Type[] localVars, Stack <Type> stack, int depth) { var ilReader = new ILReader(method); var allInstructions = ilReader.ToArray(); ILInstruction instruction; for (int i = 0; i < allInstructions.Length; i++) { instruction = allInstructions[i]; if (instruction is InlineMethodInstruction) { var methodInstruction = (InlineMethodInstruction)instruction; if (!visitedMethods.Contains(methodInstruction.Method)) { visitedMethods.Add(methodInstruction.Method); GetAllExceptions(methodInstruction.Method, exceptionTypes, visitedMethods, localVars, stack, depth + 1); } var curMethod = methodInstruction.Method; if (curMethod is ConstructorInfo) { stack.Push(((ConstructorInfo)curMethod).DeclaringType); } else if (method is MethodInfo) { stack.Push(((MethodInfo)curMethod).ReturnParameter.ParameterType); } } else if (instruction is InlineFieldInstruction) { var fieldInstruction = (InlineFieldInstruction)instruction; stack.Push(fieldInstruction.Field.FieldType); } else if (instruction is ShortInlineBrTargetInstruction) { } else if (instruction is InlineBrTargetInstruction) { } else { switch (instruction.OpCode.Value) { // ld* case 0x06: stack.Push(localVars[0]); break; case 0x07: stack.Push(localVars[1]); break; case 0x08: stack.Push(localVars[2]); break; case 0x09: stack.Push(localVars[3]); break; case 0x11: { var index = (ushort)allInstructions[i + 1].OpCode.Value; stack.Push(localVars[index]); break; } // st* case 0x0A: localVars[0] = stack.Pop(); break; case 0x0B: localVars[1] = stack.Pop(); break; case 0x0C: localVars[2] = stack.Pop(); break; case 0x0D: localVars[3] = stack.Pop(); break; case 0x13: { var index = (ushort)allInstructions[i + 1].OpCode.Value; localVars[index] = stack.Pop(); break; } // throw case 0x7A: if (stack.Peek() == null) { break; } if (!typeof(Exception).IsAssignableFrom(stack.Peek())) { //var ops = allInstructions.Select(f => f.OpCode).ToArray(); //break; } exceptionTypes.Add(stack.Pop()); break; default: switch (instruction.OpCode.StackBehaviourPop) { case StackBehaviour.Pop0: break; case StackBehaviour.Pop1: case StackBehaviour.Popi: case StackBehaviour.Popref: case StackBehaviour.Varpop: stack.Pop(); break; case StackBehaviour.Pop1_pop1: case StackBehaviour.Popi_pop1: case StackBehaviour.Popi_popi: case StackBehaviour.Popi_popi8: case StackBehaviour.Popi_popr4: case StackBehaviour.Popi_popr8: case StackBehaviour.Popref_pop1: case StackBehaviour.Popref_popi: stack.Pop(); stack.Pop(); break; case StackBehaviour.Popref_popi_pop1: case StackBehaviour.Popref_popi_popi: case StackBehaviour.Popref_popi_popi8: case StackBehaviour.Popref_popi_popr4: case StackBehaviour.Popref_popi_popr8: case StackBehaviour.Popref_popi_popref: stack.Pop(); stack.Pop(); stack.Pop(); break; } switch (instruction.OpCode.StackBehaviourPush) { case StackBehaviour.Push0: break; case StackBehaviour.Push1: case StackBehaviour.Pushi: case StackBehaviour.Pushi8: case StackBehaviour.Pushr4: case StackBehaviour.Pushr8: case StackBehaviour.Pushref: case StackBehaviour.Varpush: stack.Push(null); break; case StackBehaviour.Push1_push1: stack.Push(null); stack.Push(null); break; } break; } } } }
public static void Scan(ref DependencyList list, NodeFactory factory, MethodIL methodIL, ScanModes modes) { ILReader reader = new ILReader(methodIL.GetILBytes()); Tracker tracker = new Tracker(methodIL); // The algorithm here is really primitive: we scan the IL forward in a single pass, remembering // the last type/string/token we saw. // // We then intrinsically recognize a couple methods that consume this information. // // This has obvious problems since we don't have exact knowledge of the parameters passed // (something being in front of a call doesn't mean it's a parameter to the call). But since // this is a heuristic, it's okay. We want this to be as fast as possible. // // The main purposes of this scanner is to make following patterns work: // // * Enum.GetValues(typeof(Foo)) - this is very common and we need to make sure Foo[] is compiled. // * Type.GetType("Foo, Bar").GetMethod("Blah") - framework uses this to work around layering problems. // * typeof(Foo<>).MakeGenericType(arg).GetMethod("Blah") - used in e.g. LINQ expressions implementation // * typeof(Foo<>).GetProperty("Blah") - used in e.g. LINQ expressions implementation // * Marshal.SizeOf(typeof(Foo)) - very common and we need to make sure interop data is generated while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: tracker.TrackStringToken(reader.ReadILToken()); break; case ILOpcode.ldtoken: int token = reader.ReadILToken(); if (IsTypeEqualityTest(methodIL, reader, out ILReader newReader)) { reader = newReader; } else { tracker.TrackLdTokenToken(token); TypeDesc type = methodIL.GetObject(token) as TypeDesc; if (type != null && !type.IsCanonicalSubtype(CanonicalFormKind.Any)) { list = list ?? new DependencyList(); list.Add(factory.MaximallyConstructableType(type), "Unknown LDTOKEN use"); } } break; case ILOpcode.call: case ILOpcode.callvirt: var method = methodIL.GetObject(reader.ReadILToken()) as MethodDesc; if (method != null) { HandleCall(ref list, factory, methodIL, method, ref tracker, modes); } break; default: reader.Skip(opcode); break; } } }
/// <summary> /// Parse MIbcGroup method and return enumerable of MethodProfileData /// /// Like the AssemblyDictionary method, data is encoded via IL instructions. The format is /// /// ldtoken methodInProfileData /// Any series of instructions that does not include pop. Expansion data is encoded via ldstr "id" /// followed by a expansion specific sequence of il opcodes. /// pop /// {Repeat N times for N methods described} /// /// Extensions supported with current parser: /// /// ldstr "ExclusiveWeight" /// Any ldc.i4 or ldc.r4 or ldc.r8 instruction to indicate the exclusive weight /// /// ldstr "WeightedCallData" /// ldc.i4 <Count of methods called> /// Repeat <Count of methods called times> /// ldtoken <Method called from this method> /// ldc.i4 <Weight associated with calling the <Method called from this method>> /// /// This format is designed to be extensible to hold more data as we add new per method profile data without breaking existing parsers. /// </summary> static IEnumerable <MethodProfileData> ReadMIbcGroup(TypeSystemContext tsc, EcmaMethod method) { EcmaMethodIL ilBody = EcmaMethodIL.Create(method); MetadataLoaderForPgoData metadataLoader = new MetadataLoaderForPgoData(ilBody); ILReader ilReader = new ILReader(ilBody.GetILBytes()); object methodInProgress = null; object metadataNotResolvable = new object(); object metadataObject = null; MibcGroupParseState state = MibcGroupParseState.LookingForNextMethod; int intValue = 0; int weightedCallGraphSize = 0; int profileEntryFound = 0; double exclusiveWeight = 0; Dictionary <MethodDesc, int> weights = null; bool processIntValue = false; List <long> instrumentationDataLongs = null; PgoSchemaElem[] pgoSchemaData = null; while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); processIntValue = false; switch (opcode) { case ILOpcode.ldtoken: { int token = ilReader.ReadILToken(); if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add(token); } else { metadataObject = null; try { metadataObject = ilBody.GetObject(token); } catch (TypeSystemException) { // The method being referred to may be missing. In that situation, // use the metadataNotResolvable sentinel to indicate that this record should be ignored metadataObject = metadataNotResolvable; } switch (state) { case MibcGroupParseState.ProcessingCallgraphToken: state = MibcGroupParseState.ProcessingCallgraphWeight; break; case MibcGroupParseState.LookingForNextMethod: methodInProgress = metadataObject; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } } } break; case ILOpcode.ldc_r4: { float fltValue = ilReader.ReadILFloat(); switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = fltValue; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } break; } case ILOpcode.ldc_r8: { double dblValue = ilReader.ReadILDouble(); switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = dblValue; state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } break; } case ILOpcode.ldc_i4_0: intValue = 0; processIntValue = true; break; case ILOpcode.ldc_i4_1: intValue = 1; processIntValue = true; break; case ILOpcode.ldc_i4_2: intValue = 2; processIntValue = true; break; case ILOpcode.ldc_i4_3: intValue = 3; processIntValue = true; break; case ILOpcode.ldc_i4_4: intValue = 4; processIntValue = true; break; case ILOpcode.ldc_i4_5: intValue = 5; processIntValue = true; break; case ILOpcode.ldc_i4_6: intValue = 6; processIntValue = true; break; case ILOpcode.ldc_i4_7: intValue = 7; processIntValue = true; break; case ILOpcode.ldc_i4_8: intValue = 8; processIntValue = true; break; case ILOpcode.ldc_i4_m1: intValue = -1; processIntValue = true; break; case ILOpcode.ldc_i4_s: intValue = (sbyte)ilReader.ReadILByte(); processIntValue = true; break; case ILOpcode.ldc_i4: intValue = (int)ilReader.ReadILUInt32(); processIntValue = true; break; case ILOpcode.ldc_i8: if (state == MibcGroupParseState.ProcessingInstrumentationData) { instrumentationDataLongs.Add((long)ilReader.ReadILUInt64()); } break; case ILOpcode.ldstr: { int userStringToken = ilReader.ReadILToken(); string optionalDataName = (string)ilBody.GetObject(userStringToken); switch (optionalDataName) { case "ExclusiveWeight": state = MibcGroupParseState.ProcessingExclusiveWeight; break; case "WeightedCallData": state = MibcGroupParseState.ProcessingCallgraphCount; break; case "InstrumentationDataStart": state = MibcGroupParseState.ProcessingInstrumentationData; instrumentationDataLongs = new List <long>(); break; case "InstrumentationDataEnd": if (instrumentationDataLongs != null) { instrumentationDataLongs.Add(2); // MarshalMask 2 (Type) instrumentationDataLongs.Add(0); // PgoInstrumentationKind.Done (0) pgoSchemaData = PgoProcessor.ParsePgoData <TypeSystemEntityOrUnknown>(metadataLoader, instrumentationDataLongs, false).ToArray(); } state = MibcGroupParseState.LookingForOptionalData; break; default: state = MibcGroupParseState.LookingForOptionalData; break; } } break; case ILOpcode.pop: if (methodInProgress != metadataNotResolvable) { profileEntryFound++; if (exclusiveWeight == 0) { // If no exclusive weight is found assign a non zero value that assumes the order in the pgo file is significant. exclusiveWeight = Math.Min(1000000.0 - profileEntryFound, 0.0) / 1000000.0; } MethodProfileData mibcData = new MethodProfileData((MethodDesc)methodInProgress, MethodProfilingDataFlags.ReadMethodCode, exclusiveWeight, weights, 0xFFFFFFFF, pgoSchemaData); state = MibcGroupParseState.LookingForNextMethod; exclusiveWeight = 0; weights = null; instrumentationDataLongs = null; pgoSchemaData = null; yield return(mibcData); } methodInProgress = null; break; default: state = MibcGroupParseState.LookingForOptionalData; ilReader.Skip(opcode); break; } if (processIntValue) { switch (state) { case MibcGroupParseState.ProcessingExclusiveWeight: exclusiveWeight = intValue; state = MibcGroupParseState.LookingForOptionalData; break; case MibcGroupParseState.ProcessingCallgraphCount: weightedCallGraphSize = intValue; weights = new Dictionary <MethodDesc, int>(); if (weightedCallGraphSize > 0) { state = MibcGroupParseState.ProcessingCallgraphToken; } else { state = MibcGroupParseState.LookingForOptionalData; } break; case MibcGroupParseState.ProcessingCallgraphWeight: if (metadataObject != metadataNotResolvable) { weights.Add((MethodDesc)metadataObject, intValue); } weightedCallGraphSize--; if (weightedCallGraphSize > 0) { state = MibcGroupParseState.ProcessingCallgraphToken; } else { state = MibcGroupParseState.LookingForOptionalData; } break; case MibcGroupParseState.ProcessingInstrumentationData: instrumentationDataLongs.Add(intValue); break; default: state = MibcGroupParseState.LookingForOptionalData; instrumentationDataLongs = null; break; } } } }
public void Run(ILFunction function, ILTransformContext context) { this.context = context; foreach (var inst in function.Descendants.OfType <CallInstruction>()) { MethodDefinition methodDef = context.TypeSystem.GetCecil(inst.Method) as MethodDefinition; if (methodDef != null && methodDef.Body != null) { if (IsDefinedInCurrentOrOuterClass(inst.Method, context.Function.Method.DeclaringTypeDefinition) && inst.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { // partially copied from CSharpDecompiler var specializingTypeSystem = this.context.TypeSystem.GetSpecializingTypeSystem(inst.Method.Substitution); var ilReader = new ILReader(specializingTypeSystem); System.Threading.CancellationToken cancellationToken = new System.Threading.CancellationToken(); var proxyFunction = ilReader.ReadIL(methodDef.Body, cancellationToken); var transformContext = new ILTransformContext(proxyFunction, specializingTypeSystem, this.context.Settings) { CancellationToken = cancellationToken, DecompileRun = context.DecompileRun }; foreach (var transform in CSharp.CSharpDecompiler.GetILTransforms()) { if (transform.GetType() != typeof(ProxyCallReplacer)) // don't call itself on itself { cancellationToken.ThrowIfCancellationRequested(); transform.Run(proxyFunction, transformContext); } } if (!(proxyFunction.Body is BlockContainer blockContainer)) { return; } if (blockContainer.Blocks.Count != 1) { return; } var block = blockContainer.Blocks[0]; Call call = null; if (block.Instructions.Count == 1) { // leave IL_0000 (call Test(ldloc this, ldloc A_1)) if (!block.Instructions[0].MatchLeave(blockContainer, out ILInstruction returnValue)) { return; } call = returnValue as Call; } else if (block.Instructions.Count == 2) { // call Test(ldloc this, ldloc A_1) // leave IL_0000(nop) call = block.Instructions[0] as Call; if (!block.Instructions[1].MatchLeave(blockContainer, out ILInstruction returnValue)) { return; } if (!returnValue.MatchNop()) { return; } } if (call == null) { return; } if (call.Method.IsConstructor) { return; } // check if original arguments are only correct ldloc calls for (int i = 0; i < call.Arguments.Count; i++) { var originalArg = call.Arguments[i]; if (!originalArg.MatchLdLoc(out ILVariable var) || var.Kind != VariableKind.Parameter || var.Index != i - 1) { return; } } Call newInst = (Call)call.Clone(); newInst.Arguments.ReplaceList(inst.Arguments); inst.ReplaceWith(newInst); } } } }
/// <summary>Checks the specified assemblies for any obvious Lingo-related problems, including unused strings, mismatched enum translations.</summary> /// <typeparam name="TTranslation">The type of the translation class.</typeparam> /// <param name="rep">Post-build step reporter.</param> /// <param name="assemblies">A list of assemblies to check. The Lingo assembly is included automatically to ensure correct operation.</param> public static void PostBuildStep <TTranslation>(IPostBuildReporter rep, params Assembly[] assemblies) { if (!assemblies.Contains(Assembly.GetExecutingAssembly())) { assemblies = assemblies.Concat(Assembly.GetExecutingAssembly()).ToArray(); } // Check that all enum translations are sensible var allEnumTrs = allEnumTranslations(assemblies).ToList(); foreach (var tr in allEnumTrs) { checkEnumTranslation(rep, tr.EnumType, tr.TranslationType); } // Check all component model member translations foreach (var type in assemblies.SelectMany(a => a.GetTypes())) { // All functions returning MemberTr and accepting a TranslationBase descendant must conform var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.Name).ToHashSet(); foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (method.ReturnType == typeof(MemberTr) && method.GetParameters().Length == 1 && typeof(TranslationBase).IsAssignableFrom(method.GetParameters()[0].ParameterType)) { if (!method.IsStatic) { rep.Error("A member translation method must be static. Translation method: {0}".Fmt(method.DeclaringType.FullName + "." + method.Name), "class " + method.DeclaringType.Name, typeof(MemberTr).Name + " " + method.Name); } if (!method.Name.EndsWith("Tr")) { rep.Error("The name of a member translation method must end with the letters \"Tr\". Translation method: {0}".Fmt(method.DeclaringType.FullName + "." + method.Name), "class " + method.DeclaringType.Name, typeof(MemberTr).Name + " " + method.Name); } var propertyName = method.Name.Substring(0, method.Name.Length - 2); if (!properties.Contains(propertyName)) { rep.Error("Member translation method has no corresponding property named \"{1}\". Translation method: {0}".Fmt(method.DeclaringType.FullName + "." + method.Name, propertyName), "class " + method.DeclaringType.Name, typeof(MemberTr).Name + " " + method.Name); } } } } // Find unused strings var fields = new HashSet <FieldInfo>(); addAllLingoRelevantFields(typeof(TTranslation), fields); // Treat all fields used for enum translations as used foreach (var f in allEnumTrs.SelectMany(et => et.TranslationType.GetAllFields())) { fields.Remove(f); } // Treat all fields that occur in a ldfld / ldflda instruction as used foreach (var mod in assemblies.SelectMany(a => a.GetModules(false))) { foreach (var typ in mod.GetTypes()) { foreach (var meth in typ.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).Where(m => m.DeclaringType == typ).Cast <MethodBase>().Concat( typ.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).Where(c => c.DeclaringType == typ).Cast <MethodBase>())) { foreach (var instr in ILReader.ReadIL(meth, typ)) { if (instr.OpCode == OpCodes.Ldfld || instr.OpCode == OpCodes.Ldflda) { fields.Remove((FieldInfo)instr.Operand); if (fields.Count == 0) { goto done; // don't have to break the loop early, but there's really no point in searching the rest of the code now } } } } } } // Report warnings for all unused strings (not errors so that the developer can test things in the presence of unused strings) done: foreach (var field in fields) { rep.Warning("Unused Lingo field: " + field.DeclaringType.FullName + "." + field.Name, "class " + field.DeclaringType.Name, field.FieldType.Name, field.Name); } }
private void Transform(ModuleDefinition mod, MethodDefinition method, MethodDefinition newMethod, FieldDefinition expressionField, out TypeDefinition closureType) { var writer = newMethod.Body.GetILProcessor(); Dictionary <ParameterReference, VariableDefinition> oldParameterToNNewVariable = new Dictionary <ParameterReference, VariableDefinition>(); newMethod.Body.InitLocals = true; writer.Emit(OpCodes.Nop); var allParameters = method.Parameters.ToList(); if (method.Body.ThisParameter != null) { allParameters.Insert(0, method.Body.ThisParameter); } foreach (var p in allParameters) { var variable = new VariableDefinition(mod.ImportReference(ParameterExpression)); newMethod.Body.Variables.Add(variable); writer.Emit(OpCodes.Ldtoken, p.ParameterType); writer.Emit(OpCodes.Call, mod.ImportReference(this.Type_GetTypeFromHandle)); writer.Emit(OpCodes.Ldstr, p == method.Body.ThisParameter ? "this" : p.Name); writer.Emit(OpCodes.Call, mod.ImportReference(this.Expression_Parameter)); writer.Emit(OpCodes.Stloc, variable.Index); oldParameterToNNewVariable.Add(p, variable); } method.Body.SimplifyMacros(); var reader = new ILReader(method.Body); //var c = new <>__DisplayClass bool isDup = false; Dictionary <MetadataToken, ParameterReference> captureFieldToParameter = new Dictionary <MetadataToken, ParameterReference>(); closureType = null; if (reader.Is(OpCodes.Newobj)) { var newObj = reader.Get(OpCodes.Newobj); closureType = ((MethodDefinition)newObj.Operand).DeclaringType; if (reader.TryGet(OpCodes.Stloc) != null) //General case { //c.a = a; while (LookaheadClosureAssignment(isDup, ref reader, out var oldParameter, out var fieldReference)) { captureFieldToParameter.Add(fieldReference.MetadataToken, oldParameter); } } else if (reader.TryGet(OpCodes.Dup) != null) //only found for 1 parameter in release { isDup = true; if (!LookaheadClosureAssignment(isDup, ref reader, out var oldParameter, out var fieldReference)) { throw new InvalidOperationException($"Not expected (in {method.FullName})"); } captureFieldToParameter.Add(fieldReference.MetadataToken, oldParameter); } } Dictionary <VariableDefinition, VariableDefinition> rebasedVariables = new Dictionary <VariableDefinition, VariableDefinition>(); foreach (var v in method.Body.Variables) { if (closureType != null && v.VariableType == closureType) { } else { var newVariable = new VariableDefinition(mod.ImportReference(v.VariableType)); rebasedVariables.Add(v, newVariable); newMethod.Body.Variables.Add(newVariable); } } while (reader.HasMore()) { if (LookaheadExpressionFieldConstant(isDup, ref reader, out var token)) { var param = captureFieldToParameter[token.Value]; writer.Emit(OpCodes.Ldloc, oldParameterToNNewVariable[param].Index); } else if (!method.IsStatic && LookaheadExpressionThisConstant(ref reader, method.DeclaringType)) { writer.Emit(OpCodes.Ldloc, oldParameterToNNewVariable[method.Body.ThisParameter].Index); } else if (LookaheadArray(ref reader)) { if (reader.HasMore()) { throw new InvalidOperationException($"The method {method.FullName} should only call As.Expression with an expression tree lambda"); } if (oldParameterToNNewVariable.Count == 0) { writer.Emit(OpCodes.Call, mod.ImportReference(new GenericInstanceMethod(this.Array_Empty) { GenericArguments = { this.ParameterExpression } })); } else { writer.Emit(OpCodes.Ldc_I4, oldParameterToNNewVariable.Count); writer.Emit(OpCodes.Newarr, mod.ImportReference(this.ParameterExpression)); for (int i = 0; i < oldParameterToNNewVariable.Count; i++) { writer.Emit(OpCodes.Dup); writer.Emit(OpCodes.Ldc_I4, i); writer.Emit(OpCodes.Ldloc, i); writer.Emit(OpCodes.Stelem_Ref); } } var funcType = ((GenericInstanceType)expressionField.FieldType).GenericArguments.Single(); writer.Emit(OpCodes.Call, mod.ImportReference(new GenericInstanceMethod(this.Expression_Lambda) { GenericArguments = { funcType } })); writer.Emit(OpCodes.Stsfld, expressionField); writer.Emit(OpCodes.Ret); writer.Body.OptimizeMacros(); { method.Body.Instructions.Clear(); method.Body.Variables.Clear(); var oldWriter = method.Body.GetILProcessor(); oldWriter.Emit(OpCodes.Ldsfld, expressionField); for (int i = 0; i < allParameters.Count; i++) { oldWriter.Emit(OpCodes.Ldarg, allParameters[i]); } var evaluate = ExpressionExtensions.Methods.Single(a => a.Name == "Evaluate" && a.GenericParameters.Count == allParameters.Count + 1); var evaluateInstance = new GenericInstanceMethod(evaluate); foreach (var p in allParameters) { evaluateInstance.GenericArguments.Add(p.ParameterType); } evaluateInstance.GenericArguments.Add(method.ReturnType); oldWriter.Emit(OpCodes.Call, mod.ImportReference(evaluateInstance)); oldWriter.Emit(OpCodes.Ret); oldWriter.Body.Optimize(); } return; } else if (reader.TryGet(OpCodes.Ldloc) is Instruction ldloc) { writer.Emit(ldloc.OpCode, rebasedVariables[(VariableDefinition)ldloc.Operand]); } else if (reader.TryGet(OpCodes.Stloc) is Instruction stloc) { writer.Emit(stloc.OpCode, rebasedVariables[(VariableDefinition)stloc.Operand]); } else if (reader.TryGet(OpCodes.Ldloca) is Instruction ldloca) { writer.Emit(ldloca.OpCode, rebasedVariables[(VariableDefinition)ldloca.Operand]); } else { var ins = reader.Get(); writer.Append(ins); } } throw new InvalidOperationException($"The method {method.FullName} should only call As.Expression with an expression tree lambda"); }
private void WalkMethod(EcmaMethod method) { Instantiation typeContext = method.OwningType.Instantiation; Instantiation methodContext = method.Instantiation; MethodSignature methodSig = method.Signature; ProcessTypeReference(methodSig.ReturnType, typeContext, methodContext); foreach (TypeDesc parameterType in methodSig) { ProcessTypeReference(parameterType, typeContext, methodContext); } if (method.IsAbstract) { return; } var methodIL = EcmaMethodIL.Create(method); if (methodIL == null) { return; } // Walk the method body looking at referenced things that have some genericness. // Nongeneric things cannot be forming cycles. // In particular, we don't care about MemberRefs to non-generic things, TypeDefs/MethodDefs/FieldDefs. // Avoid the work to even materialize type system entities for those. ILReader reader = new ILReader(methodIL.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.sizeof_: case ILOpcode.newarr: case ILOpcode.initobj: case ILOpcode.stelem: case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.box: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.cpobj: case ILOpcode.ldobj: case ILOpcode.castclass: case ILOpcode.isinst: case ILOpcode.stobj: case ILOpcode.refanyval: case ILOpcode.mkrefany: case ILOpcode.constrained: EntityHandle accessedType = MetadataTokens.EntityHandle(reader.ReadILToken()); typeCase: if (accessedType.Kind == HandleKind.TypeSpecification) { var t = methodIL.GetObject(MetadataTokens.GetToken(accessedType), NotFoundBehavior.ReturnNull) as TypeDesc; if (t != null) { ProcessTypeReference(t, typeContext, methodContext); } } break; case ILOpcode.stsfld: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.stfld: case ILOpcode.ldfld: case ILOpcode.ldflda: EntityHandle accessedField = MetadataTokens.EntityHandle(reader.ReadILToken()); fieldCase: if (accessedField.Kind == HandleKind.MemberReference) { accessedType = _metadataReader.GetMemberReference((MemberReferenceHandle)accessedField).Parent; goto typeCase; } break; case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: case ILOpcode.ldftn: case ILOpcode.ldvirtftn: case ILOpcode.jmp: EntityHandle accessedMethod = MetadataTokens.EntityHandle(reader.ReadILToken()); methodCase: if (accessedMethod.Kind == HandleKind.MethodSpecification || (accessedMethod.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedMethod).Parent.Kind == HandleKind.TypeSpecification)) { var m = methodIL.GetObject(MetadataTokens.GetToken(accessedMethod), NotFoundBehavior.ReturnNull) as MethodDesc; if (m != null) { ProcessTypeReference(m.OwningType, typeContext, methodContext); ProcessMethodCall(m, typeContext, methodContext); } } break; case ILOpcode.ldtoken: EntityHandle accessedEntity = MetadataTokens.EntityHandle(reader.ReadILToken()); if (accessedEntity.Kind == HandleKind.MethodSpecification || (accessedEntity.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedEntity).GetKind() == MemberReferenceKind.Method)) { accessedMethod = accessedEntity; goto methodCase; } else if (accessedEntity.Kind == HandleKind.MemberReference) { accessedField = accessedEntity; goto fieldCase; } else if (accessedEntity.Kind == HandleKind.TypeSpecification) { accessedType = accessedEntity; goto typeCase; } break; default: reader.Skip(opcode); break; } } }
public void Scan(MethodIL methodBody) { MethodDesc thisMethod = methodBody.OwningMethod; ValueBasicBlockPair[] locals = new ValueBasicBlockPair[methodBody.GetLocals().Length]; Dictionary <int, Stack <StackSlot> > knownStacks = new Dictionary <int, Stack <StackSlot> >(); Stack <StackSlot> currentStack = new Stack <StackSlot>(methodBody.MaxStack); ScanExceptionInformation(knownStacks, methodBody); BasicBlockIterator blockIterator = new BasicBlockIterator(methodBody); MethodReturnValue = null; ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { int curBasicBlock = blockIterator.MoveNext(reader.Offset); if (knownStacks.ContainsKey(reader.Offset)) { if (currentStack == null) { // The stack copy constructor reverses the stack currentStack = new Stack <StackSlot>(knownStacks[reader.Offset].Reverse()); } else { currentStack = MergeStack(currentStack, knownStacks[reader.Offset]); } } if (currentStack == null) { currentStack = new Stack <StackSlot>(methodBody.MaxStack); } int offset = reader.Offset; ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.add: case ILOpcode.add_ovf: case ILOpcode.add_ovf_un: case ILOpcode.and: case ILOpcode.div: case ILOpcode.div_un: case ILOpcode.mul: case ILOpcode.mul_ovf: case ILOpcode.mul_ovf_un: case ILOpcode.or: case ILOpcode.rem: case ILOpcode.rem_un: case ILOpcode.sub: case ILOpcode.sub_ovf: case ILOpcode.sub_ovf_un: case ILOpcode.xor: case ILOpcode.cgt: case ILOpcode.cgt_un: case ILOpcode.clt: case ILOpcode.clt_un: case ILOpcode.shl: case ILOpcode.shr: case ILOpcode.shr_un: case ILOpcode.ceq: PopUnknown(currentStack, 2, methodBody, offset); PushUnknown(currentStack); reader.Skip(opcode); break; case ILOpcode.dup: currentStack.Push(currentStack.Peek()); break; case ILOpcode.ldnull: currentStack.Push(new StackSlot(NullValue.Instance)); break; case ILOpcode.ldc_i4_0: case ILOpcode.ldc_i4_1: case ILOpcode.ldc_i4_2: case ILOpcode.ldc_i4_3: case ILOpcode.ldc_i4_4: case ILOpcode.ldc_i4_5: case ILOpcode.ldc_i4_6: case ILOpcode.ldc_i4_7: case ILOpcode.ldc_i4_8: { int value = opcode - ILOpcode.ldc_i4_0; ConstIntValue civ = new ConstIntValue(value); StackSlot slot = new StackSlot(civ); currentStack.Push(slot); } break; case ILOpcode.ldc_i4_m1: { ConstIntValue civ = new ConstIntValue(-1); StackSlot slot = new StackSlot(civ); currentStack.Push(slot); } break; case ILOpcode.ldc_i4: { int value = (int)reader.ReadILUInt32(); ConstIntValue civ = new ConstIntValue(value); StackSlot slot = new StackSlot(civ); currentStack.Push(slot); } break; case ILOpcode.ldc_i4_s: { int value = (sbyte)reader.ReadILByte(); ConstIntValue civ = new ConstIntValue(value); StackSlot slot = new StackSlot(civ); currentStack.Push(slot); } break; case ILOpcode.arglist: case ILOpcode.ldftn: case ILOpcode.sizeof_: case ILOpcode.ldc_i8: case ILOpcode.ldc_r4: case ILOpcode.ldc_r8: PushUnknown(currentStack); reader.Skip(opcode); break; case ILOpcode.ldarg: case ILOpcode.ldarg_0: case ILOpcode.ldarg_1: case ILOpcode.ldarg_2: case ILOpcode.ldarg_3: case ILOpcode.ldarg_s: case ILOpcode.ldarga: case ILOpcode.ldarga_s: ScanLdarg(opcode, opcode switch { ILOpcode.ldarg => reader.ReadILUInt16(), ILOpcode.ldarga => reader.ReadILUInt16(), ILOpcode.ldarg_s => reader.ReadILByte(), ILOpcode.ldarga_s => reader.ReadILByte(), _ => opcode - ILOpcode.ldarg_0 }, currentStack, thisMethod); break;
internal TypeCache(MetadataType type, Logger?logger, ILProvider ilProvider) { Debug.Assert(type == type.GetTypeDefinition()); Debug.Assert(!CompilerGeneratedNames.IsGeneratedMemberName(type.Name)); Type = type; var callGraph = new CompilerGeneratedCallGraph(); var userDefinedMethods = new HashSet <MethodDesc>(); void ProcessMethod(MethodDesc method) { Debug.Assert(method == method.GetTypicalMethodDefinition()); bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) { if (!isStateMachineMember) { // If it's not a nested function, track as an entry point to the call graph. var added = userDefinedMethods.Add(method); Debug.Assert(added); } } else { // We don't expect lambdas or local functions to be emitted directly into // state machine types. Debug.Assert(!isStateMachineMember); } // Discover calls or references to lambdas or local functions. This includes // calls to local functions, and lambda assignments (which use ldftn). var methodBody = ilProvider.GetMethodIL(method); if (methodBody != null) { ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldftn: case ILOpcode.ldtoken: case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: { MethodDesc?referencedMethod = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc; if (referencedMethod == null) { continue; } referencedMethod = referencedMethod.GetTypicalMethodDefinition(); if (referencedMethod.IsConstructor && referencedMethod.OwningType is MetadataType generatedType && // Don't consider calls in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); // fill in null for now, attribute providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } continue; } if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name)) { continue; } if (isStateMachineMember) { callGraph.TrackCall((MetadataType)method.OwningType, referencedMethod); } else { callGraph.TrackCall(method, referencedMethod); } } break; case ILOpcode.stsfld: { // Same as above, but stsfld instead of a call to the constructor FieldDesc?field = methodBody.GetObject(reader.ReadILToken()) as FieldDesc; if (field == null) { continue; } field = field.GetTypicalFieldDefinition(); if (field.OwningType is MetadataType generatedType && // Don't consider field accesses in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { // It's expected that there may be multiple methods associated with the same static closure environment. // All of these methods will substitute the same type arguments into the closure environment // (if it is generic). Don't warn. } continue; } } break; default: reader.Skip(opcode); break; } } } if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) { Debug.Assert(stateMachineType.ContainingType == type || (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && stateMachineType.ContainingType.ContainingType == type)); Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); callGraph.TrackCall(method, stateMachineType); _compilerGeneratedTypeToUserCodeMethod ??= new Dictionary <MetadataType, MethodDesc>(); if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) { var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); } // Already warned above if multiple methods map to the same type // Fill in null for argument providers now, the real providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo(method, null); } } // Look for state machine methods, and methods which call local functions. foreach (MethodDesc method in type.GetMethods()) { ProcessMethod(method); } // Also scan compiler-generated state machine methods (in case they have calls to nested functions), // and nested functions inside compiler-generated closures (in case they call other nested functions). // State machines can be emitted into lambda display classes, so we need to go down at least two // levels to find calls from iterator nested functions to other nested functions. We just recurse into // all compiler-generated nested types to avoid depending on implementation details. foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) { foreach (var method in nestedType.GetMethods()) { ProcessMethod(method); } } // Now we've discovered the call graphs for calls to nested functions. // Use this to map back from nested functions to the declaring user methods. // Note: This maps all nested functions back to the user code, not to the immediately // declaring local function. The IL doesn't contain enough information in general for // us to determine the nesting of local functions and lambdas. // Note: this only discovers nested functions which are referenced from the user // code or its referenced nested functions. There is no reliable way to determine from // IL which user code an unused nested function belongs to. foreach (var userDefinedMethod in userDefinedMethods) { var callees = callGraph.GetReachableMembers(userDefinedMethod); if (!callees.Any()) { continue; } _compilerGeneratedMembers ??= new Dictionary <MethodDesc, List <TypeSystemEntity> >(); _compilerGeneratedMembers.Add(userDefinedMethod, new List <TypeSystemEntity>(callees)); foreach (var compilerGeneratedMember in callees) { switch (compilerGeneratedMember) { case MethodDesc nestedFunction: Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); // Nested functions get suppressions from the user method only. _compilerGeneratedMethodToUserCodeMethod ??= new Dictionary <MethodDesc, MethodDesc>(); if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) { var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); } break; case MetadataType stateMachineType: // Types in the call graph are always state machine types // For those all their methods are not tracked explicitly in the call graph; instead, they // are represented by the state machine type itself. // We are already tracking the association of the state machine type to the user code method // above, so no need to track it here. Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); break; default: throw new InvalidOperationException(); } } } // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute // providers if (_generatedTypeToTypeArgumentInfo != null) { foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { Debug.Assert(generatedType == generatedType.GetTypeDefinition()); if (HasGenericParameters(generatedType)) { MapGeneratedTypeTypeParameters(generatedType); } } }
private bool IsNonVersionableWithILTokensThatDoNotNeedTranslationUncached(EcmaMethod method) { bool result = false; try { // Validate that there are no tokens in the IL other than tokens associated with the following // instructions with the // 1. ldfld, ldflda, and stfld to instance fields of NonVersionable structs and NonVersionable classes // 2. cpobj, initobj, ldobj, stobj, ldelem, ldelema or sizeof, to NonVersionable structures, signature variables, pointers, function pointers, byrefs, classes, or arrays // 3. stelem, to NonVersionable structures // In addition, the method must not have any EH. // The method may only have locals which are NonVersionable structures, or classes MethodIL methodIL = new ReadyToRunILProvider().GetMethodIL(method); if (methodIL.GetExceptionRegions().Length > 0) { return(false); } foreach (var local in methodIL.GetLocals()) { if (local.Type.IsPrimitive) { continue; } if (local.Type.IsArray) { continue; } if (local.Type.IsSignatureVariable) { continue; } MetadataType metadataType = local.Type as MetadataType; if (metadataType == null) { return(false); } if (metadataType.IsValueType) { if (metadataType.IsNonVersionable()) { continue; } else { return(false); } } } ILReader ilReader = new ILReader(methodIL.GetILBytes()); while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldfld: case ILOpcode.ldflda: case ILOpcode.stfld: { int token = ilReader.ReadILToken(); FieldDesc field = methodIL.GetObject(token) as FieldDesc; if (field == null) { return(false); } if (field.IsStatic) { return(false); } MetadataType owningMetadataType = (MetadataType)field.OwningType; if (!owningMetadataType.IsNonVersionable()) { return(false); } break; } case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.stobj: case ILOpcode.ldobj: case ILOpcode.initobj: case ILOpcode.cpobj: case ILOpcode.sizeof_: { int token = ilReader.ReadILToken(); TypeDesc type = methodIL.GetObject(token) as TypeDesc; if (type == null) { return(false); } MetadataType metadataType = type as MetadataType; if (metadataType == null) { continue; // Types which are not metadata types are all well defined in size } if (!metadataType.IsValueType) { continue; // Reference types are all well defined in size for the sizeof instruction } if (metadataType.IsNonVersionable()) { continue; } return(false); } case ILOpcode.stelem: { int token = ilReader.ReadILToken(); MetadataType type = methodIL.GetObject(token) as MetadataType; if (type == null) { return(false); } if (!type.IsValueType) { return(false); } if (!type.IsNonVersionable()) { return(false); } break; } // IL instructions which refer to tokens which are not safe for NonVersionable methods case ILOpcode.box: case ILOpcode.call: case ILOpcode.calli: case ILOpcode.callvirt: case ILOpcode.castclass: case ILOpcode.jmp: case ILOpcode.isinst: case ILOpcode.ldstr: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.ldtoken: case ILOpcode.ldvirtftn: case ILOpcode.ldftn: case ILOpcode.mkrefany: case ILOpcode.newarr: case ILOpcode.newobj: case ILOpcode.refanyval: case ILOpcode.stsfld: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.constrained: return(false); default: // Unless its a opcode known to be permitted with a ilReader.Skip(opcode); break; } } result = true; } catch (TypeSystemException) { return(false); } return(result); }
public Dictionary <long, EventRegistration[]> DecompileEventMappings(string fullTypeName, CancellationToken cancellationToken) { var result = new Dictionary <long, EventRegistration[]>(); TypeDefinition type = this.assembly.MainModule.GetType(fullTypeName); if (type == null) { return(result); } MethodDefinition method = null; foreach (var m in type.Methods) { if (m.Name == "System.Windows.Markup.IComponentConnector.Connect") { method = m; break; } } if (method == null) { return(result); } // decompile method and optimize the switch var typeSystem = new DecompilerTypeSystem(method.Module); var ilReader = new ILReader(typeSystem); var function = ilReader.ReadIL(method.Body, cancellationToken); var context = new ILTransformContext(function, typeSystem) { CancellationToken = cancellationToken }; function.RunTransforms(CSharpDecompiler.GetILTransforms(), context); var block = function.Body.Children.OfType <Block>().First(); var ilSwitch = block.Children.OfType <SwitchInstruction>().FirstOrDefault(); if (ilSwitch != null) { foreach (var section in ilSwitch.Sections) { var events = FindEvents(section.Body); foreach (long id in section.Labels.Values) { result.Add(id, events); } } } else { foreach (var ifInst in function.Descendants.OfType <IfInstruction>()) { var comp = ifInst.Condition as Comp; if (comp.Kind != ComparisonKind.Inequality && comp.Kind != ComparisonKind.Equality) { continue; } int id; if (!comp.Right.MatchLdcI4(out id)) { continue; } var events = FindEvents(comp.Kind == ComparisonKind.Inequality ? ifInst.FalseInst : ifInst.TrueInst); result.Add(id, events); } } return(result); }
public static void GetAllExceptions(MethodBase method, HashSet <Type> exceptionTypes, HashSet <MethodBase> visitedMethods, int depth) { var ilReader = new ILReader(method); var allInstructions = ilReader.ToArray(); var localVars = new Type[255]; var stack = new Stack <Type>(); ILInstruction instruction; for (int i = 0; i < allInstructions.Length; i++) { instruction = allInstructions[i]; if (instruction is InlineMethodInstruction) { var methodInstruction = (InlineMethodInstruction)instruction; var curMethod = methodInstruction.Method; if (curMethod is ConstructorInfo) { stack.Push(((ConstructorInfo)curMethod).DeclaringType); } else if (method is MethodInfo) { stack.Push(((MethodInfo)curMethod).ReturnParameter.ParameterType); } if (!visitedMethods.Contains(methodInstruction.Method)) { visitedMethods.Add(methodInstruction.Method); GetAllExceptions(methodInstruction.Method, exceptionTypes, visitedMethods, depth + 1); } } else if (instruction is InlineFieldInstruction) { var fieldInstruction = (InlineFieldInstruction)instruction; stack.Push(fieldInstruction.Field.FieldType); } else if (instruction is ShortInlineBrTargetInstruction) { // } else if (instruction is InlineBrTargetInstruction) { // } else { switch (instruction.OpCode.Value) { case 0x06: stack.Push(localVars[0]); break; case 0x07: stack.Push(localVars[1]); break; case 0x08: stack.Push(localVars[2]); break; case 0x09: stack.Push(localVars[3]); break; case 0x0A: localVars[0] = stack.Pop(); break; case 0x0B: localVars[1] = stack.Pop(); break; case 0x0C: localVars[2] = stack.Pop(); break; case 0x0D: localVars[3] = stack.Pop(); break; case 0x11: { int index = allInstructions[i + 1].OpCode.Value; stack.Push(localVars[index]); break; } case 0x13: { int index = allInstructions[i + 1].OpCode.Value; localVars[index] = stack.Pop(); break; } case 0x7A: // throw exceptionTypes.Add(stack.Pop()); break; } } } }
public CallCollector(Mono.Cecil.ModuleDefinition module) { typeSystem = new DecompilerTypeSystem(module); ilReader = new ILReader(typeSystem); decompiler = new CSharpDecompiler(typeSystem, new ICSharpCode.Decompiler.DecompilerSettings()); }
/// <summary> /// Parse an MIBC file for the methods that are interesting. /// The version bubble must be specified and will describe the restrict the set of methods parsed to those relevant to the compilation /// The onlyDefinedInAssembly parameter is used to restrict the set of types parsed to include only those which are defined in a specific module. Specify null to allow definitions from all modules. /// This limited parsing is not necessarily an exact set of prevention, so detailed algorithms that work at the individual method level are still necessary, but this allows avoiding excessive parsing. /// /// The format of the Mibc file is that of a .NET dll, with a global method named "AssemblyDictionary". Inside of that file are a series of references that are broken up by which assemblies define the individual methods. /// These references are encoded as IL code that represents the details. /// The format of these IL instruction is as follows. /// /// ldstr mibcGroupName /// ldtoken mibcGroupMethod /// pop /// {Repeat the above pattern N times, once per Mibc group} /// /// See comment above ReadMIbcGroup for details of the group format /// /// The mibcGroupName is in the following format "Assembly_{definingAssemblyName};{OtherAssemblyName};{OtherAssemblyName};...; (OtherAssemblyName is ; delimited) /// /// </summary> /// <returns></returns> public static ProfileData ParseMIbcFile(TypeSystemContext tsc, PEReader peReader, HashSet <string> assemblyNamesInVersionBubble, string onlyDefinedInAssembly) { var mibcModule = EcmaModule.Create(tsc, peReader, null, null, new CustomCanonResolver(tsc)); var assemblyDictionary = (EcmaMethod)mibcModule.GetGlobalModuleType().GetMethod("AssemblyDictionary", null); IEnumerable <MethodProfileData> loadedMethodProfileData = Enumerable.Empty <MethodProfileData>(); EcmaMethodIL ilBody = EcmaMethodIL.Create(assemblyDictionary); ILReader ilReader = new ILReader(ilBody.GetILBytes()); string mibcGroupName = ""; while (ilReader.HasNext) { ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldstr: int userStringToken = ilReader.ReadILToken(); Debug.Assert(mibcGroupName == ""); if (mibcGroupName == "") { mibcGroupName = (string)ilBody.GetObject(userStringToken); } break; case ILOpcode.ldtoken: int token = ilReader.ReadILToken(); if (String.IsNullOrEmpty(mibcGroupName)) { break; } string[] assembliesByName = mibcGroupName.Split(';'); bool hasMatchingDefinition = (onlyDefinedInAssembly == null) || assembliesByName[0].Equals(onlyDefinedInAssembly); if (!hasMatchingDefinition) { break; } if (assemblyNamesInVersionBubble != null) { bool areAllEntriesInVersionBubble = true; foreach (string s in assembliesByName) { if (string.IsNullOrEmpty(s)) { continue; } if (!assemblyNamesInVersionBubble.Contains(s)) { areAllEntriesInVersionBubble = false; break; } } if (!areAllEntriesInVersionBubble) { break; } } loadedMethodProfileData = loadedMethodProfileData.Concat(ReadMIbcGroup(tsc, (EcmaMethod)ilBody.GetObject(token))); break; case ILOpcode.pop: mibcGroupName = ""; break; default: ilReader.Skip(opcode); break; } } return(new IBCProfileData(false, loadedMethodProfileData)); }
private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant) { if ((flags[offset] & OpcodeFlags.BasicBlockStart) != 0) { constant = 0; return(false); } for (int currentOffset = offset - 1; currentOffset >= 0; currentOffset--) { if ((flags[currentOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } ILReader reader = new ILReader(body, currentOffset); ILOpcode opcode = reader.ReadILOpcode(); if (opcode == ILOpcode.call || opcode == ILOpcode.callvirt) { MethodDesc method = (MethodDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { BodySubstitution substitution = GetSubstitution(method); if (substitution != null && substitution.Value is int && (opcode != ILOpcode.callvirt || !method.IsVirtual)) { constant = (int)substitution.Value; return(true); } else { constant = 0; return(false); } } argIndex--; if (method.Signature.Length > 0 || !method.Signature.IsStatic) { // We don't know how to skip over the parameters break; } } else if (opcode == ILOpcode.ldsfld) { FieldDesc field = (FieldDesc)methodIL.GetObject(reader.ReadILToken()); if (argIndex == 0) { object substitution = GetSubstitution(field); if (substitution is int) { constant = (int)substitution; return(true); } else { constant = 0; return(false); } } argIndex--; } else if (opcode >= ILOpcode.ldc_i4_0 && opcode <= ILOpcode.ldc_i4_8) { if (argIndex == 0) { constant = opcode - ILOpcode.ldc_i4_0; return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4) { if (argIndex == 0) { constant = (int)reader.ReadILUInt32(); return(true); } argIndex--; } else if (opcode == ILOpcode.ldc_i4_s) { if (argIndex == 0) { constant = (int)(sbyte)reader.ReadILByte(); return(true); } argIndex--; } else if ((opcode == ILOpcode.ldloc || opcode == ILOpcode.ldloc_s || (opcode >= ILOpcode.ldloc_0 && opcode <= ILOpcode.ldloc_3)) && ((flags[currentOffset] & OpcodeFlags.BasicBlockStart) == 0)) { // Paired stloc/ldloc that the C# compiler generates in debug code? int locIndex = opcode switch { ILOpcode.ldloc => reader.ReadILUInt16(), ILOpcode.ldloc_s => reader.ReadILByte(), _ => opcode - ILOpcode.ldloc_0, }; for (int potentialStlocOffset = currentOffset - 1; potentialStlocOffset >= 0; potentialStlocOffset--) { if ((flags[potentialStlocOffset] & OpcodeFlags.InstructionStart) == 0) { continue; } ILReader nestedReader = new ILReader(body, potentialStlocOffset); ILOpcode otherOpcode = nestedReader.ReadILOpcode(); if ((otherOpcode == ILOpcode.stloc || otherOpcode == ILOpcode.stloc_s || (otherOpcode >= ILOpcode.stloc_0 && otherOpcode <= ILOpcode.stloc_3)) && otherOpcode switch { ILOpcode.stloc => nestedReader.ReadILUInt16(), ILOpcode.stloc_s => nestedReader.ReadILByte(), _ => otherOpcode - ILOpcode.stloc_0, } == locIndex)
public ILStreamReader(MethodIL methodIL) { _methodIL = methodIL; _reader = new ILReader(methodIL.GetILBytes()); }
private void WalkMethod(EcmaMethod method) { Instantiation typeContext = method.OwningType.Instantiation; Instantiation methodContext = method.Instantiation; MethodSignature methodSig = method.Signature; ProcessTypeReference(methodSig.ReturnType, typeContext, methodContext); foreach (TypeDesc parameterType in methodSig) { ProcessTypeReference(parameterType, typeContext, methodContext); } if (method.IsAbstract) { return; } var methodIL = EcmaMethodIL.Create(method); if (methodIL == null) { return; } // If this is a generic virtual method, add an edge from each of the generic parameters // of the implementation to the generic parameters of the declaration - any call to the // declaration will be modeled as if the declaration was calling into the implementation. if (method.IsVirtual && method.HasInstantiation) { var decl = (EcmaMethod)MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(method).GetTypicalMethodDefinition(); if (decl != method) { Instantiation declInstantiation = decl.Instantiation; Instantiation implInstantiation = method.Instantiation; for (int i = 0; i < declInstantiation.Length; i++) { RecordBinding( (EcmaGenericParameter)implInstantiation[i], (EcmaGenericParameter)declInstantiation[i], isProperEmbedding: false); } } } // Walk the method body looking at referenced things that have some genericness. // Nongeneric things cannot be forming cycles. // In particular, we don't care about MemberRefs to non-generic things, TypeDefs/MethodDefs/FieldDefs. // Avoid the work to even materialize type system entities for those. ILReader reader = new ILReader(methodIL.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.sizeof_: case ILOpcode.newarr: case ILOpcode.initobj: case ILOpcode.stelem: case ILOpcode.ldelem: case ILOpcode.ldelema: case ILOpcode.box: case ILOpcode.unbox: case ILOpcode.unbox_any: case ILOpcode.cpobj: case ILOpcode.ldobj: case ILOpcode.castclass: case ILOpcode.isinst: case ILOpcode.stobj: case ILOpcode.refanyval: case ILOpcode.mkrefany: case ILOpcode.constrained: EntityHandle accessedType = MetadataTokens.EntityHandle(reader.ReadILToken()); typeCase: if (accessedType.Kind == HandleKind.TypeSpecification) { var t = methodIL.GetObject(MetadataTokens.GetToken(accessedType), NotFoundBehavior.ReturnNull) as TypeDesc; if (t != null) { ProcessTypeReference(t, typeContext, methodContext); } } break; case ILOpcode.stsfld: case ILOpcode.ldsfld: case ILOpcode.ldsflda: case ILOpcode.stfld: case ILOpcode.ldfld: case ILOpcode.ldflda: EntityHandle accessedField = MetadataTokens.EntityHandle(reader.ReadILToken()); fieldCase: if (accessedField.Kind == HandleKind.MemberReference) { accessedType = _metadataReader.GetMemberReference((MemberReferenceHandle)accessedField).Parent; goto typeCase; } break; case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: case ILOpcode.ldftn: case ILOpcode.ldvirtftn: case ILOpcode.jmp: EntityHandle accessedMethod = MetadataTokens.EntityHandle(reader.ReadILToken()); methodCase: if (accessedMethod.Kind == HandleKind.MethodSpecification || (accessedMethod.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedMethod).Parent.Kind == HandleKind.TypeSpecification)) { var m = methodIL.GetObject(MetadataTokens.GetToken(accessedMethod), NotFoundBehavior.ReturnNull) as MethodDesc; ProcessTypeReference(m.OwningType, typeContext, methodContext); ProcessMethodCall(m, typeContext, methodContext); } break; case ILOpcode.ldtoken: EntityHandle accessedEntity = MetadataTokens.EntityHandle(reader.ReadILToken()); if (accessedEntity.Kind == HandleKind.MethodSpecification || (accessedEntity.Kind == HandleKind.MemberReference && _metadataReader.GetMemberReference((MemberReferenceHandle)accessedEntity).GetKind() == MemberReferenceKind.Method)) { accessedMethod = accessedEntity; goto methodCase; } else if (accessedEntity.Kind == HandleKind.MemberReference) { accessedField = accessedEntity; goto fieldCase; } else if (accessedEntity.Kind == HandleKind.TypeSpecification) { accessedType = accessedEntity; goto typeCase; } break; default: reader.Skip(opcode); break; } } }
public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) { // This attempts to find all basic blocks that are unreachable after applying the substitutions. // // On a high level, we first find all the basic blocks and instruction boundaries in the IL stream. // This is tracked in a sidecar `flags` array that has flags for each byte of the IL stream. // // Once we have all the basic blocks and instruction boundaries, we do a marking phase to mark // the reachable blocks. We use substitutions to tell us what's unreachable. We consider conditional // branches "interesting" and whenever we see one, we seek backwards in the IL instruction stream // to find the instruction that feeds it. We make sure we don't cross the basic block boundary while // doing that. If the conditional instruction is fed by known values (either through the substitutions // or because it's an IL constant), we simulate the result of the comparison and only mark // the taken branch. We also mark any associated EH regions. // // The "seek backwards to find what feeds the comparison" only works for a couple known instructions // (load constant, call). It can't e.g. skip over arguments to the call. // // Last step is a sweep - we replace the tail of all unreachable blocks with "br $-2" // and nop out the rest. If the basic block is smaller than 2 bytes, we don't touch it. // We also eliminate any EH records that correspond to the stubbed out basic block. Debug.Assert(method.GetMethodILDefinition() == method); ILExceptionRegion[] ehRegions = method.GetExceptionRegions(); byte[] methodBytes = method.GetILBytes(); OpcodeFlags[] flags = new OpcodeFlags[methodBytes.Length]; // Offset 0 is the first basic block Stack <int> offsetsToVisit = new Stack <int>(); offsetsToVisit.Push(0); // Basic blocks also start around EH regions foreach (ILExceptionRegion ehRegion in ehRegions) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); } // Identify basic blocks and instruction boundaries while (offsetsToVisit.TryPop(out int offset)) { // If this was already visited, we're done if (flags[offset] != 0) { // Also mark as basic block start in case this was a target of a backwards branch. flags[offset] |= OpcodeFlags.BasicBlockStart; continue; } flags[offset] |= OpcodeFlags.BasicBlockStart; // Read until we reach the end of the basic block ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.InstructionStart; ILOpcode opcode = reader.ReadILOpcode(); if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } flags[offset] |= OpcodeFlags.EndBasicBlock; } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { // Ends basic block. flags[offset] |= OpcodeFlags.EndBasicBlock; reader.Skip(opcode); } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } // We fall through to the next basic block. offsetsToVisit.Push(reader.Offset); flags[offset] |= OpcodeFlags.EndBasicBlock; } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { if (reader.HasNext) { // If the bytes following this basic block are not reachable from anywhere, // the sweeping step would consider them to be part of the last instruction // of the current basic block because of how instruction boundaries are identified. // We wouldn't NOP them out if the current basic block is reachable. // // That's a problem for RyuJIT because RyuJIT looks at these bytes for... reasons. // // We can just do the same thing as RyuJIT and consider those a basic block. offsetsToVisit.Push(reader.Offset); } break; } } } // Mark all reachable basic blocks // // We also do another round of basic block marking to mark beginning of visible basic blocks // after dead branch elimination. This allows us to limit the number of potential small basic blocks // that are not interesting (because no code jumps to them anymore), but could prevent us from // finishing the process. Unreachable basic blocks smaller than 2 bytes abort the substitution // inlining process because we can't neutralize them (turn them into an infinite loop). offsetsToVisit.Push(0); while (offsetsToVisit.TryPop(out int offset)) { // Mark as a basic block visible after constant propagation. flags[offset] |= OpcodeFlags.VisibleBasicBlockStart; // If this was already marked, we're done. if ((flags[offset] & OpcodeFlags.Mark) != 0) { continue; } ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.Mark; ILOpcode opcode = reader.ReadILOpcode(); // Mark any applicable EH blocks foreach (ILExceptionRegion ehRegion in ehRegions) { int delta = offset - ehRegion.TryOffset; if (delta >= 0 && delta < ehRegion.TryLength) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. int handlerEnd = ehRegion.HandlerOffset + ehRegion.HandlerLength; if (handlerEnd < flags.Length) { flags[handlerEnd] |= OpcodeFlags.VisibleBasicBlockStart; } } } // All branches are relevant to basic block tracking if (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s || opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((constant == 0 && (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s)) || (constant != 0 && (opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s || opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int left) || !TryGetConstantArgument(method, methodBytes, flags, offset, 1, out int right)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((left == right && (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s) || (left != right) && (opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } else { // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } offsetsToVisit.Push(reader.Offset); } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { reader.Skip(opcode); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { break; } } } // Now sweep unreachable basic blocks by replacing them with nops bool hasUnmarkedIntructions = false; foreach (var flag in flags) { if ((flag & OpcodeFlags.InstructionStart) != 0 && (flag & OpcodeFlags.Mark) == 0) { hasUnmarkedIntructions = true; } } if (!hasUnmarkedIntructions) { return(method); } byte[] newBody = (byte[])methodBytes.Clone(); int position = 0; while (position < newBody.Length) { Debug.Assert((flags[position] & OpcodeFlags.InstructionStart) != 0); Debug.Assert((flags[position] & OpcodeFlags.VisibleBasicBlockStart) != 0); bool erase = (flags[position] & OpcodeFlags.Mark) == 0; int basicBlockStart = position; do { if (erase) { newBody[position] = (byte)ILOpCode.Nop; } position++; } while (position < newBody.Length && (flags[position] & OpcodeFlags.VisibleBasicBlockStart) == 0); // If we had to nop out this basic block, we need to neutralize it by appending // an infinite loop ("br $-2"). // We append instead of prepend because RyuJIT's importer has trouble with junk unreachable bytes. if (erase) { if (position - basicBlockStart < 2) { // We cannot neutralize the basic block, so better leave the method alone. // The control would fall through to the next basic block. return(method); } newBody[position - 2] = (byte)ILOpCode.Br_s; newBody[position - 1] = unchecked ((byte)-2); } } // EH regions with unmarked handlers belong to unmarked basic blocks // Need to eliminate them because they're not usable. ArrayBuilder <ILExceptionRegion> newEHRegions = new ArrayBuilder <ILExceptionRegion>(); foreach (ILExceptionRegion ehRegion in ehRegions) { if ((flags[ehRegion.HandlerOffset] & OpcodeFlags.Mark) != 0) { newEHRegions.Add(ehRegion); } } // Existing debug information might not match new instruction boundaries (plus there's little point // in generating debug information for NOPs) - generate new debug information by filtering // out the sequence points associated with nopped out instructions. MethodDebugInformation debugInfo = method.GetDebugInfo(); IEnumerable <ILSequencePoint> oldSequencePoints = debugInfo?.GetSequencePoints(); if (oldSequencePoints != null) { ArrayBuilder <ILSequencePoint> sequencePoints = new ArrayBuilder <ILSequencePoint>(); foreach (var sequencePoint in oldSequencePoints) { if (sequencePoint.Offset < flags.Length && (flags[sequencePoint.Offset] & OpcodeFlags.Mark) != 0) { sequencePoints.Add(sequencePoint); } } debugInfo = new SubstitutedDebugInformation(debugInfo, sequencePoints.ToArray()); } return(new SubstitutedMethodIL(method, newBody, newEHRegions.ToArray(), debugInfo)); }
Instruction(ILReader reader, OpCode op, Operand arg, ushort label) { this.reader = reader; this.OpCode = op; this.Operand = arg; this.label = (ushort)label; }
public static IEnumerable<MethodBase> GetMethods(MethodBase caller, bool recursive = false, string namespacePrefix = DefaultPrefix) { ILReader reader = new ILReader(caller); foreach (InlineMethodInstruction method in reader.OfType<InlineMethodInstruction>()) { yield return method.Method; if (recursive && method.Method.DeclaringType != null && method.Method.DeclaringType.FullName.StartsWith(namespacePrefix, StringComparison.InvariantCultureIgnoreCase)) { foreach (var innerMethod in GetMethods(method.Method)) { yield return innerMethod; } } } }
/// <summary> /// Construct an instruction. /// </summary> /// <param name="reader">The reader that created this instruction.</param> /// <param name="op">The instruction opcode.</param> /// <param name="arg">The instruction operand.</param> /// <param name="label">The instruction's address in the bytecode.</param> public Instruction(ILReader reader, OpCode op, Operand arg, IL.Label label) : this(reader, op, arg, (ushort)label.pos) { }
/// <summary> /// Construct an instruction. /// </summary> /// <param name="reader">The reader that created this instruction.</param> /// <param name="op">The instruction opcode.</param> /// <param name="label">The instruction's address in the bytecode.</param> public Instruction(ILReader reader, OpCode op, IL.Label label) : this(reader, op, default(Operand), label) { }