Browse Source

[nullables] Add support for lifted conversions.

pull/863/head
Daniel Grunwald 8 years ago
parent
commit
133ddac256
  1. 76
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  2. 59
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 91
      ICSharpCode.Decompiler/IL/Instructions/Conv.cs
  5. 12
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  6. 3
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  7. 6
      ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
  8. 16
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  9. 5
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  10. 159
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  11. 46
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

76
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs

@ -762,4 +762,80 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -762,4 +762,80 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
throw null;
}
}
class LiftedImplicitConversions
{
public int? ExtendI4(byte? b)
{
return b;
}
public int? ExtendToI4(sbyte? b)
{
return b;
}
public long? ExtendI8(byte? b)
{
return b;
}
public long? ExtendToI8(sbyte? b)
{
return b;
}
public long? ExtendI8(int? b)
{
return b;
}
public long? ExtendToI8(uint? b)
{
return b;
}
public double? ToFloat(int? b)
{
return b;
}
public long? InArithmetic(uint? b)
{
return long.MinValue + b;
}
static double? InArithmetic2(float? nf, double? nd, float f)
{
return nf + nd + f;
}
}
class LiftedExplicitConversions
{
static void Print<T>(T? x) where T : struct
{
Console.WriteLine(x);
}
static void UncheckedCasts(int? i4, long? i8, float? f)
{
Print((byte?)i4);
Print((short?)i4);
Print((uint?)i4);
Print((uint?)i8);
Print((uint?)f);
}
static void CheckedCasts(int? i4, long? i8, float? f)
{
checked {
Print((byte?)i4);
Print((short?)i4);
Print((uint?)i4);
Print((uint?)i8);
Print((uint?)f);
}
}
}
}

