internal void RemoveLoadInstruction(LdLoc inst) => RemoveInstruction(loadInstructions, inst.IndexInLoadInstructionList, inst);
/// <summary> /// The transform works like this: /// /// <para> /// local functions can either be used in method calls, i.e., call and callvirt instructions, /// or can be used as part of the "delegate construction" pattern, i.e., /// <c>newobj Delegate(<target-expression>, ldftn <method>)</c>. /// </para> /// As local functions can be declared practically anywhere, we have to take a look at /// all use-sites and infer the declaration location from that. Use-sites can be call, /// callvirt and ldftn instructions. /// After all use-sites are collected we construct the ILAst of the local function /// and add it to the parent function. /// Then all use-sites of the local-function are transformed to a call to the /// <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>. /// In a next step we handle all nested local functions. /// After all local functions are transformed, we move all local functions that capture /// any variables to their respective declaration scope. /// </summary> public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) { return; } // Disable the transform if we are decompiling a display-class or local function method: // This happens if a local function or display class is selected in the ILSpy tree view. if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context)) { return; } this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary <MethodDefinitionHandle, LocalFunctionInfo>(); var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); foreach (var(_, info) in localFunctions) { cancellationToken.ThrowIfCancellationRequested(); if (info.Definition == null) { function.Warnings.Add($"Could not decode local function '{info.Method}'"); continue; } context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; if (target == null) { target = info.UseSites[0].Arguments[0]; if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition())) { var variable = function.Descendants.OfType <ILFunction>().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault(); if (variable != null) { target = new LdLoc(variable); HandleArgument(localFunction, 1, 0, target); } } } context.Step($"Replace 'this' with {target}", localFunction); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } foreach (var useSite in info.UseSites) { DetermineCaptureAndDeclarationScope(localFunction, useSite); if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); } } if (localFunction.DeclarationScope == null) { localFunction.DeclarationScope = (BlockContainer)function.Body; } ILFunction declaringFunction = GetDeclaringFunction(localFunction); if (declaringFunction != function) { function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters) { Debug.Assert(false); function.Warnings.Add($"Could not decode local function '{info.Method}'"); if (declaringFunction != function) { declaringFunction.LocalFunctions.Remove(localFunction); } continue; } foreach (var useSite in info.UseSites) { context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } } } finally { context.StepEndGroup(); } } }
internal void AddLoadInstruction(LdLoc inst) => inst.IndexInLoadInstructionList = AddInstruction(loadInstructions, inst);
/// <summary> /// Matches Roslyn C# switch on nullable. /// </summary> bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch) { newSwitch = null; // match first block: // if (logic.not(call get_HasValue(target))) br nullCaseBlock // br switchBlock if (!instructions[i].MatchIfInstruction(out var condition, out var trueInst)) { return(false); } if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock)) { return(false); } if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction target) || !SemanticHelper.IsPure(target.Flags)) { return(false); } // match second block: switchBlock // note: I have seen cases where switchVar is inlined into the switch. // stloc switchVar(call GetValueOrDefault(ldloca tmp)) // switch (ldloc switchVar) { // case [0..1): br caseBlock1 // ... more cases ... // case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock // } if (switchBlock.IncomingEdgeCount != 1) { return(false); } SwitchInstruction switchInst; switch (switchBlock.Instructions.Count) { case 2: { // this is the normal case described by the pattern above if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault)) { return(false); } if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1) { return(false); } if (!(NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction target2) && target2.Match(target).Success)) { return(false); } if (!(switchBlock.Instructions[1] is SwitchInstruction si)) { return(false); } switchInst = si; break; } case 1: { // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch. if (!(switchBlock.Instructions[0] is SwitchInstruction si)) { return(false); } if (!(NullableLiftingTransform.MatchGetValueOrDefault(si.Value, out ILInstruction target2) && target2.Match(target).Success)) { return(false); } switchInst = si; break; } default: { return(false); } } ILInstruction switchValue; if (target.MatchLdLoca(out var v)) { switchValue = new LdLoc(v).WithILRange(target); } else { switchValue = new LdObj(target, ((CallInstruction)getHasValue).Method.DeclaringType); } newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue); return(true); }
protected internal override void VisitLdLoc(LdLoc inst) { EnsureInitialized(inst.Variable); }
Statement TransformToForeach(UsingInstruction inst, out Expression resource) { // Check if the using resource matches the GetEnumerator pattern. resource = exprBuilder.Translate(inst.ResourceExpression); var m = getEnumeratorPattern.Match(resource); // The using body must be a BlockContainer. if (!(inst.Body is BlockContainer container) || !m.Success) { return(null); } // The using-variable is the enumerator. var enumeratorVar = inst.Variable; // If there's another BlockContainer nested in this container and it only has one child block, unwrap it. // If there's an extra leave inside the block, extract it into optionalReturnAfterLoop. var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); // Detect whether we're dealing with a while loop with multiple embedded statements. var loop = DetectedLoop.DetectLoop(loopContainer); if (loop.Kind != LoopKind.While || !(loop.Body is Block body)) { return(null); } // The loop condition must be a call to enumerator.MoveNext() var condition = exprBuilder.TranslateCondition(loop.Conditions.Single()); var m2 = moveNextConditionPattern.Match(condition.Expression); if (!m2.Success) { return(null); } // Check enumerator variable references. var enumeratorVar2 = m2.Get <IdentifierExpression>("enumerator").Single().GetILVariable(); if (enumeratorVar2 != enumeratorVar) { return(null); } // Detect which foreach-variable transformation is necessary/possible. var transformation = DetectGetCurrentTransformation(container, body, enumeratorVar, condition.ILInstructions.Single(), out var singleGetter, out var foreachVariable); if (transformation == RequiredGetCurrentTransformation.NoForeach) { return(null); } // The existing foreach variable, if found, can only be used in the loop container. if (foreachVariable != null && !(foreachVariable.CaptureScope == null || foreachVariable.CaptureScope == loopContainer)) { return(null); } // Extract in-expression var collectionExpr = m.Get <Expression>("collection").Single(); // Special case: foreach (var item in this) is decompiled as foreach (var item in base) // but a base reference is not valid in this context. if (collectionExpr is BaseReferenceExpression) { collectionExpr = new ThisReferenceExpression().CopyAnnotationsFrom(collectionExpr); } // Handle explicit casts: // This is the case if an explicit type different from the collection-item-type was used. // For example: foreach (ClassA item in nonGenericEnumerable) var type = singleGetter.Method.ReturnType; ILInstruction instToReplace = singleGetter; switch (instToReplace.Parent) { case CastClass cc: type = cc.Type; instToReplace = cc; break; case UnboxAny ua: type = ua.Type; instToReplace = ua; break; } // Handle the required foreach-variable transformation: switch (transformation) { case RequiredGetCurrentTransformation.UseExistingVariable: foreachVariable.Type = type; foreachVariable.Kind = VariableKind.ForeachLocal; foreachVariable.Name = AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>(), foreachVariable); break; case RequiredGetCurrentTransformation.UninlineAndUseExistingVariable: // Unwrap stloc chain. var nestedStores = new Stack <ILVariable>(); var currentInst = instToReplace; // instToReplace is the innermost value of the stloc chain. while (currentInst.Parent is StLoc stloc) { // Exclude nested stores to foreachVariable // we'll insert one store at the beginning of the block. if (stloc.Variable != foreachVariable && stloc.Parent is StLoc) { nestedStores.Push(stloc.Variable); } currentInst = stloc; } // Rebuild the nested store instructions: ILInstruction reorderedStores = new LdLoc(foreachVariable); while (nestedStores.Count > 0) { reorderedStores = new StLoc(nestedStores.Pop(), reorderedStores); } currentInst.ReplaceWith(reorderedStores); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); // Adjust variable type, kind and name. goto case RequiredGetCurrentTransformation.UseExistingVariable; case RequiredGetCurrentTransformation.IntroduceNewVariable: foreachVariable = currentFunction.RegisterVariable( VariableKind.ForeachLocal, type, AssignVariableNames.GenerateForeachVariableName(currentFunction, collectionExpr.Annotation <ILInstruction>()) ); instToReplace.ReplaceWith(new LdLoc(foreachVariable)); body.Instructions.Insert(0, new StLoc(foreachVariable, instToReplace)); break; } // Convert the modified body to C# AST: var whileLoop = (WhileStatement)ConvertAsBlock(container).First(); BlockStatement foreachBody = (BlockStatement)whileLoop.EmbeddedStatement.Detach(); // Remove the first statement, as it is the foreachVariable = enumerator.Current; statement. foreachBody.Statements.First().Detach(); // Construct the foreach loop. var foreachStmt = new ForeachStatement { VariableType = settings.AnonymousTypes && foreachVariable.Type.ContainsAnonymousType() ? new SimpleType("var") : exprBuilder.ConvertType(foreachVariable.Type), VariableName = foreachVariable.Name, InExpression = collectionExpr.Detach(), EmbeddedStatement = foreachBody }; // Add the variable annotation for highlighting (TokenTextWriter expects it directly on the ForeachStatement). foreachStmt.AddAnnotation(new ILVariableResolveResult(foreachVariable, foreachVariable.Type)); // If there was an optional return statement, return it as well. if (optionalReturnAfterLoop != null) { return(new BlockStatement { Statements = { foreachStmt, optionalReturnAfterLoop.AcceptVisitor(this) } }); } return(foreachStmt); }
bool TransformDeconstruction(Block block, int pos) { int startPos = pos; Action <DeconstructInstruction> delayedActions = null; if (MatchDeconstruction(block.Instructions[pos], out IMethod deconstructMethod, out ILInstruction rootTestedOperand)) { pos++; } if (!MatchConversions(block, ref pos, out var conversions, out var conversionStLocs, ref delayedActions)) { return(false); } if (!MatchAssignments(block, ref pos, conversions, conversionStLocs, ref delayedActions)) { return(false); } // first tuple element may not be discarded, // otherwise we would run this transform on a suffix of the actual pattern. if (deconstructionResults[0] == null) { return(false); } context.Step("Deconstruction", block.Instructions[startPos]); DeconstructInstruction replacement = new DeconstructInstruction(); IType deconstructedType; if (deconstructMethod == null) { deconstructedType = this.tupleType; rootTestedOperand = new LdLoc(this.tupleVariable); } else { if (deconstructMethod.IsStatic) { deconstructedType = deconstructMethod.Parameters[0].Type; } else { deconstructedType = deconstructMethod.DeclaringType; } } var rootTempVariable = context.Function.RegisterVariable(VariableKind.PatternLocal, deconstructedType); replacement.Pattern = new MatchInstruction(rootTempVariable, deconstructMethod, rootTestedOperand) { IsDeconstructCall = deconstructMethod != null, IsDeconstructTuple = this.tupleType != null }; int index = 0; foreach (ILVariable v in deconstructionResults) { var result = v; if (result == null) { var freshVar = new ILVariable(VariableKind.PatternLocal, this.tupleType.ElementTypes[index]) { Name = "E_" + index }; context.Function.Variables.Add(freshVar); result = freshVar; } else { result.Kind = VariableKind.PatternLocal; } replacement.Pattern.SubPatterns.Add( new MatchInstruction( result, new DeconstructResultInstruction(index, result.StackType, new LdLoc(rootTempVariable)) ) ); index++; } replacement.Conversions = new Block(BlockKind.DeconstructionConversions); foreach (var convInst in conversionStLocs) { replacement.Conversions.Instructions.Add(convInst); } replacement.Assignments = new Block(BlockKind.DeconstructionAssignments); delayedActions?.Invoke(replacement); block.Instructions[startPos] = replacement; block.Instructions.RemoveRange(startPos + 1, pos - startPos - 1); return(true); }
static ILInstruction GetRef(ILVariable v, string Name) { var ty = TypeInference.GetVariableType(v); if (ty == null) { // happens on ldNull; return(null); } var isPointer = false; if (ty.Kind == TypeKind.Pointer) { ty = ((ICSharpCode.Decompiler.TypeSystem.PointerType)ty).ElementType; isPointer = true; } IType etype; ILInstruction loadInst; if (ty.IsReferenceType.Value) { etype = GetETypeBase(ty); if (isPointer) { loadInst = new LdObj(new LdLoc(v), ty); } else { loadInst = new LdLoc(v); } if (etype == null && !ty.FullName.StartsWith("System.")) { Debug.Assert(false, "Type is broken??"); } // only do ref counting for our own types if (etype == null) { return(null); } // avoid compiler warnings loadInst = new CastClass(loadInst, etype); } else // value type { etype = ty; if (v.StackType == StackType.O) { loadInst = new Conv(new LdLoca(v), PrimitiveType.I, false, Sign.None); } else { loadInst = new Conv(new LdLoc(v), PrimitiveType.I, false, Sign.None); } } // make sure we don't end up with crazy unintended names //v.HasGeneratedName = false; //var test = (DefaultResolvedTypeDefinition)ty; var m = etype.GetMethods().First(x => x.Name.EndsWith(Name + "Ref")); var inst = new Call(m); inst.Arguments.Add(loadInst); return(inst); }
static bool IsUsedAsNativeInt(LdLoc load) { return(load.Parent switch { BinaryNumericInstruction { UnderlyingResultType : StackType.I } => true,
/// <summary> /// Performs nullable lifting. /// /// Produces a lifted instruction with semantics equivalent to: /// (v1 != null && ... && vn != null) ? trueInst : falseInst, /// where the v1,...,vn are the <c>this.nullableVars</c>. /// If lifting fails, returns <c>null</c>. /// </summary> ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) { bool isNullCoalescingWithNonNullableFallback = false; if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { isNullCoalescingWithNonNullableFallback = true; utype = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode()); exprToLift = trueInst; if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0])) { // v.HasValue ? ldloc v : fallback // => v ?? fallback context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst); return(new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst) { UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(), ILRange = ilrange }); } } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { // v.HasValue ? call GetValueOrDefault(ldloca v) : fallback // => conv.nop.lifted(ldloc v) ?? fallback // This case is handled separately from DoLift() because // that doesn't introduce nop-conversions. context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst); var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); lifted = new LdLoc(nullableVars[0]); if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None) { // While the ILAst allows implicit conversions between short and int // (because both map to I4); it does not allow implicit conversions // between short? and int? (structs of different types). // So use 'conv.nop.lifted' to allow the conversion. lifted = new Conv( lifted, inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), checkForOverflow: false, isLifted: true ) { ILRange = ilrange }; } } else { context.Step("NullableLiftingTransform.DoLift", trueInst); BitSet bits; (lifted, bits) = DoLift(exprToLift); if (lifted == null) { return(null); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted && liftable.UnderlyingResultType == exprToLift.ResultType); } if (isNullCoalescingWithNonNullableFallback) { lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } else if (!MatchNull(falseInst, utype)) { // Normal lifting, but the falseInst isn't `default(utype?)` // => use the `??` operator to provide the fallback value. lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } return(lifted); }
/// <summary> /// Performs nullable lifting. /// /// Produces a lifted instruction with semantics equivalent to: /// (v1 != null && ... && vn != null) ? trueInst : falseInst, /// where the v1,...,vn are the <c>this.nullableVars</c>. /// If lifting fails, returns <c>null</c>. /// </summary> ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) { bool isNullCoalescingWithNonNullableFallback = false; if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { isNullCoalescingWithNonNullableFallback = true; utype = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode()); exprToLift = trueInst; if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0])) { // v.HasValue ? ldloc v : fallback // => v ?? fallback context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst); return(new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst) { UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(), ILRange = ilrange }); } else if (trueInst is Call call && !call.IsLifted && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) && call.Method.Name != "op_Equality" && call.Method.Name != "op_Inequality" && falseInst.MatchLdcI4(0)) { // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); if (liftedOperator != null) { var(left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]); if (left != null && right != null && bits.All(0, nullableVars.Count)) { return(new Call(liftedOperator) { Arguments = { left, right }, ConstrainedTo = call.ConstrainedTo, ILRange = call.ILRange, ILStackWasEmpty = call.ILStackWasEmpty, IsTail = call.IsTail }); } } } } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { // v.HasValue ? call GetValueOrDefault(ldloca v) : fallback // => conv.nop.lifted(ldloc v) ?? fallback // This case is handled separately from DoLift() because // that doesn't introduce nop-conversions. context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst); var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); lifted = new LdLoc(nullableVars[0]); if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None) { // While the ILAst allows implicit conversions between short and int // (because both map to I4); it does not allow implicit conversions // between short? and int? (structs of different types). // So use 'conv.nop.lifted' to allow the conversion. lifted = new Conv( lifted, inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), checkForOverflow: false, isLifted: true ) { ILRange = ilrange }; } } else { context.Step("NullableLiftingTransform.DoLift", trueInst); BitSet bits; (lifted, bits) = DoLift(exprToLift); if (lifted == null) { return(null); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted && liftable.UnderlyingResultType == exprToLift.ResultType); } if (isNullCoalescingWithNonNullableFallback) { lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } else if (!MatchNull(falseInst, utype)) { // Normal lifting, but the falseInst isn't `default(utype?)` // => use the `??` operator to provide the fallback value. lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } return(lifted); }
protected internal override void VisitLdLoc(LdLoc inst) { base.VisitLdLoc(inst); HandleLoad(inst); }