Files
wadamesh/test/test_lua_random_range.cpp
Kaj SchittecatandClaude Opus 5.5 cde29eab2e lua: wada.sys.random over the whole 32-bit range no longer divides by zero
882a2046 computes lo + esp_random() % (uint32_t)(hi - lo + 1) in 32-bit
Lua integers (LUA_32BITS). hi - lo + 1 overflows for spans past 2^31, and
for random(math.mininteger, math.maxinteger) it is 2^32, which is 0: the
ESP32-S3 compiler at -O2 emits sub / addi 1 / remu, and remu by zero is an
IntegerDivideByZero exception, so any app could panic the device with one
call. UBSan flags the same expression as signed overflow on the host.

The range math moves into src/ui-touch/LuaRandomRange.h and is done in
unsigned 32-bit arithmetic; a span of 0 means every value. random(),
random(n) and random(lo, hi) behave as documented otherwise.
test/test_lua_random_range.cpp (clean under UBSan) covers 1..n with every
value seen, (1, 1), reversed and negative ranges and both ends of int32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 13:58:15 +01:00

71 lines
3.3 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
//
// Host test for wada.sys.random's ranges (882a2046, LuaAppHost.cpp sysRandom), the
// range math in src/ui-touch/LuaRandomRange.h. The binding itself (random() raw,
// random(n) = 1..n, argument checks) runs in the web host build:
// web/scripts/lua-input.wada with web/scripts/apps/inputlen.lua.
//
// c++ -std=c++17 -Wall -Wextra -I src test/test_lua_random_range.cpp \
// -o /tmp/test_lua_random_range && /tmp/test_lua_random_range
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <initializer_list>
#include "ui-touch/LuaRandomRange.h"
// sysRandom's argument handling, as in LuaAppHost.cpp: random(n) = (1, n).
static int32_t randomN(int32_t n, uint32_t r) { return luaRandomRange(1, n, r); }
static uint32_t s = 0x9E3779B9u;
static uint32_t next() { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return s; }
int main() {
const uint32_t edge_r[] = { 0u, 1u, 2u, 5u, 6u, 0x7FFFFFFFu, 0x80000000u, 0xFFFFFFFEu, 0xFFFFFFFFu };
// random(n): 1..n, and every value turns up
for (int32_t n : { 1, 2, 6, 10, 100, 1000 }) {
bool seen[1001] = {};
for (int i = 0; i < 200000; ++i) {
const int32_t v = randomN(n, next());
assert(v >= 1 && v <= n);
seen[v] = true;
}
for (int32_t v = 1; v <= n; ++v) assert(seen[v]);
for (uint32_t r : edge_r) { const int32_t v = randomN(n, r); assert(v >= 1 && v <= n); }
}
// random(1, 1) = 1, whatever the word (the shuffle's last step, #729)
for (uint32_t r : edge_r) assert(luaRandomRange(1, 1, r) == 1);
for (int i = 0; i < 10000; ++i) assert(luaRandomRange(1, 1, next()) == 1);
// a reversed range is swapped: random(5, 3) in 3..5, all three seen
{
bool seen[6] = {};
for (int i = 0; i < 10000; ++i) { const int32_t v = luaRandomRange(5, 3, next()); assert(v >= 3 && v <= 5); seen[v] = true; }
assert(seen[3] && seen[4] && seen[5]);
}
// negative ranges
for (int i = 0; i < 10000; ++i) { const int32_t v = luaRandomRange(-10, -5, next()); assert(v >= -10 && v <= -5); }
// the ends of the 32-bit range: no overflow, no modulo by zero
for (uint32_t r : edge_r) {
// full range: one value per word (lo + r, wrapping), so every int32 is reachable
assert(luaRandomRange(INT32_MIN, INT32_MAX, r) == (int32_t)(0x80000000u + r));
assert(luaRandomRange(INT32_MAX, INT32_MIN, r) == (int32_t)(0x80000000u + r));
const int32_t a = luaRandomRange(-1, INT32_MAX, r); assert(a >= -1);
const int32_t b = luaRandomRange(INT32_MIN, -1, r); assert(b <= -1);
const int32_t c = luaRandomRange(INT32_MIN, 0, r); assert(c <= 0);
const int32_t d = luaRandomRange(0, INT32_MAX, r); assert(d >= 0);
assert(luaRandomRange(INT32_MAX, INT32_MAX, r) == INT32_MAX);
assert(luaRandomRange(INT32_MIN, INT32_MIN, r) == INT32_MIN);
}
// 882a2046's formula, lo + (esp_random() % (uint32_t)(hi - lo + 1)) in 32-bit
// signed ints, overflowed for spans past 2^31 and divided by zero for the full
// range: (hi - lo + 1) is 2^32 there. Shown in 64 bits rather than executed. The
// ESP32-S3 compiler at -O2 turns it into sub / addi 1 / remu, and remu by zero is an
// IntegerDivideByZero exception: wada.sys.random(math.mininteger, math.maxinteger)
// from any app panicked the device.
assert((uint32_t)((int64_t)INT32_MAX - (int64_t)INT32_MIN + 1) == 0u);
printf("test_lua_random_range: ok\n");
return 0;
}