[Tarantool-patches] [PATCH v1 1/1] misc: introduce misc.tonumber64()

imeevma at tarantool.org imeevma at tarantool.org
Mon Nov 16 12:34:30 MSK 2020


This patch introduces function misc.tonumber64() that accepts number,
cdata number or string and returns 64-bit integer value.

Part of tarantool/tarantool#4738
---
https://github.com/tarantool/tarantool/issues/4738
https://github.com/tarantool/luajit/tree/imeevma/gh-4738-introduce-misc-tonumber64

 src/lib_misc.c                                | 173 ++++++++++++++++++
 ...-4738-incorrect-result-tonumber64.test.lua |  38 ++++
 2 files changed, 211 insertions(+)
 create mode 100755 test/gh-4738-incorrect-result-tonumber64.test.lua

diff --git a/src/lib_misc.c b/src/lib_misc.c
index 6f7b9a9..4228836 100644
--- a/src/lib_misc.c
+++ b/src/lib_misc.c
@@ -10,11 +10,18 @@
 
 #include "lua.h"
 #include "lmisclib.h"
+#include "lauxlib.h"
+#include <errno.h>
+#include <ctype.h>
 
 #include "lj_obj.h"
 #include "lj_str.h"
 #include "lj_tab.h"
 #include "lj_lib.h"
+#include "lj_err.h"
+#include "lj_ctype.h"
+#include "lj_cdata.h"
+#include "lj_state.h"
 
 /* ------------------------------------------------------------------------ */
 
@@ -63,6 +70,172 @@ LJLIB_CF(misc_getmetrics)
   return 1;
 }
 
+LJLIB_CF(misc_tonumber64)
+{
+  TValue *o = L->base;
+  if (o >= L->top)
+    lj_err_arg(L, 1, LJ_ERR_NOVAL);
+  int base = luaL_optint(L, 2, -1);
+  luaL_argcheck(L, (2 <= base && base <= 36) || base == -1, 2,
+                "base out of range");
+  uint32_t ctypeid = 0;
+  GCcdata *cd;
+  CTSize size;
+  CTState *cts;
+  double val_d;
+  switch (lua_type(L, 1)) {
+  case LUA_TNUMBER:
+    base = (base == -1 ? 10 : base);
+    if (base != 10)
+      return luaL_argerror(L, 1, "string expected");
+    val_d = numV(L->base);
+    if (val_d < (double)INT64_MIN || val_d >= (double)UINT64_MAX)
+      return luaL_argerror(L, 1, "cannot convert to 64-bit integer");
+    cts = ctype_cts(L);
+    if (val_d < 0.0)
+      ctypeid = CTID_INT64;
+    else
+      ctypeid = CTID_UINT64;
+    lj_ctype_info(cts, ctypeid, &size);
+    assert(size != CTSIZE_INVALID);
+    cd = lj_cdata_new(cts, ctypeid, size);
+    o = L->top;
+    setcdataV(L, o, cd);
+    incr_top(L);
+    if (val_d < 0.0)
+      *(int64_t *) cdataptr(cd) = (int64_t)val_d;
+    else
+      *(uint64_t *) cdataptr(cd) = (uint64_t)val_d;
+    return 1;
+  case LUA_TSTRING: {
+    GCstr *s = strV(o);
+    const char *arg = strdata(s);
+    size_t argl = s->len;
+    while (argl > 0 && isspace(arg[argl - 1]))
+      argl--;
+    while (isspace(*arg)) {
+      arg++;
+      argl--;
+    }
+
+    /*
+     * Check if we're parsing custom format:
+     * 1) '0x' or '0X' trim in case of base == 16 or base == -1
+     * 2) '0b' or '0B' trim in case of base == 2  or base == -1
+     * 3) '-' for negative numbers
+     * 4) LL, ULL, LLU - trim, but only for base == 2 or
+     *    base == 16 or base == -1. For consistency do not bother
+     *    with any non-common bases, since user may have specified
+     *    base >= 22, in which case 'L' will be a digit.
+     */
+    char negative = 0;
+    if (arg[0] == '-') {
+      arg++;
+      argl--;
+      negative = 1;
+    }
+    if (argl > 2 && arg[0] == '0') {
+      if ((arg[1] == 'x' || arg[1] == 'X') &&
+          (base == 16 || base == -1)) {
+        base = 16; arg += 2; argl -= 2;
+      } else if ((arg[1] == 'b' || arg[1] == 'B') &&
+                 (base == 2 || base == -1)) {
+        base = 2;  arg += 2; argl -= 2;
+      }
+    }
+    int ull = 0;
+    if (argl > 2 && (base == 2 || base == 16 || base == -1)) {
+      if (arg[argl - 1] == 'u' || arg[argl - 1] == 'U') {
+        ull = 1;
+        --argl;
+      }
+      if ((arg[argl - 1] == 'l' || arg[argl - 1] == 'L') &&
+          (arg[argl - 2] == 'l' || arg[argl - 2] == 'L')) {
+        argl -= 2;
+        if (ull == 0 && (arg[argl - 1] == 'u' || arg[argl - 1] == 'U')) {
+          ull = 1;
+          --argl;
+        }
+      } else {
+        ull = 0;
+      }
+    }
+    base = (base == -1 ? 10 : base);
+    errno = 0;
+    char *arge;
+    unsigned long long result = strtoull(arg, &arge, base);
+    if (errno == 0 && arge == arg + argl) {
+      if (argl == 0) {
+        lua_pushnil(L);
+      } else if (negative) {
+        if (ull == 0 && result != 0 && result - 1 > INT64_MAX) {
+          lua_pushnil(L);
+          return 1;
+        }
+        cts = ctype_cts(L);
+        /*
+         * To test overflow, consider
+         *  result > -INT64_MIN;
+         *  result - 1 > -INT64_MIN - 1;
+         * Assumption:
+         *  INT64_MAX == -(INT64_MIN + 1);
+         * Finally,
+         *  result - 1 > INT64_MAX;
+         */
+        int64_t val_i;
+        uint64_t val_u;
+        if (ull != 0) {
+          val_u = (UINT64_MAX - result) + 1;
+          ctypeid = CTID_UINT64;
+        } else {
+          val_i = -result;
+          ctypeid = CTID_INT64;
+        }
+
+        lj_ctype_info(cts, ctypeid, &size);
+        assert(size != CTSIZE_INVALID);
+
+        cd = lj_cdata_new(cts, ctypeid, size);
+        TValue *o = L->top;
+        setcdataV(L, o, cd);
+        incr_top(L);
+        if (ull != 0)
+          *(uint64_t *) cdataptr(cd) = val_u;
+        else
+          *(int64_t *) cdataptr(cd) = val_i;
+      } else {
+        cts = ctype_cts(L);
+        uint64_t val_u = result;
+        ctypeid = CTID_UINT64;
+        lj_ctype_info(cts, ctypeid, &size);
+        assert(size != CTSIZE_INVALID);
+
+        cd = lj_cdata_new(cts, ctypeid, size);
+        TValue *o = L->top;
+        setcdataV(L, o, cd);
+        incr_top(L);
+        *(uint64_t *) cdataptr(cd) = val_u;
+      }
+      return 1;
+    }
+    break;
+  }
+  case LUA_TCDATA:
+    base = (base == -1 ? 10 : base);
+    if (base != 10)
+      return luaL_argerror(L, 1, "string expected");
+    cd = cdataV(L->base);
+    ctypeid = cd->ctypeid;
+    if (ctypeid >= CTID_INT8 && ctypeid <= CTID_DOUBLE) {
+      lua_pushvalue(L, 1);
+      return 1;
+    }
+    break;
+  }
+  lua_pushnil(L);
+  return 1;
+}
+
 /* ------------------------------------------------------------------------ */
 
 #include "lj_libdef.h"
