From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp61.i.mail.ru (smtp61.i.mail.ru [217.69.128.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 6E9B6469719 for ; Mon, 16 Nov 2020 12:34:31 +0300 (MSK) From: imeevma@tarantool.org Date: Mon, 16 Nov 2020 12:34:30 +0300 Message-Id: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v1 1/1] misc: introduce misc.tonumber64() List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: imun@tarantool.org Cc: tarantool-patches@dev.tarantool.org 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 +#include #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