Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix]: checked(-x) if x is int or long #1281

Merged
merged 3 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
}
Loading