From fd7f308c67e8eacee98a647bbbbfb2792505bc64 Mon Sep 17 00:00:00 2001 From: Petr Pucil Date: Sat, 12 Mar 2022 14:41:30 +0100 Subject: [PATCH] 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. --- .../struct/translators/JavaTranslator.scala | 4 ++-- .../struct/translators/LuaTranslator.scala | 18 ++++++++++++++++++ .../struct/translators/PHPTranslator.scala | 9 +++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala index d913558bc..1124ca1d9 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/JavaTranslator.scala @@ -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 diff --git a/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala index 3525398d4..b83d901dd 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/LuaTranslator.scala @@ -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 : + // + // - "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", diff --git a/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala index 7b474a7a5..4e237632b 100644 --- a/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala +++ b/shared/src/main/scala/io/kaitai/struct/translators/PHPTranslator.scala @@ -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 =