Skip to content

Commit fd7f308

Browse files
committed
Lua, PHP: avoid conversion of "unsigned" 64-bit int literals to float
PHP and Lua only have *signed* 64-bit integers, so a decimal literal greater than the maximum signed 64-bit integer (`2**63 - 1`) will be interpreted as float. This is usually unacceptable, because it loses precision. If we output integers `2**63 <= x <= 2**64 - 1` so that they are interpreted as signed integers, we lose the information that they were unsigned, but at least we don't lose any significant bits. This approach is consistent with how integer reading methods in runtime libraries work - read unsigned 64-bit ints as signed values if the language doesn't support the unsigned.
1 parent 5cadb04 commit fd7f308

File tree

3 files changed

+29
-2
lines changed

3 files changed

+29
-2
lines changed

shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList) extends Bas
1919
// the sign (e. g. you pass the value unmodified to something else, or you use only bit operations
2020
// or Long's "unsigned" methods).
2121
//
22-
// Of course, if `n > Long.MaxValue * 2 + 1` we'll still get out of range error
22+
// Of course, if `n > Utils.MAX_UINT64` we'll still get out of range error
2323
// TODO: Convert real big numbers to BigInteger
24-
val literal = if (n > Long.MaxValue) {
24+
val literal = if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
2525
"0x" + n.toString(16)
2626
} else {
2727
n.toString

shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ import io.kaitai.struct.exprlang.Ast
88
import io.kaitai.struct.languages.LuaCompiler
99

1010
class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
11+
override def doIntLiteral(n: BigInt): String = {
12+
if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
13+
// See <https://www.lua.org/manual/5.4/manual.html#3.1>:
14+
//
15+
// - "A numeric constant (...), if its value fits in an integer or it is a hexadecimal
16+
// constant, it denotes an integer; otherwise (that is, a decimal integer numeral that
17+
// overflows), it denotes a float."
18+
// - "Hexadecimal numerals with neither a radix point nor an exponent always denote an
19+
// integer value; if the value overflows, it wraps around to fit into a valid integer."
20+
//
21+
// This is written only in the Lua 5.4 manual, but applies to Lua 5.3 too (experimentally
22+
// verified).
23+
"0x" + n.toString(16)
24+
} else {
25+
super.doIntLiteral(n)
26+
}
27+
}
28+
1129
override val asciiCharQuoteMap: Map[Char, String] = Map(
1230
'\t' -> "\\t",
1331
'\n' -> "\\n",

shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import io.kaitai.struct.languages.PHPCompiler
88
import io.kaitai.struct.{RuntimeConfig, Utils}
99

1010
class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseTranslator(provider) {
11+
override def doIntLiteral(n: BigInt): String = {
12+
super.doIntLiteral(if (n >= Long.MinValue && n <= Utils.MAX_UINT64) {
13+
n.toLong // output unsigned 64-bit integers as signed (otherwise we would get a float and
14+
// lose precision)
15+
} else {
16+
n
17+
})
18+
}
19+
1120
override def doByteArrayLiteral(arr: Seq[Byte]): String =
1221
"\"" + Utils.hexEscapeByteArray(arr) + "\""
1322
override def doByteArrayNonLiteral(elts: Seq[Ast.expr]): String =

0 commit comments

Comments
 (0)