Browse Source

Add Mode.UnconstrainedType and implement TransformNullPropagationOnUnconstrainedGenericExpression using TryNullPropagation.

pull/1953/head
Siegfried Pammer 5 years ago
parent
commit
b114734128
  1. 5
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
  2. 110
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

5
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs

@ -100,6 +100,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name; return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name;
} }
public int? Test2()
{
return Field1?.ToString().Length ?? 42;
}
public int? GetTextLengthNRE() public int? GetTextLengthNRE()
{ {
return (Field1?.ToString()).Length; return (Field1?.ToString()).Length;

110
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -59,6 +59,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))') /// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))')
/// </summary> /// </summary>
NullableByReference, NullableByReference,
/// <summary>
/// unconstrained generic type (see the pattern described in TransformNullPropagationOnUnconstrainedGenericExpression)
/// </summary>
UnconstrainedType,
} }
/// <summary> /// <summary>
@ -154,35 +158,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var target, out var value, out var rewrapPoint, out var endBlock)) { if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var testedVariable, out var nonNullInst, out var nullInst, out var endBlock)) {
var parentInstruction = nonNullInst.Parent;
var replacement = TryNullPropagation(testedVariable, nonNullInst, nullInst, Mode.UnconstrainedType);
if (replacement == null)
return;
context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block); context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block);
// A successful match was found: switch (parentInstruction) {
// 1. The 'target' instruction, that is, the instruction where the actual 'null-propagating' call happens: case StLoc stloc:
// <target>.Call() is replaced with <value>?.Call() stloc.Value = replacement;
// 2. The 'value' instruction, that is, the instruction that produces the value: break;
// It is inlined at the location of 'target'. case Leave leave:
// 3. The 'rewrapPoint' instruction is an ancestor of the call that is used as location for the NullableRewrap instruction leave.Value = replacement;
break;
// Remove the fallback conditions and blocks default:
block.Instructions.RemoveRange(pos, 3); // if this ever happens, the pattern checked by TransformNullPropagationOnUnconstrainedGenericExpression
// inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. // has changed, but this part of the code was not properly adjusted.
target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); throw new NotSupportedException();
var siblings = rewrapPoint.Parent.Children;
int index = rewrapPoint.ChildIndex;
// remove Nullable-ctor, if necessary
if (NullableLiftingTransform.MatchNullableCtor(rewrapPoint, out var utype, out var arg) && arg.InferType(context.TypeSystem).Equals(utype)) {
rewrapPoint = arg;
} }
// insert a NullableRewrap instruction at the 'rewrapPoint' // Remove the fallback conditions and blocks
siblings[index] = new NullableRewrap(rewrapPoint); block.Instructions.RemoveRange(pos + 1, 2);
// if the endBlock is only reachable through the current block, // if the endBlock is only reachable through the current block,
// combine both blocks. // combine both blocks.
if (endBlock?.IncomingEdgeCount == 1) { if (endBlock?.IncomingEdgeCount == 1) {
block.Instructions.AddRange(endBlock.Instructions); block.Instructions.AddRange(endBlock.Instructions);
block.Instructions.RemoveAt(pos + 1); block.Instructions.RemoveAt(pos + 2);
endBlock.Remove(); endBlock.Remove();
} }
ILInlining.InlineIfPossible(block, pos, context);
} }
} }
@ -290,6 +293,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case Mode.NullableByReference: case Mode.NullableByReference:
return NullableLiftingTransform.MatchGetValueOrDefault(inst, out ILInstruction arg) return NullableLiftingTransform.MatchGetValueOrDefault(inst, out ILInstruction arg)
&& arg.MatchLdLoc(testedVar); && arg.MatchLdLoc(testedVar);
case Mode.UnconstrainedType:
// unconstrained generic type (expect: ldloc(testedVar))
return inst.MatchLdLoc(testedVar);
default: default:
throw new ArgumentOutOfRangeException(nameof(mode)); throw new ArgumentOutOfRangeException(nameof(mode));
} }
@ -334,6 +340,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
refInput: true refInput: true
).WithILRange(varLoad); ).WithILRange(varLoad);
break; break;
case Mode.UnconstrainedType:
// Wrap varLoad in nullable.unwrap:
replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref);
break;
default: default:
throw new ArgumentOutOfRangeException(nameof(mode)); throw new ArgumentOutOfRangeException(nameof(mode));
} }
@ -346,7 +356,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc defaultTemporary(ldobj type(ldloc valueTemporary))
// stloc valueTemporary(ldloca defaultTemporary) // stloc valueTemporary(ldloca defaultTemporary)
// if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 {
// stloc resultTemporary(ldnull) // stloc resultTemporary(nullInst)
// br endBlock // br endBlock
// } // }
// } // }
@ -363,23 +373,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc defaultTemporary(ldobj type(ldloc valueTemporary))
// stloc valueTemporary(ldloca defaultTemporary) // stloc valueTemporary(ldloca defaultTemporary)
// if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 {
// leave(ldnull) // leave(nullInst)
// } // }
// } // }
// leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) // leave (constrained[type].call_instruction(ldloc valueTemporary, ...))
// => // =>
// leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...)))
private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos,
out ILInstruction target, out ILInstruction value, out ILInstruction nonNullInst, out Block endBlock) out ILVariable valueTemporary, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock)
{ {
target = null; valueTemporary = null;
value = null;
nonNullInst = null; nonNullInst = null;
nullInst = null;
endBlock = null; endBlock = null;
if (pos + 3 >= block.Instructions.Count) if (pos + 3 >= block.Instructions.Count)
return false; return false;
// stloc valueTemporary(valueExpression) // stloc valueTemporary(valueExpression)
if (!(block.Instructions[pos].MatchStLoc(out var valueTemporary, out value))) if (!block.Instructions[pos].MatchStLoc(out valueTemporary, out var value))
return false; return false;
if (!(valueTemporary.Kind == VariableKind.StackSlot && valueTemporary.LoadCount == 2 && valueTemporary.StoreCount == 2)) if (!(valueTemporary.Kind == VariableKind.StackSlot && valueTemporary.LoadCount == 2 && valueTemporary.StoreCount == 2))
return false; return false;
@ -392,51 +402,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock
if (!(block.Instructions[pos + 2].MatchIfInstruction(out var condition, out var fallbackBlock1) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary))) if (!(block.Instructions[pos + 2].MatchIfInstruction(out var condition, out var fallbackBlock1) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary)))
return false; return false;
if (!MatchStLocResultTemporary(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target, out endBlock) if (!MatchStLocResultTemporary(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst, out endBlock)
&& !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target)) && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst))
return false; return false;
return true; return true;
} }
// stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...))
// br IL_0035 // br endBlock
private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock1, out ILInstruction nonNullInst, out ILInstruction finalLoad, out Block endBlock) private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock)
{ {
endBlock = null; endBlock = null;
nonNullInst = null; nonNullInst = null;
finalLoad = null; nullInst = null;
if (pos + 4 >= block.Instructions.Count) if (pos + 4 >= block.Instructions.Count)
return false; return false;
// stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...))
if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out nonNullInst))) if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out nonNullInst)))
return false; return false;
if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, nonNullInst, out finalLoad)) // br endBlock
return false;
// br IL_0035
if (!(block.Instructions[pos + 4].MatchBranch(out endBlock))) if (!(block.Instructions[pos + 4].MatchBranch(out endBlock)))
return false; return false;
// Analyze Block fallbackBlock // Analyze Block fallbackBlock
if (!(fallbackBlock1 is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock))) if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock, out nullInst)))
return false; return false;
return true; return true;
} }
private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction rewrapPoint, out ILInstruction finalLoad) private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst)
{ {
rewrapPoint = null; nonNullInst = null;
finalLoad = null; nullInst = null;
// leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) // leave (constrained[type].call_instruction(ldloc valueTemporary, ...))
if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction)) if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction))
return false; return false;
rewrapPoint = leave.Value; nonNullInst = leave.Value;
if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, rewrapPoint, out finalLoad))
return false;
// Analyze Block fallbackBlock // Analyze Block fallbackBlock
if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer))) if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer, out nullInst)))
return false; return false;
return true; return true;
} }
@ -449,8 +454,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// br endBlock // br endBlock
// } // }
// } // }
private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer) private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer, out ILInstruction nullInst)
{ {
nullInst = null;
if (!(block.Instructions.Count == 3)) if (!(block.Instructions.Count == 3))
return false; return false;
// stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc defaultTemporary(ldobj type(ldloc valueTemporary))
@ -465,21 +471,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!(block.Instructions[2].MatchIfInstruction(out var condition, out var tmp) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary))) if (!(block.Instructions[2].MatchIfInstruction(out var condition, out var tmp) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary)))
return false; return false;
// Block fallbackBlock { // Block fallbackBlock {
// stloc resultTemporary(ldnull) // stloc resultTemporary(nullInst)
// br endBlock // br endBlock
// } // }
var fallbackInst = Block.Unwrap(tmp); var fallbackInst = Block.Unwrap(tmp);
if (fallbackInst is Block fallbackBlock && endBlockOrLeaveContainer is Block endBlock) { if (fallbackInst is Block fallbackBlock && endBlockOrLeaveContainer is Block endBlock) {
if (!(fallbackBlock.Instructions.Count == 2)) if (!(fallbackBlock.Instructions.Count == 2))
return false; return false;
if (!(fallbackBlock.Instructions[0].MatchStLoc(resultTemporary, out var defaultValue) && MatchDefaultValueOrLdNull(defaultValue))) if (!fallbackBlock.Instructions[0].MatchStLoc(resultTemporary, out nullInst))
return false; return false;
if (!fallbackBlock.Instructions[1].MatchBranch(endBlock)) if (!fallbackBlock.Instructions[1].MatchBranch(endBlock))
return false; return false;
} else if (!(fallbackInst is Leave fallbackLeave && endBlockOrLeaveContainer is BlockContainer leaveContainer } else {
&& fallbackLeave.TargetContainer == leaveContainer && MatchDefaultValueOrLdNull(fallbackLeave.Value))) if (!(fallbackInst is Leave fallbackLeave && endBlockOrLeaveContainer is BlockContainer leaveContainer
return false; && fallbackLeave.TargetContainer == leaveContainer && !fallbackLeave.Value.MatchNop()))
return false;
nullInst = fallbackLeave.Value;
}
return true; return true;
} }

Loading…
Cancel
Save