Fixed method/function name collisions.
This commit is contained in:
parent
0a82eb2ac9
commit
82c040395d
11 changed files with 72 additions and 44 deletions
2
Makefile
2
Makefile
|
|
@ -399,7 +399,7 @@ bin/testTask: obj/testTask.o $(TASKADP) obj/calogHandle.o lib/libcalog.a lib/lib
|
|||
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS) -lstdc++ -lm
|
||||
|
||||
# export library test: publish from Lua, call by bare name from Lua + via callExport from JS.
|
||||
bin/testExport: obj/testExport.o obj/calogExport.o lib/libcalog.a lib/liblua.a lib/libquickjs.a lib/libsquirrel.a lib/libs7.a | bin
|
||||
bin/testExport: obj/testExport.o obj/calogExport.o lib/libcalog.a lib/liblua.a lib/libquickjs.a lib/libsquirrel.a lib/libs7.a lib/libmybasic.a | bin
|
||||
$(CC) $(LDFLAGS) -pthread -o $@ $^ $(LUALIBS) -lstdc++ -ldl -lm
|
||||
|
||||
obj bin lib:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// calogExport.c -- calog export library (see calogExport.h). A process-wide, mutex-guarded
|
||||
// map of name -> reference-counted function value. export/unexport/call are natives;
|
||||
// __exportResolve is the hook the per-engine dynamic-name resolvers call to look a name up.
|
||||
// map of name -> reference-counted function value. calogExport/calogUnexport/calogCall are
|
||||
// natives; __calogExportResolve is the hook the per-engine dynamic-name resolvers look up.
|
||||
//
|
||||
// The registry state is STATIC (not heap), and never freed: the four natives are reachable
|
||||
// (via any context, and via every Lua unknown-global lookup) right up until calogDestroy
|
||||
|
|
@ -46,10 +46,10 @@ int32_t calogExportRegister(CalogT *calog) {
|
|||
pthread_mutex_lock(&gInitMutex);
|
||||
gRefCount++;
|
||||
pthread_mutex_unlock(&gInitMutex);
|
||||
calogRegisterInline(calog, "export", exportPublish, NULL);
|
||||
calogRegisterInline(calog, "unexport", exportRemove, NULL);
|
||||
calogRegisterInline(calog, "call", exportCall, NULL);
|
||||
calogRegisterInline(calog, "__exportResolve", exportResolve, NULL);
|
||||
calogRegisterInline(calog, "calogExport", exportPublish, NULL);
|
||||
calogRegisterInline(calog, "calogUnexport", exportRemove, NULL);
|
||||
calogRegisterInline(calog, "calogCall", exportCall, NULL);
|
||||
calogRegisterInline(calog, "__calogExportResolve", exportResolve, NULL);
|
||||
return calogOkE;
|
||||
}
|
||||
|
||||
|
|
@ -84,13 +84,13 @@ static int32_t exportCall(CalogValueT *args, int32_t argCount, CalogValueT *resu
|
|||
(void)userData;
|
||||
calogValueNil(result);
|
||||
if (argCount < 1 || args[0].type != calogStringE) {
|
||||
return calogFail(result, calogErrArgE, "call expects (name, ...args)");
|
||||
return calogFail(result, calogErrArgE, "calogCall expects (name, ...args)");
|
||||
}
|
||||
pthread_mutex_lock(&gMapMutex);
|
||||
index = exportFindLocked(args[0].as.s.bytes);
|
||||
if (index < 0) {
|
||||
pthread_mutex_unlock(&gMapMutex);
|
||||
return calogFail(result, calogErrNotFoundE, "call: no such exported function");
|
||||
return calogFail(result, calogErrNotFoundE, "calogCall: no such exported function");
|
||||
}
|
||||
// Hold a reference across the (unlocked) call so a concurrent unexport can't free it.
|
||||
fn = gEntries[index].fn;
|
||||
|
|
@ -122,7 +122,7 @@ static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *r
|
|||
(void)userData;
|
||||
calogValueNil(result);
|
||||
if (argCount != 2 || args[0].type != calogStringE || args[1].type != calogFnE) {
|
||||
return calogFail(result, calogErrArgE, "export expects (name, function)");
|
||||
return calogFail(result, calogErrArgE, "calogExport expects (name, function)");
|
||||
}
|
||||
name = args[0].as.s.bytes;
|
||||
pthread_mutex_lock(&gMapMutex);
|
||||
|
|
@ -142,7 +142,7 @@ static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *r
|
|||
grown = (ExportEntryT *)realloc(gEntries, (size_t)newCap * sizeof(ExportEntryT));
|
||||
if (grown == NULL) {
|
||||
pthread_mutex_unlock(&gMapMutex);
|
||||
return calogFail(result, calogErrOomE, "export: out of memory");
|
||||
return calogFail(result, calogErrOomE, "calogExport: out of memory");
|
||||
}
|
||||
gEntries = grown;
|
||||
gCap = newCap;
|
||||
|
|
@ -150,7 +150,7 @@ static int32_t exportPublish(CalogValueT *args, int32_t argCount, CalogValueT *r
|
|||
nameCopy = strdup(name);
|
||||
if (nameCopy == NULL) {
|
||||
pthread_mutex_unlock(&gMapMutex);
|
||||
return calogFail(result, calogErrOomE, "export: out of memory");
|
||||
return calogFail(result, calogErrOomE, "calogExport: out of memory");
|
||||
}
|
||||
calogFnRetain(args[1].as.fn);
|
||||
gEntries[gCount].name = nameCopy;
|
||||
|
|
@ -167,7 +167,7 @@ static int32_t exportRemove(CalogValueT *args, int32_t argCount, CalogValueT *re
|
|||
(void)userData;
|
||||
calogValueNil(result);
|
||||
if (argCount != 1 || args[0].type != calogStringE) {
|
||||
return calogFail(result, calogErrArgE, "unexport expects (name)");
|
||||
return calogFail(result, calogErrArgE, "calogUnexport expects (name)");
|
||||
}
|
||||
pthread_mutex_lock(&gMapMutex);
|
||||
index = exportFindLocked(args[0].as.s.bytes);
|
||||
|
|
@ -188,7 +188,7 @@ static int32_t exportResolve(CalogValueT *args, int32_t argCount, CalogValueT *r
|
|||
(void)userData;
|
||||
calogValueNil(result);
|
||||
if (argCount != 1 || args[0].type != calogStringE) {
|
||||
return calogFail(result, calogErrArgE, "__exportResolve expects (name)");
|
||||
return calogFail(result, calogErrArgE, "__calogExportResolve expects (name)");
|
||||
}
|
||||
pthread_mutex_lock(&gMapMutex);
|
||||
index = exportFindLocked(args[0].as.s.bytes);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
// calogExport.h -- calog export library: publish script functions other scripts can call.
|
||||
//
|
||||
// Registers natives so a script can share a function by name across contexts and engines:
|
||||
// export(name, fn) publish a function value under a global name
|
||||
// unexport(name) remove it
|
||||
// call(name, ...args) call an exported function by name -- works in EVERY engine
|
||||
// calogExport(name, fn) publish a function value under a global name
|
||||
// calogUnexport(name) remove it
|
||||
// calogCall(name, ...args) call an exported function by name -- works in EVERY engine
|
||||
//
|
||||
// The natives are calog-prefixed so they never collide with an engine's reserved words
|
||||
// (plain `export` is a JavaScript keyword; plain `call` is a my-basic keyword).
|
||||
//
|
||||
// On engines with a runtime unknown-name hook (Lua, Squirrel, JavaScript, s7), an exported
|
||||
// name is ALSO reachable by its BARE name, e.g. `luaExample(1, 2)`; engines that resolve
|
||||
// names statically (Wren, Berry, my-basic) use call('luaExample', ...). Resolution is
|
||||
// dynamic: a name exported at any time becomes callable immediately, and unexport takes
|
||||
// names statically (Wren, Berry, my-basic) use calogCall('luaExample', ...). Resolution is
|
||||
// dynamic: a name exported at any time becomes callable immediately, and calogUnexport takes
|
||||
// effect at once.
|
||||
//
|
||||
// An exported function is an ordinary calog function value, so a call runs in the exporter's
|
||||
|
|
|
|||
13
src/broker.c
13
src/broker.c
|
|
@ -95,9 +95,18 @@ void calogForEach(CalogT *broker, void (*visit)(const CalogEntryT *entry, void *
|
|||
int64_t index;
|
||||
|
||||
for (index = 0; index < broker->slotCount; index++) {
|
||||
if (broker->slots[index].name != NULL) {
|
||||
visit(&broker->slots[index], ud);
|
||||
const char *name;
|
||||
name = broker->slots[index].name;
|
||||
if (name == NULL) {
|
||||
continue;
|
||||
}
|
||||
// Natives whose name begins with "__" are internal (e.g. the export resolver hook
|
||||
// __calogExportResolve): still reachable through calogCall/calogLookup, but never
|
||||
// exposed into an engine's global namespace, so scripts cannot see or shadow them.
|
||||
if (name[0] == '_' && name[1] == '_') {
|
||||
continue;
|
||||
}
|
||||
visit(&broker->slots[index], ud);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ void calogPump(CalogT *calog); // run pending script->native calls on THIS
|
|||
void calogSetErrorHandler(CalogT *calog, CalogErrorFnT fn, void *userData);
|
||||
|
||||
// ---- natives ----
|
||||
// A name beginning with "__" is INTERNAL: it is callable via calogCall but is NOT exposed
|
||||
// into any engine's global namespace, so scripts can neither see nor shadow it.
|
||||
int32_t calogRegister(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the host thread
|
||||
int32_t calogRegisterInline(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the calling script thread
|
||||
int32_t calogCall(CalogT *calog, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ struct CalogT {
|
|||
CalogT *calogBrokerCreate(void);
|
||||
void calogBrokerDestroy(CalogT *calog);
|
||||
CalogEntryT *calogLookup(CalogT *calog, const char *name);
|
||||
// Visit every registered entry (used by createInterpreter to expose all natives).
|
||||
// Visit every registered entry EXCEPT internal "__"-prefixed ones (used by createInterpreter
|
||||
// to expose natives to scripts; __ natives stay reachable via calogCall but are not exposed).
|
||||
// Safe only while the registry is frozen -- i.e. before any context starts.
|
||||
void calogForEach(CalogT *calog, void (*visit)(const CalogEntryT *entry, void *ud), void *ud);
|
||||
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ static int jsResolveOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, JSVa
|
|||
return 0;
|
||||
}
|
||||
calogValueNil(&result);
|
||||
status = calogCall(context->broker, "__exportResolve", &nameArg, 1, &result);
|
||||
status = calogCall(context->broker, "__calogExportResolve", &nameArg, 1, &result);
|
||||
calogValueFree(&nameArg);
|
||||
if (status != calogOkE || result.type != calogFnE) {
|
||||
calogValueFree(&result);
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ static int luaGlobalResolve(lua_State *L) {
|
|||
return 1;
|
||||
}
|
||||
calogValueNil(&result);
|
||||
status = calogCall(context->broker, "__exportResolve", &nameArg, 1, &result);
|
||||
status = calogCall(context->broker, "__calogExportResolve", &nameArg, 1, &result);
|
||||
calogValueFree(&nameArg);
|
||||
if (status == calogOkE && result.type == calogFnE) {
|
||||
if (luaPushValueDepth(L, &result, 0) != calogOkE) {
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ static s7_pointer s7ResolveUnbound(s7_scheme *sc, s7_pointer args) {
|
|||
return s7_unspecified(sc);
|
||||
}
|
||||
calogValueNil(&result);
|
||||
status = calogCall(context->broker, "__exportResolve", &nameArg, 1, &result);
|
||||
status = calogCall(context->broker, "__calogExportResolve", &nameArg, 1, &result);
|
||||
calogValueFree(&nameArg);
|
||||
if (status == calogOkE && result.type == calogFnE) {
|
||||
s7_let_set(sc, hookLet, s7_make_symbol(sc, "result"), s7FromValue(context, &result, 0));
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ static SQInteger squirrelResolveGet(HSQUIRRELVM v) {
|
|||
return sq_throwerror(v, _SC("calog resolver: out of memory"));
|
||||
}
|
||||
calogValueNil(&result);
|
||||
status = calogCall(context->broker, "__exportResolve", &nameArg, 1, &result);
|
||||
status = calogCall(context->broker, "__calogExportResolve", &nameArg, 1, &result);
|
||||
calogValueFree(&nameArg);
|
||||
if (status == calogOkE && result.type == calogFnE) {
|
||||
status = squirrelPushValueDepth(v, &result, 0);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// testExport.c -- exercises the export library. One Lua context publishes a function with
|
||||
// export(); another Lua context calls it by its BARE name (via the globals __index resolver);
|
||||
// a JavaScript context calls it via call. All calls run in the exporter's context.
|
||||
// calogExport(); another Lua context calls it by its BARE name (via the globals __index resolver);
|
||||
// a JavaScript context calls it via calogCall. All calls run in the exporter's context.
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
|
|
@ -140,6 +140,7 @@ int main(void) {
|
|||
CalogContextT *jsCaller;
|
||||
CalogContextT *s7Caller;
|
||||
CalogContextT *squirrelCaller;
|
||||
CalogContextT *mbCaller;
|
||||
CalogContextT *reloadV1;
|
||||
CalogContextT *reloadV2;
|
||||
int32_t i;
|
||||
|
|
@ -165,7 +166,7 @@ int main(void) {
|
|||
exporter = calogContextOpen(calog, &calogLuaEngine);
|
||||
calogContextEval(exporter,
|
||||
"local function adder(a, b) return a + b end\n"
|
||||
"export('adder', adder)\n"
|
||||
"calogExport('adder', adder)\n"
|
||||
"exporterReady()");
|
||||
pumpUntilReady();
|
||||
|
||||
|
|
@ -173,13 +174,14 @@ int main(void) {
|
|||
luaCaller = calogContextOpen(calog, &calogLuaEngine);
|
||||
calogContextEval(luaCaller,
|
||||
"report(1, adder(2, 3))\n"
|
||||
"report(2, call('adder', 10, 20))\n"
|
||||
"report(2, calogCall('adder', 10, 20))\n"
|
||||
"report(10, __calogExportResolve == nil and 1 or 0)\n" // internal native must be hidden
|
||||
"done()");
|
||||
|
||||
// A JavaScript context reaches the same Lua function via call, then by BARE name.
|
||||
jsCaller = calogContextOpen(calog, &calogJsEngine);
|
||||
calogContextEval(jsCaller,
|
||||
"report(3, call('adder', 100, 1));\n"
|
||||
"report(3, calogCall('adder', 100, 1));\n"
|
||||
"report(8, adder(9, 1));\n"
|
||||
"done();");
|
||||
|
||||
|
|
@ -192,14 +194,24 @@ int main(void) {
|
|||
"report(7, adder(50, 5));\n"
|
||||
"done();");
|
||||
|
||||
pumpUntilDone(4);
|
||||
// my-basic resolves names statically (no bare-name), so it reaches the export via
|
||||
// calogCall -- which the calog prefix keeps clear of its CALL keyword.
|
||||
mbCaller = calogContextOpen(calog, &calogMyBasicEngine);
|
||||
calogContextEval(mbCaller,
|
||||
"x = calogCall(\"adder\", 30, 5)\n"
|
||||
"report(9, x)\n"
|
||||
"done()");
|
||||
|
||||
pumpUntilDone(5);
|
||||
|
||||
CHECK(atomic_load(&results[1]) == 5, "a Lua script called an exported function by its bare name");
|
||||
CHECK(atomic_load(&results[2]) == 30, "call reached the same exported function");
|
||||
CHECK(atomic_load(&results[3]) == 101, "a JavaScript script called the exported Lua function via call");
|
||||
CHECK(atomic_load(&results[2]) == 30, "calogCall reached the same exported function");
|
||||
CHECK(atomic_load(&results[3]) == 101, "a JavaScript script called the exported Lua function via calogCall");
|
||||
CHECK(atomic_load(&results[8]) == 10, "a JavaScript script called the export by its bare name");
|
||||
CHECK(atomic_load(&results[6]) == 105, "an s7 script called the export by its bare name");
|
||||
CHECK(atomic_load(&results[7]) == 55, "a Squirrel script called the export by its bare name");
|
||||
CHECK(atomic_load(&results[9]) == 35, "a my-basic script reached the export via calogCall");
|
||||
CHECK(atomic_load(&results[10]) == 1, "the internal __calogExportResolve native is hidden from script globals");
|
||||
CHECK(atomic_load(&errorCount) == 0, "no errors during export/import");
|
||||
|
||||
// Hot-reload: a script exports a function, is unloaded, then a NEW version re-exports the
|
||||
|
|
@ -210,37 +222,38 @@ int main(void) {
|
|||
atomic_store(&exporterReady, false);
|
||||
calogContextEval(reloadV1,
|
||||
"local function greet() return 111 end\n"
|
||||
"export('greet', greet)\n"
|
||||
"calogExport('greet', greet)\n"
|
||||
"exporterReady()");
|
||||
pumpUntilReady();
|
||||
calogContextEval(luaCaller, "report(4, call('greet'))\n done()");
|
||||
pumpUntilDone(5);
|
||||
calogContextEval(luaCaller, "report(4, calogCall('greet'))\n done()");
|
||||
pumpUntilDone(6);
|
||||
calogContextClose(reloadV1); // greet stays exported, but its owner is gone
|
||||
reloadV2 = calogContextOpen(calog, &calogLuaEngine);
|
||||
atomic_store(&exporterReady, false);
|
||||
calogContextEval(reloadV2,
|
||||
"local function greet() return 222 end\n"
|
||||
"export('greet', greet)\n" // releases the old, dead-owner greet
|
||||
"calogExport('greet', greet)\n" // releases the old, dead-owner greet
|
||||
"exporterReady()");
|
||||
pumpUntilReady();
|
||||
calogContextEval(luaCaller, "report(5, call('greet'))\n done()");
|
||||
pumpUntilDone(6);
|
||||
calogContextEval(luaCaller, "report(5, calogCall('greet'))\n done()");
|
||||
pumpUntilDone(7);
|
||||
CHECK(atomic_load(&results[4]) == 111, "an exported function is callable before reload");
|
||||
CHECK(atomic_load(&results[5]) == 222, "a reloaded script re-exports a new version after the old owner unloaded");
|
||||
calogContextClose(reloadV2);
|
||||
|
||||
// Release exports while the runtime + contexts are still alive, then tear the runtime down.
|
||||
calogExportShutdown();
|
||||
// Regression: after shutdown, an unknown-global read still routes through __exportResolve
|
||||
// (via the _G __index) -- the static registry must keep it safe, not a freed-state UAF.
|
||||
// Regression: after shutdown, an unknown-global read still routes through
|
||||
// __calogExportResolve (via the _G __index) -- the static registry keeps it safe.
|
||||
calogContextEval(luaCaller, "local x = someUndefinedGlobalName\n done()");
|
||||
pumpUntilDone(7);
|
||||
CHECK(atomic_load(&doneCount) >= 7, "resolving an unknown global after export shutdown is safe");
|
||||
pumpUntilDone(8);
|
||||
CHECK(atomic_load(&doneCount) >= 8, "resolving an unknown global after export shutdown is safe");
|
||||
|
||||
calogContextClose(luaCaller);
|
||||
calogContextClose(jsCaller);
|
||||
calogContextClose(s7Caller);
|
||||
calogContextClose(squirrelCaller);
|
||||
calogContextClose(mbCaller);
|
||||
calogContextClose(exporter);
|
||||
calogDestroy(calog);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue