Skip to content

Commit

Permalink
[Fix]: checked(-x) if x is int or long (#1281)
Browse files Browse the repository at this point in the history
* fix: check(-x) if x is int or long

* fix: add more tests

---------

Co-authored-by: Jimmy <[email protected]>
  • Loading branch information
nan01ab and Jim8y authored Jan 21, 2025
1 parent 9fac5dc commit 0431391
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,11 @@ private void EnsureIntegerInRange(ITypeSymbol type)
{
if (type.Name == "BigInteger") return;
while (type.NullableAnnotation == NullableAnnotation.Annotated)
{
// Supporting nullable integer like `byte?`
type = ((INamedTypeSymbol)type).TypeArguments.First();
}

var (minValue, maxValue, mask) = type.Name switch
{
"SByte" => ((BigInteger)sbyte.MinValue, (BigInteger)sbyte.MaxValue, (BigInteger)0xff),
Expand All @@ -282,6 +285,7 @@ private void EnsureIntegerInRange(ITypeSymbol type)
//"Boolean" => (0, 1, 0x01),
_ => throw new CompilationException(DiagnosticId.SyntaxNotSupported, $"Unsupported type: {type}")
};

JumpTarget checkUpperBoundTarget = new(), adjustTarget = new(), endTarget = new();
AddInstruction(OpCode.DUP);
Push(minValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Neo.VM;
using System;
using System.Runtime.InteropServices;
using System.Linq;

namespace Neo.Compiler;

Expand Down Expand Up @@ -46,7 +47,7 @@ private void ConvertPrefixUnaryExpression(SemanticModel model, PrefixUnaryExpres
break;
case "-":
ConvertExpression(model, expression.Operand);
AddInstruction(OpCode.NEGATE);
EmitNegativeInteger(model.GetTypeInfo(expression.Operand).Type);
break;
case "~":
ConvertExpression(model, expression.Operand);
Expand Down Expand Up @@ -274,4 +275,38 @@ private void EmitIncrementOrDecrement(SyntaxToken operatorToken, ITypeSymbol? ty
});
if (typeSymbol != null) EnsureIntegerInRange(typeSymbol);
}

private void EmitNegativeInteger(ITypeSymbol? typeSymbol)
{
if (typeSymbol is null || (typeSymbol.Name != "Int32" && typeSymbol.Name != "Int64"))
{
// -sbyte, -byte, -short, -ushort, -char -> int, -int, -uint -> long
AddInstruction(OpCode.NEGATE); // Emit NEGATE for other integer types
return;
}

while (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated)
{
// Supporting nullable integer like `byte?`
typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments.First();
}

var minValue = typeSymbol.Name == "Int64" ? long.MinValue : int.MinValue; // int32 or int64

JumpTarget negateTarget = new(), endTarget = new();
AddInstruction(OpCode.DUP);
Push(minValue);
Jump(OpCode.JMPNE_L, negateTarget);

if (_checkedStack.Peek()) // if `checked` is true, throw exception
{
AddInstruction(OpCode.THROW);
}
else // -int.MinValue == -int.MinValue, -long.MinValue == -long.MinValue, i.e. same value
{
Jump(endTarget);
}
negateTarget.Instruction = AddInstruction(OpCode.NEGATE);
endTarget.Instruction = AddInstruction(OpCode.NOP);
}
}
15 changes: 15 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_Overflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,20 @@ public class Contract_Overflow : SmartContract.Framework.SmartContract
public static int MulInt(int a, int b) => a * b;
public static uint AddUInt(uint a, uint b) => a + b;
public static uint MulUInt(uint a, uint b) => a * b;

public static int NegateIntChecked(int a) => checked(-a);
public static int NegateInt(int a) => -a;

public static long NegateLongChecked(long a) => checked(-a);
public static long NegateLong(long a) => -a;

public static int NegateShortChecked(short a) => checked(-a);
public static int NegateShort(short a) => -a;

public static int NegateAddInt(int a, int b) => -(a + b);
public static int NegateAddIntChecked(int a, int b) => checked(-(a + b));

public static long NegateAddLong(long a, long b) => -(a + b);
public static long NegateAddLongChecked(long a, long b) => checked(-(a + b));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ public abstract class Contract_Overflow(Neo.SmartContract.Testing.SmartContractI
{
#region Compiled data

public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_Overflow"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""addInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":0,""safe"":false},{""name"":""mulInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":53,""safe"":false},{""name"":""addUInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":106,""safe"":false},{""name"":""mulUInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":141,""safe"":false}],""events"":[]},""permissions"":[],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");
public static Neo.SmartContract.Manifest.ContractManifest Manifest => Neo.SmartContract.Manifest.ContractManifest.Parse(@"{""name"":""Contract_Overflow"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""addInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":0,""safe"":false},{""name"":""mulInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":53,""safe"":false},{""name"":""addUInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":106,""safe"":false},{""name"":""mulUInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":141,""safe"":false},{""name"":""negateIntChecked"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":176,""safe"":false},{""name"":""negateInt"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":191,""safe"":false},{""name"":""negateLongChecked"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":206,""safe"":false},{""name"":""negateLong"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":225,""safe"":false},{""name"":""negateShortChecked"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":244,""safe"":false},{""name"":""negateShort"",""parameters"":[{""name"":""a"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":250,""safe"":false},{""name"":""negateAddInt"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":256,""safe"":false},{""name"":""negateAddIntChecked"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":319,""safe"":false},{""name"":""negateAddLong"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":354,""safe"":false},{""name"":""negateAddLongChecked"",""parameters"":[{""name"":""a"",""type"":""Integer""},{""name"":""b"",""type"":""Integer""}],""returntype"":""Integer"",""offset"":449,""safe"":false}],""events"":[]},""permissions"":[],""trusts"":[],""extra"":{""nef"":{""optimization"":""All""}}}");

/// <summary>
/// Optimization: "All"
/// </summary>
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALBXAAJ4eZ5KAgAAAIAuBCIKSgL///9/Mh4D/////wAAAACRSgL///9/MgwDAAAAAAEAAACfQFcAAnh5oEoCAAAAgC4EIgpKAv///38yHgP/////AAAAAJFKAv///38yDAMAAAAAAQAAAJ9AVwACeHmeShAuBCIOSgP/////AAAAADIMA/////8AAAAAkUBXAAJ4eaBKEC4EIg5KA/////8AAAAAMgwD/////wAAAACRQFd1rUY="));
public static Neo.SmartContract.NefFile Nef => Neo.IO.Helper.AsSerializable<Neo.SmartContract.NefFile>(Convert.FromBase64String(@"TkVGM1Rlc3RpbmdFbmdpbmUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP3wAVcAAnh5nkoCAAAAgC4EIgpKAv///38yHgP/////AAAAAJFKAv///38yDAMAAAAAAQAAAJ9AVwACeHmgSgIAAACALgQiCkoC////fzIeA/////8AAAAAkUoC////fzIMAwAAAAABAAAAn0BXAAJ4eZ5KEC4EIg5KA/////8AAAAAMgwD/////wAAAACRQFcAAnh5oEoQLgQiDkoD/////wAAAAAyDAP/////AAAAAJFAVwABeEoCAAAAgCoDOptAVwABeEoCAAAAgCoDQJtAVwABeEoDAAAAAAAAAIAqAzqbQFcAAXhKAwAAAAAAAACAKgNAm0BXAAF4m0BXAAF4m0BXAAJ4eZ5KAgAAAIAuBCIKSgL///9/Mh4D/////wAAAACRSgL///9/MgwDAAAAAAEAAACfSgIAAACAKgNAm0BXAAJ4eZ5KAgAAAIAuAzpKAv///38yAzpKAgAAAIAqAzqbQFcAAnh5nkoDAAAAAAAAAIAuBCIOSgP/////////fzIyBP//////////AAAAAAAAAACRSgP/////////fzIUBAAAAAAAAAAAAQAAAAAAAACfSgMAAAAAAAAAgCoDQJtAVwACeHmeSgMAAAAAAAAAgC4DOkoD/////////38yAzpKAwAAAAAAAACAKgM6m0D1Twbi"));

#endregion

Expand Down Expand Up @@ -123,5 +123,219 @@ public abstract class Contract_Overflow(Neo.SmartContract.Testing.SmartContractI
[DisplayName("mulUInt")]
public abstract BigInteger? MulUInt(BigInteger? a, BigInteger? b);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwACeHmeSgIAAACALgQiCkoC////fzIeA/////8AAAAAkUoC////fzIMAwAAAAABAAAAn0oCAAAAgCoDQJtA
/// INITSLOT 0002 [64 datoshi]
/// LDARG0 [2 datoshi]
/// LDARG1 [2 datoshi]
/// ADD [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPGE 04 [2 datoshi]
/// JMP 0A [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 FFFFFF7F [1 datoshi]
/// JMPLE 1E [2 datoshi]
/// PUSHINT64 FFFFFFFF00000000 [1 datoshi]
/// AND [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 FFFFFF7F [1 datoshi]
/// JMPLE 0C [2 datoshi]
/// PUSHINT64 0000000001000000 [1 datoshi]
/// SUB [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// RET [0 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateAddInt")]
public abstract BigInteger? NegateAddInt(BigInteger? a, BigInteger? b);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwACeHmeSgIAAACALgM6SgL///9/MgM6SgIAAACAKgM6m0A=
/// INITSLOT 0002 [64 datoshi]
/// LDARG0 [2 datoshi]
/// LDARG1 [2 datoshi]
/// ADD [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPGE 03 [2 datoshi]
/// THROW [512 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 FFFFFF7F [1 datoshi]
/// JMPLE 03 [2 datoshi]
/// THROW [512 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// THROW [512 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateAddIntChecked")]
public abstract BigInteger? NegateAddIntChecked(BigInteger? a, BigInteger? b);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwACeHmeSgMAAAAAAAAAgC4EIg5KA/////////9/MjIE//////////8AAAAAAAAAAJFKA/////////9/MhQEAAAAAAAAAAABAAAAAAAAAJ9KAwAAAAAAAACAKgNAm0A=
/// INITSLOT 0002 [64 datoshi]
/// LDARG0 [2 datoshi]
/// LDARG1 [2 datoshi]
/// ADD [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPGE 04 [2 datoshi]
/// JMP 0E [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 FFFFFFFFFFFFFF7F [1 datoshi]
/// JMPLE 32 [2 datoshi]
/// PUSHINT128 FFFFFFFFFFFFFFFF0000000000000000 [4 datoshi]
/// AND [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 FFFFFFFFFFFFFF7F [1 datoshi]
/// JMPLE 14 [2 datoshi]
/// PUSHINT128 00000000000000000100000000000000 [4 datoshi]
/// SUB [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// RET [0 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateAddLong")]
public abstract BigInteger? NegateAddLong(BigInteger? a, BigInteger? b);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwACeHmeSgMAAAAAAAAAgC4DOkoD/////////38yAzpKAwAAAAAAAACAKgM6m0A=
/// INITSLOT 0002 [64 datoshi]
/// LDARG0 [2 datoshi]
/// LDARG1 [2 datoshi]
/// ADD [8 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPGE 03 [2 datoshi]
/// THROW [512 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 FFFFFFFFFFFFFF7F [1 datoshi]
/// JMPLE 03 [2 datoshi]
/// THROW [512 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// THROW [512 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateAddLongChecked")]
public abstract BigInteger? NegateAddLongChecked(BigInteger? a, BigInteger? b);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeEoCAAAAgCoDQJtA
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// RET [0 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateInt")]
public abstract BigInteger? NegateInt(BigInteger? a);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeEoCAAAAgCoDOptA
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT32 00000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// THROW [512 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateIntChecked")]
public abstract BigInteger? NegateIntChecked(BigInteger? a);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeEoDAAAAAAAAAIAqA0CbQA==
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// RET [0 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateLong")]
public abstract BigInteger? NegateLong(BigInteger? a);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeEoDAAAAAAAAAIAqAzqbQA==
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// DUP [2 datoshi]
/// PUSHINT64 0000000000000080 [1 datoshi]
/// JMPNE 03 [2 datoshi]
/// THROW [512 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateLongChecked")]
public abstract BigInteger? NegateLongChecked(BigInteger? a);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeJtA
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateShort")]
public abstract BigInteger? NegateShort(BigInteger? a);

/// <summary>
/// Unsafe method
/// </summary>
/// <remarks>
/// Script: VwABeJtA
/// INITSLOT 0001 [64 datoshi]
/// LDARG0 [2 datoshi]
/// NEGATE [4 datoshi]
/// RET [0 datoshi]
/// </remarks>
[DisplayName("negateShortChecked")]
public abstract BigInteger? NegateShortChecked(BigInteger? a);

#endregion
}
44 changes: 43 additions & 1 deletion tests/Neo.Compiler.CSharp.UnitTests/UnitTest_Overflow.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.SmartContract.Testing;
using Neo.SmartContract.Testing.Exceptions;
using System.Numerics;

namespace Neo.Compiler.CSharp.UnitTests
{
Expand Down Expand Up @@ -49,5 +48,48 @@ public void Test_MulUInt()
Assert.AreEqual(unchecked(uint.MinValue * (-uint.MaxValue)), Contract.MulUInt(uint.MinValue, -uint.MaxValue));
Assert.AreEqual(unchecked((-uint.MinValue) * uint.MaxValue), Contract.MulUInt(unchecked(-uint.MinValue), uint.MaxValue));
}

[TestMethod]
public void Test_NegateChecked()
{
Assert.AreEqual(-2147483647, Contract.NegateIntChecked(int.MaxValue));
Assert.AreEqual(-2147483647, Contract.NegateInt(int.MaxValue));

// VMUnhandledException -int.MinValue
Assert.ThrowsException<TestException>(() => Contract.NegateIntChecked(int.MinValue));

Assert.AreEqual(-long.MaxValue, Contract.NegateLongChecked(long.MaxValue));
Assert.AreEqual(-long.MaxValue, Contract.NegateLong(long.MaxValue));

// VMUnhandledException -long.MinValue
Assert.ThrowsException<TestException>(() => Contract.NegateLongChecked(long.MinValue));

// -short -> int
Assert.AreEqual(-32767, Contract.NegateShortChecked(32767));
Assert.AreEqual(32768, Contract.NegateShort(short.MinValue));

// unchecked(-int.MinValue) == int.MinValue
Assert.AreEqual(int.MinValue, unchecked(-int.MinValue));

// unchecked(-long.MinValue) == long.MinValue
Assert.AreEqual(long.MinValue, unchecked(-long.MinValue));

// it is different for short.MinValue, because `-short` is an int
Assert.AreEqual(32768, unchecked(-short.MinValue));


// add and negate
Assert.AreEqual(-2147483648, Contract.NegateAddInt(int.MaxValue, 1));
Assert.ThrowsException<TestException>(() => Contract.NegateAddIntChecked(int.MaxValue, 1));

Assert.AreEqual(-9223372036854775808, Contract.NegateAddLong(long.MaxValue, 1));
Assert.ThrowsException<TestException>(() => Contract.NegateAddLongChecked(long.MaxValue, 1));

Assert.AreEqual(-2147483648, Contract.NegateAddInt(-2147483647, -1));
Assert.ThrowsException<TestException>(() => Contract.NegateAddIntChecked(-2147483647, -1));

Assert.AreEqual(-9223372036854775808, Contract.NegateAddLong(-9223372036854775807, -1));
Assert.ThrowsException<TestException>(() => Contract.NegateAddLongChecked(-9223372036854775807, -1));
}
}
}

0 comments on commit 0431391

Please sign in to comment.