@ -39,6 +39,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
{
/// <summary>
/// <summary>
/// Returns true if the instruction matches the pattern for Expression.Lambda calls.
/// 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
/// <see cref="IsEmptyParameterList"/>) or a Block of kind ArrayInitializer.
/// This is only a cheap pre-filter, the actual conversion is done by
/// <see cref="ConvertLambda"/>.
/// </summary>
/// </summary>
static bool MightBeExpressionTree ( ILInstruction inst , ILInstruction stmt )
static bool MightBeExpressionTree ( ILInstruction inst , ILInstruction stmt )
{
{
@ -53,6 +60,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true ;
return true ;
}
}
/// <summary>
/// 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.
/// </summary>
static bool IsEmptyParameterList ( ILInstruction inst )
static bool IsEmptyParameterList ( ILInstruction inst )
{
{
if ( inst is CallInstruction emptyCall & & emptyCall . Method . FullNameIs ( "System.Array" , "Empty" ) & & emptyCall . Arguments . Count = = 0 )
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 ;
return false ;
}
}
/// <summary>
/// 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.
/// </summary>
bool MatchParameterVariableAssignment ( ILInstruction expr , out ILVariable parameterReferenceVar , out IType type , out string name )
bool MatchParameterVariableAssignment ( ILInstruction expr , out ILVariable parameterReferenceVar , out IType type , out string name )
{
{
// stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...)))
// stloc(v, call(Expression::Parameter, call(Type::GetTypeFromHandle, ldtoken(...)), ldstr(...)))
@ -97,6 +118,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
CSharpConversions conversions ;
CSharpConversions conversions ;
CSharpResolver resolver ;
CSharpResolver resolver ;
/// <summary>
/// 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
/// <see cref="TryConvertExpressionTree"/>. On success the parameter declarations
/// consumed by the converted tree are removed from the block.
/// </summary>
public void Run ( Block block , int pos , StatementTransformContext context )
public void Run ( Block block , int pos , StatementTransformContext context )
{
{
if ( ! context . Settings . ExpressionTrees )
if ( ! context . Settings . ExpressionTrees )
@ -125,6 +155,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// Searches instruction for the first
///
/// call Lambda(<body>, <parameter array>)
///
/// and replaces it with the ILFunction built by <see cref="ConvertLambda"/>.
/// Nested control-flow blocks are not searched. Returns true if a tree was converted.
/// </summary>
bool TryConvertExpressionTree ( ILInstruction instruction , ILInstruction statement )
bool TryConvertExpressionTree ( ILInstruction instruction , ILInstruction statement )
{
{
if ( MightBeExpressionTree ( instruction , statement ) )
if ( MightBeExpressionTree ( instruction , statement ) )
@ -154,6 +192,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// <summary>
/// Converts a Expression.Lambda call into an ILFunction.
/// Converts a Expression.Lambda call into an ILFunction.
/// If the conversion fails, null is returned.
/// 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 <see cref="ReadParameters"/>.
/// 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.
/// </summary>
/// </summary>
Func < ILInstruction > ConvertLambda ( CallInstruction instruction )
Func < ILInstruction > ConvertLambda ( CallInstruction instruction )
{
{
@ -202,6 +250,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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 <see cref="SetExpressionTreeFlag"/>.
/// </summary>
Func < ILInstruction > ConvertQuote ( CallInstruction invocation )
Func < ILInstruction > ConvertQuote ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 1 )
if ( invocation . Arguments . Count ! = 1 )
@ -231,12 +289,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
void SetExpressionTreeFlag ( ILFunction lambda , CallInstruction call )
void SetExpressionTreeFlag ( ILFunction lambda , CallInstruction call )
{
{
lambda . Kind = IsExpressionTree ( call . Method . ReturnType ) ? ILFunctionKind . ExpressionTree : ILFunctionKind . Delegate ;
lambda . Kind = IsExpressionTree ( call . Method . ReturnType ) ? ILFunctionKind . ExpressionTree : ILFunctionKind . Delegate ;
lambda . DelegateType = call . Method . ReturnType ;
lambda . DelegateType = call . Method . ReturnType ;
}
}
/// <summary>
/// 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 <see cref="MatchParameterVariableAssignment"/>.
/// An empty parameter list (see <see cref="IsEmptyParameterList"/>) yields none.
///
/// Each ParameterExpression variable enters the mapping only once; its defining
/// stloc is queued for removal.
/// </summary>
bool ReadParameters ( ILInstruction initializer , IList < IParameter > parameters , IList < ILVariable > parameterVariables , ITypeResolveContext resolveContext )
bool ReadParameters ( ILInstruction initializer , IList < IParameter > parameters , IList < ILVariable > parameterVariables , ITypeResolveContext resolveContext )
{
{
switch ( initializer )
switch ( initializer )
@ -270,6 +346,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertInstruction ( ILInstruction instruction , IType typeHint = null )
Func < ILInstruction > ConvertInstruction ( ILInstruction instruction , IType typeHint = null )
{
{
var inst = Convert ( ) ;
var inst = Convert ( ) ;
@ -437,10 +528,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// Returns true for System.Linq.Expressions.Expression<T>.
/// </summary>
bool IsExpressionTree ( IType delegateType ) = > delegateType is ParameterizedType pt
bool IsExpressionTree ( IType delegateType ) = > delegateType is ParameterizedType pt
& & pt . FullName = = "System.Linq.Expressions.Expression"
& & pt . FullName = = "System.Linq.Expressions.Expression"
& & pt . TypeArguments . Count = = 1 ;
& & pt . TypeArguments . Count = = 1 ;
/// <summary>
/// Returns T for System.Linq.Expressions.Expression<T>; any other type is returned unchanged.
/// </summary>
IType UnwrapExpressionTree ( IType delegateType )
IType UnwrapExpressionTree ( IType delegateType )
{
{
if ( delegateType is ParameterizedType pt & & pt . FullName = = "System.Linq.Expressions.Expression" & & pt . TypeArguments . Count = = 1 )
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 ;
return delegateType ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertArrayIndex ( CallInstruction invocation )
Func < ILInstruction > ConvertArrayIndex ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -480,6 +585,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return Convert ;
return Convert ;
}
}
/// <summary>
/// call ArrayLength(array)
/// =>
/// ldlen.i4(array)
/// </summary>
Func < ILInstruction > ConvertArrayLength ( CallInstruction invocation )
Func < ILInstruction > ConvertArrayLength ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 1 )
if ( invocation . Arguments . Count ! = 1 )
@ -490,6 +600,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return ( ) = > new LdLen ( StackType . I4 , converted ( ) ) ;
return ( ) = > new LdLen ( StackType . I4 , converted ( ) ) ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertBinaryNumericOperator ( CallInstruction invocation , BinaryNumericOperator op , string operatorName , bool? isChecked = null )
Func < ILInstruction > ConvertBinaryNumericOperator ( CallInstruction invocation , BinaryNumericOperator op , string operatorName , bool? isChecked = null )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -504,6 +626,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IMember method ;
IMember method ;
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call Add(left, right): built-in operator, or the operator method of decimal
case 2 :
case 2 :
return ( ) = > {
return ( ) = > {
var leftInst = left ( ) ;
var leftInst = left ( ) ;
@ -538,12 +661,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
leftType . GetSign ( ) ,
leftType . GetSign ( ) ,
isLifted : NullableType . IsNullable ( leftType ) | | NullableType . IsNullable ( rightType ) ) ;
isLifted : NullableType . IsNullable ( leftType ) | | NullableType . IsNullable ( rightType ) ) ;
} ;
} ;
// call Add(left, right, methodInfo): user-defined operator
case 3 :
case 3 :
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 2 ] , out method ) )
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 2 ] , out method ) )
return null ;
return null ;
return ( ) = > new Call ( ( IMethod ) method ) {
return ( ) = > new Call ( ( IMethod ) method ) {
Arguments = { left ( ) , right ( ) }
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 :
case 4 :
if ( ! invocation . Arguments [ 2 ] . MatchLdcI4 ( out _ ) )
if ( ! invocation . Arguments [ 2 ] . MatchLdcI4 ( out _ ) )
return null ;
return null ;
@ -566,6 +692,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILVariable , ILInstruction > ConvertBind ( CallInstruction invocation )
Func < ILVariable , ILInstruction > ConvertBind ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -605,6 +739,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertCall ( CallInstruction invocation )
Func < ILInstruction > ConvertCall ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -673,6 +821,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall ;
return BuildCall ;
}
}
/// <summary>
/// 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.
/// </summary>
ILInstruction PrepareCallTarget ( IType expectedType , ILInstruction target , IType targetType )
ILInstruction PrepareCallTarget ( IType expectedType , ILInstruction target , IType targetType )
{
{
ILInstruction result ;
ILInstruction result ;
@ -718,6 +873,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return result ;
return result ;
}
}
/// <summary>
/// Returns the value of call Constant(value, typeToken); any other instruction is returned unchanged.
/// </summary>
ILInstruction UnpackConstant ( ILInstruction inst )
ILInstruction UnpackConstant ( ILInstruction inst )
{
{
if ( ! ( inst is CallInstruction call & & call . Method . FullName = = "System.Linq.Expressions.Expression.Constant" & & call . Arguments . Count = = 2 ) )
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 ] ;
return call . Arguments [ 0 ] ;
}
}
/// <summary>
/// Converts each argument using the corresponding parameter type of method as type hint.
/// Returns null if any argument cannot be converted.
/// </summary>
Func < ILInstruction > [ ] ConvertCallArguments ( IList < ILInstruction > arguments , IMethod method )
Func < ILInstruction > [ ] ConvertCallArguments ( IList < ILInstruction > arguments , IMethod method )
{
{
var converted = new Func < ILInstruction > [ arguments . Count ] ;
var converted = new Func < ILInstruction > [ arguments . Count ] ;
@ -740,6 +902,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return converted ;
return converted ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertCast ( CallInstruction invocation , bool isChecked )
Func < ILInstruction > ConvertCast ( CallInstruction invocation , bool isChecked )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -759,6 +928,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} ;
} ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertCoalesce ( CallInstruction invocation )
Func < ILInstruction > ConvertCoalesce ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -796,6 +974,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} ;
} ;
}
}
/// <summary>
/// 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, ...
/// </summary>
Func < ILInstruction > ConvertComparison ( CallInstruction invocation , ComparisonKind kind )
Func < ILInstruction > ConvertComparison ( CallInstruction invocation , ComparisonKind kind )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -857,6 +1045,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} ;
} ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertCondition ( CallInstruction invocation )
Func < ILInstruction > ConvertCondition ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 3 )
if ( invocation . Arguments . Count ! = 3 )
@ -886,6 +1081,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} ;
} ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertConstant ( CallInstruction invocation )
Func < ILInstruction > ConvertConstant ( CallInstruction invocation )
{
{
if ( ! MatchConstantCall ( invocation , out var value ) )
if ( ! MatchConstantCall ( invocation , out var value ) )
@ -912,6 +1117,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertElementInit ( CallInstruction invocation )
Func < ILInstruction > ConvertElementInit ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -940,6 +1152,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall ;
return BuildCall ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertField ( CallInstruction invocation , IType typeHint )
Func < ILInstruction > ConvertField ( CallInstruction invocation , IType typeHint )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -953,11 +1176,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
if ( ! MatchGetFieldFromHandle ( invocation . Arguments [ 1 ] , out var member ) )
if ( ! MatchGetFieldFromHandle ( invocation . Arguments [ 1 ] , out var member ) )
return null ;
return null ;
IType type = member . ReturnType ;
if ( typeHint . SkipModifiers ( ) is ByReferenceType & & ! member . ReturnType . IsByRefLike )
{
type = typeHint ;
}
return BuildField ;
return BuildField ;
ILInstruction BuildField ( )
ILInstruction BuildField ( )
@ -987,6 +1205,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertInvoke ( CallInstruction invocation )
Func < ILInstruction > ConvertInvoke ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1016,6 +1241,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildCall ;
return BuildCall ;
}
}
/// <summary>
/// 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
/// }
/// </summary>
Func < ILInstruction > ConvertListInit ( CallInstruction invocation )
Func < ILInstruction > ConvertListInit ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -1072,6 +1308,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildBlock ;
return BuildBlock ;
}
}
/// <summary>
/// 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>.
/// </summary>
Func < ILInstruction > ConvertLogicOperator ( CallInstruction invocation , bool and )
Func < ILInstruction > ConvertLogicOperator ( CallInstruction invocation , bool and )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -1085,14 +1332,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IMember method ;
IMember method ;
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call AndAlso(left, right): built-in operator
case 2 :
case 2 :
return ( ) = > and ? IfInstruction . LogicAnd ( left ( ) , right ( ) , context . TypeSystem ) : IfInstruction . LogicOr ( left ( ) , right ( ) , context . TypeSystem ) ;
return ( ) = > and ? IfInstruction . LogicAnd ( left ( ) , right ( ) , context . TypeSystem ) : IfInstruction . LogicOr ( left ( ) , right ( ) , context . TypeSystem ) ;
// call AndAlso(left, right, methodInfo): user-defined operator
case 3 :
case 3 :
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 2 ] , out method ) )
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 2 ] , out method ) )
return null ;
return null ;
return ( ) = > new Call ( ( IMethod ) method ) {
return ( ) = > new Call ( ( IMethod ) method ) {
Arguments = { left ( ) , right ( ) }
Arguments = { left ( ) , right ( ) }
} ;
} ;
// call AndAlso(left, right, ldc.i4 liftToNull, methodInfo): AndAlso and OrElse
// declare no such overload
case 4 :
case 4 :
if ( ! invocation . Arguments [ 2 ] . MatchLdcI4 ( out _ ) )
if ( ! invocation . Arguments [ 2 ] . MatchLdcI4 ( out _ ) )
return null ;
return null ;
@ -1115,6 +1366,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertMemberInit ( CallInstruction invocation )
Func < ILInstruction > ConvertMemberInit ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1162,6 +1423,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildBlock ;
return BuildBlock ;
}
}
/// <summary>
/// call NewArrayBounds(call GetTypeFromHandle(ldtypetoken T), block ArrayInitializer { bounds })
/// =>
/// newarr T(bounds)
/// </summary>
Func < ILInstruction > ConvertNewArrayBounds ( CallInstruction invocation )
Func < ILInstruction > ConvertNewArrayBounds ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1183,6 +1449,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return ( ) = > new NewArr ( type , indices . SelectArray ( f = > f ( ) ) ) ;
return ( ) = > new NewArr ( type , indices . SelectArray ( f = > f ( ) ) ) ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertNewArrayInit ( CallInstruction invocation )
Func < ILInstruction > ConvertNewArrayInit ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1222,6 +1498,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildInitializer ;
return BuildInitializer ;
}
}
/// <summary>
/// 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)).
/// </summary>
bool MatchNew ( CallInstruction invocation , out IMethod ctor )
bool MatchNew ( CallInstruction invocation , out IMethod ctor )
{
{
ctor = null ;
ctor = null ;
@ -1229,6 +1514,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false ;
return false ;
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call New(typeHandle) or call New(constructorInfo)
case 1 :
case 1 :
if ( MatchGetTypeFromHandle ( invocation . Arguments [ 0 ] , out var type ) )
if ( MatchGetTypeFromHandle ( invocation . Arguments [ 0 ] , out var type ) )
{
{
@ -1241,6 +1527,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true ;
return true ;
}
}
return false ;
return false ;
// call New(constructorInfo, argumentList[, memberList])
case 2 :
case 2 :
case 3 :
case 3 :
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
@ -1252,10 +1539,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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)).
/// </summary>
Func < ILInstruction > ConvertNewObject ( CallInstruction invocation )
Func < ILInstruction > ConvertNewObject ( CallInstruction invocation )
{
{
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call New(typeHandle) or call New(constructorInfo): parameterless constructor
case 1 :
case 1 :
if ( MatchGetTypeFromHandle ( invocation . Arguments [ 0 ] , out var type ) )
if ( MatchGetTypeFromHandle ( invocation . Arguments [ 0 ] , out var type ) )
{
{
@ -1269,6 +1567,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return ( ) = > new NewObj ( ( IMethod ) member ) ;
return ( ) = > new NewObj ( ( IMethod ) member ) ;
}
}
return null ;
return null ;
// call New(constructorInfo, argumentList)
case 2 :
case 2 :
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
return null ;
return null ;
@ -1279,6 +1578,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if ( convertedArguments = = null )
if ( convertedArguments = = null )
return null ;
return null ;
return ( ) = > BuildNewObj ( method , convertedArguments ) ;
return ( ) = > BuildNewObj ( method , convertedArguments ) ;
// call New(constructorInfo, argumentList, memberList): anonymous types
case 3 :
case 3 :
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
if ( ! MatchGetConstructorFromHandle ( invocation . Arguments [ 0 ] , out member ) )
return null ;
return null ;
@ -1301,6 +1601,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
}
}
/// <summary>
/// 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)
/// </summary>
Func < ILInstruction > ConvertNotOperator ( CallInstruction invocation )
Func < ILInstruction > ConvertNotOperator ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count < 1 )
if ( invocation . Arguments . Count < 1 )
@ -1310,6 +1620,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call Not(expression): built-in operator
case 1 :
case 1 :
return ( ) = > {
return ( ) = > {
var argumentInst = argument ( ) ;
var argumentInst = argument ( ) ;
@ -1322,6 +1633,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
? Comp . LogicNot ( argumentInst , isLifted )
? Comp . LogicNot ( argumentInst , isLifted )
: ( ILInstruction ) new BitNot ( argumentInst , isLifted , underlyingType . GetStackType ( ) ) ;
: ( ILInstruction ) new BitNot ( argumentInst , isLifted , underlyingType . GetStackType ( ) ) ;
} ;
} ;
// call Not(expression, methodInfo): user-defined op_LogicalNot or op_OnesComplement
case 2 :
case 2 :
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 1 ] , out var method ) )
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 1 ] , out var method ) )
return null ;
return null ;
@ -1333,6 +1645,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// 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).
/// </summary>
Func < ILInstruction > ConvertProperty ( CallInstruction invocation )
Func < ILInstruction > ConvertProperty ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count < 2 )
if ( invocation . Arguments . Count < 2 )
@ -1378,6 +1699,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildProperty ;
return BuildProperty ;
}
}
/// <summary>
/// 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.
/// </summary>
Func < ILInstruction > ConvertTypeAs ( CallInstruction invocation )
Func < ILInstruction > ConvertTypeAs ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1399,6 +1727,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return BuildTypeAs ;
return BuildTypeAs ;
}
}
/// <summary>
/// call TypeIs(value, call GetTypeFromHandle(ldtypetoken T))
/// =>
/// comp.obj(isinst T(value) != ldnull)
/// </summary>
Func < ILInstruction > ConvertTypeIs ( CallInstruction invocation )
Func < ILInstruction > ConvertTypeIs ( CallInstruction invocation )
{
{
if ( invocation . Arguments . Count ! = 2 )
if ( invocation . Arguments . Count ! = 2 )
@ -1412,6 +1745,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
}
}
/// <summary>
/// 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)
/// </summary>
Func < ILInstruction > ConvertUnaryNumericOperator ( CallInstruction invocation , BinaryNumericOperator op , bool? isChecked = null )
Func < ILInstruction > ConvertUnaryNumericOperator ( CallInstruction invocation , BinaryNumericOperator op , bool? isChecked = null )
{
{
if ( invocation . Arguments . Count < 1 )
if ( invocation . Arguments . Count < 1 )
@ -1421,6 +1770,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
switch ( invocation . Arguments . Count )
switch ( invocation . Arguments . Count )
{
{
// call Negate(expression): built-in operator
case 1 :
case 1 :
return ( ) = > {
return ( ) = > {
var argumentInst = argument ( ) ;
var argumentInst = argument ( ) ;
@ -1460,6 +1810,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
argumentType . GetSign ( ) ,
argumentType . GetSign ( ) ,
isLifted : NullableType . IsNullable ( argumentType ) ) ;
isLifted : NullableType . IsNullable ( argumentType ) ) ;
} ;
} ;
// call Negate(expression, methodInfo): user-defined op_UnaryNegation
case 2 :
case 2 :
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 1 ] , out var method ) )
if ( ! MatchGetMethodFromHandle ( invocation . Arguments [ 1 ] , out var method ) )
return null ;
return null ;
@ -1470,6 +1821,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null ;
return null ;
}
}
/// <summary>
/// Post-processes the value operand of a converted Expression.Constant call;
/// <paramref name="context"/> 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.
/// </summary>
ILInstruction ConvertValue ( ILInstruction value , ILInstruction context )
ILInstruction ConvertValue ( ILInstruction value , ILInstruction context )
{
{
switch ( value )
switch ( value )
@ -1511,6 +1874,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
}
/// <summary>
/// Whether the variable has a single store of the form stloc v(newobj DisplayClass..ctor())
/// that TransformDisplayClassUsage recognizes as a potential closure.
/// </summary>
bool IsClosureReference ( ILVariable variable )
bool IsClosureReference ( ILVariable variable )
{
{
if ( ! variable . IsSingleDefinition | | ! ( variable . StoreInstructions . SingleOrDefault ( ) is StLoc store ) )
if ( ! variable . IsSingleDefinition | | ! ( variable . StoreInstructions . SingleOrDefault ( ) is StLoc store ) )
@ -1520,11 +1887,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return TransformDisplayClassUsage . IsPotentialClosure ( this . context , newObj ) ;
return TransformDisplayClassUsage . IsPotentialClosure ( this . context , newObj ) ;
}
}
/// <summary>
/// Whether the variable holds a System.Linq.Expressions.ParameterExpression.
/// </summary>
bool IsExpressionTreeParameter ( ILVariable variable )
bool IsExpressionTreeParameter ( ILVariable variable )
{
{
return variable . Type . FullName = = "System.Linq.Expressions.ParameterExpression" ;
return variable . Type . FullName = = "System.Linq.Expressions.ParameterExpression" ;
}
}
/// <summary>
/// call GetTypeFromHandle(ldtypetoken T)
/// Hands back T.
/// </summary>
internal static bool MatchGetTypeFromHandle ( ILInstruction inst , out IType type )
internal static bool MatchGetTypeFromHandle ( ILInstruction inst , out IType type )
{
{
type = null ;
type = null ;
@ -1534,6 +1908,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
& & getTypeCall . Arguments [ 0 ] . MatchLdTypeToken ( out type ) ;
& & getTypeCall . Arguments [ 0 ] . MatchLdTypeToken ( out type ) ;
}
}
/// <summary>
/// castclass System.Reflection.MethodInfo(call GetMethodFromHandle(ldmembertoken M))
/// Hands back the method M; see MatchFromHandleParameterList for the accepted
/// argument lists of the GetMethodFromHandle call.
/// </summary>
bool MatchGetMethodFromHandle ( ILInstruction inst , out IMember member )
bool MatchGetMethodFromHandle ( ILInstruction inst , out IMember member )
{
{
member = null ;
member = null ;
@ -1547,6 +1926,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList ( call , out member ) ;
return MatchFromHandleParameterList ( call , out member ) ;
}
}
/// <summary>
/// castclass System.Reflection.ConstructorInfo(call GetMethodFromHandle(ldmembertoken C))
/// Hands back the constructor C; see MatchFromHandleParameterList for the accepted
/// argument lists of the GetMethodFromHandle call.
/// </summary>
bool MatchGetConstructorFromHandle ( ILInstruction inst , out IMember member )
bool MatchGetConstructorFromHandle ( ILInstruction inst , out IMember member )
{
{
member = null ;
member = null ;
@ -1560,6 +1944,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList ( call , out member ) ;
return MatchFromHandleParameterList ( call , out member ) ;
}
}
/// <summary>
/// call GetFieldFromHandle(ldmembertoken F)
/// Hands back the field F; see MatchFromHandleParameterList for the accepted
/// argument lists of the call.
/// </summary>
bool MatchGetFieldFromHandle ( ILInstruction inst , out IMember member )
bool MatchGetFieldFromHandle ( ILInstruction inst , out IMember member )
{
{
member = null ;
member = null ;
@ -1568,6 +1957,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return MatchFromHandleParameterList ( call , out member ) ;
return MatchFromHandleParameterList ( call , out member ) ;
}
}
/// <summary>
/// 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.
/// </summary>
static bool MatchFromHandleParameterList ( CallInstruction call , out IMember member )
static bool MatchFromHandleParameterList ( CallInstruction call , out IMember member )
{
{
member = null ;
member = null ;
@ -1589,6 +1984,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true ;
return true ;
}
}
/// <summary>
/// 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().
/// </summary>
bool MatchArgumentList ( ILInstruction inst , out IList < ILInstruction > arguments )
bool MatchArgumentList ( ILInstruction inst , out IList < ILInstruction > arguments )
{
{
arguments = null ;
arguments = null ;