diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 546cd2ea7..d37764e45 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -61,6 +61,7 @@ + @@ -91,6 +92,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 49c3bca2c..d9a9e2ede 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -196,6 +196,12 @@ namespace ICSharpCode.Decompiler.Tests Run(); } + [Test] + public void CallIndirect() + { + Run(); + } + [Test] public void FSharpLoops_Debug() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs new file mode 100644 index 000000000..a87ff898e --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.cs @@ -0,0 +1,8 @@ +using System; +internal class CallIndirect +{ + private unsafe void Test(IntPtr f) + { + ((delegate* stdcall)f)(42); + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il new file mode 100644 index 000000000..8ba3b409a --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CallIndirect.il @@ -0,0 +1,39 @@ +#define CORE_ASSEMBLY "System.Runtime" + +.assembly extern CORE_ASSEMBLY +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 4:0:0:0 +} + +.class private auto ansi beforefieldinit CallIndirect + extends [System.Private.CoreLib]System.Object +{ + // Methods + .method private hidebysig + instance void Test ( + native int f + ) cil managed + { + .maxstack 8 + + ldc.i4.s 42 + ldarg.1 + calli unmanaged stdcall void(int32) + ret + } // end of method Example::Test + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x206e + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Example::.ctor + +} // end of class Example diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs index f32b017d4..4bbc8dd9a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Runtime.CompilerServices; [assembly: AssemblyFileVersion("4.0.0.0")] [assembly: AssemblyInformationalVersion("4.0.0.0")] @@ -12,6 +13,20 @@ using System.Reflection; [assembly: AssemblyProduct("Microsoft® .NET Framework")] [assembly: CLSCompliant(false)] +internal sealed class ExtraUnsafeTests +{ + public unsafe static void PinWithTypeMismatch(ref uint managedPtr) + { + fixed (ushort* ptr = &Unsafe.As(ref managedPtr)) { + } + } + + public unsafe static uint* RefToPointerWithoutPinning(ref uint managedPtr) + { + return (uint*)Unsafe.AsPointer(ref managedPtr); + } +} + namespace System.Runtime.CompilerServices { public static class Unsafe @@ -67,7 +82,7 @@ namespace System.Runtime.CompilerServices [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe static void* AsPointer(ref T value) { - return &value; + return Unsafe.AsPointer(ref value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -151,9 +166,9 @@ namespace System.Runtime.CompilerServices } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe static ref TTo As(ref TFrom source) + public static ref TTo As(ref TFrom source) { - return ref *(TTo*)(&source); + return ref Unsafe.As(ref source); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il index 7ea5061df..e2b140d3f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.il @@ -437,3 +437,41 @@ } // end of class System.Runtime.CompilerServices.IsReadOnlyAttribute #endif + +.class private auto ansi sealed beforefieldinit ExtraUnsafeTests + extends [CORE_ASSEMBLY]System.Object +{ + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + .maxstack 1 + ldarg.0 + call instance void [CORE_ASSEMBLY]System.Object::.ctor() + ret + } // end of method ExtraUnsafeTests::.ctor + + .method public hidebysig static void PinWithTypeMismatch(uint32& managedPtr) + { + .maxstack 8 + .locals ( + [0] uint16& pinned + ) + // Pin: + ldarg.0 + stloc.0 + + // Unpin: + ldc.i4 0 + stloc.0 + + ret + } + + .method public hidebysig static uint32* RefToPointerWithoutPinning(uint32& managedPtr) + { + .maxstack 8 + + ldarg.0 + ret + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 9ae593ab0..cc77d11e1 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1138,13 +1138,17 @@ namespace ICSharpCode.Decompiler.CSharp return null; } - internal TranslatedExpression CallUnsafeIntrinsic(string name, Expression[] arguments, IType returnType, ILInstruction inst) + internal TranslatedExpression CallUnsafeIntrinsic(string name, Expression[] arguments, IType returnType, ILInstruction inst = null, IEnumerable typeArguments = null) { var target = new MemberReferenceExpression { Target = new TypeReferenceExpression(astBuilder.ConvertType(compilation.FindType(KnownTypeCode.Unsafe))), MemberName = name }; - var invocation = new InvocationExpression(target, arguments).WithILInstruction(inst); + if (typeArguments != null) { + target.TypeArguments.AddRange(typeArguments.Select(astBuilder.ConvertType)); + } + var invocationExpr = new InvocationExpression(target, arguments); + var invocation = inst != null ? invocationExpr.WithILInstruction(inst) : invocationExpr.WithoutILInstruction(); if (returnType is ByReferenceType brt) { return WrapInRef(invocation.WithRR(new ResolveResult(brt.ElementType)), brt); } else { @@ -1672,9 +1676,17 @@ namespace ICSharpCode.Decompiler.CSharp return arg; case ConversionKind.StopGCTracking: if (inputType.Kind == TypeKind.ByReference) { - // cast to corresponding pointer type: - var pointerType = new PointerType(((ByReferenceType)inputType).ElementType); - return arg.ConvertTo(pointerType, this).WithILInstruction(inst); + if (PointerArithmeticOffset.IsFixedVariable(inst.Argument)) { + // cast to corresponding pointer type: + var pointerType = new PointerType(((ByReferenceType)inputType).ElementType); + return arg.ConvertTo(pointerType, this).WithILInstruction(inst); + } else { + // emit Unsafe.AsPointer() intrinsic: + return CallUnsafeIntrinsic("AsPointer", + arguments: new Expression[] { arg }, + returnType: new PointerType(compilation.FindType(KnownTypeCode.Void)), + inst: inst); + } } else if (arg.Type.GetStackType().IsIntegerType()) { // ConversionKind.StopGCTracking should only be used with managed references, // but it's possible that we're supposed to stop tracking something we just started to track. @@ -3244,6 +3256,27 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst); } + protected internal override TranslatedExpression VisitCallIndirect(CallIndirect inst, TranslationContext context) + { + if (inst.IsInstance) { + return ErrorExpression("calli with instance method signature not supportd"); + } + var ty = new FunctionPointerType(); + if (inst.CallingConvention != System.Reflection.Metadata.SignatureCallingConvention.Default) { + ty.CallingConvention = inst.CallingConvention.ToString().ToLowerInvariant(); + } + foreach (var parameterType in inst.ParameterTypes) { + ty.TypeArguments.Add(astBuilder.ConvertType(parameterType)); + } + ty.TypeArguments.Add(astBuilder.ConvertType(inst.ReturnType)); + var functionPointer = Translate(inst.FunctionPointer); + var invocation = new InvocationExpression(new CastExpression(ty, functionPointer)); + foreach (var (arg, paramType) in inst.Arguments.Zip(inst.ParameterTypes)) { + invocation.Arguments.Add(Translate(arg, typeHint: paramType).ConvertTo(paramType, this, allowImplicitConversion: true)); + } + return invocation.WithRR(new ResolveResult(inst.ReturnType)).WithILInstruction(inst); + } + protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) { string message = "Error"; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 1d3f99617..01c7a3d69 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -2335,6 +2335,19 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor EndNode(tupleTypeElement); } + public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + StartNode(functionPointerType); + WriteKeyword(Roles.DelegateKeyword); + WriteToken(FunctionPointerType.PointerRole); + if (!functionPointerType.CallingConventionIdentifier.IsNull) { + Space(); + WriteIdentifier(functionPointerType.CallingConventionIdentifier); + } + WriteTypeArguments(functionPointerType.TypeArguments); + EndNode(functionPointerType); + } + public virtual void VisitComposedType(ComposedType composedType) { StartNode(composedType); diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 52ffa500e..c84b2850b 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -832,7 +832,19 @@ namespace ICSharpCode.Decompiler.CSharp initExpr = exprBuilder.Translate(gpr.Argument); } } else { - initExpr = exprBuilder.Translate(inst.Init, typeHint: inst.Variable.Type).ConvertTo(inst.Variable.Type, exprBuilder); + IType refType = inst.Variable.Type; + if (refType is PointerType pointerType) { + refType = new ByReferenceType(pointerType.ElementType); + } + initExpr = exprBuilder.Translate(inst.Init, typeHint: refType).ConvertTo(refType, exprBuilder); + if (initExpr is DirectionExpression dirExpr) { + if (dirExpr.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.Dereference) { + initExpr = uoe.Expression.Detach(); + } else { + initExpr = new UnaryOperatorExpression(UnaryOperatorType.AddressOf, dirExpr.Expression.Detach()) + .WithRR(new ResolveResult(inst.Variable.Type)); + } + } } fixedStmt.Variables.Add(new VariableInitializer(inst.Variable.Name, initExpr).WithILVariable(inst.Variable)); fixedStmt.EmbeddedStatement = Convert(inst.Body); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 3b3a0b216..73a4862db 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -126,6 +126,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax VisitChildren (tupleTypeElement); } + public virtual void VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + VisitChildren(functionPointerType); + } + public virtual void VisitAttribute (Attribute attribute) { VisitChildren (attribute); @@ -778,6 +783,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (tupleTypeElement); } + public virtual T VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + return VisitChildren (functionPointerType); + } + public virtual T VisitAttribute (Attribute attribute) { return VisitChildren (attribute); @@ -1430,6 +1440,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (tupleTypeElement, data); } + public virtual S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data) + { + return VisitChildren (functionPointerType, data); + } + public virtual S VisitAttribute (Attribute attribute, T data) { return VisitChildren (attribute, data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs new file mode 100644 index 000000000..4352f7ffa --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/FunctionPointerType.cs @@ -0,0 +1,83 @@ +// +// FullTypeName.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Collections.Generic; +using ICSharpCode.Decompiler.CSharp.Resolver; +using ICSharpCode.Decompiler.CSharp.TypeSystem; +using ICSharpCode.Decompiler.IL; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + public class FunctionPointerType : AstType + { + public static readonly TokenRole PointerRole = new TokenRole("*"); + public static readonly Role CallingConventionRole = new Role("Target", Identifier.Null); + + public string CallingConvention { + get { + return GetChildByRole (CallingConventionRole).Name; + } + set { + SetChildByRole (CallingConventionRole, Identifier.Create (value)); + } + } + + public Identifier CallingConventionIdentifier => GetChildByRole(CallingConventionRole); + + public AstNodeCollection TypeArguments { + get { return GetChildrenByRole (Roles.TypeArgument); } + } + + public override void AcceptVisitor (IAstVisitor visitor) + { + visitor.VisitFunctionPointerType(this); + } + + public override T AcceptVisitor (IAstVisitor visitor) + { + return visitor.VisitFunctionPointerType(this); + } + + public override S AcceptVisitor (IAstVisitor visitor, T data) + { + return visitor.VisitFunctionPointerType(this, data); + } + + protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) + { + return other is FunctionPointerType o && MatchString(this.CallingConvention, o.CallingConvention) + && this.TypeArguments.DoMatch(o.TypeArguments, match); + } + + public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null) + { + throw new NotImplementedException(); + } + } +} + diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 9ed151865..f9c2976ad 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -137,6 +137,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitMemberType(MemberType memberType); void VisitTupleType(TupleAstType tupleType); void VisitTupleTypeElement(TupleTypeElement tupleTypeElement); + void VisitFunctionPointerType(FunctionPointerType functionPointerType); void VisitComposedType(ComposedType composedType); void VisitArraySpecifier(ArraySpecifier arraySpecifier); void VisitPrimitiveType(PrimitiveType primitiveType); @@ -279,6 +280,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitMemberType(MemberType memberType); S VisitTupleType(TupleAstType tupleType); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement); + S VisitFunctionPointerType(FunctionPointerType functionPointerType); S VisitComposedType(ComposedType composedType); S VisitArraySpecifier(ArraySpecifier arraySpecifier); S VisitPrimitiveType(PrimitiveType primitiveType); @@ -421,6 +423,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitMemberType(MemberType memberType, T data); S VisitTupleType(TupleAstType tupleType, T data); S VisitTupleTypeElement(TupleTypeElement tupleTypeElement, T data); + S VisitFunctionPointerType(FunctionPointerType functionPointerType, T data); S VisitComposedType(ComposedType composedType, T data); S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data); S VisitPrimitiveType(PrimitiveType primitiveType, T data); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs index 03e08bba6..cd97b257b 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUnsafeModifier.cs @@ -73,7 +73,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms else return base.VisitComposedType(composedType); } - + + public override bool VisitFunctionPointerType(FunctionPointerType functionPointerType) + { + return true; + } + public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression) { bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index bbdceed64..4b04441a3 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -388,6 +388,9 @@ namespace ICSharpCode.Decompiler.CSharp return pointerExpr.ConvertTo(targetType, expressionBuilder); } if (targetType.Kind == TypeKind.ByReference) { + if (NormalizeTypeVisitor.TypeErasure.EquivalentTypes(targetType, this.Type)) { + return this; + } var elementType = ((ByReferenceType)targetType).ElementType; if (this.Expression is DirectionExpression thisDir && this.ILInstructions.Any(i => i.OpCode == OpCode.AddressOf) && thisDir.Expression.GetResolveResult()?.Type.GetStackType() == elementType.GetStackType()) { @@ -398,6 +401,14 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(this.ILInstructions) .WithRR(new ByReferenceResolveResult(convertedTemp.ResolveResult, ReferenceKind.Ref)); } + if (this.Type.Kind == TypeKind.ByReference && !IsFixedVariable()) { + // Convert between managed reference types. + // We can't do this by going through a pointer type because that would temporarily stop GC tracking. + // Instead, emit `ref Unsafe.As(ref expr)` + return expressionBuilder.CallUnsafeIntrinsic("As", new[] { this.Expression }, + typeArguments: new IType[] { ((ByReferenceType)this.Type).ElementType, elementType }, + returnType: targetType); + } // Convert from integer/pointer to reference. // First, convert to the corresponding pointer type: var arg = this.ConvertTo(new PointerType(elementType), expressionBuilder, checkForOverflow); @@ -472,7 +483,17 @@ namespace ICSharpCode.Decompiler.CSharp } return castExpr.WithoutILInstruction().WithRR(rr); } - + + bool IsFixedVariable() + { + if (this.Expression is DirectionExpression dirExpr) { + var inst = dirExpr.Expression.Annotation(); + return inst != null && PointerArithmeticOffset.IsFixedVariable(inst); + } else { + return false; + } + } + /// /// Gets whether an implicit conversion from 'inputType' to 'newTargetType' /// would have the same semantics as the existing cast from 'inputType' to 'oldTargetType'. diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b207c5f6d..e3af17f9b 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -70,6 +70,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs b/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs index ec9d7a088..14c4ea687 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/DetectPinnedRegions.cs @@ -453,24 +453,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } } } - + // Validate that all uses of a block consistently are inside or outside the pinned region. // (we cannot do this anymore after we start moving blocks around) + bool cloneBlocks = false; for (int i = 0; i < sourceContainer.Blocks.Count; i++) { if (reachedEdgesPerBlock[i] != 0 && reachedEdgesPerBlock[i] != sourceContainer.Blocks[i].IncomingEdgeCount) { - return false; + // Don't abort in this case, we still need to somehow represent the pinned variable with a fixed statement. + // We'll duplicate the code so that it can be both inside and outside the pinned region. + cloneBlocks = true; + break; } } context.Step("CreatePinnedRegion", block); BlockContainer body = new BlockContainer(); + Block[] clonedBlocks = cloneBlocks ? new Block[sourceContainer.Blocks.Count] : null; for (int i = 0; i < sourceContainer.Blocks.Count; i++) { if (reachedEdgesPerBlock[i] > 0) { var innerBlock = sourceContainer.Blocks[i]; + if (cloneBlocks) { + innerBlock = (Block)innerBlock.Clone(); + clonedBlocks[i] = innerBlock; + } Branch br = innerBlock.Instructions.LastOrDefault() as Branch; - if (br != null && br.TargetBlock.IncomingEdgeCount == 1 - && br.TargetContainer == sourceContainer && reachedEdgesPerBlock[br.TargetBlock.ChildIndex] == 0) - { + if (br != null && br.TargetBlock.IncomingEdgeCount == 1 + && br.TargetContainer == sourceContainer && reachedEdgesPerBlock[br.TargetBlock.ChildIndex] == 0) { // branch that leaves body. // The target block should have an instruction that resets the pin; delete that instruction: StLoc unpin = br.TargetBlock.Instructions.First() as StLoc; @@ -478,12 +486,35 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow br.TargetBlock.Instructions.RemoveAt(0); } } - + body.Blocks.Add(innerBlock); // move block into body - sourceContainer.Blocks[i] = new Block(); // replace with dummy block - // we'll delete the dummy block later + if (!cloneBlocks) { + sourceContainer.Blocks[i] = new Block(); // replace with dummy block + // we'll delete the dummy block later + } } } + if (cloneBlocks) { + // Adjust branches between cloned blocks. + foreach (var branch in body.Descendants.OfType()) { + if (branch.TargetContainer == sourceContainer) { + int i = branch.TargetBlock.ChildIndex; + if (clonedBlocks[i] != null) { + branch.TargetBlock = clonedBlocks[i]; + } + } + } + // Replace unreachable blocks in sourceContainer with dummy blocks: + bool[] isAlive = new bool[sourceContainer.Blocks.Count]; + foreach (var remainingBlock in sourceContainer.TopologicalSort(deleteUnreachableBlocks: true)) { + isAlive[remainingBlock.ChildIndex] = true; + } + for (int i = 0; i < isAlive.Length; i++) { + if (!isAlive[i]) + sourceContainer.Blocks[i] = new Block(); + } + // we'll delete the dummy blocks later + } if (body.Blocks.Count == 0) { // empty body, the entryBlock itself doesn't belong into the pinned region Debug.Assert(reachedEdgesPerBlock[entryBlock.ChildIndex] == 0); @@ -618,7 +649,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow ReplacePinnedVar(oldVar, newVar, child); } } - + private bool IsSlotAcceptingBothManagedAndUnmanagedPointers(SlotInfo slotInfo) { return slotInfo == Block.InstructionSlot || slotInfo == LdObj.TargetSlot || slotInfo == StObj.TargetSlot; @@ -634,7 +665,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow && left.MatchLdLoc(nativeVar) && IsNullOrZero(right) && trueInst.MatchBranch(out targetBlock); } - + void HandleStringToPointer(PinnedRegion pinnedRegion) { Debug.Assert(pinnedRegion.Variable.Type.IsKnownType(KnownTypeCode.String)); diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index c5638eeb2..e86597bd9 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -1456,8 +1456,8 @@ namespace ICSharpCode.Decompiler.IL signature.Header.CallingConvention, signature.ReturnType, signature.ParameterTypes, - arguments, - functionPointer + functionPointer, + arguments ); if (call.ResultType != StackType.Void) return Push(call); diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs index 20ac323a7..fb83552da 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs @@ -27,11 +27,13 @@ namespace ICSharpCode.Decompiler.IL { partial class CallIndirect { - public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true); + // Note: while in IL the arguments come first and the function pointer last; + // in the ILAst we're handling it as in C#: the function pointer is evaluated first, the arguments later. public static readonly SlotInfo FunctionPointerSlot = new SlotInfo("FunctionPointer", canInlineInto: true); + public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true); - public readonly InstructionCollection Arguments; ILInstruction functionPointer; + public readonly InstructionCollection Arguments; public bool IsInstance { get; } public bool HasExplicitThis { get; } public System.Reflection.Metadata.SignatureCallingConvention CallingConvention { get; } @@ -51,34 +53,27 @@ namespace ICSharpCode.Decompiler.IL } set { ValidateChild(value); - SetChildInstruction(ref functionPointer, value, Arguments.Count); + SetChildInstruction(ref functionPointer, value, 0); } } - protected internal override void InstructionCollectionUpdateComplete() - { - base.InstructionCollectionUpdateComplete(); - if (functionPointer?.Parent == this) - functionPointer.ChildIndex = Arguments.Count; - } - public CallIndirect(bool isInstance, bool hasExplicitThis, System.Reflection.Metadata.SignatureCallingConvention callingConvention, IType returnType, ImmutableArray parameterTypes, - IEnumerable arguments, ILInstruction functionPointer) : base(OpCode.CallIndirect) + ILInstruction functionPointer, IEnumerable arguments) : base(OpCode.CallIndirect) { this.IsInstance = isInstance; this.HasExplicitThis = hasExplicitThis; this.CallingConvention = callingConvention; this.ReturnType = returnType ?? throw new ArgumentNullException(nameof(returnType)); this.ParameterTypes = parameterTypes.ToImmutableArray(); - this.Arguments = new InstructionCollection(this, 0); - this.Arguments.AddRange(arguments); this.FunctionPointer = functionPointer; + this.Arguments = new InstructionCollection(this, 1); + this.Arguments.AddRange(arguments); } public override ILInstruction Clone() { return new CallIndirect(IsInstance, HasExplicitThis, CallingConvention, ReturnType, ParameterTypes, - this.Arguments.Select(inst => inst.Clone()), functionPointer.Clone() + functionPointer.Clone(), this.Arguments.Select(inst => inst.Clone()) ).WithILRange(this); } @@ -96,24 +91,19 @@ namespace ICSharpCode.Decompiler.IL output.Write("call.indirect "); ReturnType.WriteTo(output); output.Write('('); - bool first = true; + functionPointer.WriteTo(output, options); int firstArgument = IsInstance ? 1 : 0; if (firstArgument == 1) { + output.Write(", "); Arguments[0].WriteTo(output, options); - first = false; } - foreach (var (inst, type) in Arguments.Skip(firstArgument).Zip(ParameterTypes, (a,b) => (a,b))) { - if (first) - first = false; - else - output.Write(", "); + foreach (var (inst, type) in Arguments.Zip(ParameterTypes, (a,b) => (a,b))) { + output.Write(", "); inst.WriteTo(output, options); output.Write(" : "); type.WriteTo(output); } if (Arguments.Count > 0) - output.Write(", "); - functionPointer.WriteTo(output, options); output.Write(')'); } @@ -124,22 +114,22 @@ namespace ICSharpCode.Decompiler.IL protected override ILInstruction GetChild(int index) { - if (index == Arguments.Count) + if (index == 0) return functionPointer; - return Arguments[index]; + return Arguments[index - 1]; } protected override void SetChild(int index, ILInstruction value) { - if (index == Arguments.Count) + if (index == 0) FunctionPointer = value; else - Arguments[index] = value; + Arguments[index - 1] = value; } protected override SlotInfo GetChildSlot(int index) { - if (index == Arguments.Count) + if (index == 0) return FunctionPointerSlot; else return ArgumentSlot; @@ -148,10 +138,10 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { var flags = this.DirectFlags; + flags |= functionPointer.Flags; foreach (var inst in Arguments) { flags |= inst.Flags; } - flags |= functionPointer.Flags; return flags; } diff --git a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs index 4a32b9358..f1e7032dc 100644 --- a/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs +++ b/ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs @@ -82,5 +82,22 @@ namespace ICSharpCode.Decompiler.IL } return null; } + + + /// + /// Returns true if inst computes the address of a fixed variable; false if it computes the address of a moveable variable. + /// (see "Fixed and moveable variables" in the C# specification) + /// + internal static bool IsFixedVariable(ILInstruction inst) + { + switch (inst) { + case LdLoca ldloca: + return ldloca.Variable.CaptureScope == null; // locals are fixed if uncaptured + case LdFlda ldflda: + return IsFixedVariable(ldflda.Target); + default: + return inst.ResultType == StackType.I; + } + } } }