Browse Source

#2050: Don't put ldflda/ldelema with immediate exceptions into StObj.TargetSlot.

The C# translation of StObj will always apply delayed exceptions in these two cases, so putting an instruction with delayed exceptions in that slot would change program semantics.
pull/2069/head
Daniel Grunwald 6 years ago
parent
commit
925a4e1e65
  1. 29
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs
  2. 3
      ICSharpCode.Decompiler/IL/Instructions.cs
  3. 12
      ICSharpCode.Decompiler/IL/Instructions.tt
  4. 21
      ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs
  5. 4
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  6. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

29
ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs

@ -26,6 +26,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -26,6 +26,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
AvoidLifting();
BitNot();
FieldAccessOrderOfEvaluation(null);
}
static void AvoidLifting()
@ -82,5 +83,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -82,5 +83,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
if (!b)
throw new InvalidOperationException();
}
static int GetInt()
{
Console.WriteLine("got int");
return 42;
}
int intField;
static void FieldAccessOrderOfEvaluation(NullableTests c)
{
Console.WriteLine("GetInt, then NRE:");
try {
c.intField = GetInt();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.WriteLine("NRE before GetInt:");
try {
#if CS60
ref int i = ref c.intField;
i = GetInt();
#endif
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
}
}
}

3
ICSharpCode.Decompiler/IL/Instructions.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2014 Daniel Grunwald
// Copyright (c) 2014-2020 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@ -4117,6 +4117,7 @@ namespace ICSharpCode.Decompiler.IL @@ -4117,6 +4117,7 @@ namespace ICSharpCode.Decompiler.IL
base.CheckInvariant(phase);
Debug.Assert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
Debug.Assert(value.ResultType == type.GetStackType());
Debug.Assert(IsValidTarget(target));
}
}
}

12
ICSharpCode.Decompiler/IL/Instructions.tt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
// Copyright (c) 2014 Daniel Grunwald
// Copyright (c) 2014-2020 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
@ -253,7 +253,8 @@ @@ -253,7 +253,8 @@
new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine
+ "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())",
CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")),
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()"),
CustomInvariant("Debug.Assert(IsValidTarget(target));")),
new OpCode("box", "Boxes a value.",
Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("O")),
@ -1158,6 +1159,13 @@ protected override void Disconnected() @@ -1158,6 +1159,13 @@ protected override void Disconnected()
opCode.WriteOpCodePrefix.Add("if (IsReadOnly)" + Environment.NewLine + "\toutput.Write(\"readonly.\");");
opCode.PerformMatchConditions.Add("IsReadOnly == o.IsReadOnly");
};
static Action<OpCode> CustomInvariant(string code)
{
return opCode => {
opCode.Invariants.Add(code);
};
}
static Action<OpCode> Pattern = opCode => {
BaseClass("PatternInstruction")(opCode);

21
ICSharpCode.Decompiler/IL/Instructions/LdFlda.cs

@ -43,4 +43,25 @@ namespace ICSharpCode.Decompiler.IL @@ -43,4 +43,25 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
public sealed partial class StObj
{
/// <summary>
/// For a store to a field or array element, C# will only throw NullReferenceException/IndexOfBoundsException
/// after the value-to-be-stored has been computed.
/// This means a LdFlda/LdElema used as target for StObj must have DelayExceptions==true to allow a translation to C#
/// without changing the program semantics. See https://github.com/icsharpcode/ILSpy/issues/2050
/// </summary>
/// <remarks>This is part of the StObj invariant.</remarks>
public static bool IsValidTarget(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.LdElema:
case OpCode.LdFlda:
return !inst.HasDirectFlag(InstructionFlags.MayThrow);
default:
return true;
}
}
}
}

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

@ -581,6 +581,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -581,6 +581,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return FindResult.Stop;
if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) {
// Match found, we can inline
if (expr.SlotInfo == StObj.TargetSlot && !StObj.IsValidTarget(expressionBeingMoved)) {
// special case: the StObj.TargetSlot does not accept some kinds of expressions
return FindResult.Stop;
}
return FindResult.Found(expr);
} else if (expr is Block block) {
// Inlining into inline-blocks?

2
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -670,7 +670,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -670,7 +670,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (type.GetStackType() != value.ResultType) {
value = new Conv(value, type.ToPrimitiveType(), false, Sign.None);
}
return new StObj(new LdElema(type, array, indices), value, type);
return new StObj(new LdElema(type, array, indices) { DelayExceptions = true }, value, type);
}
internal static ILInstruction GetNullExpression(IType elementType)

Loading…
Cancel
Save