diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
index dc4ce8a46..e191e807c 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
@@ -39,6 +39,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
///
/// Returns true if the instruction matches the pattern for Expression.Lambda calls.
+ ///
+ /// call Lambda(<body>, <parameter array>)
+ ///
+ /// where <parameter array> is either an empty parameter list (see
+ /// ) or a Block of kind ArrayInitializer.
+ /// This is only a cheap pre-filter, the actual conversion is done by
+ /// .
///
static bool MightBeExpressionTree(ILInstruction inst, ILInstruction stmt)
{
@@ -53,6 +60,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
+ ///
+ /// Matches the argument array of a call that has no arguments:
+ /// call System.Array.Empty(), newarr System.Linq.Expressions.ParameterExpression(...)
+ /// or newarr System.Linq.Expressions.Expression(...).
+ /// The array length is not inspected for the two newarr forms.
+ ///
static bool IsEmptyParameterList(ILInstruction inst)
{
if (inst is CallInstruction emptyCall && emptyCall.Method.FullNameIs("System.Array", "Empty") && emptyCall.Arguments.Count == 0)
@@ -64,6 +77,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
+ ///
+ /// stloc v(call Parameter(call GetTypeFromHandle(ldtypetoken T), ldstr "name"))
+ /// =>
+ /// true, with parameterReferenceVar = v, type = T and name = "name".
+ ///
+ /// v must be a single-definition local or stack slot of type
+ /// System.Linq.Expressions.ParameterExpression.
+ ///
bool MatchParameterVariableAssignment(ILInstruction expr, out ILVariable parameterReferenceVar, out IType type, out string name)
{
// stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...)))
@@ -97,6 +118,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
CSharpConversions conversions;
CSharpResolver resolver;
+ ///
+ /// Starting at pos, collects the leading run of lambda parameter declarations
+ ///
+ /// stloc v(call Parameter(call GetTypeFromHandle(ldtypetoken T), ldstr "name"))
+ ///
+ /// then tries to convert the first statement that is not such a declaration; see
+ /// . On success the parameter declarations
+ /// consumed by the converted tree are removed from the block.
+ ///
public void Run(Block block, int pos, StatementTransformContext context)
{
if (!context.Settings.ExpressionTrees)
@@ -125,6 +155,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// Searches instruction for the first
+ ///
+ /// call Lambda(<body>, <parameter array>)
+ ///
+ /// and replaces it with the ILFunction built by .
+ /// Nested control-flow blocks are not searched. Returns true if a tree was converted.
+ ///
bool TryConvertExpressionTree(ILInstruction instruction, ILInstruction statement)
{
if (MightBeExpressionTree(instruction, statement))
@@ -154,6 +192,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
/// Converts a Expression.Lambda call into an ILFunction.
/// If the conversion fails, null is returned.
+ ///
+ /// call Lambda(<body>, Block (ArrayInitializer) { stobj System.Object(delayex.ldelema System.Object(ldloc S, ldc.i4 0), ldloc V_0), ... })
+ /// =>
+ /// ILFunction(<parameters>) { BlockContainer { Block { leave (<converted body>) } } }
+ ///
+ /// The parameters are read from the array initializer by .
+ /// The call must return Expression<TDelegate>; the ILFunction gets
+ /// DelegateType = TDelegate and kind ExpressionTree if TDelegate is itself an
+ /// expression tree type, Delegate otherwise. The returned delegate does the actual
+ /// building: nothing is mutated until it is invoked.
///
Func ConvertLambda(CallInstruction instruction)
{
@@ -202,6 +250,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call Quote(<lambda>)
+ /// =>
+ /// <converted lambda>
+ ///
+ /// An argument that is already an ILFunction is passed through unchanged. Otherwise
+ /// the argument (typically a nested call Lambda(...)) is converted, and if that
+ /// yields an ILFunction its DelegateType and kind are taken from the return type of
+ /// the argument call; see .
+ ///
Func ConvertQuote(CallInstruction invocation)
{
if (invocation.Arguments.Count != 1)
@@ -231,12 +289,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// Sets DelegateType and Kind of lambda from the return type of call: a return type
+ /// Expression<TDelegate> gives ILFunctionKind.ExpressionTree, any other type gives
+ /// ILFunctionKind.Delegate.
+ ///
void SetExpressionTreeFlag(ILFunction lambda, CallInstruction call)
{
lambda.Kind = IsExpressionTree(call.Method.ReturnType) ? ILFunctionKind.ExpressionTree : ILFunctionKind.Delegate;
lambda.DelegateType = call.Method.ReturnType;
}
+ ///
+ /// Reads the lambda parameter list from the ParameterExpression[] argument of a
+ /// call Lambda(...).
+ ///
+ /// Block (ArrayInitializer) { stobj System.Object(delayex.ldelema System.Object(ldloc S, ldc.i4 i), ldloc V_i), ... }
+ /// =>
+ /// one IParameter and one ILVariable of kind Parameter per element, using the type
+ /// and name recorded for V_i by .
+ /// An empty parameter list (see ) yields none.
+ ///
+ /// Each ParameterExpression variable enters the mapping only once; its defining
+ /// stloc is queued for removal.
+ ///
bool ReadParameters(ILInstruction initializer, IList parameters, IList parameterVariables, ITypeResolveContext resolveContext)
{
switch (initializer)
@@ -270,6 +346,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// Converts one node of the expression tree into a Func<ILInstruction> building the
+ /// equivalent ILAst, or null if the node cannot be converted:
+ ///
+ /// call <name>(...) on System.Linq.Expressions.Expression => the result of the
+ /// Convert* method for <name>, e.g. call Add(a, b) => binary.numeric.add(a, b).
+ /// ILFunction (an already converted nested lambda) => the same function, with an
+ /// expression tree DelegateType unwrapped to TDelegate and kind set to Delegate.
+ /// ldloc v, v a ParameterExpression => ldloc/ldloca of the mapped parameter variable,
+ /// or, for a not yet mapped parameter of an enclosing lambda,
+ /// expression.tree.cast T(ldloc v), so conversion can continue.
+ ///
+ /// If typeHint is given and the built instruction has a different stack type, it is
+ /// wrapped in a conv to that stack type.
+ ///
Func ConvertInstruction(ILInstruction instruction, IType typeHint = null)
{
var inst = Convert();
@@ -437,10 +528,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// Returns true for System.Linq.Expressions.Expression<T>.
+ ///
bool IsExpressionTree(IType delegateType) => delegateType is ParameterizedType pt
&& pt.FullName == "System.Linq.Expressions.Expression"
&& pt.TypeArguments.Count == 1;
+ ///
+ /// Returns T for System.Linq.Expressions.Expression<T>; any other type is returned unchanged.
+ ///
IType UnwrapExpressionTree(IType delegateType)
{
if (delegateType is ParameterizedType pt && pt.FullName == "System.Linq.Expressions.Expression" && pt.TypeArguments.Count == 1)
@@ -450,6 +547,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return delegateType;
}
+ ///
+ /// call ArrayIndex(array, index)
+ /// call ArrayIndex(array, argumentList) // multi-dimensional arrays
+ /// =>
+ /// ldobj T(delayex.ldelema T(array, indices))
+ /// The element type T is taken from the inferred type of the converted array expression;
+ /// conversion fails if that type is not an array type.
+ ///
Func ConvertArrayIndex(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -480,6 +585,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return Convert;
}
+ ///
+ /// call ArrayLength(array)
+ /// =>
+ /// ldlen.i4(array)
+ ///
Func ConvertArrayLength(CallInstruction invocation)
{
if (invocation.Arguments.Count != 1)
@@ -490,6 +600,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return () => new LdLen(StackType.I4, converted());
}
+ ///
+ /// call Add(left, right) // built-in operator
+ /// call Add(left, right, MethodInfo) // user-defined operator
+ /// call Add(left, right, ldc.i4 isLiftedToNull, MethodInfo) // user-defined operator
+ /// =>
+ /// binary.add.i4(left, right) | call op_Addition(left, right)
+ /// The two-argument shape infers both operand types: decimal operands select the operator
+ /// method named operatorName, everything else produces a BinaryNumericInstruction, lifted
+ /// if either operand type is nullable. Shift operators require an Int32 right operand, all
+ /// other operators require the two operand types to match. The four-argument shape lifts
+ /// the given method if the left operand type is nullable.
+ ///
Func ConvertBinaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, string operatorName, bool? isChecked = null)
{
if (invocation.Arguments.Count < 2)
@@ -504,6 +626,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IMember method;
switch (invocation.Arguments.Count)
{
+ // call Add(left, right): built-in operator, or the operator method of decimal
case 2:
return () => {
var leftInst = left();
@@ -538,12 +661,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
leftType.GetSign(),
isLifted: NullableType.IsNullable(leftType) || NullableType.IsNullable(rightType));
};
+ // call Add(left, right, methodInfo): user-defined operator
case 3:
if (!MatchGetMethodFromHandle(invocation.Arguments[2], out method))
return null;
return () => new Call((IMethod)method) {
Arguments = { left(), right() }
};
+ // call Add(left, right, ldc.i4 liftToNull, methodInfo): the shape of the
+ // comparison factories; no arithmetic or bitwise factory declares it
case 4:
if (!invocation.Arguments[2].MatchLdcI4(out _))
return null;
@@ -566,6 +692,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call Bind(castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken set_P)), value)
+ /// call Bind(call GetFieldFromHandle(ldmembertoken F), value)
+ /// =>
+ /// callvirt set_P(ldloc target, value)
+ /// stobj T(delayex.ldflda F(ldloc target), value)
+ /// The returned builder takes the variable holding the object being initialized.
+ ///
Func ConvertBind(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -605,6 +739,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
}
+ ///
+ /// call Call(MethodInfo, argumentList) // static method
+ /// call Call(target, MethodInfo, argumentList) // target is ldnull for static methods
+ /// =>
+ /// call M(arguments) | callvirt M(target, arguments)
+ ///
+ /// Method group conversion:
+ /// call Call(call Constant(MethodInfo M, ...), MethodInfo MethodInfo.CreateDelegate, argumentList { call Constant(typeof(D), ...), targetObject })
+ /// =>
+ /// newobj D..ctor(targetObject, ldftn M)
+ ///
+ /// The argument list is normally a single array-initializer block; if it is not, the
+ /// remaining arguments of the invocation are taken as the argument list directly.
+ ///
Func ConvertCall(CallInstruction invocation)
{
if (invocation.Arguments.Count < 2)
@@ -673,6 +821,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall;
}
+ ///
+ /// Adapts a converted call target to the 'this' argument expected by a call on
+ /// expectedType: takes its address (ldloca or addressof) where a by-reference 'this' is
+ /// required, and boxes it where a boxed value type is required. If exactly one of the
+ /// expected type and the result is unknown, a conv to the other side's primitive type is
+ /// inserted, so that missing references do not produce mismatched call arguments.
+ ///
ILInstruction PrepareCallTarget(IType expectedType, ILInstruction target, IType targetType)
{
ILInstruction result;
@@ -718,6 +873,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return result;
}
+ ///
+ /// Returns the value of call Constant(value, typeToken); any other instruction is returned unchanged.
+ ///
ILInstruction UnpackConstant(ILInstruction inst)
{
if (!(inst is CallInstruction call && call.Method.FullName == "System.Linq.Expressions.Expression.Constant" && call.Arguments.Count == 2))
@@ -725,6 +883,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return call.Arguments[0];
}
+ ///
+ /// Converts each argument using the corresponding parameter type of method as type hint.
+ /// Returns null if any argument cannot be converted.
+ ///
Func[] ConvertCallArguments(IList arguments, IMethod method)
{
var converted = new Func[arguments.Count];
@@ -740,6 +902,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return converted;
}
+ ///
+ /// call Convert(expr, call GetTypeFromHandle(ldtypetoken T))
+ /// =>
+ /// expression.tree.cast T(expr)
+ /// A conversion from a small integer type to Int32 produces the operand unchanged,
+ /// because such values already occupy an I4 stack slot.
+ ///
Func ConvertCast(CallInstruction invocation, bool isChecked)
{
if (invocation.Arguments.Count < 2)
@@ -759,6 +928,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
}
+ ///
+ /// call Coalesce(leftExpr, rightExpr)
+ /// =>
+ /// if.notnull(left, right)
+ /// The result type and NullCoalescingKind are picked from the inferred operand types: a
+ /// nullable left whose underlying type the right operand implicitly converts to gives
+ /// Nullable or NullableWithValueFallback, everything else gives Ref.
+ /// The three-argument overload, which carries an explicit conversion lambda, is not matched.
+ ///
Func ConvertCoalesce(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -796,6 +974,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
}
+ ///
+ /// call Equal(left, right, ldc.i4 liftToNull, castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken op_Equality)))
+ /// =>
+ /// call op_Equality(left, right), lifted via LiftUserDefinedOperator when left is Nullable<T>
+ /// call Equal(left, right)
+ /// =>
+ /// call op_Equality(left, right) for a user-defined operator found by the resolver, or for two
+ /// string operands; otherwise comp.i4(left == right), lifted[C#] when left is Nullable<T>.
+ /// Equal stands for whichever factory kind selects: NotEqual, LessThan, GreaterThan, ...
+ ///
Func ConvertComparison(CallInstruction invocation, ComparisonKind kind)
{
if (invocation.Arguments.Count < 2)
@@ -857,6 +1045,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
}
+ ///
+ /// call Condition(conditionExpr, trueExpr, falseExpr)
+ /// =>
+ /// if (condition) trueValue else falseValue
+ /// The builder bails out unless the condition infers to bool and both branches infer to types
+ /// that are equivalent under type erasure; the true branch's type becomes the result type.
+ ///
Func ConvertCondition(CallInstruction invocation)
{
if (invocation.Arguments.Count != 3)
@@ -886,6 +1081,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
};
}
+ ///
+ /// call Constant(box T(value), call GetTypeFromHandle(ldtypetoken T))
+ /// =>
+ /// value, or expression.tree.cast T(value) when T is an enum or bool
+ /// call Constant(ldstr "a" / ldnull / call GetTypeFromHandle(ldtypetoken X) / ldloc displayClass)
+ /// =>
+ /// the reference itself; only value-type constants are boxed.
+ /// Roslyn emits the two-argument Constant(object, Type) overload; the legacy .NET Framework
+ /// csc uses the one-argument Constant(object) overload for display-class instances.
+ ///
Func ConvertConstant(CallInstruction invocation)
{
if (!MatchConstantCall(invocation, out var value))
@@ -912,6 +1117,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call ElementInit(castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken Add)),
+ /// block ArrayInitializer { newarr Expression + one stobj per argument })
+ /// =>
+ /// callvirt Add(args), or call Add(args) for a static method, with no target argument yet;
+ /// ConvertListInit inserts the collection instance at index 0.
+ ///
Func ConvertElementInit(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -940,6 +1152,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall;
}
+ ///
+ /// call Field(ldnull, call GetFieldFromHandle(ldmembertoken F))
+ /// =>
+ /// ldobj T(ldsflda F)
+ /// call Field(targetExpr, call GetFieldFromHandle(ldmembertoken F))
+ /// =>
+ /// ldobj T(delayex.ldflda F(target)), with target wrapped in addressof when the declaring
+ /// type is a value type.
+ /// A by-ref typeHint on a field whose type is not by-ref-like drops the ldobj, so the field
+ /// address itself is produced.
+ ///
Func ConvertField(CallInstruction invocation, IType typeHint)
{
if (invocation.Arguments.Count != 2)
@@ -953,11 +1176,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (!MatchGetFieldFromHandle(invocation.Arguments[1], out var member))
return null;
- IType type = member.ReturnType;
- if (typeHint.SkipModifiers() is ByReferenceType && !member.ReturnType.IsByRefLike)
- {
- type = typeHint;
- }
return BuildField;
ILInstruction BuildField()
@@ -987,6 +1205,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call Invoke(targetExpr, block ArrayInitializer { newarr Expression + one stobj per argument })
+ /// =>
+ /// callvirt Invoke(target, args)
+ /// The invoke method comes from the delegate type the target infers to; the builder bails out
+ /// if that type has none, or if an argument fails to convert.
+ ///
Func ConvertInvoke(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1016,6 +1241,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall;
}
+ ///
+ /// call ListInit(call New(...), block ArrayInitializer { call ElementInit(addMethod, args), ... })
+ /// or, with the add-method handle passed separately:
+ /// call ListInit(call New(...), addMethod, block ArrayInitializer { args })
+ /// =>
+ /// Block (CollectionInitializer) {
+ /// stloc initializer(newobj ctor(...))
+ /// callvirt Add(ldloc initializer, args) // one per element
+ /// final: ldloc initializer
+ /// }
+ ///
Func ConvertListInit(CallInstruction invocation)
{
if (invocation.Arguments.Count < 2)
@@ -1072,6 +1308,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildBlock;
}
+ ///
+ /// call AndAlso(left, right) / call OrElse(left, right)
+ /// =>
+ /// if (left) right else ldc.i4 0 / if (left) ldc.i4 1 else right
+ ///
+ /// call AndAlso(left, right, method)
+ /// call AndAlso(left, right, ldc.i4 liftToNull, method)
+ /// =>
+ /// call method(left, right); the four-argument form lifts the user-defined operator
+ /// if the left operand infers to Nullable<T>.
+ ///
Func ConvertLogicOperator(CallInstruction invocation, bool and)
{
if (invocation.Arguments.Count < 2)
@@ -1085,14 +1332,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IMember method;
switch (invocation.Arguments.Count)
{
+ // call AndAlso(left, right): built-in operator
case 2:
return () => and ? IfInstruction.LogicAnd(left(), right(), context.TypeSystem) : IfInstruction.LogicOr(left(), right(), context.TypeSystem);
+ // call AndAlso(left, right, methodInfo): user-defined operator
case 3:
if (!MatchGetMethodFromHandle(invocation.Arguments[2], out method))
return null;
return () => new Call((IMethod)method) {
Arguments = { left(), right() }
};
+ // call AndAlso(left, right, ldc.i4 liftToNull, methodInfo): AndAlso and OrElse
+ // declare no such overload
case 4:
if (!invocation.Arguments[2].MatchLdcI4(out _))
return null;
@@ -1115,6 +1366,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call MemberInit(call New(...), block ArrayInitializer { call Bind(member, value), ... })
+ /// =>
+ /// Block (CollectionInitializer) {
+ /// stloc initializer(newobj ctor(...))
+ /// callvirt set_Member(ldloc initializer, value) // stobj for field bindings
+ /// final: ldloc initializer
+ /// }
+ /// Only Expression.Bind elements are supported; any other binding kind fails the match.
+ ///
Func ConvertMemberInit(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1162,6 +1423,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildBlock;
}
+ ///
+ /// call NewArrayBounds(call GetTypeFromHandle(ldtypetoken T), block ArrayInitializer { bounds })
+ /// =>
+ /// newarr T(bounds)
+ ///
Func ConvertNewArrayBounds(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1183,6 +1449,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return () => new NewArr(type, indices.SelectArray(f => f()));
}
+ ///
+ /// call NewArrayInit(call GetTypeFromHandle(ldtypetoken T), block ArrayInitializer { values })
+ /// =>
+ /// Block (ArrayInitializer) {
+ /// stloc initializer(newarr T(ldc.i4 n))
+ /// stobj T(delayex.ldelema T(ldloc initializer, ldc.i4 i), value) // one per element
+ /// final: ldloc initializer
+ /// }
+ /// An empty value list produces a bare newarr T(ldc.i4 0) instead of a block.
+ ///
Func ConvertNewArrayInit(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1222,6 +1498,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildInitializer;
}
+ ///
+ /// Matches the constructor named by a call to Expression.New; produces no ILAst.
+ /// call New(call GetTypeFromHandle(ldtypetoken T)) -> the parameterless constructor of T
+ /// call New(ctorInfo)
+ /// call New(ctorInfo, block ArrayInitializer { args })
+ /// call New(ctorInfo, block ArrayInitializer { args }, block ArrayInitializer { members })
+ /// -> the constructor named by ctorInfo, which is
+ /// castclass ConstructorInfo(call GetMethodFromHandle(ldmembertoken .ctor, ldtypetoken T)).
+ ///
bool MatchNew(CallInstruction invocation, out IMethod ctor)
{
ctor = null;
@@ -1229,6 +1514,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
switch (invocation.Arguments.Count)
{
+ // call New(typeHandle) or call New(constructorInfo)
case 1:
if (MatchGetTypeFromHandle(invocation.Arguments[0], out var type))
{
@@ -1241,6 +1527,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
return false;
+ // call New(constructorInfo, argumentList[, memberList])
case 2:
case 3:
if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member))
@@ -1252,10 +1539,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call New(call GetTypeFromHandle(ldtypetoken T)) / call New(ctorInfo)
+ /// => newobj ctor()
+ /// call New(ctorInfo, block ArrayInitializer { args })
+ /// => newobj ctor(args)
+ /// call New(ctorInfo, block ArrayInitializer { args }, block ArrayInitializer { members })
+ /// => newobj ctor(args); the member list, which names the anonymous type's property
+ /// accessors, has no ILAst equivalent and is dropped.
+ /// ctorInfo is castclass ConstructorInfo(call GetMethodFromHandle(ldmembertoken .ctor, ldtypetoken T)).
+ ///
Func ConvertNewObject(CallInstruction invocation)
{
switch (invocation.Arguments.Count)
{
+ // call New(typeHandle) or call New(constructorInfo): parameterless constructor
case 1:
if (MatchGetTypeFromHandle(invocation.Arguments[0], out var type))
{
@@ -1269,6 +1567,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return () => new NewObj((IMethod)member);
}
return null;
+ // call New(constructorInfo, argumentList)
case 2:
if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member))
return null;
@@ -1279,6 +1578,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (convertedArguments == null)
return null;
return () => BuildNewObj(method, convertedArguments);
+ // call New(constructorInfo, argumentList, memberList): anonymous types
case 3:
if (!MatchGetConstructorFromHandle(invocation.Arguments[0], out member))
return null;
@@ -1301,6 +1601,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
}
+ ///
+ /// call Not(value) / call OnesComplement(value)
+ /// =>
+ /// logic.not(value) if value infers to bool, otherwise bit.not(value) on the
+ /// underlying type's stack type; both are lifted if the inferred type is Nullable<T>.
+ ///
+ /// call Not(value, castclass MethodInfo(call GetMethodFromHandle(ldmembertoken op_LogicalNot, ldtypetoken T)))
+ /// =>
+ /// call op_LogicalNot(value)
+ ///
Func ConvertNotOperator(CallInstruction invocation)
{
if (invocation.Arguments.Count < 1)
@@ -1310,6 +1620,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
switch (invocation.Arguments.Count)
{
+ // call Not(expression): built-in operator
case 1:
return () => {
var argumentInst = argument();
@@ -1322,6 +1633,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
? Comp.LogicNot(argumentInst, isLifted)
: (ILInstruction)new BitNot(argumentInst, isLifted, underlyingType.GetStackType());
};
+ // call Not(expression, methodInfo): user-defined op_LogicalNot or op_OnesComplement
case 2:
if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var method))
return null;
@@ -1333,6 +1645,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// call Property(target, castclass MethodInfo(call GetMethodFromHandle(ldmembertoken get_X, ldtypetoken T)))
+ /// call Property(target, accessorInfo, block ArrayInitializer { indices })
+ /// =>
+ /// callvirt get_X(target, indices)
+ /// A static accessor uses call instead of callvirt; ldnull as the first argument
+ /// emits no target argument. The target is adapted to the accessor's this-pointer
+ /// stack type (address-of or box for value types).
+ ///
Func ConvertProperty(CallInstruction invocation)
{
if (invocation.Arguments.Count < 2)
@@ -1378,6 +1699,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildProperty;
}
+ ///
+ /// call TypeAs(value, call GetTypeFromHandle(ldtypetoken T))
+ /// =>
+ /// isinst T(value)
+ /// For T = Nullable<U> the result is wrapped in unbox.any T, because isinst on a
+ /// nullable type tests for boxed U per ECMA-335, III.4.6.
+ ///
Func ConvertTypeAs(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1399,6 +1727,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildTypeAs;
}
+ ///
+ /// call TypeIs(value, call GetTypeFromHandle(ldtypetoken T))
+ /// =>
+ /// comp.obj(isinst T(value) != ldnull)
+ ///
Func ConvertTypeIs(CallInstruction invocation)
{
if (invocation.Arguments.Count != 2)
@@ -1412,6 +1745,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
}
+ ///
+ /// call Negate(argumentExpr)
+ /// =>
+ /// binary.sub.i4(ldc.i4 0, argument)
+ ///
+ /// The built-in form has no MethodInfo: the operation is expressed as a binary
+ /// instruction with a zero literal on the left. The literal is picked in the returned
+ /// builder from the stack type inferred for the converted argument: ldc.i4 0 for I4,
+ /// ldc.i8 0 for I8, conv i4->i for I, ldc.f4/ldc.f8 0 for F4/F8 and ldc.decimal 0
+ /// for System.Decimal; any other stack type is rejected. A nullable argument type
+ /// produces a lifted instruction over the underlying type.
+ ///
+ /// call Negate(argumentExpr, castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken op_UnaryNegation)))
+ /// =>
+ /// call op_UnaryNegation(argument)
+ ///
Func ConvertUnaryNumericOperator(CallInstruction invocation, BinaryNumericOperator op, bool? isChecked = null)
{
if (invocation.Arguments.Count < 1)
@@ -1421,6 +1770,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
switch (invocation.Arguments.Count)
{
+ // call Negate(expression): built-in operator
case 1:
return () => {
var argumentInst = argument();
@@ -1460,6 +1810,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
argumentType.GetSign(),
isLifted: NullableType.IsNullable(argumentType));
};
+ // call Negate(expression, methodInfo): user-defined op_UnaryNegation
case 2:
if (!MatchGetMethodFromHandle(invocation.Arguments[1], out var method))
return null;
@@ -1470,6 +1821,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
}
+ ///
+ /// Post-processes the value operand of a converted Expression.Constant call;
+ /// is the surrounding Expression call instruction.
+ /// A ldloc of an expression-tree ParameterExpression variable is mapped to the
+ /// ILVariable generated for that parameter, but only where a constant may legally
+ /// stand in for it: under Expression.Call with an integer stack type it becomes
+ /// ldloca of the mapped variable, an unmapped variable is cloned unchanged, and any
+ /// other mapped use is rejected (null).
+ /// A ldloc of a closure reference is returned as is, after marking the variable as
+ /// a display-class local and registering it as a captured variable of the enclosing
+ /// ILFunction. Everything else is cloned.
+ ///
ILInstruction ConvertValue(ILInstruction value, ILInstruction context)
{
switch (value)
@@ -1511,6 +1874,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
+ ///
+ /// Whether the variable has a single store of the form stloc v(newobj DisplayClass..ctor())
+ /// that TransformDisplayClassUsage recognizes as a potential closure.
+ ///
bool IsClosureReference(ILVariable variable)
{
if (!variable.IsSingleDefinition || !(variable.StoreInstructions.SingleOrDefault() is StLoc store))
@@ -1520,11 +1887,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return TransformDisplayClassUsage.IsPotentialClosure(this.context, newObj);
}
+ ///
+ /// Whether the variable holds a System.Linq.Expressions.ParameterExpression.
+ ///
bool IsExpressionTreeParameter(ILVariable variable)
{
return variable.Type.FullName == "System.Linq.Expressions.ParameterExpression";
}
+ ///
+ /// call GetTypeFromHandle(ldtypetoken T)
+ /// Hands back T.
+ ///
internal static bool MatchGetTypeFromHandle(ILInstruction inst, out IType type)
{
type = null;
@@ -1534,6 +1908,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
&& getTypeCall.Arguments[0].MatchLdTypeToken(out type);
}
+ ///
+ /// castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken M))
+ /// Hands back the method M; see MatchFromHandleParameterList for the accepted
+ /// argument lists of the GetMethodFromHandle call.
+ ///
bool MatchGetMethodFromHandle(ILInstruction inst, out IMember member)
{
member = null;
@@ -1547,6 +1926,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList(call, out member);
}
+ ///
+ /// castclass System.Reflection.ConstructorInfo(call GetMethodFromHandle(ldmembertoken C))
+ /// Hands back the constructor C; see MatchFromHandleParameterList for the accepted
+ /// argument lists of the GetMethodFromHandle call.
+ ///
bool MatchGetConstructorFromHandle(ILInstruction inst, out IMember member)
{
member = null;
@@ -1560,6 +1944,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList(call, out member);
}
+ ///
+ /// call GetFieldFromHandle(ldmembertoken F)
+ /// Hands back the field F; see MatchFromHandleParameterList for the accepted
+ /// argument lists of the call.
+ ///
bool MatchGetFieldFromHandle(ILInstruction inst, out IMember member)
{
member = null;
@@ -1568,6 +1957,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList(call, out member);
}
+ ///
+ /// Accepts the argument list of a GetMethodFromHandle/GetFieldFromHandle call in both
+ /// its overloads: (ldmembertoken M), and (ldmembertoken M, ldtypetoken T) for a member
+ /// of a generic type. Hands back M; the declaring-type token is only checked for shape,
+ /// because the member token already carries the specialized member.
+ ///
static bool MatchFromHandleParameterList(CallInstruction call, out IMember member)
{
member = null;
@@ -1589,6 +1984,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
+ ///
+ /// Block (ArrayInitializer) {
+ /// stloc S(newarr T(ldc.i4 n))
+ /// stobj T(ldelema T(ldloc S, ldc.i4 0), value0)
+ /// ...
+ /// stobj T(ldelema T(ldloc S, ldc.i4 n-1), value_n-1)
+ /// final: ldloc S
+ /// }
+ /// Hands back the element values in index order; the indices must be the dense
+ /// sequence 0..n-1. An empty list is also matched outside a block, as
+ /// newarr ParameterExpression/Expression(ldc.i4 0) or call Array.Empty().
+ ///
bool MatchArgumentList(ILInstruction inst, out IList arguments)
{
arguments = null;