59
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -55,7 +55,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -55,7 +55,11 @@ namespace ICSharpCode.Decompiler.CSharp
/// * If sizeof(C# type) &lt; sizeof(IL stack type), the C# value (sign/zero-)extended to the width of the IL stack type
/// must equal the IL value.
/// Whether sign or zero extension is used depends on the sign of the C# type (as determined by <c>IType.GetSign()</c>).
/// * If the IL instruction evaluates to a non-integer type, the C# type of the resulting expression shall match the IL stack type,
/// * If the IL instruction is a lifted nullable operation, and the underlying operation evaluates to an integer stack type,
/// the C# type of the resulting expression shall be Nullable{T}, where T is an integer type (as above).
/// The C# value shall be null iff the IL-level value evaluates to null, and otherwise the values shall correspond
/// as with non-lifted integer operations.
/// * Otherwise, the C# type of the resulting expression shall match the IL stack type,
/// and the evaluated values shall be the same.
/// </remarks>
class ExpressionBuilder : ILVisitor<TranslationContext, TranslatedExpression>
@ -122,9 +126,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -122,9 +126,19 @@ namespace ICSharpCode.Decompiler.CSharp
var cexpr = inst.AcceptVisitor(this, context);
#if DEBUG
if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown) {
// Validate the Translate post-condition (documented at beginning of this file):
if (inst.ResultType.IsIntegerType()) {
Debug.Assert(cexpr.Type.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type");
Debug.Assert(cexpr.Type.GetSign() != Sign.None, "Must have a sign specified for zero/sign-extension");
} else if (inst is ILiftableInstruction liftable && liftable.IsLifted) {
Debug.Assert(NullableType.IsNullable(cexpr.Type));
IType underlying = NullableType.GetUnderlyingType(cexpr.Type);
if (liftable.UnderlyingResultType.IsIntegerType()) {
Debug.Assert(underlying.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type");
Debug.Assert(underlying.GetSign() != Sign.None, "Must have a sign specified for zero/sign-extension");
} else {
Debug.Assert(underlying.GetStackType() == liftable.UnderlyingResultType);
}
} else {
Debug.Assert(cexpr.Type.GetStackType() == inst.ResultType);
}
@ -802,38 +816,47 @@ namespace ICSharpCode.Decompiler.CSharp @@ -802,38 +816,47 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitConv(Conv inst, TranslationContext context)
{
var arg = Translate(inst.Argument);
StackType inputStackType = inst.Argument.ResultType;
IType inputType = NullableType.GetUnderlyingType(arg.Type);
StackType inputStackType = inst.InputType;
// Note: we're dealing with two conversions here:
// a) the implicit conversion from `arg.Type` to `inputStackType`
// a) the implicit conversion from `inputType` to `inputStackType`
// (due to the ExpressionBuilder post-condition being flexible with regards to the integer type width)
// If this is a widening conversion, I'm calling the argument C# type "oversized".
// If this is a narrowing conversion, I'm calling the argument C# type "undersized".
// b) the actual conversion instruction from `inputStackType` to `inst.TargetType`
// Also, we need to be very careful with regards to the conversions we emit:
// In C#, zero vs. sign-extension depends on the input type,
// but in the ILAst conv instruction it depends on the output type.
// However, in the conv.ovf instructions, the .NET runtime behavior seems to depend on the input type,
// in violation of the ECMA-335 spec!
IType GetType(KnownTypeCode typeCode)
{
IType type = compilation.FindType(typeCode);
if (inst.IsLifted)
type = NullableType.Create(compilation, type);
return type;
}
if (inst.CheckForOverflow || inst.Kind == ConversionKind.IntToFloat) {
// We need to first convert the argument to the expected sign.
// We also need to perform any input narrowing conversion so that it doesn't get mixed up with the overflow check.
Debug.Assert(inst.InputSign != Sign.None);
if (arg.Type.GetSize() > inputStackType.GetSize() || arg.Type.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
if (inputType.GetSize() > inputStackType.GetSize() || inputType.GetSign() != inst.InputSign) {
arg = arg.ConvertTo(GetType(inputStackType.ToKnownTypeCode(inst.InputSign)), this);
}
// Because casts with overflow check match C# semantics (zero/sign-extension depends on source type),
// we can just directly cast to the target type.
return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, true)
return arg.ConvertTo(GetType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow)
.WithILInstruction(inst);
}
switch (inst.Kind) {
case ConversionKind.StopGCTracking:
if (arg.Type.Kind == TypeKind.ByReference) {
if (inputType.Kind == TypeKind.ByReference) {
// cast to corresponding pointer type:
var pointerType = new PointerType(((ByReferenceType)arg.Type).ElementType);
var pointerType = new PointerType(((ByReferenceType)inputType).ElementType);
return arg.ConvertTo(pointerType, this).WithILInstruction(inst);
} else {
Debug.Fail("ConversionKind.StopGCTracking should only be used with managed references");
@ -843,11 +866,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -843,11 +866,11 @@ namespace ICSharpCode.Decompiler.CSharp
// We just need to ensure the input type before the conversion is signed.
// Also, if the argument was translated into an oversized C# type,
// we need to perform the truncatation to the input stack type.
if (arg.Type.GetSign() != Sign.Signed || arg.Type.GetSize() > inputStackType.GetSize()) {
if (inputType.GetSign() != Sign.Signed || inputType.GetSize() > inputStackType.GetSize()) {
// Note that an undersized C# type is handled just fine:
// If it is unsigned we'll zero-extend it to the width of the inputStackType here,
// and it is signed we just combine the two sign-extensions into a single sign-extending conversion.
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Signed)), this);
arg = arg.ConvertTo(GetType(inputStackType.ToKnownTypeCode(Sign.Signed)), this);
}
// Then, we can just return the argument as-is: the ExpressionBuilder post-condition allows us
// to force our parent instruction to handle the actual sign-extension conversion.
@ -855,8 +878,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -855,8 +878,8 @@ namespace ICSharpCode.Decompiler.CSharp
return arg.WithILInstruction(inst);
case ConversionKind.ZeroExtend:
// If overflow check cannot fail, handle this just like sign extension (except for swapped signs)
if (arg.Type.GetSign() != Sign.Unsigned || arg.Type.GetSize() > inputStackType.GetSize()) {
arg = arg.ConvertTo(compilation.FindType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this);
if (inputType.GetSign() != Sign.Unsigned || inputType.GetSize() > inputStackType.GetSize()) {
arg = arg.ConvertTo(GetType(inputStackType.ToKnownTypeCode(Sign.Unsigned)), this);
}
return arg.WithILInstruction(inst);
case ConversionKind.Nop:
@ -864,7 +887,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -864,7 +887,7 @@ namespace ICSharpCode.Decompiler.CSharp
return arg.WithILInstruction(inst);
case ConversionKind.Truncate:
// Note: there are three sizes involved here:
// A = arg.Type.GetSize()
// A = inputType.GetSize()
// B = inputStackType.GetSize()
// C = inst.TargetType.GetSize().
// We know that C <= B (otherwise this wouldn't be the truncation case).
@ -882,7 +905,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -882,7 +905,7 @@ namespace ICSharpCode.Decompiler.CSharp
// Note that we must handle truncation to small integer types ourselves:
// our caller only sees the StackType.I4 and doesn't know to truncate to the small type.
if (arg.Type.GetSize() <= inst.TargetType.GetSize() && arg.Type.GetSign() == inst.TargetType.GetSign()) {
if (inputType.GetSize() <= inst.TargetType.GetSize() && inputType.GetSign() == inst.TargetType.GetSign()) {
// There's no actual truncation involved, and the result of the Conv instruction is extended
// the same way as the original instruction
// -> we can return arg directly
@ -892,7 +915,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -892,7 +915,7 @@ namespace ICSharpCode.Decompiler.CSharp
goto default; // Emit simple cast to inst.TargetType
}
} else {
Debug.Assert(inst.TargetType.GetSize() == inst.ResultType.GetSize());
Debug.Assert(inst.TargetType.GetSize() == inst.UnderlyingResultType.GetSize());
// For non-small integer types, we can let the whole unchecked truncation
// get handled by our caller (using the ExpressionBuilder post-condition).
@ -900,7 +923,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -900,7 +923,7 @@ namespace ICSharpCode.Decompiler.CSharp
return arg.WithILInstruction(inst);
}
default:
return arg.ConvertTo(compilation.FindType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow)
return arg.ConvertTo(GetType(inst.TargetType.ToKnownTypeCode()), this, inst.CheckForOverflow)
.WithILInstruction(inst);
}
}

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -279,6 +279,7 @@ @@ -279,6 +279,7 @@
<Compile Include="IL\DetectedLoop.cs" />
<Compile Include="IL\Transforms\AssignVariableNames.cs" />
<Compile Include="IL\Transforms\DetectCatchWhenConditionBlocks.cs" />
<Compile Include="IL\Transforms\NullableLiftingTransform.cs" />
<Compile Include="IL\Transforms\NullCoalescingTransform.cs" />
<Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" />
<Compile Include="Output\TextTokenWriter.cs" />

91
ICSharpCode.Decompiler/IL/Instructions/Conv.cs

@ -78,23 +78,42 @@ namespace ICSharpCode.Decompiler.IL @@ -78,23 +78,42 @@ namespace ICSharpCode.Decompiler.IL
StopGCTracking
}
partial class Conv : UnaryInstruction
partial class Conv : UnaryInstruction, ILiftableInstruction
{
/// <summary>
/// Gets the conversion kind.
/// </summary>
public readonly ConversionKind Kind;
/// <summary>
/// The target type of the conversion.
/// </summary>
public readonly PrimitiveType TargetType;
/// <summary>
/// Gets whether the conversion performs overflow-checking.
/// </summary>
public readonly bool CheckForOverflow;
/// <summary>
/// Gets whether this conversion is a lifted nullable conversion.
/// </summary>
/// <remarks>
/// A lifted conversion expects its argument to be a value of type Nullable{T}, where
/// T.GetStackType() == conv.InputType.
/// If the value is non-null:
/// * it is sign/zero-extended to InputType (based on T's sign)
/// * the underlying conversion is performed
/// * the result is wrapped in a Nullable{TargetType}.
/// If the value is null, the conversion evaluates to null of type Nullable{TargetType}.
/// (this result type is underspecified, since there may be multiple C# types for the TargetType)
/// </remarks>
public bool IsLifted { get; set; }
/// <summary>
/// Gets the stack type of the input type.
/// </summary>
/// <remarks>
/// For non-lifted conversions, this is equal to <c>Argument.ResultType</c>.
/// For lifted conversions, corresponds to the underlying type of the argument.
/// </remarks>
public readonly StackType InputType;
/// <summary>
/// Gets the sign of the input type.
///
@ -106,15 +125,38 @@ namespace ICSharpCode.Decompiler.IL @@ -106,15 +125,38 @@ namespace ICSharpCode.Decompiler.IL
/// that is purely determined by the <c>TargetType</c>.
/// </remarks>
public readonly Sign InputSign;
/// <summary>
/// The target type of the conversion.
/// </summary>
/// <remarks>
/// For lifted conversions, corresponds to the underlying target type.
/// </remarks>
public readonly PrimitiveType TargetType;
public Conv(ILInstruction argument, PrimitiveType targetType, bool checkForOverflow, Sign inputSign) : base(OpCode.Conv, argument)
public Conv(ILInstruction argument, PrimitiveType targetType, bool checkForOverflow, Sign inputSign)
: this(argument, argument.ResultType, inputSign, targetType, checkForOverflow)
{
}
public Conv(ILInstruction argument, StackType inputType, Sign inputSign, PrimitiveType targetType, bool checkForOverflow)
: base(OpCode.Conv, argument)
{
bool needsSign = checkForOverflow || targetType == PrimitiveType.R4 || targetType == PrimitiveType.R8;
Debug.Assert(!(needsSign && inputSign == Sign.None));
this.InputType = inputType;
this.InputSign = needsSign ? inputSign : Sign.None;
this.TargetType = targetType;
this.CheckForOverflow = checkForOverflow;
this.InputSign = inputSign;
Debug.Assert((inputSign != Sign.None) == (checkForOverflow || targetType == PrimitiveType.R4 || targetType == PrimitiveType.R8));
this.Kind = GetConversionKind(targetType, argument.ResultType, inputSign);
Debug.Assert(this.Kind != ConversionKind.Invalid);
this.Kind = GetConversionKind(targetType, this.InputType, this.InputSign);
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
Debug.Assert(Kind != ConversionKind.Invalid);
Debug.Assert(Argument.ResultType == (IsLifted ? StackType.O : InputType));
Debug.Assert(!(IsLifted && Kind == ConversionKind.StopGCTracking));
}
/// <summary>
@ -207,20 +249,29 @@ namespace ICSharpCode.Decompiler.IL @@ -207,20 +249,29 @@ namespace ICSharpCode.Decompiler.IL
}
public override StackType ResultType {
get { return TargetType.GetStackType(); }
get => IsLifted ? StackType.O : TargetType.GetStackType();
}
public StackType UnderlyingResultType {
get => TargetType.GetStackType();
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
if (CheckForOverflow)
if (CheckForOverflow) {
output.Write(".ovf");
if (InputSign == Sign.Unsigned)
}
if (InputSign == Sign.Unsigned) {
output.Write(".unsigned");
else if (InputSign == Sign.Signed)
} else if (InputSign == Sign.Signed) {
output.Write(".signed");
}
if (IsLifted) {
output.Write(".lifted");
}
output.Write(' ');
output.Write(Argument.ResultType);
output.Write(InputType);
output.Write("->");
output.Write(TargetType);
output.Write(' ');
@ -250,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL @@ -250,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL
public override ILInstruction UnwrapConv(ConversionKind kind)
{
if (this.Kind == kind)
if (this.Kind == kind && !IsLifted)
return Argument.UnwrapConv(kind);
else
return this;

12
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -96,6 +96,9 @@ namespace ICSharpCode.Decompiler.IL @@ -96,6 +96,9 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public abstract StackType ResultType { get; }
/* Not sure if it's a good idea to offer this on all instructions --
* e.g. ldloc for a local of type `int?` would return StackType.O (because it's not a lifted operation),
* even though the underlying type is int = StackType.I4.
/// <summary>
/// Gets the underlying result type of the value produced by this instruction.
///
@ -105,7 +108,8 @@ namespace ICSharpCode.Decompiler.IL @@ -105,7 +108,8 @@ namespace ICSharpCode.Decompiler.IL
/// If this is not a lifted operation, the underlying result type is equal to the result type.
/// </summary>
public virtual StackType UnderlyingResultType { get => ResultType; }
*/
internal static StackType CommonResultType(StackType a, StackType b)
{
if (a == StackType.I || b == StackType.I)
@ -700,4 +704,10 @@ namespace ICSharpCode.Decompiler.IL @@ -700,4 +704,10 @@ namespace ICSharpCode.Decompiler.IL
{
IMethod Method { get; }
}
public interface ILiftableInstruction
{
bool IsLifted { get; }
StackType UnderlyingResultType { get; }
}
}

3
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -410,6 +410,9 @@ namespace ICSharpCode.Decompiler.IL @@ -410,6 +410,9 @@ namespace ICSharpCode.Decompiler.IL
/// If this instruction is a conversion of the specified kind, return its argument.
/// Otherwise, return the instruction itself.
/// </summary>
/// <remarks>
/// Does not unwrap lifted conversions.
/// </remarks>
public virtual ILInstruction UnwrapConv(ConversionKind kind)
{
return this;

6
ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

@ -10,6 +10,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -10,6 +10,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
public interface IBlockTransform
{
/// <summary>
/// Runs the transform on the specified block.
///
/// Note: the transform may only modify the specified block and its descendants,
/// as well as any sibling blocks that are dominated by the specified block.
/// </summary>
void Run(Block block, BlockTransformContext context);
}

16
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -288,19 +288,29 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -288,19 +288,29 @@ namespace ICSharpCode.Decompiler.IL.Transforms
base.VisitIfInstruction(inst);
inst = HandleConditionalOperator(inst);
NullableLiftingTransform.Run(inst, context);
}
IfInstruction HandleConditionalOperator(IfInstruction inst)
{
// if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2)
Block trueInst = inst.TrueInst as Block;
if (trueInst == null || trueInst.Instructions.Count != 1)
return;
return inst;
Block falseInst = inst.FalseInst as Block;
if (falseInst == null || falseInst.Instructions.Count != 1)
return;
return inst;
ILVariable v;
ILInstruction value1, value2;
if (trueInst.Instructions[0].MatchStLoc(out v, out value1) && falseInst.Instructions[0].MatchStLoc(v, out value2)) {
context.Step("conditional operator", inst);
inst.ReplaceWith(new StLoc(v, new IfInstruction(new LogicNot(inst.Condition), value2, value1)));
var newIf = new IfInstruction(new LogicNot(inst.Condition), value2, value1);
newIf.ILRange = inst.ILRange;
inst.ReplaceWith(new StLoc(v, newIf));
return newIf;
}
return inst;
}
protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst)

5
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -275,8 +275,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -275,8 +275,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break;
}
// decide based on the target into which we are inlining
var parent = loadInst.Parent;
if (parent is ILiftableInstruction liftable && liftable.IsLifted && NullableType.IsNullable(v.Type)) {
return true; // inline into lifted operators
}
// decide based on the target into which we are inlining
switch (next.OpCode) {
case OpCode.Leave:
return parent == next;

159
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -0,0 +1,159 @@ @@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
class NullableLiftingTransform
{
public static void Run(IfInstruction inst, ILTransformContext context)
{
// if (call Nullable<inputUType>.get_HasValue(ldloca v))
// newobj Nullable<utype>.ctor(...)
// else
// default.value System.Nullable<utype>[[System.Int32]]
if (MatchHasValueCall(inst.Condition, out var v)
&& MatchNullableCtor(inst.TrueInst, out var utype, out var arg)
&& MatchNull(inst.FalseInst, utype))
{
ILInstruction lifted;
if (MatchGetValueOrDefault(arg, v)) {
// v != null ? call GetValueOrDefault(ldloca v) : null
// => conv.nop.lifted(ldloc v)
// This case is handled separately from LiftUnary() because
// that doesn't introduce nop-conversions.
context.Step("if => conv.nop.lifted", inst);
var inputUType = NullableType.GetUnderlyingType(v.Type);
lifted = new Conv(new LdLoc(v), inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), false) {
IsLifted = true,
ILRange = inst.ILRange
};
} else {
lifted = LiftUnary(arg, v, context);
}
if (lifted != null) {
Debug.Assert(IsLifted(lifted));
inst.ReplaceWith(lifted);
}
}
}
/// <summary>
/// Lifting for unary expressions.
/// Recurses into the instruction and checks that everything can be lifted,
/// and that the chain ends with `call GetValueOrDefault(ldloca inputVar)`.
/// </summary>
static ILInstruction LiftUnary(ILInstruction inst, ILVariable inputVar, ILTransformContext context)
{
if (MatchGetValueOrDefault(inst, inputVar)) {
// We found the end: the whole chain can be lifted.
context.Step("NullableLiftingTransform.LiftUnary", inst);
return new LdLoc(inputVar) { ILRange = inst.ILRange };
} else if (inst is Conv conv) {
var arg = LiftUnary(conv.Argument, inputVar, context);
if (arg != null) {
conv.Argument = arg;
conv.IsLifted = true;
return conv;
}
}
return null;
}
static bool IsLifted(ILInstruction inst)
{
return inst is ILiftableInstruction liftable && liftable.IsLifted;
}
#region Match...Call
/// <summary>
/// Matches 'call get_HasValue(ldloca v)'
/// </summary>
static bool MatchHasValueCall(ILInstruction inst, out ILVariable v)
{
v = null;
if (!(inst is Call call))
return false;
if (call.Arguments.Count != 1)
return false;
if (call.Method.Name != "get_HasValue")
return false;
if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT)
return false;
return call.Arguments[0].MatchLdLoca(out v);
}
/// <summary>
/// Matches 'newobj Nullable{underlyingType}.ctor(arg)'
/// </summary>
static bool MatchNullableCtor(ILInstruction inst, out IType underlyingType, out ILInstruction arg)
{
underlyingType = null;
arg = null;
if (!(inst is NewObj newobj))
return false;
if (!newobj.Method.IsConstructor || newobj.Arguments.Count != 1)
return false;
if (newobj.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT)
return false;
arg = newobj.Arguments[0];
underlyingType = NullableType.GetUnderlyingType(newobj.Method.DeclaringType);
return true;
}
/// <summary>
/// Matches 'call Nullable{T}.GetValueOrDefault(arg)'
/// </summary>
static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction arg)
{
arg = null;
if (!(inst is Call call))
return false;
if (call.Method.Name != "GetValueOrDefault" || call.Arguments.Count != 1)
return false;
if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT)
return false;
arg = call.Arguments[0];
return true;
}
/// <summary>
/// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)'
/// </summary>
static bool MatchGetValueOrDefault(ILInstruction inst, out ILVariable v)
{
v = null;
return MatchGetValueOrDefault(inst, out ILInstruction arg)
&& arg.MatchLdLoca(out v);
}
/// <summary>
/// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)'
/// </summary>
static bool MatchGetValueOrDefault(ILInstruction inst, ILVariable v)
{
return MatchGetValueOrDefault(inst, out ILVariable v2) && v == v2;
}
static bool MatchNull(ILInstruction inst, out IType underlyingType)
{
underlyingType = null;
if (inst.MatchDefaultValue(out IType type)) {
underlyingType = NullableType.GetUnderlyingType(type);
return NullableType.IsNullable(type);
}
underlyingType = null;
return false;
}
static bool MatchNull(ILInstruction inst, IType underlyingType)
{
return MatchNull(inst, out var utype) && utype.Equals(underlyingType);
}
#endregion
}
}

