diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs index f4a3ed8d7..6659f1503 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NullableTests.cs @@ -23,6 +23,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness class NullableTests { static void Main() + { + AvoidLifting(); + BitNot(); + } + + static void AvoidLifting() { Console.WriteLine("MayThrow:"); Console.WriteLine(MayThrow(10, 2, 3)); @@ -33,8 +39,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Console.WriteLine(NotUsingAllInputs(5, null)); Console.WriteLine("UsingUntestedValue:"); - Console.WriteLine(NotUsingAllInputs(5, 3)); - Console.WriteLine(NotUsingAllInputs(5, null)); + Console.WriteLine(UsingUntestedValue(5, 3)); + Console.WriteLine(UsingUntestedValue(5, null)); } static int? MayThrow(int? a, int? b, int? c) @@ -54,5 +60,27 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness // cannot be lifted because the value differs if b == null return a.HasValue ? a.GetValueOrDefault() + b.GetValueOrDefault() : default(int?); } + + static void BitNot() + { + UInt32? value = 0; + Assert(~value == UInt32.MaxValue); + UInt64? value2 = 0; + Assert(~value2 == UInt64.MaxValue); + UInt16? value3 = 0; + Assert((UInt16)~value3 == (UInt16)UInt16.MaxValue); + UInt32 value4 = 0; + Assert(~value4 == UInt32.MaxValue); + UInt64 value5 = 0; + Assert(~value5 == UInt64.MaxValue); + UInt16 value6 = 0; + Assert((UInt16)~value6 == UInt16.MaxValue); + } + + static void Assert(bool b) + { + if (!b) + throw new InvalidOperationException(); + } } } diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index ff5b8838a..f65fe7d16 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -283,10 +283,9 @@ namespace ICSharpCode.Decompiler.CSharp UnwrapChild(uoe.Expression).ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion) ).WithRR(new ResolveResult(targetType)).WithoutILInstruction(); } - bool isLifted = type.IsKnownType(KnownTypeCode.NullableOfT) && targetType.IsKnownType(KnownTypeCode.NullableOfT); - IType utype = isLifted ? NullableType.GetUnderlyingType(type) : type; - IType targetUType = isLifted ? NullableType.GetUnderlyingType(targetType) : targetType; - if (type.IsKnownType(KnownTypeCode.Boolean) && targetType.GetStackType().IsIntegerType()) { + IType utype = NullableType.GetUnderlyingType(type); + IType targetUType = NullableType.GetUnderlyingType(targetType); + if (type.IsKnownType(KnownTypeCode.Boolean) && targetUType.GetStackType().IsIntegerType()) { // convert from boolean to integer (or enum) return new ConditionalExpression( this.Expression, @@ -318,7 +317,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } - if (targetType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr + if (targetUType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr if (type.IsKnownType(KnownTypeCode.Int32)) { // normal casts work for int (both in checked and unchecked context) } else if (checkForOverflow) { @@ -336,7 +335,7 @@ namespace ICSharpCode.Decompiler.CSharp .ConvertTo(targetType, expressionBuilder, checkForOverflow); } } - } else if (targetType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr + } else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer) { // normal casts work for uint and pointers (both in checked and unchecked context) } else if (checkForOverflow) { @@ -359,14 +358,14 @@ namespace ICSharpCode.Decompiler.CSharp // -> convert via underlying type return this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); - } else if (targetType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) { + } else if (targetUType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) { // pointer to enum: C# doesn't allow such casts // -> convert via underlying type - return this.ConvertTo(targetType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) + return this.ConvertTo(targetUType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow); } if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char) - || targetType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) { + || targetUType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) { // char <-> pointer: C# doesn't allow such casts // -> convert via ushort return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt16), expressionBuilder, checkForOverflow) @@ -418,6 +417,17 @@ namespace ICSharpCode.Decompiler.CSharp .WithoutILInstruction() .WithRR(new ByReferenceResolveResult(elementRR, ReferenceKind.Ref)); } + if (this.ResolveResult.IsCompileTimeConstant && this.ResolveResult.ConstantValue != null + && NullableType.IsNullable(targetType) && !utype.Equals(targetUType)) + { + // Casts like `(uint?)-1` are only valid in an explicitly unchecked context, but we + // don't have logic to ensure such a context (usually we emit into an implicitly unchecked context). + // This only applies with constants as input (int->uint? is fine in implicitly unchecked context). + // We use an intermediate cast to the nullable's underlying type, which results + // in a constant conversion, so the final output will be something like `(uint?)uint.MaxValue` + return ConvertTo(targetUType, expressionBuilder, checkForOverflow, allowImplicitConversion: false) + .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); + } var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult); if (rr.IsCompileTimeConstant && !rr.IsError) { return expressionBuilder.ConvertConstantValue(rr, allowImplicitConversion)