Skip to content

Commit

Permalink
Fix -2**63 (min sint64 value), add test cases of utmost ints
Browse files Browse the repository at this point in the history
  • Loading branch information
generalmimon committed Mar 12, 2022
1 parent fd7f308 commit e33828a
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,78 @@ class TranslatorSpec extends FunSuite {
JavaCompiler -> "100000000000L"
))

// 0x7fff_ffff
everybody("2147483647", "2147483647")
// 0x8000_0000
everybodyExcept("2147483648", "2147483648", Map[LanguageCompilerStatic, String](
CppCompiler -> "2147483648UL",
GoCompiler -> "uint32(2147483648)",
JavaCompiler -> "2147483648L",
))
// 0xffff_ffff
everybodyExcept("4294967295", "4294967295", Map[LanguageCompilerStatic, String](
CppCompiler -> "4294967295UL",
GoCompiler -> "uint32(4294967295)",
JavaCompiler -> "4294967295L",
))
// 0x1_0000_0000
everybodyExcept("4294967296", "4294967296", Map[LanguageCompilerStatic, String](
CppCompiler -> "4294967296LL",
GoCompiler -> "int64(4294967296)",
JavaCompiler -> "4294967296L",
))
// -0x7fff_ffff
everybody("-2147483647", "-2147483647")
// -0x8000_0000
everybodyExcept("-2147483648", "-2147483648", Map[LanguageCompilerStatic, String](
CppCompiler -> "(-2147483647 - 1)",
LuaCompiler -> "(-2147483647 - 1)",
PHPCompiler -> "(-2147483647 - 1)",
))
// -0x8000_0001
everybodyExcept("-2147483649", "-2147483649", Map[LanguageCompilerStatic, String](
CppCompiler -> "-2147483649LL",
GoCompiler -> "int64(-2147483649)",
JavaCompiler -> "-2147483649L",
))

// 0x7fff_ffff_ffff_ffff
everybodyExcept("9223372036854775807", "9223372036854775807", Map[LanguageCompilerStatic, String](
CppCompiler -> "9223372036854775807LL",
GoCompiler -> "int64(9223372036854775807)",
JavaCompiler -> "9223372036854775807L",
))
// 0x8000_0000_0000_0000
everybodyExcept("9223372036854775808", "9223372036854775808", Map[LanguageCompilerStatic, String](
CppCompiler -> "9223372036854775808ULL",
GoCompiler -> "uint64(9223372036854775808)",
JavaCompiler -> "0x8000000000000000L",
LuaCompiler -> "0x8000000000000000",
PHPCompiler -> "(-9223372036854775807 - 1)",
))
// 0xffff_ffff_ffff_ffff
everybodyExcept("18446744073709551615", "18446744073709551615", Map[LanguageCompilerStatic, String](
CppCompiler -> "18446744073709551615ULL",
GoCompiler -> "uint64(18446744073709551615)",
JavaCompiler -> "0xffffffffffffffffL",
LuaCompiler -> "0xffffffffffffffff",
PHPCompiler -> "-1",
))
// -0x7fff_ffff_ffff_ffff
everybodyExcept("-9223372036854775807", "-9223372036854775807", Map[LanguageCompilerStatic, String](
CppCompiler -> "-9223372036854775807LL",
GoCompiler -> "int64(-9223372036854775807)",
JavaCompiler -> "-9223372036854775807L",
))
// -0x8000_0000_0000_0000
everybodyExcept("-9223372036854775808", "-9223372036854775808", Map[LanguageCompilerStatic, String](
CppCompiler -> "(-9223372036854775807LL - 1)",
GoCompiler -> "int64(-9223372036854775808)",
JavaCompiler -> "-9223372036854775808L",
LuaCompiler -> "(-9223372036854775807 - 1)",
PHPCompiler -> "(-9223372036854775807 - 1)",
))

// Float literals
everybody("1.0", "1.0", CalcFloatType)
everybody("123.456", "123.456", CalcFloatType)
Expand Down
5 changes: 5 additions & 0 deletions shared/src/main/scala/io/kaitai/struct/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import java.nio.charset.Charset
import scala.collection.mutable.ListBuffer

