From adfd4b24096c44fa68c4af51712410c349ffeae9 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:22:37 +0100 Subject: [PATCH 01/20] add official lua test suite --- .dockerignore | 3 + .gitattributes | 3 + .gitignore | 4 + Dockerfile | 8 +- README.md | 16 +- docker-test-runner.lua | 544 ++++----- scripts/run-tests.sh | 136 +-- scripts/test/lib.lua | 71 ++ scripts/test/metadata.lua | 77 ++ scripts/test/obfuscate.lua | 25 + scripts/test/prepare-upstream.py | 24 + scripts/test/upstream-case.lua | 27 + tests/harness/metadata.lua | 27 + tests/harness/runner.py | 93 ++ tests/upstream/lua51/LICENSE | 19 + tests/upstream/lua51/README.md | 127 ++ tests/upstream/lua51/SHA256SUMS | 32 + tests/upstream/lua51/exclusions.json | 116 ++ tests/upstream/lua51/manifest.lua | 27 + tests/upstream/lua51/patches/portable.patch | 450 ++++++++ tests/upstream/lua51/vendor/README | 41 + tests/upstream/lua51/vendor/all.lua | 137 +++ tests/upstream/lua51/vendor/api.lua | 711 ++++++++++++ tests/upstream/lua51/vendor/attrib.lua | 339 ++++++ tests/upstream/lua51/vendor/big.lua | 381 ++++++ tests/upstream/lua51/vendor/calls.lua | 294 +++++ tests/upstream/lua51/vendor/checktable.lua | 77 ++ tests/upstream/lua51/vendor/closure.lua | 422 +++++++ tests/upstream/lua51/vendor/code.lua | 143 +++ tests/upstream/lua51/vendor/constructs.lua | 240 ++++ tests/upstream/lua51/vendor/db.lua | 499 ++++++++ tests/upstream/lua51/vendor/errors.lua | 250 ++++ tests/upstream/lua51/vendor/etc/ltests.c | 1147 +++++++++++++++++++ tests/upstream/lua51/vendor/etc/ltests.h | 92 ++ tests/upstream/lua51/vendor/events.lua | 360 ++++++ tests/upstream/lua51/vendor/files.lua | 324 ++++++ tests/upstream/lua51/vendor/gc.lua | 312 +++++ tests/upstream/lua51/vendor/libs/lib1.c | 40 + tests/upstream/lua51/vendor/libs/lib11.c | 18 + tests/upstream/lua51/vendor/libs/lib2.c | 28 + tests/upstream/lua51/vendor/libs/lib21.c | 18 + tests/upstream/lua51/vendor/libs/makefile | 16 + tests/upstream/lua51/vendor/literals.lua | 176 +++ tests/upstream/lua51/vendor/locals.lua | 127 ++ tests/upstream/lua51/vendor/main.lua | 159 +++ tests/upstream/lua51/vendor/math.lua | 208 ++++ tests/upstream/lua51/vendor/nextvar.lua | 396 +++++++ tests/upstream/lua51/vendor/pm.lua | 273 +++++ tests/upstream/lua51/vendor/sort.lua | 74 ++ tests/upstream/lua51/vendor/strings.lua | 176 +++ tests/upstream/lua51/vendor/vararg.lua | 126 ++ tests/upstream/lua51/vendor/verybig.lua | 100 ++ 52 files changed, 9122 insertions(+), 411 deletions(-) create mode 100644 scripts/test/lib.lua create mode 100644 scripts/test/metadata.lua create mode 100644 scripts/test/obfuscate.lua create mode 100644 scripts/test/prepare-upstream.py create mode 100644 scripts/test/upstream-case.lua create mode 100644 tests/harness/metadata.lua create mode 100644 tests/harness/runner.py create mode 100644 tests/upstream/lua51/LICENSE create mode 100644 tests/upstream/lua51/README.md create mode 100644 tests/upstream/lua51/SHA256SUMS create mode 100644 tests/upstream/lua51/exclusions.json create mode 100644 tests/upstream/lua51/manifest.lua create mode 100644 tests/upstream/lua51/patches/portable.patch create mode 100644 tests/upstream/lua51/vendor/README create mode 100644 tests/upstream/lua51/vendor/all.lua create mode 100644 tests/upstream/lua51/vendor/api.lua create mode 100644 tests/upstream/lua51/vendor/attrib.lua create mode 100644 tests/upstream/lua51/vendor/big.lua create mode 100644 tests/upstream/lua51/vendor/calls.lua create mode 100644 tests/upstream/lua51/vendor/checktable.lua create mode 100644 tests/upstream/lua51/vendor/closure.lua create mode 100644 tests/upstream/lua51/vendor/code.lua create mode 100644 tests/upstream/lua51/vendor/constructs.lua create mode 100644 tests/upstream/lua51/vendor/db.lua create mode 100644 tests/upstream/lua51/vendor/errors.lua create mode 100644 tests/upstream/lua51/vendor/etc/ltests.c create mode 100644 tests/upstream/lua51/vendor/etc/ltests.h create mode 100644 tests/upstream/lua51/vendor/events.lua create mode 100644 tests/upstream/lua51/vendor/files.lua create mode 100644 tests/upstream/lua51/vendor/gc.lua create mode 100644 tests/upstream/lua51/vendor/libs/lib1.c create mode 100644 tests/upstream/lua51/vendor/libs/lib11.c create mode 100644 tests/upstream/lua51/vendor/libs/lib2.c create mode 100644 tests/upstream/lua51/vendor/libs/lib21.c create mode 100644 tests/upstream/lua51/vendor/libs/makefile create mode 100644 tests/upstream/lua51/vendor/literals.lua create mode 100644 tests/upstream/lua51/vendor/locals.lua create mode 100644 tests/upstream/lua51/vendor/main.lua create mode 100644 tests/upstream/lua51/vendor/math.lua create mode 100644 tests/upstream/lua51/vendor/nextvar.lua create mode 100644 tests/upstream/lua51/vendor/pm.lua create mode 100644 tests/upstream/lua51/vendor/sort.lua create mode 100644 tests/upstream/lua51/vendor/strings.lua create mode 100644 tests/upstream/lua51/vendor/vararg.lua create mode 100644 tests/upstream/lua51/vendor/verybig.lua diff --git a/.dockerignore b/.dockerignore index 5156ac2f..238021da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,3 +13,6 @@ Dockerfile .dockerignore .git .gitignore + +test-results +__pycache__ diff --git a/.gitattributes b/.gitattributes index ca69452d..cdec5dc2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ *.lua text=auto *.lua eol=lf + +# Preserve the official archive byte-for-byte for checksum verification. +tests/upstream/lua51/vendor/** -text !eol diff --git a/.gitignore b/.gitignore index 4107fb9f..f26c1274 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,7 @@ web/playwright-report test.lua test.obfuscated.lua config.lua + +# Lua test logs and reproduction artifacts +/test-results/ +__pycache__/ diff --git a/Dockerfile b/Dockerfile index 03b02e7f..8736e032 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,17 @@ FROM ubuntu:24.04 RUN apt-get update && apt-get install -y \ lua5.1 \ + python3 \ + patch \ git \ unzip \ curl \ && rm -rf /var/lib/apt/lists/* # Download pre-built Luau binary -RUN curl -sSLo /tmp/luau-ubuntu.zip \ +RUN --mount=type=secret,id=ca-bundle \ + if [ -f /run/secrets/ca-bundle ]; then export CURL_CA_BUNDLE=/run/secrets/ca-bundle; fi; \ + curl -fsSLo /tmp/luau-ubuntu.zip \ https://github.com/luau-lang/luau/releases/latest/download/luau-ubuntu.zip \ && unzip -j /tmp/luau-ubuntu.zip luau -d /usr/local/bin \ && chmod +x /usr/local/bin/luau \ @@ -20,4 +24,6 @@ RUN lua5.1 -v && printf 'print("luau OK")\n' > /tmp/luau_check.lua && luau /tmp/ WORKDIR /app COPY . /app +RUN lua5.1 tests/harness/metadata.lua && python3 tests/harness/runner.py + ENTRYPOINT ["lua5.1", "docker-test-runner.lua"] diff --git a/README.md b/README.md index ef778801..0680efac 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,8 @@ For more advanced use cases, configuration, and presets, see the [documentation] ## Tests -The test suite runs inside Docker with lua5.1 and Luau: +The test suite runs existing cases on Lua 5.1 and Luau, plus the adapted +official Lua 5.1 suite on Lua 5.1, inside Docker: ```bash ./scripts/run-tests.sh # Run all tests (default: 10 iterations) @@ -146,8 +147,21 @@ The test suite runs inside Docker with lua5.1 and Luau: ./scripts/run-tests.sh -n 5 # Run with 5 iterations ./scripts/run-tests.sh -c config.lua # Use a custom config ./scripts/run-tests.sh -v # Verbose output +./scripts/run-tests.sh --suite upstream --baseline-only # Verify official originals +./scripts/run-tests.sh --case lua51/closure.lua --preset Vmify --seed 4 -n 1 ``` +Failures do not stop remaining cases, presets, or iterations. Logs and failing +inputs/output persist in `test-results/`. Each obfuscation or execution has a +30-second limit (override with `--timeout N`). AntiTamper is removed from tested +presets. See [upstream suite provenance, exclusions, and reproduction](tests/upstream/lua51/README.md). + +Metadata directives may appear anywhere in the leading comment region, including +after license comments and blank lines: `@skip`, `@runtime lua5.1 luau`, +`@luau-only`, and `@skip-preset Weak`. Underscore aliases are accepted for the +last two directives, and `lua51` is an alias for `lua5.1`. Preset names are +case-sensitive; unknown or malformed directives fail with a file/line diagnostic. + --- ## Community diff --git a/docker-test-runner.lua b/docker-test-runner.lua index eb7ddaab..a2ef4984 100644 --- a/docker-test-runner.lua +++ b/docker-test-runner.lua @@ -1,365 +1,249 @@ --- docker-test-runner.lua --- Docker-based test orchestrator for Prometheus --- Runs each test script against all Lua runtimes and presets, comparing output - -local Prometheus = require("src.prometheus") -local Presets = require("src.presets") - --- === Argument Parsing === +-- Baselines and transformed cases run in isolated, timed subprocesses. +local lib = require("scripts.test.lib") +local metadata = require("scripts.test.metadata") +local presets = require("src.presets") local args = {} for _, a in ipairs(arg) do - local key, val = a:match("^%-%-([%w_]+)=(.+)$") - if key then - args[key] = val - elseif a:match("^%-%-([%w_]+)$") then - args[a:match("^%-%-(.+)$")] = true + local key, value = a:match("^%-%-([%w_%-]+)=(.*)$") + if key then args[key] = value + else + key = a:match("^%-%-([%w_%-]+)$") + assert(key, "invalid argument " .. a) + args[key] = true end end - -local ITERATIONS = math.max(tonumber(args.iterations) or 10, 1) -local CUSTOM_CONFIG = args.config -local CI_MODE = args.CI == true or args.ci == true -local TEST_DIR = "./tests/" -local PASS_RUNNERS = (args["pass-runners"] ~= nil) -local VERBOSE = args.verbose == true - --- === Runtimes === -local RUNTIMES = { - { - name = "lua5.1", - cmd = "lua5.1", - }, - { - name = "luau", - cmd = "luau", - }, -} - --- === Helpers === -local function shell_escape(s) - return "'" .. s:gsub("'", "'\\''") .. "'" +local function integer(key, default) + local value = tonumber(args[key] or default) + assert(value and value >= 1 and value == math.floor(value), "--" .. key .. " must be a positive integer") + return value end - -local function exec(cmd) - local handle = io.popen(cmd .. " 2>&1") - if not handle then - return nil, "failed to execute: " .. cmd - end - local output = handle:read("*a") - local ok = handle:close() - -- In Lua 5.1, popen:close() returns true for zero exit, nil otherwise - -- In LuaJIT/Lua 5.2+, returns true/nil with additional status info - if ok then - return output, 0 - else - return output, 1 - end +local iterations, timeout, seed = integer("iterations", 10), integer("timeout", 30), integer("seed", 1) +assert(seed + iterations - 1 <= 2147483647, "seed range must fit a signed 32-bit integer") +local suite = args.suite or "all" +assert(suite == "all" or suite == "existing" or suite == "upstream", "invalid --suite") +local cwd_handle = assert(io.popen("pwd")) +local root = assert(cwd_handle:read("*l")) +cwd_handle:close() +local function absolute(path) + return path:sub(1, 1) == "/" and path or root .. "/" .. path end - -local function trim(s) - return (s:gsub("^%s+", ""):gsub("%s+$", "")) +local artifact_parent = absolute(args.artifacts or "test-results") +lib.mkdir(artifact_parent) +local artifacts = lib.tempdir(artifact_parent, "run-" .. os.date("!%Y%m%d-%H%M%S")) +lib.mkdir(artifacts .. "/work") +local report = assert(io.open(artifacts .. "/run.log", "w")) +io.stdout:setvbuf("no") +local function say(message) + print(message) + report:write(message .. "\n") + report:flush() end - -local function normalize_output(s) - s = s:gsub("\r\n", "\n"):gsub("\r", "\n") - local lines = {} - for line in s:gmatch("[^\n]*") do - table.insert(lines, trim(line)) - end - while #lines > 0 and lines[#lines] == "" do - table.remove(lines) - end - return table.concat(lines, "\n") +local results = assert(io.open(artifacts .. "/results.tsv", "w")) +results:write("case\tphase\tpreset\titeration\truntime\tseed\tstatus\texit_code\tartifact\n") +local totals = { passed = 0, failed = 0, baseline_passed = 0, baseline_failed = 0, timeouts = 0, excluded = 0, skipped = 0 } +local function record(case, phase, preset, iteration, runtime, run_seed, status, exit_code, path) + results:write(table.concat({case, phase, preset, iteration, runtime, run_seed, status, exit_code, path}, "\t") .. "\n") + results:flush() + local counter = phase == "baseline" and (status == "PASS" and "baseline_passed" or "baseline_failed") or + (status == "PASS" and "passed" or "failed") + totals[counter] = totals[counter] + 1 + if exit_code == 124 or exit_code == 137 then totals.timeouts = totals.timeouts + 1 end end -local colors = { - red = "\27[31m", - green = "\27[32m", - yellow = "\27[33m", - magenta = "\27[35m", - cyan = "\27[36m", - reset = "\27[0m", - bold = "\27[1m", -} -local function fmt(col, text) - return (colors[col] or "") .. text .. colors.reset +local runtimes = { { name = "lua5.1", cmd = "lua5.1" }, { name = "luau", cmd = "luau" } } +if args.runtime == "lua51" then args.runtime = "lua5.1" end +assert(not args.runtime or args.runtime == "lua5.1" or args.runtime == "luau", "invalid --runtime") +local configs = {} +local config_path = args.config and absolute(args.config) or "" +if args.config then + local loaded = assert(loadfile(config_path))() + if type(loaded[1]) == "table" then + for i, cfg in ipairs(loaded) do configs[#configs + 1] = { name = cfg.Name or ("custom-" .. i), selector = tostring(i) } end + else configs[1] = { name = loaded.Name or "custom", selector = "1" } end +else + for name in pairs(presets) do configs[#configs + 1] = { name = name, selector = name } end + table.sort(configs, function(a, b) return a.name < b.name end) end - --- === Metadata parsing === --- Test files can have metadata comments at the top: --- -- @skip (skip this test entirely) --- -- @luau-only (only run with luau) --- -- @runtime lua51 luajit (only run with specified runtimes) --- -- @skip-preset Weak (skip a specific preset) -local function parse_metadata(code) - local meta = { - runtimes = {}, - skip_presets = {}, - skip = false, - } - for line in code:gmatch("[^\n]*") do - if not line:match("^%s*%-%-%s*@") then - break - end - local key, val = line:match("^%s*%-%-%s*@(%w+)%s+(.+)") - if key then - if key == "runtime" then - for r in val:gmatch("%S+") do - meta.runtimes[r:lower()] = true - end - elseif key == "skip_preset" or key == "skip-preset" then - meta.skip_presets[val] = true - elseif key == "luau_only" or key == "luau-only" then - meta.runtimes["luau"] = true - end - end - if line:match("^%s*%-%-%s*@skip%s*$") then - meta.skip = true - end - if line:match("^%s*%-%-%s*@luau%-only%s*$") then - meta.runtimes["luau"] = true - end - end - return meta +if args.preset then + local selected = {} + for _, cfg in ipairs(configs) do if cfg.name == args.preset then selected[#selected + 1] = cfg end end + assert(#selected > 0, "unknown --preset " .. args.preset) + configs = selected end --- === Baseline capture === -local function run_script(runtime, script_path) - local out, status = exec(runtime.cmd .. " " .. shell_escape(script_path)) - if status ~= 0 then - return nil, "exit code " .. tostring(status) .. ": " .. (out or "") +say("=== Prometheus Test Runner ===") +say("Iterations: " .. iterations .. "; seeds: " .. seed .. ".." .. (seed + iterations - 1) .. "; timeout: " .. timeout .. "s") +say("Suite: " .. suite .. "; AntiTamper removed from presets") +say("Artifacts: " .. artifacts) +local cases = {} +if suite ~= "upstream" then + local directory = absolute(args["test-dir"] or "tests") + for _, file in ipairs(lib.files(directory)) do + cases[#cases + 1] = { name = file, file = file, directory = directory, supports = {} } end - return normalize_output(out), nil end - --- === List test files === -local function scandir(dir) - local handle = io.popen("ls -1 " .. shell_escape(dir)) - if not handle then - error("Failed to list directory: " .. dir) - end - local files = {} - for name in handle:lines() do - if name:match("%.lua$") then - table.insert(files, name) +if suite ~= "existing" then + local upstream = root .. "/tests/upstream/lua51" + local manifest = assert(loadfile(upstream .. "/manifest.lua"))() + local seen = {} + for _, entry in ipairs(manifest) do + assert(not seen[entry.file], "duplicate upstream manifest entry " .. entry.file) + seen[entry.file] = entry + assert(entry.role == "case" or entry.role == "support" or entry.role == "launcher" or entry.role == "excluded", "invalid upstream role") + end + local files = lib.files(upstream .. "/vendor") + assert(#files == #manifest, "upstream manifest must account for every Lua script") + for _, file in ipairs(files) do assert(seen[file], "unaccounted upstream script " .. file) end + local output, status = lib.run("python3 scripts/test/prepare-upstream.py " .. lib.quote(artifacts .. "/adapted"), root, timeout, artifacts .. "/prepare.log") + assert(status == 0, "upstream preparation failed: " .. output) + for _, entry in ipairs(manifest) do + if entry.role == "case" then + local supports = entry.supports or {} + for _, support in ipairs(supports) do assert(seen[support] and seen[support].role == "support", "invalid support file " .. support) end + cases[#cases + 1] = { name = "lua51/" .. entry.file, file = entry.file, directory = artifacts .. "/adapted", + upstream = true, protocol = entry.protocol, supports = supports } + elseif entry.role ~= "support" then + totals.excluded = totals.excluded + 1 + say("[EXCLUDED] lua51/" .. entry.file .. ": " .. entry.reason) end end - handle:close() - table.sort(files) - return files end - --- === Shallow copy === -local function shallowcopy(orig) - if type(orig) ~= "table" then - return orig - end - local copy = {} - for k, v in pairs(orig) do - copy[k] = v - end - return copy +if args.case then + local selected = {} + for _, case in ipairs(cases) do if case.name == args.case then selected[#selected + 1] = case end end + assert(#selected > 0, "unknown --case " .. args.case) + cases = selected end --- === Test a single runfile against a single test file === --- Returns true if passed, false if failed -local function test_runfile(filename, code, meta, runfile, active_runtimes, baselines) - local runfile_name = runfile._name - - -- Check for skip-preset - if meta.skip_presets[runfile_name] then - print(" " .. fmt("yellow", "[SKIP]") .. " preset " .. runfile_name) - return true +local function new_job(case, label) + local safe = (case.name .. "-" .. label):gsub("[^%w._%-]", "_") + local path = lib.tempdir(artifacts .. "/work", safe) + lib.mkdir(path .. "/input") + lib.mkdir(path .. "/generated") + lib.write(path .. "/input/" .. case.file, lib.read(case.directory .. "/" .. case.file)) + for _, support in ipairs(case.supports) do + lib.write(path .. "/input/" .. support, lib.read(case.directory .. "/" .. support)) end - - for iter = 1, ITERATIONS do - -- Remove AntiTamper step before testing - local steps = {} - for _, step in ipairs(runfile.Steps) do - if step.Name ~= "AntiTamper" then - table.insert(steps, shallowcopy(step)) - end - end - local cfg = shallowcopy(runfile) - cfg.Steps = steps - - local pipeline = Prometheus.Pipeline:fromConfig(cfg) - pipeline:setNameGenerator("MangledShuffled") - - local ok, obfuscated = pcall(pipeline.apply, pipeline, code) - if not ok then - local err_msg = "obfuscation error: " .. tostring(obfuscated) - print(" " .. fmt("red", "[FAIL] ") .. runfile_name .. " #" .. iter .. " - " .. fmt("red", err_msg)) - if type(obfuscated) == "string" and #obfuscated > 200 then - print(" " .. obfuscated:sub(1, 200) .. "...") - end - return false - end - - -- Write obfuscated code to a temp file - local tmpfile = "/tmp/prometheus_test_obfuscated.lua" - local tfh = io.open(tmpfile, "w") - if not tfh then - print(" " .. fmt("red", "[FAIL] ") .. runfile_name .. " #" .. iter .. " - cannot write temp file") - return false - end - tfh:write(obfuscated) - tfh:close() - - -- Run against each active runtime - for _, rt in ipairs(active_runtimes) do - local out, err = run_script(rt, tmpfile) - if err then - print(" " .. fmt("red", "[FAIL] ") .. runfile_name .. " #" .. iter .. " on " .. rt.name .. fmt("red", " - error")) - print(" " .. fmt("yellow", "[ERR] ") .. err:sub(1, 300)) - print(" " .. fmt("yellow", "[SRC] ") .. obfuscated:gsub("\n", "\\n"):sub(1, 300)) - os.remove(tmpfile) - return false - end - - local expected = baselines[rt.name] - if normalize_output(out) ~= normalize_output(expected) then - print(" " .. fmt("red", "[FAIL] ") .. runfile_name .. " #" .. iter .. " on " .. rt.name .. " - output mismatch") - local max_show = 300 - print(" " .. fmt("yellow", "[EXPECTED] ") .. expected:sub(1, max_show)) - print(" " .. fmt("yellow", "[GOT] ") .. out:sub(1, max_show)) - os.remove(tmpfile) - return false - end - end - - os.remove(tmpfile) - end - - local runtime_list = {} - for _, rt in ipairs(active_runtimes) do - table.insert(runtime_list, rt.name) - end - local n_runtimes = #active_runtimes - print(string.format(" " .. fmt("green", "[PASS]") .. " %s (%d iterations on %s)", runfile_name, ITERATIONS, table.concat(runtime_list, ", "))) - return true + return path end - --- === Main === - --- Determine which presets/runfiles to use -local runfiles = {} -if CUSTOM_CONFIG then - local cfg, err = loadfile(CUSTOM_CONFIG) - if not cfg then - error("Failed to load custom config: " .. tostring(err)) - end - local loaded = cfg() - if type(loaded[1]) == "table" then - for i, c in ipairs(loaded) do - c._name = c.Name or ("custom-" .. i) - table.insert(runfiles, c) - end - else - loaded._name = loaded.Name or "custom" - table.insert(runfiles, loaded) - end -else - for name, preset in pairs(Presets) do - local copy = shallowcopy(preset) - copy._name = name - table.insert(runfiles, copy) - end - table.sort(runfiles, function(a, b) return a._name < b._name end) +local function stage(case, directory) + lib.mkdir(directory .. "/libs/P1") + if case.upstream then lib.write(directory .. "/case-runner.lua", lib.read(root .. "/scripts/test/upstream-case.lua")) end +end +local function execute(case, runtime, directory, log) + local runtime_directory = directory .. "-" .. runtime.name + lib.mkdir(runtime_directory) + lib.write(runtime_directory .. "/" .. case.file, lib.read(directory .. "/" .. case.file)) + for _, support in ipairs(case.supports) do + lib.write(runtime_directory .. "/" .. support, lib.read(directory .. "/" .. support)) + end + stage(case, runtime_directory) + local command = runtime.cmd .. " " .. lib.quote(case.file) + if case.upstream then + command = runtime.cmd .. " case-runner.lua " .. lib.quote(case.file) .. " " .. lib.quote(case.protocol) + end + local output, code = lib.run(command, runtime_directory, timeout, log) + if code ~= 0 then return output, code, "exit " .. code end + if case.upstream and not output:find("\nPROMETHEUS_UPSTREAM_COMPLETE\n", 1, true) then + return output, code, "missing completion marker" + end + return output, code +end +local function clean(path) + -- Only remove fresh, runner-owned work directories; retain every failed job. + assert(path:sub(1, #artifacts + 6) == artifacts .. "/work/") + assert(lib.status(os.execute("rm -rf -- " .. lib.quote(path))) == 0) +end +local function failure(case, phase, cfg, iter, runtime, run_seed, output, code, reason, path) + lib.write(path .. "/failure-" .. runtime .. ".txt", reason .. "\ncase=" .. case.name .. "\npreset=" .. cfg .. "\niteration=" .. iter .. + "\nruntime=" .. runtime .. "\nseed=" .. run_seed .. "\n") + record(case.name, phase, cfg, iter, runtime, run_seed, "FAIL", code, path) + say("[" .. (phase == "baseline" and "BASELINE FAIL" or "FAIL") .. "] " .. case.name .. " " .. cfg .. " #" .. iter .. + " on " .. runtime .. " seed=" .. run_seed .. ": " .. reason .. " (" .. path .. ")") + say(output:sub(1, 500)) end --- Suppress Prometheus logger output during tests -Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error - -print(fmt("bold", "=== Prometheus Docker Test Runner ===")) -print(string.format("Test files: %s", TEST_DIR)) -print(string.format("Iterations: %d", ITERATIONS)) -print(string.format("Preset(s): %s", CUSTOM_CONFIG and "custom (" .. CUSTOM_CONFIG .. ")" or "all built-in")) -print(string.format("CI mode: %s", CI_MODE and "yes" or "no")) -print("") - -local test_files = scandir(TEST_DIR) -local total_passed = 0 -local total_failed = 0 - -for _, filename in ipairs(test_files) do - local filepath = TEST_DIR .. filename - local fh = io.open(filepath, "r") - if not fh then - print(fmt("red", "[ERROR] ") .. "cannot open " .. filepath) - total_failed = total_failed + 1 +for _, case in ipairs(cases) do + local parsed, meta = pcall(metadata.parse, lib.read(case.directory .. "/" .. case.file), case.name) + if not parsed then + totals.failed = totals.failed + 1 + say("[METADATA FAIL] " .. meta) + elseif meta.skip then + totals.skipped = totals.skipped + 1 + say("[SKIP] " .. case.name .. " (@skip)") else - local code = fh:read("*a") - fh:close() - - local meta = parse_metadata(code) - if meta.skip then - print(fmt("yellow", "[SKIP] ") .. filename .. " (marked @skip)") + local active, baselines = {}, {} + for _, runtime in ipairs(runtimes) do + if (not args.runtime or args.runtime == runtime.name) and (not case.upstream or runtime.name == "lua5.1") and + (not next(meta.runtimes) or meta.runtimes[runtime.name]) then active[#active + 1] = runtime end + end + if #active == 0 then + totals.skipped = totals.skipped + 1 + say("[SKIP] " .. case.name .. " (no matching runtime)") else - -- Determine active runtimes - local active_runtimes = {} - local has_runtime_filter = next(meta.runtimes) ~= nil - for _, rt in ipairs(RUNTIMES) do - if has_runtime_filter then - if meta.runtimes[rt.name] then - table.insert(active_runtimes, rt) - end + say("[TEST] " .. case.name) + for _, runtime in ipairs(active) do + local path = new_job(case, "baseline-" .. runtime.name) + local output, code, reason = execute(case, runtime, path .. "/input", path .. "/output.log") + if reason then failure(case, "baseline", "-", 0, runtime.name, 0, output, code, reason, path) else - table.insert(active_runtimes, rt) + baselines[runtime.name] = output + record(case.name, "baseline", "-", 0, runtime.name, 0, "PASS", 0, "-") + if args["pass-runners"] or args.verbose then say("[BASELINE PASS] " .. case.name .. " on " .. runtime.name) end + clean(path) end end - - if #active_runtimes == 0 then - print(fmt("yellow", "[SKIP] ") .. filename .. " (no matching runtimes)") - else - -- Get baselines - local baselines = {} - local baseline_ok = true - for _, rt in ipairs(active_runtimes) do - local out, err = run_script(rt, filepath) - if err then - print(fmt("red", "[BASELINE FAIL] ") .. filename .. " on " .. rt.name .. ": " .. err) - baseline_ok = false - break - end - if PASS_RUNNERS then - print(string.format(" " .. fmt("green", "[BASELINE] ") .. "%s on %s PASSED", filename, rt.name)) - end - baselines[rt.name] = out - end - - if not baseline_ok then - total_failed = total_failed + 1 - else - print(fmt("magenta", "[TEST] ") .. filename) - - local file_failed = false - for _, runfile in ipairs(runfiles) do - if not test_runfile(filename, code, meta, runfile, active_runtimes, baselines) then - file_failed = true - break - end - end - - if file_failed then - total_failed = total_failed + 1 + if not args["baseline-only"] and next(baselines) then + for _, cfg in ipairs(configs) do + if meta.skip_presets[cfg.name] then + totals.skipped = totals.skipped + 1 + say("[SKIP] " .. case.name .. " preset " .. cfg.name) else - total_passed = total_passed + 1 + local passed = true + for iter = 1, iterations do + local run_seed = seed + iter - 1 + local path = new_job(case, cfg.name .. "-" .. iter) + local files = {case.file} + for _, support in ipairs(case.supports) do files[#files + 1] = support end + local transform_error, transform_output, transform_code + for _, file in ipairs(files) do + local command = "lua5.1 scripts/test/obfuscate.lua " .. lib.quote(config_path) .. " " .. lib.quote(cfg.selector) .. + " " .. run_seed .. " " .. lib.quote(path .. "/input/" .. file) .. " " .. lib.quote(path .. "/generated/" .. file) + local output, code = lib.run(command, root, timeout, path .. "/obfuscate-" .. file .. ".log") + if code ~= 0 then + transform_error, transform_output, transform_code = "obfuscation of " .. file .. " exited " .. code, output, code + break + end + end + local failed = false + for _, runtime in ipairs(active) do + if baselines[runtime.name] then + local output, code, reason + if transform_error then output, code, reason = transform_output, transform_code, transform_error + else + output, code, reason = execute(case, runtime, path .. "/generated", path .. "/" .. runtime.name .. ".log") + if not reason and not case.upstream and lib.normalize(output) ~= lib.normalize(baselines[runtime.name]) then + reason = "output mismatch" + lib.write(path .. "/expected-" .. runtime.name .. ".log", baselines[runtime.name]) + end + end + if reason then + failed, passed = true, false + failure(case, "transformed", cfg.name, iter, runtime.name, run_seed, output, code, reason, path) + else record(case.name, "transformed", cfg.name, iter, runtime.name, run_seed, "PASS", 0, "-") end + end + end + if not failed then clean(path) end + end + if passed then say("[PASS] " .. case.name .. " " .. cfg.name .. " (" .. iterations .. " iterations)") end end end end end end end - -print("") -local total = total_passed + total_failed -print(string.format("Total: %d passed, %d failed, %d total", total_passed, total_failed, total)) - -if total_failed == 0 then - print(fmt("green", fmt("bold", "=== ALL TESTS PASSED ==="))) - os.exit(0) -else - print(fmt("red", fmt("bold", string.format("=== %d TEST(S) FAILED ===", total_failed)))) - os.exit(1) -end +say(string.format("Total: %d transformed passed, %d failed; %d baselines passed, %d failed; %d timeouts; %d scripts excluded; %d selections skipped", + totals.passed, totals.failed, totals.baseline_passed, totals.baseline_failed, totals.timeouts, totals.excluded, totals.skipped)) +results:close() +report:close() +os.exit(totals.failed + totals.baseline_failed == 0 and 0 or 1) diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index d22e95b1..2e4fc1b1 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -1,106 +1,84 @@ #!/bin/bash -# scripts/run-tests.sh -# Convenience script to build and run Prometheus tests in Docker +# Build and run the complete Lua semantic suite in Docker. set -euo pipefail - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" - IMAGE_NAME="${IMAGE_NAME:-prometheus-tests}" N="${N:-10}" -DOCKER_EXTRA_ARGS="" +BUILD=0 +CUSTOM_CONFIG="" +ARTIFACT_DIR="${ARTIFACT_DIR:-$PROJECT_DIR/test-results}" +BUILD_ARGS=() +RUNNER_ARGS=() +if [[ -n "${TEST_CA_BUNDLE:-}" ]]; then + BUILD_ARGS+=(--secret "id=ca-bundle,src=$TEST_CA_BUNDLE") +fi usage() { - cat <&2; exit 2; } + case "$1" in + -n) N="$2" ;; + -c) CUSTOM_CONFIG="$2" ;; + --artifacts) ARTIFACT_DIR="$2" ;; + *) RUNNER_ARGS+=("$1=$2") ;; + esac + shift 2 ;; + -b) BUILD=1; shift ;; + -v) RUNNER_ARGS+=(--verbose); shift ;; + --pass-runners|--baseline-only) RUNNER_ARGS+=("$1"); shift ;; + --no-cache) BUILD_ARGS+=(--no-cache); BUILD=1; shift ;; + --ci) RUNNER_ARGS+=(--ci); shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; esac done -# Check if image exists; if not, build if [[ $BUILD -eq 1 ]] || ! docker image inspect "$IMAGE_NAME" &>/dev/null; then - echo "=== Building Docker image '$IMAGE_NAME' ===" - docker build $DOCKER_EXTRA_ARGS -t "$IMAGE_NAME" "$PROJECT_DIR" - echo "" + docker build "${BUILD_ARGS[@]}" -t "$IMAGE_NAME" "$PROJECT_DIR" fi -# Prepare config mount if custom config is specified -CONFIG_MOUNT="" -RUNNER_ARGS="--iterations=$N $CI_FLAG $VERBOSE_FLAG $PASS_RUNNERS_FLAG" +mkdir -p "$ARTIFACT_DIR" +ARTIFACT_ABS="$(cd "$ARTIFACT_DIR" && pwd)" +MOUNTS=(-v "$ARTIFACT_ABS:/artifacts") +RUNNER_ARGS+=("--iterations=$N" --artifacts=/artifacts) if [[ -n "$CUSTOM_CONFIG" ]]; then CONFIG_ABS="$(cd "$(dirname "$CUSTOM_CONFIG")" && pwd)/$(basename "$CUSTOM_CONFIG")" - CONFIG_CONTAINER="/tmp/prometheus_custom_config.lua" - CONFIG_MOUNT="-v $CONFIG_ABS:$CONFIG_CONTAINER:ro" - RUNNER_ARGS="$RUNNER_ARGS --config=$CONFIG_CONTAINER" + MOUNTS+=(-v "$CONFIG_ABS:/tmp/prometheus_custom_config.lua:ro") + RUNNER_ARGS+=(--config=/tmp/prometheus_custom_config.lua) fi -echo "=== Running Prometheus Tests ===" -echo "Iterations: $N" -[[ -n "$CUSTOM_CONFIG" ]] && echo "Custom config: $CUSTOM_CONFIG" - -docker run --rm \ - $CONFIG_MOUNT \ - "$IMAGE_NAME" \ - $RUNNER_ARGS +echo "Running Prometheus tests ($N iterations); artifacts: $ARTIFACT_ABS" +# Running as the invoking user keeps mounted artifacts readable and writable. +docker run --rm --user "$(id -u):$(id -g)" "${MOUNTS[@]}" "$IMAGE_NAME" "${RUNNER_ARGS[@]}" diff --git a/scripts/test/lib.lua b/scripts/test/lib.lua new file mode 100644 index 00000000..d7fa2590 --- /dev/null +++ b/scripts/test/lib.lua @@ -0,0 +1,71 @@ +local M = {} + +function M.quote(s) + return "'" .. tostring(s):gsub("'", "'\\''") .. "'" +end + +function M.read(path) + local f = assert(io.open(path, "rb")) + local contents = f:read("*a") + f:close() + return contents +end + +function M.write(path, contents) + local f = assert(io.open(path, "wb")) + assert(f:write(contents)) + assert(f:close()) +end + +-- Lua 5.1 returns the encoded wait status; later Lua versions return a tuple. +-- Do not use io.popen():close(): stock Lua 5.1 loses child exit status there. +function M.status(a, why, code) + if type(a) == "number" then + if a == -1 then return 125 end + local signal = a % 128 + if signal ~= 0 then return 128 + signal end + return math.floor(a / 256) + end + if a == true then return 0 end + return why == "signal" and 128 + (code or 0) or (code or 125) +end + +function M.run(command, cwd, seconds, log) + local shell = "cd " .. M.quote(cwd) .. " && LC_ALL=C TZ=UTC timeout --kill-after=5s " .. + M.quote(seconds) .. "s " .. command + local status = M.status(os.execute("(" .. shell .. ") > " .. M.quote(log) .. " 2>&1")) + return M.read(log), status +end + +function M.mkdir(path) + assert(M.status(os.execute("mkdir -p " .. M.quote(path))) == 0, "cannot create " .. path) +end + +function M.tempdir(parent, prefix) + local p = assert(io.popen("mktemp -d " .. M.quote(parent .. "/" .. prefix .. "-XXXXXX"))) + local path = assert(p:read("*l")) + p:close() + assert(path ~= "", "cannot create temporary directory") + return path +end + +function M.files(dir) + local p = assert(io.popen("find " .. M.quote(dir) .. " -maxdepth 1 -type f -name '*.lua' -printf '%f\\n'")) + local files = {} + for file in p:lines() do files[#files + 1] = file end + p:close() + table.sort(files) + return files +end + +function M.normalize(s) + s = s:gsub("\r\n", "\n"):gsub("\r", "\n") + local lines = {} + for line in (s .. "\n"):gmatch("(.-)\n") do + lines[#lines + 1] = line:gsub("^%s+", ""):gsub("%s+$", "") + end + while lines[#lines] == "" do table.remove(lines) end + return table.concat(lines, "\n") +end + +return M diff --git a/scripts/test/metadata.lua b/scripts/test/metadata.lua new file mode 100644 index 00000000..bff5ed75 --- /dev/null +++ b/scripts/test/metadata.lua @@ -0,0 +1,77 @@ +-- Directives are only read from the leading whitespace/comment region. +local M = {} + +function M.parse(code, filename) + local meta = { runtimes = {}, skip_presets = {}, skip = false } + filename = filename or "" + local function directive(line, line_number) + line = line:gsub("^%s*%-%-%s*", "") + local body = line:match("^%s*@(.*)$") + if not body then return end + local key, value = body:match("^([%w_%-]+)%s*(.-)%s*$") + local function invalid(message) + error(filename .. ":" .. line_number .. ": " .. message, 0) + end + if not key then invalid("malformed metadata directive") end + key = key:gsub("_", "-") + if key == "skip" or key == "luau-only" then + if value ~= "" then invalid("@" .. key .. " takes no arguments") end + if key == "skip" then meta.skip = true else meta.runtimes.luau = true end + elseif key == "runtime" or key == "skip-preset" then + if value == "" then invalid("@" .. key .. " requires an argument") end + for token in value:gmatch("%S+") do + if key == "runtime" then + token = token:lower() + if token == "lua51" then token = "lua5.1" end + if token ~= "lua5.1" and token ~= "luau" and token ~= "luajit" then + invalid("unknown runtime " .. token) + end + meta.runtimes[token] = true + else + meta.skip_presets[token] = true + end + end + else + invalid("unknown metadata directive @" .. key) + end + end + + local pos, line_number = 1, 1 + local function consume(last, inspect) + local chunk = code:sub(pos, last) + if inspect then + local n = line_number + for line in (chunk .. "\n"):gmatch("(.-)\n") do + directive(line:gsub("\r$", ""), n) + n = n + 1 + end + end + local _, newlines = chunk:gsub("\n", "") + line_number = line_number + newlines + pos = last + 1 + end + if code:sub(1, 1) == "#" then consume((code:find("\n", 1, true) or (#code + 1)) - 1) end + while pos <= #code do + local whitespace = code:sub(pos):match("^%s+") + if whitespace then + consume(pos + #whitespace - 1) + elseif code:sub(pos, pos + 1) == "--" then + local opener, equals = code:sub(pos + 2):match("^(%[(=*)%[)") + if opener then + consume(pos + 1 + #opener) + local close = "]" .. equals .. "]" + local last = code:find(close, pos, true) + if not last then error(filename .. ":" .. line_number .. ": unterminated header comment", 0) end + consume(last - 1, true) + consume(last + #close - 1) + else + consume((code:find("\n", pos, true) or (#code + 1)) - 1, true) + end + else + break + end + end + return meta +end + +return M diff --git a/scripts/test/obfuscate.lua b/scripts/test/obfuscate.lua new file mode 100644 index 00000000..f3731492 --- /dev/null +++ b/scripts/test/obfuscate.lua @@ -0,0 +1,25 @@ +-- Run in a separate process so parser/compiler hangs cannot stop the suite. +local lib = require("scripts.test.lib") +local Prometheus = require("src.prometheus") +local presets = require("src.presets") +local config_path, preset, seed, input, output = unpack(arg) +local config +if config_path ~= "" then + local loaded = assert(loadfile(config_path))() + config = type(loaded[1]) == "table" and loaded[tonumber(preset)] or loaded +else + config = assert(presets[preset]) +end +local cfg = {} +for k, v in pairs(config) do cfg[k] = v end +cfg.Seed = assert(tonumber(seed)) +cfg.Steps = {} +for _, step in ipairs(config.Steps) do + if step.Name ~= "AntiTamper" then cfg.Steps[#cfg.Steps + 1] = step end +end +Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error +local pipeline = Prometheus.Pipeline:fromConfig(cfg) +pipeline:setNameGenerator("MangledShuffled") +local code = lib.read(input) +if code:sub(1, 1) == "#" then code = code:gsub("^[^\n]*", "", 1) end +lib.write(output, pipeline:apply(code, input)) diff --git a/scripts/test/prepare-upstream.py b/scripts/test/prepare-upstream.py new file mode 100644 index 00000000..08be11e4 --- /dev/null +++ b/scripts/test/prepare-upstream.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +"""Verify untouched upstream sources and apply checked patches to a fresh copy.""" +import hashlib +from pathlib import Path +import shutil +import subprocess +import sys + +root = Path(__file__).resolve().parents[2] / "tests/upstream/lua51" +vendor = root / "vendor" +expected = {} +for line in (root / "SHA256SUMS").read_text().splitlines(): + digest, name = line.split(" ", 1) + expected[name] = digest +actual = {p.relative_to(vendor).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest() + for p in vendor.rglob("*") if p.is_file()} +if actual != expected: + raise SystemExit("Vendored Lua 5.1 files differ from SHA256SUMS") +destination = Path(sys.argv[1]) +shutil.copytree(vendor, destination) # Refuse to overwrite any existing copy. +for patch in sorted((root / "patches").glob("*.patch")): + subprocess.run(["patch", "--batch", "--fuzz=0", "-p1", "-i", str(patch)], + cwd=destination, check=True, stdout=subprocess.DEVNULL) +print("Verified upstream checksums and applied compatibility patches") diff --git a/scripts/test/upstream-case.lua b/scripts/test/upstream-case.lua new file mode 100644 index 00000000..52280e61 --- /dev/null +++ b/scripts/test/upstream-case.lua @@ -0,0 +1,27 @@ +-- Preserve references: upstream cases deliberately modify environments/globals. +local check, load, globals = assert, loadfile, _G +local stdout, write, flush = io.stdout, io.stdout.write, io.stdout.flush +local wrap = coroutine.wrap +local file, protocol = arg[1], arg[2] +math.randomseed(0) +check(os.setlocale("C")) +collectgarbage("setstepmul", 180) +collectgarbage("setpause", 190) +package.path = "?;./?.lua;" .. package.path +T = nil +Message = function(message) print(message) end +local chunk = check(load(file)) +if protocol == "big" then + local co = wrap(chunk) + check(co() == "b", "big.lua must yield b") + check(co() == "a", "big.lua must return a") +elseif protocol == "calls" then + local result = chunk() + check(result and result == globals.deep, "calls.lua must return deep") +elseif protocol ~= "none" then + check(chunk() == tonumber(protocol), "unexpected upstream return value") +else + chunk() +end +write(stdout, "\nPROMETHEUS_UPSTREAM_COMPLETE\n") +flush(stdout) diff --git a/tests/harness/metadata.lua b/tests/harness/metadata.lua new file mode 100644 index 00000000..1ff99c60 --- /dev/null +++ b/tests/harness/metadata.lua @@ -0,0 +1,27 @@ +local parse = require("scripts.test.metadata").parse +local function test(header, expected) + local actual = parse(header, "fixture.lua") + assert(actual.skip == (expected.skip or false)) + for _, field in ipairs({"runtimes", "skip_presets"}) do + for key in pairs(actual[field]) do assert((expected[field] or {})[key], field .. ": unexpected " .. key) end + for key in pairs(expected[field] or {}) do assert(actual[field][key], field .. ": missing " .. key) end + end +end +test("-- @skip\nerror('unused')", {skip = true}) +test("#!/usr/bin/lua\n\n-- License\n-- @runtime LUA51 luau\n\n-- @skip-preset Weak Strong\nprint(1)", + {runtimes = {["lua5.1"] = true, luau = true}, skip_presets = {Weak = true, Strong = true}}) +test("--[=[\nlicense\n@runtime lua5.1\n-- @skip_preset Minify\n]=] -- @skip-preset Medium\nprint(1)", + {runtimes = {["lua5.1"] = true}, skip_presets = {Minify = true, Medium = true}}) +test("--[[ @luau_only ]]\n-- @skip_preset weak\n", {runtimes = {luau = true}, skip_presets = {weak = true}}) +test("-- @luau-only\r\n", {runtimes = {luau = true}}) +test("-- @runtime luajit", {runtimes = {luajit = true}}) +test("-- Ordinary comment\nprint('-- @skip')\n-- @skip", {}) +test("--[[ license ]] print(1) -- @skip", {}) +test("print(1)\n-- @unknown", {}) +for _, directive in ipairs({"@unknown", "@runtime", "@runtime lua54", "@skip-preset", "@skip extra", "@luau-only extra", "@"}) do + local ok, err = pcall(parse, "-- " .. directive .. "\n", "bad.lua") + assert(not ok and err:find("bad.lua:1:", 1, true), "expected diagnostic for " .. directive) +end +local ok = pcall(parse, "--[[ unterminated", "bad.lua") +assert(not ok) +print("Metadata regression checks passed") diff --git a/tests/harness/runner.py b/tests/harness/runner.py new file mode 100644 index 00000000..7052ac6b --- /dev/null +++ b/tests/harness/runner.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Exercise subprocess status, selection, continuation, and upstream preparation.""" +import csv +import hashlib +from pathlib import Path +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[2] + +def lua(script, *args): + return subprocess.run(["lua5.1", "-e", script, *map(str, args)], cwd=root, + text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60) + +with tempfile.TemporaryDirectory(prefix="prometheus-harness-") as temporary: + temporary = Path(temporary) + # The stdout text cannot substitute for a successful exit status. + status = lua("local l=require('scripts.test.lib'); local out,code=l.run(\"lua5.1 -e 'print(123); os.exit(7)'\",'.',5," + + repr(str(temporary / "status.log")) + "); assert(code==7 and out:find('123'))") + assert status.returncode == 0, status.stdout + timeout = lua("local l=require('scripts.test.lib'); local _,code=l.run(\"lua5.1 -e 'while true do end'\",'.',1," + + repr(str(temporary / "timeout.log")) + "); assert(code==124)") + assert timeout.returncode == 0, timeout.stdout + fixtures = temporary / "fixtures" + fixtures.mkdir() + (fixtures / "a-error.lua").write_text("print('output before error'); error('expected harness failure')\n") + (fixtures / "b-timeout.lua").write_text("while true do end\n") + (fixtures / "c-filter.lua").write_text("-- license\n-- @runtime lua51\n-- @skip_preset BrokenA\nprint('ok')\n") + (fixtures / "d-skipped.lua").write_text("--[[ license ]]\n-- @skip\nerror('must not run')\n") + (fixtures / "e-no-runtime.lua").write_text("-- @runtime luajit\nerror('must not run')\n") + (fixtures / "f-pass.lua").write_text("print('ok')\n") + config = temporary / "presets.lua" + config.write_text("return { {Name='BrokenA',LuaVersion='Lua51',Steps={{Name='UnknownHarnessStep'}}}, " + "{Name='BrokenB',LuaVersion='Lua51',Steps={{Name='UnknownHarnessStep'}}} }\n") + run = subprocess.run(["lua5.1", "docker-test-runner.lua", "--suite=existing", "--runtime=lua51", + "--iterations=2", "--timeout=1", "--seed=42", f"--test-dir={fixtures}", + f"--config={config}", f"--artifacts={temporary / 'artifacts'}"], + cwd=root, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60) + assert run.returncode == 1, run.stdout + report = next((temporary / "artifacts").glob("run-*/results.tsv")) + with report.open() as stream: + rows = list(csv.DictReader(stream, delimiter="\t")) + baselines = [r for r in rows if r["phase"] == "baseline"] + assert len(baselines) == 4, rows + assert [r["exit_code"] for r in baselines[:2]] == ["1", "124"], rows + attempts = [r for r in rows if r["phase"] == "transformed"] + assert len(attempts) == 6, rows # Two remaining iterations, then both presets of the next case. + assert {r["seed"] for r in attempts} == {"42", "43"}, rows + assert not any(r["case"] == "c-filter.lua" and r["preset"] == "BrokenA" for r in attempts), rows + assert "[SKIP] d-skipped.lua (@skip)" in run.stdout, run.stdout + assert "[SKIP] e-no-runtime.lua (no matching runtime)" in run.stdout, run.stdout + assert "f-pass.lua" in attempts[-1]["case"], rows + for row in attempts: + artifact = Path(row["artifact"]) + assert (artifact / "input" / row["case"]).is_file(), row + assert (artifact / f"obfuscate-{row['case']}.log").is_file(), row + + # A successful transformed case preserves comparison behavior and drops scratch files. + success = subprocess.run(["lua5.1", "docker-test-runner.lua", "--suite=existing", "--runtime=lua5.1", + "--case=f-pass.lua", "--preset=Minify", "--iterations=1", f"--test-dir={fixtures}", + f"--artifacts={temporary / 'success'}"], cwd=root, text=True, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60) + assert success.returncode == 0, success.stdout + assert not list((temporary / "success").glob("run-*/work/*")) + + adapted = temporary / "adapted" + subprocess.run(["python3", "scripts/test/prepare-upstream.py", str(adapted)], cwd=root, check=True, + stdout=subprocess.DEVNULL) + again = temporary / "adapted-again" + subprocess.run(["python3", "scripts/test/prepare-upstream.py", str(again)], cwd=root, check=True, + stdout=subprocess.DEVNULL) + def digests(directory): + return {p.relative_to(directory): hashlib.sha256(p.read_bytes()).hexdigest() + for p in directory.rglob("*") if p.is_file()} + assert digests(adapted) == digests(again) + + # Support files are transformed, and upstream scripts run from the generated copy. + support = subprocess.run(["lua5.1", "docker-test-runner.lua", "--suite=upstream", "--case=lua51/big.lua", + "--preset=Minify", "--iterations=1", f"--artifacts={temporary / 'support'}"], + cwd=root, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60) + assert support.returncode == 0, support.stdout + + # A premature os.exit(0) cannot count as an assertion-suite pass. + (temporary / "premature.lua").write_text("os.exit(0)\n") + marker = subprocess.run(["lua5.1", str(root / "scripts/test/upstream-case.lua"), "premature.lua", "none"], + cwd=temporary, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10) + assert marker.returncode == 0 and "PROMETHEUS_UPSTREAM_COMPLETE" not in marker.stdout + (temporary / "protocol.lua").write_text("return 27\n") + wrong = subprocess.run(["lua5.1", str(root / "scripts/test/upstream-case.lua"), "protocol.lua", "12"], + cwd=temporary, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10) + assert wrong.returncode != 0 and "PROMETHEUS_UPSTREAM_COMPLETE" not in wrong.stdout + +print("Runner regression checks passed") diff --git a/tests/upstream/lua51/LICENSE b/tests/upstream/lua51/LICENSE new file mode 100644 index 00000000..c5ba51ad --- /dev/null +++ b/tests/upstream/lua51/LICENSE @@ -0,0 +1,19 @@ +Copyright © 1994–2026 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tests/upstream/lua51/README.md b/tests/upstream/lua51/README.md new file mode 100644 index 00000000..1fd8c779 --- /dev/null +++ b/tests/upstream/lua51/README.md @@ -0,0 +1,127 @@ +# Official Lua 5.1 tests + +The untouched `vendor/` directory is from the official archive published on +2016-01-18: + +- Source: https://www.lua.org/tests/lua5.1-tests.tar.gz +- Archive SHA-256: `49e4ca6561f82ea605908c5041ab5fad66ed9930fa0686675bd51b02767f18ad` +- License: MIT; see `LICENSE` and https://www.lua.org/license.html. + +`SHA256SUMS` records every vendored file's checksum. Preparation verifies the +complete file inventory and checksums, then applies `patches/portable.patch` +without fuzz to a fresh artifact working directory. No download is needed to +run tests. `manifest.lua` accounts for all 24 upstream Lua scripts: 18 test +cases, one support module, the replaced launcher, and four excluded cases. + +The upstream cases run on Lua 5.1. Existing Prometheus tests continue to run +on both Lua 5.1 and Luau. Each upstream case receives the launcher's locale, +random seed, GC settings, `Message` helper, and module paths. Return-value +checks from `all.lua` and `big.lua`'s coroutine yield/return protocol are +preserved. Cases execute in separate processes and fresh working directories, +so an error or modification of globals cannot prevent later cases from running. + +Upstream assertions and successful process completion are the oracle. Timing, +allocation, temporary-name, and address output is retained in failure logs, +but is not compared against the original's stdout. The completion marker is +emitted only after the case and its return-value checks finish. Original and +transformed executions use equivalent environment settings and file layouts. + +## Exclusions and coverage boundaries + +`exclusions.json` identifies adapted blocks by original file, line range, and +reason. The patch preserves surrounding semantic checks and does not replace +assertions with unconditional successes. Whole-script exclusions are in the +manifest. Debug introspection, exact tail-call stack capacity, original source +names/lines, collector step counts and finalization order, host parser limits, +and native C libraries fall outside this suite's portable scope. + +The native `T` library is absent. Upstream guards therefore also exclude: + +- `nextvar.lua`: native table sizes/layout and `T.int2fb` checks; +- `closure.lua`: native yield/hook checks; +- `checktable.lua`: native table-layout inspection; its portable `stat` helper + is still compiled and transformed when required; +- `locals.lua`: optional `querytab` checks, which the native runtime supplies. + +`all.lua` is retained for provenance and replaced by isolated orchestration. +`main.lua` exercises the host interpreter's CLI and interactive input using +newly generated scripts, so it is recorded as excluded. `api.lua`, `code.lua`, +and `db.lua` require native internals or the debug library. + +All executable vendored case files and their required Lua support files are +transformed. Source strings and files generated *at runtime* still execute +through the host interpreter's `loadstring`, `load`, `loadfile`, or `require`. +Those tests cover their transformed callers and data construction; they do not +establish Prometheus parser/compiler coverage of the generated source. This is +particularly relevant to `verybig.lua` and the literal/syntax-generation tests. + +The historical archive has no `_U` flag. The harness does not enable `_soft`, +which would remove otherwise useful tests. AntiTamper is removed from all +presets, matching the existing suite's convention. + +A failure of supported semantics remains a failure. Do not add exclusions +based solely on a failed transformed execution. + +## Running and reproducing + +From the repository root: + +```sh +bash scripts/run-tests.sh -b -n 10 --ci +bash scripts/run-tests.sh --suite upstream --baseline-only --pass-runners +bash scripts/run-tests.sh --suite upstream --case lua51/closure.lua --preset Vmify --seed 4 -n 1 +``` + +The same selectors are available directly when `lua5.1`, `luau`, Python 3, +`patch`, and GNU coreutils are installed: + +```sh +lua5.1 docker-test-runner.lua --suite=upstream --case=lua51/closure.lua --preset=Vmify --seed=4 --iterations=1 +lua5.1 tests/harness/metadata.lua +python3 tests/harness/runner.py +``` + +Every applicable preset, iteration, and runtime is attempted, even after +failures. Only a failed baseline blocks transformed executions for that +case/runtime. Each subprocess has a 30-second limit (overridable with +`--timeout`), including obfuscation. A process that ignores termination is +forcibly killed after five additional seconds. Seeds default to 1 through 10 +and are recorded per result. + +Artifacts persist under `test-results/run-*` (override with `--artifacts DIR` +in the Docker wrapper or `--artifacts=DIR` directly). Each run contains: + +- `run.log`, the complete runner report, and `results.tsv`, one row per execution; +- `adapted/`, the verified and patched upstream source; +- `work/`, failed jobs with pristine `input/`, `generated/` output, isolated + runtime directories, obfuscation/runtime logs, and `failure-*.txt` metadata. + +Successful scratch jobs are removed. Reproduce a failure using the case, +preset, and seed from `results.tsv`, with one iteration. Docker paths under +`/artifacts/` map to the host artifact directory. To replay saved upstream +output directly, enter its `generated-lua5.1/` directory and run: + +```sh +lua5.1 case-runner.lua closure.lua none +``` + +Use the affected file and its `protocol` from the manifest; for example +`big.lua big`, `attrib.lua 27`, or `calls.lua calls`. A baseline uses the +corresponding `input-lua5.1/` directory. An existing case runs as +`lua5.1 case.lua` or `luau case.lua` from its runtime directory. + +Docker downloads the current prebuilt Luau release, as before. If a corporate +proxy requires additional trusted certificates, supply a trusted CA bundle as +an optional BuildKit secret; certificate verification remains enabled: + +```sh +TEST_CA_BUNDLE=/path/to/trusted-ca-bundle.pem bash scripts/run-tests.sh -b +``` + +## Updating upstream + +Download and verify the official archive checksum before extracting its full +contents to `vendor/`. Regenerate per-file `SHA256SUMS` from those unmodified +files. Review and update patches, exclusions, and the complete manifest; then +verify deterministic preparation, all original baselines, and the full +transformed suite. Keep failing supported tests enabled. diff --git a/tests/upstream/lua51/SHA256SUMS b/tests/upstream/lua51/SHA256SUMS new file mode 100644 index 00000000..d9dd834b --- /dev/null +++ b/tests/upstream/lua51/SHA256SUMS @@ -0,0 +1,32 @@ +17e90e1e3342ddba253719f3863d51c58a3a1e472460f653a35d66b22ab23a9d README +cae825d59d32e9a3c94f6a01b1cf292aeb229b351a55c5f3a810a9cd08d98eab all.lua +68994a85415486217e6852ad30245a4987cbf14cc60381be9c66f886e4af7123 api.lua +c8c7db0c5b7439139a767075102100717890c6004c7c6a3931bba16d99af6eba attrib.lua +44ef5a981a6b885573d4ed3b0cd19b5ea452c6f16d3eabed02927593d53b34c4 big.lua +343fc099d121f0bdc068bd7293a52fd5f32c2f3eff6f1e823c41471bdf7c4639 calls.lua +2f905e55f23b8db43b0c118460ffaa0d0517584c415e84c0e84298b8a0760cf0 checktable.lua +9496056e3b89461e06391a565e2c7e8935abbf8f92fbb658c7714985fbbfb830 closure.lua +25265ced9789e6a4d6d996093d3aa2645765ae026149eba8f2620ad12b8926bb code.lua +da83de439859ca44d6450d9bd76e4abfa9891624207491238b2227683fdfde5f constructs.lua +45bee0f25a06a9b058f3d746c794488ffbf95ab591c5360a477eca5b226b1bc4 db.lua +9d23589cae9ed6bdfa3386fc4c3b474274ae469120a56664e935886c031bbbe5 errors.lua +45f0dfcb17d8930785a1e8a18e0afe209f4be0dba4e6e1df68a4663aaeecb54e etc/ltests.c +d12b37b364fa2d7321f2657267b3ddb7cbdf2c2da7e7e4049c962455540ca9b5 etc/ltests.h +6dd484a0c6f17be699e0fdc577ad0341b60784f3f69666956b8af7d6b778d73c events.lua +d9f714f374fba9b9ed8db8aea88d538f04fb3e9aff689bd6cc3d4036926e8dc7 files.lua +f3cc02f690371fa0cc2ddced1c2378d36a0e1025a7359044a80d42b1e06efe23 gc.lua +f4485757e24896d82147b282b1979ce3dea9418da00c8a94bc559ce15d559dbb libs/lib1.c +00b82132e55c675e3035a5a8ef4c16b83c471d6d8b9e0cb633c98885e52d5895 libs/lib11.c +c9010467eb88b084cfaff902fb21cf6e5bb7701cf4c9905deab8e17eb57dc8ba libs/lib2.c +a4373e51c578ecd3f79759d57be0349a66287980d5352baf150d84e43e9cfc39 libs/lib21.c +b1e39b71cc100dffa516eb12819344c28fa709aa246234c9bc92ea6f983565ec libs/makefile +633b3cb94fe7652c1a1ea99425f161a6df0b64e572d8354739cc9e4e9a0b0f8b literals.lua +7ee8a582fef614fa367852d4426b28485fd4dbeded3fa29bf03890a179792ff0 locals.lua +d0b7b2e5bb806973fc5c853702f346258669cf4bc708f9fb292edfe1d4d4ec65 main.lua +24a862ff81b7099eaa08170d317a5d75a6f7a9debe4328108cc248517f6696ca math.lua +3e81e2ecf908f5576081a691a954b11acaa15dc9eaa6e4c9cd4f53e40baf07b6 nextvar.lua +f7888930a69457635a606fe0597be1dc69114602eaf61293ca510eed9d1971d6 pm.lua +b8f6fefe976dd484a86b0c0d4989af70421005abbb6005567886fb9a40181cb2 sort.lua +c97c706a6120847472aff5020bedaf29411b5177f4cf98d059bfd41eada730f8 strings.lua +afa3796df77bf56574c2e71f8e4f219a6c4b4ba08d914b8110ea60e363a7d9ff vararg.lua +2e647a4285ac2c09be7e9cb10ce83e19a359a2b3c630cb6e62962319c8caec1e verybig.lua diff --git a/tests/upstream/lua51/exclusions.json b/tests/upstream/lua51/exclusions.json new file mode 100644 index 00000000..a3297ce2 --- /dev/null +++ b/tests/upstream/lua51/exclusions.json @@ -0,0 +1,116 @@ +[ + { + "file": "attrib.lua", + "lines": "10-10", + "reason": "Debug library" + }, + { + "file": "attrib.lua", + "lines": "176-211", + "reason": "Native C-library integration" + }, + { + "file": "big.lua", + "lines": "3-14", + "reason": "String-size overflow depends on host allocation limits" + }, + { + "file": "calls.lua", + "lines": "104-104", + "reason": "Tail-call stack capacity" + }, + { + "file": "calls.lua", + "lines": "107-107", + "reason": "Tail-call stack capacity" + }, + { + "file": "calls.lua", + "lines": "241-241", + "reason": "Debug source identity" + }, + { + "file": "calls.lua", + "lines": "270-287", + "reason": "Debug upvalue names and mutation after bytecode loading" + }, + { + "file": "closure.lua", + "lines": "276-277", + "reason": "Debug stack/source lines" + }, + { + "file": "closure.lua", + "lines": "409-421", + "reason": "Debug coroutine environment access" + }, + { + "file": "constructs.lua", + "lines": "189-189", + "reason": "Debug function names" + }, + { + "file": "errors.lua", + "lines": "141-169", + "reason": "Debug traceback and original stack-overflow lines" + }, + { + "file": "errors.lua", + "lines": "210-249", + "reason": "Host parser limits and exact diagnostics" + }, + { + "file": "events.lua", + "lines": "328-357", + "reason": "Debug mutation of primitive-type metatables" + }, + { + "file": "gc.lua", + "lines": "88-130", + "reason": "Exact allocation thresholds and collector step counts" + }, + { + "file": "gc.lua", + "lines": "203-257", + "reason": "Exact userdata finalization order and lifetime" + }, + { + "file": "gc.lua", + "lines": "287-311", + "reason": "Shutdown finalization order and errors" + }, + { + "file": "literals.lua", + "lines": "112-112", + "reason": "Debug line counting in generated source" + }, + { + "file": "literals.lua", + "lines": "117-117", + "reason": "Debug line-count assertion; preserve execution and string assertions" + }, + { + "file": "nextvar.lua", + "lines": "15-134", + "reason": "Native table sizes/layout; guarded by T", + "condition": "native runtime helper unavailable" + }, + { + "file": "closure.lua", + "lines": "365-398", + "reason": "Native yield/hook tests; skipped when T is absent", + "condition": "native runtime helper unavailable" + }, + { + "file": "checktable.lua", + "lines": "10-77", + "reason": "Native table-layout functions; portable stat fallback returns before these", + "condition": "native runtime helper unavailable" + }, + { + "file": "locals.lua", + "lines": "109-123", + "reason": "Native querytab checks; querytab is absent", + "condition": "native runtime helper unavailable" + } +] diff --git a/tests/upstream/lua51/manifest.lua b/tests/upstream/lua51/manifest.lua new file mode 100644 index 00000000..e9982db7 --- /dev/null +++ b/tests/upstream/lua51/manifest.lua @@ -0,0 +1,27 @@ +-- Every official Lua script must have exactly one entry. +return { + { file = "all.lua", role = "launcher", protocol = "none", reason = "Replaced by isolated case orchestration; removes dump/undump, debug cleanup, and timing output" }, + { file = "api.lua", role = "excluded", protocol = "none", reason = "Requires the native testC runtime-internals library" }, + { file = "attrib.lua", role = "case", protocol = "27", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "big.lua", role = "case", supports = { "checktable.lua" }, protocol = "big", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "calls.lua", role = "case", protocol = "calls", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "checktable.lua", role = "support", protocol = "none", reason = "Native table-layout branch is guarded by T; portable stat helper is transformed" }, + { file = "closure.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "code.lua", role = "excluded", protocol = "none", reason = "Requires testC to inspect exact host bytecode" }, + { file = "constructs.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "db.lua", role = "excluded", protocol = "none", reason = "Debug library introspection and hooks" }, + { file = "errors.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "events.lua", role = "case", protocol = "12", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "files.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "gc.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "literals.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "locals.lua", role = "case", protocol = "5", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "main.lua", role = "excluded", protocol = "none", reason = "Host lua.c command-line and interactive interpreter tests execute generated untransformed scripts" }, + { file = "math.lua", role = "case", supports = { "checktable.lua" }, protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "nextvar.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "pm.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "sort.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "strings.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "vararg.lua", role = "case", protocol = "none", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, + { file = "verybig.lua", role = "case", supports = { "checktable.lua" }, protocol = "10", reason = "Portable assertions; see exclusions.json and patches/portable.patch" }, +} diff --git a/tests/upstream/lua51/patches/portable.patch b/tests/upstream/lua51/patches/portable.patch new file mode 100644 index 00000000..3e04a69c --- /dev/null +++ b/tests/upstream/lua51/patches/portable.patch @@ -0,0 +1,450 @@ +--- a/attrib.lua ++++ b/attrib.lua +@@ -7,7 +7,7 @@ + assert(require"table" == table) + assert(require"io" == io) + assert(require"os" == os) +-assert(require"debug" == debug) ++-- Prometheus: debug-library assertions excluded. + assert(require"coroutine" == coroutine) + + assert(type(package.path) == "string") +@@ -173,41 +173,7 @@ + assert(not pcall(module, "math.sin")) + + +--- testing C libraries +- +- +-local p = "" -- On Mac OS X, redefine this to "_" +- +--- assert(loadlib == package.loadlib) -- only for compatibility +-local f, err, when = package.loadlib("libs/lib1.so", p.."luaopen_lib1") +-if not f then +- (Message or print)('\a\n >>> cannot load dynamic library <<<\n\a') +- print(err, when) +-else +- f() -- open library +- assert(require("lib1") == lib1) +- collectgarbage() +- assert(lib1.id("x") == "x") +- f = assert(package.loadlib("libs/lib1.so", p.."anotherfunc")) +- assert(f(10, 20) == "1020\n") +- f, err, when = package.loadlib("libs/lib1.so", p.."xuxu") +- assert(not f and type(err) == "string" and when == "init") +- package.cpath = "libs/?.so" +- require"lib2" +- assert(lib2.id("x") == "x") +- local fs = require"lib1.sub" +- assert(fs == lib1.sub and next(lib1.sub) == nil) +- module("lib2", package.seeall) +- f = require"-lib2" +- assert(f.id("x") == "x" and _M == f and _NAME == "lib2") +- module("lib1.sub", package.seeall) +- assert(_M == fs) +- setfenv(1, _G) +- +-end +-f, err, when = package.loadlib("donotexist", p.."xuxu") +-assert(not f and type(err) == "string" and (when == "open" or when == "absent")) +- ++-- Prometheus exclusion: Native C-library integration + + -- testing preload + +--- a/big.lua ++++ b/big.lua +@@ -1,16 +1,6 @@ + print "testing string length overflow" + +-local longs = string.rep("\0", 2^25) +-local function catter (i) +- return assert(loadstring( +- string.format("return function(a) return a%s end", +- string.rep("..a", i-1))))() +-end +-rep129 = catter(129) +-local a, b = pcall(rep129, longs) +-assert(not a and string.find(b, "overflow")) +-print('+') +- ++-- Prometheus exclusion: String-size overflow depends on host allocation limits + + require "checktable" + +--- a/calls.lua ++++ b/calls.lua +@@ -101,10 +101,10 @@ + + -- testing tail call + function deep (n) if n>0 then return deep(n-1) else return 101 end end +-assert(deep(30000) == 101) ++-- Prometheus: original tail-call stack capacity excluded. + a = {} + function a:deep (n) if n>0 then return self:deep(n-1) else return 101 end end +-assert(a:deep(30000) == 101) ++-- Prometheus: original tail-call stack capacity excluded. + + print('+') + +@@ -238,7 +238,7 @@ + + a = assert(load(read1(x), "modname")) + assert(a() == "\0" and _G.x == 33) +-assert(debug.getinfo(a).source == "modname") ++-- Prometheus: original source identity excluded. + + x = string.dump(loadstring("x = 1; return x")) + i = 0 +@@ -267,23 +267,7 @@ + assert(a()(2)(3)(10) == 15) + + +--- test for dump/undump with upvalues +-local a, b = 20, 30 +-x = loadstring(string.dump(function (x) +- if x == "set" then a = 10+b; b = b+1 else +- return a +- end +-end)) +-assert(x() == nil) +-assert(debug.setupvalue(x, 1, "hi") == "a") +-assert(x() == "hi") +-assert(debug.setupvalue(x, 2, 13) == "b") +-assert(not debug.setupvalue(x, 3, 10)) -- only 2 upvalues +-x("set") +-assert(x() == 23) +-x("set") +-assert(x() == 24) +- ++-- Prometheus exclusion: Debug upvalue names and mutation after bytecode loading + + -- test for bug in parameter adjustment + assert((function () return nil end)(4) == nil) +--- a/closure.lua ++++ b/closure.lua +@@ -273,8 +273,7 @@ + + -- errors in coroutines + function foo () +- assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1) +- assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined) ++ -- Prometheus: original coroutine stack lines excluded. + coroutine.yield(3) + error(foo) + end +@@ -406,17 +405,6 @@ + _X() + + +--- coroutine environments +-co = coroutine.create(function () +- coroutine.yield(getfenv(0)) +- return loadstring("return a")() +- end) +- +-a = {a = 15} +-debug.setfenv(co, a) +-assert(debug.getfenv(co) == a) +-assert(select(2, coroutine.resume(co)) == a) +-assert(select(2, coroutine.resume(co)) == a.a) +- ++-- Prometheus exclusion: Debug coroutine environment access + + print'OK' +--- a/constructs.lua ++++ b/constructs.lua +@@ -186,7 +186,7 @@ + end + + function F(a) +- assert(debug.getinfo(1, "n").name == 'F') ++ -- Prometheus: original function name excluded. + return a,2,3 + end + +--- a/errors.lua ++++ b/errors.lua +@@ -138,34 +138,7 @@ + + lineerror = nil + +-C = 0 +-local l = debug.getinfo(1, "l").currentline; function y () C=C+1; y() end +- +-local function checkstackmessage (m) +- return (string.find(m, "^.-:%d+: stack overflow")) +-end +-assert(checkstackmessage(doit('y()'))) +-assert(checkstackmessage(doit('y()'))) +-assert(checkstackmessage(doit('y()'))) +--- teste de linhas em erro +-C = 0 +-local l1 +-local function g() +- l1 = debug.getinfo(1, "l").currentline; y() +-end +-local _, stackmsg = xpcall(g, debug.traceback) +-local stack = {} +-for line in string.gmatch(stackmsg, "[^\n]*") do +- local curr = string.match(line, ":(%d+):") +- if curr then table.insert(stack, tonumber(curr)) end +-end +-local i=1 +-while stack[i] ~= l1 do +- assert(stack[i] == l) +- i = i+1 +-end +-assert(i > 15) +- ++-- Prometheus exclusion: Debug traceback and original stack-overflow lines + + -- error in error handling + local res, msg = xpcall(error, error) +@@ -207,44 +180,6 @@ + end + + +--- testing syntax limits +-local function testrep (init, rep) +- local s = "local a; "..init .. string.rep(rep, 400) +- local a,b = loadstring(s) +- assert(not a and string.find(b, "syntax levels")) +-end +-testrep("a=", "{") +-testrep("a=", "(") +-testrep("", "a(") +-testrep("", "do ") +-testrep("", "while a do ") +-testrep("", "if a then else ") +-testrep("", "function foo () ") +-testrep("a=", "a..") +-testrep("a=", "a^") +- +- +--- testing other limits +--- upvalues +-local s = "function foo ()\n local " +-for j = 1,70 do +- s = s.."a"..j..", " +-end +-s = s.."b\n" +-for j = 1,70 do +- s = s.."function foo"..j.." ()\n a"..j.."=3\n" +-end +-local a,b = loadstring(s) +-assert(string.find(b, "line 3")) +- +--- local variables +-s = "\nfunction foo ()\n local " +-for j = 1,300 do +- s = s.."a"..j..", " +-end +-s = s.."b\n" +-local a,b = loadstring(s) +-assert(string.find(b, "line 2")) +- ++-- Prometheus exclusion: Host parser limits and exact diagnostics + + print('OK') +--- a/events.lua ++++ b/events.lua +@@ -325,35 +325,7 @@ + + print '+' + +--- testing metatables for basic types +-mt = {} +-debug.setmetatable(10, mt) +-assert(getmetatable(-2) == mt) +-mt.__index = function (a,b) return a+b end +-assert((10)[3] == 13) +-assert((10)["3"] == 13) +-debug.setmetatable(23, nil) +-assert(getmetatable(-2) == nil) +- +-debug.setmetatable(true, mt) +-assert(getmetatable(false) == mt) +-mt.__index = function (a,b) return a or b end +-assert((true)[false] == true) +-assert((false)[false] == false) +-debug.setmetatable(false, nil) +-assert(getmetatable(true) == nil) +- +-debug.setmetatable(nil, mt) +-assert(getmetatable(nil) == mt) +-mt.__add = function (a,b) return (a or 0) + (b or 0) end +-assert(10 + nil == 10) +-assert(nil + 23 == 23) +-assert(nil + nil == 0) +-debug.setmetatable(nil, nil) +-assert(getmetatable(nil) == nil) +- +-debug.setmetatable(nil, {}) +- ++-- Prometheus exclusion: Debug mutation of primitive-type metatables + + print 'OK' + +--- a/gc.lua ++++ b/gc.lua +@@ -85,48 +85,7 @@ + assert(_G["while"] == 234) + + +-local bytes = gcinfo() +-while 1 do +- local nbytes = gcinfo() +- if nbytes < bytes then break end -- run until gc +- bytes = nbytes +- a = {} +-end +- +- +-local function dosteps (siz) +- collectgarbage() +- collectgarbage"stop" +- local a = {} +- for i=1,100 do a[i] = {{}}; local b = {} end +- local x = gcinfo() +- local i = 0 +- repeat +- i = i+1 +- until collectgarbage("step", siz) +- assert(gcinfo() < x) +- return i +-end +- +-assert(dosteps(0) > 10) +-assert(dosteps(6) < dosteps(2)) +-assert(dosteps(10000) == 1) +-assert(collectgarbage("step", 1000000) == true) +-assert(collectgarbage("step", 1000000)) +- +- +-do +- local x = gcinfo() +- collectgarbage() +- collectgarbage"stop" +- repeat +- local a = {} +- until gcinfo() > 1000 +- collectgarbage"restart" +- repeat +- local a = {} +- until gcinfo() < 1000 +-end ++-- Prometheus exclusion: Exact allocation thresholds and collector step counts + + lim = 15 + a = {} +@@ -200,60 +159,7 @@ + assert(next(a) == string.rep('$', 11)) + + +--- testing userdata +-collectgarbage("stop") -- stop collection +-local u = newproxy(true) +-local s = 0 +-local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'}) +-for i=1,10 do a[newproxy(u)] = i end +-for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end +-local a1 = {}; for k,v in pairs(a) do a1[k] = v end +-for k,v in pairs(a1) do a[v] = k end +-for i =1,10 do assert(a[i]) end +-getmetatable(u).a = a1 +-getmetatable(u).u = u +-do +- local u = u +- getmetatable(u).__gc = function (o) +- assert(a[o] == 10-s) +- assert(a[10-s] == nil) -- udata already removed from weak table +- assert(getmetatable(o) == getmetatable(u)) +- assert(getmetatable(o).a[o] == 10-s) +- s=s+1 +- end +-end +-a1, u = nil +-assert(next(a) ~= nil) +-collectgarbage() +-assert(s==11) +-collectgarbage() +-assert(next(a) == nil) -- finalized keys are removed in two cycles +- +- +--- __gc x weak tables +-local u = newproxy(true) +-setmetatable(getmetatable(u), {__mode = "v"}) +-getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen +-collectgarbage() +- +-local u = newproxy(true) +-local m = getmetatable(u) +-m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"}); +-m.__gc = function (o) +- assert(next(getmetatable(o).x) == nil) +- m = 10 +-end +-u, m = nil +-collectgarbage() +-assert(m==10) +- +- +--- errors during collection +-u = newproxy(true) +-getmetatable(u).__gc = function () error "!!!" end +-u = nil +-assert(not pcall(collectgarbage)) +- ++-- Prometheus exclusion: Exact userdata finalization order and lifetime + + if not rawget(_G, "_soft") then + print("deep structures") +@@ -284,29 +190,6 @@ + + + +--- create a userdata to be collected when state is closed +-do +- local newproxy,assert,type,print,getmetatable = +- newproxy,assert,type,print,getmetatable +- local u = newproxy(true) +- local tt = getmetatable(u) +- ___Glob = {u} -- avoid udata being collected before program end +- tt.__gc = function (o) +- assert(getmetatable(o) == tt) +- -- create new objects during GC +- local a = 'xuxu'..(10+3)..'joao', {} +- ___Glob = o -- ressurect object! +- newproxy(o) -- creates a new one with same metatable +- print(">>> closing state " .. "<<<\n") +- end +-end +- +--- create several udata to raise errors when collected while closing state +-do +- local u = newproxy(true) +- getmetatable(u).__gc = function (o) return o + 1 end +- table.insert(___Glob, u) -- preserve udata until the end +- for i = 1,10 do table.insert(___Glob, newproxy(u)) end +-end ++-- Prometheus exclusion: Shutdown finalization order and errors + + print('OK') +--- a/literals.lua ++++ b/literals.lua +@@ -109,12 +109,12 @@ + y = "\ + hello\r\n\ + " +-return debug.getinfo(1).currentline ++-- Prometheus: retain line-ending syntax checks without debug line counting. + ]] + + for _, n in pairs{"\n", "\r", "\n\r", "\r\n"} do + local prog, nn = string.gsub(prog, "\n", n) +- assert(dostring(prog) == nn) ++ dostring(prog) + assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n") + end + diff --git a/tests/upstream/lua51/vendor/README b/tests/upstream/lua51/vendor/README new file mode 100644 index 00000000..e2d4b285 --- /dev/null +++ b/tests/upstream/lua51/vendor/README @@ -0,0 +1,41 @@ +This tarball contains the official test scripts for Lua 5.1. +Unlike Lua itself, these tests do not aim portability, small footprint, +or easy of use. (Their main goal is to try to crash Lua.) They are not +intended for general use. You are wellcome to use them, but expect to +have to "dirt your hands". + +The tarball should expand in the following contents: + - several .lua scripts with the tests + - a main "all.lua" Lua script that invokes all the other scripts + - a subdirectory "libs" with an empty subdirectory "libs/P1", + to be used by the scripts + - a subdirectory "etc" with some extra files + +To run the tests, do as follows: + +- go to the test directory + +- set LUA_PATH to "?;./?.lua" (or, better yet, set LUA_PATH to "./?.lua;;" + and LUA_INIT to "package.path = '?;'..package.path") + +- run "lua all.lua" + + +-------------------------------------------- +Internal tests +-------------------------------------------- + +Some tests need a special library, "testC", that gives access to +several internal structures in Lua. +This library is only available when Lua is compiled in debug mode. +The scripts automatically detect its absence and skip those tests. + +If you want to run these tests, move etc/ltests.c and etc/ltests.h to +the directory with the source Lua files, and recompile Lua with +the option -DLUA_USER_H='"ltests.h"' (or its equivalent to define +LUA_USER_H as the string "ltests.h", including the quotes). This +option not only adds the testC library, but it adds several other +internal tests as well. After the recompilation, run the tests +as before. + + diff --git a/tests/upstream/lua51/vendor/all.lua b/tests/upstream/lua51/vendor/all.lua new file mode 100644 index 00000000..8c4aface --- /dev/null +++ b/tests/upstream/lua51/vendor/all.lua @@ -0,0 +1,137 @@ +#!../lua + +math.randomseed(0) + +collectgarbage("setstepmul", 180) +collectgarbage("setpause", 190) + + +--[=[ + example of a long [comment], + [[spanning several [lines]]] + +]=] + +print("current path:\n " .. string.gsub(package.path, ";", "\n ")) + + +local msgs = {} +function Message (m) + print(m) + msgs[#msgs+1] = string.sub(m, 3, -3) +end + + +local c = os.clock() + +assert(os.setlocale"C") + +local T,print,gcinfo,format,write,assert,type = + T,print,gcinfo,string.format,io.write,assert,type + +local function formatmem (m) + if m < 1024 then return m + else + m = m/1024 - m/1024%1 + if m < 1024 then return m.."K" + else + m = m/1024 - m/1024%1 + return m.."M" + end + end +end + +local showmem = function () + if not T then + print(format(" ---- total memory: %s ----\n", formatmem(gcinfo()))) + else + T.checkmemory() + local a,b,c = T.totalmem() + local d,e = gcinfo() + print(format( + "\n ---- total memory: %s (%dK), max use: %s, blocks: %d\n", + formatmem(a), d, formatmem(c), b)) + end +end + + +-- +-- redefine dofile to run files through dump/undump +-- +dofile = function (n) + showmem() + local f = assert(loadfile(n)) + local b = string.dump(f) + f = assert(loadstring(b)) + return f() +end + +dofile('main.lua') + +do + local u = newproxy(true) + local newproxy, stderr = newproxy, io.stderr + getmetatable(u).__gc = function (o) + stderr:write'.' + newproxy(o) + end +end + +local f = assert(loadfile('gc.lua')) +f() +dofile('db.lua') +assert(dofile('calls.lua') == deep and deep) +dofile('strings.lua') +dofile('literals.lua') +assert(dofile('attrib.lua') == 27) +assert(dofile('locals.lua') == 5) +dofile('constructs.lua') +dofile('code.lua') +do + local f = coroutine.wrap(assert(loadfile('big.lua'))) + assert(f() == 'b') + assert(f() == 'a') +end +dofile('nextvar.lua') +dofile('pm.lua') +dofile('api.lua') +assert(dofile('events.lua') == 12) +dofile('vararg.lua') +dofile('closure.lua') +dofile('errors.lua') +dofile('math.lua') +dofile('sort.lua') +assert(dofile('verybig.lua') == 10); collectgarbage() +dofile('files.lua') + +if #msgs > 0 then + print("\ntests not performed:") + for i=1,#msgs do + print(msgs[i]) + end + print() +end + +print("final OK !!!") +print('cleaning all!!!!') + +debug.sethook(function (a) assert(type(a) == 'string') end, "cr") + +local _G, collectgarbage, showmem, print, format, clock = + _G, collectgarbage, showmem, print, format, os.clock + +local a={} +for n in pairs(_G) do a[n] = 1 end +a.tostring = nil +a.___Glob = nil +for n in pairs(a) do _G[n] = nil end + +a = nil +collectgarbage() +collectgarbage() +collectgarbage() +collectgarbage() +collectgarbage() +collectgarbage();showmem() + +print(format("\n\ntotal time: %.2f\n", clock()-c)) diff --git a/tests/upstream/lua51/vendor/api.lua b/tests/upstream/lua51/vendor/api.lua new file mode 100644 index 00000000..c2d262f0 --- /dev/null +++ b/tests/upstream/lua51/vendor/api.lua @@ -0,0 +1,711 @@ + +if T==nil then + (Message or print)('\a\n >>> testC not active: skipping API tests <<<\n\a') + return +end + + + +function tcheck (t1, t2) + table.remove(t1, 1) -- remove code + assert(table.getn(t1) == table.getn(t2)) + for i=1,table.getn(t1) do assert(t1[i] == t2[i]) end +end + +function pack(...) return arg end + + +print('testing C API') + +-- testing allignment +a = T.d2s(12458954321123) +assert(string.len(a) == 8) -- sizeof(double) +assert(T.s2d(a) == 12458954321123) + +a,b,c = T.testC("pushnum 1; pushnum 2; pushnum 3; return 2") +assert(a == 2 and b == 3 and not c) + +-- test that all trues are equal +a,b,c = T.testC("pushbool 1; pushbool 2; pushbool 0; return 3") +assert(a == b and a == true and c == false) +a,b,c = T.testC"pushbool 0; pushbool 10; pushnil;\ + tobool -3; tobool -3; tobool -3; return 3" +assert(a==0 and b==1 and c==0) + + +a,b,c = T.testC("gettop; return 2", 10, 20, 30, 40) +assert(a == 40 and b == 5 and not c) + +t = pack(T.testC("settop 5; gettop; return .", 2, 3)) +tcheck(t, {n=4,2,3}) + +t = pack(T.testC("settop 0; settop 15; return 10", 3, 1, 23)) +assert(t.n == 10 and t[1] == nil and t[10] == nil) + +t = pack(T.testC("remove -2; gettop; return .", 2, 3, 4)) +tcheck(t, {n=2,2,4}) + +t = pack(T.testC("insert -1; gettop; return .", 2, 3)) +tcheck(t, {n=2,2,3}) + +t = pack(T.testC("insert 3; gettop; return .", 2, 3, 4, 5)) +tcheck(t, {n=4,2,5,3,4}) + +t = pack(T.testC("replace 2; gettop; return .", 2, 3, 4, 5)) +tcheck(t, {n=3,5,3,4}) + +t = pack(T.testC("replace -2; gettop; return .", 2, 3, 4, 5)) +tcheck(t, {n=3,2,3,5}) + +t = pack(T.testC("remove 3; gettop; return .", 2, 3, 4, 5)) +tcheck(t, {n=3,2,4,5}) + +t = pack(T.testC("insert 3; pushvalue 3; remove 3; pushvalue 2; remove 2; \ + insert 2; pushvalue 1; remove 1; insert 1; \ + insert -2; pushvalue -2; remove -3; gettop; return .", + 2, 3, 4, 5, 10, 40, 90)) +tcheck(t, {n=7,2,3,4,5,10,40,90}) + +t = pack(T.testC("concat 5; gettop; return .", "alo", 2, 3, "joao", 12)) +tcheck(t, {n=1,"alo23joao12"}) + +-- testing MULTRET +t = pack(T.testC("rawcall 2,-1; gettop; return .", + function (a,b) return 1,2,3,4,a,b end, "alo", "joao")) +tcheck(t, {n=6,1,2,3,4,"alo", "joao"}) + +do -- test returning more results than fit in the caller stack + local a = {} + for i=1,1000 do a[i] = true end; a[999] = 10 + local b = T.testC([[call 1 -1; pop 1; tostring -1; return 1]], unpack, a) + assert(b == "10") +end + + +-- testing lessthan +assert(T.testC("lessthan 2 5, return 1", 3, 2, 2, 4, 2, 2)) +assert(T.testC("lessthan 5 2, return 1", 4, 2, 2, 3, 2, 2)) +assert(not T.testC("lessthan 2 -3, return 1", "4", "2", "2", "3", "2", "2")) +assert(not T.testC("lessthan -3 2, return 1", "3", "2", "2", "4", "2", "2")) + +local b = {__lt = function (a,b) return a[1] < b[1] end} +local a1,a3,a4 = setmetatable({1}, b), + setmetatable({3}, b), + setmetatable({4}, b) +assert(T.testC("lessthan 2 5, return 1", a3, 2, 2, a4, 2, 2)) +assert(T.testC("lessthan 5 -6, return 1", a4, 2, 2, a3, 2, 2)) +a,b = T.testC("lessthan 5 -6, return 2", a1, 2, 2, a3, 2, 20) +assert(a == 20 and b == false) + + +-- testing lua_is + +function count (x, n) + n = n or 2 + local prog = [[ + isnumber %d; + isstring %d; + isfunction %d; + iscfunction %d; + istable %d; + isuserdata %d; + isnil %d; + isnull %d; + return 8 + ]] + prog = string.format(prog, n, n, n, n, n, n, n, n) + local a,b,c,d,e,f,g,h = T.testC(prog, x) + return a+b+c+d+e+f+g+(100*h) +end + +assert(count(3) == 2) +assert(count('alo') == 1) +assert(count('32') == 2) +assert(count({}) == 1) +assert(count(print) == 2) +assert(count(function () end) == 1) +assert(count(nil) == 1) +assert(count(io.stdin) == 1) +assert(count(nil, 15) == 100) + +-- testing lua_to... + +function to (s, x, n) + n = n or 2 + return T.testC(string.format("%s %d; return 1", s, n), x) +end + +assert(to("tostring", {}) == nil) +assert(to("tostring", "alo") == "alo") +assert(to("tostring", 12) == "12") +assert(to("tostring", 12, 3) == nil) +assert(to("objsize", {}) == 0) +assert(to("objsize", "alo\0\0a") == 6) +assert(to("objsize", T.newuserdata(0)) == 0) +assert(to("objsize", T.newuserdata(101)) == 101) +assert(to("objsize", 12) == 2) +assert(to("objsize", 12, 3) == 0) +assert(to("tonumber", {}) == 0) +assert(to("tonumber", "12") == 12) +assert(to("tonumber", "s2") == 0) +assert(to("tonumber", 1, 20) == 0) +a = to("tocfunction", math.deg) +assert(a(3) == math.deg(3) and a ~= math.deg) + + +-- testing errors + +a = T.testC([[ + loadstring 2; call 0,1; + pushvalue 3; insert -2; call 1, 1; + call 0, 0; + return 1 +]], "x=150", function (a) assert(a==nil); return 3 end) + +assert(type(a) == 'string' and x == 150) + +function check3(p, ...) + assert(arg.n == 3) + assert(string.find(arg[3], p)) +end +check3(":1:", T.testC("loadstring 2; gettop; return .", "x=")) +check3("cannot read", T.testC("loadfile 2; gettop; return .", ".")) +check3("cannot open xxxx", T.testC("loadfile 2; gettop; return .", "xxxx")) + +-- testing table access + +a = {x=0, y=12} +x, y = T.testC("gettable 2; pushvalue 4; gettable 2; return 2", + a, 3, "y", 4, "x") +assert(x == 0 and y == 12) +T.testC("settable -5", a, 3, 4, "x", 15) +assert(a.x == 15) +a[a] = print +x = T.testC("gettable 2; return 1", a) -- table and key are the same object! +assert(x == print) +T.testC("settable 2", a, "x") -- table and key are the same object! +assert(a[a] == "x") + +b = setmetatable({p = a}, {}) +getmetatable(b).__index = function (t, i) return t.p[i] end +k, x = T.testC("gettable 3, return 2", 4, b, 20, 35, "x") +assert(x == 15 and k == 35) +getmetatable(b).__index = function (t, i) return a[i] end +getmetatable(b).__newindex = function (t, i,v ) a[i] = v end +y = T.testC("insert 2; gettable -5; return 1", 2, 3, 4, "y", b) +assert(y == 12) +k = T.testC("settable -5, return 1", b, 3, 4, "x", 16) +assert(a.x == 16 and k == 4) +a[b] = 'xuxu' +y = T.testC("gettable 2, return 1", b) +assert(y == 'xuxu') +T.testC("settable 2", b, 19) +assert(a[b] == 19) + +-- testing next +a = {} +t = pack(T.testC("next; gettop; return .", a, nil)) +tcheck(t, {n=1,a}) +a = {a=3} +t = pack(T.testC("next; gettop; return .", a, nil)) +tcheck(t, {n=3,a,'a',3}) +t = pack(T.testC("next; pop 1; next; gettop; return .", a, nil)) +tcheck(t, {n=1,a}) + + + +-- testing upvalues + +do + local A = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]] + t, b, c = A([[pushvalue U0; pushvalue U1; pushvalue U2; return 3]]) + assert(b == 10 and c == 20 and type(t) == 'table') + a, b = A([[tostring U3; tonumber U4; return 2]]) + assert(a == nil and b == 0) + A([[pushnum 100; pushnum 200; replace U2; replace U1]]) + b, c = A([[pushvalue U1; pushvalue U2; return 2]]) + assert(b == 100 and c == 200) + A([[replace U2; replace U1]], {x=1}, {x=2}) + b, c = A([[pushvalue U1; pushvalue U2; return 2]]) + assert(b.x == 1 and c.x == 2) + T.checkmemory() +end + +local f = T.testC[[ pushnum 10; pushnum 20; pushcclosure 2; return 1]] +assert(T.upvalue(f, 1) == 10 and + T.upvalue(f, 2) == 20 and + T.upvalue(f, 3) == nil) +T.upvalue(f, 2, "xuxu") +assert(T.upvalue(f, 2) == "xuxu") + + +-- testing environments + +assert(T.testC"pushvalue G; return 1" == _G) +assert(T.testC"pushvalue E; return 1" == _G) +local a = {} +T.testC("replace E; return 1", a) +assert(T.testC"pushvalue G; return 1" == _G) +assert(T.testC"pushvalue E; return 1" == a) +assert(debug.getfenv(T.testC) == a) +assert(debug.getfenv(T.upvalue) == _G) +-- userdata inherit environment +local u = T.testC"newuserdata 0; return 1" +assert(debug.getfenv(u) == a) +-- functions inherit environment +u = T.testC"pushcclosure 0; return 1" +assert(debug.getfenv(u) == a) +debug.setfenv(T.testC, _G) +assert(T.testC"pushvalue E; return 1" == _G) + +local b = newproxy() +assert(debug.getfenv(b) == _G) +assert(debug.setfenv(b, a)) +assert(debug.getfenv(b) == a) + + + +-- testing locks (refs) + +-- reuse of references +local i = T.ref{} +T.unref(i) +assert(T.ref{} == i) + +Arr = {} +Lim = 100 +for i=1,Lim do -- lock many objects + Arr[i] = T.ref({}) +end + +assert(T.ref(nil) == -1 and T.getref(-1) == nil) +T.unref(-1); T.unref(-1) + +for i=1,Lim do -- unlock all them + T.unref(Arr[i]) +end + +function printlocks () + local n = T.testC("gettable R; return 1", "n") + print("n", n) + for i=0,n do + print(i, T.testC("gettable R; return 1", i)) + end +end + + +for i=1,Lim do -- lock many objects + Arr[i] = T.ref({}) +end + +for i=1,Lim,2 do -- unlock half of them + T.unref(Arr[i]) +end + +assert(type(T.getref(Arr[2])) == 'table') + + +assert(T.getref(-1) == nil) + + +a = T.ref({}) + +collectgarbage() + +assert(type(T.getref(a)) == 'table') + + +-- colect in cl the `val' of all collected userdata +tt = {} +cl = {n=0} +A = nil; B = nil +local F +F = function (x) + local udval = T.udataval(x) + table.insert(cl, udval) + local d = T.newuserdata(100) -- cria lixo + d = nil + assert(debug.getmetatable(x).__gc == F) + loadstring("table.insert({}, {})")() -- cria mais lixo + collectgarbage() -- forca coleta de lixo durante coleta! + assert(debug.getmetatable(x).__gc == F) -- coleta anterior nao melou isso? + local dummy = {} -- cria lixo durante coleta + if A ~= nil then + assert(type(A) == "userdata") + assert(T.udataval(A) == B) + debug.getmetatable(A) -- just acess it + end + A = x -- ressucita userdata + B = udval + return 1,2,3 +end +tt.__gc = F + +-- test whether udate collection frees memory in the right time +do + collectgarbage(); + collectgarbage(); + local x = collectgarbage("count"); + local a = T.newuserdata(5001) + assert(T.testC("objsize 2; return 1", a) == 5001) + assert(collectgarbage("count") >= x+4) + a = nil + collectgarbage(); + assert(collectgarbage("count") <= x+1) + -- udata without finalizer + x = collectgarbage("count") + collectgarbage("stop") + for i=1,1000 do newproxy(false) end + assert(collectgarbage("count") > x+10) + collectgarbage() + assert(collectgarbage("count") <= x+1) + -- udata with finalizer + x = collectgarbage("count") + collectgarbage() + collectgarbage("stop") + a = newproxy(true) + getmetatable(a).__gc = function () end + for i=1,1000 do newproxy(a) end + assert(collectgarbage("count") >= x+10) + collectgarbage() -- this collection only calls TM, without freeing memory + assert(collectgarbage("count") >= x+10) + collectgarbage() -- now frees memory + assert(collectgarbage("count") <= x+1) +end + + +collectgarbage("stop") + +-- create 3 userdatas with tag `tt' +a = T.newuserdata(0); debug.setmetatable(a, tt); na = T.udataval(a) +b = T.newuserdata(0); debug.setmetatable(b, tt); nb = T.udataval(b) +c = T.newuserdata(0); debug.setmetatable(c, tt); nc = T.udataval(c) + +-- create userdata without meta table +x = T.newuserdata(4) +y = T.newuserdata(0) + +assert(debug.getmetatable(x) == nil and debug.getmetatable(y) == nil) + +d=T.ref(a); +e=T.ref(b); +f=T.ref(c); +t = {T.getref(d), T.getref(e), T.getref(f)} +assert(t[1] == a and t[2] == b and t[3] == c) + +t=nil; a=nil; c=nil; +T.unref(e); T.unref(f) + +collectgarbage() + +-- check that unref objects have been collected +assert(table.getn(cl) == 1 and cl[1] == nc) + +x = T.getref(d) +assert(type(x) == 'userdata' and debug.getmetatable(x) == tt) +x =nil +tt.b = b -- create cycle +tt=nil -- frees tt for GC +A = nil +b = nil +T.unref(d); +n5 = T.newuserdata(0) +debug.setmetatable(n5, {__gc=F}) +n5 = T.udataval(n5) +collectgarbage() +assert(table.getn(cl) == 4) +-- check order of collection +assert(cl[2] == n5 and cl[3] == nb and cl[4] == na) + + +a, na = {}, {} +for i=30,1,-1 do + a[i] = T.newuserdata(0) + debug.setmetatable(a[i], {__gc=F}) + na[i] = T.udataval(a[i]) +end +cl = {} +a = nil; collectgarbage() +assert(table.getn(cl) == 30) +for i=1,30 do assert(cl[i] == na[i]) end +na = nil + + +for i=2,Lim,2 do -- unlock the other half + T.unref(Arr[i]) +end + +x = T.newuserdata(41); debug.setmetatable(x, {__gc=F}) +assert(T.testC("objsize 2; return 1", x) == 41) +cl = {} +a = {[x] = 1} +x = T.udataval(x) +collectgarbage() +-- old `x' cannot be collected (`a' still uses it) +assert(table.getn(cl) == 0) +for n in pairs(a) do a[n] = nil end +collectgarbage() +assert(table.getn(cl) == 1 and cl[1] == x) -- old `x' must be collected + +-- testing lua_equal +assert(T.testC("equal 2 4; return 1", print, 1, print, 20)) +assert(T.testC("equal 3 2; return 1", 'alo', "alo")) +assert(T.testC("equal 2 3; return 1", nil, nil)) +assert(not T.testC("equal 2 3; return 1", {}, {})) +assert(not T.testC("equal 2 3; return 1")) +assert(not T.testC("equal 2 3; return 1", 3)) + +-- testing lua_equal with fallbacks +do + local map = {} + local t = {__eq = function (a,b) return map[a] == map[b] end} + local function f(x) + local u = T.newuserdata(0) + debug.setmetatable(u, t) + map[u] = x + return u + end + assert(f(10) == f(10)) + assert(f(10) ~= f(11)) + assert(T.testC("equal 2 3; return 1", f(10), f(10))) + assert(not T.testC("equal 2 3; return 1", f(10), f(20))) + t.__eq = nil + assert(f(10) ~= f(10)) +end + +print'+' + + + +------------------------------------------------------------------------- +do -- testing errors during GC + local a = {} + for i=1,20 do + a[i] = T.newuserdata(i) -- creates several udata + end + for i=1,20,2 do -- mark half of them to raise error during GC + debug.setmetatable(a[i], {__gc = function (x) error("error inside gc") end}) + end + for i=2,20,2 do -- mark the other half to count and to create more garbage + debug.setmetatable(a[i], {__gc = function (x) loadstring("A=A+1")() end}) + end + _G.A = 0 + a = 0 + while 1 do + if xpcall(collectgarbage, function (s) a=a+1 end) then + break -- stop if no more errors + end + end + assert(a == 10) -- number of errors + assert(A == 10) -- number of normal collections +end +------------------------------------------------------------------------- +-- test for userdata vals +do + local a = {}; local lim = 30 + for i=0,lim do a[i] = T.pushuserdata(i) end + for i=0,lim do assert(T.udataval(a[i]) == i) end + for i=0,lim do assert(T.pushuserdata(i) == a[i]) end + for i=0,lim do a[a[i]] = i end + for i=0,lim do a[T.pushuserdata(i)] = i end + assert(type(tostring(a[1])) == "string") +end + + +------------------------------------------------------------------------- +-- testing multiple states +T.closestate(T.newstate()); +L1 = T.newstate() +assert(L1) +assert(pack(T.doremote(L1, "function f () return 'alo', 3 end; f()")).n == 0) + +a, b = T.doremote(L1, "return f()") +assert(a == 'alo' and b == '3') + +T.doremote(L1, "_ERRORMESSAGE = nil") +-- error: `sin' is not defined +a, b = T.doremote(L1, "return sin(1)") +assert(a == nil and b == 2) -- 2 == run-time error + +-- error: syntax error +a, b, c = T.doremote(L1, "return a+") +assert(a == nil and b == 3 and type(c) == "string") -- 3 == syntax error + +T.loadlib(L1) +a, b = T.doremote(L1, [[ + a = strlibopen() + a = packageopen() + a = baselibopen(); assert(a == _G and require("_G") == a) + a = iolibopen(); assert(type(a.read) == "function") + assert(require("io") == a) + a = tablibopen(); assert(type(a.insert) == "function") + a = dblibopen(); assert(type(a.getlocal) == "function") + a = mathlibopen(); assert(type(a.sin) == "function") + return string.sub('okinama', 1, 2) +]]) +assert(a == "ok") + +T.closestate(L1); + +L1 = T.newstate() +T.loadlib(L1) +T.doremote(L1, "a = {}") +T.testC(L1, [[pushstring a; gettable G; pushstring x; pushnum 1; + settable -3]]) +assert(T.doremote(L1, "return a.x") == "1") + +T.closestate(L1) + +L1 = nil + +print('+') + +------------------------------------------------------------------------- +-- testing memory limits +------------------------------------------------------------------------- +collectgarbage() +T.totalmem(T.totalmem()+5000) -- set low memory limit (+5k) +assert(not pcall(loadstring"local a={}; for i=1,100000 do a[i]=i end")) +T.totalmem(1000000000) -- restore high limit + + +local function stack(x) if x>0 then stack(x-1) end end + +-- test memory errors; increase memory limit in small steps, so that +-- we get memory errors in different parts of a given task, up to there +-- is enough memory to complete the task without errors +function testamem (s, f) + collectgarbage() + stack(10) -- ensure minimum stack size + local M = T.totalmem() + local oldM = M + local a,b = nil + while 1 do + M = M+3 -- increase memory limit in small steps + T.totalmem(M) + a, b = pcall(f) + if a and b then break end -- stop when no more errors + collectgarbage() + if not a and not string.find(b, "memory") then -- `real' error? + T.totalmem(1000000000) -- restore high limit + error(b, 0) + end + end + T.totalmem(1000000000) -- restore high limit + print("\nlimit for " .. s .. ": " .. M-oldM) + return b +end + + +-- testing memory errors when creating a new state + +b = testamem("state creation", T.newstate) +T.closestate(b); -- close new state + + +-- testing threads + +function expand (n,s) + if n==0 then return "" end + local e = string.rep("=", n) + return string.format("T.doonnewstack([%s[ %s;\n collectgarbage(); %s]%s])\n", + e, s, expand(n-1,s), e) +end + +G=0; collectgarbage(); a =collectgarbage("count") +loadstring(expand(20,"G=G+1"))() +assert(G==20); collectgarbage(); -- assert(gcinfo() <= a+1) + +testamem("thread creation", function () + return T.doonnewstack("x=1") == 0 -- try to create thread +end) + + +-- testing memory x compiler + +testamem("loadstring", function () + return loadstring("x=1") -- try to do a loadstring +end) + + +local testprog = [[ +local function foo () return end +local t = {"x"} +a = "aaa" +for _, v in ipairs(t) do a=a..v end +return true +]] + +-- testing memory x dofile +_G.a = nil +local t =os.tmpname() +local f = assert(io.open(t, "w")) +f:write(testprog) +f:close() +testamem("dofile", function () + local a = loadfile(t) + return a and a() +end) +assert(os.remove(t)) +assert(_G.a == "aaax") + + +-- other generic tests + +testamem("string creation", function () + local a, b = string.gsub("alo alo", "(a)", function (x) return x..'b' end) + return (a == 'ablo ablo') +end) + +testamem("dump/undump", function () + local a = loadstring(testprog) + local b = a and string.dump(a) + a = b and loadstring(b) + return a and a() +end) + +local t = os.tmpname() +testamem("file creation", function () + local f = assert(io.open(t, 'w')) + assert (not io.open"nomenaoexistente") + io.close(f); + return not loadfile'nomenaoexistente' +end) +assert(os.remove(t)) + +testamem("table creation", function () + local a, lim = {}, 10 + for i=1,lim do a[i] = i; a[i..'a'] = {} end + return (type(a[lim..'a']) == 'table' and a[lim] == lim) +end) + +local a = 1 +close = nil +testamem("closure creation", function () + function close (b,c) + return function (x) return a+b+c+x end + end + return (close(2,3)(4) == 10) +end) + +testamem("coroutines", function () + local a = coroutine.wrap(function () + coroutine.yield(string.rep("a", 10)) + return {} + end) + assert(string.len(a()) == 10) + return a() +end) + +print'+' + +-- testing some auxlib functions +assert(T.gsub("alo.alo.uhuh.", ".", "//") == "alo//alo//uhuh//") +assert(T.gsub("alo.alo.uhuh.", "alo", "//") == "//.//.uhuh.") +assert(T.gsub("", "alo", "//") == "") +assert(T.gsub("...", ".", "/.") == "/././.") +assert(T.gsub("...", "...", "") == "") + + +print'OK' + diff --git a/tests/upstream/lua51/vendor/attrib.lua b/tests/upstream/lua51/vendor/attrib.lua new file mode 100644 index 00000000..b14d6866 --- /dev/null +++ b/tests/upstream/lua51/vendor/attrib.lua @@ -0,0 +1,339 @@ +do --[ + +print "testing require" + +assert(require"string" == string) +assert(require"math" == math) +assert(require"table" == table) +assert(require"io" == io) +assert(require"os" == os) +assert(require"debug" == debug) +assert(require"coroutine" == coroutine) + +assert(type(package.path) == "string") +assert(type(package.cpath) == "string") +assert(type(package.loaded) == "table") +assert(type(package.preload) == "table") + + +local DIR = "libs/" + +local function createfiles (files, preextras, posextras) + for n,c in pairs(files) do + io.output(DIR..n) + io.write(string.format(preextras, n)) + io.write(c) + io.write(string.format(posextras, n)) + io.close(io.output()) + end +end + +function removefiles (files) + for n in pairs(files) do + os.remove(DIR..n) + end +end + +local files = { + ["A.lua"] = "", + ["B.lua"] = "assert(...=='B');require 'A'", + ["A.lc"] = "", + ["A"] = "", + ["L"] = "", + ["XXxX"] = "", + ["C.lua"] = "package.loaded[...] = 25; require'C'" +} + +AA = nil +local extras = [[ +NAME = '%s' +REQUIRED = ... +return AA]] + +createfiles(files, "", extras) + + +local oldpath = package.path + +package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR) + +local try = function (p, n, r) + NAME = nil + local rr = require(p) + assert(NAME == n) + assert(REQUIRED == p) + assert(rr == r) +end + +assert(require"C" == 25) +assert(require"C" == 25) +AA = nil +try('B', 'B.lua', true) +assert(package.loaded.B) +assert(require"B" == true) +assert(package.loaded.A) +package.loaded.A = nil +try('B', nil, true) -- should not reload package +try('A', 'A.lua', true) +package.loaded.A = nil +os.remove(DIR..'A.lua') +AA = {} +try('A', 'A.lc', AA) -- now must find second option +assert(require("A") == AA) +AA = false +try('K', 'L', false) -- default option +try('K', 'L', false) -- default option (should reload it) +assert(rawget(_G, "_REQUIREDNAME") == nil) + +AA = "x" +try("X", "XXxX", AA) + + +removefiles(files) + + +-- testing require of sub-packages + +package.path = string.gsub("D/?.lua;D/?/init.lua", "D/", DIR) + +files = { + ["P1/init.lua"] = "AA = 10", + ["P1/xuxu.lua"] = "AA = 20", +} + +createfiles(files, "module(..., package.seeall)\n", "") +AA = 0 + +local m = assert(require"P1") +assert(m == P1 and m._NAME == "P1" and AA == 0 and m.AA == 10) +assert(require"P1" == P1 and P1 == m) +assert(require"P1" == P1) +assert(P1._PACKAGE == "") + +local m = assert(require"P1.xuxu") +assert(m == P1.xuxu and m._NAME == "P1.xuxu" and AA == 0 and m.AA == 20) +assert(require"P1.xuxu" == P1.xuxu and P1.xuxu == m) +assert(require"P1.xuxu" == P1.xuxu) +assert(require"P1" == P1) +assert(P1.xuxu._PACKAGE == "P1.") +assert(P1.AA == 10 and P1._PACKAGE == "") +assert(P1._G == _G and P1.xuxu._G == _G) + + + +removefiles(files) + + +package.path = "" +assert(not pcall(require, "file_does_not_exist")) +package.path = "??\0?" +assert(not pcall(require, "file_does_not_exist1")) + +package.path = oldpath + +-- check 'require' error message +local fname = "file_does_not_exist2" +local m, err = pcall(require, fname) +for t in string.gmatch(package.path..";"..package.cpath, "[^;]+") do + t = string.gsub(t, "?", fname) + assert(string.find(err, t, 1, true)) +end + + +local function import(...) + local f = {...} + return function (m) + for i=1, #f do m[f[i]] = _G[f[i]] end + end +end + +local assert, module, package = assert, module, package +X = nil; x = 0; assert(_G.x == 0) -- `x' must be a global variable +module"X"; x = 1; assert(_M.x == 1) +module"X.a.b.c"; x = 2; assert(_M.x == 2) +module("X.a.b", package.seeall); x = 3 +assert(X._NAME == "X" and X.a.b.c._NAME == "X.a.b.c" and X.a.b._NAME == "X.a.b") +assert(X._M == X and X.a.b.c._M == X.a.b.c and X.a.b._M == X.a.b) +assert(X.x == 1 and X.a.b.c.x == 2 and X.a.b.x == 3) +assert(X._PACKAGE == "" and X.a.b.c._PACKAGE == "X.a.b." and + X.a.b._PACKAGE == "X.a.") +assert(_PACKAGE.."c" == "X.a.c") +assert(X.a._NAME == nil and X.a._M == nil) +module("X.a", import("X")) ; x = 4 +assert(X.a._NAME == "X.a" and X.a.x == 4 and X.a._M == X.a) +module("X.a.b", package.seeall); assert(x == 3); x = 5 +assert(_NAME == "X.a.b" and X.a.b.x == 5) + +assert(X._G == nil and X.a._G == nil and X.a.b._G == _G and X.a.b.c._G == nil) + +setfenv(1, _G) +assert(x == 0) + +assert(not pcall(module, "x")) +assert(not pcall(module, "math.sin")) + + +-- testing C libraries + + +local p = "" -- On Mac OS X, redefine this to "_" + +-- assert(loadlib == package.loadlib) -- only for compatibility +local f, err, when = package.loadlib("libs/lib1.so", p.."luaopen_lib1") +if not f then + (Message or print)('\a\n >>> cannot load dynamic library <<<\n\a') + print(err, when) +else + f() -- open library + assert(require("lib1") == lib1) + collectgarbage() + assert(lib1.id("x") == "x") + f = assert(package.loadlib("libs/lib1.so", p.."anotherfunc")) + assert(f(10, 20) == "1020\n") + f, err, when = package.loadlib("libs/lib1.so", p.."xuxu") + assert(not f and type(err) == "string" and when == "init") + package.cpath = "libs/?.so" + require"lib2" + assert(lib2.id("x") == "x") + local fs = require"lib1.sub" + assert(fs == lib1.sub and next(lib1.sub) == nil) + module("lib2", package.seeall) + f = require"-lib2" + assert(f.id("x") == "x" and _M == f and _NAME == "lib2") + module("lib1.sub", package.seeall) + assert(_M == fs) + setfenv(1, _G) + +end +f, err, when = package.loadlib("donotexist", p.."xuxu") +assert(not f and type(err) == "string" and (when == "open" or when == "absent")) + + +-- testing preload + +do + local p = package + package = {} + p.preload.pl = function (...) + module(...) + function xuxu (x) return x+20 end + end + + require"pl" + assert(require"pl" == pl) + assert(pl.xuxu(10) == 30) + + package = p + assert(type(package.path) == "string") +end + + + +end --] + +print('+') + +print("testing assignments, logical operators, and constructors") + +local res, res2 = 27 + +a, b = 1, 2+3 +assert(a==1 and b==5) +a={} +function f() return 10, 11, 12 end +a.x, b, a[1] = 1, 2, f() +assert(a.x==1 and b==2 and a[1]==10) +a[f()], b, a[f()+3] = f(), a, 'x' +assert(a[10] == 10 and b == a and a[13] == 'x') + +do + local f = function (n) local x = {}; for i=1,n do x[i]=i end; + return unpack(x) end; + local a,b,c + a,b = 0, f(1) + assert(a == 0 and b == 1) + A,b = 0, f(1) + assert(A == 0 and b == 1) + a,b,c = 0,5,f(4) + assert(a==0 and b==5 and c==1) + a,b,c = 0,5,f(0) + assert(a==0 and b==5 and c==nil) +end + + +a, b, c, d = 1 and nil, 1 or nil, (1 and (nil or 1)), 6 +assert(not a and b and c and d==6) + +d = 20 +a, b, c, d = f() +assert(a==10 and b==11 and c==12 and d==nil) +a,b = f(), 1, 2, 3, f() +assert(a==10 and b==1) + +assert(ab == true) +assert((10 and 2) == 2) +assert((10 or 2) == 10) +assert((10 or assert(nil)) == 10) +assert(not (nil and assert(nil))) +assert((nil or "alo") == "alo") +assert((nil and 10) == nil) +assert((false and 10) == false) +assert((true or 10) == true) +assert((false or 10) == 10) +assert(false ~= nil) +assert(nil ~= false) +assert(not nil == true) +assert(not not nil == false) +assert(not not 1 == true) +assert(not not a == true) +assert(not not (6 or nil) == true) +assert(not not (nil and 56) == false) +assert(not not (nil and true) == false) +print('+') + +a = {} +a[true] = 20 +a[false] = 10 +assert(a[1<2] == 20 and a[1>2] == 10) + +function f(a) return a end + +local a = {} +for i=3000,-3000,-1 do a[i] = i; end +a[10e30] = "alo"; a[true] = 10; a[false] = 20 +assert(a[10e30] == 'alo' and a[not 1] == 20 and a[10<20] == 10) +for i=3000,-3000,-1 do assert(a[i] == i); end +a[print] = assert +a[f] = print +a[a] = a +assert(a[a][a][a][a][print] == assert) +a[print](a[a[f]] == a[print]) +a = nil + +a = {10,9,8,7,6,5,4,3,2; [-3]='a', [f]=print, a='a', b='ab'} +a, a.x, a.y = a, a[-3] +assert(a[1]==10 and a[-3]==a.a and a[f]==print and a.x=='a' and not a.y) +a[1], f(a)[2], b, c = {['alo']=assert}, 10, a[1], a[f], 6, 10, 23, f(a), 2 +a[1].alo(a[2]==10 and b==10 and c==print) + +a[2^31] = 10; a[2^31+1] = 11; a[-2^31] = 12; +a[2^32] = 13; a[-2^32] = 14; a[2^32+1] = 15; a[10^33] = 16; + +assert(a[2^31] == 10 and a[2^31+1] == 11 and a[-2^31] == 12 and + a[2^32] == 13 and a[-2^32] == 14 and a[2^32+1] == 15 and + a[10^33] == 16) + +a = nil + + +do + local a,i,j,b + a = {'a', 'b'}; i=1; j=2; b=a + i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i + assert(i == 2 and b[1] == 1 and a == 1 and j == b and b[2] == 2 and + b[3] == 1) +end + +print('OK') + +return res diff --git a/tests/upstream/lua51/vendor/big.lua b/tests/upstream/lua51/vendor/big.lua new file mode 100644 index 00000000..92612880 --- /dev/null +++ b/tests/upstream/lua51/vendor/big.lua @@ -0,0 +1,381 @@ +print "testing string length overflow" + +local longs = string.rep("\0", 2^25) +local function catter (i) + return assert(loadstring( + string.format("return function(a) return a%s end", + string.rep("..a", i-1))))() +end +rep129 = catter(129) +local a, b = pcall(rep129, longs) +assert(not a and string.find(b, "overflow")) +print('+') + + +require "checktable" + +--[[ lots of empty lines (to force SETLINEW) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +--]] + + +a,b = nil,nil +while not b do +if a then +b = { -- lots of strings (to force JMPW and PUSHCONSTANTW) +"n1", "n2", "n3", "n4", "n5", "n6", "n7", "n8", "n9", "n10", +"n11", "n12", "j301", "j302", "j303", "j304", "j305", "j306", "j307", "j308", +"j309", "a310", "n311", "n312", "n313", "n314", "n315", "n316", "n317", "n318", +"n319", "n320", "n321", "n322", "n323", "n324", "n325", "n326", "n327", "n328", +"a329", "n330", "n331", "n332", "n333", "n334", "n335", "n336", "n337", "n338", +"n339", "n340", "n341", "z342", "n343", "n344", "n345", "n346", "n347", "n348", +"n349", "n350", "n351", "n352", "r353", "n354", "n355", "n356", "n357", "n358", +"n359", "n360", "n361", "n362", "n363", "n364", "n365", "n366", "z367", "n368", +"n369", "n370", "n371", "n372", "n373", "n374", "n375", "a376", "n377", "n378", +"n379", "n380", "n381", "n382", "n383", "n384", "n385", "n386", "n387", "n388", +"n389", "n390", "n391", "n392", "n393", "n394", "n395", "n396", "n397", "n398", +"n399", "n400", "n13", "n14", "n15", "n16", "n17", "n18", "n19", "n20", +"n21", "n22", "n23", "a24", "n25", "n26", "n27", "n28", "n29", "j30", +"n31", "n32", "n33", "n34", "n35", "n36", "n37", "n38", "n39", "n40", +"n41", "n42", "n43", "n44", "n45", "n46", "n47", "n48", "n49", "n50", +"n51", "n52", "n53", "n54", "n55", "n56", "n57", "n58", "n59", "n60", +"n61", "n62", "n63", "n64", "n65", "a66", "z67", "n68", "n69", "n70", +"n71", "n72", "n73", "n74", "n75", "n76", "n77", "n78", "n79", "n80", +"n81", "n82", "n83", "n84", "n85", "n86", "n87", "n88", "n89", "n90", +"n91", "n92", "n93", "n94", "n95", "n96", "n97", "n98", "n99", "n100", +"n201", "n202", "n203", "n204", "n205", "n206", "n207", "n208", "n209", "n210", +"n211", "n212", "n213", "n214", "n215", "n216", "n217", "n218", "n219", "n220", +"n221", "n222", "n223", "n224", "n225", "n226", "n227", "n228", "n229", "n230", +"n231", "n232", "n233", "n234", "n235", "n236", "n237", "n238", "n239", "a240", +"a241", "a242", "a243", "a244", "a245", "a246", "a247", "a248", "a249", "n250", +"n251", "n252", "n253", "n254", "n255", "n256", "n257", "n258", "n259", "n260", +"n261", "n262", "n263", "n264", "n265", "n266", "n267", "n268", "n269", "n270", +"n271", "n272", "n273", "n274", "n275", "n276", "n277", "n278", "n279", "n280", +"n281", "n282", "n283", "n284", "n285", "n286", "n287", "n288", "n289", "n290", +"n291", "n292", "n293", "n294", "n295", "n296", "n297", "n298", "n299" +; x=23} +else a = 1 end + + +end + +assert(b.x == 23) +print('+') + +stat(b) + +repeat +a = { +n1 = 1.5, n2 = 2.5, n3 = 3.5, n4 = 4.5, n5 = 5.5, n6 = 6.5, n7 = 7.5, +n8 = 8.5, n9 = 9.5, n10 = 10.5, n11 = 11.5, n12 = 12.5, +j301 = 301.5, j302 = 302.5, j303 = 303.5, j304 = 304.5, j305 = 305.5, +j306 = 306.5, j307 = 307.5, j308 = 308.5, j309 = 309.5, a310 = 310.5, +n311 = 311.5, n312 = 312.5, n313 = 313.5, n314 = 314.5, n315 = 315.5, +n316 = 316.5, n317 = 317.5, n318 = 318.5, n319 = 319.5, n320 = 320.5, +n321 = 321.5, n322 = 322.5, n323 = 323.5, n324 = 324.5, n325 = 325.5, +n326 = 326.5, n327 = 327.5, n328 = 328.5, a329 = 329.5, n330 = 330.5, +n331 = 331.5, n332 = 332.5, n333 = 333.5, n334 = 334.5, n335 = 335.5, +n336 = 336.5, n337 = 337.5, n338 = 338.5, n339 = 339.5, n340 = 340.5, +n341 = 341.5, z342 = 342.5, n343 = 343.5, n344 = 344.5, n345 = 345.5, +n346 = 346.5, n347 = 347.5, n348 = 348.5, n349 = 349.5, n350 = 350.5, +n351 = 351.5, n352 = 352.5, r353 = 353.5, n354 = 354.5, n355 = 355.5, +n356 = 356.5, n357 = 357.5, n358 = 358.5, n359 = 359.5, n360 = 360.5, +n361 = 361.5, n362 = 362.5, n363 = 363.5, n364 = 364.5, n365 = 365.5, +n366 = 366.5, z367 = 367.5, n368 = 368.5, n369 = 369.5, n370 = 370.5, +n371 = 371.5, n372 = 372.5, n373 = 373.5, n374 = 374.5, n375 = 375.5, +a376 = 376.5, n377 = 377.5, n378 = 378.5, n379 = 379.5, n380 = 380.5, +n381 = 381.5, n382 = 382.5, n383 = 383.5, n384 = 384.5, n385 = 385.5, +n386 = 386.5, n387 = 387.5, n388 = 388.5, n389 = 389.5, n390 = 390.5, +n391 = 391.5, n392 = 392.5, n393 = 393.5, n394 = 394.5, n395 = 395.5, +n396 = 396.5, n397 = 397.5, n398 = 398.5, n399 = 399.5, n400 = 400.5, +n13 = 13.5, n14 = 14.5, n15 = 15.5, n16 = 16.5, n17 = 17.5, +n18 = 18.5, n19 = 19.5, n20 = 20.5, n21 = 21.5, n22 = 22.5, +n23 = 23.5, a24 = 24.5, n25 = 25.5, n26 = 26.5, n27 = 27.5, +n28 = 28.5, n29 = 29.5, j30 = 30.5, n31 = 31.5, n32 = 32.5, +n33 = 33.5, n34 = 34.5, n35 = 35.5, n36 = 36.5, n37 = 37.5, +n38 = 38.5, n39 = 39.5, n40 = 40.5, n41 = 41.5, n42 = 42.5, +n43 = 43.5, n44 = 44.5, n45 = 45.5, n46 = 46.5, n47 = 47.5, +n48 = 48.5, n49 = 49.5, n50 = 50.5, n51 = 51.5, n52 = 52.5, +n53 = 53.5, n54 = 54.5, n55 = 55.5, n56 = 56.5, n57 = 57.5, +n58 = 58.5, n59 = 59.5, n60 = 60.5, n61 = 61.5, n62 = 62.5, +n63 = 63.5, n64 = 64.5, n65 = 65.5, a66 = 66.5, z67 = 67.5, +n68 = 68.5, n69 = 69.5, n70 = 70.5, n71 = 71.5, n72 = 72.5, +n73 = 73.5, n74 = 74.5, n75 = 75.5, n76 = 76.5, n77 = 77.5, +n78 = 78.5, n79 = 79.5, n80 = 80.5, n81 = 81.5, n82 = 82.5, +n83 = 83.5, n84 = 84.5, n85 = 85.5, n86 = 86.5, n87 = 87.5, +n88 = 88.5, n89 = 89.5, n90 = 90.5, n91 = 91.5, n92 = 92.5, +n93 = 93.5, n94 = 94.5, n95 = 95.5, n96 = 96.5, n97 = 97.5, +n98 = 98.5, n99 = 99.5, n100 = 100.5, n201 = 201.5, n202 = 202.5, +n203 = 203.5, n204 = 204.5, n205 = 205.5, n206 = 206.5, n207 = 207.5, +n208 = 208.5, n209 = 209.5, n210 = 210.5, n211 = 211.5, n212 = 212.5, +n213 = 213.5, n214 = 214.5, n215 = 215.5, n216 = 216.5, n217 = 217.5, +n218 = 218.5, n219 = 219.5, n220 = 220.5, n221 = 221.5, n222 = 222.5, +n223 = 223.5, n224 = 224.5, n225 = 225.5, n226 = 226.5, n227 = 227.5, +n228 = 228.5, n229 = 229.5, n230 = 230.5, n231 = 231.5, n232 = 232.5, +n233 = 233.5, n234 = 234.5, n235 = 235.5, n236 = 236.5, n237 = 237.5, +n238 = 238.5, n239 = 239.5, a240 = 240.5, a241 = 241.5, a242 = 242.5, +a243 = 243.5, a244 = 244.5, a245 = 245.5, a246 = 246.5, a247 = 247.5, +a248 = 248.5, a249 = 249.5, n250 = 250.5, n251 = 251.5, n252 = 252.5, +n253 = 253.5, n254 = 254.5, n255 = 255.5, n256 = 256.5, n257 = 257.5, +n258 = 258.5, n259 = 259.5, n260 = 260.5, n261 = 261.5, n262 = 262.5, +n263 = 263.5, n264 = 264.5, n265 = 265.5, n266 = 266.5, n267 = 267.5, +n268 = 268.5, n269 = 269.5, n270 = 270.5, n271 = 271.5, n272 = 272.5, +n273 = 273.5, n274 = 274.5, n275 = 275.5, n276 = 276.5, n277 = 277.5, +n278 = 278.5, n279 = 279.5, n280 = 280.5, n281 = 281.5, n282 = 282.5, +n283 = 283.5, n284 = 284.5, n285 = 285.5, n286 = 286.5, n287 = 287.5, +n288 = 288.5, n289 = 289.5, n290 = 290.5, n291 = 291.5, n292 = 292.5, +n293 = 293.5, n294 = 294.5, n295 = 295.5, n296 = 296.5, n297 = 297.5, +n298 = 298.5, n299 = 299.5, j300 = 300} or 1 +until 1 + +assert(a.n299 == 299.5) +xxx = 1 +assert(xxx == 1) + +stat(a) + +function a:findfield (f) + local i,v = next(self, nil) + while i ~= f do + if not i then return end + i,v = next(self, i) + end + return v +end + +local ii = 0 +i = 1 +while b[i] do + local r = a:findfield(b[i]); + assert(a[b[i]] == r) + ii = math.max(ii,i) + i = i+1 +end + +assert(ii == 299) + +function xxxx (x) coroutine.yield('b'); return ii+x end + +assert(xxxx(10) == 309) + +a = nil +b = nil +a1 = nil + +print("tables with table indices:") +i = 1; a={} +while i <= 1023 do a[{}] = i; i=i+1 end +stat(a) +a = nil + +print("tables with function indices:") +a={} +for i=1,511 do local x; a[function () return x end] = i end +stat(a) +a = nil + +print'OK' + +return 'a' diff --git a/tests/upstream/lua51/vendor/calls.lua b/tests/upstream/lua51/vendor/calls.lua new file mode 100644 index 00000000..788f9a1c --- /dev/null +++ b/tests/upstream/lua51/vendor/calls.lua @@ -0,0 +1,294 @@ +print("testing functions and calls") + +-- get the opportunity to test 'type' too ;) + +assert(type(1<2) == 'boolean') +assert(type(true) == 'boolean' and type(false) == 'boolean') +assert(type(nil) == 'nil' and type(-3) == 'number' and type'x' == 'string' and + type{} == 'table' and type(type) == 'function') + +assert(type(assert) == type(print)) +f = nil +function f (x) return a:x (x) end +assert(type(f) == 'function') + + +-- testing local-function recursion +fact = false +do + local res = 1 + local function fact (n) + if n==0 then return res + else return n*fact(n-1) + end + end + assert(fact(5) == 120) +end +assert(fact == false) + +-- testing declarations +a = {i = 10} +self = 20 +function a:x (x) return x+self.i end +function a.y (x) return x+self end + +assert(a:x(1)+10 == a.y(1)) + +a.t = {i=-100} +a["t"].x = function (self, a,b) return self.i+a+b end + +assert(a.t:x(2,3) == -95) + +do + local a = {x=0} + function a:add (x) self.x, a.y = self.x+x, 20; return self end + assert(a:add(10):add(20):add(30).x == 60 and a.y == 20) +end + +local a = {b={c={}}} + +function a.b.c.f1 (x) return x+1 end +function a.b.c:f2 (x,y) self[x] = y end +assert(a.b.c.f1(4) == 5) +a.b.c:f2('k', 12); assert(a.b.c.k == 12) + +print('+') + +t = nil -- 'declare' t +function f(a,b,c) local d = 'a'; t={a,b,c,d} end + +f( -- this line change must be valid + 1,2) +assert(t[1] == 1 and t[2] == 2 and t[3] == nil and t[4] == 'a') +f(1,2, -- this one too + 3,4) +assert(t[1] == 1 and t[2] == 2 and t[3] == 3 and t[4] == 'a') + +function fat(x) + if x <= 1 then return 1 + else return x*loadstring("return fat(" .. x-1 .. ")")() + end +end + +assert(loadstring "loadstring 'assert(fat(6)==720)' () ")() +a = loadstring('return fat(5), 3') +a,b = a() +assert(a == 120 and b == 3) +print('+') + +function err_on_n (n) + if n==0 then error(); exit(1); + else err_on_n (n-1); exit(1); + end +end + +do + function dummy (n) + if n > 0 then + assert(not pcall(err_on_n, n)) + dummy(n-1) + end + end +end + +dummy(10) + +function deep (n) + if n>0 then deep(n-1) end +end +deep(10) +deep(200) + +-- testing tail call +function deep (n) if n>0 then return deep(n-1) else return 101 end end +assert(deep(30000) == 101) +a = {} +function a:deep (n) if n>0 then return self:deep(n-1) else return 101 end end +assert(a:deep(30000) == 101) + +print('+') + + +a = nil +(function (x) a=x end)(23) +assert(a == 23 and (function (x) return x*2 end)(20) == 40) + + +local x,y,z,a +a = {}; lim = 2000 +for i=1, lim do a[i]=i end +assert(select(lim, unpack(a)) == lim and select('#', unpack(a)) == lim) +x = unpack(a) +assert(x == 1) +x = {unpack(a)} +assert(table.getn(x) == lim and x[1] == 1 and x[lim] == lim) +x = {unpack(a, lim-2)} +assert(table.getn(x) == 3 and x[1] == lim-2 and x[3] == lim) +x = {unpack(a, 10, 6)} +assert(next(x) == nil) -- no elements +x = {unpack(a, 11, 10)} +assert(next(x) == nil) -- no elements +x,y = unpack(a, 10, 10) +assert(x == 10 and y == nil) +x,y,z = unpack(a, 10, 11) +assert(x == 10 and y == 11 and z == nil) +a,x = unpack{1} +assert(a==1 and x==nil) +a,x = unpack({1,2}, 1, 1) +assert(a==1 and x==nil) + + +-- testing closures + +-- fixed-point operator +Y = function (le) + local function a (f) + return le(function (x) return f(f)(x) end) + end + return a(a) + end + + +-- non-recursive factorial + +F = function (f) + return function (n) + if n == 0 then return 1 + else return n*f(n-1) end + end + end + +fat = Y(F) + +assert(fat(0) == 1 and fat(4) == 24 and Y(F)(5)==5*Y(F)(4)) + +local function g (z) + local function f (a,b,c,d) + return function (x,y) return a+b+c+d+a+x+y+z end + end + return f(z,z+1,z+2,z+3) +end + +f = g(10) +assert(f(9, 16) == 10+11+12+13+10+9+16+10) + +Y, F, f = nil +print('+') + +-- testing multiple returns + +function unlpack (t, i) + i = i or 1 + if (i <= table.getn(t)) then + return t[i], unlpack(t, i+1) + end +end + +function equaltab (t1, t2) + assert(table.getn(t1) == table.getn(t2)) + for i,v1 in ipairs(t1) do + assert(v1 == t2[i]) + end +end + +local function pack (...) + local x = {...} + x.n = select('#', ...) + return x +end + +function f() return 1,2,30,4 end +function ret2 (a,b) return a,b end + +local a,b,c,d = unlpack{1,2,3} +assert(a==1 and b==2 and c==3 and d==nil) +a = {1,2,3,4,false,10,'alo',false,assert} +equaltab(pack(unlpack(a)), a) +equaltab(pack(unlpack(a), -1), {1,-1}) +a,b,c,d = ret2(f()), ret2(f()) +assert(a==1 and b==1 and c==2 and d==nil) +a,b,c,d = unlpack(pack(ret2(f()), ret2(f()))) +assert(a==1 and b==1 and c==2 and d==nil) +a,b,c,d = unlpack(pack(ret2(f()), (ret2(f())))) +assert(a==1 and b==1 and c==nil and d==nil) + +a = ret2{ unlpack{1,2,3}, unlpack{3,2,1}, unlpack{"a", "b"}} +assert(a[1] == 1 and a[2] == 3 and a[3] == "a" and a[4] == "b") + + +-- testing calls with 'incorrect' arguments +rawget({}, "x", 1) +rawset({}, "x", 1, 2) +assert(math.sin(1,2) == math.sin(1)) +table.sort({10,9,8,4,19,23,0,0}, function (a,b) return a" then + assert(val==nil) + else + assert(t[key] == val) + local mp = T.hash(key, t) + if l[i] then + assert(l[i] == mp) + elseif mp ~= i then + l[i] = mp + else -- list head + l[mp] = {mp} -- first element + while next do + assert(ff <= next and next < hsize) + if l[next] then assert(l[next] == mp) else l[next] = mp end + table.insert(l[mp], next) + key,val,next = T.querytab(t, next) + assert(key) + end + end + end + end + l.asize = asize; l.hsize = hsize; l.ff = ff + return l +end + +function mostra (t) + local asize, hsize, ff = T.querytab(t) + print(asize, hsize, ff) + print'------' + for i=0,asize-1 do + local _, v = T.querytab(t, i) + print(string.format("[%d] -", i), v) + end + print'------' + for i=0,hsize-1 do + print(i, T.querytab(t, i+asize)) + end + print'-------------' +end + +function stat (t) + t = checktable(t) + local nelem, nlist = 0, 0 + local maxlist = {} + for i=0,t.hsize-1 do + if type(t[i]) == 'table' then + local n = table.getn(t[i]) + nlist = nlist+1 + nelem = nelem + n + if not maxlist[n] then maxlist[n] = 0 end + maxlist[n] = maxlist[n]+1 + end + end + print(string.format("hsize=%d elements=%d load=%.2f med.len=%.2f (asize=%d)", + t.hsize, nelem, nelem/t.hsize, nelem/nlist, t.asize)) + for i=1,table.getn(maxlist) do + local n = maxlist[i] or 0 + print(string.format("%5d %10d %.2f%%", i, n, n*100/nlist)) + end +end + diff --git a/tests/upstream/lua51/vendor/closure.lua b/tests/upstream/lua51/vendor/closure.lua new file mode 100644 index 00000000..94f72ae4 --- /dev/null +++ b/tests/upstream/lua51/vendor/closure.lua @@ -0,0 +1,422 @@ +print "testing closures and coroutines" + +local A,B = 0,{g=10} +function f(x) + local a = {} + for i=1,1000 do + local y = 0 + do + a[i] = function () B.g = B.g+1; y = y+x; return y+A end + end + end + local dummy = function () return a[A] end + collectgarbage() + A = 1; assert(dummy() == a[1]); A = 0; + assert(a[1]() == x) + assert(a[3]() == x) + collectgarbage() + assert(B.g == 12) + return a +end + +a = f(10) +-- force a GC in this level +local x = {[1] = {}} -- to detect a GC +setmetatable(x, {__mode = 'kv'}) +while x[1] do -- repeat until GC + local a = A..A..A..A -- create garbage + A = A+1 +end +assert(a[1]() == 20+A) +assert(a[1]() == 30+A) +assert(a[2]() == 10+A) +collectgarbage() +assert(a[2]() == 20+A) +assert(a[2]() == 30+A) +assert(a[3]() == 20+A) +assert(a[8]() == 10+A) +assert(getmetatable(x).__mode == 'kv') +assert(B.g == 19) + +-- testing closures with 'for' control variable +a = {} +for i=1,10 do + a[i] = {set = function(x) i=x end, get = function () return i end} + if i == 3 then break end +end +assert(a[4] == nil) +a[1].set(10) +assert(a[2].get() == 2) +a[2].set('a') +assert(a[3].get() == 3) +assert(a[2].get() == 'a') + +a = {} +for i, k in pairs{'a', 'b'} do + a[i] = {set = function(x, y) i=x; k=y end, + get = function () return i, k end} + if i == 2 then break end +end +a[1].set(10, 20) +local r,s = a[2].get() +assert(r == 2 and s == 'b') +r,s = a[1].get() +assert(r == 10 and s == 20) +a[2].set('a', 'b') +r,s = a[2].get() +assert(r == "a" and s == "b") + + +-- testing closures with 'for' control variable x break +for i=1,3 do + f = function () return i end + break +end +assert(f() == 1) + +for k, v in pairs{"a", "b"} do + f = function () return k, v end + break +end +assert(({f()})[1] == 1) +assert(({f()})[2] == "a") + + +-- testing closure x break x return x errors + +local b +function f(x) + local first = 1 + while 1 do + if x == 3 and not first then return end + local a = 'xuxu' + b = function (op, y) + if op == 'set' then + a = x+y + else + return a + end + end + if x == 1 then do break end + elseif x == 2 then return + else if x ~= 3 then error() end + end + first = nil + end +end + +for i=1,3 do + f(i) + assert(b('get') == 'xuxu') + b('set', 10); assert(b('get') == 10+i) + b = nil +end + +pcall(f, 4); +assert(b('get') == 'xuxu') +b('set', 10); assert(b('get') == 14) + + +local w +-- testing multi-level closure +function f(x) + return function (y) + return function (z) return w+x+y+z end + end +end + +y = f(10) +w = 1.345 +assert(y(20)(30) == 60+w) + +-- testing closures x repeat-until + +local a = {} +local i = 1 +repeat + local x = i + a[i] = function () i = x+1; return x end +until i > 10 or a[i]() ~= x +assert(i == 11 and a[1]() == 1 and a[3]() == 3 and i == 4) + +print'+' + + +-- test for correctly closing upvalues in tail calls of vararg functions +local function t () + local function c(a,b) assert(a=="test" and b=="OK") end + local function v(f, ...) c("test", f() ~= 1 and "FAILED" or "OK") end + local x = 1 + return v(function() return x end) +end +t() + + +-- coroutine tests + +local f + +assert(coroutine.running() == nil) + + +-- tests for global environment + +local function foo (a) + setfenv(0, a) + coroutine.yield(getfenv()) + assert(getfenv(0) == a) + assert(getfenv(1) == _G) + assert(getfenv(loadstring"") == a) + return getfenv() +end + +f = coroutine.wrap(foo) +local a = {} +assert(f(a) == _G) +local a,b = pcall(f) +assert(a and b == _G) + + +-- tests for multiple yield/resume arguments + +local function eqtab (t1, t2) + assert(table.getn(t1) == table.getn(t2)) + for i,v in ipairs(t1) do + assert(t2[i] == v) + end +end + +_G.x = nil -- declare x +function foo (a, ...) + assert(coroutine.running() == f) + assert(coroutine.status(f) == "running") + local arg = {...} + for i=1,table.getn(arg) do + _G.x = {coroutine.yield(unpack(arg[i]))} + end + return unpack(a) +end + +f = coroutine.create(foo) +assert(type(f) == "thread" and coroutine.status(f) == "suspended") +assert(string.find(tostring(f), "thread")) +local s,a,b,c,d +s,a,b,c,d = coroutine.resume(f, {1,2,3}, {}, {1}, {'a', 'b', 'c'}) +assert(s and a == nil and coroutine.status(f) == "suspended") +s,a,b,c,d = coroutine.resume(f) +eqtab(_G.x, {}) +assert(s and a == 1 and b == nil) +s,a,b,c,d = coroutine.resume(f, 1, 2, 3) +eqtab(_G.x, {1, 2, 3}) +assert(s and a == 'a' and b == 'b' and c == 'c' and d == nil) +s,a,b,c,d = coroutine.resume(f, "xuxu") +eqtab(_G.x, {"xuxu"}) +assert(s and a == 1 and b == 2 and c == 3 and d == nil) +assert(coroutine.status(f) == "dead") +s, a = coroutine.resume(f, "xuxu") +assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead") + + +-- yields in tail calls +local function foo (i) return coroutine.yield(i) end +f = coroutine.wrap(function () + for i=1,10 do + assert(foo(i) == _G.x) + end + return 'a' +end) +for i=1,10 do _G.x = i; assert(f(i) == i) end +_G.x = 'xuxu'; assert(f('xuxu') == 'a') + +-- recursive +function pf (n, i) + coroutine.yield(n) + pf(n*i, i+1) +end + +f = coroutine.wrap(pf) +local s=1 +for i=1,10 do + assert(f(1, 1) == s) + s = s*i +end + +-- sieve +function gen (n) + return coroutine.wrap(function () + for i=2,n do coroutine.yield(i) end + end) +end + + +function filter (p, g) + return coroutine.wrap(function () + while 1 do + local n = g() + if n == nil then return end + if math.mod(n, p) ~= 0 then coroutine.yield(n) end + end + end) +end + +local x = gen(100) +local a = {} +while 1 do + local n = x() + if n == nil then break end + table.insert(a, n) + x = filter(n, x) +end + +assert(table.getn(a) == 25 and a[table.getn(a)] == 97) + + +-- errors in coroutines +function foo () + assert(debug.getinfo(1).currentline == debug.getinfo(foo).linedefined + 1) + assert(debug.getinfo(2).currentline == debug.getinfo(goo).linedefined) + coroutine.yield(3) + error(foo) +end + +function goo() foo() end +x = coroutine.wrap(goo) +assert(x() == 3) +local a,b = pcall(x) +assert(not a and b == foo) + +x = coroutine.create(goo) +a,b = coroutine.resume(x) +assert(a and b == 3) +a,b = coroutine.resume(x) +assert(not a and b == foo and coroutine.status(x) == "dead") +a,b = coroutine.resume(x) +assert(not a and string.find(b, "dead") and coroutine.status(x) == "dead") + + +-- co-routines x for loop +function all (a, n, k) + if k == 0 then coroutine.yield(a) + else + for i=1,n do + a[k] = i + all(a, n, k-1) + end + end +end + +local a = 0 +for t in coroutine.wrap(function () all({}, 5, 4) end) do + a = a+1 +end +assert(a == 5^4) + + +-- access to locals of collected corroutines +local C = {}; setmetatable(C, {__mode = "kv"}) +local x = coroutine.wrap (function () + local a = 10 + local function f () a = a+10; return a end + while true do + a = a+1 + coroutine.yield(f) + end + end) + +C[1] = x; + +local f = x() +assert(f() == 21 and x()() == 32 and x() == f) +x = nil +collectgarbage() +assert(C[1] == nil) +assert(f() == 43 and f() == 53) + + +-- old bug: attempt to resume itself + +function co_func (current_co) + assert(coroutine.running() == current_co) + assert(coroutine.resume(current_co) == false) + assert(coroutine.resume(current_co) == false) + return 10 +end + +local co = coroutine.create(co_func) +local a,b = coroutine.resume(co, co) +assert(a == true and b == 10) +assert(coroutine.resume(co, co) == false) +assert(coroutine.resume(co, co) == false) + +-- access to locals of erroneous coroutines +local x = coroutine.create (function () + local a = 10 + _G.f = function () a=a+1; return a end + error('x') + end) + +assert(not coroutine.resume(x)) +-- overwrite previous position of local `a' +assert(not coroutine.resume(x, 1, 1, 1, 1, 1, 1, 1)) +assert(_G.f() == 11) +assert(_G.f() == 12) + + +if not T then + (Message or print)('\a\n >>> testC not active: skipping yield/hook tests <<<\n\a') +else + + local turn + + function fact (t, x) + assert(turn == t) + if x == 0 then return 1 + else return x*fact(t, x-1) + end + end + + local A,B,a,b = 0,0,0,0 + + local x = coroutine.create(function () + T.setyhook("", 2) + A = fact("A", 10) + end) + + local y = coroutine.create(function () + T.setyhook("", 3) + B = fact("B", 11) + end) + + while A==0 or B==0 do + if A==0 then turn = "A"; T.resume(x) end + if B==0 then turn = "B"; T.resume(y) end + end + + assert(B/A == 11) +end + + +-- leaving a pending coroutine open +_X = coroutine.wrap(function () + local a = 10 + local x = function () a = a+1 end + coroutine.yield() + end) + +_X() + + +-- coroutine environments +co = coroutine.create(function () + coroutine.yield(getfenv(0)) + return loadstring("return a")() + end) + +a = {a = 15} +debug.setfenv(co, a) +assert(debug.getfenv(co) == a) +assert(select(2, coroutine.resume(co)) == a) +assert(select(2, coroutine.resume(co)) == a.a) + + +print'OK' diff --git a/tests/upstream/lua51/vendor/code.lua b/tests/upstream/lua51/vendor/code.lua new file mode 100644 index 00000000..efb13c7a --- /dev/null +++ b/tests/upstream/lua51/vendor/code.lua @@ -0,0 +1,143 @@ + +if T==nil then + (Message or print)('\a\n >>> testC not active: skipping opcode tests <<<\n\a') + return +end +print "testing code generation and optimizations" + + +-- this code gave an error for the code checker +do + local function f (a) + for k,v,w in a do end + end +end + + +function check (f, ...) + local c = T.listcode(f) + for i=1, arg.n do + -- print(arg[i], c[i]) + assert(string.find(c[i], '- '..arg[i]..' *%d')) + end + assert(c[arg.n+2] == nil) +end + + +function checkequal (a, b) + a = T.listcode(a) + b = T.listcode(b) + for i = 1, table.getn(a) do + a[i] = string.gsub(a[i], '%b()', '') -- remove line number + b[i] = string.gsub(b[i], '%b()', '') -- remove line number + assert(a[i] == b[i]) + end +end + + +-- some basic instructions +check(function () + (function () end){f()} +end, 'CLOSURE', 'NEWTABLE', 'GETGLOBAL', 'CALL', 'SETLIST', 'CALL', 'RETURN') + + +-- sequence of LOADNILs +check(function () + local a,b,c + local d; local e; + a = nil; d=nil +end, 'RETURN') + + +-- single return +check (function (a,b,c) return a end, 'RETURN') + + +-- infinite loops +check(function () while true do local a = -1 end end, +'LOADK', 'JMP', 'RETURN') + +check(function () while 1 do local a = -1 end end, +'LOADK', 'JMP', 'RETURN') + +check(function () repeat local x = 1 until false end, +'LOADK', 'JMP', 'RETURN') + +check(function () repeat local x until nil end, +'LOADNIL', 'JMP', 'RETURN') + +check(function () repeat local x = 1 until true end, +'LOADK', 'RETURN') + + +-- concat optimization +check(function (a,b,c,d) return a..b..c..d end, + 'MOVE', 'MOVE', 'MOVE', 'MOVE', 'CONCAT', 'RETURN') + +-- not +check(function () return not not nil end, 'LOADBOOL', 'RETURN') +check(function () return not not false end, 'LOADBOOL', 'RETURN') +check(function () return not not true end, 'LOADBOOL', 'RETURN') +check(function () return not not 1 end, 'LOADBOOL', 'RETURN') + +-- direct access to locals +check(function () + local a,b,c,d + a = b*2 + c[4], a[b] = -((a + d/-20.5 - a[b]) ^ a.x), b +end, + 'MUL', + 'DIV', 'ADD', 'GETTABLE', 'SUB', 'GETTABLE', 'POW', + 'UNM', 'SETTABLE', 'SETTABLE', 'RETURN') + + +-- direct access to constants +check(function () + local a,b + a.x = 0 + a.x = b + a[b] = 'y' + a = 1 - a + b = 1/a + b = 5+4 + a[true] = false +end, + 'SETTABLE', 'SETTABLE', 'SETTABLE', 'SUB', 'DIV', 'LOADK', + 'SETTABLE', 'RETURN') + +local function f () return -((2^8 + -(-1)) % 8)/2 * 4 - 3 end + +check(f, 'LOADK', 'RETURN') +assert(f() == -5) + +check(function () + local a,b,c + b[c], a = c, b + b[a], a = c, b + a, b = c, a + a = a +end, + 'MOVE', 'MOVE', 'SETTABLE', + 'MOVE', 'MOVE', 'MOVE', 'SETTABLE', + 'MOVE', 'MOVE', 'MOVE', + -- no code for a = a + 'RETURN') + + +-- x == nil , x ~= nil +checkequal(function () if (a==nil) then a=1 end; if a~=nil then a=1 end end, + function () if (a==9) then a=1 end; if a~=9 then a=1 end end) + +check(function () if a==nil then a=1 end end, +'GETGLOBAL', 'EQ', 'JMP', 'LOADK', 'SETGLOBAL', 'RETURN') + +-- de morgan +checkequal(function () local a; if not (a or b) then b=a end end, + function () local a; if (not a and not b) then b=a end end) + +checkequal(function (l) local a; return 0 <= a and a <= l end, + function (l) local a; return not (not(a >= 0) or not(a <= l)) end) + + +print 'OK' + diff --git a/tests/upstream/lua51/vendor/constructs.lua b/tests/upstream/lua51/vendor/constructs.lua new file mode 100644 index 00000000..470f8539 --- /dev/null +++ b/tests/upstream/lua51/vendor/constructs.lua @@ -0,0 +1,240 @@ +print "testing syntax" + +-- testing priorities + +assert(2^3^2 == 2^(3^2)); +assert(2^3*4 == (2^3)*4); +assert(2^-2 == 1/4 and -2^- -2 == - - -4); +assert(not nil and 2 and not(2>3 or 3<2)); +assert(-3-1-5 == 0+0-9); +assert(-2^2 == -4 and (-2)^2 == 4 and 2*2-3-1 == 0); +assert(2*1+3/3 == 3 and 1+2 .. 3*1 == "33"); +assert(not(2+1 > 3*1) and "a".."b" > "a"); + +assert(not ((true or false) and nil)) +assert( true or false and nil) + +local a,b = 1,nil; +assert(-(1 or 2) == -1 and (1 and 2)+(-1.25 or -4) == 0.75); +x = ((b or a)+1 == 2 and (10 or a)+1 == 11); assert(x); +x = (((2<3) or 1) == true and (2<3 and 4) == 4); assert(x); + +x,y=1,2; +assert((x>y) and x or y == 2); +x,y=2,1; +assert((x>y) and x or y == 2); + +assert(1234567890 == tonumber('1234567890') and 1234567890+1 == 1234567891) + + +-- silly loops +repeat until 1; repeat until true; +while false do end; while nil do end; + +do -- test old bug (first name could not be an `upvalue') + local a; function f(x) x={a=1}; x={x=1}; x={G=1} end +end + +function f (i) + if type(i) ~= 'number' then return i,'jojo'; end; + if i > 0 then return i, f(i-1); end; +end + +x = {f(3), f(5), f(10);}; +assert(x[1] == 3 and x[2] == 5 and x[3] == 10 and x[4] == 9 and x[12] == 1); +assert(x[nil] == nil) +x = {f'alo', f'xixi', nil}; +assert(x[1] == 'alo' and x[2] == 'xixi' and x[3] == nil); +x = {f'alo'..'xixi'}; +assert(x[1] == 'aloxixi') +x = {f{}} +assert(x[2] == 'jojo' and type(x[1]) == 'table') + + +local f = function (i) + if i < 10 then return 'a'; + elseif i < 20 then return 'b'; + elseif i < 30 then return 'c'; + end; +end + +assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == nil) + +for i=1,1000 do break; end; +n=100; +i=3; +t = {}; +a=nil +while not a do + a=0; for i=1,n do for i=i,1,-1 do a=a+1; t[i]=1; end; end; +end +assert(a == n*(n+1)/2 and i==3); +assert(t[1] and t[n] and not t[0] and not t[n+1]) + +function f(b) + local x = 1; + repeat + local a; + if b==1 then local b=1; x=10; break + elseif b==2 then x=20; break; + elseif b==3 then x=30; + else local a,b,c,d=math.sin(1); x=x+1; + end + until x>=12; + return x; +end; + +assert(f(1) == 10 and f(2) == 20 and f(3) == 30 and f(4)==12) + + +local f = function (i) + if i < 10 then return 'a' + elseif i < 20 then return 'b' + elseif i < 30 then return 'c' + else return 8 + end +end + +assert(f(3) == 'a' and f(12) == 'b' and f(26) == 'c' and f(100) == 8) + +local a, b = nil, 23 +x = {f(100)*2+3 or a, a or b+2} +assert(x[1] == 19 and x[2] == 25) +x = {f=2+3 or a, a = b+2} +assert(x.f == 5 and x.a == 25) + +a={y=1} +x = {a.y} +assert(x[1] == 1) + +function f(i) + while 1 do + if i>0 then i=i-1; + else return; end; + end; +end; + +function g(i) + while 1 do + if i>0 then i=i-1 + else return end + end +end + +f(10); g(10); + +do + function f () return 1,2,3; end + local a, b, c = f(); + assert(a==1 and b==2 and c==3) + a, b, c = (f()); + assert(a==1 and b==nil and c==nil) +end + +local a,b = 3 and f(); +assert(a==1 and b==nil) + +function g() f(); return; end; +assert(g() == nil) +function g() return nil or f() end +a,b = g() +assert(a==1 and b==nil) + +print'+'; + + +f = [[ +return function ( a , b , c , d , e ) + local x = a >= b or c or ( d and e ) or nil + return x +end , { a = 1 , b = 2 >= 1 , } or { 1 }; +]] +f = string.gsub(f, "%s+", "\n"); -- force a SETLINE between opcodes +f,a = loadstring(f)(); +assert(a.a == 1 and a.b) + +function g (a,b,c,d,e) + if not (a>=b or c or d and e or nil) then return 0; else return 1; end; +end + +function h (a,b,c,d,e) + while (a>=b or c or (d and e) or nil) do return 1; end; + return 0; +end; + +assert(f(2,1) == true and g(2,1) == 1 and h(2,1) == 1) +assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1) +assert(f(1,2,'a') +~= -- force SETLINE before nil +nil, "") +assert(f(1,2,'a') == 'a' and g(1,2,'a') == 1 and h(1,2,'a') == 1) +assert(f(1,2,nil,1,'x') == 'x' and g(1,2,nil,1,'x') == 1 and + h(1,2,nil,1,'x') == 1) +assert(f(1,2,nil,nil,'x') == nil and g(1,2,nil,nil,'x') == 0 and + h(1,2,nil,nil,'x') == 0) +assert(f(1,2,nil,1,nil) == nil and g(1,2,nil,1,nil) == 0 and + h(1,2,nil,1,nil) == 0) + +assert(1 and 2<3 == true and 2<3 and 'a'<'b' == true) +x = 2<3 and not 3; assert(x==false) +x = 2<1 or (2>1 and 'a'); assert(x=='a') + + +do + local a; if nil then a=1; else a=2; end; -- this nil comes as PUSHNIL 2 + assert(a==2) +end + +function F(a) + assert(debug.getinfo(1, "n").name == 'F') + return a,2,3 +end + +a,b = F(1)~=nil; assert(a == true and b == nil); +a,b = F(nil)==nil; assert(a == true and b == nil) + +---------------------------------------------------------------- +-- creates all combinations of +-- [not] ([not] arg op [not] (arg op [not] arg )) +-- and tests each one + +function ID(x) return x end + +function f(t, i) + local b = t.n + local res = math.mod(math.floor(i/c), b)+1 + c = c*b + return t[res] +end + +local arg = {" ( 1 < 2 ) ", " ( 1 >= 2 ) ", " F ( ) ", " nil "; n=4} + +local op = {" and ", " or ", " == ", " ~= "; n=4} + +local neg = {" ", " not "; n=2} + +local i = 0 +repeat + c = 1 + local s = f(neg, i)..'ID('..f(neg, i)..f(arg, i)..f(op, i).. + f(neg, i)..'ID('..f(arg, i)..f(op, i)..f(neg, i)..f(arg, i)..'))' + local s1 = string.gsub(s, 'ID', '') + K,X,NX,WX1,WX2 = nil + s = string.format([[ + local a = %s + local b = not %s + K = b + local xxx; + if %s then X = a else X = b end + if %s then NX = b else NX = a end + while %s do WX1 = a; break end + while %s do WX2 = a; break end + repeat if (%s) then break end; assert(b) until not(%s) + ]], s1, s, s1, s, s1, s, s1, s, s) + assert(loadstring(s))() + assert(X and not NX and not WX1 == K and not WX2 == K) + if math.mod(i,4000) == 0 then print('+') end + i = i+1 +until i==c + +print'OK' diff --git a/tests/upstream/lua51/vendor/db.lua b/tests/upstream/lua51/vendor/db.lua new file mode 100644 index 00000000..09496f6f --- /dev/null +++ b/tests/upstream/lua51/vendor/db.lua @@ -0,0 +1,499 @@ +-- testing debug library + +local function dostring(s) return assert(loadstring(s))() end + +print"testing debug library and debug information" + +do +local a=1 +end + +function test (s, l, p) + collectgarbage() -- avoid gc during trace + local function f (event, line) + assert(event == 'line') + local l = table.remove(l, 1) + if p then print(l, line) end + assert(l == line, "wrong trace!!") + end + debug.sethook(f,"l"); loadstring(s)(); debug.sethook() + assert(table.getn(l) == 0) +end + + +do + local a = debug.getinfo(print) + assert(a.what == "C" and a.short_src == "[C]") + local b = debug.getinfo(test, "SfL") + assert(b.name == nil and b.what == "Lua" and b.linedefined == 11 and + b.lastlinedefined == b.linedefined + 10 and + b.func == test and not string.find(b.short_src, "%[")) + assert(b.activelines[b.linedefined + 1] and + b.activelines[b.lastlinedefined]) + assert(not b.activelines[b.linedefined] and + not b.activelines[b.lastlinedefined + 1]) +end + + +-- test file and string names truncation +a = "function f () end" +local function dostring (s, x) return loadstring(s, x)() end +dostring(a) +assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a)) +dostring(a..string.format("; %s\n=1", string.rep('p', 400))) +assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$')) +dostring("\n"..a) +assert(debug.getinfo(f).short_src == '[string "..."]') +dostring(a, "") +assert(debug.getinfo(f).short_src == '[string ""]') +dostring(a, "@xuxu") +assert(debug.getinfo(f).short_src == "xuxu") +dostring(a, "@"..string.rep('p', 1000)..'t') +assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$")) +dostring(a, "=xuxu") +assert(debug.getinfo(f).short_src == "xuxu") +dostring(a, string.format("=%s", string.rep('x', 500))) +assert(string.find(debug.getinfo(f).short_src, "^x*")) +dostring(a, "=") +assert(debug.getinfo(f).short_src == "") +a = nil; f = nil; + + +repeat + local g = {x = function () + local a = debug.getinfo(2) + assert(a.name == 'f' and a.namewhat == 'local') + a = debug.getinfo(1) + assert(a.name == 'x' and a.namewhat == 'field') + return 'xixi' + end} + local f = function () return 1+1 and (not 1 or g.x()) end + assert(f() == 'xixi') + g = debug.getinfo(f) + assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name) + + function f (x, name) -- local! + name = name or 'f' + local a = debug.getinfo(1) + assert(a.name == name and a.namewhat == 'local') + return x + end + + -- breaks in different conditions + if 3>4 then break end; f() + if 3<4 then a=1 else break end; f() + while 1 do local x=10; break end; f() + local b = 1 + if 3>4 then return math.sin(1) end; f() + a = 3<4; f() + a = 3<4 or 1; f() + repeat local x=20; if 4>3 then f() else break end; f() until 1 + g = {} + f(g).x = f(2) and f(10)+f(9) + assert(g.x == f(19)) + function g(x) if not x then return 3 end return (x('a', 'x')) end + assert(g(f) == 'a') +until 1 + +test([[if +math.sin(1) +then + a=1 +else + a=2 +end +]], {2,4,7}) + +test([[-- +if nil then + a=1 +else + a=2 +end +]], {2,5,6}) + +test([[a=1 +repeat + a=a+1 +until a==3 +]], {1,3,4,3,4}) + +test([[ do + return +end +]], {2}) + +test([[local a +a=1 +while a<=3 do + a=a+1 +end +]], {2,3,4,3,4,3,4,3,5}) + +test([[while math.sin(1) do + if math.sin(1) + then + break + end +end +a=1]], {1,2,4,7}) + +test([[for i=1,3 do + a=i +end +]], {1,2,1,2,1,2,1,3}) + +test([[for i,v in pairs{'a','b'} do + a=i..v +end +]], {1,2,1,2,1,3}) + +test([[for i=1,4 do a=1 end]], {1,1,1,1,1}) + + + +print'+' + +a = {}; L = nil +local glob = 1 +local oldglob = glob +debug.sethook(function (e,l) + collectgarbage() -- force GC during a hook + local f, m, c = debug.gethook() + assert(m == 'crl' and c == 0) + if e == "line" then + if glob ~= oldglob then + L = l-1 -- get the first line where "glob" has changed + oldglob = glob + end + elseif e == "call" then + local f = debug.getinfo(2, "f").func + a[f] = 1 + else assert(e == "return") + end +end, "crl") + +function f(a,b) + collectgarbage() + local _, x = debug.getlocal(1, 1) + local _, y = debug.getlocal(1, 2) + assert(x == a and y == b) + assert(debug.setlocal(2, 3, "pera") == "AA".."AA") + assert(debug.setlocal(2, 4, "maçã") == "B") + x = debug.getinfo(2) + assert(x.func == g and x.what == "Lua" and x.name == 'g' and + x.nups == 0 and string.find(x.source, "^@.*db%.lua")) + glob = glob+1 + assert(debug.getinfo(1, "l").currentline == L+1) + assert(debug.getinfo(1, "l").currentline == L+2) +end + +function foo() + glob = glob+1 + assert(debug.getinfo(1, "l").currentline == L+1) +end; foo() -- set L +-- check line counting inside strings and empty lines + +_ = 'alo\ +alo' .. [[ + +]] +--[[ +]] +assert(debug.getinfo(1, "l").currentline == L+11) -- check count of lines + + +function g(...) + do local a,b,c; a=math.sin(40); end + local feijao + local AAAA,B = "xuxu", "mamão" + f(AAAA,B) + assert(AAAA == "pera" and B == "maçã") + do + local B = 13 + local x,y = debug.getlocal(1,5) + assert(x == 'B' and y == 13) + end +end + +g() + + +assert(a[f] and a[g] and a[assert] and a[debug.getlocal] and not a[print]) + + +-- tests for manipulating non-registered locals (C and Lua temporaries) + +local n, v = debug.getlocal(0, 1) +assert(v == 0 and n == "(*temporary)") +local n, v = debug.getlocal(0, 2) +assert(v == 2 and n == "(*temporary)") +assert(not debug.getlocal(0, 3)) +assert(not debug.getlocal(0, 0)) + +function f() + assert(select(2, debug.getlocal(2,3)) == 1) + assert(not debug.getlocal(2,4)) + debug.setlocal(2, 3, 10) + return 20 +end + +function g(a,b) return (a+1) + f() end + +assert(g(0,0) == 30) + + +debug.sethook(nil); +assert(debug.gethook() == nil) + + +-- testing access to function arguments + +X = nil +a = {} +function a:f (a, b, ...) local c = 13 end +debug.sethook(function (e) + assert(e == "call") + dostring("XX = 12") -- test dostring inside hooks + -- testing errors inside hooks + assert(not pcall(loadstring("a='joao'+1"))) + debug.sethook(function (e, l) + assert(debug.getinfo(2, "l").currentline == l) + local f,m,c = debug.gethook() + assert(e == "line") + assert(m == 'l' and c == 0) + debug.sethook(nil) -- hook is called only once + assert(not X) -- check that + X = {}; local i = 1 + local x,y + while 1 do + x,y = debug.getlocal(2, i) + if x==nil then break end + X[x] = y + i = i+1 + end + end, "l") +end, "c") + +a:f(1,2,3,4,5) +assert(X.self == a and X.a == 1 and X.b == 2 and X.arg.n == 3 and X.c == nil) +assert(XX == 12) +assert(debug.gethook() == nil) + + +-- testing upvalue access +local function getupvalues (f) + local t = {} + local i = 1 + while true do + local name, value = debug.getupvalue(f, i) + if not name then break end + assert(not t[name]) + t[name] = value + i = i + 1 + end + return t +end + +local a,b,c = 1,2,3 +local function foo1 (a) b = a; return c end +local function foo2 (x) a = x; return c+b end +assert(debug.getupvalue(foo1, 3) == nil) +assert(debug.getupvalue(foo1, 0) == nil) +assert(debug.setupvalue(foo1, 3, "xuxu") == nil) +local t = getupvalues(foo1) +assert(t.a == nil and t.b == 2 and t.c == 3) +t = getupvalues(foo2) +assert(t.a == 1 and t.b == 2 and t.c == 3) +assert(debug.setupvalue(foo1, 1, "xuxu") == "b") +assert(({debug.getupvalue(foo2, 3)})[2] == "xuxu") +-- cannot manipulate C upvalues from Lua +assert(debug.getupvalue(io.read, 1) == nil) +assert(debug.setupvalue(io.read, 1, 10) == nil) + + +-- testing count hooks +local a=0 +debug.sethook(function (e) a=a+1 end, "", 1) +a=0; for i=1,1000 do end; assert(1000 < a and a < 1012) +debug.sethook(function (e) a=a+1 end, "", 4) +a=0; for i=1,1000 do end; assert(250 < a and a < 255) +local f,m,c = debug.gethook() +assert(m == "" and c == 4) +debug.sethook(function (e) a=a+1 end, "", 4000) +a=0; for i=1,1000 do end; assert(a == 0) +debug.sethook(print, "", 2^24 - 1) -- count upperbound +local f,m,c = debug.gethook() +assert(({debug.gethook()})[3] == 2^24 - 1) +debug.sethook() + + +-- tests for tail calls +local function f (x) + if x then + assert(debug.getinfo(1, "S").what == "Lua") + local tail = debug.getinfo(2) + assert(not pcall(getfenv, 3)) + assert(tail.what == "tail" and tail.short_src == "(tail call)" and + tail.linedefined == -1 and tail.func == nil) + assert(debug.getinfo(3, "f").func == g1) + assert(getfenv(3)) + assert(debug.getinfo(4, "S").what == "tail") + assert(not pcall(getfenv, 5)) + assert(debug.getinfo(5, "S").what == "main") + assert(getfenv(5)) + print"+" + end +end + +function g(x) return f(x) end + +function g1(x) g(x) end + +local function h (x) local f=g1; return f(x) end + +h(true) + +local b = {} +debug.sethook(function (e) table.insert(b, e) end, "cr") +h(false) +debug.sethook() +local res = {"return", -- first return (from sethook) + "call", "call", "call", "call", + "return", "tail return", "return", "tail return", + "call", -- last call (to sethook) +} +for _, k in ipairs(res) do assert(k == table.remove(b, 1)) end + + +lim = 30000 +local function foo (x) + if x==0 then + assert(debug.getinfo(lim+2).what == "main") + for i=2,lim do assert(debug.getinfo(i, "S").what == "tail") end + else return foo(x-1) + end +end + +foo(lim) + + +print"+" + + +-- testing traceback + +assert(debug.traceback(print) == print) +assert(debug.traceback(print, 4) == print) +assert(string.find(debug.traceback("hi", 4), "^hi\n")) +assert(string.find(debug.traceback("hi"), "^hi\n")) +assert(not string.find(debug.traceback("hi"), "'traceback'")) +assert(string.find(debug.traceback("hi", 0), "'traceback'")) +assert(string.find(debug.traceback(), "^stack traceback:\n")) + +-- testing debugging of coroutines + +local function checktraceback (co, p) + local tb = debug.traceback(co) + local i = 0 + for l in string.gmatch(tb, "[^\n]+\n?") do + assert(i == 0 or string.find(l, p[i])) + i = i+1 + end + assert(p[i] == nil) +end + + +local function f (n) + if n > 0 then return f(n-1) + else coroutine.yield() end +end + +local co = coroutine.create(f) +coroutine.resume(co, 3) +checktraceback(co, {"yield", "db.lua", "tail", "tail", "tail"}) + + +co = coroutine.create(function (x) + local a = 1 + coroutine.yield(debug.getinfo(1, "l")) + coroutine.yield(debug.getinfo(1, "l").currentline) + return a + end) + +local tr = {} +local foo = function (e, l) table.insert(tr, l) end +debug.sethook(co, foo, "l") + +local _, l = coroutine.resume(co, 10) +local x = debug.getinfo(co, 1, "lfLS") +assert(x.currentline == l.currentline and x.activelines[x.currentline]) +assert(type(x.func) == "function") +for i=x.linedefined + 1, x.lastlinedefined do + assert(x.activelines[i]) + x.activelines[i] = nil +end +assert(next(x.activelines) == nil) -- no 'extra' elements +assert(debug.getinfo(co, 2) == nil) +local a,b = debug.getlocal(co, 1, 1) +assert(a == "x" and b == 10) +a,b = debug.getlocal(co, 1, 2) +assert(a == "a" and b == 1) +debug.setlocal(co, 1, 2, "hi") +assert(debug.gethook(co) == foo) +assert(table.getn(tr) == 2 and + tr[1] == l.currentline-1 and tr[2] == l.currentline) + +a,b,c = pcall(coroutine.resume, co) +assert(a and b and c == l.currentline+1) +checktraceback(co, {"yield", "in function <"}) + +a,b = coroutine.resume(co) +assert(a and b == "hi") +assert(table.getn(tr) == 4 and tr[4] == l.currentline+2) +assert(debug.gethook(co) == foo) +assert(debug.gethook() == nil) +checktraceback(co, {}) + + +-- check traceback of suspended (or dead with error) coroutines + +function f(i) if i==0 then error(i) else coroutine.yield(); f(i-1) end end + +co = coroutine.create(function (x) f(x) end) +a, b = coroutine.resume(co, 3) +t = {"'yield'", "'f'", "in function <"} +while coroutine.status(co) == "suspended" do + checktraceback(co, t) + a, b = coroutine.resume(co) + table.insert(t, 2, "'f'") -- one more recursive call to 'f' +end +t[1] = "'error'" +checktraceback(co, t) + + +-- test acessing line numbers of a coroutine from a resume inside +-- a C function (this is a known bug in Lua 5.0) + +local function g(x) + coroutine.yield(x) +end + +local function f (i) + debug.sethook(function () end, "l") + for j=1,1000 do + g(i+j) + end +end + +local co = coroutine.wrap(f) +co(10) +pcall(co) +pcall(co) + + +assert(type(debug.getregistry()) == "table") + + +print"OK" + diff --git a/tests/upstream/lua51/vendor/errors.lua b/tests/upstream/lua51/vendor/errors.lua new file mode 100644 index 00000000..3cc3211d --- /dev/null +++ b/tests/upstream/lua51/vendor/errors.lua @@ -0,0 +1,250 @@ +print("testing errors") + +function doit (s) + local f, msg = loadstring(s) + if f == nil then return msg end + local cond, msg = pcall(f) + return (not cond) and msg +end + + +function checkmessage (prog, msg) + assert(string.find(doit(prog), msg, 1, true)) +end + +function checksyntax (prog, extra, token, line) + local msg = doit(prog) + token = string.gsub(token, "(%p)", "%%%1") + local pt = string.format([[^%%[string ".*"%%]:%d: .- near '%s'$]], + line, token) + assert(string.find(msg, pt)) + assert(string.find(msg, msg, 1, true)) +end + + +-- test error message with no extra info +assert(doit("error('hi', 0)") == 'hi') + +-- test error message with no info +assert(doit("error()") == nil) + + +-- test common errors/errors that crashed in the past +assert(doit("unpack({}, 1, n=2^30)")) +assert(doit("a=math.sin()")) +assert(not doit("tostring(1)") and doit("tostring()")) +assert(doit"tonumber()") +assert(doit"repeat until 1; a") +checksyntax("break label", "", "label", 1) +assert(doit";") +assert(doit"a=1;;") +assert(doit"return;;") +assert(doit"assert(false)") +assert(doit"assert(nil)") +assert(doit"a=math.sin\n(3)") +assert(doit("function a (... , ...) end")) +assert(doit("function a (, ...) end")) + +checksyntax([[ + local a = {4 + +]], "'}' expected (to close '{' at line 1)", "", 3) + + +-- tests for better error messages + +checkmessage("a=1; bbbb=2; a=math.sin(3)+bbbb(3)", "global 'bbbb'") +checkmessage("a=1; local a,bbbb=2,3; a = math.sin(1) and bbbb(3)", + "local 'bbbb'") +checkmessage("a={}; do local a=1 end a:bbbb(3)", "method 'bbbb'") +checkmessage("local a={}; a.bbbb(3)", "field 'bbbb'") +assert(not string.find(doit"a={13}; local bbbb=1; a[bbbb](3)", "'bbbb'")) +checkmessage("a={13}; local bbbb=1; a[bbbb](3)", "number") + +aaa = nil +checkmessage("aaa.bbb:ddd(9)", "global 'aaa'") +checkmessage("local aaa={bbb=1}; aaa.bbb:ddd(9)", "field 'bbb'") +checkmessage("local aaa={bbb={}}; aaa.bbb:ddd(9)", "method 'ddd'") +checkmessage("local a,b,c; (function () a = b+1 end)()", "upvalue 'b'") +assert(not doit"local aaa={bbb={ddd=next}}; aaa.bbb:ddd(nil)") + +checkmessage("b=1; local aaa='a'; x=aaa+b", "local 'aaa'") +checkmessage("aaa={}; x=3/aaa", "global 'aaa'") +checkmessage("aaa='2'; b=nil;x=aaa*b", "global 'b'") +checkmessage("aaa={}; x=-aaa", "global 'aaa'") +assert(not string.find(doit"aaa={}; x=(aaa or aaa)+(aaa and aaa)", "'aaa'")) +assert(not string.find(doit"aaa={}; (aaa or aaa)()", "'aaa'")) + +checkmessage([[aaa=9 +repeat until 3==3 +local x=math.sin(math.cos(3)) +if math.sin(1) == x then return math.sin(1) end -- tail call +local a,b = 1, { + {x='a'..'b'..'c', y='b', z=x}, + {1,2,3,4,5} or 3+3<=3+3, + 3+1>3+1, + {d = x and aaa[x or y]}} +]], "global 'aaa'") + +checkmessage([[ +local x,y = {},1 +if math.sin(1) == 0 then return 3 end -- return +x.a()]], "field 'a'") + +checkmessage([[ +prefix = nil +insert = nil +while 1 do + local a + if nil then break end + insert(prefix, a) +end]], "global 'insert'") + +checkmessage([[ -- tail call + return math.sin("a") +]], "'sin'") + +checkmessage([[collectgarbage("nooption")]], "invalid option") + +checkmessage([[x = print .. "a"]], "concatenate") + +checkmessage("getmetatable(io.stdin).__gc()", "no value") + +print'+' + + +-- testing line error + +function lineerror (s) + local err,msg = pcall(loadstring(s)) + local line = string.match(msg, ":(%d+):") + return line and line+0 +end + +assert(lineerror"local a\n for i=1,'a' do \n print(i) \n end" == 2) +assert(lineerror"\n local a \n for k,v in 3 \n do \n print(k) \n end" == 3) +assert(lineerror"\n\n for k,v in \n 3 \n do \n print(k) \n end" == 4) +assert(lineerror"function a.x.y ()\na=a+1\nend" == 1) + +local p = [[ +function g() f() end +function f(x) error('a', X) end +g() +]] +X=3;assert(lineerror(p) == 3) +X=0;assert(lineerror(p) == nil) +X=1;assert(lineerror(p) == 2) +X=2;assert(lineerror(p) == 1) + +lineerror = nil + +C = 0 +local l = debug.getinfo(1, "l").currentline; function y () C=C+1; y() end + +local function checkstackmessage (m) + return (string.find(m, "^.-:%d+: stack overflow")) +end +assert(checkstackmessage(doit('y()'))) +assert(checkstackmessage(doit('y()'))) +assert(checkstackmessage(doit('y()'))) +-- teste de linhas em erro +C = 0 +local l1 +local function g() + l1 = debug.getinfo(1, "l").currentline; y() +end +local _, stackmsg = xpcall(g, debug.traceback) +local stack = {} +for line in string.gmatch(stackmsg, "[^\n]*") do + local curr = string.match(line, ":(%d+):") + if curr then table.insert(stack, tonumber(curr)) end +end +local i=1 +while stack[i] ~= l1 do + assert(stack[i] == l) + i = i+1 +end +assert(i > 15) + + +-- error in error handling +local res, msg = xpcall(error, error) +assert(not res and type(msg) == 'string') + +local function f (x) + if x==0 then error('a\n') + else + local aux = function () return f(x-1) end + local a,b = xpcall(aux, aux) + return a,b + end +end +f(3) + +-- non string messages +function f() error{msg='x'} end +res, msg = xpcall(f, function (r) return {msg=r.msg..'y'} end) +assert(msg.msg == 'xy') + +print('+') +checksyntax("syntax error", "", "error", 1) +checksyntax("1.000", "", "1.000", 1) +checksyntax("[[a]]", "", "[[a]]", 1) +checksyntax("'aa'", "", "'aa'", 1) + +-- test 255 as first char in a chunk +checksyntax("\255a = 1", "", "\255", 1) + +doit('I = loadstring("a=9+"); a=3') +assert(a==3 and I == nil) +print('+') + +lim = 1000 +if rawget(_G, "_soft") then lim = 100 end +for i=1,lim do + doit('a = ') + doit('a = 4+nil') +end + + +-- testing syntax limits +local function testrep (init, rep) + local s = "local a; "..init .. string.rep(rep, 400) + local a,b = loadstring(s) + assert(not a and string.find(b, "syntax levels")) +end +testrep("a=", "{") +testrep("a=", "(") +testrep("", "a(") +testrep("", "do ") +testrep("", "while a do ") +testrep("", "if a then else ") +testrep("", "function foo () ") +testrep("a=", "a..") +testrep("a=", "a^") + + +-- testing other limits +-- upvalues +local s = "function foo ()\n local " +for j = 1,70 do + s = s.."a"..j..", " +end +s = s.."b\n" +for j = 1,70 do + s = s.."function foo"..j.." ()\n a"..j.."=3\n" +end +local a,b = loadstring(s) +assert(string.find(b, "line 3")) + +-- local variables +s = "\nfunction foo ()\n local " +for j = 1,300 do + s = s.."a"..j..", " +end +s = s.."b\n" +local a,b = loadstring(s) +assert(string.find(b, "line 2")) + + +print('OK') diff --git a/tests/upstream/lua51/vendor/etc/ltests.c b/tests/upstream/lua51/vendor/etc/ltests.c new file mode 100644 index 00000000..b14608be --- /dev/null +++ b/tests/upstream/lua51/vendor/etc/ltests.c @@ -0,0 +1,1147 @@ +/* +** $Id: ltests.c,v 2.36 2006/01/10 13:13:06 roberto Exp $ +** Internal Module for Debugging of the Lua Implementation +** See Copyright Notice in lua.h +*/ + + +#include +#include +#include +#include +#include + +#define ltests_c +#define LUA_CORE + +#include "lua.h" + +#include "lapi.h" +#include "lauxlib.h" +#include "lcode.h" +#include "ldebug.h" +#include "ldo.h" +#include "lfunc.h" +#include "lmem.h" +#include "lopcodes.h" +#include "lstate.h" +#include "lstring.h" +#include "ltable.h" +#include "lualib.h" + + + +/* +** The whole module only makes sense with LUA_DEBUG on +*/ +#if defined(LUA_DEBUG) + + +int Trick = 0; + + +static lua_State *lua_state = NULL; + +int islocked = 0; + + +#define obj_at(L,k) (L->ci->base+(k) - 1) + + +static void setnameval (lua_State *L, const char *name, int val) { + lua_pushstring(L, name); + lua_pushinteger(L, val); + lua_settable(L, -3); +} + + +/* +** {====================================================================== +** Controlled version for realloc. +** ======================================================================= +*/ + +#define MARK 0x55 /* 01010101 (a nice pattern) */ + +#ifndef EXTERNMEMCHECK +/* full memory check */ +#define HEADER (sizeof(L_Umaxalign)) /* ensures maximum alignment for HEADER */ +#define MARKSIZE 16 /* size of marks after each block */ +#define blockhead(b) (cast(char *, b) - HEADER) +#define setsize(newblock, size) (*cast(size_t *, newblock) = size) +#define checkblocksize(b, size) (size == (*cast(size_t *, blockhead(b)))) +#define fillmem(mem,size) memset(mem, -MARK, size) +#else +/* external memory check: don't do it twice */ +#define HEADER 0 +#define MARKSIZE 0 +#define blockhead(b) (b) +#define setsize(newblock, size) /* empty */ +#define checkblocksize(b,size) (1) +#define fillmem(mem,size) /* empty */ +#endif + + +Memcontrol memcontrol = {0L, 0L, 0L, ULONG_MAX}; + + +static void *checkblock (void *block, size_t size) { + void *b = blockhead(block); + int i; + for (i=0;inumblocks--; + mc->total -= size; + } +} + + +void *debug_realloc (void *ud, void *block, size_t oldsize, size_t size) { + Memcontrol *mc = cast(Memcontrol *, ud); + lua_assert(oldsize == 0 || checkblocksize(block, oldsize)); + if (size == 0) { + freeblock(mc, block, oldsize); + return NULL; + } + else if (size > oldsize && mc->total+size-oldsize > mc->memlimit) + return NULL; /* to test memory allocation errors */ + else { + void *newblock; + int i; + size_t realsize = HEADER+size+MARKSIZE; + size_t commonsize = (oldsize < size) ? oldsize : size; + if (realsize < size) return NULL; /* overflow! */ + newblock = malloc(realsize); /* alloc a new block */ + if (newblock == NULL) return NULL; + if (block) { + memcpy(cast(char *, newblock)+HEADER, block, commonsize); + freeblock(mc, block, oldsize); /* erase (and check) old copy */ + } + /* initialize new part of the block with something `weird' */ + fillmem(cast(char *, newblock)+HEADER+commonsize, size-commonsize); + mc->total += size; + if (mc->total > mc->maxmem) + mc->maxmem = mc->total; + mc->numblocks++; + setsize(newblock, size); + for (i=0;igcstate == GCSpropagate) + return !isblack(f) || !iswhite(t); + else if (g->gcstate == GCSfinalize) + return iswhite(f); + else + return 1; +} + + +static void printobj (global_State *g, GCObject *o) { + int i = 0; + GCObject *p; + for (p = g->rootgc; p != o && p != NULL; p = p->gch.next) i++; + if (p == NULL) i = -1; + printf("%d:%s(%p)-%c(%02X)", i, luaT_typenames[o->gch.tt], (void *)o, + isdead(g,o)?'d':isblack(o)?'b':iswhite(o)?'w':'g', o->gch.marked); +} + + +static int testobjref (global_State *g, GCObject *f, GCObject *t) { + int r = testobjref1(g,f,t); + if (!r) { + printf("%d(%02X) - ", g->gcstate, g->currentwhite); + printobj(g, f); + printf("\t-> "); + printobj(g, t); + printf("\n"); + } + return r; +} + +#define checkobjref(g,f,t) lua_assert(testobjref(g,f,obj2gco(t))) + +#define checkvalref(g,f,t) lua_assert(!iscollectable(t) || \ + ((ttype(t) == (t)->value.gc->gch.tt) && testobjref(g,f,gcvalue(t)))) + + + +static void checktable (global_State *g, Table *h) { + int i; + int weakkey = 0; + int weakvalue = 0; + const TValue *mode; + GCObject *hgc = obj2gco(h); + if (h->metatable) + checkobjref(g, hgc, h->metatable); + mode = gfasttm(g, h->metatable, TM_MODE); + if (mode && ttisstring(mode)) { /* is there a weak mode? */ + weakkey = (strchr(svalue(mode), 'k') != NULL); + weakvalue = (strchr(svalue(mode), 'v') != NULL); + } + i = h->sizearray; + while (i--) + checkvalref(g, hgc, &h->array[i]); + i = sizenode(h); + while (i--) { + Node *n = gnode(h, i); + if (!ttisnil(gval(n))) { + lua_assert(!ttisnil(gkey(n))); + checkvalref(g, hgc, gkey(n)); + checkvalref(g, hgc, gval(n)); + } + } +} + + +/* +** All marks are conditional because a GC may happen while the +** prototype is still being created +*/ +static void checkproto (global_State *g, Proto *f) { + int i; + GCObject *fgc = obj2gco(f); + if (f->source) checkobjref(g, fgc, f->source); + for (i=0; isizek; i++) { + if (ttisstring(f->k+i)) + checkobjref(g, fgc, rawtsvalue(f->k+i)); + } + for (i=0; isizeupvalues; i++) { + if (f->upvalues[i]) + checkobjref(g, fgc, f->upvalues[i]); + } + for (i=0; isizep; i++) { + if (f->p[i]) + checkobjref(g, fgc, f->p[i]); + } + for (i=0; isizelocvars; i++) { + if (f->locvars[i].varname) + checkobjref(g, fgc, f->locvars[i].varname); + } +} + + + +static void checkclosure (global_State *g, Closure *cl) { + GCObject *clgc = obj2gco(cl); + checkobjref(g, clgc, cl->l.env); + if (cl->c.isC) { + int i; + for (i=0; ic.nupvalues; i++) + checkvalref(g, clgc, &cl->c.upvalue[i]); + } + else { + int i; + lua_assert(cl->l.nupvalues == cl->l.p->nups); + checkobjref(g, clgc, cl->l.p); + for (i=0; il.nupvalues; i++) { + if (cl->l.upvals[i]) { + lua_assert(cl->l.upvals[i]->tt == LUA_TUPVAL); + checkobjref(g, clgc, cl->l.upvals[i]); + } + } + } +} + + +static void checkstack (global_State *g, lua_State *L1) { + StkId o; + CallInfo *ci; + GCObject *uvo; + lua_assert(!isdead(g, obj2gco(L1))); + for (uvo = L1->openupval; uvo != NULL; uvo = uvo->gch.next) { + UpVal *uv = gco2uv(uvo); + lua_assert(uv->v != &uv->u.value); /* must be open */ + lua_assert(!isblack(uvo)); /* open upvalues cannot be black */ + } + checkliveness(g, gt(L1)); + if (L1->base_ci) { + for (ci = L1->base_ci; ci <= L1->ci; ci++) { + lua_assert(ci->top <= L1->stack_last); + lua_assert(lua_checkpc(L1, ci)); + } + } + else lua_assert(L1->size_ci == 0); + if (L1->stack) { + for (o = L1->stack; o < L1->top; o++) + checkliveness(g, o); + } + else lua_assert(L1->stacksize == 0); +} + + +static void checkobject (global_State *g, GCObject *o) { + if (isdead(g, o)) +/* lua_assert(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);*/ +{ if (!(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep)) +printf(">>> %d %s %02x\n", g->gcstate, luaT_typenames[o->gch.tt], o->gch.marked); +} + else { + if (g->gcstate == GCSfinalize) + lua_assert(iswhite(o)); + switch (o->gch.tt) { + case LUA_TUPVAL: { + UpVal *uv = gco2uv(o); + lua_assert(uv->v == &uv->u.value); /* must be closed */ + lua_assert(!isgray(o)); /* closed upvalues are never gray */ + checkvalref(g, o, uv->v); + break; + } + case LUA_TUSERDATA: { + Table *mt = gco2u(o)->metatable; + if (mt) checkobjref(g, o, mt); + break; + } + case LUA_TTABLE: { + checktable(g, gco2h(o)); + break; + } + case LUA_TTHREAD: { + checkstack(g, gco2th(o)); + break; + } + case LUA_TFUNCTION: { + checkclosure(g, gco2cl(o)); + break; + } + case LUA_TPROTO: { + checkproto(g, gco2p(o)); + break; + } + default: lua_assert(0); + } + } +} + + +int lua_checkpc (lua_State *L, pCallInfo ci) { + if (ci == L->base_ci || !f_isLua(ci)) return 1; + else { + Proto *p = ci_func(ci)->l.p; + if (ci < L->ci) + return p->code <= ci->savedpc && ci->savedpc <= p->code + p->sizecode; + else + return p->code <= L->savedpc && L->savedpc <= p->code + p->sizecode; + } +} + + +int lua_checkmemory (lua_State *L) { + global_State *g = G(L); + GCObject *o; + UpVal *uv; + checkstack(g, g->mainthread); + for (o = g->rootgc; o != obj2gco(g->mainthread); o = o->gch.next) + checkobject(g, o); + for (o = o->gch.next; o != NULL; o = o->gch.next) { + lua_assert(o->gch.tt == LUA_TUSERDATA); + checkobject(g, o); + } + for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) { + lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv); + lua_assert(uv->v != &uv->u.value); /* must be open */ + lua_assert(!isblack(obj2gco(uv))); /* open upvalues are never black */ + checkvalref(g, obj2gco(uv), uv->v); + } + return 0; +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Disassembler +** ======================================================= +*/ + + +static char *buildop (Proto *p, int pc, char *buff) { + Instruction i = p->code[pc]; + OpCode o = GET_OPCODE(i); + const char *name = luaP_opnames[o]; + int line = getline(p, pc); + sprintf(buff, "(%4d) %4d - ", line, pc); + switch (getOpMode(o)) { + case iABC: + sprintf(buff+strlen(buff), "%-12s%4d %4d %4d", name, + GETARG_A(i), GETARG_B(i), GETARG_C(i)); + break; + case iABx: + sprintf(buff+strlen(buff), "%-12s%4d %4d", name, GETARG_A(i), GETARG_Bx(i)); + break; + case iAsBx: + sprintf(buff+strlen(buff), "%-12s%4d %4d", name, GETARG_A(i), GETARG_sBx(i)); + break; + } + return buff; +} + + +#if 0 +void luaI_printcode (Proto *pt, int size) { + int pc; + for (pc=0; pcl.p; + lua_newtable(L); + setnameval(L, "maxstack", p->maxstacksize); + setnameval(L, "numparams", p->numparams); + for (pc=0; pcsizecode; pc++) { + char buff[100]; + lua_pushinteger(L, pc+1); + lua_pushstring(L, buildop(p, pc, buff)); + lua_settable(L, -3); + } + return 1; +} + + +static int listk (lua_State *L) { + Proto *p; + int i; + luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), + 1, "Lua function expected"); + p = clvalue(obj_at(L, 1))->l.p; + lua_createtable(L, p->sizek, 0); + for (i=0; isizek; i++) { + luaA_pushobject(L, p->k+i); + lua_rawseti(L, -2, i+1); + } + return 1; +} + + +static int listlocals (lua_State *L) { + Proto *p; + int pc = luaL_checkint(L, 2) - 1; + int i = 0; + const char *name; + luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), + 1, "Lua function expected"); + p = clvalue(obj_at(L, 1))->l.p; + while ((name = luaF_getlocalname(p, ++i, pc)) != NULL) + lua_pushstring(L, name); + return i-1; +} + +/* }====================================================== */ + + + + +static int get_limits (lua_State *L) { + lua_createtable(L, 0, 5); + setnameval(L, "BITS_INT", LUAI_BITSINT); + setnameval(L, "LFPF", LFIELDS_PER_FLUSH); + setnameval(L, "MAXVARS", LUAI_MAXVARS); + setnameval(L, "MAXSTACK", MAXSTACK); + setnameval(L, "MAXUPVALUES", LUAI_MAXUPVALUES); + setnameval(L, "NUM_OPCODES", NUM_OPCODES); + return 1; +} + + +static int mem_query (lua_State *L) { + if (lua_isnone(L, 1)) { + lua_pushinteger(L, memcontrol.total); + lua_pushinteger(L, memcontrol.numblocks); + lua_pushinteger(L, memcontrol.maxmem); + return 3; + } + else { + memcontrol.memlimit = luaL_checkint(L, 1); + return 0; + } +} + + +static int settrick (lua_State *L) { + Trick = lua_tointeger(L, 1); + return 0; +} + + +/*static int set_gcstate (lua_State *L) { + static const char *const state[] = {"propagate", "sweep", "finalize"}; + return 0; +}*/ + + +static int get_gccolor (lua_State *L) { + TValue *o; + luaL_checkany(L, 1); + o = obj_at(L, 1); + if (!iscollectable(o)) + lua_pushstring(L, "no collectable"); + else + lua_pushstring(L, iswhite(gcvalue(o)) ? "white" : + isblack(gcvalue(o)) ? "black" : "grey"); + return 1; +} + + +static int gcstate (lua_State *L) { + switch(G(L)->gcstate) { + case GCSpropagate: lua_pushstring(L, "propagate"); break; + case GCSsweepstring: lua_pushstring(L, "sweep strings"); break; + case GCSsweep: lua_pushstring(L, "sweep"); break; + case GCSfinalize: lua_pushstring(L, "finalize"); break; + } + return 1; +} + + +static int hash_query (lua_State *L) { + if (lua_isnone(L, 2)) { + luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, "string expected"); + lua_pushinteger(L, tsvalue(obj_at(L, 1))->hash); + } + else { + TValue *o = obj_at(L, 1); + Table *t; + luaL_checktype(L, 2, LUA_TTABLE); + t = hvalue(obj_at(L, 2)); + lua_pushinteger(L, luaH_mainposition(t, o) - t->node); + } + return 1; +} + + +static int stacklevel (lua_State *L) { + unsigned long a = 0; + lua_pushinteger(L, (L->top - L->stack)); + lua_pushinteger(L, (L->stack_last - L->stack)); + lua_pushinteger(L, (L->ci - L->base_ci)); + lua_pushinteger(L, (L->end_ci - L->base_ci)); + lua_pushinteger(L, (unsigned long)&a); + return 5; +} + + +static int table_query (lua_State *L) { + const Table *t; + int i = luaL_optint(L, 2, -1); + luaL_checktype(L, 1, LUA_TTABLE); + t = hvalue(obj_at(L, 1)); + if (i == -1) { + lua_pushinteger(L, t->sizearray); + lua_pushinteger(L, luaH_isdummy(t->node) ? 0 : sizenode(t)); + lua_pushinteger(L, t->lastfree - t->node); + } + else if (i < t->sizearray) { + lua_pushinteger(L, i); + luaA_pushobject(L, &t->array[i]); + lua_pushnil(L); + } + else if ((i -= t->sizearray) < sizenode(t)) { + if (!ttisnil(gval(gnode(t, i))) || + ttisnil(gkey(gnode(t, i))) || + ttisnumber(gkey(gnode(t, i)))) { + luaA_pushobject(L, key2tval(gnode(t, i))); + } + else + lua_pushliteral(L, ""); + luaA_pushobject(L, gval(gnode(t, i))); + if (gnext(&t->node[i])) + lua_pushinteger(L, gnext(&t->node[i]) - t->node); + else + lua_pushnil(L); + } + return 3; +} + + +static int string_query (lua_State *L) { + stringtable *tb = &G(L)->strt; + int s = luaL_optint(L, 2, 0) - 1; + if (s==-1) { + lua_pushinteger(L ,tb->nuse); + lua_pushinteger(L ,tb->size); + return 2; + } + else if (s < tb->size) { + GCObject *ts; + int n = 0; + for (ts = tb->hash[s]; ts; ts = ts->gch.next) { + setsvalue2s(L, L->top, gco2ts(ts)); + incr_top(L); + n++; + } + return n; + } + return 0; +} + + +static int tref (lua_State *L) { + int level = lua_gettop(L); + int lock = luaL_optint(L, 2, 1); + luaL_checkany(L, 1); + lua_pushvalue(L, 1); + lua_pushinteger(L, lua_ref(L, lock)); + lua_assert(lua_gettop(L) == level+1); /* +1 for result */ + return 1; +} + +static int getref (lua_State *L) { + int level = lua_gettop(L); + lua_getref(L, luaL_checkint(L, 1)); + lua_assert(lua_gettop(L) == level+1); + return 1; +} + +static int unref (lua_State *L) { + int level = lua_gettop(L); + lua_unref(L, luaL_checkint(L, 1)); + lua_assert(lua_gettop(L) == level); + return 0; +} + + +static int upvalue (lua_State *L) { + int n = luaL_checkint(L, 2); + luaL_checktype(L, 1, LUA_TFUNCTION); + if (lua_isnone(L, 3)) { + const char *name = lua_getupvalue(L, 1, n); + if (name == NULL) return 0; + lua_pushstring(L, name); + return 2; + } + else { + const char *name = lua_setupvalue(L, 1, n); + lua_pushstring(L, name); + return 1; + } +} + + +static int newuserdata (lua_State *L) { + size_t size = luaL_checkint(L, 1); + char *p = cast(char *, lua_newuserdata(L, size)); + while (size--) *p++ = '\0'; + return 1; +} + + +static int pushuserdata (lua_State *L) { + lua_pushlightuserdata(L, cast(void *, luaL_checkint(L, 1))); + return 1; +} + + +static int udataval (lua_State *L) { + lua_pushinteger(L, cast(long, lua_touserdata(L, 1))); + return 1; +} + + +static int doonnewstack (lua_State *L) { + lua_State *L1 = lua_newthread(L); + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + int status = luaL_loadbuffer(L1, s, l, s); + if (status == 0) + status = lua_pcall(L1, 0, 0, 0); + lua_pushinteger(L, status); + return 1; +} + + +static int s2d (lua_State *L) { + lua_pushnumber(L, *cast(const double *, luaL_checkstring(L, 1))); + return 1; +} + + +static int d2s (lua_State *L) { + double d = luaL_checknumber(L, 1); + lua_pushlstring(L, cast(char *, &d), sizeof(d)); + return 1; +} + + +static int num2int (lua_State *L) { + lua_pushinteger(L, lua_tointeger(L, 1)); + return 1; +} + + +static int newstate (lua_State *L) { + void *ud; + lua_Alloc f = lua_getallocf(L, &ud); + lua_State *L1 = lua_newstate(f, ud); + if (L1) + lua_pushinteger(L, (unsigned long)L1); + else + lua_pushnil(L); + return 1; +} + + +static int loadlib (lua_State *L) { + static const luaL_Reg libs[] = { + {"baselibopen", luaopen_base}, + {"dblibopen", luaopen_debug}, + {"iolibopen", luaopen_io}, + {"mathlibopen", luaopen_math}, + {"strlibopen", luaopen_string}, + {"tablibopen", luaopen_table}, + {"packageopen", luaopen_package}, + {NULL, NULL} + }; + lua_State *L1 = cast(lua_State *, + cast(unsigned long, luaL_checknumber(L, 1))); + lua_pushvalue(L1, LUA_GLOBALSINDEX); + luaL_register(L1, NULL, libs); + return 0; +} + +static int closestate (lua_State *L) { + lua_State *L1 = cast(lua_State *, cast(unsigned long, luaL_checknumber(L, 1))); + lua_close(L1); + return 0; +} + +static int doremote (lua_State *L) { + lua_State *L1 = cast(lua_State *,cast(unsigned long,luaL_checknumber(L, 1))); + size_t lcode; + const char *code = luaL_checklstring(L, 2, &lcode); + int status; + lua_settop(L1, 0); + status = luaL_loadbuffer(L1, code, lcode, code); + if (status == 0) + status = lua_pcall(L1, 0, LUA_MULTRET, 0); + if (status != 0) { + lua_pushnil(L); + lua_pushinteger(L, status); + lua_pushstring(L, lua_tostring(L1, -1)); + return 3; + } + else { + int i = 0; + while (!lua_isnone(L1, ++i)) + lua_pushstring(L, lua_tostring(L1, i)); + lua_pop(L1, i-1); + return i-1; + } +} + + +static int log2_aux (lua_State *L) { + lua_pushinteger(L, luaO_log2(luaL_checkint(L, 1))); + return 1; +} + +static int int2fb_aux (lua_State *L) { + int b = luaO_int2fb(luaL_checkint(L, 1)); + lua_pushinteger(L, b); + lua_pushinteger(L, luaO_fb2int(b)); + return 2; +} + + + +/* +** {====================================================== +** function to test the API with C. It interprets a kind of assembler +** language with calls to the API, so the test can be driven by Lua code +** ======================================================= +*/ + +static const char *const delimits = " \t\n,;"; + +static void skip (const char **pc) { + while (**pc != '\0' && strchr(delimits, **pc)) (*pc)++; +} + +static int getnum_aux (lua_State *L, const char **pc) { + int res = 0; + int sig = 1; + skip(pc); + if (**pc == '.') { + res = cast_int(lua_tonumber(L, -1)); + lua_pop(L, 1); + (*pc)++; + return res; + } + else if (**pc == '-') { + sig = -1; + (*pc)++; + } + while (isdigit(cast_int(**pc))) res = res*10 + (*(*pc)++) - '0'; + return sig*res; +} + +static const char *getname_aux (char *buff, const char **pc) { + int i = 0; + skip(pc); + while (**pc != '\0' && !strchr(delimits, **pc)) + buff[i++] = *(*pc)++; + buff[i] = '\0'; + return buff; +} + + +static int getindex_aux (lua_State *L, const char **pc) { + skip(pc); + switch (*(*pc)++) { + case 'R': return LUA_REGISTRYINDEX; + case 'G': return LUA_GLOBALSINDEX; + case 'E': return LUA_ENVIRONINDEX; + case 'U': return lua_upvalueindex(getnum_aux(L, pc)); + default: (*pc)--; return getnum_aux(L, pc); + } +} + +#define EQ(s1) (strcmp(s1, inst) == 0) + +#define getnum (getnum_aux(L, &pc)) +#define getname (getname_aux(buff, &pc)) +#define getindex (getindex_aux(L, &pc)) + + +static int testC (lua_State *L) { + char buff[30]; + lua_State *L1; + const char *pc; + if (lua_isnumber(L, 1)) { + L1 = cast(lua_State *,cast(unsigned long,luaL_checknumber(L, 1))); + pc = luaL_checkstring(L, 2); + } + else { + L1 = L; + pc = luaL_checkstring(L, 1); + } + for (;;) { + const char *inst = getname; + if EQ("") return 0; + else if EQ("isnumber") { + lua_pushinteger(L1, lua_isnumber(L1, getindex)); + } + else if EQ("isstring") { + lua_pushinteger(L1, lua_isstring(L1, getindex)); + } + else if EQ("istable") { + lua_pushinteger(L1, lua_istable(L1, getindex)); + } + else if EQ("iscfunction") { + lua_pushinteger(L1, lua_iscfunction(L1, getindex)); + } + else if EQ("isfunction") { + lua_pushinteger(L1, lua_isfunction(L1, getindex)); + } + else if EQ("isuserdata") { + lua_pushinteger(L1, lua_isuserdata(L1, getindex)); + } + else if EQ("isudataval") { + lua_pushinteger(L1, lua_islightuserdata(L1, getindex)); + } + else if EQ("isnil") { + lua_pushinteger(L1, lua_isnil(L1, getindex)); + } + else if EQ("isnull") { + lua_pushinteger(L1, lua_isnone(L1, getindex)); + } + else if EQ("tonumber") { + lua_pushnumber(L1, lua_tonumber(L1, getindex)); + } + else if EQ("tostring") { + const char *s = lua_tostring(L1, getindex); + lua_pushstring(L1, s); + } + else if EQ("objsize") { + lua_pushinteger(L1, lua_objlen(L1, getindex)); + } + else if EQ("tocfunction") { + lua_pushcfunction(L1, lua_tocfunction(L1, getindex)); + } + else if EQ("return") { + return getnum; + } + else if EQ("gettop") { + lua_pushinteger(L1, lua_gettop(L1)); + } + else if EQ("settop") { + lua_settop(L1, getnum); + } + else if EQ("pop") { + lua_pop(L1, getnum); + } + else if EQ("pushnum") { + lua_pushinteger(L1, getnum); + } + else if EQ("pushstring") { + lua_pushstring(L1, getname); + } + else if EQ("pushnil") { + lua_pushnil(L1); + } + else if EQ("pushbool") { + lua_pushboolean(L1, getnum); + } + else if EQ("newuserdata") { + lua_newuserdata(L1, getnum); + } + else if EQ("tobool") { + lua_pushinteger(L1, lua_toboolean(L1, getindex)); + } + else if EQ("pushvalue") { + lua_pushvalue(L1, getindex); + } + else if EQ("pushcclosure") { + lua_pushcclosure(L1, testC, getnum); + } + else if EQ("remove") { + lua_remove(L1, getnum); + } + else if EQ("insert") { + lua_insert(L1, getnum); + } + else if EQ("replace") { + lua_replace(L1, getindex); + } + else if EQ("gettable") { + lua_gettable(L1, getindex); + } + else if EQ("settable") { + lua_settable(L1, getindex); + } + else if EQ("next") { + lua_next(L1, -2); + } + else if EQ("concat") { + lua_concat(L1, getnum); + } + else if EQ("lessthan") { + int a = getindex; + lua_pushboolean(L1, lua_lessthan(L1, a, getindex)); + } + else if EQ("equal") { + int a = getindex; + lua_pushboolean(L1, lua_equal(L1, a, getindex)); + } + else if EQ("rawcall") { + int narg = getnum; + int nres = getnum; + lua_call(L1, narg, nres); + } + else if EQ("call") { + int narg = getnum; + int nres = getnum; + lua_pcall(L1, narg, nres, 0); + } + else if EQ("loadstring") { + size_t sl; + const char *s = luaL_checklstring(L1, getnum, &sl); + luaL_loadbuffer(L1, s, sl, s); + } + else if EQ("loadfile") { + luaL_loadfile(L1, luaL_checkstring(L1, getnum)); + } + else if EQ("setmetatable") { + lua_setmetatable(L1, getindex); + } + else if EQ("getmetatable") { + if (lua_getmetatable(L1, getindex) == 0) + lua_pushnil(L1); + } + else if EQ("type") { + lua_pushstring(L1, luaL_typename(L1, getnum)); + } + else if EQ("getn") { + int i = getindex; + lua_pushinteger(L1, luaL_getn(L1, i)); + } +#ifndef luaL_setn + else if EQ("setn") { + int i = getindex; + int n = cast_int(lua_tonumber(L1, -1)); + luaL_setn(L1, i, n); + lua_pop(L1, 1); + } +#endif + else if EQ("throw") { +#if defined(__cplusplus) +static struct X { int x; } x; + throw x; +#else + luaL_error(L1, "C++"); +#endif + break; + } + else luaL_error(L, "unknown instruction %s", buff); + } + return 0; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** tests for yield inside hooks +** ======================================================= +*/ + +static void yieldf (lua_State *L, lua_Debug *ar) { + lua_yield(L, 0); +} + +static int setyhook (lua_State *L) { + if (lua_isnoneornil(L, 1)) + lua_sethook(L, NULL, 0, 0); /* turn off hooks */ + else { + const char *smask = luaL_checkstring(L, 1); + int count = luaL_optint(L, 2, 0); + int mask = 0; + if (strchr(smask, 'l')) mask |= LUA_MASKLINE; + if (count > 0) mask |= LUA_MASKCOUNT; + lua_sethook(L, yieldf, mask, count); + } + return 0; +} + + +static int coresume (lua_State *L) { + int status; + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, co, 1, "coroutine expected"); + status = lua_resume(co, 0); + if (status != 0) { + lua_pushboolean(L, 0); + lua_insert(L, -2); + return 2; /* return false + error message */ + } + else { + lua_pushboolean(L, 1); + return 1; + } +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** tests auxlib functions +** ======================================================= +*/ + +static int auxgsub (lua_State *L) { + const char *s1 = luaL_checkstring(L, 1); + const char *s2 = luaL_checkstring(L, 2); + const char *s3 = luaL_checkstring(L, 3); + lua_settop(L, 3); + luaL_gsub(L, s1, s2, s3); + lua_assert(lua_gettop(L) == 4); + return 1; +} + + +/* }====================================================== */ + + + +static const struct luaL_Reg tests_funcs[] = { + {"checkmemory", lua_checkmemory}, + {"closestate", closestate}, + {"d2s", d2s}, + {"doonnewstack", doonnewstack}, + {"doremote", doremote}, + {"gccolor", get_gccolor}, + {"gcstate", gcstate}, + {"getref", getref}, + {"gsub", auxgsub}, + {"hash", hash_query}, + {"int2fb", int2fb_aux}, + {"limits", get_limits}, + {"listcode", listcode}, + {"listk", listk}, + {"listlocals", listlocals}, + {"loadlib", loadlib}, + {"log2", log2_aux}, + {"newstate", newstate}, + {"newuserdata", newuserdata}, + {"num2int", num2int}, + {"pushuserdata", pushuserdata}, + {"querystr", string_query}, + {"querytab", table_query}, + {"ref", tref}, + {"resume", coresume}, + {"s2d", s2d}, + {"setyhook", setyhook}, + {"stacklevel", stacklevel}, + {"testC", testC}, + {"totalmem", mem_query}, + {"trick", settrick}, + {"udataval", udataval}, + {"unref", unref}, + {"upvalue", upvalue}, + {NULL, NULL} +}; + + +int luaB_opentests (lua_State *L) { + void *ud; + lua_assert(lua_getallocf(L, &ud) == debug_realloc); + lua_assert(ud == cast(void *, &memcontrol)); + lua_setallocf(L, lua_getallocf(L, NULL), ud); + lua_state = L; /* keep first state to be opened */ + luaL_register(L, "T", tests_funcs); + return 0; +} + + +#undef main +int main (int argc, char *argv[]) { + int ret; + char *limit = getenv("MEMLIMIT"); + if (limit) + memcontrol.memlimit = strtoul(limit, NULL, 10); + ret = l_main(argc, argv); + lua_assert(memcontrol.numblocks == 0); + lua_assert(memcontrol.total == 0); + return ret; +} + +#endif diff --git a/tests/upstream/lua51/vendor/etc/ltests.h b/tests/upstream/lua51/vendor/etc/ltests.h new file mode 100644 index 00000000..e5702127 --- /dev/null +++ b/tests/upstream/lua51/vendor/etc/ltests.h @@ -0,0 +1,92 @@ +/* +** $Id: ltests.h,v 2.17 2005/12/27 17:12:00 roberto Exp $ +** Internal Header for Debugging of the Lua Implementation +** See Copyright Notice in lua.h +*/ + +#ifndef ltests_h +#define ltests_h + + +#include + + +#define LUA_DEBUG + +#undef NDEBUG +#include +#define lua_assert(c) assert(c) + + +/* to avoid warnings, and to make sure value is really unused */ +#define UNUSED(x) (x=0, (void)(x)) + + +/* memory allocator control variables */ +typedef struct Memcontrol { + unsigned long numblocks; + unsigned long total; + unsigned long maxmem; + unsigned long memlimit; +} Memcontrol; + +LUAI_DATA Memcontrol memcontrol; + + +/* +** generic variable for debug tricks +*/ +LUAI_DATA int Trick; + + +void *debug_realloc (void *ud, void *block, size_t osize, size_t nsize); + +#ifdef lua_c +#define luaL_newstate() lua_newstate(debug_realloc, &memcontrol) +#endif + + +typedef struct CallInfo *pCallInfo; + +int lua_checkmemory (lua_State *L); +int lua_checkpc (lua_State *L, pCallInfo ci); + + +/* test for lock/unlock */ +#undef luai_userstateopen +#undef luai_userstatethread +#undef lua_lock +#undef lua_unlock +#undef LUAI_EXTRASPACE + +struct L_EXTRA { int lock; int *plock; }; +#define LUAI_EXTRASPACE sizeof(struct L_EXTRA) +#define getlock(l) (cast(struct L_EXTRA *, l) - 1) +#define luai_userstateopen(l) \ + (getlock(l)->lock = 0, getlock(l)->plock = &(getlock(l)->lock)) +#define luai_userstatethread(l,l1) (getlock(l1)->plock = getlock(l)->plock) +#define lua_lock(l) lua_assert((*getlock(l)->plock)++ == 0) +#define lua_unlock(l) lua_assert(--(*getlock(l)->plock) == 0) + + +int luaB_opentests (lua_State *L); + +#ifdef lua_c +#define luaL_openlibs(L) { (luaL_openlibs)(L); luaB_opentests(L); } +#endif + + + +/* real main will be defined at `ltests.c' */ +int l_main (int argc, char *argv[]); +#define main l_main + + + +/* change some sizes to give some bugs a chance */ + +#undef LUAL_BUFFERSIZE +#define LUAL_BUFFERSIZE 27 +#define MINSTRTABSIZE 2 + +#endif diff --git a/tests/upstream/lua51/vendor/events.lua b/tests/upstream/lua51/vendor/events.lua new file mode 100644 index 00000000..5234b007 --- /dev/null +++ b/tests/upstream/lua51/vendor/events.lua @@ -0,0 +1,360 @@ +print('testing metatables') + +X = 20; B = 30 + +setfenv(1, setmetatable({}, {__index=_G})) + +collectgarbage() + +X = X+10 +assert(X == 30 and _G.X == 20) +B = false +assert(B == false) +B = nil +assert(B == 30) + +assert(getmetatable{} == nil) +assert(getmetatable(4) == nil) +assert(getmetatable(nil) == nil) +a={}; setmetatable(a, {__metatable = "xuxu", + __tostring=function(x) return x.name end}) +assert(getmetatable(a) == "xuxu") +assert(tostring(a) == nil) +-- cannot change a protected metatable +assert(pcall(setmetatable, a, {}) == false) +a.name = "gororoba" +assert(tostring(a) == "gororoba") + +local a, t = {10,20,30; x="10", y="20"}, {} +assert(setmetatable(a,t) == a) +assert(getmetatable(a) == t) +assert(setmetatable(a,nil) == a) +assert(getmetatable(a) == nil) +assert(setmetatable(a,t) == a) + + +function f (t, i, e) + assert(not e) + local p = rawget(t, "parent") + return (p and p[i]+3), "dummy return" +end + +t.__index = f + +a.parent = {z=25, x=12, [4] = 24} +assert(a[1] == 10 and a.z == 28 and a[4] == 27 and a.x == "10") + +collectgarbage() + +a = setmetatable({}, t) +function f(t, i, v) rawset(t, i, v-3) end +t.__newindex = f +a[1] = 30; a.x = "101"; a[5] = 200 +assert(a[1] == 27 and a.x == 98 and a[5] == 197) + + +local c = {} +a = setmetatable({}, t) +t.__newindex = c +a[1] = 10; a[2] = 20; a[3] = 90 +assert(c[1] == 10 and c[2] == 20 and c[3] == 90) + + +do + local a; + a = setmetatable({}, {__index = setmetatable({}, + {__index = setmetatable({}, + {__index = function (_,n) return a[n-3]+4, "lixo" end})})}) + a[0] = 20 + for i=0,10 do + assert(a[i*3] == 20 + i*4) + end +end + + +do -- newindex + local foi + local a = {} + for i=1,10 do a[i] = 0; a['a'..i] = 0; end + setmetatable(a, {__newindex = function (t,k,v) foi=true; rawset(t,k,v) end}) + foi = false; a[1]=0; assert(not foi) + foi = false; a['a1']=0; assert(not foi) + foi = false; a['a11']=0; assert(foi) + foi = false; a[11]=0; assert(foi) + foi = false; a[1]=nil; assert(not foi) + foi = false; a[1]=nil; assert(foi) +end + + +function f (t, ...) return t, {...} end +t.__call = f + +do + local x,y = a(unpack{'a', 1}) + assert(x==a and y[1]=='a' and y[2]==1 and y[3]==nil) + x,y = a() + assert(x==a and y[1]==nil) +end + + +local b = setmetatable({}, t) +setmetatable(b,t) + +function f(op) + return function (...) cap = {[0] = op, ...} ; return (...) end +end +t.__add = f("add") +t.__sub = f("sub") +t.__mul = f("mul") +t.__div = f("div") +t.__mod = f("mod") +t.__unm = f("unm") +t.__pow = f("pow") + +assert(b+5 == b) +assert(cap[0] == "add" and cap[1] == b and cap[2] == 5 and cap[3]==nil) +assert(b+'5' == b) +assert(cap[0] == "add" and cap[1] == b and cap[2] == '5' and cap[3]==nil) +assert(5+b == 5) +assert(cap[0] == "add" and cap[1] == 5 and cap[2] == b and cap[3]==nil) +assert('5'+b == '5') +assert(cap[0] == "add" and cap[1] == '5' and cap[2] == b and cap[3]==nil) +b=b-3; assert(getmetatable(b) == t) +assert(5-a == 5) +assert(cap[0] == "sub" and cap[1] == 5 and cap[2] == a and cap[3]==nil) +assert('5'-a == '5') +assert(cap[0] == "sub" and cap[1] == '5' and cap[2] == a and cap[3]==nil) +assert(a*a == a) +assert(cap[0] == "mul" and cap[1] == a and cap[2] == a and cap[3]==nil) +assert(a/0 == a) +assert(cap[0] == "div" and cap[1] == a and cap[2] == 0 and cap[3]==nil) +assert(a%2 == a) +assert(cap[0] == "mod" and cap[1] == a and cap[2] == 2 and cap[3]==nil) +assert(-a == a) +assert(cap[0] == "unm" and cap[1] == a) +assert(a^4 == a) +assert(cap[0] == "pow" and cap[1] == a and cap[2] == 4 and cap[3]==nil) +assert(a^'4' == a) +assert(cap[0] == "pow" and cap[1] == a and cap[2] == '4' and cap[3]==nil) +assert(4^a == 4) +assert(cap[0] == "pow" and cap[1] == 4 and cap[2] == a and cap[3]==nil) +assert('4'^a == '4') +assert(cap[0] == "pow" and cap[1] == '4' and cap[2] == a and cap[3]==nil) + + +t = {} +t.__lt = function (a,b,c) + collectgarbage() + assert(c == nil) + if type(a) == 'table' then a = a.x end + if type(b) == 'table' then b = b.x end + return aOp(1)) and not(Op(1)>Op(2)) and (Op(2)>Op(1))) + assert(not(Op('a')>Op('a')) and not(Op('a')>Op('b')) and (Op('b')>Op('a'))) + assert((Op(1)>=Op(1)) and not(Op(1)>=Op(2)) and (Op(2)>=Op(1))) + assert((Op('a')>=Op('a')) and not(Op('a')>=Op('b')) and (Op('b')>=Op('a'))) +end + +test() + +t.__le = function (a,b,c) + assert(c == nil) + if type(a) == 'table' then a = a.x end + if type(b) == 'table' then b = b.x end + return a<=b, "dummy" +end + +test() -- retest comparisons, now using both `lt' and `le' + + +-- test `partial order' + +local function Set(x) + local y = {} + for _,k in pairs(x) do y[k] = 1 end + return setmetatable(y, t) +end + +t.__lt = function (a,b) + for k in pairs(a) do + if not b[k] then return false end + b[k] = nil + end + return next(b) ~= nil +end + +t.__le = nil + +assert(Set{1,2,3} < Set{1,2,3,4}) +assert(not(Set{1,2,3,4} < Set{1,2,3,4})) +assert((Set{1,2,3,4} <= Set{1,2,3,4})) +assert((Set{1,2,3,4} >= Set{1,2,3,4})) +assert((Set{1,3} <= Set{3,5})) -- wrong!! model needs a `le' method ;-) + +t.__le = function (a,b) + for k in pairs(a) do + if not b[k] then return false end + end + return true +end + +assert(not (Set{1,3} <= Set{3,5})) -- now its OK! +assert(not(Set{1,3} <= Set{3,5})) +assert(not(Set{1,3} >= Set{3,5})) + +t.__eq = function (a,b) + for k in pairs(a) do + if not b[k] then return false end + b[k] = nil + end + return next(b) == nil +end + +local s = Set{1,3,5} +assert(s == Set{3,5,1}) +assert(not rawequal(s, Set{3,5,1})) +assert(rawequal(s, s)) +assert(Set{1,3,5,1} == Set{3,5,1}) +assert(Set{1,3,5} ~= Set{3,5,1,6}) +t[Set{1,3,5}] = 1 +assert(t[Set{1,3,5}] == nil) -- `__eq' is not valid for table accesses + + +t.__concat = function (a,b,c) + assert(c == nil) + if type(a) == 'table' then a = a.val end + if type(b) == 'table' then b = b.val end + if A then return a..b + else + return setmetatable({val=a..b}, t) + end +end + +c = {val="c"}; setmetatable(c, t) +d = {val="d"}; setmetatable(d, t) + +A = true +assert(c..d == 'cd') +assert(0 .."a".."b"..c..d.."e".."f"..(5+3).."g" == "0abcdef8g") + +A = false +x = c..d +assert(getmetatable(x) == t and x.val == 'cd') +x = 0 .."a".."b"..c..d.."e".."f".."g" +assert(x.val == "0abcdefg") + + +-- test comparison compatibilities +local t1, t2, c, d +t1 = {}; c = {}; setmetatable(c, t1) +d = {} +t1.__eq = function () return true end +t1.__lt = function () return true end +assert(c ~= d and not pcall(function () return c < d end)) +setmetatable(d, t1) +assert(c == d and c < d and not(d <= c)) +t2 = {} +t2.__eq = t1.__eq +t2.__lt = t1.__lt +setmetatable(d, t2) +assert(c == d and c < d and not(d <= c)) + + + +-- test for several levels of calls +local i +local tt = { + __call = function (t, ...) + i = i+1 + if t.f then return t.f(...) + else return {...} + end + end +} + +local a = setmetatable({}, tt) +local b = setmetatable({f=a}, tt) +local c = setmetatable({f=b}, tt) + +i = 0 +x = c(3,4,5) +assert(i == 3 and x[1] == 3 and x[3] == 5) + + +assert(_G.X == 20) +assert(_G == getfenv(0)) + +print'+' + +local _g = _G +setfenv(1, setmetatable({}, {__index=function (_,k) return _g[k] end})) + +-- testing proxies +assert(getmetatable(newproxy()) == nil) +assert(getmetatable(newproxy(false)) == nil) + +local u = newproxy(true) + +getmetatable(u).__newindex = function (u,k,v) + getmetatable(u)[k] = v +end + +getmetatable(u).__index = function (u,k) + return getmetatable(u)[k] +end + +for i=1,10 do u[i] = i end +for i=1,10 do assert(u[i] == i) end + +local k = newproxy(u) +assert(getmetatable(k) == getmetatable(u)) + + +a = {} +rawset(a, "x", 1, 2, 3) +assert(a.x == 1 and rawget(a, "x", 3) == 1) + +print '+' + +-- testing metatables for basic types +mt = {} +debug.setmetatable(10, mt) +assert(getmetatable(-2) == mt) +mt.__index = function (a,b) return a+b end +assert((10)[3] == 13) +assert((10)["3"] == 13) +debug.setmetatable(23, nil) +assert(getmetatable(-2) == nil) + +debug.setmetatable(true, mt) +assert(getmetatable(false) == mt) +mt.__index = function (a,b) return a or b end +assert((true)[false] == true) +assert((false)[false] == false) +debug.setmetatable(false, nil) +assert(getmetatable(true) == nil) + +debug.setmetatable(nil, mt) +assert(getmetatable(nil) == mt) +mt.__add = function (a,b) return (a or 0) + (b or 0) end +assert(10 + nil == 10) +assert(nil + 23 == 23) +assert(nil + nil == 0) +debug.setmetatable(nil, nil) +assert(getmetatable(nil) == nil) + +debug.setmetatable(nil, {}) + + +print 'OK' + +return 12 diff --git a/tests/upstream/lua51/vendor/files.lua b/tests/upstream/lua51/vendor/files.lua new file mode 100644 index 00000000..4175dc34 --- /dev/null +++ b/tests/upstream/lua51/vendor/files.lua @@ -0,0 +1,324 @@ + +print('testing i/o') + +assert(io.input(io.stdin) == io.stdin) +assert(io.output(io.stdout) == io.stdout) + + +assert(type(io.input()) == "userdata" and io.type(io.output()) == "file") +assert(io.type(8) == nil) +local a = {}; setmetatable(a, {}) +assert(io.type(a) == nil) + +local a,b,c = io.open('xuxu_nao_existe') +assert(not a and type(b) == "string" and type(c) == "number") + +a,b,c = io.open('/a/b/c/d', 'w') +assert(not a and type(b) == "string" and type(c) == "number") + +local file = os.tmpname() +local otherfile = os.tmpname() + +assert(os.setlocale('C', 'all')) + +io.input(io.stdin); io.output(io.stdout); + +os.remove(file) +assert(loadfile(file) == nil) +assert(io.open(file) == nil) +io.output(file) +assert(io.output() ~= io.stdout) + +assert(io.output():seek() == 0) +assert(io.write("alo alo")) +assert(io.output():seek() == string.len("alo alo")) +assert(io.output():seek("cur", -3) == string.len("alo alo")-3) +assert(io.write("joao")) +assert(io.output():seek("end") == string.len("alo joao")) + +assert(io.output():seek("set") == 0) + +assert(io.write('"álo"', "{a}\n", "second line\n", "third line \n")) +assert(io.write('çfourth_line')) +io.output(io.stdout) +collectgarbage() -- file should be closed by GC +assert(io.input() == io.stdin and rawequal(io.output(), io.stdout)) +print('+') + +-- test GC for files +collectgarbage() +for i=1,120 do + for i=1,5 do + io.input(file) + assert(io.open(file, 'r')) + io.lines(file) + end + collectgarbage() +end + +assert(os.rename(file, otherfile)) +assert(os.rename(file, otherfile) == nil) + +io.output(io.open(otherfile, "a")) +assert(io.write("\n\n\t\t 3450\n")); +io.close() + +-- test line generators +assert(os.rename(otherfile, file)) +io.output(otherfile) +local f = io.lines(file) +while f() do end; +assert(not pcall(f)) -- read lines after EOF +assert(not pcall(f)) -- read lines after EOF +-- copy from file to otherfile +for l in io.lines(file) do io.write(l, "\n") end +io.close() +-- copy from otherfile back to file +local f = assert(io.open(otherfile)) +assert(io.type(f) == "file") +io.output(file) +assert(io.output():read() == nil) +for l in f:lines() do io.write(l, "\n") end +assert(f:close()); io.close() +assert(not pcall(io.close, f)) -- error trying to close again +assert(tostring(f) == "file (closed)") +assert(io.type(f) == "closed file") +io.input(file) +f = io.open(otherfile):lines() +for l in io.lines() do assert(l == f()) end +assert(os.remove(otherfile)) + +io.input(file) +do -- test error returns + local a,b,c = io.input():write("xuxu") + assert(not a and type(b) == "string" and type(c) == "number") +end +assert(io.read(0) == "") -- not eof +assert(io.read(5, '*l') == '"álo"') +assert(io.read(0) == "") +assert(io.read() == "second line") +local x = io.input():seek() +assert(io.read() == "third line ") +assert(io.input():seek("set", x)) +assert(io.read('*l') == "third line ") +assert(io.read(1) == "ç") +assert(io.read(string.len"fourth_line") == "fourth_line") +assert(io.input():seek("cur", -string.len"fourth_line")) +assert(io.read() == "fourth_line") +assert(io.read() == "") -- empty line +assert(io.read('*n') == 3450) +assert(io.read(1) == '\n') +assert(io.read(0) == nil) -- end of file +assert(io.read(1) == nil) -- end of file +assert(({io.read(1)})[2] == nil) +assert(io.read() == nil) -- end of file +assert(({io.read()})[2] == nil) +assert(io.read('*n') == nil) -- end of file +assert(({io.read('*n')})[2] == nil) +assert(io.read('*a') == '') -- end of file (OK for `*a') +assert(io.read('*a') == '') -- end of file (OK for `*a') +collectgarbage() +print('+') +io.close(io.input()) +assert(not pcall(io.read)) + +assert(os.remove(file)) + +local t = '0123456789' +for i=1,12 do t = t..t; end +assert(string.len(t) == 10*2^12) + +io.output(file) +io.write("alo\n") +io.close() +assert(not pcall(io.write)) +local f = io.open(file, "a") +io.output(f) +collectgarbage() + +assert(io.write(' ' .. t .. ' ')) +assert(io.write(';', 'end of file\n')) +f:flush(); io.flush() +f:close() +print('+') + +io.input(file) +assert(io.read() == "alo") +assert(io.read(1) == ' ') +assert(io.read(string.len(t)) == t) +assert(io.read(1) == ' ') +assert(io.read(0)) +assert(io.read('*a') == ';end of file\n') +assert(io.read(0) == nil) +assert(io.close(io.input())) + +assert(os.remove(file)) +print('+') + +local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'" +io.output(file) +assert(io.write(string.format("x2 = %q\n-- comment without ending EOS", x1))) +io.close() +assert(loadfile(file))() +assert(x1 == x2) +print('+') +assert(os.remove(file)) +assert(os.remove(file) == nil) +assert(os.remove(otherfile) == nil) + +io.output(file) +assert(io.write("qualquer coisa\n")) +assert(io.write("mais qualquer coisa")) +io.close() +io.output(assert(io.open(otherfile, 'wb'))) +assert(io.write("outra coisa\0\1\3\0\0\0\0\255\0")) +io.close() + +local filehandle = assert(io.open(file, 'r')) +local otherfilehandle = assert(io.open(otherfile, 'rb')) +assert(filehandle ~= otherfilehandle) +assert(type(filehandle) == "userdata") +assert(filehandle:read('*l') == "qualquer coisa") +io.input(otherfilehandle) +assert(io.read(string.len"outra coisa") == "outra coisa") +assert(filehandle:read('*l') == "mais qualquer coisa") +filehandle:close(); +assert(type(filehandle) == "userdata") +io.input(otherfilehandle) +assert(io.read(4) == "\0\1\3\0") +assert(io.read(3) == "\0\0\0") +assert(io.read(0) == "") -- 255 is not eof +assert(io.read(1) == "\255") +assert(io.read('*a') == "\0") +assert(not io.read(0)) +assert(otherfilehandle == io.input()) +otherfilehandle:close() +assert(os.remove(file)) +assert(os.remove(otherfile)) +collectgarbage() + +io.output(file) +io.write[[ + 123.4 -56e-2 not a number +second line +third line + +and the rest of the file +]] +io.close() +io.input(file) +local _,a,b,c,d,e,h,__ = io.read(1, '*n', '*n', '*l', '*l', '*l', '*a', 10) +assert(io.close(io.input())) +assert(_ == ' ' and __ == nil) +assert(type(a) == 'number' and a==123.4 and b==-56e-2) +assert(d=='second line' and e=='third line') +assert(h==[[ + +and the rest of the file +]]) +assert(os.remove(file)) +collectgarbage() + +-- testing buffers +do + local f = assert(io.open(file, "w")) + local fr = assert(io.open(file, "r")) + assert(f:setvbuf("full", 2000)) + f:write("x") + assert(fr:read("*all") == "") -- full buffer; output not written yet + f:close() + fr:seek("set") + assert(fr:read("*all") == "x") -- `close' flushes it + f = assert(io.open(file), "w") + assert(f:setvbuf("no")) + f:write("x") + fr:seek("set") + assert(fr:read("*all") == "x") -- no buffer; output is ready + f:close() + f = assert(io.open(file, "a")) + assert(f:setvbuf("line")) + f:write("x") + fr:seek("set", 1) + assert(fr:read("*all") == "") -- line buffer; no output without `\n' + f:write("a\n") + fr:seek("set", 1) + assert(fr:read("*all") == "xa\n") -- now we have a whole line + f:close(); fr:close() +end + + +-- testing large files (> BUFSIZ) +io.output(file) +for i=1,5001 do io.write('0123456789123') end +io.write('\n12346') +io.close() +io.input(file) +local x = io.read('*a') +io.input():seek('set', 0) +local y = io.read(30001)..io.read(1005)..io.read(0)..io.read(1)..io.read(100003) +assert(x == y and string.len(x) == 5001*13 + 6) +io.input():seek('set', 0) +y = io.read() -- huge line +assert(x == y..'\n'..io.read()) +assert(io.read() == nil) +io.close(io.input()) +assert(os.remove(file)) +x = nil; y = nil + +x, y = pcall(io.popen, "ls") +if x then + assert(y:read("*a")) + assert(y:close()) +else + (Message or print)('\a\n >>> popen not available<<<\n\a') +end + +print'+' + +local t = os.time() +T = os.date("*t", t) +loadstring(os.date([[assert(T.year==%Y and T.month==%m and T.day==%d and + T.hour==%H and T.min==%M and T.sec==%S and + T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))() + +assert(os.time(T) == t) + +T = os.date("!*t", t) +loadstring(os.date([[!assert(T.year==%Y and T.month==%m and T.day==%d and + T.hour==%H and T.min==%M and T.sec==%S and + T.wday==%w+1 and T.yday==%j and type(T.isdst) == 'boolean')]], t))() + +do + local T = os.date("*t") + local t = os.time(T) + assert(type(T.isdst) == 'boolean') + T.isdst = nil + local t1 = os.time(T) + assert(t == t1) -- if isdst is absent uses correct default +end + +t = os.time(T) +T.year = T.year-1; +local t1 = os.time(T) +-- allow for leap years +assert(math.abs(os.difftime(t,t1)/(24*3600) - 365) < 2) + +t = os.time() +t1 = os.time(os.date("*t")) +assert(os.difftime(t1,t) <= 2) + +local t1 = os.time{year=2000, month=10, day=1, hour=23, min=12, sec=17} +local t2 = os.time{year=2000, month=10, day=1, hour=23, min=10, sec=19} +assert(os.difftime(t1,t2) == 60*2-2) + +io.output(io.stdout) +local d = os.date('%d') +local m = os.date('%m') +local a = os.date('%Y') +local ds = os.date('%w') + 1 +local h = os.date('%H') +local min = os.date('%M') +local s = os.date('%S') +io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a)) +io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s)) +io.write(string.format('%s\n', _VERSION)) diff --git a/tests/upstream/lua51/vendor/gc.lua b/tests/upstream/lua51/vendor/gc.lua new file mode 100644 index 00000000..86a9f758 --- /dev/null +++ b/tests/upstream/lua51/vendor/gc.lua @@ -0,0 +1,312 @@ +print('testing garbage collection') + +collectgarbage() + +_G["while"] = 234 + +limit = 5000 + + + +contCreate = 0 + +print('tables') +while contCreate <= limit do + local a = {}; a = nil + contCreate = contCreate+1 +end + +a = "a" + +contCreate = 0 +print('strings') +while contCreate <= limit do + a = contCreate .. "b"; + a = string.gsub(a, '(%d%d*)', string.upper) + a = "a" + contCreate = contCreate+1 +end + + +contCreate = 0 + +a = {} + +print('functions') +function a:test () + while contCreate <= limit do + loadstring(string.format("function temp(a) return 'a%d' end", contCreate))() + assert(temp() == string.format('a%d', contCreate)) + contCreate = contCreate+1 + end +end + +a:test() + +-- collection of functions without locals, globals, etc. +do local f = function () end end + + +print("functions with errors") +prog = [[ +do + a = 10; + function foo(x,y) + a = sin(a+0.456-0.23e-12); + return function (z) return sin(%x+z) end + end + local x = function (w) a=a+w; end +end +]] +do + local step = 1 + if rawget(_G, "_soft") then step = 13 end + for i=1, string.len(prog), step do + for j=i, string.len(prog), step do + pcall(loadstring(string.sub(prog, i, j))) + end + end +end + +print('long strings') +x = "01234567890123456789012345678901234567890123456789012345678901234567890123456789" +assert(string.len(x)==80) +s = '' +n = 0 +k = 300 +while n < k do s = s..x; n=n+1; j=tostring(n) end +assert(string.len(s) == k*80) +s = string.sub(s, 1, 20000) +s, i = string.gsub(s, '(%d%d%d%d)', math.sin) +assert(i==20000/4) +s = nil +x = nil + +assert(_G["while"] == 234) + + +local bytes = gcinfo() +while 1 do + local nbytes = gcinfo() + if nbytes < bytes then break end -- run until gc + bytes = nbytes + a = {} +end + + +local function dosteps (siz) + collectgarbage() + collectgarbage"stop" + local a = {} + for i=1,100 do a[i] = {{}}; local b = {} end + local x = gcinfo() + local i = 0 + repeat + i = i+1 + until collectgarbage("step", siz) + assert(gcinfo() < x) + return i +end + +assert(dosteps(0) > 10) +assert(dosteps(6) < dosteps(2)) +assert(dosteps(10000) == 1) +assert(collectgarbage("step", 1000000) == true) +assert(collectgarbage("step", 1000000)) + + +do + local x = gcinfo() + collectgarbage() + collectgarbage"stop" + repeat + local a = {} + until gcinfo() > 1000 + collectgarbage"restart" + repeat + local a = {} + until gcinfo() < 1000 +end + +lim = 15 +a = {} +-- fill a with `collectable' indices +for i=1,lim do a[{}] = i end +b = {} +for k,v in pairs(a) do b[k]=v end +-- remove all indices and collect them +for n in pairs(b) do + a[n] = nil + assert(type(n) == 'table' and next(n) == nil) + collectgarbage() +end +b = nil +collectgarbage() +for n in pairs(a) do error'cannot be here' end +for i=1,lim do a[i] = i end +for i=1,lim do assert(a[i] == i) end + + +print('weak tables') +a = {}; setmetatable(a, {__mode = 'k'}); +-- fill a with some `collectable' indices +for i=1,lim do a[{}] = i end +-- and some non-collectable ones +for i=1,lim do local t={}; a[t]=t end +for i=1,lim do a[i] = i end +for i=1,lim do local s=string.rep('@', i); a[s] = s..'#' end +collectgarbage() +local i = 0 +for k,v in pairs(a) do assert(k==v or k..'#'==v); i=i+1 end +assert(i == 3*lim) + +a = {}; setmetatable(a, {__mode = 'v'}); +a[1] = string.rep('b', 21) +collectgarbage() +assert(a[1]) -- strings are *values* +a[1] = nil +-- fill a with some `collectable' values (in both parts of the table) +for i=1,lim do a[i] = {} end +for i=1,lim do a[i..'x'] = {} end +-- and some non-collectable ones +for i=1,lim do local t={}; a[t]=t end +for i=1,lim do a[i+lim]=i..'x' end +collectgarbage() +local i = 0 +for k,v in pairs(a) do assert(k==v or k-lim..'x' == v); i=i+1 end +assert(i == 2*lim) + +a = {}; setmetatable(a, {__mode = 'vk'}); +local x, y, z = {}, {}, {} +-- keep only some items +a[1], a[2], a[3] = x, y, z +a[string.rep('$', 11)] = string.rep('$', 11) +-- fill a with some `collectable' values +for i=4,lim do a[i] = {} end +for i=1,lim do a[{}] = i end +for i=1,lim do local t={}; a[t]=t end +collectgarbage() +assert(next(a) ~= nil) +local i = 0 +for k,v in pairs(a) do + assert((k == 1 and v == x) or + (k == 2 and v == y) or + (k == 3 and v == z) or k==v); + i = i+1 +end +assert(i == 4) +x,y,z=nil +collectgarbage() +assert(next(a) == string.rep('$', 11)) + + +-- testing userdata +collectgarbage("stop") -- stop collection +local u = newproxy(true) +local s = 0 +local a = {[u] = 0}; setmetatable(a, {__mode = 'vk'}) +for i=1,10 do a[newproxy(u)] = i end +for k in pairs(a) do assert(getmetatable(k) == getmetatable(u)) end +local a1 = {}; for k,v in pairs(a) do a1[k] = v end +for k,v in pairs(a1) do a[v] = k end +for i =1,10 do assert(a[i]) end +getmetatable(u).a = a1 +getmetatable(u).u = u +do + local u = u + getmetatable(u).__gc = function (o) + assert(a[o] == 10-s) + assert(a[10-s] == nil) -- udata already removed from weak table + assert(getmetatable(o) == getmetatable(u)) + assert(getmetatable(o).a[o] == 10-s) + s=s+1 + end +end +a1, u = nil +assert(next(a) ~= nil) +collectgarbage() +assert(s==11) +collectgarbage() +assert(next(a) == nil) -- finalized keys are removed in two cycles + + +-- __gc x weak tables +local u = newproxy(true) +setmetatable(getmetatable(u), {__mode = "v"}) +getmetatable(u).__gc = function (o) os.exit(1) end -- cannot happen +collectgarbage() + +local u = newproxy(true) +local m = getmetatable(u) +m.x = {[{0}] = 1; [0] = {1}}; setmetatable(m.x, {__mode = "kv"}); +m.__gc = function (o) + assert(next(getmetatable(o).x) == nil) + m = 10 +end +u, m = nil +collectgarbage() +assert(m==10) + + +-- errors during collection +u = newproxy(true) +getmetatable(u).__gc = function () error "!!!" end +u = nil +assert(not pcall(collectgarbage)) + + +if not rawget(_G, "_soft") then + print("deep structures") + local a = {} + for i = 1,200000 do + a = {next = a} + end + collectgarbage() +end + +-- create many threads with self-references and open upvalues +local thread_id = 0 +local threads = {} + +function fn(thread) + local x = {} + threads[thread_id] = function() + thread = x + end + coroutine.yield() +end + +while thread_id < 1000 do + local thread = coroutine.create(fn) + coroutine.resume(thread, thread) + thread_id = thread_id + 1 +end + + + +-- create a userdata to be collected when state is closed +do + local newproxy,assert,type,print,getmetatable = + newproxy,assert,type,print,getmetatable + local u = newproxy(true) + local tt = getmetatable(u) + ___Glob = {u} -- avoid udata being collected before program end + tt.__gc = function (o) + assert(getmetatable(o) == tt) + -- create new objects during GC + local a = 'xuxu'..(10+3)..'joao', {} + ___Glob = o -- ressurect object! + newproxy(o) -- creates a new one with same metatable + print(">>> closing state " .. "<<<\n") + end +end + +-- create several udata to raise errors when collected while closing state +do + local u = newproxy(true) + getmetatable(u).__gc = function (o) return o + 1 end + table.insert(___Glob, u) -- preserve udata until the end + for i = 1,10 do table.insert(___Glob, newproxy(u)) end +end + +print('OK') diff --git a/tests/upstream/lua51/vendor/libs/lib1.c b/tests/upstream/lua51/vendor/libs/lib1.c new file mode 100644 index 00000000..da2c4148 --- /dev/null +++ b/tests/upstream/lua51/vendor/libs/lib1.c @@ -0,0 +1,40 @@ +/* +** compile with +** Linux: gcc -Wall -O2 -I.. -ansi -shared -o lib1.so lib1.c +** Mac OS X: export MACOSX_DEPLOYMENT_TARGET=10.3 +** gcc -bundle -undefined dynamic_lookup -Wall -O2 -o lib1.so lib1.c +*/ + + +#include "lua.h" +#include "lauxlib.h" + +static int id (lua_State *L) { + return lua_gettop(L); +} + + +static const struct luaL_reg funcs[] = { + {"id", id}, + {NULL, NULL} +}; + + +int anotherfunc (lua_State *L) { + lua_pushfstring(L, "%f%f\n", lua_tonumber(L, 1), lua_tonumber(L, 2)); + return 1; +} + + +int luaopen_lib1_sub (lua_State *L) { + luaL_register(L, "lib1.sub", funcs + 1); + return 1; +} + + +int luaopen_lib1 (lua_State *L) { + luaL_register(L, "lib1", funcs); + return 1; +} + + diff --git a/tests/upstream/lua51/vendor/libs/lib11.c b/tests/upstream/lua51/vendor/libs/lib11.c new file mode 100644 index 00000000..3efa3d3e --- /dev/null +++ b/tests/upstream/lua51/vendor/libs/lib11.c @@ -0,0 +1,18 @@ +/* +** compile with +** Linux: gcc -Wall -O2 -I.. -ansi -shared -o lib1.so lib1.c +** Mac OS X: export MACOSX_DEPLOYMENT_TARGET=10.3 +** gcc -bundle -undefined dynamic_lookup -Wall -O2 -o lib1.so lib1.c +*/ + + +#include "lua.h" + + +int luaopen_lib1 (lua_State *L); + +int luaopen_lib11 (lua_State *L) { + return luaopen_lib1(L); +} + + diff --git a/tests/upstream/lua51/vendor/libs/lib2.c b/tests/upstream/lua51/vendor/libs/lib2.c new file mode 100644 index 00000000..9972cbe4 --- /dev/null +++ b/tests/upstream/lua51/vendor/libs/lib2.c @@ -0,0 +1,28 @@ +/* +** compile with +** gcc -Wall -O2 -I.. -ansi -shared -o lib1.so lib1.c +*/ + + +#include "lua.h" +#include "lauxlib.h" + +static int id (lua_State *L) { + return lua_gettop(L); +} + + +static const struct luaL_reg funcs[] = { + {"id", id}, + {NULL, NULL} +}; + + +int luaopen_lib2 (lua_State *L) { + luaL_register(L, "lib2", funcs); + lua_pushnumber(L, 0.5); + lua_setglobal(L, "x"); + return 1; +} + + diff --git a/tests/upstream/lua51/vendor/libs/lib21.c b/tests/upstream/lua51/vendor/libs/lib21.c new file mode 100644 index 00000000..167507f9 --- /dev/null +++ b/tests/upstream/lua51/vendor/libs/lib21.c @@ -0,0 +1,18 @@ +/* +** compile with +** Linux: gcc -Wall -O2 -I.. -ansi -shared -o lib1.so lib1.c +** Mac OS X: export MACOSX_DEPLOYMENT_TARGET=10.3 +** gcc -bundle -undefined dynamic_lookup -Wall -O2 -o lib1.so lib1.c +*/ + + +#include "lua.h" + + +int luaopen_lib2 (lua_State *L); + +int luaopen_lib21 (lua_State *L) { + return luaopen_lib2(L); +} + + diff --git a/tests/upstream/lua51/vendor/libs/makefile b/tests/upstream/lua51/vendor/libs/makefile new file mode 100644 index 00000000..03c33a34 --- /dev/null +++ b/tests/upstream/lua51/vendor/libs/makefile @@ -0,0 +1,16 @@ +all: lib1.so lib11.so lib2.so lib21.so -lib2.so + +lib1.so: lib1.c + gcc -Wall -O2 -I../../ -ansi -shared -o lib1.so lib1.c + +lib11.so: lib11.c + gcc -Wall -O2 -I../../ -ansi -shared -o lib11.so lib11.c + +lib2.so: lib2.c + gcc -Wall -O2 -I../../ -ansi -shared -o lib2.so lib2.c + +lib21.so: lib21.c + gcc -Wall -O2 -I../../ -ansi -shared -o lib21.so lib21.c + +-lib2.so: lib2.so + cp lib2.so ./-lib2.so diff --git a/tests/upstream/lua51/vendor/literals.lua b/tests/upstream/lua51/vendor/literals.lua new file mode 100644 index 00000000..01d84d5a --- /dev/null +++ b/tests/upstream/lua51/vendor/literals.lua @@ -0,0 +1,176 @@ +print('testing scanner') + +local function dostring (x) return assert(loadstring(x))() end + +dostring("x = 'a\0a'") +assert(x == 'a\0a' and string.len(x) == 3) + +-- escape sequences +assert('\n\"\'\\' == [[ + +"'\]]) + +assert(string.find("\a\b\f\n\r\t\v", "^%c%c%c%c%c%c%c$")) + +-- assume ASCII just for tests: +assert("\09912" == 'c12') +assert("\99ab" == 'cab') +assert("\099" == '\99') +assert("\099\n" == 'c\10') +assert('\0\0\0alo' == '\0' .. '\0\0' .. 'alo') + +assert(010 .. 020 .. -030 == "1020-30") + +-- long variable names + +var = string.rep('a', 15000) +prog = string.format("%s = 5", var) +dostring(prog) +assert(_G[var] == 5) +var = nil +print('+') + +-- escapes -- +assert("\n\t" == [[ + + ]]) +assert([[ + + $debug]] == "\n $debug") +assert([[ [ ]] ~= [[ ] ]]) +-- long strings -- +b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789" +assert(string.len(b) == 960) +prog = [=[ +print('+') + +a1 = [["isto e' um string com várias 'aspas'"]] +a2 = "'aspas'" + +assert(string.find(a1, a2) == 31) +print('+') + +a1 = [==[temp = [[um valor qualquer]]; ]==] +assert(loadstring(a1))() +assert(temp == 'um valor qualquer') +-- long strings -- +b = "001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789001234567890123456789012345678901234567891234567890123456789012345678901234567890012345678901234567890123456789012345678912345678901234567890123456789012345678900123456789012345678901234567890123456789123456789012345678901234567890123456789" +assert(string.len(b) == 960) +print('+') + +a = [[00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +00123456789012345678901234567890123456789123456789012345678901234567890123456789 +]] +assert(string.len(a) == 1863) +assert(string.sub(a, 1, 40) == string.sub(b, 1, 40)) +x = 1 +]=] + +print('+') +x = nil +dostring(prog) +assert(x) + +prog = nil +a = nil +b = nil + + +-- testing line ends +prog = [[ +a = 1 -- a comment +b = 2 + + +x = [=[ +hi +]=] +y = "\ +hello\r\n\ +" +return debug.getinfo(1).currentline +]] + +for _, n in pairs{"\n", "\r", "\n\r", "\r\n"} do + local prog, nn = string.gsub(prog, "\n", n) + assert(dostring(prog) == nn) + assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n") +end + + +-- testing comments and strings with long brackets +a = [==[]=]==] +assert(a == "]=") + +a = [==[[===[[=[]]=][====[]]===]===]==] +assert(a == "[===[[=[]]=][====[]]===]===") + +a = [====[[===[[=[]]=][====[]]===]===]====] +assert(a == "[===[[=[]]=][====[]]===]===") + +a = [=[]]]]]]]]]=] +assert(a == "]]]]]]]]") + + +--[===[ +x y z [==[ blu foo +]== +] +]=]==] +error error]=]===] + +-- generate all strings of four of these chars +local x = {"=", "[", "]", "\n"} +local len = 4 +local function gen (c, n) + if n==0 then coroutine.yield(c) + else + for _, a in pairs(x) do + gen(c..a, n-1) + end + end +end + +for s in coroutine.wrap(function () gen("", len) end) do + assert(s == loadstring("return [====[\n"..s.."]====]")()) +end + + +-- testing decimal point locale +if os.setlocale("pt_BR") or os.setlocale("ptb") then + assert(tonumber("3,4") == 3.4 and tonumber"3.4" == nil) + assert(assert(loadstring("return 3.4"))() == 3.4) + assert(assert(loadstring("return .4,3"))() == .4) + assert(assert(loadstring("return 4."))() == 4.) + assert(assert(loadstring("return 4.+.5"))() == 4.5) + local a,b = loadstring("return 4.5.") + assert(string.find(b, "'4%.5%.'")) + assert(os.setlocale("C")) +else + (Message or print)( + '\a\n >>> pt_BR locale not available: skipping decimal point tests <<<\n\a') +end + + +print('OK') diff --git a/tests/upstream/lua51/vendor/locals.lua b/tests/upstream/lua51/vendor/locals.lua new file mode 100644 index 00000000..b0fc556f --- /dev/null +++ b/tests/upstream/lua51/vendor/locals.lua @@ -0,0 +1,127 @@ +print('testing local variables plus some extra stuff') + +do + local i = 10 + do local i = 100; assert(i==100) end + do local i = 1000; assert(i==1000) end + assert(i == 10) + if i ~= 10 then + local i = 20 + else + local i = 30 + assert(i == 30) + end +end + + + +f = nil + +local f +x = 1 + +a = nil +loadstring('local a = {}')() +assert(type(a) ~= 'table') + +function f (a) + local _1, _2, _3, _4, _5 + local _6, _7, _8, _9, _10 + local x = 3 + local b = a + local c,d = a,b + if (d == b) then + local x = 'q' + x = b + assert(x == 2) + else + assert(nil) + end + assert(x == 3) + local f = 10 +end + +local b=10 +local a; repeat local b; a,b=1,2; assert(a+1==b); until a+b==3 + + +assert(x == 1) + +f(2) +assert(type(f) == 'function') + + +-- testing globals ;-) +do + local f = {} + local _G = _G + for i=1,10 do f[i] = function (x) A=A+1; return A, _G.getfenv(x) end end + A=10; assert(f[1]() == 11) + for i=1,10 do assert(setfenv(f[i], {A=i}) == f[i]) end + assert(f[3]() == 4 and A == 11) + local a,b = f[8](1) + assert(b.A == 9) + a,b = f[8](0) + assert(b.A == 11) -- `real' global + local g + local function f () assert(setfenv(2, {a='10'}) == g) end + g = function () f(); _G.assert(_G.getfenv(1).a == '10') end + g(); assert(getfenv(g).a == '10') +end + +-- test for global table of loaded chunks +local function foo (s) + return loadstring(s) +end + +assert(getfenv(foo("")) == _G) +local a = {loadstring = loadstring} +setfenv(foo, a) +assert(getfenv(foo("")) == _G) +setfenv(0, a) -- change global environment +assert(getfenv(foo("")) == a) +setfenv(0, _G) + + +-- testing limits for special instructions + +local a +local p = 4 +for i=2,31 do + for j=-3,3 do + assert(loadstring(string.format([[local a=%s;a=a+ + %s; + assert(a + ==2^%s)]], j, p-j, i))) () + assert(loadstring(string.format([[local a=%s; + a=a-%s; + assert(a==-2^%s)]], -j, p-j, i))) () + assert(loadstring(string.format([[local a,b=0,%s; + a=b-%s; + assert(a==-2^%s)]], -j, p-j, i))) () + end + p =2*p +end + +print'+' + + +if rawget(_G, "querytab") then + -- testing clearing of dead elements from tables + collectgarbage("stop") -- stop GC + local a = {[{}] = 4, [3] = 0, alo = 1, + a1234567890123456789012345678901234567890 = 10} + + local t = querytab(a) + + for k,_ in pairs(a) do a[k] = nil end + collectgarbage() -- restore GC and collect dead fiels in `a' + for i=0,t-1 do + local k = querytab(a, i) + assert(k == nil or type(k) == 'number' or k == 'alo') + end +end + +print('OK') + +return 5,f diff --git a/tests/upstream/lua51/vendor/main.lua b/tests/upstream/lua51/vendor/main.lua new file mode 100644 index 00000000..22b4f74b --- /dev/null +++ b/tests/upstream/lua51/vendor/main.lua @@ -0,0 +1,159 @@ +# testing special comment on first line + +print ("testing lua.c options") + +assert(os.execute() ~= 0) -- machine has a system command + +prog = os.tmpname() +otherprog = os.tmpname() +out = os.tmpname() + +do + local i = 0 + while arg[i] do i=i-1 end + progname = '"'..arg[i+1]..'"' +end +print(progname) + +local prepfile = function (s, p) + p = p or prog + io.output(p) + io.write(s) + assert(io.close()) +end + +function checkout (s) + io.input(out) + local t = io.read("*a") + io.input():close() + assert(os.remove(out)) + if s ~= t then print(string.format("'%s' - '%s'\n", s, t)) end + assert(s == t) + return t +end + +function auxrun (...) + local s = string.format(...) + s = string.gsub(s, "lua", progname, 1) + return os.execute(s) +end + +function RUN (...) + assert(auxrun(...) == 0) +end + +function NoRun (...) + print("\n(the next error is expected by the test)") + assert(auxrun(...) ~= 0) +end + +-- test 2 files +prepfile("print(1); a=2") +prepfile("print(a)", otherprog) +RUN("lua -l %s -l%s -lstring -l io %s > %s", prog, otherprog, otherprog, out) +checkout("1\n2\n2\n") + +local a = [[ + assert(table.getn(arg) == 3 and arg[1] == 'a' and + arg[2] == 'b' and arg[3] == 'c') + assert(arg[-1] == '--' and arg[-2] == "-e " and arg[-3] == %s) + assert(arg[4] == nil and arg[-4] == nil) + local a, b, c = ... + assert(... == 'a' and a == 'a' and b == 'b' and c == 'c') +]] +a = string.format(a, progname) +prepfile(a) +RUN('lua "-e " -- %s a b c', prog) + +prepfile"assert(arg==nil)" +prepfile("assert(arg)", otherprog) +RUN("lua -l%s - < %s", prog, otherprog) + +prepfile"" +RUN("lua - < %s > %s", prog, out) +checkout("") + +-- test many arguments +prepfile[[print(({...})[30])]] +RUN("lua %s %s > %s", prog, string.rep(" a", 30), out) +checkout("a\n") + +RUN([[lua "-eprint(1)" -ea=3 -e "print(a)" > %s]], out) +checkout("1\n3\n") + +prepfile[[ + print( +1, a +) +]] +RUN("lua - < %s > %s", prog, out) +checkout("1\tnil\n") + +prepfile[[ += (6*2-6) -- === +a += 10 +print(a) += a]] +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkout("6\n10\n10\n\n") + +prepfile("a = [[b\nc\nd\ne]]\n=a") +print(prog) +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkout("b\nc\nd\ne\n\n") + +prompt = "alo" +prepfile[[ -- +a = 2 +]] +RUN([[lua "-e_PROMPT='%s'" -i < %s > %s]], prompt, prog, out) +checkout(string.rep(prompt, 3).."\n") + +s = [=[ -- +function f ( x ) + local a = [[ +xuxu +]] + local b = "\ +xuxu\n" + if x == 11 then return 1 , 2 end --[[ test multiple returns ]] + return x + 1 + --\\ +end +=( f( 10 ) ) +assert( a == b ) +=f( 11 ) ]=] +s = string.gsub(s, ' ', '\n\n') +prepfile(s) +RUN([[lua -e"_PROMPT='' _PROMPT2=''" -i < %s > %s]], prog, out) +checkout("11\n1\t2\n\n") + +prepfile[[#comment in 1st line without \n at the end]] +RUN("lua %s", prog) + +prepfile("#comment with a binary file\n"..string.dump(loadstring("print(1)"))) +RUN("lua %s > %s", prog, out) +checkout("1\n") + +prepfile("#comment with a binary file\r\n"..string.dump(loadstring("print(1)"))) +RUN("lua %s > %s", prog, out) +checkout("1\n") + +-- close Lua with an open file +prepfile(string.format([[io.output(%q); io.write('alo')]], out)) +RUN("lua %s", prog) +checkout('alo') + +assert(os.remove(prog)) +assert(os.remove(otherprog)) +assert(not os.remove(out)) + +RUN("lua -v") + +NoRun("lua -h") +NoRun("lua -e") +NoRun("lua -e a") +NoRun("lua -f") + +print("OK") diff --git a/tests/upstream/lua51/vendor/math.lua b/tests/upstream/lua51/vendor/math.lua new file mode 100644 index 00000000..5076f38d --- /dev/null +++ b/tests/upstream/lua51/vendor/math.lua @@ -0,0 +1,208 @@ +print("testing numbers and math lib") + +do + local a,b,c = "2", " 3e0 ", " 10 " + assert(a+b == 5 and -b == -3 and b+"2" == 5 and "10"-c == 0) + assert(type(a) == 'string' and type(b) == 'string' and type(c) == 'string') + assert(a == "2" and b == " 3e0 " and c == " 10 " and -c == -" 10 ") + assert(c%a == 0 and a^b == 8) +end + + +do + local a,b = math.modf(3.5) + assert(a == 3 and b == 0.5) + assert(math.huge > 10e30) + assert(-math.huge < -10e30) +end + +function f(...) + if select('#', ...) == 1 then + return (...) + else + return "***" + end +end + +assert(tonumber{} == nil) +assert(tonumber'+0.01' == 1/100 and tonumber'+.01' == 0.01 and + tonumber'.01' == 0.01 and tonumber'-1.' == -1 and + tonumber'+1.' == 1) +assert(tonumber'+ 0.01' == nil and tonumber'+.e1' == nil and + tonumber'1e' == nil and tonumber'1.0e+' == nil and + tonumber'.' == nil) +assert(tonumber('-12') == -10-2) +assert(tonumber('-1.2e2') == - - -120) +assert(f(tonumber('1 a')) == nil) +assert(f(tonumber('e1')) == nil) +assert(f(tonumber('e 1')) == nil) +assert(f(tonumber(' 3.4.5 ')) == nil) +assert(f(tonumber('')) == nil) +assert(f(tonumber('', 8)) == nil) +assert(f(tonumber(' ')) == nil) +assert(f(tonumber(' ', 9)) == nil) +assert(f(tonumber('99', 8)) == nil) +assert(tonumber(' 1010 ', 2) == 10) +assert(tonumber('10', 36) == 36) +--assert(tonumber('\n -10 \n', 36) == -36) +--assert(tonumber('-fFfa', 16) == -(10+(16*(15+(16*(15+(16*15))))))) +assert(tonumber('fFfa', 15) == nil) +--assert(tonumber(string.rep('1', 42), 2) + 1 == 2^42) +assert(tonumber(string.rep('1', 32), 2) + 1 == 2^32) +--assert(tonumber('-fffffFFFFF', 16)-1 == -2^40) +assert(tonumber('ffffFFFF', 16)+1 == 2^32) + +assert(1.1 == 1.+.1) +assert(100.0 == 1E2 and .01 == 1e-2) +assert(1111111111111111-1111111111111110== 1000.00e-03) +-- 1234567890123456 +assert(1.1 == '1.'+'.1') +assert('1111111111111111'-'1111111111111110' == tonumber" +0.001e+3 \n\t") + +function eq (a,b,limit) + if not limit then limit = 10E-10 end + return math.abs(a-b) <= limit +end + +assert(0.1e-30 > 0.9E-31 and 0.9E30 < 0.1e31) + +assert(0.123456 > 0.123455) + +assert(tonumber('+1.23E30') == 1.23*10^30) + +-- testing order operators +assert(not(1<1) and (1<2) and not(2<1)) +assert(not('a'<'a') and ('a'<'b') and not('b'<'a')) +assert((1<=1) and (1<=2) and not(2<=1)) +assert(('a'<='a') and ('a'<='b') and not('b'<='a')) +assert(not(1>1) and not(1>2) and (2>1)) +assert(not('a'>'a') and not('a'>'b') and ('b'>'a')) +assert((1>=1) and not(1>=2) and (2>=1)) +assert(('a'>='a') and not('a'>='b') and ('b'>='a')) + +-- testing mod operator +assert(-4%3 == 2) +assert(4%-3 == -2) +assert(math.pi - math.pi % 1 == 3) +assert(math.pi - math.pi % 0.001 == 3.141) + +local function testbit(a, n) + return a/2^n % 2 >= 1 +end + +assert(eq(math.sin(-9.8)^2 + math.cos(-9.8)^2, 1)) +assert(eq(math.tan(math.pi/4), 1)) +assert(eq(math.sin(math.pi/2), 1) and eq(math.cos(math.pi/2), 0)) +assert(eq(math.atan(1), math.pi/4) and eq(math.acos(0), math.pi/2) and + eq(math.asin(1), math.pi/2)) +assert(eq(math.deg(math.pi/2), 90) and eq(math.rad(90), math.pi/2)) +assert(math.abs(-10) == 10) +assert(eq(math.atan2(1,0), math.pi/2)) +assert(math.ceil(4.5) == 5.0) +assert(math.floor(4.5) == 4.0) +assert(math.mod(10,3) == 1) +assert(eq(math.sqrt(10)^2, 10)) +assert(eq(math.log10(2), math.log(2)/math.log(10))) +assert(eq(math.exp(0), 1)) +assert(eq(math.sin(10), math.sin(10%(2*math.pi)))) +local v,e = math.frexp(math.pi) +assert(eq(math.ldexp(v,e), math.pi)) + +assert(eq(math.tanh(3.5), math.sinh(3.5)/math.cosh(3.5))) + +assert(tonumber(' 1.3e-2 ') == 1.3e-2) +assert(tonumber(' -1.00000000000001 ') == -1.00000000000001) + +-- testing constant limits +-- 2^23 = 8388608 +assert(8388609 + -8388609 == 0) +assert(8388608 + -8388608 == 0) +assert(8388607 + -8388607 == 0) + +if rawget(_G, "_soft") then return end + +f = io.tmpfile() +assert(f) +f:write("a = {") +i = 1 +repeat + f:write("{", math.sin(i), ", ", math.cos(i), ", ", i/3, "},\n") + i=i+1 +until i > 1000 +f:write("}") +f:seek("set", 0) +assert(loadstring(f:read('*a')))() +assert(f:close()) + +assert(eq(a[300][1], math.sin(300))) +assert(eq(a[600][1], math.sin(600))) +assert(eq(a[500][2], math.cos(500))) +assert(eq(a[800][2], math.cos(800))) +assert(eq(a[200][3], 200/3)) +assert(eq(a[1000][3], 1000/3, 0.001)) +print('+') + +do -- testing NaN + local NaN = 10e500 - 10e400 + assert(NaN ~= NaN) + assert(not (NaN < NaN)) + assert(not (NaN <= NaN)) + assert(not (NaN > NaN)) + assert(not (NaN >= NaN)) + assert(not (0 < NaN)) + assert(not (NaN < 0)) + local a = {} + assert(not pcall(function () a[NaN] = 1 end)) + assert(a[NaN] == nil) + a[1] = 1 + assert(not pcall(function () a[NaN] = 1 end)) + assert(a[NaN] == nil) +end + +require "checktable" +stat(a) + +a = nil + +-- testing implicit convertions + +local a,b = '10', '20' +assert(a*b == 200 and a+b == 30 and a-b == -10 and a/b == 0.5 and -b == -20) +assert(a == '10' and b == '20') + + +math.randomseed(0) + +local i = 0 +local Max = 0 +local Min = 2 +repeat + local t = math.random() + Max = math.max(Max, t) + Min = math.min(Min, t) + i=i+1 + flag = eq(Max, 1, 0.001) and eq(Min, 0, 0.001) +until flag or i>10000 +assert(0 <= Min and Max<1) +assert(flag); + +for i=1,10 do + local t = math.random(5) + assert(1 <= t and t <= 5) +end + +i = 0 +Max = -200 +Min = 200 +repeat + local t = math.random(-10,0) + Max = math.max(Max, t) + Min = math.min(Min, t) + i=i+1 + flag = (Max == 0 and Min == -10) +until flag or i>10000 +assert(-10 <= Min and Max<=0) +assert(flag); + + +print('OK') diff --git a/tests/upstream/lua51/vendor/nextvar.lua b/tests/upstream/lua51/vendor/nextvar.lua new file mode 100644 index 00000000..23be43d8 --- /dev/null +++ b/tests/upstream/lua51/vendor/nextvar.lua @@ -0,0 +1,396 @@ +print('testing tables, next, and for') + +local a = {} + +-- make sure table has lots of space in hash part +for i=1,100 do a[i.."+"] = true end +for i=1,100 do a[i.."+"] = nil end +-- fill hash part with numeric indices testing size operator +for i=1,100 do + a[i] = true + assert(#a == i) +end + + +if T then +-- testing table sizes + +local l2 = math.log(2) +local function log2 (x) return math.log(x)/l2 end + +local function mp2 (n) -- minimum power of 2 >= n + local mp = 2^math.ceil(log2(n)) + assert(n == 0 or (mp/2 < n and n <= mp)) + return mp +end + +local function fb (n) + local r, nn = T.int2fb(n) + assert(r < 256) + return nn +end + +-- test fb function +local a = 1 +local lim = 2^30 +while a < lim do + local n = fb(a) + assert(a <= n and n <= a*1.125) + a = math.ceil(a*1.3) +end + + +local function check (t, na, nh) + local a, h = T.querytab(t) + if a ~= na or h ~= nh then + print(na, nh, a, h) + assert(nil) + end +end + +-- testing constructor sizes +local lim = 40 +local s = 'return {' +for i=1,lim do + s = s..i..',' + local s = s + for k=0,lim do + local t = loadstring(s..'}')() + assert(#t == i) + check(t, fb(i), mp2(k)) + s = string.format('%sa%d=%d,', s, k, k) + end +end + + +-- tests with unknown number of elements +local a = {} +for i=1,lim do a[i] = i end -- build auxiliary table +for k=0,lim do + local a = {unpack(a,1,k)} + assert(#a == k) + check(a, k, 0) + a = {1,2,3,unpack(a,1,k)} + check(a, k+3, 0) + assert(#a == k + 3) +end + + +print'+' + +-- testing tables dynamically built +local lim = 130 +local a = {}; a[2] = 1; check(a, 0, 1) +a = {}; a[0] = 1; check(a, 0, 1); a[2] = 1; check(a, 0, 2) +a = {}; a[0] = 1; a[1] = 1; check(a, 1, 1) +a = {} +for i = 1,lim do + a[i] = 1 + assert(#a == i) + check(a, mp2(i), 0) +end + +a = {} +for i = 1,lim do + a['a'..i] = 1 + assert(#a == 0) + check(a, 0, mp2(i)) +end + +a = {} +for i=1,16 do a[i] = i end +check(a, 16, 0) +for i=1,11 do a[i] = nil end +for i=30,40 do a[i] = nil end -- force a rehash (?) +check(a, 0, 8) +a[10] = 1 +for i=30,40 do a[i] = nil end -- force a rehash (?) +check(a, 0, 8) +for i=1,14 do a[i] = nil end +for i=30,50 do a[i] = nil end -- force a rehash (?) +check(a, 0, 4) + +-- reverse filling +for i=1,lim do + local a = {} + for i=i,1,-1 do a[i] = i end -- fill in reverse + check(a, mp2(i), 0) +end + +-- size tests for vararg +lim = 35 +function foo (n, ...) + local arg = {...} + check(arg, n, 0) + assert(select('#', ...) == n) + arg[n+1] = true + check(arg, mp2(n+1), 0) + arg.x = true + check(arg, mp2(n+1), 1) +end +local a = {} +for i=1,lim do a[i] = true; foo(i, unpack(a)) end + +end + + +-- test size operation on empty tables +assert(#{} == 0) +assert(#{nil} == 0) +assert(#{nil, nil} == 0) +assert(#{nil, nil, nil} == 0) +assert(#{nil, nil, nil, nil} == 0) +print'+' + + +local nofind = {} + +a,b,c = 1,2,3 +a,b,c = nil + +local function find (name) + local n,v + while 1 do + n,v = next(_G, n) + if not n then return nofind end + assert(v ~= nil) + if n == name then return v end + end +end + +local function find1 (name) + for n,v in pairs(_G) do + if n==name then return v end + end + return nil -- not found +end + +do -- create 10000 new global variables + for i=1,10000 do _G[i] = i end +end + + +a = {x=90, y=8, z=23} +assert(table.foreach(a, function(i,v) if i=='x' then return v end end) == 90) +assert(table.foreach(a, function(i,v) if i=='a' then return v end end) == nil) +table.foreach({}, error) + +table.foreachi({x=10, y=20}, error) +local a = {n = 1} +table.foreachi({n=3}, function (i, v) + assert(a.n == i and not v) + a.n=a.n+1 +end) +a = {10,20,30,nil,50} +table.foreachi(a, function (i,v) assert(a[i] == v) end) +assert(table.foreachi({'a', 'b', 'c'}, function (i,v) + if i==2 then return v end + end) == 'b') + + +assert(print==find("print") and print == find1("print")) +assert(_G["print"]==find("print")) +assert(assert==find1("assert")) +assert(nofind==find("return")) +assert(not find1("return")) +_G["ret" .. "urn"] = nil +assert(nofind==find("return")) +_G["xxx"] = 1 +assert(xxx==find("xxx")) +print('+') + +a = {} +for i=0,10000 do + if math.mod(i,10) ~= 0 then + a['x'..i] = i + end +end + +n = {n=0} +for i,v in pairs(a) do + n.n = n.n+1 + assert(i and v and a[i] == v) +end +assert(n.n == 9000) +a = nil + +-- remove those 10000 new global variables +for i=1,10000 do _G[i] = nil end + +do -- clear global table + local a = {} + local preserve = {io = 1, string = 1, debug = 1, os = 1, + coroutine = 1, table = 1, math = 1} + for n,v in pairs(_G) do a[n]=v end + for n,v in pairs(a) do + if not preserve[n] and type(v) ~= "function" and + not string.find(n, "^[%u_]") then + _G[n] = nil + end + collectgarbage() + end +end + +local function foo () + local getfenv, setfenv, assert, next = + getfenv, setfenv, assert, next + local n = {gl1=3} + setfenv(foo, n) + assert(getfenv(foo) == getfenv(1)) + assert(getfenv(foo) == n) + assert(print == nil and gl1 == 3) + gl1 = nil + gl = 1 + assert(n.gl == 1 and next(n, 'gl') == nil) +end +foo() + +print'+' + +local function checknext (a) + local b = {} + table.foreach(a, function (k,v) b[k] = v end) + for k,v in pairs(b) do assert(a[k] == v) end + for k,v in pairs(a) do assert(b[k] == v) end + b = {} + do local k,v = next(a); while k do b[k] = v; k,v = next(a,k) end end + for k,v in pairs(b) do assert(a[k] == v) end + for k,v in pairs(a) do assert(b[k] == v) end +end + +checknext{1,x=1,y=2,z=3} +checknext{1,2,x=1,y=2,z=3} +checknext{1,2,3,x=1,y=2,z=3} +checknext{1,2,3,4,x=1,y=2,z=3} +checknext{1,2,3,4,5,x=1,y=2,z=3} + +assert(table.getn{} == 0) +assert(table.getn{[-1] = 2} == 0) +assert(table.getn{1,2,3,nil,nil} == 3) +for i=0,40 do + local a = {} + for j=1,i do a[j]=j end + assert(table.getn(a) == i) +end + + +assert(table.maxn{} == 0) +assert(table.maxn{["1000"] = true} == 0) +assert(table.maxn{["1000"] = true, [24.5] = 3} == 24.5) +assert(table.maxn{[1000] = true} == 1000) +assert(table.maxn{[10] = true, [100*math.pi] = print} == 100*math.pi) + + +-- int overflow +a = {} +for i=0,50 do a[math.pow(2,i)] = true end +assert(a[table.getn(a)]) + +print("+") + + +-- erasing values +local t = {[{1}] = 1, [{2}] = 2, [string.rep("x ", 4)] = 3, + [100.3] = 4, [4] = 5} + +local n = 0 +for k, v in pairs( t ) do + n = n+1 + assert(t[k] == v) + t[k] = nil + collectgarbage() + assert(t[k] == nil) +end +assert(n == 5) + + +local function test (a) + table.insert(a, 10); table.insert(a, 2, 20); + table.insert(a, 1, -1); table.insert(a, 40); + table.insert(a, table.getn(a)+1, 50) + table.insert(a, 2, -2) + assert(table.remove(a,1) == -1) + assert(table.remove(a,1) == -2) + assert(table.remove(a,1) == 10) + assert(table.remove(a,1) == 20) + assert(table.remove(a,1) == 40) + assert(table.remove(a,1) == 50) + assert(table.remove(a,1) == nil) +end + +a = {n=0, [-7] = "ban"} +test(a) +assert(a.n == 0 and a[-7] == "ban") + +a = {[-7] = "ban"}; +test(a) +assert(a.n == nil and table.getn(a) == 0 and a[-7] == "ban") + + +table.insert(a, 1, 10); table.insert(a, 1, 20); table.insert(a, 1, -1) +assert(table.remove(a) == 10) +assert(table.remove(a) == 20) +assert(table.remove(a) == -1) + +a = {'c', 'd'} +table.insert(a, 3, 'a') +table.insert(a, 'b') +assert(table.remove(a, 1) == 'c') +assert(table.remove(a, 1) == 'd') +assert(table.remove(a, 1) == 'a') +assert(table.remove(a, 1) == 'b') +assert(table.getn(a) == 0 and a.n == nil) +print("+") + +a = {} +for i=1,1000 do + a[i] = i; a[i-1] = nil +end +assert(next(a,nil) == 1000 and next(a,1000) == nil) + +assert(next({}) == nil) +assert(next({}, nil) == nil) + +for a,b in pairs{} do error"not here" end +for i=1,0 do error'not here' end +for i=0,1,-1 do error'not here' end +a = nil; for i=1,1 do assert(not a); a=1 end; assert(a) +a = nil; for i=1,1,-1 do assert(not a); a=1 end; assert(a) + +a = 0; for i=0, 1, 0.1 do a=a+1 end; assert(a==11) +-- precision problems +--a = 0; for i=1, 0, -0.01 do a=a+1 end; assert(a==101) +a = 0; for i=0, 0.999999999, 0.1 do a=a+1 end; assert(a==10) +a = 0; for i=1, 1, 1 do a=a+1 end; assert(a==1) +a = 0; for i=1e10, 1e10, -1 do a=a+1 end; assert(a==1) +a = 0; for i=1, 0.99999, 1 do a=a+1 end; assert(a==0) +a = 0; for i=99999, 1e5, -1 do a=a+1 end; assert(a==0) +a = 0; for i=1, 0.99999, -1 do a=a+1 end; assert(a==1) + +-- conversion +a = 0; for i="10","1","-2" do a=a+1 end; assert(a==5) + + +collectgarbage() + + +-- testing generic 'for' + +local function f (n, p) + local t = {}; for i=1,p do t[i] = i*10 end + return function (_,n) + if n > 0 then + n = n-1 + return n, unpack(t) + end + end, nil, n +end + +local x = 0 +for n,a,b,c,d in f(5,3) do + x = x+1 + assert(a == 10 and b == 20 and c == 30 and d == nil) +end +assert(x == 5) + +print"OK" diff --git a/tests/upstream/lua51/vendor/pm.lua b/tests/upstream/lua51/vendor/pm.lua new file mode 100644 index 00000000..fa125dc9 --- /dev/null +++ b/tests/upstream/lua51/vendor/pm.lua @@ -0,0 +1,273 @@ +print('testing pattern matching') + +function f(s, p) + local i,e = string.find(s, p) + if i then return string.sub(s, i, e) end +end + +function f1(s, p) + p = string.gsub(p, "%%([0-9])", function (s) return "%" .. (s+1) end) + p = string.gsub(p, "^(^?)", "%1()", 1) + p = string.gsub(p, "($?)$", "()%1", 1) + local t = {string.match(s, p)} + return string.sub(s, t[1], t[#t] - 1) +end + +a,b = string.find('', '') -- empty patterns are tricky +assert(a == 1 and b == 0); +a,b = string.find('alo', '') +assert(a == 1 and b == 0) +a,b = string.find('a\0o a\0o a\0o', 'a', 1) -- first position +assert(a == 1 and b == 1) +a,b = string.find('a\0o a\0o a\0o', 'a\0o', 2) -- starts in the midle +assert(a == 5 and b == 7) +a,b = string.find('a\0o a\0o a\0o', 'a\0o', 9) -- starts in the midle +assert(a == 9 and b == 11) +a,b = string.find('a\0a\0a\0a\0\0ab', '\0ab', 2); -- finds at the end +assert(a == 9 and b == 11); +a,b = string.find('a\0a\0a\0a\0\0ab', 'b') -- last position +assert(a == 11 and b == 11) +assert(string.find('a\0a\0a\0a\0\0ab', 'b\0') == nil) -- check ending +assert(string.find('', '\0') == nil) +assert(string.find('alo123alo', '12') == 4) +assert(string.find('alo123alo', '^12') == nil) + +assert(f('aloALO', '%l*') == 'alo') +assert(f('aLo_ALO', '%a*') == 'aLo') + +assert(f('aaab', 'a*') == 'aaa'); +assert(f('aaa', '^.*$') == 'aaa'); +assert(f('aaa', 'b*') == ''); +assert(f('aaa', 'ab*a') == 'aa') +assert(f('aba', 'ab*a') == 'aba') +assert(f('aaab', 'a+') == 'aaa') +assert(f('aaa', '^.+$') == 'aaa') +assert(f('aaa', 'b+') == nil) +assert(f('aaa', 'ab+a') == nil) +assert(f('aba', 'ab+a') == 'aba') +assert(f('a$a', '.$') == 'a') +assert(f('a$a', '.%$') == 'a$') +assert(f('a$a', '.$.') == 'a$a') +assert(f('a$a', '$$') == nil) +assert(f('a$b', 'a$') == nil) +assert(f('a$a', '$') == '') +assert(f('', 'b*') == '') +assert(f('aaa', 'bb*') == nil) +assert(f('aaab', 'a-') == '') +assert(f('aaa', '^.-$') == 'aaa') +assert(f('aabaaabaaabaaaba', 'b.*b') == 'baaabaaabaaab') +assert(f('aabaaabaaabaaaba', 'b.-b') == 'baaab') +assert(f('alo xo', '.o$') == 'xo') +assert(f(' \n isto é assim', '%S%S*') == 'isto') +assert(f(' \n isto é assim', '%S*$') == 'assim') +assert(f(' \n isto é assim', '[a-z]*$') == 'assim') +assert(f('um caracter ? extra', '[^%sa-z]') == '?') +assert(f('', 'a?') == '') +assert(f('á', 'á?') == 'á') +assert(f('ábl', 'á?b?l?') == 'ábl') +assert(f(' ábl', 'á?b?l?') == '') +assert(f('aa', '^aa?a?a') == 'aa') +assert(f(']]]áb', '[^]]') == 'á') +assert(f("0alo alo", "%x*") == "0a") +assert(f("alo alo", "%C+") == "alo alo") +print('+') + +assert(f1('alo alx 123 b\0o b\0o', '(..*) %1') == "b\0o b\0o") +assert(f1('axz123= 4= 4 34', '(.+)=(.*)=%2 %1') == '3= 4= 4 3') +assert(f1('=======', '^(=*)=%1$') == '=======') +assert(string.match('==========', '^([=]*)=%1$') == nil) + +local function range (i, j) + if i <= j then + return i, range(i+1, j) + end +end + +local abc = string.char(range(0, 255)); + +assert(string.len(abc) == 256) + +function strset (p) + local res = {s=''} + string.gsub(abc, p, function (c) res.s = res.s .. c end) + return res.s +end; + +assert(string.len(strset('[\200-\210]')) == 11) + +assert(strset('[a-z]') == "abcdefghijklmnopqrstuvwxyz") +assert(strset('[a-z%d]') == strset('[%da-uu-z]')) +assert(strset('[a-]') == "-a") +assert(strset('[^%W]') == strset('[%w]')) +assert(strset('[]%%]') == '%]') +assert(strset('[a%-z]') == '-az') +assert(strset('[%^%[%-a%]%-b]') == '-[]^ab') +assert(strset('%Z') == strset('[\1-\255]')) +assert(strset('.') == strset('[\1-\255%z]')) +print('+'); + +assert(string.match("alo xyzK", "(%w+)K") == "xyz") +assert(string.match("254 K", "(%d*)K") == "") +assert(string.match("alo ", "(%w*)$") == "") +assert(string.match("alo ", "(%w+)$") == nil) +assert(string.find("(álo)", "%(á") == 1) +local a, b, c, d, e = string.match("âlo alo", "^(((.).).* (%w*))$") +assert(a == 'âlo alo' and b == 'âl' and c == 'â' and d == 'alo' and e == nil) +a, b, c, d = string.match('0123456789', '(.+(.?)())') +assert(a == '0123456789' and b == '' and c == 11 and d == nil) +print('+') + +assert(string.gsub('ülo ülo', 'ü', 'x') == 'xlo xlo') +assert(string.gsub('alo úlo ', ' +$', '') == 'alo úlo') -- trim +assert(string.gsub(' alo alo ', '^%s*(.-)%s*$', '%1') == 'alo alo') -- double trim +assert(string.gsub('alo alo \n 123\n ', '%s+', ' ') == 'alo alo 123 ') +t = "abç d" +a, b = string.gsub(t, '(.)', '%1@') +assert('@'..a == string.gsub(t, '', '@') and b == 5) +a, b = string.gsub('abçd', '(.)', '%0@', 2) +assert(a == 'a@b@çd' and b == 2) +assert(string.gsub('alo alo', '()[al]', '%1') == '12o 56o') +assert(string.gsub("abc=xyz", "(%w*)(%p)(%w+)", "%3%2%1-%0") == + "xyz=abc-abc=xyz") +assert(string.gsub("abc", "%w", "%1%0") == "aabbcc") +assert(string.gsub("abc", "%w+", "%0%1") == "abcabc") +assert(string.gsub('áéí', '$', '\0óú') == 'áéí\0óú') +assert(string.gsub('', '^', 'r') == 'r') +assert(string.gsub('', '$', 'r') == 'r') +print('+') + +assert(string.gsub("um (dois) tres (quatro)", "(%(%w+%))", string.upper) == + "um (DOIS) tres (QUATRO)") + +do + local function setglobal (n,v) rawset(_G, n, v) end + string.gsub("a=roberto,roberto=a", "(%w+)=(%w%w*)", setglobal) + assert(_G.a=="roberto" and _G.roberto=="a") +end + +function f(a,b) return string.gsub(a,'.',b) end +assert(string.gsub("trocar tudo em |teste|b| é |beleza|al|", "|([^|]*)|([^|]*)|", f) == + "trocar tudo em bbbbb é alalalalalal") + +local function dostring (s) return loadstring(s)() or "" end +assert(string.gsub("alo $a=1$ novamente $return a$", "$([^$]*)%$", dostring) == + "alo novamente 1") + +x = string.gsub("$x=string.gsub('alo', '.', string.upper)$ assim vai para $return x$", + "$([^$]*)%$", dostring) +assert(x == ' assim vai para ALO') + +t = {} +s = 'a alo jose joao' +r = string.gsub(s, '()(%w+)()', function (a,w,b) + assert(string.len(w) == b-a); + t[a] = b-a; + end) +assert(s == r and t[1] == 1 and t[3] == 3 and t[7] == 4 and t[13] == 4) + + +function isbalanced (s) + return string.find(string.gsub(s, "%b()", ""), "[()]") == nil +end + +assert(isbalanced("(9 ((8))(\0) 7) \0\0 a b ()(c)() a")) +assert(not isbalanced("(9 ((8) 7) a b (\0 c) a")) +assert(string.gsub("alo 'oi' alo", "%b''", '"') == 'alo " alo') + + +local t = {"apple", "orange", "lime"; n=0} +assert(string.gsub("x and x and x", "x", function () t.n=t.n+1; return t[t.n] end) + == "apple and orange and lime") + +t = {n=0} +string.gsub("first second word", "%w%w*", function (w) t.n=t.n+1; t[t.n] = w end) +assert(t[1] == "first" and t[2] == "second" and t[3] == "word" and t.n == 3) + +t = {n=0} +assert(string.gsub("first second word", "%w+", + function (w) t.n=t.n+1; t[t.n] = w end, 2) == "first second word") +assert(t[1] == "first" and t[2] == "second" and t[3] == nil) + +assert(not pcall(string.gsub, "alo", "(.", print)) +assert(not pcall(string.gsub, "alo", ".)", print)) +assert(not pcall(string.gsub, "alo", "(.", {})) +assert(not pcall(string.gsub, "alo", "(.)", "%2")) +assert(not pcall(string.gsub, "alo", "(%1)", "a")) +assert(not pcall(string.gsub, "alo", "(%0)", "a")) + +-- big strings +local a = string.rep('a', 300000) +assert(string.find(a, '^a*.?$')) +assert(not string.find(a, '^a*.?b$')) +assert(string.find(a, '^a-.?$')) + +-- deep nest of gsubs +function rev (s) + return string.gsub(s, "(.)(.+)", function (c,s1) return rev(s1)..c end) +end + +local x = string.rep('012345', 10) +assert(rev(rev(x)) == x) + + +-- gsub with tables +assert(string.gsub("alo alo", ".", {}) == "alo alo") +assert(string.gsub("alo alo", "(.)", {a="AA", l=""}) == "AAo AAo") +assert(string.gsub("alo alo", "(.).", {a="AA", l="K"}) == "AAo AAo") +assert(string.gsub("alo alo", "((.)(.?))", {al="AA", o=false}) == "AAo AAo") + +assert(string.gsub("alo alo", "().", {2,5,6}) == "256 alo") + +t = {}; setmetatable(t, {__index = function (t,s) return string.upper(s) end}) +assert(string.gsub("a alo b hi", "%w%w+", t) == "a ALO b HI") + + +-- tests for gmatch +assert(string.gfind == string.gmatch) +local a = 0 +for i in string.gmatch('abcde', '()') do assert(i == a+1); a=i end +assert(a==6) + +t = {n=0} +for w in string.gmatch("first second word", "%w+") do + t.n=t.n+1; t[t.n] = w +end +assert(t[1] == "first" and t[2] == "second" and t[3] == "word") + +t = {3, 6, 9} +for i in string.gmatch ("xuxx uu ppar r", "()(.)%2") do + assert(i == table.remove(t, 1)) +end +assert(table.getn(t) == 0) + +t = {} +for i,j in string.gmatch("13 14 10 = 11, 15= 16, 22=23", "(%d+)%s*=%s*(%d+)") do + t[i] = j +end +a = 0 +for k,v in pairs(t) do assert(k+1 == v+0); a=a+1 end +assert(a == 3) + + +-- tests for `%f' (`frontiers') + +assert(string.gsub("aaa aa a aaa a", "%f[%w]a", "x") == "xaa xa x xaa x") +assert(string.gsub("[[]] [][] [[[[", "%f[[].", "x") == "x[]] x]x] x[[[") +assert(string.gsub("01abc45de3", "%f[%d]", ".") == ".01abc.45de.3") +assert(string.gsub("01abc45 de3x", "%f[%D]%w", ".") == "01.bc45 de3.") +assert(string.gsub("function", "%f[\1-\255]%w", ".") == ".unction") +assert(string.gsub("function", "%f[^\1-\255]", ".") == "function.") + +local i, e = string.find(" alo aalo allo", "%f[%S].-%f[%s].-%f[%S]") +assert(i == 2 and e == 5) +local k = string.match(" alo aalo allo", "%f[%S](.-%f[%s].-%f[%S])") +assert(k == 'alo ') + +local a = {1, 5, 9, 14, 17,} +for k in string.gmatch("alo alo th02 is 1hat", "()%f[%w%d]") do + assert(table.remove(a, 1) == k) +end +assert(table.getn(a) == 0) + + +print('OK') diff --git a/tests/upstream/lua51/vendor/sort.lua b/tests/upstream/lua51/vendor/sort.lua new file mode 100644 index 00000000..6fccd49e --- /dev/null +++ b/tests/upstream/lua51/vendor/sort.lua @@ -0,0 +1,74 @@ +print"testing sort" + + +function check (a, f) + f = f or function (x,y) return x 'alo\0alo\0') +assert('alo' < 'alo\0') +assert('alo\0' > 'alo') +assert('\0' < '\1') +assert('\0\0' < '\0\1') +assert('\1\0a\0a' <= '\1\0a\0a') +assert(not ('\1\0a\0b' <= '\1\0a\0a')) +assert('\0\0\0' < '\0\0\0\0') +assert(not('\0\0\0\0' < '\0\0\0')) +assert('\0\0\0' <= '\0\0\0\0') +assert(not('\0\0\0\0' <= '\0\0\0')) +assert('\0\0\0' <= '\0\0\0') +assert('\0\0\0' >= '\0\0\0') +assert(not ('\0\0b' < '\0\0a\0')) +print('+') + +assert(string.sub("123456789",2,4) == "234") +assert(string.sub("123456789",7) == "789") +assert(string.sub("123456789",7,6) == "") +assert(string.sub("123456789",7,7) == "7") +assert(string.sub("123456789",0,0) == "") +assert(string.sub("123456789",-10,10) == "123456789") +assert(string.sub("123456789",1,9) == "123456789") +assert(string.sub("123456789",-10,-20) == "") +assert(string.sub("123456789",-1) == "9") +assert(string.sub("123456789",-4) == "6789") +assert(string.sub("123456789",-6, -4) == "456") +assert(string.sub("\000123456789",3,5) == "234") +assert(("\000123456789"):sub(8) == "789") +print('+') + +assert(string.find("123456789", "345") == 3) +a,b = string.find("123456789", "345") +assert(string.sub("123456789", a, b) == "345") +assert(string.find("1234567890123456789", "345", 3) == 3) +assert(string.find("1234567890123456789", "345", 4) == 13) +assert(string.find("1234567890123456789", "346", 4) == nil) +assert(string.find("1234567890123456789", ".45", -9) == 13) +assert(string.find("abcdefg", "\0", 5, 1) == nil) +assert(string.find("", "") == 1) +assert(string.find('', 'aaa', 1) == nil) +assert(('alo(.)alo'):find('(.)', 1, 1) == 4) +print('+') + +assert(string.len("") == 0) +assert(string.len("\0\0\0") == 3) +assert(string.len("1234567890") == 10) + +assert(#"" == 0) +assert(#"\0\0\0" == 3) +assert(#"1234567890" == 10) + +assert(string.byte("a") == 97) +assert(string.byte("á") > 127) +assert(string.byte(string.char(255)) == 255) +assert(string.byte(string.char(0)) == 0) +assert(string.byte("\0") == 0) +assert(string.byte("\0\0alo\0x", -1) == string.byte('x')) +assert(string.byte("ba", 2) == 97) +assert(string.byte("\n\n", 2, -1) == 10) +assert(string.byte("\n\n", 2, 2) == 10) +assert(string.byte("") == nil) +assert(string.byte("hi", -3) == nil) +assert(string.byte("hi", 3) == nil) +assert(string.byte("hi", 9, 10) == nil) +assert(string.byte("hi", 2, 1) == nil) +assert(string.char() == "") +assert(string.char(0, 255, 0) == "\0\255\0") +assert(string.char(0, string.byte("á"), 0) == "\0á\0") +assert(string.char(string.byte("ál\0óu", 1, -1)) == "ál\0óu") +assert(string.char(string.byte("ál\0óu", 1, 0)) == "") +assert(string.char(string.byte("ál\0óu", -10, 100)) == "ál\0óu") +print('+') + +assert(string.upper("ab\0c") == "AB\0C") +assert(string.lower("\0ABCc%$") == "\0abcc%$") +assert(string.rep('teste', 0) == '') +assert(string.rep('tés\00tê', 2) == 'tés\0têtés\000tê') +assert(string.rep('', 10) == '') + +assert(string.reverse"" == "") +assert(string.reverse"\0\1\2\3" == "\3\2\1\0") +assert(string.reverse"\0001234" == "4321\0") + +for i=0,30 do assert(string.len(string.rep('a', i)) == i) end + +assert(type(tostring(nil)) == 'string') +assert(type(tostring(12)) == 'string') +assert(''..12 == '12' and type(12 .. '') == 'string') +assert(string.find(tostring{}, 'table:')) +assert(string.find(tostring(print), 'function:')) +assert(tostring(1234567890123) == '1234567890123') +assert(#tostring('\0') == 1) +assert(tostring(true) == "true") +assert(tostring(false) == "false") +print('+') + +x = '"ílo"\n\\' +assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\') +assert(string.format('%q', "\0") == [["\000"]]) +assert(string.format("\0%c\0%c%x\0", string.byte("á"), string.byte("b"), 140) == + "\0á\0b8c\0") +assert(string.format('') == "") +assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) == + string.format("%c%c%c%c", 34, 48, 90, 100)) +assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be') +assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023") +assert(tonumber(string.format("%f", 10.3)) == 10.3) +x = string.format('"%-50s"', 'a') +assert(#x == 52) +assert(string.sub(x, 1, 4) == '"a ') + +assert(string.format("-%.20s.20s", string.rep("%", 2000)) == "-"..string.rep("%", 20)..".20s") +assert(string.format('"-%20s.20s"', string.rep("%", 2000)) == + string.format("%q", "-"..string.rep("%", 2000)..".20s")) + + +-- longest number that can be formated +assert(string.len(string.format('%99.99f', -1e308)) >= 100) + +assert(loadstring("return 1\n--comentário sem EOL no final")() == 1) + + +assert(table.concat{} == "") +assert(table.concat({}, 'x') == "") +assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2") +local a = {}; for i=1,3000 do a[i] = "xuxu" end +assert(table.concat(a, "123").."123" == string.rep("xuxu123", 3000)) +assert(table.concat(a, "b", 20, 20) == "xuxu") +assert(table.concat(a, "", 20, 21) == "xuxuxuxu") +assert(table.concat(a, "", 22, 21) == "") +assert(table.concat(a, "3", 2999) == "xuxu3xuxu") + +a = {"a","b","c"} +assert(table.concat(a, ",", 1, 0) == "") +assert(table.concat(a, ",", 1, 1) == "a") +assert(table.concat(a, ",", 1, 2) == "a,b") +assert(table.concat(a, ",", 2) == "b,c") +assert(table.concat(a, ",", 3) == "c") +assert(table.concat(a, ",", 4) == "") + +local locales = { "ptb", "ISO-8859-1", "pt_BR" } +local function trylocale (w) + for _, l in ipairs(locales) do + if os.setlocale(l, w) then return true end + end + return false +end + +if not trylocale("collate") then + print("locale not supported") +else + assert("alo" < "álo" and "álo" < "amo") +end + +if not trylocale("ctype") then + print("locale not supported") +else + assert(string.gsub("áéíóú", "%a", "x") == "xxxxx") + assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ") + assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx") + assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO") +end + +os.setlocale("C") +assert(os.setlocale() == 'C') +assert(os.setlocale(nil, "numeric") == 'C') + +print('OK') + + diff --git a/tests/upstream/lua51/vendor/vararg.lua b/tests/upstream/lua51/vendor/vararg.lua new file mode 100644 index 00000000..ae068fac --- /dev/null +++ b/tests/upstream/lua51/vendor/vararg.lua @@ -0,0 +1,126 @@ +print('testing vararg') + +_G.arg = nil + +function f(a, ...) + assert(type(arg) == 'table') + assert(type(arg.n) == 'number') + for i=1,arg.n do assert(a[i]==arg[i]) end + return arg.n +end + +function c12 (...) + assert(arg == nil) + local x = {...}; x.n = table.getn(x) + local res = (x.n==2 and x[1] == 1 and x[2] == 2) + if res then res = 55 end + return res, 2 +end + +function vararg (...) return arg end + +local call = function (f, args) return f(unpack(args, 1, args.n)) end + +assert(f() == 0) +assert(f({1,2,3}, 1, 2, 3) == 3) +assert(f({"alo", nil, 45, f, nil}, "alo", nil, 45, f, nil) == 5) + +assert(c12(1,2)==55) +a,b = assert(call(c12, {1,2})) +assert(a == 55 and b == 2) +a = call(c12, {1,2;n=2}) +assert(a == 55 and b == 2) +a = call(c12, {1,2;n=1}) +assert(not a) +assert(c12(1,2,3) == false) +local a = vararg(call(next, {_G,nil;n=2})) +local b,c = next(_G) +assert(a[1] == b and a[2] == c and a.n == 2) +a = vararg(call(call, {c12, {1,2}})) +assert(a.n == 2 and a[1] == 55 and a[2] == 2) +a = call(print, {'+'}) +assert(a == nil) + +local t = {1, 10} +function t:f (...) return self[arg[1]]+arg.n end +assert(t:f(1,4) == 3 and t:f(2) == 11) +print('+') + +lim = 20 +local i, a = 1, {} +while i <= lim do a[i] = i+0.3; i=i+1 end + +function f(a, b, c, d, ...) + local more = {...} + assert(a == 1.3 and more[1] == 5.3 and + more[lim-4] == lim+0.3 and not more[lim-3]) +end + +function g(a,b,c) + assert(a == 1.3 and b == 2.3 and c == 3.3) +end + +call(f, a) +call(g, a) + +a = {} +i = 1 +while i <= lim do a[i] = i; i=i+1 end +assert(call(math.max, a) == lim) + +print("+") + + +-- new-style varargs + +function oneless (a, ...) return ... end + +function f (n, a, ...) + local b + assert(arg == nil) + if n == 0 then + local b, c, d = ... + return a, b, c, d, oneless(oneless(oneless(...))) + else + n, b, a = n-1, ..., a + assert(b == ...) + return f(n, a, ...) + end +end + +a,b,c,d,e = assert(f(10,5,4,3,2,1)) +assert(a==5 and b==4 and c==3 and d==2 and e==1) + +a,b,c,d,e = f(4) +assert(a==nil and b==nil and c==nil and d==nil and e==nil) + + +-- varargs for main chunks +f = loadstring[[ return {...} ]] +x = f(2,3) +assert(x[1] == 2 and x[2] == 3 and x[3] == nil) + + +f = loadstring[[ + local x = {...} + for i=1,select('#', ...) do assert(x[i] == select(i, ...)) end + assert(x[select('#', ...)+1] == nil) + return true +]] + +assert(f("a", "b", nil, {}, assert)) +assert(f()) + +a = {select(3, unpack{10,20,30,40})} +assert(table.getn(a) == 2 and a[1] == 30 and a[2] == 40) +a = {select(1)} +assert(next(a) == nil) +a = {select(-1, 3, 5, 7)} +assert(a[1] == 7 and a[2] == nil) +a = {select(-2, 3, 5, 7)} +assert(a[1] == 5 and a[2] == 7 and a[3] == nil) +pcall(select, 10000) +pcall(select, -10000) + +print('OK') + diff --git a/tests/upstream/lua51/vendor/verybig.lua b/tests/upstream/lua51/vendor/verybig.lua new file mode 100644 index 00000000..59e0142a --- /dev/null +++ b/tests/upstream/lua51/vendor/verybig.lua @@ -0,0 +1,100 @@ +if rawget(_G, "_soft") then return 10 end + +print "testing large programs (>64k)" + +-- template to create a very big test file +prog = [[$ + +local a,b + +b = {$1$ + b30009 = 65534, + b30010 = 65535, + b30011 = 65536, + b30012 = 65537, + b30013 = 16777214, + b30014 = 16777215, + b30015 = 16777216, + b30016 = 16777217, + b30017 = 4294967294, + b30018 = 4294967295, + b30019 = 4294967296, + b30020 = 4294967297, + b30021 = -65534, + b30022 = -65535, + b30023 = -65536, + b30024 = -4294967297, + b30025 = 15012.5, + $2$ +}; + +assert(b.a50008 == 25004 and b["a11"] == 5.5) +assert(b.a33007 == 16503.5 and b.a50009 == 25004.5) +assert(b["b"..30024] == -4294967297) + +function b:xxx (a,b) return a+b end +assert(b:xxx(10, 12) == 22) -- pushself with non-constant index +b.xxx = nil + +s = 0; n=0 +for a,b in pairs(b) do s=s+b; n=n+1 end +assert(s==13977183656.5 and n==70001) + +require "checktable" +stat(b) + +a = nil; b = nil +print'+' + +function f(x) b=x end + +a = f{$3$} or 10 + +assert(a==10) +assert(b[1] == "a10" and b[2] == 5 and b[table.getn(b)-1] == "a50009") + + +function xxxx (x) return b[x] end + +assert(xxxx(3) == "a11") + +a = nil; b=nil +xxxx = nil + +return 10 + +]] + +-- functions to fill in the $n$ +F = { +function () -- $1$ + for i=10,50009 do + io.write('a', i, ' = ', 5+((i-10)/2), ',\n') + end +end, + +function () -- $2$ + for i=30026,50009 do + io.write('b', i, ' = ', 15013+((i-30026)/2), ',\n') + end +end, + +function () -- $3$ + for i=10,50009 do + io.write('"a', i, '", ', 5+((i-10)/2), ',\n') + end +end, +} + +file = os.tmpname() +io.output(file) +for s in string.gmatch(prog, "$([^$]+)") do + local n = tonumber(s) + if not n then io.write(s) else F[n]() end +end +io.close() +result = dofile(file) +assert(os.remove(file)) +print'OK' +return result + From 356cd84ecc0d0c9afdacd9d34c5600d5a9cd23d1 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:23:19 +0100 Subject: [PATCH 02/20] remove redundant README notes --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 0680efac..f0b2ea59 100644 --- a/README.md +++ b/README.md @@ -151,17 +151,6 @@ official Lua 5.1 suite on Lua 5.1, inside Docker: ./scripts/run-tests.sh --case lua51/closure.lua --preset Vmify --seed 4 -n 1 ``` -Failures do not stop remaining cases, presets, or iterations. Logs and failing -inputs/output persist in `test-results/`. Each obfuscation or execution has a -30-second limit (override with `--timeout N`). AntiTamper is removed from tested -presets. See [upstream suite provenance, exclusions, and reproduction](tests/upstream/lua51/README.md). - -Metadata directives may appear anywhere in the leading comment region, including -after license comments and blank lines: `@skip`, `@runtime lua5.1 luau`, -`@luau-only`, and `@skip-preset Weak`. Underscore aliases are accepted for the -last two directives, and `lua51` is an alias for `lua5.1`. Preset names are -case-sensitive; unknown or malformed directives fail with a file/line diagnostic. - --- ## Community From e389f848393f962a314d5d667a8bc03d0f8d48e7 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:30:29 +0100 Subject: [PATCH 03/20] fix number serialization --- src/prometheus/unparser.lua | 23 +++++++++++++++++------ tests/numbers.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 tests/numbers.lua diff --git a/src/prometheus/unparser.lua b/src/prometheus/unparser.lua index 0aacd511..abf06aa8 100644 --- a/src/prometheus/unparser.lua +++ b/src/prometheus/unparser.lua @@ -407,12 +407,23 @@ function Unparser:unparseExpression(expression, tabbing) end if(expression.kind == AstKind.NumberExpression) then - local str = tostring(expression.value); - if(str == "inf") then - return "2e1024" - end - if(str == "-inf") then - return "-2e1024" + local value = expression.value; + local str = tostring(value); + -- Some steps supply numeric source strings rather than numeric values. + if(type(value) == "number") then + if(value ~= value) then + return "(0/0)"; + end + if(value == math.huge) then + return "2e1024"; + end + if(value == -math.huge) then + return "-2e1024"; + end + -- Keep compact output when it round-trips; doubles can need 17 digits. + if(tonumber(str) ~= value) then + str = string.format("%.17g", value); + end end if(str:sub(1, 2) == "0.") then str = str:sub(2); diff --git a/tests/numbers.lua b/tests/numbers.lua new file mode 100644 index 00000000..c2b8690c --- /dev/null +++ b/tests/numbers.lua @@ -0,0 +1,26 @@ +-- Number emission must preserve values, including folded non-finite results. +local precise = -1.00000000000001 +assert(precise == tonumber("-1.00000000000001")) +local large = 1111111111111111 +local adjacent = 1111111111111110 +assert(large == tonumber("1111111111111111")) +assert(adjacent == tonumber("1111111111111110")) +assert(large - adjacent == 1) + +local fraction = 0.12345678901234567 +assert(fraction == tonumber("0.12345678901234567")) +local tiny = 1.2345678901234567e-300 +local maximum = 1.7976931348623157e308 +assert(tiny == tonumber("1.2345678901234567e-300")) +assert(maximum == tonumber("1.7976931348623157e308")) + +local positiveInfinity = 10e500 +local negativeInfinity = -10e500 +assert(positiveInfinity == math.huge) +assert(negativeInfinity == -math.huge) +local nan = 10e500 - 10e400 +assert(type(nan) == "number" and nan ~= nan) +assert(not (nan < 0) and not (nan >= 0)) +assert(not pcall(function() local t = {}; t[nan] = true end)) + +print("numbers OK") From b5cf7f3f7621aca410c933ed2c4423b001ee899d Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:32:57 +0100 Subject: [PATCH 04/20] preserve non-finite values during number mutation --- Dockerfile | 4 ++- src/prometheus/steps/NumbersToExpressions.lua | 5 +++ tests/harness/numbers-to-expressions.lua | 34 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/harness/numbers-to-expressions.lua diff --git a/Dockerfile b/Dockerfile index 8736e032..06c2ea8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,8 @@ RUN lua5.1 -v && printf 'print("luau OK")\n' > /tmp/luau_check.lua && luau /tmp/ WORKDIR /app COPY . /app -RUN lua5.1 tests/harness/metadata.lua && python3 tests/harness/runner.py +RUN lua5.1 tests/harness/metadata.lua \ + && timeout 30s lua5.1 tests/harness/numbers-to-expressions.lua \ + && python3 tests/harness/runner.py ENTRYPOINT ["lua5.1", "docker-test-runner.lua"] diff --git a/src/prometheus/steps/NumbersToExpressions.lua b/src/prometheus/steps/NumbersToExpressions.lua index 6d4b3838..ed4ab657 100644 --- a/src/prometheus/steps/NumbersToExpressions.lua +++ b/src/prometheus/steps/NumbersToExpressions.lua @@ -106,6 +106,11 @@ function NumbersToExpressions:init(_) end function NumbersToExpressions:CreateNumberExpression(val, depth) + -- Non-finite values have no safe integer or scientific representation. + if val ~= val or val == math.huge or val == -math.huge then + return Ast.NumberExpression(val) + end + if depth > 0 and math.random() >= self.InternalThreshold or depth > 15 then local format = self.AllowedNumberRepresentations[math.random(1, #self.AllowedNumberRepresentations)] if not self.NumberRepresentationMutation then diff --git a/tests/harness/numbers-to-expressions.lua b/tests/harness/numbers-to-expressions.lua new file mode 100644 index 00000000..756a0f14 --- /dev/null +++ b/tests/harness/numbers-to-expressions.lua @@ -0,0 +1,34 @@ +local Prometheus = require("src.prometheus") +Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error +local oldPath = package.path +package.path = "src/?.lua;" .. package.path +local NumbersToExpressions = require("prometheus.steps.NumbersToExpressions") +local Unparser = require("prometheus.unparser") +package.path = oldPath +local unparser = Unparser:new({LuaVersion = "Lua51"}) + +-- Exercise every representation directly, including recursive/depth-limit paths. +-- Infinity previously produced invalid scientific/hex text or looped in binary. +for _, representation in ipairs({"hex", "binary", "scientific", "normal"}) do + for _, mutation in ipairs({false, true}) do + local step = NumbersToExpressions:new({ + InternalThreshold = 0, + NumberRepresentationMutation = mutation, + AllowedNumberRepresentations = {representation}, + }) + for _, depth in ipairs({0, 1, 16}) do + for _, value in ipairs({math.huge, -math.huge, 0/0}) do + local node = step:CreateNumberExpression(value, depth) + local code = unparser:unparseExpression(node, 0) + local actual = assert(loadstring("return " .. code))() + assert(type(actual) == "number", representation .. ": " .. code) + if value ~= value then + assert(actual ~= actual, representation .. ": lost NaN") + else + assert(actual == value, representation .. ": changed infinity") + end + end + end + end +end +print("Non-finite number mutation regression checks passed") From 67e3fd8e0743457fa45852a4f435ba0eb654f82b Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:37:42 +0100 Subject: [PATCH 05/20] preserve nil argument and return counts in vmify --- Dockerfile | 1 + src/prometheus/compiler/compile_top.lua | 12 +++--- src/prometheus/compiler/compiler.lua | 39 +++++++++++++++++- src/prometheus/compiler/emit.lua | 2 +- .../compiler/expressions/function_call.lua | 2 +- .../expressions/pass_self_function_call.lua | 2 +- src/prometheus/compiler/register.lua | 9 +++- src/prometheus/compiler/statements/return.lua | 8 ++-- tests/harness/vm-arity.lua | 18 ++++++++ tests/vararg-nils.lua | 41 +++++++++++++++++++ 10 files changed, 117 insertions(+), 17 deletions(-) create mode 100644 tests/harness/vm-arity.lua create mode 100644 tests/vararg-nils.lua diff --git a/Dockerfile b/Dockerfile index 06c2ea8f..5f8d4e30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,7 @@ COPY . /app RUN lua5.1 tests/harness/metadata.lua \ && timeout 30s lua5.1 tests/harness/numbers-to-expressions.lua \ + && timeout 30s lua5.1 tests/harness/vm-arity.lua \ && python3 tests/harness/runner.py ENTRYPOINT ["lua5.1", "docker-test-runner.lua"] diff --git a/src/prometheus/compiler/compile_top.lua b/src/prometheus/compiler/compile_top.lua index f42c15df..b98c8184 100644 --- a/src/prometheus/compiler/compile_top.lua +++ b/src/prometheus/compiler/compile_top.lua @@ -56,7 +56,7 @@ return function(Compiler) self:compileBlock(node.body, 0); if(self.activeBlock.advanceToNextBlock) then self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); - self:addStatement(self:setReturn(self.activeBlock.scope, Ast.TableConstructorExpression({})), {self.RETURN_REGISTER}, {}, false) + self:addStatement(self:setReturn(self.activeBlock.scope, self:pack(self.activeBlock.scope, {})), {self.RETURN_REGISTER}, {}, false) self.activeBlock.advanceToNextBlock = false; end @@ -123,13 +123,13 @@ return function(Compiler) scope:addReferenceToHigherScope(self.containerFuncScope, self.argsVar); scope:addReferenceToHigherScope(self.scope, self.selectVar); scope:addReferenceToHigherScope(self.scope, self.unpackVar); - self:addStatement(self:setRegister(scope, self.varargReg, Ast.TableConstructorExpression({ - Ast.TableEntry(Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.selectVar), { + self:addStatement(self:setRegister(scope, self.varargReg, self:pack(scope, { + Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.selectVar), { Ast.NumberExpression(i); - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), { + Ast.FunctionCallExpression(self:unpack(scope), { Ast.VariableExpression(self.containerFuncScope, self.argsVar), }); - })), + }), })), {self.varargReg}, {}, false); end end @@ -137,7 +137,7 @@ return function(Compiler) self:compileBlock(node.body, funcDepth); if(self.activeBlock.advanceToNextBlock) then self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); - self:addStatement(self:setReturn(self.activeBlock.scope, Ast.TableConstructorExpression({})), {self.RETURN_REGISTER}, {}, false); + self:addStatement(self:setReturn(self.activeBlock.scope, self:pack(self.activeBlock.scope, {})), {self.RETURN_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end diff --git a/src/prometheus/compiler/compiler.lua b/src/prometheus/compiler/compiler.lua index 107333f8..3ba8f1e1 100644 --- a/src/prometheus/compiler/compiler.lua +++ b/src/prometheus/compiler/compiler.lua @@ -116,11 +116,14 @@ function Compiler:compile(ast) psc:addReferenceToHigherScope(newGlobalScope, newproxyVar); psc:addReferenceToHigherScope(newGlobalScope, setmetatableVar); psc:addReferenceToHigherScope(newGlobalScope, getmetatableVar); + psc:addReferenceToHigherScope(newGlobalScope, selectVar); self.scope = Scope:new(psc); self.envVar = self.scope:addVariable(); self.containerFuncVar = self.scope:addVariable(); self.unpackVar = self.scope:addVariable(); + self.packVar = self.scope:addVariable(); + self.unpackPackedVar = self.scope:addVariable(); self.newproxyVar = self.scope:addVariable(); self.setmetatableVar = self.scope:addVariable(); self.getmetatableVar = self.scope:addVariable(); @@ -183,7 +186,34 @@ function Compiler:compile(ast) self:compileTopNode(ast); + -- VM value lists carry an explicit count: table length loses trailing nils. + local packScope = Scope:new(self.scope); + packScope:addReferenceToHigherScope(self.scope, self.selectVar); + local unpackScope = Scope:new(self.scope); + local unpackValuesVar = unpackScope:addVariable(); + unpackScope:addReferenceToHigherScope(self.scope, self.unpackVar); + local functionNodeAssignments = { + { + var = Ast.AssignmentVariable(self.scope, self.packVar), + val = Ast.FunctionLiteralExpression({Ast.VarargExpression()}, Ast.Block({ + Ast.ReturnStatement{Ast.TableConstructorExpression({ + Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.FunctionCallExpression( + Ast.VariableExpression(self.scope, self.selectVar), + {Ast.StringExpression("#"), Ast.VarargExpression()})), + Ast.TableEntry(Ast.VarargExpression()), + })}, + }, packScope)), + }, { + var = Ast.AssignmentVariable(self.scope, self.unpackPackedVar), + val = Ast.FunctionLiteralExpression({Ast.VariableExpression(unpackScope, unpackValuesVar)}, Ast.Block({ + Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), { + Ast.VariableExpression(unpackScope, unpackValuesVar), + Ast.NumberExpression(1), + Ast.IndexExpression(Ast.VariableExpression(unpackScope, unpackValuesVar), Ast.StringExpression("n")), + })}, + }, unpackScope)), + }, { var = Ast.AssignmentVariable(self.scope, self.containerFuncVar), val = Ast.FunctionLiteralExpression({ @@ -214,7 +244,7 @@ function Compiler:compile(ast) Ast.ReturnStatement{ Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.containerFuncVar), { Ast.VariableExpression(createClosureScope, createClosurePosArg), - Ast.TableConstructorExpression({Ast.TableEntry(Ast.VarargExpression())}), + self:pack(createClosureSubScope, {Ast.VarargExpression()}), Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), Ast.VariableExpression(createClosureScope, createClosureProxyObject) }) @@ -250,6 +280,8 @@ function Compiler:compile(ast) } local tbl = { + Ast.VariableExpression(self.scope, self.packVar), + Ast.VariableExpression(self.scope, self.unpackPackedVar), Ast.VariableExpression(self.scope, self.containerFuncVar), Ast.VariableExpression(self.scope, self.createVarargClosureVar), Ast.VariableExpression(self.scope, self.upvaluesTable), @@ -294,6 +326,8 @@ function Compiler:compile(ast) Ast.VariableExpression(newGlobalScope, getmetatableVar); Ast.VariableExpression(newGlobalScope, selectVar); Ast.TableConstructorExpression({ + Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.FunctionCallExpression( + Ast.VariableExpression(newGlobalScope, selectVar), {Ast.StringExpression("#"), Ast.VarargExpression()})), Ast.TableEntry(Ast.VarargExpression()); }) } @@ -308,7 +342,7 @@ function Compiler:compile(ast) Ast.FunctionCallExpression(Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { Ast.NumberExpression(self.startBlockId); Ast.TableConstructorExpression(upvalEntries); - }), {Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), {Ast.VariableExpression(self.scope, argVar)})}); + }), {Ast.FunctionCallExpression(self:unpack(self.scope), {Ast.VariableExpression(self.scope, argVar)})}); } }, self.scope)); @@ -343,6 +377,7 @@ function Compiler:getCreateClosureVar(argCount) argsTb[i] = Ast.VariableExpression(createClosureSubScope, arg); argsTb2[i] = Ast.TableEntry(Ast.VariableExpression(createClosureSubScope, arg)); end + table.insert(argsTb2, Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.NumberExpression(argCount))); local val = Ast.FunctionLiteralExpression({ Ast.VariableExpression(createClosureScope, createClosurePosArg), diff --git a/src/prometheus/compiler/emit.lua b/src/prometheus/compiler/emit.lua index 0cc55f43..53db382c 100644 --- a/src/prometheus/compiler/emit.lua +++ b/src/prometheus/compiler/emit.lua @@ -348,7 +348,7 @@ return function(Compiler) })); table.insert(stats, Ast.ReturnStatement{ - Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.unpackVar), { + Ast.FunctionCallExpression(self:unpack(self.containerFuncScope), { Ast.VariableExpression(self.containerFuncScope, self.returnVar) }); }); diff --git a/src/prometheus/compiler/expressions/function_call.lua b/src/prometheus/compiler/expressions/function_call.lua index a9de1f6c..d1bd20f7 100644 --- a/src/prometheus/compiler/expressions/function_call.lua +++ b/src/prometheus/compiler/expressions/function_call.lua @@ -38,7 +38,7 @@ return function(self, expression, funcDepth, numReturns) end if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], Ast.TableConstructorExpression{Ast.TableEntry(Ast.FunctionCallExpression(self:register(scope, baseReg), args))}), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {Ast.FunctionCallExpression(self:register(scope, baseReg), args)})), {retRegs[1]}, {baseReg, unpack(regs)}, true); else if numReturns > 1 then local tmpReg = self:allocRegister(false); diff --git a/src/prometheus/compiler/expressions/pass_self_function_call.lua b/src/prometheus/compiler/expressions/pass_self_function_call.lua index c0a7c4db..c158fc61 100644 --- a/src/prometheus/compiler/expressions/pass_self_function_call.lua +++ b/src/prometheus/compiler/expressions/pass_self_function_call.lua @@ -44,7 +44,7 @@ return function(self, expression, funcDepth, numReturns) self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, false); if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], Ast.TableConstructorExpression{Ast.TableEntry(Ast.FunctionCallExpression(self:register(scope, tmpReg), args))}), {retRegs[1]}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {Ast.FunctionCallExpression(self:register(scope, tmpReg), args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); else self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(Ast.FunctionCallExpression(self:register(scope, tmpReg), args))}), {tmpReg}, {tmpReg, unpack(regs)}, true); diff --git a/src/prometheus/compiler/register.lua b/src/prometheus/compiler/register.lua index e4b4b2f5..82792f69 100644 --- a/src/prometheus/compiler/register.lua +++ b/src/prometheus/compiler/register.lua @@ -202,8 +202,13 @@ return function(Compiler) end function Compiler:unpack(scope) - scope:addReferenceToHigherScope(self.scope, self.unpackVar); - return Ast.VariableExpression(self.scope, self.unpackVar); + scope:addReferenceToHigherScope(self.scope, self.unpackPackedVar); + return Ast.VariableExpression(self.scope, self.unpackPackedVar); + end + + function Compiler:pack(scope, values) + scope:addReferenceToHigherScope(self.scope, self.packVar); + return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.packVar), values); end function Compiler:env(scope) diff --git a/src/prometheus/compiler/statements/return.lua b/src/prometheus/compiler/statements/return.lua index c60ca7de..70c540d2 100644 --- a/src/prometheus/compiler/statements/return.lua +++ b/src/prometheus/compiler/statements/return.lua @@ -15,13 +15,13 @@ return function(self, statement, funcDepth) for i, expr in ipairs(statement.args) do if i == #statement.args and (expr.kind == AstKind.FunctionCallExpression or expr.kind == AstKind.PassSelfFunctionCallExpression or expr.kind == AstKind.VarargExpression) then local reg = self:compileExpression(expr, funcDepth, self.RETURN_ALL)[1]; - table.insert(entries, Ast.TableEntry(Ast.FunctionCallExpression( + table.insert(entries, Ast.FunctionCallExpression( self:unpack(scope), - {self:register(scope, reg)}))); + {self:register(scope, reg)})); table.insert(regs, reg); else local reg = self:compileExpression(expr, funcDepth, 1)[1]; - table.insert(entries, Ast.TableEntry(self:register(scope, reg))); + table.insert(entries, self:register(scope, reg)); table.insert(regs, reg); end end @@ -30,7 +30,7 @@ return function(self, statement, funcDepth) self:freeRegister(reg, false); end - self:addStatement(self:setReturn(scope, Ast.TableConstructorExpression(entries)), {self.RETURN_REGISTER}, regs, false); + self:addStatement(self:setReturn(scope, self:pack(scope, entries)), {self.RETURN_REGISTER}, regs, false); self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end; diff --git a/tests/harness/vm-arity.lua b/tests/harness/vm-arity.lua new file mode 100644 index 00000000..5ab26f5e --- /dev/null +++ b/tests/harness/vm-arity.lua @@ -0,0 +1,18 @@ +local Prometheus = require("src.prometheus") +Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error +local function pack(...) return {n = select("#", ...), ...} end +-- The outer wrapper must preserve chunk arguments and chunk return counts too. +for _, seed in ipairs({1, 2, 3}) do + local config = {LuaVersion = "Lua51", Seed = seed, Steps = {{Name = "Vmify"}}} + local generated = Prometheus.Pipeline:fromConfig(config):apply("return ...") + local chunk = assert(loadstring(generated)) + for _, args in ipairs({pack(), pack(nil), pack(1, nil), pack(nil, 2, nil)}) do + local actual = pack(chunk(unpack(args, 1, args.n))) + assert(actual.n == args.n, "changed chunk arity") + for i = 1, args.n do assert(actual[i] == args[i], "changed chunk value") end + end + local generated = Prometheus.Pipeline:fromConfig(config):apply("return nil, nil") + local actual = pack(assert(loadstring(generated))()) + assert(actual.n == 2 and actual[1] == nil and actual[2] == nil) +end +print("VM chunk arity regression checks passed") diff --git a/tests/vararg-nils.lua b/tests/vararg-nils.lua new file mode 100644 index 00000000..e5ba3fe4 --- /dev/null +++ b/tests/vararg-nils.lua @@ -0,0 +1,41 @@ +-- Packed VM argument/return lists must distinguish no values from nil values. +local function echo(...) return ... end +local function noResults() end +local function nilResult() return nil end +assert(select("#", echo()) == 0) +assert(select("#", echo(nil)) == 1) +assert(select("#", echo(1, nil)) == 2) +assert(select("#", echo(nil, nil, nil)) == 3) +assert(select("#", echo(noResults())) == 0) +assert(select("#", echo(nilResult())) == 1) +assert(select("#", echo(tonumber("invalid"))) == 1) +assert(select("#", echo("prefix", echo(nil, nil))) == 3) + +local function tail(first, second, ...) return ... end +assert(select("#", tail()) == 0) +assert(select("#", tail(1)) == 0) +assert(select("#", tail(1, 2, nil, nil)) == 2) +local function fixed(first, second) return first, second end +assert(select("#", fixed()) == 2) +assert(select("#", fixed(1, nil)) == 2) + +local object = {} +function object:echo(...) assert(self == object); return ... end +assert(select("#", object:echo(nil, nil)) == 2) +assert(select("#", echo(object:echo(1, nil))) == 2) +local observed +function object:observe(...) observed = select("#", ...) end +object:observe(echo(nil, nil)) +assert(observed == 2) +local function observe(...) observed = select("#", ...) end +observe(echo(1, nil)) +assert(observed == 2) + +local a, b, c = echo(nil, 2, nil) +assert(a == nil and b == 2 and c == nil) +local empty = {echo(nil, nil)} +assert(next(empty) == nil) -- Internal counts must not leak into source tables. +local values = {"prefix", echo(nil, "last", nil)} +assert(values[1] == "prefix" and values[2] == nil and values[3] == "last") +assert(values[4] == nil and values.n == nil) +print("nil varargs OK") From 47452abf53b159d121c3b3f593eb85f16138f299 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:41:07 +0100 Subject: [PATCH 06/20] fix nested function declaration dependencies in vmify --- .../statements/function_declaration.lua | 2 +- tests/nested-function-declarations.lua | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/nested-function-declarations.lua diff --git a/src/prometheus/compiler/statements/function_declaration.lua b/src/prometheus/compiler/statements/function_declaration.lua index 6f825e9b..cf0f22ff 100644 --- a/src/prometheus/compiler/statements/function_declaration.lua +++ b/src/prometheus/compiler/statements/function_declaration.lua @@ -37,7 +37,7 @@ return function(self, statement, funcDepth) local indexReg = self:compileExpression(Ast.StringExpression(index), funcDepth, 1)[1]; local tblRegOld = tblReg; tblReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tblReg, Ast.IndexExpression(self:register(scope, tblRegOld), self:register(scope, indexReg))), {tblReg}, {tblReg, indexReg}, false); + self:addStatement(self:setRegister(scope, tblReg, Ast.IndexExpression(self:register(scope, tblRegOld), self:register(scope, indexReg))), {tblReg}, {tblRegOld, indexReg}, false); self:freeRegister(tblRegOld, false); self:freeRegister(indexReg, false); end diff --git a/tests/nested-function-declarations.lua b/tests/nested-function-declarations.lua new file mode 100644 index 00000000..e2d5d862 --- /dev/null +++ b/tests/nested-function-declarations.lua @@ -0,0 +1,44 @@ +-- Intermediate table lookups must depend on the table they read. +local t = {a = {b = {}}} +function t.a.f() return 42 end +function t.a.b.g() return 43 end +function t.a.b:method(value) return self.g() + value end +assert(t.a.f() == 42) +assert(t.a.b.g() == 43) +assert(t.a.b:method(7) == 50) + +-- Lookups with observable side effects must finish before the method is stored. +local order = {} +local leaf = setmetatable({}, { + __newindex = function(target, key, value) + order[#order + 1] = "store " .. key + rawset(target, key, value) + end, +}) +local middle = setmetatable({}, { + __index = function(_, key) + order[#order + 1] = "lookup " .. key + return leaf + end, +}) +local proxy = setmetatable({}, { + __index = function(_, key) + order[#order + 1] = "lookup " .. key + return middle + end, +}) +function proxy.a.b.f() return "done" end +assert(#order == 3 and order[1] == "lookup a" and order[2] == "lookup b" and order[3] == "store f") +assert(leaf.f() == "done") + +-- Include a global root and a root captured by a nested closure. +nestedDeclarationRoot = {a = {b = {}}} +function nestedDeclarationRoot.a.b.f() return 44 end +assert(nestedDeclarationRoot.a.b.f() == 44) +nestedDeclarationRoot = nil +local function define() + function t.a.b.captured() return t.a.f() end +end +define() +assert(t.a.b.captured() == 42) +print("nested function declarations OK") From 493f3eab177be379253eb14cf31f8fd100fdfd82 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:45:02 +0100 Subject: [PATCH 07/20] preserve single-value semantics for parenthesized expressions --- src/prometheus/compiler/compile_core.lua | 19 +++++++++ tests/harness/vm-arity.lua | 5 +++ tests/parenthesized-results.lua | 54 ++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 tests/parenthesized-results.lua diff --git a/src/prometheus/compiler/compile_core.lua b/src/prometheus/compiler/compile_core.lua index e53f84e3..73202504 100644 --- a/src/prometheus/compiler/compile_core.lua +++ b/src/prometheus/compiler/compile_core.lua @@ -25,6 +25,25 @@ return function(Compiler) function Compiler:compileExpression(expression, funcDepth, numReturns) local handler = expressionHandlers[expression.kind]; if handler then + local kind = expression.kind; + if expression.isParenthesizedExpression and + (kind == Ast.AstKind.FunctionCallExpression or kind == Ast.AstKind.PassSelfFunctionCallExpression or kind == Ast.AstKind.VarargExpression) and + (numReturns == self.RETURN_ALL or numReturns > 1) then + -- Parentheses collapse multiple values to one, even when it is nil. + local regs = handler(self, expression, funcDepth, 1); + local scope = self.activeBlock.scope; + if numReturns == self.RETURN_ALL then + local packed = self:allocRegister(false); + self:addStatement(self:setRegister(scope, packed, self:pack(scope, {self:register(scope, regs[1])})), {packed}, {regs[1]}, false); + self:freeRegister(regs[1], false); + return {packed}; + end + for i = 2, numReturns do + regs[i] = self:allocRegister(false); + self:addStatement(self:setRegister(scope, regs[i], Ast.NilExpression()), {regs[i]}, {}, false); + end + return regs; + end return handler(self, expression, funcDepth, numReturns); end logger:error(string.format("%s is not an compliable expression!", expression.kind)); diff --git a/tests/harness/vm-arity.lua b/tests/harness/vm-arity.lua index 5ab26f5e..45938134 100644 --- a/tests/harness/vm-arity.lua +++ b/tests/harness/vm-arity.lua @@ -11,6 +11,11 @@ for _, seed in ipairs({1, 2, 3}) do assert(actual.n == args.n, "changed chunk arity") for i = 1, args.n do assert(actual[i] == args[i], "changed chunk value") end end + local collapsed = assert(loadstring(Prometheus.Pipeline:fromConfig(config):apply("return (...)"))) + for _, args in ipairs({pack(), pack(nil), pack(1, nil), pack(nil, 2, nil)}) do + local actual = pack(collapsed(unpack(args, 1, args.n))) + assert(actual.n == 1 and actual[1] == args[1], "parenthesized chunk varargs must return one value") + end local generated = Prometheus.Pipeline:fromConfig(config):apply("return nil, nil") local actual = pack(assert(loadstring(generated))()) assert(actual.n == 2 and actual[1] == nil and actual[2] == nil) diff --git a/tests/parenthesized-results.lua b/tests/parenthesized-results.lua new file mode 100644 index 00000000..0b2c0fc5 --- /dev/null +++ b/tests/parenthesized-results.lua @@ -0,0 +1,54 @@ +-- Parenthesized calls and varargs always produce exactly one value. +local calls = 0 +local function pair() calls = calls + 1; return 1, 2 end +local function noResults() end +local function nilFirst() return nil, 2 end +local function echo(...) return ... end +assert(select("#", (pair())) == 1) +assert(calls == 1) -- Collapsing the results must not evaluate the call again. +local a, b = (pair()) +assert(a == 1 and b == nil and calls == 2) +a, b = (pair()) +assert(a == 1 and b == nil and calls == 3) +assert(select("#", (noResults())) == 1) +assert(select("#", (nilFirst())) == 1) +assert(echo((nilFirst())) == nil) +assert(select("#", echo("prefix", (pair()))) == 2) +assert(select("#", echo(pair())) == 2) +assert(select("#", echo(noResults())) == 0) + +local function first(...) return (...) end +assert(select("#", first(1, 2)) == 1 and first(1, 2) == 1) +assert(select("#", first()) == 1 and first() == nil) +assert(select("#", first(nil, 2)) == 1 and first(nil, 2) == nil) +local function localFirst(...) + local x, y = (...) + assert(y == nil) + x, y = (...) + assert(y == nil) + return x +end +assert(localFirst(1, 2) == 1 and localFirst(nil, 2) == nil) +local function returnPair() return (pair()) end +assert(select("#", returnPair()) == 1) + +local observed +local object = { + pair = function(self) return 3, 4 end, + observe = function(self, ...) observed = select("#", ...) end, +} +assert(select("#", (object:pair())) == 1) +a, b = (object:pair()) +assert(a == 3 and b == nil) +local function observe(...) observed = select("#", ...) end +observe((pair())) +assert(observed == 1) +object:observe((pair())) +assert(observed == 1) + +local values = {(pair())} +assert(values[1] == 1 and values[2] == nil and values.n == nil) +local function tableFirst(...) return {(...)} end +values = tableFirst(1, 2) +assert(values[1] == 1 and values[2] == nil) +print("parenthesized results OK") From a39dc3a2f8a0585b739a2d65c08f1cb84fbfa42e Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:48:32 +0100 Subject: [PATCH 08/20] preserve method lookup ordering in vmify --- .../expressions/pass_self_function_call.lua | 4 +- .../statements/pass_self_function_call.lua | 2 +- tests/method-lookup-order.lua | 42 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/method-lookup-order.lua diff --git a/src/prometheus/compiler/expressions/pass_self_function_call.lua b/src/prometheus/compiler/expressions/pass_self_function_call.lua index c158fc61..44bc3af5 100644 --- a/src/prometheus/compiler/expressions/pass_self_function_call.lua +++ b/src/prometheus/compiler/expressions/pass_self_function_call.lua @@ -41,7 +41,7 @@ return function(self, expression, funcDepth, numReturns) local tmpReg = self:allocRegister(false); self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, false); + self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); if returnAll then self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {Ast.FunctionCallExpression(self:register(scope, tmpReg), args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); @@ -58,7 +58,7 @@ return function(self, expression, funcDepth, numReturns) local tmpReg = retRegs[1] or self:allocRegister(false); self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, false); + self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); self:addStatement(self:setRegister(scope, retRegs[1], Ast.FunctionCallExpression(self:register(scope, tmpReg), args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); end diff --git a/src/prometheus/compiler/statements/pass_self_function_call.lua b/src/prometheus/compiler/statements/pass_self_function_call.lua index 8b31bbe4..e2622978 100644 --- a/src/prometheus/compiler/statements/pass_self_function_call.lua +++ b/src/prometheus/compiler/statements/pass_self_function_call.lua @@ -28,7 +28,7 @@ return function(self, statement, funcDepth) end end self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(statement.passSelfFunctionName)), {tmpReg}, {}, false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {tmpReg, baseReg}, false); + self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {tmpReg, baseReg}, true); self:addStatement(self:setRegister(scope, tmpReg, Ast.FunctionCallExpression(self:register(scope, tmpReg), args)), {tmpReg}, {tmpReg, unpack(regs)}, true); diff --git a/tests/method-lookup-order.lua b/tests/method-lookup-order.lua new file mode 100644 index 00000000..23c080ff --- /dev/null +++ b/tests/method-lookup-order.lua @@ -0,0 +1,42 @@ +-- Method lookups must observe prior declarations and table mutations. +local t = {} +function t:one() return 42 end +assert(t:one() == 42) +function t:two() return 43, nil end +local a, b = t:two() +assert(a == 43 and b == nil) +assert(select("#", t:two()) == 2) +local observed +function t:observe() observed = 44 end +t:observe() +assert(observed == 44) + +t.one = function(self) return 45 end +assert(t:one() == 45) +t.two = function(self) return 46, 47 end +a, b = t:two() +assert(a == 46 and b == 47) +assert(select(2, t:two()) == 47) +t.observe = function(self) observed = 48 end +t:observe() +assert(observed == 48) + +-- A lookup can invoke __index; its side effects must precede the method body. +local order = {} +local proxy = setmetatable({}, { + __index = function(self, key) + order[#order + 1] = "lookup " .. key + return function(receiver) + assert(receiver == self) + order[#order + 1] = "call " .. key + return 49, nil + end + end, +}) +assert(proxy:one() == 49) +a, b = proxy:two() +assert(a == 49 and b == nil) +assert(select("#", proxy:all()) == 2) +proxy:statement() +assert(table.concat(order, ",") == "lookup one,call one,lookup two,call two,lookup all,call all,lookup statement,call statement") +print("method lookup order OK") From 08af2d05361c019528501b7d90b31659bd271977 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:53:04 +0100 Subject: [PATCH 09/20] prevent overflow in hexadecimal number mutation --- src/prometheus/steps/NumbersToExpressions.lua | 6 ++++- tests/harness/numbers-to-expressions.lua | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/prometheus/steps/NumbersToExpressions.lua b/src/prometheus/steps/NumbersToExpressions.lua index ed4ab657..e65c6692 100644 --- a/src/prometheus/steps/NumbersToExpressions.lua +++ b/src/prometheus/steps/NumbersToExpressions.lua @@ -121,7 +121,11 @@ function NumbersToExpressions:CreateNumberExpression(val, depth) if val ~= math.floor(val) or val < 0 then return Ast.NumberExpression(val) end - local hexStr = string.format("0x%X", val) + local ok, hexStr = pcall(string.format, "0x%X", val) + -- Integer formatting can overflow or reject values outside its range. + if not ok or tonumber(hexStr) ~= val then + return Ast.NumberExpression(val) + end local result = "" for i = 1, #hexStr do local c = hexStr:sub(i, i) diff --git a/tests/harness/numbers-to-expressions.lua b/tests/harness/numbers-to-expressions.lua index 756a0f14..4182709e 100644 --- a/tests/harness/numbers-to-expressions.lua +++ b/tests/harness/numbers-to-expressions.lua @@ -31,4 +31,24 @@ for _, representation in ipairs({"hex", "binary", "scientific", "normal"}) do end end end -print("Non-finite number mutation regression checks passed") + +-- Hex formatting has a host-dependent integer range; overflow must preserve +-- the original number. Also check ordinary hex values still work. +local hex = NumbersToExpressions:new({ + InternalThreshold = 0, + NumberRepresentationMutation = true, + AllowedNumberRepresentations = {"hex"}, +}) +for _, value in ipairs({0, 255, 2^32, 2^53, 2^63, 2^64, 1.23e30, 1.7976931348623157e308}) do + local code = unparser:unparseExpression(hex:CreateNumberExpression(value, 1), 0) + assert(assert(loadstring("return " .. code))() == value, "hex mutation changed " .. tostring(value)) +end +assert(type(hex:CreateNumberExpression(255, 1).value) == "string", "ordinary hex mutation was disabled") + +local config = {LuaVersion = "Lua51", Seed = 1, Steps = {{Name = "NumbersToExpressions", Settings = { + NumberRepresentationMutation = true, + AllowedNumberRepresentations = {"hex"}, +}}}} +local generated = Prometheus.Pipeline:fromConfig(config):apply('local n = 1.23e30; assert(n == tonumber("1.23e30"))') +assert(loadstring(generated))() +print("Number mutation regression checks passed") From feee70b64d103a63dd93b3e7c525e74f11f60129 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:11:08 +0100 Subject: [PATCH 10/20] release dead vm references after their final use --- src/prometheus/compiler/block.lua | 3 +- src/prometheus/compiler/emit.lua | 14 +++++ src/prometheus/compiler/register.lua | 18 +++--- .../compiler/statements/assignment.lua | 2 +- .../compiler/statements/for_statement.lua | 3 +- src/prometheus/compiler/statements/return.lua | 3 +- tests/gc-temporaries.lua | 61 +++++++++++++++++++ tests/harness/vm-arity.lua | 7 +++ 8 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 tests/gc-temporaries.lua diff --git a/src/prometheus/compiler/block.lua b/src/prometheus/compiler/block.lua index c5544ee8..a4cddcc6 100644 --- a/src/prometheus/compiler/block.lua +++ b/src/prometheus/compiler/block.lua @@ -32,13 +32,14 @@ return function(Compiler) self.activeBlock = block; end - function Compiler:addStatement(statement, writes, reads, usesUpvals) + function Compiler:addStatement(statement, writes, reads, usesUpvals, isCleanup) if(self.activeBlock.advanceToNextBlock) then table.insert(self.activeBlock.statements, { statement = statement, writes = lookupify(writes), reads = lookupify(reads), usesUpvals = usesUpvals or false, + isCleanup = isCleanup or false, }); end end diff --git a/src/prometheus/compiler/emit.lua b/src/prometheus/compiler/emit.lua index 53db382c..1bc2045e 100644 --- a/src/prometheus/compiler/emit.lua +++ b/src/prometheus/compiler/emit.lua @@ -32,6 +32,9 @@ return function(Compiler) if type(statA) ~= "table" or type(statB) ~= "table" then return false; end + if statA.isCleanup ~= statB.isCleanup then + return false; + end if statA.usesUpvals or statB.usesUpvals then return false; @@ -49,6 +52,11 @@ return function(Compiler) if type(a.lhs) ~= "table" or type(a.rhs) ~= "table" or type(b.lhs) ~= "table" or type(b.rhs) ~= "table" then return false; end + -- Spilled temporaries can create more cleanup targets than Lua accepts + -- in one assignment. Keep cleanup batches within the register budget. + if statA.isCleanup and #a.lhs + #b.lhs > MAX_REGS then + return false; + end if #a.lhs ~= #a.rhs or #b.lhs ~= #b.rhs then return false; @@ -112,6 +120,7 @@ return function(Compiler) writes = unionLookupTables(statA.writes, statB.writes), reads = unionLookupTables(statA.reads, statB.reads), usesUpvals = statA.usesUpvals or statB.usesUpvals, + isCleanup = statA.isCleanup, }; end @@ -150,6 +159,11 @@ return function(Compiler) for shift = 1, i - 1 do local stat2 = blockstats[i - shift]; + -- Collection may happen during any operation, including allocation. + if stat.isCleanup or stat2.isCleanup then + break; + end + if stat2.usesUpvals and usesUpvals then break; end diff --git a/src/prometheus/compiler/register.lua b/src/prometheus/compiler/register.lua index 82792f69..2df8a47a 100644 --- a/src/prometheus/compiler/register.lua +++ b/src/prometheus/compiler/register.lua @@ -13,6 +13,12 @@ local MAX_REGS = constants.MAX_REGS; return function(Compiler) function Compiler:freeRegister(id, force) if force or not (self.registers[id] == self.VAR_REGISTER) then + -- Releasing an allocator slot must also release its runtime reference. + -- Control/return slots are reserved and never contain temporaries. + if id ~= self.POS_REGISTER and id ~= self.RETURN_REGISTER then + local scope = self.activeBlock.scope; + self:addStatement(self:setRegister(scope, id, Ast.NilExpression()), {id}, {}, false, true); + end self.usedRegisters = self.usedRegisters - 1; self.registers[id] = false end @@ -25,17 +31,7 @@ return function(Compiler) function Compiler:allocRegister(isVar) self.usedRegisters = self.usedRegisters + 1; - if not isVar then - if not self.registers[self.POS_REGISTER] then - self.registers[self.POS_REGISTER] = true; - return self.POS_REGISTER; - end - - if not self.registers[self.RETURN_REGISTER] then - self.registers[self.RETURN_REGISTER] = true; - return self.RETURN_REGISTER; - end - end + -- Dispatch position and final returns outlive ordinary temporary slots. local id = 0; if self.usedRegisters < MAX_REGS * constants.MAX_REGS_MUL then diff --git a/src/prometheus/compiler/statements/assignment.lua b/src/prometheus/compiler/statements/assignment.lua index 3831ddb6..a0f56b05 100644 --- a/src/prometheus/compiler/statements/assignment.lua +++ b/src/prometheus/compiler/statements/assignment.lua @@ -78,11 +78,11 @@ return function(self, statement, funcDepth) }, { self:register(scope, exprregs[i]) }), {}, {exprregs[i], baseReg, indexReg}, true); - self:freeRegister(exprregs[i], false); self:freeRegister(baseReg, false); self:freeRegister(indexReg, false); else error(string.format("Invalid Assignment lhs: %s", statement.lhs)); end + self:freeRegister(exprregs[i], false); end end; diff --git a/src/prometheus/compiler/statements/for_statement.lua b/src/prometheus/compiler/statements/for_statement.lua index afe43081..b086a07d 100644 --- a/src/prometheus/compiler/statements/for_statement.lua +++ b/src/prometheus/compiler/statements/for_statement.lua @@ -99,6 +99,8 @@ return function(self, statement, funcDepth) self:compileBlock(statement.body, funcDepth); self:addStatement(self:setRegister(scope, self.POS_REGISTER, Ast.NumberExpression(checkBlock.id)), {self.POS_REGISTER}, {}, false); + -- Loop state is live on the back edge; release it only after exiting. + self:setActiveBlock(finalBlock); self.registers[self.POS_REGISTER] = self.VAR_REGISTER; self:freeRegister(finalReg); self:freeRegister(incrementIsNegReg); @@ -106,6 +108,5 @@ return function(self, statement, funcDepth) self:freeRegister(currentReg, true); self.registers[self.POS_REGISTER] = posState; - self:setActiveBlock(finalBlock); end; diff --git a/src/prometheus/compiler/statements/return.lua b/src/prometheus/compiler/statements/return.lua index 70c540d2..392ff280 100644 --- a/src/prometheus/compiler/statements/return.lua +++ b/src/prometheus/compiler/statements/return.lua @@ -26,11 +26,10 @@ return function(self, statement, funcDepth) end end + self:addStatement(self:setReturn(scope, self:pack(scope, entries)), {self.RETURN_REGISTER}, regs, false); for _, reg in ipairs(regs) do self:freeRegister(reg, false); end - - self:addStatement(self:setReturn(scope, self:pack(scope, entries)), {self.RETURN_REGISTER}, regs, false); self:addStatement(self:setPos(self.activeBlock.scope, nil), {self.POS_REGISTER}, {}, false); self.activeBlock.advanceToNextBlock = false; end; diff --git a/tests/gc-temporaries.lua b/tests/gc-temporaries.lua new file mode 100644 index 00000000..b6b6e659 --- /dev/null +++ b/tests/gc-temporaries.lua @@ -0,0 +1,61 @@ +-- @runtime lua5.1 +-- Dead VM slots must not keep weakly referenced objects alive. +local weak = setmetatable({}, {__mode = "v"}) +local object = {} +weak[1] = object +object = nil +collectgarbage() +assert(weak[1] == nil) + +local function checkCollected() + collectgarbage() + assert(weak[1] == nil) + return 7 +end +local proxy = setmetatable({}, { + __index = checkCollected, + __newindex = checkCollected, + __add = checkCollected, + __unm = checkCollected, + __concat = checkCollected, +}) +do local discarded = {}; weak[1] = discarded; discarded = nil; assert(proxy.missing == 7) end +do local discarded = {}; weak[1] = discarded; discarded = nil; proxy.missing = true end +do local discarded = {}; weak[1] = discarded; discarded = nil; assert(proxy + proxy == 7) end +do local discarded = {}; weak[1] = discarded; discarded = nil; assert(-proxy == 7) end +do local discarded = {}; weak[1] = discarded; discarded = nil; assert(proxy .. proxy == 7) end + +-- Trigger automatic collection through allocation inside a metamethod. The +-- caller's dead temporaries stay on its stack throughout the metamethod. +local automatic = setmetatable({}, {__index = function() + -- Do not read the weak value during allocation: even the reference VM can + -- keep that read's temporary alive until the next loop iteration. + for i = 1, 100000 do local garbage = {i} end + assert(weak[1] == nil) + return true +end}) +local pause = collectgarbage("setpause", 100) +local multiplier = collectgarbage("setstepmul", 1000) +collectgarbage("restart") +do local discarded = {}; weak[1] = discarded; discarded = nil; assert(automatic.missing) end +collectgarbage("setpause", pause) +collectgarbage("setstepmul", multiplier) + +-- Live arguments, returned values, and loop state must survive cleanup. +local function returnLive(value) + collectgarbage() + assert(weak[1] == value) + return value, nil +end +local live = {} +weak[1] = live +local result, other = returnLive(live) +assert(result == live and other == nil) +local total = 0 +for i = 1, 5 do collectgarbage(); total = total + i end +assert(total == 15) +for _, value in ipairs({live}) do collectgarbage(); assert(value == live) end +local getLive = function() return live end +collectgarbage() +assert(getLive() == live) +print("GC temporaries OK") diff --git a/tests/harness/vm-arity.lua b/tests/harness/vm-arity.lua index 45938134..8bab9e02 100644 --- a/tests/harness/vm-arity.lua +++ b/tests/harness/vm-arity.lua @@ -19,5 +19,12 @@ for _, seed in ipairs({1, 2, 3}) do local generated = Prometheus.Pipeline:fromConfig(config):apply("return nil, nil") local actual = pack(assert(loadstring(generated))()) assert(actual.n == 2 and actual[1] == nil and actual[2] == nil) + + -- Cleanup must preserve values and remain valid when registers spill. + local entries = {} + for i = 1, 250 do entries[i] = "value(" .. i .. ")" end + local source = "local function value(i) return i end; local values = {" .. table.concat(entries, ",") .. "}; assert(#values == 250 and values[250] == 250); return values, nil" + local actual = pack(assert(loadstring(Prometheus.Pipeline:fromConfig(config):apply(source)))()) + assert(actual.n == 2 and actual[1][1] == 1 and actual[1][250] == 250 and actual[2] == nil) end print("VM chunk arity regression checks passed") From 78cb5c1d4144556d5746eebb9a38f510ea8e3521 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:23:12 +0100 Subject: [PATCH 11/20] limit merged vm assignments to the temporary register budget --- src/prometheus/compiler/emit.lua | 18 +++++++++++++++--- tests/harness/vm-arity.lua | 6 ++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/prometheus/compiler/emit.lua b/src/prometheus/compiler/emit.lua index 1bc2045e..58b53c9d 100644 --- a/src/prometheus/compiler/emit.lua +++ b/src/prometheus/compiler/emit.lua @@ -28,6 +28,18 @@ return function(Compiler) return out; end + local function assignmentSlotCost(statement) + local slots = #statement.rhs; + for _, target in ipairs(statement.lhs) do + -- Indexed targets may keep both their table and key in temporary + -- slots while Lua evaluates the right-hand values. + if target.kind == AstKind.AssignmentIndexing then + slots = slots + 2; + end + end + return slots; + end + local function canMergeParallelAssignmentStatements(statA, statB) if type(statA) ~= "table" or type(statB) ~= "table" then return false; @@ -52,9 +64,9 @@ return function(Compiler) if type(a.lhs) ~= "table" or type(a.rhs) ~= "table" or type(b.lhs) ~= "table" or type(b.rhs) ~= "table" then return false; end - -- Spilled temporaries can create more cleanup targets than Lua accepts - -- in one assignment. Keep cleanup batches within the register budget. - if statA.isCleanup and #a.lhs + #b.lhs > MAX_REGS then + -- Leave room for the container's locals by keeping merged assignments' + -- temporary slot cost within the register budget, including spill targets. + if assignmentSlotCost(a) + assignmentSlotCost(b) > MAX_REGS then return false; end diff --git a/tests/harness/vm-arity.lua b/tests/harness/vm-arity.lua index 8bab9e02..7c0286d2 100644 --- a/tests/harness/vm-arity.lua +++ b/tests/harness/vm-arity.lua @@ -26,5 +26,11 @@ for _, seed in ipairs({1, 2, 3}) do local source = "local function value(i) return i end; local values = {" .. table.concat(entries, ",") .. "}; assert(#values == 250 and values[250] == 250); return values, nil" local actual = pack(assert(loadstring(Prometheus.Pipeline:fromConfig(config):apply(source)))()) assert(actual.n == 2 and actual[1][1] == 1 and actual[1][250] == 250 and actual[2] == nil) + + -- Literal initializers may merge, but must stay within Lua's assignment limit. + for i = 1, 250 do entries[i] = tostring(i) end + local source = "local values = {" .. table.concat(entries, ",") .. "}; for i = 1, 250 do assert(values[i] == i) end; return values, nil" + local actual = pack(assert(loadstring(Prometheus.Pipeline:fromConfig(config):apply(source)))()) + assert(actual.n == 2 and #actual[1] == 250 and actual[2] == nil) end print("VM chunk arity regression checks passed") From 9913a6b9670a2aa7f2c7316a602ee1da1704096b Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:13:06 +0100 Subject: [PATCH 12/20] drop legacy arg compatibility from upstream tests --- README.md | 3 ++ doc/SUMMARY.md | 1 + doc/reference/limitations.md | 20 ++++++++ tests/upstream/lua51/README.md | 6 +++ tests/upstream/lua51/exclusions.json | 25 ++++++++++ tests/upstream/lua51/patches/portable.patch | 51 +++++++++++++++++++++ 6 files changed, 106 insertions(+) create mode 100644 doc/reference/limitations.md diff --git a/README.md b/README.md index f0b2ea59..a3885b55 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ Currently, Prometheus supports: - **Lua 5.1** - **LuaU** *(basic support is available, but still not fully finished)* +Lua 5.1's legacy implicit `arg` table in vararg functions is unsupported. +Use `...` and `select('#', ...)` instead; see [Limitations](doc/reference/limitations.md). + --- diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md index e5bcbf11..3396f43f 100644 --- a/doc/SUMMARY.md +++ b/doc/SUMMARY.md @@ -17,6 +17,7 @@ ## Reference +* [Limitations](reference/limitations.md) * [CLI Options Reference](reference/cli-options.md) * [Config Object Reference](reference/config-object.md) * [Step Pipeline Overview](reference/steps/overview.md) diff --git a/doc/reference/limitations.md b/doc/reference/limitations.md new file mode 100644 index 00000000..74c764bd --- /dev/null +++ b/doc/reference/limitations.md @@ -0,0 +1,20 @@ +# Limitations + +## Legacy implicit `arg` table + +Prometheus does not support Lua 5.1's legacy compatibility behavior that creates +an implicit local `arg` table in vararg functions, or an implicit nil `arg` +when the function uses `...` in its body. Minification may preserve this +behavior on a compatible host runtime, but obfuscation does not guarantee it. + +Use explicit varargs instead: + +```lua +local function pack(...) + return {n = select('#', ...), ...} +end +``` + +The explicit `n` preserves the argument count even when arguments contain nils. +Ordinary explicit locals named `arg` and the CLI's global argument table are +unaffected by this limitation. diff --git a/tests/upstream/lua51/README.md b/tests/upstream/lua51/README.md index 1fd8c779..4ab86e65 100644 --- a/tests/upstream/lua51/README.md +++ b/tests/upstream/lua51/README.md @@ -35,6 +35,12 @@ manifest. Debug introspection, exact tail-call stack capacity, original source names/lines, collector step counts and finalization order, host parser limits, and native C libraries fall outside this suite's portable scope. +Lua 5.1's legacy implicit `arg` compatibility is unsupported; see +[Limitations](../../../doc/reference/limitations.md). In `vararg.lua`, legacy +table uses are rewritten with `{n = select('#', ...), ...}` and assertions +about the implicit nil `arg` are removed. Argument values, nils, counts, +forwarding, and method-call checks remain covered using explicit varargs. + The native `T` library is absent. Upstream guards therefore also exclude: - `nextvar.lua`: native table sizes/layout and `T.int2fb` checks; diff --git a/tests/upstream/lua51/exclusions.json b/tests/upstream/lua51/exclusions.json index a3297ce2..894801b9 100644 --- a/tests/upstream/lua51/exclusions.json +++ b/tests/upstream/lua51/exclusions.json @@ -1,4 +1,29 @@ [ + { + "file": "vararg.lua", + "lines": "3-9", + "reason": "Unsupported legacy implicit arg compatibility; replace implicit table with explicit varargs while preserving value and count assertions" + }, + { + "file": "vararg.lua", + "lines": "13-13", + "reason": "Unsupported legacy implicit nil arg compatibility in functions using explicit varargs" + }, + { + "file": "vararg.lua", + "lines": "20-20", + "reason": "Unsupported legacy implicit arg compatibility; pack explicit varargs with an exact count" + }, + { + "file": "vararg.lua", + "lines": "45-45", + "reason": "Unsupported legacy implicit arg compatibility; preserve method-call checks with explicitly packed varargs" + }, + { + "file": "vararg.lua", + "lines": "80-80", + "reason": "Unsupported legacy implicit nil arg compatibility in functions using explicit varargs" + }, { "file": "attrib.lua", "lines": "10-10", diff --git a/tests/upstream/lua51/patches/portable.patch b/tests/upstream/lua51/patches/portable.patch index 3e04a69c..29ebb78c 100644 --- a/tests/upstream/lua51/patches/portable.patch +++ b/tests/upstream/lua51/patches/portable.patch @@ -448,3 +448,54 @@ assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n") end +--- a/vararg.lua ++++ b/vararg.lua +@@ -1,8 +1,9 @@ + print('testing vararg') + +-_G.arg = nil ++-- Prometheus: legacy implicit arg compatibility is unsupported. + + function f(a, ...) ++ local arg = {n = select('#', ...), ...} + assert(type(arg) == 'table') + assert(type(arg.n) == 'number') + for i=1,arg.n do assert(a[i]==arg[i]) end +@@ -10,14 +11,14 @@ + end + + function c12 (...) +- assert(arg == nil) ++ -- Prometheus: implicit nil arg compatibility excluded. + local x = {...}; x.n = table.getn(x) + local res = (x.n==2 and x[1] == 1 and x[2] == 2) + if res then res = 55 end + return res, 2 + end + +-function vararg (...) return arg end ++function vararg (...) return {n = select('#', ...), ...} end + + local call = function (f, args) return f(unpack(args, 1, args.n)) end + +@@ -42,7 +43,10 @@ + assert(a == nil) + + local t = {1, 10} +-function t:f (...) return self[arg[1]]+arg.n end ++function t:f (...) ++ local arg = {n = select('#', ...), ...} ++ return self[arg[1]]+arg.n ++end + assert(t:f(1,4) == 3 and t:f(2) == 11) + print('+') + +@@ -77,7 +81,7 @@ + + function f (n, a, ...) + local b +- assert(arg == nil) ++ -- Prometheus: implicit nil arg compatibility excluded. + if n == 0 then + local b, c, d = ... + return a, b, c, d, oneless(oneless(oneless(...))) From db8801d973d0721ca14fca50b1cc33baf2258432 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:18:11 +0100 Subject: [PATCH 13/20] exclude running environment changes from upstream tests --- README.md | 2 + doc/reference/limitations.md | 23 ++++ tests/upstream/lua51/README.md | 8 ++ tests/upstream/lua51/exclusions.json | 30 ++++ tests/upstream/lua51/patches/portable.patch | 145 ++++++++++++++++++-- 5 files changed, 198 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a3885b55..ba174753 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Currently, Prometheus supports: Lua 5.1's legacy implicit `arg` table in vararg functions is unsupported. Use `...` and `select('#', ...)` instead; see [Limitations](doc/reference/limitations.md). +Replacing a function's environment while it is running is also unsupported; +set the environment before calling the function. --- diff --git a/doc/reference/limitations.md b/doc/reference/limitations.md index 74c764bd..a2ad1a76 100644 --- a/doc/reference/limitations.md +++ b/doc/reference/limitations.md @@ -1,5 +1,28 @@ # Limitations +## Replacing a running function's environment + +Prometheus does not support replacing a function's environment while that +function is running. This includes `setfenv(1, env)`, changing an active +caller with `setfenv(2, env)`, and `setfenv(f, env)` when `f` is currently +executing. Subsequent global reads and writes may still use the environment +from the start of the call. + +Lua 5.1's `module(...)` also replaces the calling function's environment, +so calling it from transformed code is subject to the same limitation. +Use explicit module tables instead. + +Setting a function's environment before calling it is supported: + +```lua +setfenv(f, env) +f() +``` + +This limitation concerns replacing a running function's environment, not +mutating values in an environment table or changing the thread's default +environment with `setfenv(0, env)`. + ## Legacy implicit `arg` table Prometheus does not support Lua 5.1's legacy compatibility behavior that creates diff --git a/tests/upstream/lua51/README.md b/tests/upstream/lua51/README.md index 4ab86e65..fabefa42 100644 --- a/tests/upstream/lua51/README.md +++ b/tests/upstream/lua51/README.md @@ -41,6 +41,14 @@ table uses are rewritten with `{n = select('#', ...), ...}` and assertions about the implicit nil `arg` are removed. Argument values, nils, counts, forwarding, and method-call checks remain covered using explicit varargs. +Replacing a running function's environment is also unsupported, including +`module(...)` calls from transformed code. The affected blocks in `events.lua`, +`attrib.lua`, `locals.lua`, and `nextvar.lua` are adapted or excluded in +`patches/portable.patch` and recorded in `exclusions.json`. Explicit table +metatable and preload checks remain covered. Tests that set a function's +environment before calling it, and thread-default environment checks, stay +enabled; failures of those supported behaviors remain failures. + The native `T` library is absent. Upstream guards therefore also exclude: - `nextvar.lua`: native table sizes/layout and `T.int2fb` checks; diff --git a/tests/upstream/lua51/exclusions.json b/tests/upstream/lua51/exclusions.json index 894801b9..73ae6d7c 100644 --- a/tests/upstream/lua51/exclusions.json +++ b/tests/upstream/lua51/exclusions.json @@ -137,5 +137,35 @@ "lines": "109-123", "reason": "Native querytab checks; querytab is absent", "condition": "native runtime helper unavailable" + }, + { + "file": "events.lua", + "lines": "5-14", + "reason": "Unsupported replacement of a running function environment; retain fallback, false-value, nil-value, and write-isolation checks using an explicit table" + }, + { + "file": "events.lua", + "lines": "298-299", + "reason": "Unsupported replacement of a running function environment; retain subsequent proxy checks" + }, + { + "file": "attrib.lua", + "lines": "143-170", + "reason": "Unsupported module calls that replace the running function environment" + }, + { + "file": "attrib.lua", + "lines": "217-220", + "reason": "Unsupported module call in an executing preload loader; retain preload and require checks using an explicit module table" + }, + { + "file": "locals.lua", + "lines": "66-69", + "reason": "Unsupported replacement of an active caller environment; retain pre-call function environment tests" + }, + { + "file": "nextvar.lua", + "lines": "234-246", + "reason": "Unsupported replacement of the executing function environment" } ] diff --git a/tests/upstream/lua51/patches/portable.patch b/tests/upstream/lua51/patches/portable.patch index 29ebb78c..2b2681e1 100644 --- a/tests/upstream/lua51/patches/portable.patch +++ b/tests/upstream/lua51/patches/portable.patch @@ -7,12 +7,46 @@ -assert(require"debug" == debug) +-- Prometheus: debug-library assertions excluded. assert(require"coroutine" == coroutine) - + assert(type(package.path) == "string") -@@ -173,41 +173,7 @@ +@@ -140,83 +140,26 @@ + end + + +-local function import(...) +- local f = {...} +- return function (m) +- for i=1, #f do m[f[i]] = _G[f[i]] end +- end +-end +- +-local assert, module, package = assert, module, package +-X = nil; x = 0; assert(_G.x == 0) -- `x' must be a global variable +-module"X"; x = 1; assert(_M.x == 1) +-module"X.a.b.c"; x = 2; assert(_M.x == 2) +-module("X.a.b", package.seeall); x = 3 +-assert(X._NAME == "X" and X.a.b.c._NAME == "X.a.b.c" and X.a.b._NAME == "X.a.b") +-assert(X._M == X and X.a.b.c._M == X.a.b.c and X.a.b._M == X.a.b) +-assert(X.x == 1 and X.a.b.c.x == 2 and X.a.b.x == 3) +-assert(X._PACKAGE == "" and X.a.b.c._PACKAGE == "X.a.b." and +- X.a.b._PACKAGE == "X.a.") +-assert(_PACKAGE.."c" == "X.a.c") +-assert(X.a._NAME == nil and X.a._M == nil) +-module("X.a", import("X")) ; x = 4 +-assert(X.a._NAME == "X.a" and X.a.x == 4 and X.a._M == X.a) +-module("X.a.b", package.seeall); assert(x == 3); x = 5 +-assert(_NAME == "X.a.b" and X.a.b.x == 5) +- +-assert(X._G == nil and X.a._G == nil and X.a.b._G == _G and X.a.b.c._G == nil) +- +-setfenv(1, _G) +-assert(x == 0) ++-- Prometheus: module calls that replace the running environment excluded. + + assert(not pcall(module, "x")) assert(not pcall(module, "math.sin")) - - + + --- testing C libraries - - @@ -49,9 +83,25 @@ -assert(not f and type(err) == "string" and (when == "open" or when == "absent")) - +-- Prometheus exclusion: Native C-library integration - + -- testing preload - + + do + local p = package + package = {} +- p.preload.pl = function (...) +- module(...) +- function xuxu (x) return x+20 end ++ -- Prometheus: retain preload checks using an explicit module table. ++ p.preload.pl = function (name) ++ local m = {} ++ p.loaded[name] = m ++ _G[name] = m ++ function m.xuxu (x) return x+20 end ++ return m + end + + require"pl" --- a/big.lua +++ b/big.lua @@ -1,16 +1,6 @@ @@ -249,10 +299,48 @@ print('OK') --- a/events.lua +++ b/events.lua +@@ -2,16 +2,17 @@ + + X = 20; B = 30 + +-setfenv(1, setmetatable({}, {__index=_G})) +- +-collectgarbage() +- +-X = X+10 +-assert(X == 30 and _G.X == 20) +-B = false +-assert(B == false) +-B = nil +-assert(B == 30) ++-- Prometheus: test table fallback without replacing the running environment. ++do ++ local env = setmetatable({}, {__index=_G}) ++ collectgarbage() ++ env.X = env.X+10 ++ assert(env.X == 30 and _G.X == 20) ++ env.B = false ++ assert(env.B == false) ++ env.B = nil ++ assert(env.B == 30) ++end + + assert(getmetatable{} == nil) + assert(getmetatable(4) == nil) +@@ -295,8 +296,7 @@ + + print'+' + +-local _g = _G +-setfenv(1, setmetatable({}, {__index=function (_,k) return _g[k] end})) ++-- Prometheus: running-function environment replacement excluded. + + -- testing proxies + assert(getmetatable(newproxy()) == nil) @@ -325,35 +325,7 @@ - + print '+' - + --- testing metatables for basic types -mt = {} -debug.setmetatable(10, mt) @@ -283,9 +371,9 @@ -debug.setmetatable(nil, {}) - +-- Prometheus exclusion: Debug mutation of primitive-type metatables - + print 'OK' - + --- a/gc.lua +++ b/gc.lua @@ -85,48 +85,7 @@ @@ -448,6 +536,43 @@ assert(_G.x == "hi\n" and _G.y == "\nhello\r\n\n") end +--- a/locals.lua ++++ b/locals.lua +@@ -63,10 +63,7 @@ + assert(b.A == 9) + a,b = f[8](0) + assert(b.A == 11) -- `real' global +- local g +- local function f () assert(setfenv(2, {a='10'}) == g) end +- g = function () f(); _G.assert(_G.getfenv(1).a == '10') end +- g(); assert(getfenv(g).a == '10') ++ -- Prometheus: replacing an active caller environment excluded. + end + + -- test for global table of loaded chunks +--- a/nextvar.lua ++++ b/nextvar.lua +@@ -231,19 +231,7 @@ + end + end + +-local function foo () +- local getfenv, setfenv, assert, next = +- getfenv, setfenv, assert, next +- local n = {gl1=3} +- setfenv(foo, n) +- assert(getfenv(foo) == getfenv(1)) +- assert(getfenv(foo) == n) +- assert(print == nil and gl1 == 3) +- gl1 = nil +- gl = 1 +- assert(n.gl == 1 and next(n, 'gl') == nil) +-end +-foo() ++-- Prometheus: replacing the executing function environment excluded. + + print'+' + --- a/vararg.lua +++ b/vararg.lua @@ -1,8 +1,9 @@ From 9c4c9f833a858a6c91948e8bacbbc171f4fe6585 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:21:18 +0100 Subject: [PATCH 14/20] fix numeric for loop coercion in vmify --- src/prometheus/compiler/compiler.lua | 18 +++- .../compiler/statements/for_statement.lua | 25 +++++- tests/numeric-for-coercion.lua | 90 +++++++++++++++++++ 3 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 tests/numeric-for-coercion.lua diff --git a/src/prometheus/compiler/compiler.lua b/src/prometheus/compiler/compiler.lua index 3ba8f1e1..4055594c 100644 --- a/src/prometheus/compiler/compiler.lua +++ b/src/prometheus/compiler/compiler.lua @@ -108,6 +108,8 @@ function Compiler:compile(ast) local _, setmetatableVar = newGlobalScope:resolve("setmetatable"); local _, getmetatableVar = newGlobalScope:resolve("getmetatable"); local _, selectVar = newGlobalScope:resolve("select"); + local _, tonumberVar = newGlobalScope:resolve("tonumber"); + local _, errorVar = newGlobalScope:resolve("error"); psc:addReferenceToHigherScope(newGlobalScope, getfenvVar, 2); psc:addReferenceToHigherScope(newGlobalScope, tableVar); @@ -117,6 +119,8 @@ function Compiler:compile(ast) psc:addReferenceToHigherScope(newGlobalScope, setmetatableVar); psc:addReferenceToHigherScope(newGlobalScope, getmetatableVar); psc:addReferenceToHigherScope(newGlobalScope, selectVar); + psc:addReferenceToHigherScope(newGlobalScope, tonumberVar); + psc:addReferenceToHigherScope(newGlobalScope, errorVar); self.scope = Scope:new(psc); self.envVar = self.scope:addVariable(); @@ -128,6 +132,8 @@ function Compiler:compile(ast) self.setmetatableVar = self.scope:addVariable(); self.getmetatableVar = self.scope:addVariable(); self.selectVar = self.scope:addVariable(); + self.tonumberVar = self.scope:addVariable(); + self.errorVar = self.scope:addVariable(); local argVar = self.scope:addVariable(); @@ -306,7 +312,7 @@ function Compiler:compile(ast) -- NEW: Position Shuffler - local ids = util.shuffle({1, 2, 3, 4, 5, 6, 7}); + local ids = util.shuffle({1, 2, 3, 4, 5, 6, 7, 8, 9}); local items = { Ast.VariableExpression(self.scope, self.envVar), @@ -316,6 +322,8 @@ function Compiler:compile(ast) Ast.VariableExpression(self.scope, self.getmetatableVar), Ast.VariableExpression(self.scope, self.selectVar), Ast.VariableExpression(self.scope, argVar), + Ast.VariableExpression(self.scope, self.tonumberVar), + Ast.VariableExpression(self.scope, self.errorVar), } local astItems = { @@ -329,12 +337,14 @@ function Compiler:compile(ast) Ast.KeyedTableEntry(Ast.StringExpression("n"), Ast.FunctionCallExpression( Ast.VariableExpression(newGlobalScope, selectVar), {Ast.StringExpression("#"), Ast.VarargExpression()})), Ast.TableEntry(Ast.VarargExpression()); - }) + }); + Ast.VariableExpression(newGlobalScope, tonumberVar); + Ast.VariableExpression(newGlobalScope, errorVar); } local functionNode = Ast.FunctionLiteralExpression({ items[ids[1]], items[ids[2]], items[ids[3]], items[ids[4]], - items[ids[5]], items[ids[6]], items[ids[7]], + items[ids[5]], items[ids[6]], items[ids[7]], items[ids[8]], items[ids[9]], unpack(util.shuffle(tbl)) }, Ast.Block({ Ast.AssignmentStatement(assignmentStatLhs, assignmentStatRhs); @@ -349,7 +359,7 @@ function Compiler:compile(ast) return Ast.TopNode(Ast.Block({ Ast.ReturnStatement{Ast.FunctionCallExpression(functionNode, { astItems[ids[1]], astItems[ids[2]], astItems[ids[3]], astItems[ids[4]], - astItems[ids[5]], astItems[ids[6]], astItems[ids[7]], + astItems[ids[5]], astItems[ids[6]], astItems[ids[7]], astItems[ids[8]], astItems[ids[9]], })}; }, psc), newGlobalScope); end diff --git a/src/prometheus/compiler/statements/for_statement.lua b/src/prometheus/compiler/statements/for_statement.lua index b086a07d..597fd9d2 100644 --- a/src/prometheus/compiler/statements/for_statement.lua +++ b/src/prometheus/compiler/statements/for_statement.lua @@ -19,7 +19,10 @@ return function(self, statement, funcDepth) local posState = self.registers[self.POS_REGISTER]; self.registers[self.POS_REGISTER] = self.VAR_REGISTER; - local initialReg = self:compileExpression(statement.initialValue, funcDepth, 1)[1]; + local initialExprReg = self:compileExpression(statement.initialValue, funcDepth, 1)[1]; + local initialReg = self:allocRegister(false); + self:addStatement(self:copyRegisters(scope, {initialReg}, {initialExprReg}), {initialReg}, {initialExprReg}, false); + self:freeRegister(initialExprReg); local finalExprReg = self:compileExpression(statement.finalValue, funcDepth, 1)[1]; local finalReg = self:allocRegister(false); @@ -31,6 +34,26 @@ return function(self, statement, funcDepth) self:addStatement(self:copyRegisters(scope, {incrementReg}, {incrementExprReg}), {incrementReg}, {incrementExprReg}, false); self:freeRegister(incrementExprReg); + -- Lua evaluates all three expressions before coercing and validating them. + -- Use captured builtins so replacing globals cannot change loop semantics. + scope:addReferenceToHigherScope(self.scope, self.tonumberVar, 3); + scope:addReferenceToHigherScope(self.scope, self.errorVar, 3); + for _, input in ipairs({ + {initialReg, "initial value"}, + {finalReg, "limit"}, + {incrementReg, "step"}, + }) do + local reg, name = input[1], input[2]; + self:addStatement(self:setRegister(scope, reg, Ast.OrExpression( + Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.tonumberVar), { + self:register(scope, reg), + }), + Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.errorVar), { + Ast.StringExpression("'for' " .. name .. " must be a number"), + }) + )), {reg}, {reg}, true); + end + local tmpReg = self:allocRegister(false); self:addStatement(self:setRegister(scope, tmpReg, Ast.NumberExpression(0)), {tmpReg}, {}, false); local incrementIsNegReg = self:allocRegister(false); diff --git a/tests/numeric-for-coercion.lua b/tests/numeric-for-coercion.lua new file mode 100644 index 00000000..697a1461 --- /dev/null +++ b/tests/numeric-for-coercion.lua @@ -0,0 +1,90 @@ +local function values(first, last, step) + local result = {} + for i = first, last, step do + assert(type(i) == "number") + result[#result + 1] = i + end + return result +end + +local function check(first, last, step, expected) + local actual = values(first, last, step) + assert(#actual == #expected) + for i = 1, #expected do assert(actual[i] == expected[i]) end +end + +check("1", 3, 1, {1, 2, 3}) +check(1, "3", 1, {1, 2, 3}) +check(3, 1, "-1", {3, 2, 1}) +check("10", "1", "-2", {10, 8, 6, 4, 2}) +check(" 1 ", "3e0", "0x1", {1, 2, 3}) +check("3", "1", "1", {}) +check("1", "3", "-1", {}) + +-- Coercion must not overwrite the expressions' source variables. +local first, last, step = "1", "3", "1" +local count = 0 +for i = first, last, step do count = count + 1 end +assert(count == 3 and first == "1" and last == "3" and step == "1") + +-- Every bound is evaluated once, in order, before validation can fail. +local function invalid(first, last, step) + local order = {} + local function bound(position, value) + order[#order + 1] = position + return value + end + local entered = false + local ok = pcall(function() + for i = bound(1, first), bound(2, last), bound(3, step) do + entered = true + end + end) + assert(not ok and not entered) + assert(#order == 3 and order[1] == 1 and order[2] == 2 and order[3] == 3) +end + +invalid(nil, 1, 1) +invalid(false, 1, 1) +invalid("invalid", 1, 1) +invalid(1, "invalid", 1) +invalid(3, 1, "invalid") + +-- Numeric loops reject tables instead of using arithmetic metamethods. +local invoked = false +local object = setmetatable({}, { + __add = function() invoked = true; return 1 end, + __sub = function() invoked = true; return 1 end, + __lt = function() invoked = true; return false end, + __le = function() invoked = true; return false end, +}) +invalid(object, 1, 1) +invalid(1, object, 1) +invalid(1, 1, object) +assert(not invoked) + +-- A loop uses its initial values even if bound evaluation or its body changes them. +first = "1" +local function limit() first = "99"; return "3" end +count = 0 +for i = first, limit(), "1" do count = count + 1 end +assert(count == 3 and first == "99") +first, last, step = "1", "3", "1" +count = 0 +for i = first, last, step do + count = count + 1 + first, last, step = "99", "0", "-1" +end +assert(count == 3) + +local nan = 0 / 0 +check(nan, 1, 1, {}) +check(1, nan, 1, {}) + +-- Native numeric loops do not depend on the source program's tonumber global. +local original = tonumber +tonumber = function() error("overridden tonumber called") end +check("1", "3", "1", {1, 2, 3}) +tonumber = original + +print("OK") From ee9fa6d363c691507da5f83a0d275d0423e271b1 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:25:12 +0100 Subject: [PATCH 15/20] preserve all generic for iterator results in vmify --- .../compiler/statements/for_in_statement.lua | 20 +++--- tests/generic-for-results.lua | 69 +++++++++++++++++++ 2 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 tests/generic-for-results.lua diff --git a/src/prometheus/compiler/statements/for_in_statement.lua b/src/prometheus/compiler/statements/for_in_statement.lua index 0d16872b..3a489321 100644 --- a/src/prometheus/compiler/statements/for_in_statement.lua +++ b/src/prometheus/compiler/statements/for_in_statement.lua @@ -53,15 +53,21 @@ return function(self, statement, funcDepth) - self:addStatement(Ast.AssignmentStatement({ - self:registerAssignment(scope, exprregs[3]), - varRegs[2] and self:registerAssignment(scope, varRegs[2]), - }, { + -- Keep the control result separate from the visible first loop variable. + -- Native multiple assignment preserves nils and pads missing results. + local resultRegs = {exprregs[3]}; + local resultTargets = {self:registerAssignment(scope, exprregs[3])}; + for i = 2, #varRegs do + resultRegs[i] = varRegs[i]; + resultTargets[i] = self:registerAssignment(scope, varRegs[i]); + end + + self:addStatement(Ast.AssignmentStatement(resultTargets, { Ast.FunctionCallExpression(self:register(scope, exprregs[1]), { self:register(scope, exprregs[2]), self:register(scope, exprregs[3]), }) - }), {exprregs[3], varRegs[2]}, {exprregs[1], exprregs[2], exprregs[3]}, true); + }), resultRegs, {exprregs[1], exprregs[2], exprregs[3]}, true); @@ -76,10 +82,6 @@ return function(self, statement, funcDepth) self:addStatement(self:copyRegisters(scope, {varRegs[1]}, {exprregs[3]}), {varRegs[1]}, {exprregs[3]}, false); - for i=3, #varRegs do - self:addStatement(self:setRegister(scope, varRegs[i], Ast.NilExpression()), {varRegs[i]}, {}, false); - end - for i, id in ipairs(statement.ids) do if(self:isUpvalue(statement.scope, id)) then local varreg = varRegs[i]; diff --git a/tests/generic-for-results.lua b/tests/generic-for-results.lua new file mode 100644 index 00000000..14285d12 --- /dev/null +++ b/tests/generic-for-results.lua @@ -0,0 +1,69 @@ +local calls = 0 +local state = {} +local function iterator(actualState, control) + assert(actualState == state) + calls = calls + 1 + assert(control == calls - 1) + if control == 0 then return 1, 10, nil, 30, nil, "extra" end + if control == 1 then return 2, 20, 40 end + if control == 2 then return 3 end +end + +local count = 0 +for control, a, b, c, d in iterator, state, 0 do + count = count + 1 + assert(control == count and d == nil) + if control == 1 then + assert(a == 10 and b == nil and c == 30) + elseif control == 2 then + assert(a == 20 and b == 40 and c == nil) + else + assert(a == nil and b == nil and c == nil) + end + -- These assignments cannot affect the internal control or the next results. + control, a, b, c, d = 99, 99, 99, 99, 99 +end +assert(count == 3 and calls == 4) + +local function countdown(_, control) + if control > 0 then return control - 1, nil, control * 10, control * 100 end +end + +-- Every iteration gives closures their own cells for every captured result. +local closures = {} +for control, a, b, c, d in countdown, nil, 3 do + closures[#closures + 1] = function() return control, a, b, c, d end +end +assert(#closures == 3) +for index, closure in ipairs(closures) do + local control, a, b, c, d = closure() + assert(control == 3 - index and a == nil and b == (4 - index) * 10) + assert(c == (4 - index) * 100 and d == nil) +end + +-- Fewer declared variables discard extra results without extra iterator calls. +count = 0 +for control in countdown, nil, 3 do + count = count + 1 + assert(control == 3 - count) +end +assert(count == 3) + +local captured +for control, a, b, c in countdown, nil, 3 do + captured = function() return control, a, b, c end + break +end +local control, a, b, c = captured() +assert(control == 2 and a == nil and b == 30 and c == 300) + +local function earlyReturn() + for control, a, b, c, d in countdown, nil, 3 do + return control, a, b, c, d + end +end +local result = {n = select("#", earlyReturn()), earlyReturn()} +assert(result.n == 5 and result[1] == 2 and result[2] == nil) +assert(result[3] == 30 and result[4] == 300 and result[5] == nil) + +print("OK") From 225e0b0e261088900aa6be628c91214c8f9d4874 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:28:54 +0100 Subject: [PATCH 16/20] preserve indexed assignment targets in vmify --- .../compiler/statements/assignment.lua | 15 ++++- tests/assignment-targets.lua | 58 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/assignment-targets.lua diff --git a/src/prometheus/compiler/statements/assignment.lua b/src/prometheus/compiler/statements/assignment.lua index a0f56b05..71162fb4 100644 --- a/src/prometheus/compiler/statements/assignment.lua +++ b/src/prometheus/compiler/statements/assignment.lua @@ -11,11 +11,22 @@ return function(self, statement, funcDepth) local scope = self.activeBlock.scope; local exprregs = {}; local assignmentIndexingRegs = {}; + local function captureTarget(expression) + local reg = self:compileExpression(expression, funcDepth, 1)[1]; + if self:isVarRegister(reg) then + local snapshot = self:allocRegister(false); + self:addStatement(self:copyRegisters(scope, {snapshot}, {reg}), {snapshot}, {reg}, false); + reg = snapshot; + end + return reg; + end + for i, primaryExpr in ipairs(statement.lhs) do if(primaryExpr.kind == AstKind.AssignmentIndexing) then + -- Later target/RHS evaluation and writes may replace these locals. assignmentIndexingRegs [i] = { - base = self:compileExpression(primaryExpr.base, funcDepth, 1)[1], - index = self:compileExpression(primaryExpr.index, funcDepth, 1)[1], + base = captureTarget(primaryExpr.base), + index = captureTarget(primaryExpr.index), }; end end diff --git a/tests/assignment-targets.lua b/tests/assignment-targets.lua new file mode 100644 index 00000000..84cfb5e8 --- /dev/null +++ b/tests/assignment-targets.lua @@ -0,0 +1,58 @@ +-- Indexed targets retain the table and key from before assignment writes. +local t = {} +local original = t +t, t[1] = 0, 42 +assert(t == 0 and original[1] == 42) + +local index = 1 +local values = {} +index, values[index] = 2, 99 +assert(index == 2 and values[1] == 99 and values[2] == nil) + +local a, i, j, b = {"a", "b"}, 1, 2 +b = a +i, a[i], a, j, a[j], a[i+j] = j, i, i, b, j, i +assert(i == 2 and b[1] == 1 and a == 1 and j == b) +assert(b[2] == 2 and b[3] == 1) + +-- Evaluate each target component once and before the right-hand values. +local order = {} +local target = {} +local function tableValue() + order[#order + 1] = "table" + return target +end +local function keyValue() + order[#order + 1] = "key" + return 1 +end +local function rhs() + order[#order + 1] = "rhs" + target = {} + return 7 +end +original = target +tableValue()[keyValue()] = rhs() +assert(original[1] == 7 and target[1] == nil) +assert(table.concat(order, ",") == "table,key,rhs") + +-- Nested assignments also retain captured tables and keys. +local function assignCaptured() + local tableValue, key = {}, "old" + local saved = tableValue + local function assign() + tableValue, key, tableValue[key] = false, "new", 9 + end + assign() + assert(tableValue == false and key == "new") + assert(saved.old == 9 and saved.new == nil) +end +assignCaptured() + +-- Missing results write nil into the captured table after replacing the local. +t = {value = true} +original = t +t, t.value = false +assert(t == false and original.value == nil) + +print("OK") From e34bc1b697ec3050d9cd9b2b9bf68b5d0462279d Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:40:03 +0100 Subject: [PATCH 17/20] honor function environments in vmify --- src/prometheus/compiler/compile_top.lua | 6 +- src/prometheus/compiler/compiler.lua | 89 ++++++++++++++- .../compiler/expressions/function_call.lua | 6 +- .../expressions/pass_self_function_call.lua | 6 +- src/prometheus/compiler/register.lua | 11 +- .../compiler/statements/function_call.lua | 2 +- .../statements/pass_self_function_call.lua | 2 +- tests/function-environment-gc.lua | 14 +++ tests/function-environments.lua | 108 ++++++++++++++++++ 9 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 tests/function-environment-gc.lua create mode 100644 tests/function-environments.lua diff --git a/src/prometheus/compiler/compile_top.lua b/src/prometheus/compiler/compile_top.lua index b98c8184..db99125c 100644 --- a/src/prometheus/compiler/compile_top.lua +++ b/src/prometheus/compiler/compile_top.lua @@ -161,14 +161,16 @@ return function(Compiler) scope:addReferenceToHigherScope(self.scope, self.createVarargClosureVar); retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { Ast.NumberExpression(block.id), - Ast.TableConstructorExpression(upvalueExpressions) + Ast.TableConstructorExpression(upvalueExpressions), + self:env(scope) }); else local varScope, var = self:getCreateClosureVar(#node.args + math.random(0, 5)); scope:addReferenceToHigherScope(varScope, var); retrieveExpression = Ast.FunctionCallExpression(Ast.VariableExpression(varScope, var), { Ast.NumberExpression(block.id), - Ast.TableConstructorExpression(upvalueExpressions) + Ast.TableConstructorExpression(upvalueExpressions), + self:env(scope) }); end diff --git a/src/prometheus/compiler/compiler.lua b/src/prometheus/compiler/compiler.lua index 4055594c..f02c7a56 100644 --- a/src/prometheus/compiler/compiler.lua +++ b/src/prometheus/compiler/compiler.lua @@ -101,6 +101,7 @@ function Compiler:compile(ast) local psc = Scope:new(newGlobalScope, nil); local _, getfenvVar = newGlobalScope:resolve("getfenv"); + local _, setfenvVar = newGlobalScope:resolve("setfenv"); local _, tableVar = newGlobalScope:resolve("table"); local _, unpackVar = newGlobalScope:resolve("unpack"); local _, envVar = newGlobalScope:resolve("_ENV"); @@ -111,7 +112,8 @@ function Compiler:compile(ast) local _, tonumberVar = newGlobalScope:resolve("tonumber"); local _, errorVar = newGlobalScope:resolve("error"); - psc:addReferenceToHigherScope(newGlobalScope, getfenvVar, 2); + psc:addReferenceToHigherScope(newGlobalScope, getfenvVar, 3); + psc:addReferenceToHigherScope(newGlobalScope, setfenvVar); psc:addReferenceToHigherScope(newGlobalScope, tableVar); psc:addReferenceToHigherScope(newGlobalScope, unpackVar); psc:addReferenceToHigherScope(newGlobalScope, envVar); @@ -124,6 +126,9 @@ function Compiler:compile(ast) self.scope = Scope:new(psc); self.envVar = self.scope:addVariable(); + self.getfenvVar = self.scope:addVariable(); + self.setfenvVar = self.scope:addVariable(); + self.callFunctionVar = self.scope:addVariable(); self.containerFuncVar = self.scope:addVariable(); self.unpackVar = self.scope:addVariable(); self.packVar = self.scope:addVariable(); @@ -144,6 +149,7 @@ function Compiler:compile(ast) self.argsVar = self.containerFuncScope:addVariable(); self.currentUpvaluesVar = self.containerFuncScope:addVariable(); self.detectGcCollectVar = self.containerFuncScope:addVariable(); + self.currentEnvVar = self.containerFuncScope:addVariable(); self.returnVar = self.containerFuncScope:addVariable(); self.upvaluesTable = self.scope:addVariable(); @@ -160,6 +166,7 @@ function Compiler:compile(ast) local createClosureScope = Scope:new(self.scope); local createClosurePosArg = createClosureScope:addVariable(); local createClosureUpvalsArg = createClosureScope:addVariable(); + local createClosureEnvArg = createClosureScope:addVariable(); local createClosureProxyObject = createClosureScope:addVariable(); local createClosureFuncVar = createClosureScope:addVariable(); @@ -199,7 +206,38 @@ function Compiler:compile(ast) local unpackValuesVar = unpackScope:addVariable(); unpackScope:addReferenceToHigherScope(self.scope, self.unpackVar); + -- Keep query handling shared: a per-call closure would capture the frame's + -- environment and delay its collection when another Vmify pass wraps it. + -- Ordinary functions receive exactly their original arguments via a tail call. + local callScope = Scope:new(self.scope); + local callFuncVar, callEnvVar = callScope:addVariable(), callScope:addVariable(); + callScope:addReferenceToHigherScope(self.scope, self.getfenvVar, 2); + local getterScope = Scope:new(callScope); + local levelVar = getterScope:addVariable(); + local currentScope = Scope:new(getterScope); + currentScope:addReferenceToHigherScope(callScope, callEnvVar); + local functionNodeAssignments = { + { + var = Ast.AssignmentVariable(self.scope, self.callFunctionVar), + val = Ast.FunctionLiteralExpression({ + Ast.VariableExpression(callScope, callFuncVar), + Ast.VariableExpression(callScope, callEnvVar), + Ast.VarargExpression(), + }, Ast.Block({ + Ast.IfStatement(Ast.AndExpression( + Ast.VariableExpression(self.scope, self.getfenvVar), + Ast.EqualsExpression(Ast.VariableExpression(callScope, callFuncVar), Ast.VariableExpression(self.scope, self.getfenvVar)) + ), Ast.Block({ + Ast.LocalVariableDeclaration(getterScope, {levelVar}, {Ast.VarargExpression()}), + Ast.IfStatement(Ast.OrExpression( + Ast.EqualsExpression(Ast.VariableExpression(getterScope, levelVar), Ast.NilExpression()), + Ast.EqualsExpression(Ast.VariableExpression(getterScope, levelVar), Ast.NumberExpression(1)) + ), Ast.Block({Ast.ReturnStatement{Ast.VariableExpression(callScope, callEnvVar)}}, currentScope), {}), + }, getterScope), {}), + Ast.ReturnStatement{Ast.FunctionCallExpression(Ast.VariableExpression(callScope, callFuncVar), {Ast.VarargExpression()})}, + }, callScope)), + }, { var = Ast.AssignmentVariable(self.scope, self.packVar), val = Ast.FunctionLiteralExpression({Ast.VarargExpression()}, Ast.Block({ @@ -226,13 +264,15 @@ function Compiler:compile(ast) Ast.VariableExpression(self.containerFuncScope, self.posVar), Ast.VariableExpression(self.containerFuncScope, self.argsVar), Ast.VariableExpression(self.containerFuncScope, self.currentUpvaluesVar), - Ast.VariableExpression(self.containerFuncScope, self.detectGcCollectVar) + Ast.VariableExpression(self.containerFuncScope, self.detectGcCollectVar), + Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar) }, self:emitContainerFuncBody()); }, { var = Ast.AssignmentVariable(self.scope, self.createVarargClosureVar), val = Ast.FunctionLiteralExpression({ Ast.VariableExpression(createClosureScope, createClosurePosArg), Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), + Ast.VariableExpression(createClosureScope, createClosureEnvArg), }, Ast.Block({ Ast.LocalVariableDeclaration(createClosureScope, { @@ -252,12 +292,14 @@ function Compiler:compile(ast) Ast.VariableExpression(createClosureScope, createClosurePosArg), self:pack(createClosureSubScope, {Ast.VarargExpression()}), Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureProxyObject) + Ast.VariableExpression(createClosureScope, createClosureProxyObject), + self:closureEnvironment(createClosureSubScope, createClosureScope, createClosureEnvArg) }) } }, createClosureSubScope) ); }); + self:inheritClosureEnvironment(createClosureScope, createClosureFuncVar, createClosureEnvArg); Ast.ReturnStatement{Ast.VariableExpression(createClosureScope, createClosureFuncVar)}; }, createClosureScope) ); @@ -286,6 +328,7 @@ function Compiler:compile(ast) } local tbl = { + Ast.VariableExpression(self.scope, self.callFunctionVar), Ast.VariableExpression(self.scope, self.packVar), Ast.VariableExpression(self.scope, self.unpackPackedVar), Ast.VariableExpression(self.scope, self.containerFuncVar), @@ -312,7 +355,7 @@ function Compiler:compile(ast) -- NEW: Position Shuffler - local ids = util.shuffle({1, 2, 3, 4, 5, 6, 7, 8, 9}); + local ids = util.shuffle({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); local items = { Ast.VariableExpression(self.scope, self.envVar), @@ -324,6 +367,8 @@ function Compiler:compile(ast) Ast.VariableExpression(self.scope, argVar), Ast.VariableExpression(self.scope, self.tonumberVar), Ast.VariableExpression(self.scope, self.errorVar), + Ast.VariableExpression(self.scope, self.getfenvVar), + Ast.VariableExpression(self.scope, self.setfenvVar), } local astItems = { @@ -340,11 +385,14 @@ function Compiler:compile(ast) }); Ast.VariableExpression(newGlobalScope, tonumberVar); Ast.VariableExpression(newGlobalScope, errorVar); + Ast.VariableExpression(newGlobalScope, getfenvVar); + Ast.VariableExpression(newGlobalScope, setfenvVar); } local functionNode = Ast.FunctionLiteralExpression({ items[ids[1]], items[ids[2]], items[ids[3]], items[ids[4]], items[ids[5]], items[ids[6]], items[ids[7]], items[ids[8]], items[ids[9]], + items[ids[10]], items[ids[11]], unpack(util.shuffle(tbl)) }, Ast.Block({ Ast.AssignmentStatement(assignmentStatLhs, assignmentStatRhs); @@ -352,6 +400,7 @@ function Compiler:compile(ast) Ast.FunctionCallExpression(Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.createVarargClosureVar), { Ast.NumberExpression(self.startBlockId); Ast.TableConstructorExpression(upvalEntries); + Ast.VariableExpression(self.scope, self.envVar); }), {Ast.FunctionCallExpression(self:unpack(self.scope), {Ast.VariableExpression(self.scope, argVar)})}); } }, self.scope)); @@ -360,10 +409,36 @@ function Compiler:compile(ast) Ast.ReturnStatement{Ast.FunctionCallExpression(functionNode, { astItems[ids[1]], astItems[ids[2]], astItems[ids[3]], astItems[ids[4]], astItems[ids[5]], astItems[ids[6]], astItems[ids[7]], astItems[ids[8]], astItems[ids[9]], + astItems[ids[10]], astItems[ids[11]], })}; }, psc), newGlobalScope); end +-- Each wrapper reads its own environment at entry, before tail-calling the VM. +-- The VM keeps this table in its call frame so recursion and yields are isolated. +function Compiler:closureEnvironment(scope, inheritedScope, inheritedVar) + scope:addReferenceToHigherScope(self.scope, self.getfenvVar, 2); + scope:addReferenceToHigherScope(inheritedScope, inheritedVar); + return Ast.OrExpression(Ast.AndExpression( + Ast.VariableExpression(self.scope, self.getfenvVar), + Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.getfenvVar), {Ast.NumberExpression(1)}) + ), Ast.VariableExpression(inheritedScope, inheritedVar)); +end + +function Compiler:inheritClosureEnvironment(scope, funcVar, envVar) + scope:addReferenceToHigherScope(self.scope, self.setfenvVar); + local bodyScope = Scope:new(scope); + bodyScope:addReferenceToHigherScope(self.scope, self.setfenvVar); + bodyScope:addReferenceToHigherScope(scope, funcVar); + bodyScope:addReferenceToHigherScope(scope, envVar); + return Ast.IfStatement(Ast.VariableExpression(self.scope, self.setfenvVar), Ast.Block({ + Ast.FunctionCallStatement(Ast.VariableExpression(self.scope, self.setfenvVar), { + Ast.VariableExpression(scope, funcVar), + Ast.VariableExpression(scope, envVar), + }), + }, bodyScope), {}); +end + function Compiler:getCreateClosureVar(argCount) if not self.createClosureVars[argCount] then local var = Ast.AssignmentVariable(self.scope, self.scope:addVariable()); @@ -372,6 +447,7 @@ function Compiler:getCreateClosureVar(argCount) local createClosurePosArg = createClosureScope:addVariable(); local createClosureUpvalsArg = createClosureScope:addVariable(); + local createClosureEnvArg = createClosureScope:addVariable(); local createClosureProxyObject = createClosureScope:addVariable(); local createClosureFuncVar = createClosureScope:addVariable(); @@ -392,6 +468,7 @@ function Compiler:getCreateClosureVar(argCount) local val = Ast.FunctionLiteralExpression({ Ast.VariableExpression(createClosureScope, createClosurePosArg), Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), + Ast.VariableExpression(createClosureScope, createClosureEnvArg), }, Ast.Block({ Ast.LocalVariableDeclaration(createClosureScope, { createClosureProxyObject @@ -408,12 +485,14 @@ function Compiler:getCreateClosureVar(argCount) Ast.VariableExpression(createClosureScope, createClosurePosArg), Ast.TableConstructorExpression(argsTb2), Ast.VariableExpression(createClosureScope, createClosureUpvalsArg), - Ast.VariableExpression(createClosureScope, createClosureProxyObject) + Ast.VariableExpression(createClosureScope, createClosureProxyObject), + self:closureEnvironment(createClosureSubScope, createClosureScope, createClosureEnvArg) }) } }, createClosureSubScope) ); }); + self:inheritClosureEnvironment(createClosureScope, createClosureFuncVar, createClosureEnvArg); Ast.ReturnStatement{Ast.VariableExpression(createClosureScope, createClosureFuncVar)} }, createClosureScope) ); diff --git a/src/prometheus/compiler/expressions/function_call.lua b/src/prometheus/compiler/expressions/function_call.lua index d1bd20f7..30b9e722 100644 --- a/src/prometheus/compiler/expressions/function_call.lua +++ b/src/prometheus/compiler/expressions/function_call.lua @@ -38,12 +38,12 @@ return function(self, expression, funcDepth, numReturns) end if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {Ast.FunctionCallExpression(self:register(scope, baseReg), args)})), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {self:functionCall(scope, baseReg, args)})), {retRegs[1]}, {baseReg, unpack(regs)}, true); else if numReturns > 1 then local tmpReg = self:allocRegister(false); - self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(Ast.FunctionCallExpression(self:register(scope, baseReg), args))}), {tmpReg}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(scope, baseReg, args))}), {tmpReg}, {baseReg, unpack(regs)}, true); for i, reg in ipairs(retRegs) do @@ -52,7 +52,7 @@ return function(self, expression, funcDepth, numReturns) self:freeRegister(tmpReg, false); else - self:addStatement(self:setRegister(scope, retRegs[1], Ast.FunctionCallExpression(self:register(scope, baseReg), args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:functionCall(scope, baseReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); end end diff --git a/src/prometheus/compiler/expressions/pass_self_function_call.lua b/src/prometheus/compiler/expressions/pass_self_function_call.lua index 44bc3af5..30e124b9 100644 --- a/src/prometheus/compiler/expressions/pass_self_function_call.lua +++ b/src/prometheus/compiler/expressions/pass_self_function_call.lua @@ -44,9 +44,9 @@ return function(self, expression, funcDepth, numReturns) self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); if returnAll then - self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {Ast.FunctionCallExpression(self:register(scope, tmpReg), args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:pack(scope, {self:functionCall(scope, tmpReg, args)})), {retRegs[1]}, {tmpReg, unpack(regs)}, true); else - self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(Ast.FunctionCallExpression(self:register(scope, tmpReg), args))}), {tmpReg}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, tmpReg, Ast.TableConstructorExpression{Ast.TableEntry(self:functionCall(scope, tmpReg, args))}), {tmpReg}, {tmpReg, unpack(regs)}, true); for i, reg in ipairs(retRegs) do self:addStatement(self:setRegister(scope, reg, Ast.IndexExpression(self:register(scope, tmpReg), Ast.NumberExpression(i))), {reg}, {tmpReg}, false); @@ -59,7 +59,7 @@ return function(self, expression, funcDepth, numReturns) self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(expression.passSelfFunctionName)), {tmpReg}, {}, false); self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {baseReg, tmpReg}, true); - self:addStatement(self:setRegister(scope, retRegs[1], Ast.FunctionCallExpression(self:register(scope, tmpReg), args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retRegs[1], self:functionCall(scope, tmpReg, args)), {retRegs[1]}, {baseReg, unpack(regs)}, true); end for i, reg in ipairs(regs) do diff --git a/src/prometheus/compiler/register.lua b/src/prometheus/compiler/register.lua index 2df8a47a..ab5522b9 100644 --- a/src/prometheus/compiler/register.lua +++ b/src/prometheus/compiler/register.lua @@ -208,8 +208,15 @@ return function(Compiler) end function Compiler:env(scope) - scope:addReferenceToHigherScope(self.scope, self.envVar); - return Ast.VariableExpression(self.scope, self.envVar); + scope:addReferenceToHigherScope(self.containerFuncScope, self.currentEnvVar); + return Ast.VariableExpression(self.containerFuncScope, self.currentEnvVar); + end + + function Compiler:functionCall(scope, reg, args) + scope:addReferenceToHigherScope(self.scope, self.callFunctionVar); + local callArgs = {self:register(scope, reg), self:env(scope)}; + for _, arg in ipairs(args) do table.insert(callArgs, arg); end + return Ast.FunctionCallExpression(Ast.VariableExpression(self.scope, self.callFunctionVar), callArgs); end function Compiler:jmp(scope, to) diff --git a/src/prometheus/compiler/statements/function_call.lua b/src/prometheus/compiler/statements/function_call.lua index cd53fc23..bfd21f25 100644 --- a/src/prometheus/compiler/statements/function_call.lua +++ b/src/prometheus/compiler/statements/function_call.lua @@ -29,7 +29,7 @@ return function(self, statement, funcDepth) end end - self:addStatement(self:setRegister(scope, retReg, Ast.FunctionCallExpression(self:register(scope, baseReg), args)), {retReg}, {baseReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, retReg, self:functionCall(scope, baseReg, args)), {retReg}, {baseReg, unpack(regs)}, true); self:freeRegister(baseReg, false); self:freeRegister(retReg, false); for _, reg in ipairs(regs) do diff --git a/src/prometheus/compiler/statements/pass_self_function_call.lua b/src/prometheus/compiler/statements/pass_self_function_call.lua index e2622978..aef4e928 100644 --- a/src/prometheus/compiler/statements/pass_self_function_call.lua +++ b/src/prometheus/compiler/statements/pass_self_function_call.lua @@ -30,7 +30,7 @@ return function(self, statement, funcDepth) self:addStatement(self:setRegister(scope, tmpReg, Ast.StringExpression(statement.passSelfFunctionName)), {tmpReg}, {}, false); self:addStatement(self:setRegister(scope, tmpReg, Ast.IndexExpression(self:register(scope, baseReg), self:register(scope, tmpReg))), {tmpReg}, {tmpReg, baseReg}, true); - self:addStatement(self:setRegister(scope, tmpReg, Ast.FunctionCallExpression(self:register(scope, tmpReg), args)), {tmpReg}, {tmpReg, unpack(regs)}, true); + self:addStatement(self:setRegister(scope, tmpReg, self:functionCall(scope, tmpReg, args)), {tmpReg}, {tmpReg, unpack(regs)}, true); self:freeRegister(tmpReg, false); for _, reg in ipairs(regs) do diff --git a/tests/function-environment-gc.lua b/tests/function-environment-gc.lua new file mode 100644 index 00000000..4193f8b9 --- /dev/null +++ b/tests/function-environment-gc.lua @@ -0,0 +1,14 @@ +-- @runtime lua51 +-- Luau can retain the scoped locals until the surrounding function returns. +local references = setmetatable({}, {__mode = "v"}) +do + local environment = {Value = 1} + local temporary = function() return Value end + setfenv(temporary, environment) + assert(temporary() == 1) + references[1], references[2] = temporary, environment +end +collectgarbage() +collectgarbage() +assert(references[1] == nil and references[2] == nil) +print("OK") diff --git a/tests/function-environments.lua b/tests/function-environments.lua new file mode 100644 index 00000000..832843d0 --- /dev/null +++ b/tests/function-environments.lua @@ -0,0 +1,108 @@ +local check, get, set = assert, getfenv, setfenv +local globals, select = _G, select +local function pack(...) return {n = select("#", ...), ...} end + +Value = 10 +local function fixed(delta) + Value = Value + delta + return Value +end +local first, second = {Value = 3}, {Value = 30} +check(set(fixed, first) == fixed and get(fixed) == first) +check(fixed(1) == 4 and first.Value == 4 and Value == 10) +set(fixed, second) +check(fixed(2) == 32 and second.Value == 32 and first.Value == 4) + +local function variadic(delta, ...) + Value = Value + delta + return Value, ... +end +set(variadic, first) +local result = pack(variadic(1, nil, "tail", nil)) +check(result.n == 4 and result[1] == 5 and result[2] == nil) +check(result[3] == "tail" and result[4] == nil and Value == 10) + +-- Queries through captured aliases and _G report the active call's environment. +local function query() + return get(), get(nil), get(1), globals.getfenv(1), get(0) +end +set(query, second) +local a, b, c, d, thread = query() +check(a == second and b == second and c == second and d == second) +check(thread == get(0)) +local function invoke(fn, ...) return fn(...) end +local function indirectQuery() return invoke(get, 1) end +-- The query belongs to invoke, whose environment differs from its caller's. +set(invoke, first) +set(indirectQuery, second) +check(indirectQuery() == first) + +-- New fixed and variadic closures inherit their creator's current environment. +local function maker(delta) + Value = Value + delta + return function(extra) Value = Value + extra; return Value end, + function(...) return Value, ... end +end +set(maker, first) +local child, varargChild = maker(2) +check(get(child) == first and get(varargChild) == first) +set(maker, second) +check(child(1) == 8 and first.Value == 8) +result = pack(varargChild(nil, "child", nil)) +check(result.n == 4 and result[1] == 8 and result[2] == nil) +check(result[3] == "child" and result[4] == nil) +check(second.Value == 32 and Value == 10) + +-- Nested calls and recursion retain each caller's environment. +local outer, inner +outer = function(depth) + Value = Value + 1 + if depth > 0 then inner(depth - 1) end + return Value +end +inner = function(depth) + Value = Value + 10 + if depth > 0 then outer(depth - 1) end + return Value +end +set(outer, first) +set(inner, second) +check(outer(2) == 10 and first.Value == 10 and second.Value == 42) + +-- Interleaved coroutines retain their environment across suspension. +local yield = coroutine.yield +local function worker() + Value = Value + 1 + yield(Value) + Value = Value + 1 + return Value +end +set(worker, first) +local co1 = coroutine.create(worker) +local worker2 = function() + Value = Value + 10 + yield(Value) + Value = Value + 10 + return Value +end +set(worker2, second) +local co2 = coroutine.create(worker2) +local ok, value = coroutine.resume(co1) +check(ok and value == 11) +ok, value = coroutine.resume(co2) +check(ok and value == 52) +ok, value = coroutine.resume(co1) +check(ok and value == 12) +ok, value = coroutine.resume(co2) +check(ok and value == 62) +check(Value == 10) + +-- Shadowed getters remain ordinary functions; VM helpers keep their builtins. +local original = getfenv +getfenv = function() return "custom" end +check(getfenv(1) == "custom") +local anotherChild = maker(1) +check(get(anotherChild) == second and anotherChild(1) == 64) +getfenv = original + +print("OK") From 3ba0fdd6e1fe34dd5162a15667be94621d882eee Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:47:48 +0100 Subject: [PATCH 18/20] extend limitation docs --- doc/reference/limitations.md | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/doc/reference/limitations.md b/doc/reference/limitations.md index a2ad1a76..4848f63b 100644 --- a/doc/reference/limitations.md +++ b/doc/reference/limitations.md @@ -1,5 +1,17 @@ # Limitations +Scripts that rely on the following behavior may require changes before +obfuscation: + +- Replacing a function's environment while it is running, including `module(...)`. +- Lua 5.1's legacy implicit `arg` variable in vararg functions. +- Debug access to original functions, locals, upvalues, source locations, or call stacks. +- Debug hooks or debug-based changes to coroutine environments or primitive metatables. +- Exact tail-call capacity, memory usage, collection timing, or runtime limits. +- Inspection of original bytecode or native runtime layout. + +Source code constructed or loaded at runtime is not automatically obfuscated. + ## Replacing a running function's environment Prometheus does not support replacing a function's environment while that @@ -41,3 +53,52 @@ end The explicit `n` preserves the argument count even when arguments contain nils. Ordinary explicit locals named `arg` and the CLI's global argument table are unaffected by this limitation. + +## Debug introspection and mutation + +Obfuscation changes functions, locals, upvalues, source text, and call stacks. +Compatibility with the original program's debug representation is unsupported. +In particular, do not rely on: + +- `debug.getinfo` returning original source names, function names, or line numbers. +- Debug local and upvalue access or mutation referring to the original variables, + including after a function is dumped and loaded. +- Tracebacks preserving original stack frames, formatting, or error locations. +- Debug hooks observing the original call, return, line, or instruction sequence. +- `debug.getfenv` or `debug.setfenv` accessing coroutine environments as in the + original script. +- `debug.setmetatable` changing the behavior of primitive types such as numbers, + booleans, and nil. + +Ordinary table and userdata metatables are supported. + +## Exact runtime limits and garbage-collection behavior + +Prometheus does not guarantee that transformed code has the same resource +usage, implementation limits, or collection schedule as the original code. +Do not rely on identical: + +- Tail-call or method-tail-call stack capacity. Deep tail recursion may exhaust + the stack after obfuscation. +- String-size and allocation limits, memory usage, or execution timing. +- Parser nesting, local-variable or upvalue limits, or exact diagnostic text. +- Garbage-collector allocation thresholds, step sizes, or step counts. +- Userdata lifetime, finalization order, or the timing of weak-table removal. +- Finalizer resurrection and error behavior during collection or interpreter shutdown. + +`collectgarbage`, weak tables, and userdata finalizers are not generally +unsupported, but scripts should not depend on their exact scheduling. + +## Bytecode and native runtime layout + +Obfuscation does not preserve the original bytecode, instructions, function +layout, or table allocation layout. Scripts or native extensions that inspect +these details cannot assume they match the original program. Dumping a +transformed function produces its transformed representation. + +## Code loaded at runtime + +Prometheus transforms the source files supplied to it. Source strings or files +loaded later through `loadstring`, `load`, `loadfile`, or `require` execute +through the host interpreter and are not automatically transformed. Obfuscate +those files separately if they also need protection. From ad98af4167cefca223846014b292fe34806dac18 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:07:14 +0100 Subject: [PATCH 19/20] fix vm dispatch for consecutive block ids --- src/prometheus/compiler/emit.lua | 9 ++++---- tests/harness/vm-dispatch.lua | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 tests/harness/vm-dispatch.lua diff --git a/src/prometheus/compiler/emit.lua b/src/prometheus/compiler/emit.lua index 58b53c9d..953ce4b6 100644 --- a/src/prometheus/compiler/emit.lua +++ b/src/prometheus/compiler/emit.lua @@ -237,7 +237,8 @@ return function(Compiler) -- Build a strict threshold condition between adjacent block IDs. -- Using a midpoint avoids exact-id comparisons while preserving dispatch. local function buildBlockThresholdCondition(scope, leftId, rightId, useAndOr) - local bound = math.floor((leftId + rightId) / 2); + -- Keep the midpoint strictly between the IDs, even when consecutive. + local bound = (leftId + rightId) / 2; local posExpr = self:pos(scope); local boundExpr = Ast.NumberExpression(bound); @@ -303,9 +304,9 @@ return function(Compiler) local mid = l + math.ceil(len / 2); local leftMaxId = tb[mid - 1].id; local rightMinId = tb[mid].id; - -- Float-safe split: any bound strictly between adjacent IDs works. - -- Midpoint avoids integer-only math.random(min, max) behavior. - local bound = math.floor((leftMaxId + rightMinId) / 2); + -- IDs are 24-bit integers, so half-integer midpoints are exact. + -- Rounding would make consecutive IDs share a dispatch branch. + local bound = (leftMaxId + rightMinId) / 2; local ifScope = Scope:new(pScope); local lBlock = buildElseifChain(tb, l, mid - 1, ifScope); diff --git a/tests/harness/vm-dispatch.lua b/tests/harness/vm-dispatch.lua new file mode 100644 index 00000000..a31a0349 --- /dev/null +++ b/tests/harness/vm-dispatch.lua @@ -0,0 +1,37 @@ +local Prometheus = require("src.prometheus") +Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error +local Compiler = require("prometheus.compiler.compiler") +local createBlock = Compiler.createBlock + +-- Force consecutive IDs so a rounded midpoint cannot silently skip a block. +function Compiler:createBlock() + local block = createBlock(self) + self.dispatchTestNextId = (self.dispatchTestNextId or 99) + 1 + block.id = self.dispatchTestNextId + self.usedBlockIds[block.id] = true + return block +end + +local ok, err = pcall(function() + for seed = 1, 10 do + local config = {LuaVersion = "Lua51", Seed = seed, Steps = {{Name = "Vmify"}}} + local pipeline = Prometheus.Pipeline:fromConfig(config) + + -- Four blocks exercise the short elseif dispatcher. + local source = "local value=0; if ... then value=1 else value=2 end; return value" + local chunk = assert(loadstring(pipeline:apply(source))) + assert(chunk(true) == 1 and chunk(false) == 2, "short dispatcher selected the wrong block") + + -- More blocks exercise binary splits and their reversed comparisons. + local source = "local value=...; if value<0 then return -1 elseif value==0 then return 0 elseif value<5 then return value*2 elseif value<10 then return value+20 else return value-10 end" + local chunk = assert(loadstring(pipeline:apply(source))) + for _, case in ipairs({{-2, -1}, {0, 0}, {1, 2}, {4, 8}, {5, 25}, {9, 29}, {10, 0}, {12, 2}}) do + local actual = chunk(case[1]) + assert(actual == case[2], "binary dispatcher selected the wrong block: seed " .. seed .. + ", input " .. case[1] .. ", got " .. tostring(actual)) + end + end +end) +Compiler.createBlock = createBlock +assert(ok, err) +print("VM dispatch boundary regression checks passed") From 20d547cb2a8778e15d16aded6d01c5b895e95f34 Mon Sep 17 00:00:00 2001 From: Elias Oelschner <62939318+levno-710@users.noreply.github.com> Date: Tue, 27 Oct 2026 13:12:07 +0100 Subject: [PATCH 20/20] prevent block ids from overwriting dispatch entries --- src/prometheus/compiler/emit.lua | 2 +- tests/harness/vm-dispatch.lua | 35 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/prometheus/compiler/emit.lua b/src/prometheus/compiler/emit.lua index 953ce4b6..96af117e 100644 --- a/src/prometheus/compiler/emit.lua +++ b/src/prometheus/compiler/emit.lua @@ -228,8 +228,8 @@ return function(Compiler) end local block = { id = id, index = i, block = Ast.Block(blockstats, block.scope) } + -- Block IDs can overlap list indices; keep this table an ordered list. table.insert(blocks, block); - blocks[id] = block; end table.sort(blocks, function(a, b) return a.id < b.id end); diff --git a/tests/harness/vm-dispatch.lua b/tests/harness/vm-dispatch.lua index a31a0349..3445ce59 100644 --- a/tests/harness/vm-dispatch.lua +++ b/tests/harness/vm-dispatch.lua @@ -2,33 +2,38 @@ local Prometheus = require("src.prometheus") Prometheus.Logger.logLevel = Prometheus.Logger.LogLevel.Error local Compiler = require("prometheus.compiler.compiler") local createBlock = Compiler.createBlock +local firstId -- Force consecutive IDs so a rounded midpoint cannot silently skip a block. function Compiler:createBlock() local block = createBlock(self) - self.dispatchTestNextId = (self.dispatchTestNextId or 99) + 1 + self.dispatchTestNextId = (self.dispatchTestNextId or (firstId - 1)) + 1 block.id = self.dispatchTestNextId self.usedBlockIds[block.id] = true return block end local ok, err = pcall(function() - for seed = 1, 10 do - local config = {LuaVersion = "Lua51", Seed = seed, Steps = {{Name = "Vmify"}}} - local pipeline = Prometheus.Pipeline:fromConfig(config) + -- Low IDs must not overwrite entries in the ordered block list. + for _, start in ipairs({0, 100}) do + firstId = start + for seed = 1, 10 do + local config = {LuaVersion = "Lua51", Seed = seed, Steps = {{Name = "Vmify"}}} + local pipeline = Prometheus.Pipeline:fromConfig(config) - -- Four blocks exercise the short elseif dispatcher. - local source = "local value=0; if ... then value=1 else value=2 end; return value" - local chunk = assert(loadstring(pipeline:apply(source))) - assert(chunk(true) == 1 and chunk(false) == 2, "short dispatcher selected the wrong block") + -- Four blocks exercise the short elseif dispatcher. + local source = "local value=0; if ... then value=1 else value=2 end; return value" + local chunk = assert(loadstring(pipeline:apply(source))) + assert(chunk(true) == 1 and chunk(false) == 2, "short dispatcher selected the wrong block") - -- More blocks exercise binary splits and their reversed comparisons. - local source = "local value=...; if value<0 then return -1 elseif value==0 then return 0 elseif value<5 then return value*2 elseif value<10 then return value+20 else return value-10 end" - local chunk = assert(loadstring(pipeline:apply(source))) - for _, case in ipairs({{-2, -1}, {0, 0}, {1, 2}, {4, 8}, {5, 25}, {9, 29}, {10, 0}, {12, 2}}) do - local actual = chunk(case[1]) - assert(actual == case[2], "binary dispatcher selected the wrong block: seed " .. seed .. - ", input " .. case[1] .. ", got " .. tostring(actual)) + -- More blocks exercise binary splits and their reversed comparisons. + local source = "local value=...; if value<0 then return -1 elseif value==0 then return 0 elseif value<5 then return value*2 elseif value<10 then return value+20 else return value-10 end" + local chunk = assert(loadstring(pipeline:apply(source))) + for _, case in ipairs({{-2, -1}, {0, 0}, {1, 2}, {4, 8}, {5, 25}, {9, 29}, {10, 0}, {12, 2}}) do + local actual = chunk(case[1]) + assert(actual == case[2], "binary dispatcher selected the wrong block: seed " .. seed .. + ", first ID " .. firstId .. ", input " .. case[1] .. ", got " .. tostring(actual)) + end end end end)