46
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -260,7 +260,51 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -260,7 +260,51 @@ namespace ICSharpCode.Decompiler.TypeSystem
return Sign.None;
}
}
/// <summary>
/// Maps the KnownTypeCode values to the corresponding PrimitiveTypes.
/// </summary>
public static PrimitiveType ToPrimitiveType(this KnownTypeCode knownTypeCode)
{
switch (knownTypeCode) {
case KnownTypeCode.SByte:
return PrimitiveType.I1;
case KnownTypeCode.Int16:
return PrimitiveType.I2;
case KnownTypeCode.Int32:
return PrimitiveType.I4;
case KnownTypeCode.Int64:
return PrimitiveType.I8;
case KnownTypeCode.Single:
return PrimitiveType.R4;
case KnownTypeCode.Double:
return PrimitiveType.R8;
case KnownTypeCode.Byte:
return PrimitiveType.U1;
case KnownTypeCode.UInt16:
return PrimitiveType.U2;
case KnownTypeCode.UInt32:
return PrimitiveType.U4;
case KnownTypeCode.UInt64:
return PrimitiveType.U8;
case KnownTypeCode.IntPtr:
return PrimitiveType.I;
case KnownTypeCode.UIntPtr:
return PrimitiveType.U;
default:
return PrimitiveType.None;
}
}
/// <summary>
/// Maps the KnownTypeCode values to the corresponding PrimitiveTypes.
/// </summary>
public static PrimitiveType ToPrimitiveType(this IType type)
{
var def = type.GetEnumUnderlyingType().GetDefinition();
return def != null ? def.KnownTypeCode.ToPrimitiveType() : PrimitiveType.None;
}
/// <summary>
/// Maps the PrimitiveType values to the corresponding KnownTypeCodes.
/// </summary>

Loading…
Cancel
Save