public CallInfo[] Collect(Mono.Cecil.MethodDefinition method, Func <Instruction, bool> filter = null) { //if (method.Name != "SimpleLogFromTryCatch" // && method.Name != "EnqueueNoOpIfNeeded") return new CallInfo[0]; if (!method.HasBody) { return(new CallInfo[0]); } var body = method.Body; if (method.Body.Instructions.Any(i => i.Is(OpCodes.Call, OpCodes.Callvirt) && filter(i))) { var function = ilReader.ReadIL(body); var opsByOffset = body.Instructions.ToDictionary(instr => instr.Offset); var transformContext = decompiler.CreateILTransformContext(function); //foreach (var t in ILTransforms) //{ // t.Run(function, transformContext); //} var collector = new BlockVisitor(function, i => { var range = i.ILRange; var op = opsByOffset[range.Start]; while (!op.Is(OpCodes.Call, OpCodes.Callvirt)) { op = op.Next; if (op.Offset > range.End) { return(false); } } return(filter(op)); }); function.AcceptVisitor(collector); if (collector.CallStarts.Count > 0) { var retval = new CallInfo[collector.CallStarts.Count]; var i = 0; foreach (var(blockStart, callOffset) in collector.CallStarts) { retval[i++] = new CallInfo(opsByOffset[blockStart], opsByOffset[callOffset]); } return(retval); } } return(new CallInfo[0]); }
public override void DecompileMethod(IMethod method, ITextOutput output, DecompilationOptions options) { base.DecompileMethod(method, output, options); var module = method.ParentModule.PEFile; var metadata = module.Metadata; var methodDef = metadata.GetMethodDefinition((SRM.MethodDefinitionHandle)method.MetadataToken); if (!methodDef.HasBody()) { return; } IAssemblyResolver assemblyResolver = module.GetAssemblyResolver(); var typeSystem = new DecompilerTypeSystem(module, assemblyResolver); var reader = new ILReader(typeSystem.MainModule); reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; var methodBody = module.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken); var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken }; ILTransformContext context = decompiler.CreateILTransformContext(il); 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 { Docking.DockWorkspace.Instance.ShowToolPane(DebugStepsPaneModel.PaneContentId); }); 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)) { return(null); } target = value.Arguments[0]; var methodDefinition = (Mono.Cecil.MethodDefinition)context.TypeSystem.GetCecil(targetMethod); if (methodDefinition == null) { return(null); } var localTypeSystem = context.TypeSystem.GetSpecializingTypeSystem(targetMethod.Substitution); 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, DecompileRun = context.DecompileRun }; 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); }
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)?.AddUIElement(OptionsCheckBox(nameof(writingOptions.UseFieldSugar))); output.WriteLine(); (output as ISmartTextOutput)?.AddUIElement(OptionsCheckBox(nameof(writingOptions.UseLogicOperationSugar))); output.WriteLine(); (output as ISmartTextOutput)?.AddButton(Images.ViewCode, "Show Steps", delegate { DebugSteps.Show(); }); output.WriteLine(); il.WriteTo(output, writingOptions); }
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); ILReader reader = new ILReader(typeSystem); reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols; ILFunction il = reader.ReadIL(method.Body, options.CancellationToken); ILTransformContext context = new ILTransformContext { Settings = options.DecompilerSettings, TypeSystem = typeSystem, CancellationToken = options.CancellationToken }; context.Stepper.StepLimit = options.StepLimit; context.Stepper.IsDebug = options.IsDebug; try { il.RunTransforms(transforms, context); } catch (StepLimitReachedException) { } 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); }
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); } } } }
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); }
/// <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); } }
/// <summary> /// Runs all post-build checks defined in the specified assemblies. This is intended to be run as a post-build /// event. See remarks for details.</summary> /// <remarks> /// <para> /// In non-DEBUG mode, does nothing and returns 0.</para> /// <para> /// Intended use is as follows:</para> /// <list type="bullet"> /// <item><description> /// <para> /// Add the following line to your project's post-build event:</para> /// <code> /// "$(TargetPath)" --post-build-check "$(SolutionDir)."</code></description></item> /// <item><description> /// <para> /// Add the following code at the beginning of your project's Main() method:</para> /// <code> /// if (args.Length == 2 && args[0] == "--post-build-check") /// return PostBuildChecker.RunPostBuildChecks(args[1], Assembly.GetExecutingAssembly());</code> /// <para> /// If your project entails several assemblies, you can specify additional assemblies in the call to /// <see cref="PostBuildChecker.RunPostBuildChecks"/>. For example, you could specify /// <c>typeof(SomeTypeInMyLibrary).Assembly</c>.</para></description></item> /// <item><description> /// <para> /// Add post-build check methods to any type where they may be relevant. For example, you might use /// code similar to the following:</para> /// <code> /// #if DEBUG /// private static void PostBuildCheck(IPostBuildReporter rep) /// { /// if (somethingWrong()) /// rep.Error("Error XYZ occurred.", "class", "Gizmo"); /// } /// #endif</code> /// <para> /// The method is expected to have one parameter of type <see cref="IPostBuildReporter"/>, a return /// type of void, and it is expected to be static and non-public. Errors and warnings can be reported /// by calling methods on said <see cref="IPostBuildReporter"/> object. (In the above example, /// PostBuildChecker will attempt to find a class called Gizmo to link the error message to a location /// in the source.) Throwing an exception will also report an error.</para></description></item></list></remarks> /// <param name="sourcePath"> /// Specifies the path to the folder containing the C# source files.</param> /// <param name="assemblies"> /// Specifies the compiled assemblies from which to run post-build checks.</param> /// <returns> /// 1 if any errors occurred, otherwise 0.</returns> public static int RunPostBuildChecks(string sourcePath, params Assembly[] assemblies) { int countMethods = 0; var rep = new postBuildReporter(sourcePath); var attempt = Ut.Lambda((Action action) => { try { action(); } catch (Exception e) { rep.AnyErrors = true; string indent = ""; while (e != null) { var st = new StackTrace(e, true); string fileLine = null; for (int i = 0; i < st.FrameCount; i++) { var frame = st.GetFrame(i); if (frame.GetFileName() != null) { fileLine = frame.GetFileName() + "(" + frame.GetFileLineNumber() + "," + frame.GetFileColumnNumber() + "): "; break; } } Console.Error.WriteLine($"{fileLine}Error: {indent}{e.Message.Replace("\n", " ").Replace("\r", "")} ({e.GetType().FullName})"); Console.Error.WriteLine(e.StackTrace); e = e.InnerException; indent += "---- "; } } }); // Step 1: Run all the custom-defined PostBuildCheck methods foreach (var ty in assemblies.SelectMany(asm => asm.GetTypes())) { attempt(() => { var meth = ty.GetMethod("PostBuildCheck", BindingFlags.NonPublic | BindingFlags.Static); if (meth != null) { if (meth.GetParameters().Select(p => p.ParameterType).SequenceEqual(new Type[] { typeof(IPostBuildReporter) }) && meth.ReturnType == typeof(void)) { countMethods++; meth.Invoke(null, new object[] { rep }); } else { rep.Error( $"The type {ty.FullName} has a method called PostBuildCheck() that is not of the expected signature. There should be one parameter of type {typeof(IPostBuildReporter).FullName}, and the return type should be void.", (ty.IsValueType ? "struct " : "class ") + ty.Name, "PostBuildCheck"); } } }); } // Step 2: Run all the built-in checks on IL code foreach (var asm in assemblies) { foreach (var type in asm.GetTypes()) { foreach (var meth in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { attempt(() => { var instructions = ILReader.ReadIL(meth, type).ToArray(); for (int i = 0; i < instructions.Length; i++) { // Check that “throw new ArgumentNullException(...)” statements refer to an actual parameter if (instructions[i].OpCode.Value == OpCodes.Newobj.Value) { var constructor = (ConstructorInfo)instructions[i].Operand; string wrong = null; string wrongException = "ArgumentNullException"; if (constructor.DeclaringType == typeof(ArgumentNullException) && constructor.GetParameters().Select(p => p.ParameterType).SequenceEqual(typeof(string))) { if (instructions[i - 1].OpCode.Value == OpCodes.Ldstr.Value) { if (!meth.GetParameters().Any(p => p.Name == (string)instructions[i - 1].Operand)) { wrong = (string)instructions[i - 1].Operand; } } } if (constructor.DeclaringType == typeof(ArgumentNullException) && constructor.GetParameters().Select(p => p.ParameterType).SequenceEqual(typeof(string), typeof(string))) { if (instructions[i - 1].OpCode.Value == OpCodes.Ldstr.Value && instructions[i - 2].OpCode.Value == OpCodes.Ldstr.Value) { if (!meth.GetParameters().Any(p => p.Name == (string)instructions[i - 2].Operand)) { wrong = (string)instructions[i - 2].Operand; } } } if (constructor.DeclaringType == typeof(ArgumentException) && constructor.GetParameters().Select(p => p.ParameterType).SequenceEqual(typeof(string), typeof(string))) { if (instructions[i - 1].OpCode.Value == OpCodes.Ldstr.Value) { if (!meth.GetParameters().Any(p => p.Name == (string)instructions[i - 1].Operand)) { wrong = (string)instructions[i - 1].Operand; wrongException = "ArgumentException"; } } } if (wrong != null) { rep.Error( Regex.IsMatch(meth.DeclaringType.Name, @"<.*>d__\d") ? $@"The iterator method ""{type.FullName}.{meth.Name}"" constructs a {wrongException}. Move this argument check outside the iterator." : $@"The method ""{type.FullName}.{meth.Name}"" constructs an {wrongException} with a parameter name ""{wrong}"" which doesn't appear to be a parameter in that method.", getDebugClassName(meth), getDebugMethodName(meth), wrongException, wrong ); } if (constructor.DeclaringType == typeof(ArgumentException) && constructor.GetParameters().Select(p => p.ParameterType).SequenceEqual(typeof(string))) { rep.Error( Regex.IsMatch(meth.DeclaringType.Name, @"<.*>d__\d") ? $@"The iterator method ""{type.FullName}.{meth.Name}"" constructs an ArgumentException. Move this argument check outside the iterator." : $@"The method ""{type.FullName}.{meth.Name}"" uses the single-argument constructor to ArgumentException. Please use the two-argument constructor and specify the parameter name. If there is no parameter involved, use InvalidOperationException.", getDebugClassName(meth), getDebugMethodName(meth), "ArgumentException"); } } else if (i < instructions.Length - 1 && (instructions[i].OpCode.Value == OpCodes.Call.Value || instructions[i].OpCode.Value == OpCodes.Callvirt.Value) && instructions[i + 1].OpCode.Value == OpCodes.Pop.Value) { var method = (MethodInfo)instructions[i].Operand; var mType = method.DeclaringType; if (postBuildGetNoPopMethods().Contains(method)) { rep.Error( $@"Useless call to ""{mType.FullName}.{method.Name}"" (the return value is discarded).", getDebugClassName(meth), getDebugMethodName(meth), method.Name ); } } } }); } } } Console.WriteLine($"Post-build checks ran on {assemblies.Length} assemblies, {countMethods} methods and completed {(rep.AnyErrors ? "with ERRORS" : "SUCCESSFULLY")}."); return(rep.AnyErrors ? 1 : 0); }