Skip to content

Commit

Permalink
Lua, PHP: avoid conversion of "unsigned" 64-bit int literals to float
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
generalmimon committed Mar 12, 2022
1 parent 5cadb04 commit fd7f308
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class JavaTranslator(provider: TypeProvider, importList: ImportList) extends Bas
// the sign (e. g. you pass the value unmodified to something else, or you use only bit operations
// or Long's "unsigned" methods).
//
// Of course, if `n > Long.MaxValue * 2 + 1` we'll still get out of range error
// Of course, if `n > Utils.MAX_UINT64` we'll still get out of range error
// TODO: Convert real big numbers to BigInteger
val literal = if (n > Long.MaxValue) {
val literal = if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
"0x" + n.toString(16)
} else {
n.toString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.languages.LuaCompiler

class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
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>:
//
// - "A numeric constant (...), if its value fits in an integer or it is a hexadecimal
// constant, it denotes an integer; otherwise (that is, a decimal integer numeral that
// overflows), it denotes a float."
// - "Hexadecimal numerals with neither a radix point nor an exponent always denote an
// integer value; if the value overflows, it wraps around to fit into a valid integer."
//
// This is written only in the Lua 5.4 manual, but applies to Lua 5.3 too (experimentally
// verified).
"0x" + n.toString(16)
} else {
super.doIntLiteral(n)
}
}

override val asciiCharQuoteMap: Map[Char, String] = Map(
'\t' -> "\\t",
'\n' -> "\\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import io.kaitai.struct.languages.PHPCompiler
import io.kaitai.struct.{RuntimeConfig, Utils}

class PHPTranslator(provider: TypeProvider, config: RuntimeConfig) extends BaseTranslator(provider) {
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
// lose precision)
} else {
n
})
}

override def doByteArrayLiteral(arr: Seq[Byte]): String =
"\"" + Utils.hexEscapeByteArray(arr) + "\""
override def doByteArrayNonLiteral(elts: Seq[Ast.expr]): String =
Expand Down

0 comments on commit fd7f308

Please sign in to comment.