From ebab7d5de00b49e661b2e605e62edfe1c548962e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 7 Oct 2017 22:25:40 +0200 Subject: [PATCH] #899: Add calli opcode to ILAst --- .../Disassembler/DisassemblerHelpers.cs | 13 ++ .../ICSharpCode.Decompiler.csproj | 1 + ICSharpCode.Decompiler/IL/ILReader.cs | 35 +++- ICSharpCode.Decompiler/IL/Instructions.cs | 40 +++++ ICSharpCode.Decompiler/IL/Instructions.tt | 6 + .../IL/Instructions/CallIndirect.cs | 170 ++++++++++++++++++ .../TypeSystem/CecilLoader.cs | 2 + 7 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs diff --git a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 481b2b5c5..ba895ed79 100644 --- a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -289,6 +289,19 @@ namespace ICSharpCode.Decompiler.Disassembler writer.Write(" modreq("); ((RequiredModifierType)type).ModifierType.WriteTo(writer, ILNameSyntax.TypeName); writer.Write(") "); + } else if (type is FunctionPointerType fpt) { + writer.Write("method "); + fpt.ReturnType.WriteTo(writer, syntax); + writer.Write(" *("); + bool first = true; + foreach (var p in fpt.Parameters) { + if (first) + first = false; + else + writer.Write(", "); + p.ParameterType.WriteTo(writer, syntax); + } + writer.Write(')'); } else if (type is SentinelType) { writer.Write("..., "); ((SentinelType)type).ElementType.WriteTo(writer, syntax); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 581380e6d..912fa636d 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -271,6 +271,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index ac122c74a..dbf152ee7 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -443,7 +443,7 @@ namespace ICSharpCode.Decompiler.IL case Cil.Code.Callvirt: return DecodeCall(OpCode.CallVirt); case Cil.Code.Calli: - throw new NotImplementedException(); + return DecodeCallIndirect(); case Cil.Code.Ceq: return Push(Comparison(ComparisonKind.Equality)); case Cil.Code.Cgt: @@ -916,9 +916,16 @@ namespace ICSharpCode.Decompiler.IL ILInstruction inst = Pop(); if (expectedType != inst.ResultType) { if (expectedType == StackType.I && inst.ResultType == StackType.I4) { + // IL allows implicit I4->I conversions inst = new Conv(inst, PrimitiveType.I, false, Sign.None); + } else if (expectedType == StackType.I4 && inst.ResultType == StackType.I) { + // C++/CLI also sometimes implicitly converts in the other direction: + inst = new Conv(inst, PrimitiveType.I4, false, Sign.None); } else if (expectedType == StackType.Ref && inst.ResultType == StackType.I) { // implicitly start GC tracking + } else if (expectedType == StackType.I && inst.ResultType == StackType.Ref) { + // Implicitly stop GC tracking; this occurs when passing the result of 'ldloca' or 'ldsflda' + // to a method expecting a native pointer. } else if (inst is InvalidExpression) { ((InvalidExpression)inst).ExpectedResultType = expectedType; } else { @@ -1105,7 +1112,31 @@ namespace ICSharpCode.Decompiler.IL return call; } } - + + ILInstruction DecodeCallIndirect() + { + var functionPointer = Pop(StackType.I); + var signature = (CallSite)currentInstruction.Operand; + Debug.Assert(!signature.HasThis); + var parameterTypes = new IType[signature.Parameters.Count]; + var arguments = new ILInstruction[parameterTypes.Length]; + for (int i = signature.Parameters.Count - 1; i >= 0; i--) { + parameterTypes[i] = typeSystem.Resolve(signature.Parameters[i].ParameterType); + arguments[i] = Pop(parameterTypes[i].GetStackType()); + } + var call = new CallIndirect( + signature.CallingConvention, + typeSystem.Resolve(signature.ReturnType), + parameterTypes.ToImmutableArray(), + arguments, + functionPointer + ); + if (call.ResultType != StackType.Void) + return Push(call); + else + return call; + } + static int GetPopCount(OpCode callCode, MethodReference methodReference) { int popCount = methodReference.Parameters.Count; diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index fc83ed656..a98f573bf 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -83,6 +83,8 @@ namespace ICSharpCode.Decompiler.IL Call, /// Virtual method call. CallVirt, + /// Unsafe function pointer call. + CallIndirect, /// Checks that the input float is not NaN or infinite. Ckfinite, /// Numeric cast. @@ -1954,6 +1956,31 @@ namespace ICSharpCode.Decompiler.IL } } namespace ICSharpCode.Decompiler.IL +{ + /// Unsafe function pointer call. + public sealed partial class CallIndirect : ILInstruction + { + + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitCallIndirect(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitCallIndirect(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitCallIndirect(this, context); + } + protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) + { + var o = other as CallIndirect; + return o != null && EqualSignature(o) && Patterns.ListMatch.DoMatch(this.Arguments, o.Arguments, ref match) && this.FunctionPointer.PerformMatch(o.FunctionPointer, ref match); + } + } +} +namespace ICSharpCode.Decompiler.IL { /// Checks that the input float is not NaN or infinite. public sealed partial class Ckfinite : UnaryInstruction @@ -4769,6 +4796,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitCallIndirect(CallIndirect inst) + { + Default(inst); + } protected internal virtual void VisitCkfinite(Ckfinite inst) { Default(inst); @@ -5063,6 +5094,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitCallIndirect(CallIndirect inst) + { + return Default(inst); + } protected internal virtual T VisitCkfinite(Ckfinite inst) { return Default(inst); @@ -5357,6 +5392,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } + protected internal virtual T VisitCallIndirect(CallIndirect inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitCkfinite(Ckfinite inst, C context) { return Default(inst, context); @@ -5565,6 +5604,7 @@ namespace ICSharpCode.Decompiler.IL "comp", "call", "callvirt", + "calli", "ckfinite", "conv", "ldloc", diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 927b59c24..eacecfe0f 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -135,6 +135,12 @@ new OpCode("call", "Non-virtual method call.", Call), new OpCode("callvirt", "Virtual method call.", CustomClassName("CallVirt"), Call), + new OpCode("calli", "Unsafe function pointer call.", + CustomClassName("CallIndirect"), + CustomConstructor, CustomWriteTo, + MatchCondition("EqualSignature(o)"), + MatchCondition("Patterns.ListMatch.DoMatch(this.Arguments, o.Arguments, ref match)"), + MatchCondition("this.FunctionPointer.PerformMatch(o.FunctionPointer, ref match)")), new OpCode("ckfinite", "Checks that the input float is not NaN or infinite.", Unary, MayThrow, VoidResult), new OpCode("conv", "Numeric cast.", diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs new file mode 100644 index 000000000..7b74939a2 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/CallIndirect.cs @@ -0,0 +1,170 @@ +// Copyright (c) 2017 Daniel Grunwald +// +// 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 System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL +{ + partial class CallIndirect + { + public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true, isCollection: true); + public static readonly SlotInfo FunctionPointerSlot = new SlotInfo("FunctionPointer", canInlineInto: true); + + public readonly InstructionCollection Arguments; + ILInstruction functionPointer; + + public Mono.Cecil.MethodCallingConvention CallingConvention { get; } + public IType ReturnType { get; } + public ImmutableArray ParameterTypes { get; } + + /// + /// A 'final instruction' that gets executed after the Instructions collection. + /// Provides the return value for the block. + /// + /// + /// Blocks in containers must have 'Nop' as a final instruction. + /// + public ILInstruction FunctionPointer { + get { + return functionPointer; + } + set { + ValidateChild(value); + SetChildInstruction(ref functionPointer, value, Arguments.Count); + } + } + + protected internal override void InstructionCollectionUpdateComplete() + { + base.InstructionCollectionUpdateComplete(); + if (functionPointer?.Parent == this) + functionPointer.ChildIndex = Arguments.Count; + } + + public CallIndirect(Mono.Cecil.MethodCallingConvention callingConvention, IType returnType, ImmutableArray parameterTypes, + IEnumerable arguments, ILInstruction functionPointer) : base(OpCode.CallIndirect) + { + this.CallingConvention = callingConvention; + this.ReturnType = returnType ?? throw new ArgumentNullException("returnType"); + this.ParameterTypes = parameterTypes.ToImmutableArray(); + this.Arguments = new InstructionCollection(this, 0); + this.Arguments.AddRange(arguments); + this.FunctionPointer = functionPointer; + } + + public override ILInstruction Clone() + { + return new CallIndirect(CallingConvention, ReturnType, ParameterTypes, + this.Arguments.Select(inst => inst.Clone()), functionPointer.Clone() + ) { + ILRange = this.ILRange + }; + } + + public override StackType ResultType => ReturnType.GetStackType(); + + internal override void CheckInvariant(ILPhase phase) + { + base.CheckInvariant(phase); + Debug.Assert(Arguments.Count == ParameterTypes.Length); + } + + public override void WriteTo(ITextOutput output, ILAstWritingOptions options) + { + output.Write("call.indirect "); + ReturnType.WriteTo(output); + output.Write('('); + bool first = true; + foreach (var (inst, type) in Arguments.Zip(ParameterTypes, (a,b) => (a,b))) { + if (first) + first = false; + else + output.Write(", "); + inst.WriteTo(output, options); + output.Write(" : "); + type.WriteTo(output); + } + if (Arguments.Count > 0) + output.Write(", "); + functionPointer.WriteTo(output, options); + output.Write(')'); + } + + protected override int GetChildCount() + { + return Arguments.Count + 1; + } + + protected override ILInstruction GetChild(int index) + { + if (index == Arguments.Count) + return functionPointer; + return Arguments[index]; + } + + protected override void SetChild(int index, ILInstruction value) + { + if (index == Arguments.Count) + FunctionPointer = value; + else + Arguments[index] = value; + } + + protected override SlotInfo GetChildSlot(int index) + { + if (index == Arguments.Count) + return FunctionPointerSlot; + else + return ArgumentSlot; + } + + protected override InstructionFlags ComputeFlags() + { + var flags = this.DirectFlags; + foreach (var inst in Arguments) { + flags |= inst.Flags; + } + flags |= functionPointer.Flags; + return flags; + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow | InstructionFlags.SideEffect; + } + } + + bool EqualSignature(CallIndirect other) + { + if (CallingConvention != other.CallingConvention) + return false; + if (ParameterTypes.Length != other.ParameterTypes.Length) + return false; + for (int i = 0; i < ParameterTypes.Length; i++) { + if (!ParameterTypes[i].Equals(other.ParameterTypes[i])) + return false; + } + return ReturnType.Equals(other.ReturnType); + } + } +} diff --git a/ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs b/ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs index c52d0df6b..b07a0c835 100644 --- a/ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.Decompiler/TypeSystem/CecilLoader.cs @@ -350,6 +350,8 @@ namespace ICSharpCode.Decompiler.TypeSystem } else if (type is GenericParameter) { GenericParameter typeGP = (GenericParameter)type; return TypeParameterReference.Create(typeGP.Owner is MethodReference ? SymbolKind.Method : SymbolKind.TypeDefinition, typeGP.Position); + } else if (type is FunctionPointerType) { + return KnownTypeReference.Get(KnownTypeCode.IntPtr); } else if (type.IsNested) { ITypeReference typeRef = CreateType(type.DeclaringType, typeAttributes, ref typeIndex); int partTypeParameterCount;