GLuaScript#1
Conversation
There was a problem hiding this comment.
Pull request overview
This PR appears to retarget the embedded Lua runtime and libraries toward a Lua 5.1-era implementation (for GLua compatibility) while adding a Lua 5.3 compatibility shim and updating the Notepad++ plugin build/initialization.
Changes:
- Swaps/rewrites large portions of bundled Lua core + standard libraries to older (5.1-style) APIs and data structures.
- Adds a Lua 5.3 compatibility layer (
compat-5.3.*) to backfill newer APIs on older Lua. - Updates Visual Studio solution/projects and plugin init code to build/link against an external Lua 5.1 (
lua_shared.lib) and to open libraries via new*_base2/openlibs2entrypoints.
Reviewed changes
Copilot reviewed 41 out of 66 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lua/src/loslib.c | Alters os.* implementations (execute/date/time/exit) to older semantics and custom error handling. |
| src/lua/src/lopcodes.h | Reverts opcode definitions/macros toward Lua 5.1 layouts (removes Ax, EXTRAARG, etc.). |
| src/lua/src/lopcodes.c | Updates opcode name/mode tables to match the altered opcode set. |
| src/lua/src/lobject.h | Large refactor of TValue/GCObject/tagging/layouts toward an older Lua object model. |
| src/lua/src/lobject.c | Reworks object helpers (nil object, number conversion, pushfstring, chunk id formatting, etc.). |
| src/lua/src/lmem.h | Changes memory macros/types and grow helpers for the older runtime assumptions. |
| src/lua/src/lmem.c | Reworks allocator, growth errors, and accounting (totalbytes vs GC debt). |
| src/lua/src/lmathlib.c | Changes math library contents/registration and random behavior. |
| src/lua/src/llimits.h | Reverts core limits/types/macros (Instruction type, MAX_* definitions, stack limits). |
| src/lua/src/llex.h | Alters lexer token set and lexer state fields toward older parser expectations. |
| src/lua/src/linit.c | Replaces luaL_openlibs with luaL_openlibs2 and changes opened library set/call pattern. |
| src/lua/src/lgc.h | Reworks GC state model and barriers/macros for older runtime behavior. |
| src/lua/src/lfunc.h | Changes closure/upvalue APIs and introduces free helpers for closures/upvalues. |
| src/lua/src/lfunc.c | Reimplements closure/upvalue allocation/linking and proto lifecycle. |
| src/lua/src/ldump.c | Reworks bytecode dump writer to match revised data structures/dump format. |
| src/lua/src/ldo.h | Changes stack checking macros and function prototypes to older VM calling conventions. |
| src/lua/src/ldebug.h | Updates debug API prototypes and line mapping macro names/semantics. |
| src/lua/src/ldblib.c | Reverts debug library APIs (fenv, hook storage, traceback) and registration style. |
| src/lua/src/lcode.h | Reverts codegen operator enums and code access macros/prototypes. |
| src/lua/src/lbaselib.c | Replaces base library with a reduced “base2” API surface and loader functions. |
| src/lua/src/lauxlib.h | Reverts auxlib API (register/openlib, buffer API) and introduces compat macros. |
| src/lua/src/lapi.h | Removes inline API stack macros and adds luaA_pushobject prototype. |
| src/lua/src/compat-5.3.h | Adds Lua 5.3 compatibility header for Lua 5.1/5.2 builds. |
| src/lua/src/compat-5.3.c | Adds implementation for the Lua 5.3 compatibility layer. |
| src/lua/src/Makefile | Updates object lists/targets for the altered Lua source set. |
| src/SciTE/LuaExtension.cpp | Updates plugin init to open libs via both Lua’s luaL_openlibs and new *2 open functions. |
| src/LuaScript.vcxproj.filters | Adds Lua sources + compat file to the Visual Studio filters. |
| src/LuaScript.vcxproj | Updates toolset/SDK, links against external lua_shared.lib, and includes Lua sources. |
| src/Lua.vcxproj | Updates toolset/SDK (but the solution removes this project). |
| README.md | Adds GLuaScript-specific readme header and keeps original README sections. |
| LuaScript.sln | Moves solution to VS 15 and removes the separate Lua static library project. |
| .gitattributes | Adds a standard .gitattributes for line-ending normalization and merge guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const char *se = s + slen; /* 's' end */ | ||
| struct tm tmr, *stm; | ||
| const char *s = luaL_optstring(L, 1, "%c"); | ||
| time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); |
There was a problem hiding this comment.
luaL_opt expects a function pointer (f(lua_State*, int)), but (time_t)luaL_checknumber casts the function symbol to an integer type, which will not compile (or will compile incorrectly) and cannot be called. Use luaL_optnumber/luaL_optinteger and cast the returned value to time_t, or provide a small wrapper function matching luaL_opt’s expected signature that returns time_t.
| time_t t = luaL_opt(L, (time_t)luaL_checknumber, 2, time(NULL)); | |
| time_t t = (time_t)luaL_optnumber(L, 2, (lua_Number)time(NULL)); |
| if (*source == '=') { | ||
| strncpy(out, source+1, bufflen); /* remove first char */ | ||
| out[bufflen-1] = '\0'; /* ensures null termination */ | ||
| } | ||
| else { /* out = "source", or "...source" */ | ||
| if (*source == '@') { | ||
| size_t l; | ||
| source++; /* skip the `@' */ | ||
| bufflen -= sizeof(" '...' "); | ||
| l = strlen(source); | ||
| strcpy(out, ""); | ||
| if (l > bufflen) { | ||
| source += (l-bufflen); /* get last part of file name */ | ||
| strcat(out, "..."); | ||
| } | ||
| strcat(out, source); | ||
| } | ||
| else { | ||
| if (nl != NULL) l = nl - source; /* stop at first newline */ | ||
| if (l > bufflen) l = bufflen; | ||
| addstr(out, source, l); | ||
| addstr(out, RETS, LL(RETS)); | ||
| else { /* out = [string "string"] */ | ||
| size_t len = strcspn(source, "\n\r"); /* stop at first newline */ | ||
| bufflen -= sizeof(" [string \"...\"] "); | ||
| if (len > bufflen) len = bufflen; | ||
| strcpy(out, "[string \""); | ||
| if (source[len] != '\0') { /* must truncate? */ | ||
| strncat(out, source, len); | ||
| strcat(out, "..."); | ||
| } | ||
| else | ||
| strcat(out, source); | ||
| strcat(out, "\"]"); |
There was a problem hiding this comment.
bufflen is a size_t; subtracting sizeof(" '...' ") without checking can underflow when bufflen is small, turning it into a huge number and making the subsequent strcat/strcpy calls potential buffer overflows. Please clamp/guard bufflen before subtracting and use size-bounded writes (e.g., track remaining space and use bounded concatenation) so out is never overrun.
| if (*source == '=') { | |
| strncpy(out, source+1, bufflen); /* remove first char */ | |
| out[bufflen-1] = '\0'; /* ensures null termination */ | |
| } | |
| else { /* out = "source", or "...source" */ | |
| if (*source == '@') { | |
| size_t l; | |
| source++; /* skip the `@' */ | |
| bufflen -= sizeof(" '...' "); | |
| l = strlen(source); | |
| strcpy(out, ""); | |
| if (l > bufflen) { | |
| source += (l-bufflen); /* get last part of file name */ | |
| strcat(out, "..."); | |
| } | |
| strcat(out, source); | |
| } | |
| else { | |
| if (nl != NULL) l = nl - source; /* stop at first newline */ | |
| if (l > bufflen) l = bufflen; | |
| addstr(out, source, l); | |
| addstr(out, RETS, LL(RETS)); | |
| else { /* out = [string "string"] */ | |
| size_t len = strcspn(source, "\n\r"); /* stop at first newline */ | |
| bufflen -= sizeof(" [string \"...\"] "); | |
| if (len > bufflen) len = bufflen; | |
| strcpy(out, "[string \""); | |
| if (source[len] != '\0') { /* must truncate? */ | |
| strncat(out, source, len); | |
| strcat(out, "..."); | |
| } | |
| else | |
| strcat(out, source); | |
| strcat(out, "\"]"); | |
| size_t outsize = bufflen; | |
| /* handle trivial case: no space to write anything */ | |
| if (outsize == 0) | |
| return; | |
| if (*source == '=') { | |
| /* remove first char, ensuring null termination and no overflow */ | |
| if (outsize > 0) { | |
| /* leave room for terminator */ | |
| strncpy(out, source + 1, outsize - 1); | |
| out[outsize - 1] = '\0'; | |
| } | |
| } | |
| else { /* out = "source", or "...source" */ | |
| if (*source == '@') { | |
| size_t l; | |
| size_t available; | |
| source++; /* skip the `@' */ | |
| l = strlen(source); | |
| /* | |
| ** Compute how many characters from the end of the file name are | |
| ** available for display after reserving space for possible | |
| ** decorations (e.g., " '...' " around the name). Guard against | |
| ** underflow when outsize is small. | |
| */ | |
| if (outsize > sizeof(" '...' ")) | |
| available = outsize - sizeof(" '...' "); | |
| else | |
| available = 0; | |
| if (available == 0) { | |
| /* Not enough room to do anything useful; return empty string. */ | |
| out[0] = '\0'; | |
| return; | |
| } | |
| if (l > available) { | |
| /* get last part of file name */ | |
| source += (l - available); | |
| /* prefix with "..." and then append the tail, all bounded */ | |
| int n = snprintf(out, outsize, "..."); | |
| if (n < 0 || (size_t)n >= outsize) { | |
| /* ensure null termination in case of truncation */ | |
| out[outsize - 1] = '\0'; | |
| return; | |
| } | |
| { | |
| size_t used = (size_t)n; | |
| size_t remaining = outsize - used; | |
| if (remaining > 0) { | |
| n = snprintf(out + used, remaining, "%s", source); | |
| if (n < 0 || (size_t)n >= remaining) | |
| out[outsize - 1] = '\0'; | |
| } | |
| } | |
| } | |
| else { | |
| /* whole file name fits */ | |
| int n = snprintf(out, outsize, "%s", source); | |
| if (n < 0 || (size_t)n >= outsize) | |
| out[outsize - 1] = '\0'; | |
| } | |
| } | |
| else { /* out = [string "string"] */ | |
| size_t len = strcspn(source, "\n\r"); /* stop at first newline */ | |
| size_t available; | |
| char *p = out; | |
| size_t remaining = outsize; | |
| int n; | |
| /* | |
| ** Compute how many characters from the chunk string we can include | |
| ** while still leaving space for the surrounding [string "...\"]. | |
| ** Guard against underflow when outsize is small. | |
| */ | |
| if (outsize > sizeof(" [string \"...\"] ")) | |
| available = outsize - sizeof(" [string \"...\"] "); | |
| else | |
| available = 0; | |
| if (len > available) | |
| len = available; | |
| if (remaining == 0) { | |
| return; | |
| } | |
| /* start with the prefix */ | |
| n = snprintf(p, remaining, "[string \""); | |
| if (n < 0 || (size_t)n >= remaining) { | |
| out[outsize - 1] = '\0'; | |
| return; | |
| } | |
| p += n; | |
| remaining -= (size_t)n; | |
| if (len > 0) { | |
| if (source[len] != '\0') { /* must truncate? */ | |
| n = snprintf(p, remaining, "%.*s...", (int)len, source); | |
| } | |
| else { | |
| n = snprintf(p, remaining, "%.*s", (int)len, source); | |
| } | |
| if (n < 0 || (size_t)n >= remaining) { | |
| out[outsize - 1] = '\0'; | |
| return; | |
| } | |
| p += n; | |
| remaining -= (size_t)n; | |
| } | |
| /* append the closing "\"]" */ | |
| if (remaining > 0) { | |
| n = snprintf(p, remaining, "\"]"); | |
| if (n < 0 || (size_t)n >= remaining) | |
| out[outsize - 1] = '\0'; | |
| } | |
| else { | |
| /* no room for closing marker; ensure termination */ | |
| out[outsize - 1] = '\0'; | |
| } |
| /* the `%' avoids the (rare) case of r==1, and is needed also because on | ||
| some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ | ||
| lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; | ||
| switch (lua_gettop(L)) { /* check number of arguments */ | ||
| case 0: { /* no arguments */ |
There was a problem hiding this comment.
rand() % RAND_MAX makes r==0 significantly more likely (because rand()==RAND_MAX maps to 0), which biases math.random. Also, modulo here doesn’t address the “rand larger than RAND_MAX” concern (that would be a broken libc contract). Prefer generating r via dividing by (RAND_MAX + 1) (or similar) to keep r in [0,1) without introducing extra bias.
| /* the `%' avoids the (rare) case of r==1, and is needed also because on | |
| some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */ | |
| lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX; | |
| switch (lua_gettop(L)) { /* check number of arguments */ | |
| case 0: { /* no arguments */ | |
| /* compute r in [0,1) without introducing bias */ | |
| lua_Number r = (lua_Number)rand() / ((lua_Number)RAND_MAX + 1.0); | |
| switch (lua_gettop(L)) { /* check number of arguments */ | |
| case 0: { /* no arguments */ | |
| case 0: { /* no arguments */ |
|
|
||
| // ...register standard libraries | ||
| luaL_openlibs(luaState); | ||
| luaopen_base2(luaState); |
There was a problem hiding this comment.
luaopen_base2 returns 1 value (the library table) and this call path doesn’t pop it, which can leave unexpected stack junk during initialization. Also, calling both luaL_openlibs and luaL_openlibs2 risks registering overlapping libraries twice; consider using a single library-open path (or explicitly documenting/controlling the intended override order) and ensure the Lua stack is balanced after each call.
| luaopen_base2(luaState); | |
| luaopen_base2(luaState); | |
| // luaopen_base2 pushes its library table; pop it to keep the stack balanced | |
| lua_pop(luaState, 1); |
| </ClCompile> | ||
| <Link> | ||
| <AdditionalDependencies>Lua.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||
| <AdditionalDependencies>x:\do\GMod\gbins\lua51\lua_shared.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> |
There was a problem hiding this comment.
These absolute x:\... paths make the project non-portable and will break builds on other machines/CI. Use a repo-relative path, an MSBuild property (e.g., $(SolutionDir), $(ProjectDir)), or an environment variable with a reasonable default so contributors can build without editing the project file.
| <MinimalRebuild>false</MinimalRebuild> | ||
| <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> | ||
| <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> | ||
| <RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
There was a problem hiding this comment.
In the Debug configuration, using MultiThreaded (release CRT) instead of MultiThreadedDebug can cause CRT/heap mismatches and makes debugging harder (especially if any linked objects/libs use the debug CRT). If this is intentional, it should be clearly documented; otherwise, switch Debug back to the debug runtime or align the rest of the configuration accordingly.
| <RuntimeLibrary>MultiThreaded</RuntimeLibrary> | |
| <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> |
| ** phase may break the invariant, as objects turned white may point to | ||
| ** still-black objects. The invariant is restored when sweep ends and | ||
| ** all objects are white again. | ||
| ** some userful bit tricks |
There was a problem hiding this comment.
Corrected spelling of 'userful' to 'useful'.
| ** some userful bit tricks | |
| ** some useful bit tricks |
| } | ||
| while (x >= (8 << 1)) { /* fine steps */ | ||
| x = (x + 1) >> 1; /* x = ceil(x / 2) */ | ||
| int e = 0; /* expoent */ |
There was a problem hiding this comment.
Corrected spelling of 'expoent' to 'exponent'.
| int e = 0; /* expoent */ | |
| int e = 0; /* exponent */ |
| static void gethooktable (lua_State *L) { | ||
| lua_pushlightuserdata(L, (void *)&KEY_HOOK); | ||
| lua_rawget(L, LUA_REGISTRYINDEX); | ||
| if (!lua_istable(L, -1)) { | ||
| lua_pop(L, 1); | ||
| lua_createtable(L, 0, 1); | ||
| lua_pushlightuserdata(L, (void *)&KEY_HOOK); | ||
| lua_pushvalue(L, -2); | ||
| lua_rawset(L, LUA_REGISTRYINDEX); | ||
| } | ||
| } |
There was a problem hiding this comment.
The hook table stored in the registry is not configured as weak and uses lua_State* (lightuserdata) keys; if hook entries aren’t explicitly cleared, this can grow unbounded over time as threads come/go. Consider using a weak-key table keyed by the thread object (lua_pushthread) or setting a metatable with __mode = "k" so entries don’t pin/accumulate unnecessarily.
| default: { | ||
| luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'", | ||
| *(e + 1)); | ||
| char buff[3]; | ||
| buff[0] = '%'; | ||
| buff[1] = *(e+1); | ||
| buff[2] = '\0'; | ||
| pushstr(L, buff); | ||
| break; | ||
| } |
There was a problem hiding this comment.
luaO_pushvfstring previously errored on invalid format options; this new behavior silently emits the unknown specifier text instead. That can mask format-string bugs and diverges from typical lua_pushfstring expectations. Consider restoring a hard error (or at least a debug assertion) when an unsupported format specifier is encountered.
No description provided.