diff --git a/test/gh-4738-incorrect-result-tonumber64.test.lua b/test/gh-4738-incorrect-result-tonumber64.test.lua
new file mode 100755
index 0000000..1d6da4a
--- /dev/null
+++ b/test/gh-4738-incorrect-result-tonumber64.test.lua
@@ -0,0 +1,38 @@
+#!/usr/bin/env tarantool
+
+-- Miscellaneous test for LuaJIT bugs
+local tap = require('tap')
+local misc = require('misc')
+
+local test = tap.test("gh-4738-incorrect-result-tonumber64")
+test:plan(24)
+--
+-- gh-4738: Make sure that tonumber64 always returns cdata.
+--
+test:ok(misc.tonumber64(1) == 1)
+test:ok(misc.tonumber64(-1) == -1)
+test:ok(misc.tonumber64(1.5) == 1)
+test:ok(misc.tonumber64(-1.5) == -1)
+test:ok(misc.tonumber64(1LL) == 1)
+test:ok(misc.tonumber64(1ULL) == 1)
+test:ok(misc.tonumber64(1LLU) == 1)
+test:ok(misc.tonumber64(-1ULL) == 18446744073709551615ULL)
+test:ok(misc.tonumber64('1') == 1)
+test:ok(misc.tonumber64('1LL') == 1)
+test:ok(misc.tonumber64('1ULL') == 1)
+test:ok(misc.tonumber64('-1ULL') == 18446744073709551615ULL)
+
+test:is(type(misc.tonumber64(1)), 'cdata')
+test:is(type(misc.tonumber64(-1)), 'cdata')
+test:is(type(misc.tonumber64(1.5)), 'cdata')
+test:is(type(misc.tonumber64(-1.5)), 'cdata')
+test:is(type(misc.tonumber64(1LL)), 'cdata')
+test:is(type(misc.tonumber64(1ULL)), 'cdata')
+test:is(type(misc.tonumber64(1LLU)), 'cdata')
+test:is(type(misc.tonumber64(-1ULL)), 'cdata')
+test:is(type(misc.tonumber64('1')), 'cdata')
+test:is(type(misc.tonumber64('1LL')), 'cdata')
+test:is(type(misc.tonumber64('1ULL')), 'cdata')
+test:is(type(misc.tonumber64('-1ULL')), 'cdata')
+
+os.exit(test:check() and 0 or 1)
-- 
2.25.1



More information about the Tarantool-patches mailing list