object Utils {
/**
* BigInt-typed max value of unsigned 32-bit integer.
*/
final val MAX_UINT32 = BigInt("4294967295")

/**
* BigInt-typed max value of unsigned 64-bit integer.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ abstract class BaseTranslator(val provider: TypeProvider)
doLocalName(name.name)
}
case Ast.expr.UnaryOp(op: Ast.unaryop, inner: Ast.expr) =>
unaryOp(op) + (inner match {
case Ast.expr.IntNum(_) | Ast.expr.FloatNum(_) =>
translate(inner)
val opStr = unaryOp(op)
(op, inner) match {
/** required by trait [[MinSignedIntegers]] - see also test cases in [[TranslatorSpec]] */
case (Ast.unaryop.Minus, Ast.expr.IntNum(n)) => translate(Ast.expr.IntNum(-n))
case (_, Ast.expr.IntNum(_) | Ast.expr.FloatNum(_)) =>
s"$opStr${translate(inner)}"
case _ =>
s"(${translate(inner)})"
})
s"$opStr(${translate(inner)})"
}
case Ast.expr.Compare(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) =>
(detectType(left), detectType(right)) match {
case (_: NumericType, _: NumericType) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,57 +12,52 @@ import io.kaitai.struct.languages.CppCompiler
import io.kaitai.struct.languages.components.CppImportList
import io.kaitai.struct.{RuntimeConfig, Utils}

class CppTranslator(provider: TypeProvider, importListSrc: CppImportList, importListHdr: CppImportList, config: RuntimeConfig) extends BaseTranslator(provider) {
class CppTranslator(provider: TypeProvider, importListSrc: CppImportList, importListHdr: CppImportList, config: RuntimeConfig)
extends BaseTranslator(provider)
with MinSignedIntegers {
val CHARSET_UTF8 = Charset.forName("UTF-8")

/**
* Handles integer literals for C++ by appending relevant suffix to
* decimal notation.
* Handles integer literals for C++ by appending relevant suffix to decimal notation.
*
* Note that suffixes essentially mean "long", "unsigned long",
* and "unsigned long long", which are not really guaranteed to match
* `int32_t`, `uint32_t` and `uint64_t`, but it would work for majority
* of current compilers.
* Note that suffixes essentially mean "long", "unsigned long", and "unsigned long long", which
* are not really guaranteed to match `int32_t`, `uint32_t` and `uint64_t`, but it would work for
* the majority of current compilers.
*
* For reference, ranges of integers that are used in this conversion are:
*
* * int32_t (no suffix): -2147483648..2147483647
* * uint32_t (UL): 0..4294967295
* * int64_t (LL): -9223372036854775808..9223372036854775807
* * uint64_t (ULL): 0..18446744073709551615
* - int32_t (no suffix): -2147483648..2147483647
* - uint32_t (UL): 0..4294967295
* - int64_t (LL): -9223372036854775808..9223372036854775807
* - uint64_t (ULL): 0..18446744073709551615
*
* Merging all these ranges, we get the following decision tree:
* Beyond these boundaries, C++ is unlikely to be able to represent these anyway, so we just drop
* the suffix and hope for the miracle.
*
* * -9223372036854775808..-2147483649 => LL
* * -2147483648..2147483647 => no suffix
* * 2147483648..4294967295 => UL
* * 4294967296..9223372036854775807 => LL
* * 9223372036854775808..18446744073709551615 => ULL
*
* Beyond these boundaries, C++ is unlikely to be able to represent
* these anyway, so we just drop the suffix and hope for the miracle.
* The minimum signed 32-bit and 64-bit integers (Int.MinValue and Long.MinValue) are
* intentionally omitted, since they're handled by [[MinSignedIntegers]].
*
* @param n integer to render
* @return rendered integer literal in C++ syntax as string
*/
override def doIntLiteral(n: BigInt): String = {
val suffix = if (n < -9223372036854775808L) {
"" // too low, no suffix would help anyway
} else if (n <= -2147483649L) {
"LL" // -9223372036854775808..-2147483649
} else if (n <= 2147483647L) {
"" // -2147483648..2147483647
} else if (n <= 4294967295L) {
"UL" // 2147483648..4294967295
} else if (n <= 9223372036854775807L) {
"LL" // 4294967296..9223372036854775807
} else if (n <= Utils.MAX_UINT64) {
"ULL" // 9223372036854775808..18446744073709551615
} else {
"" // too high, no suffix would help anyway
}
val suffixOpt: Option[String] =
if (n >= Int.MinValue + 1 && n <= Int.MaxValue) {
Some("") // -2147483647..2147483647
} else if (n > Int.MaxValue && n <= Utils.MAX_UINT32) {
Some("UL") // 2147483648..4294967295
} else if ((n >= Long.MinValue + 1 && n < Int.MinValue) || (n > Utils.MAX_UINT32 && n <= Long.MaxValue)) {
Some("LL") // -9223372036854775807..-2147483649 | 4294967296..9223372036854775807
} else if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
Some("ULL") // 9223372036854775808..18446744073709551615
} else {
None
}

s"$n$suffix"
suffixOpt match {
case Some(suffix) => s"$n$suffix"
case None => super.doIntLiteral(n) // delegate to parent implementations
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,18 @@ class GoTranslator(out: StringLanguageOutputWriter, provider: TypeProvider, impo
} else {
trLocalName(name.name)
}
case Ast.expr.UnaryOp(op, operand) =>
ResultString(unaryOp(op) + (operand match {
case Ast.expr.IntNum(_) | Ast.expr.FloatNum(_) =>
translate(operand)
case Ast.expr.UnaryOp(op: Ast.unaryop, inner: Ast.expr) =>
val opStr = unaryOp(op)
ResultString((op, inner) match {
/** [[doIntLiteral]] has to know when a negative number is being translated - if it
* doesn't, the result is things like `-uint32(2147483648)` that will not compile in Go
* (the error is "constant -2147483648 overflows uint32") */
case (Ast.unaryop.Minus, Ast.expr.IntNum(n)) => translate(Ast.expr.IntNum(-n))
case (_, Ast.expr.IntNum(_) | Ast.expr.FloatNum(_)) =>
s"$opStr${translate(inner)}"
case _ =>
s"(${translate(operand)})"
}))
s"$opStr(${translate(inner)})"
})
case Ast.expr.Compare(left, op, right) =>
(detectType(left), detectType(right)) match {
case (_: NumericType, _: NumericType) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format.Identifier
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.languages.LuaCompiler
import io.kaitai.struct.Utils

class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider)
with MinSignedIntegers {
override def doIntLiteral(n: BigInt): String = {
if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
// See <https://www.lua.org/manual/5.4/manual.html#3.1>:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.kaitai.struct.translators

import io.kaitai.struct.exprlang.Ast

/**
* Special handling of the minimum values of 32-bit (`-2**31 = -0x8000_0000`) and 64-bit (`-2**63 =
* -0x8000_0000_0000_0000`) signed integer types. Almost all languages have a unary minus (`-`) as
* a normal unary operator; it takes its operand (usually a positive integer) and negates it.
* However, there is no positive counterpart to
*
* 1. `-2**31` in signed 32-bit integers (the maximum `sint32` is `2**31 - 1`);
* 2. `-2**64` in signed 64-bit integers (the maximum `sint64` is `2**63 - 1`).
*
* `2**31` and `2**63` generally require either an *un*signed {32,64}-bit integer, or a signed
* integer of a larger size (e.g. `sint64` for `2**31`). But these types may not be available in
* all languages/platforms and it doesn't make sense to rely on them just to be able to get the
* minimum negative value.
*/
trait MinSignedIntegers
extends AbstractTranslator
with CommonLiterals {
override def doIntLiteral(n: BigInt): String = {
if (n == Long.MinValue || n == Int.MinValue) {
translate(
Ast.expr.BinOp(
Ast.expr.IntNum(n + 1),
Ast.operator.Sub,
Ast.expr.IntNum(1)
)
)
} else {
super.doIntLiteral(n)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import io.kaitai.struct.format.Identifier
import io.kaitai.struct.languages.PHPCompiler
import io.kaitai.struct.{RuntimeConfig, Utils}

class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseTranslator(provider) {
class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseTranslator(provider)
with MinSignedIntegers {
override def doIntLiteral(n: BigInt): String = {
super.doIntLiteral(if (n >= Long.MinValue && n <= Utils.MAX_UINT64) {
n.toLong // output unsigned 64-bit integers as signed (otherwise we would get a float and
Expand Down

0 comments on commit e33828a

Please sign in to comment.