diff --git a/build-linuxcnc-wasm-standalone-probe.sh b/build-linuxcnc-wasm-standalone-probe.sh index 577ca4f..bd87699 100755 --- a/build-linuxcnc-wasm-standalone-probe.sh +++ b/build-linuxcnc-wasm-standalone-probe.sh @@ -7,7 +7,7 @@ cd "$(dirname "$0")" # rs274/tooldata sources directly or link wasm-safe shims whose source basis is # checked against LinuxCNC source comments before building. usage() { - echo "usage: $0 --mode tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|rs274ngc-pre-object" >&2 + echo "usage: $0 --mode tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|python-plugin-inittab-link|rs274ngc-pre-object" >&2 } mode= @@ -33,7 +33,7 @@ while [[ "$#" -gt 0 ]]; do esac done case "$mode" in - tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|rs274ngc-pre-object) + tooldata-link|tooldata-common-link|interp-base-link|python-plugin-link|python-plugin-inittab-link|rs274ngc-pre-object) ;; "") usage @@ -114,14 +114,21 @@ case "$mode" in ) target_name=interp_base_probe ;; - python-plugin-link) + python-plugin-link|python-plugin-inittab-link) python_plugin_shim=$(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) - grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' core/wasm_shims/pythonplugin/python_plugin_probe_main.cc >/dev/null + if [[ "$mode" == "python-plugin-link" ]]; then + probe_source=core/wasm_shims/pythonplugin/python_plugin_probe_main.cc + target_name=python_plugin_probe + grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' "$probe_source" >/dev/null + else + probe_source=core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc + target_name=python_plugin_inittab_probe + grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc' "$probe_source" >/dev/null + fi sources+=( "$python_plugin_shim" - core/wasm_shims/pythonplugin/python_plugin_probe_main.cc + "$probe_source" ) - target_name=python_plugin_probe ;; rs274ngc-pre-object) rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc) diff --git a/check-linuxcnc-wasm-shims-source-map.sh b/check-linuxcnc-wasm-shims-source-map.sh index b993377..480c3a1 100755 --- a/check-linuxcnc-wasm-shims-source-map.sh +++ b/check-linuxcnc-wasm-shims-source-map.sh @@ -93,11 +93,15 @@ grep -F 'return vsnprintf(buffer, size, fmt, args);' \ grep -F 'class PythonPlugin' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_PYTHON_NOT_INITIALIZED = -12,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_BAD_PATH = -5,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null +grep -F 'PLUGIN_INITTAB_FAILED = -7 ,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_EXCEPTION_DURING_PATH_PREPEND = -9,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_EXCEPTION_DURING_PATH_APPEND = -10,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_NO_CALLABLE = 1,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PLUGIN_EXCEPTION = 2' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null grep -F 'PythonPlugin::instantiate' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'for (int i = 0; inittab[i].name != NULL; i++) {' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'if (module == NULL) {' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null +grep -F 'status = PLUGIN_INITTAB_FAILED;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null grep -F 'if (status < PLUGIN_OK)' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null grep -F 'if (auto inistring = inifile.findString("TOPLEVEL", section)) {' \ "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null @@ -327,6 +331,7 @@ for needle in [ python_plugin_shim = Path("core/wasm_shims/pythonplugin/python_plugin.cc").read_text(encoding="utf-8") python_plugin_probe = Path("core/wasm_shims/pythonplugin/python_plugin_probe_main.cc").read_text(encoding="utf-8") +python_plugin_inittab_probe = Path("core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc").read_text(encoding="utf-8") python_c_api_shim = Path("core/wasm_shims/python_c_api_shim.cc").read_text(encoding="utf-8") for forbidden in [ "bp::exec(", @@ -340,6 +345,8 @@ for needle in [ 'find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)', "realpath(toplevel.c_str(), resolved_path) == nullptr", "status = PLUGIN_BAD_PATH;", + "status = PLUGIN_INITTAB_FAILED;", + "failed to initialize built-in module", 'check_python_path_entries(iniFilename,', "PLUGIN_EXCEPTION_DURING_PATH_PREPEND", "PLUGIN_EXCEPTION_DURING_PATH_APPEND", @@ -351,6 +358,16 @@ for needle in [ ]: if needle not in python_plugin_shim: raise SystemExit(f"PythonPlugin wasm shim missing disabled-python boundary: {needle}") +for needle in [ + "PythonPlugin::PythonPlugin(struct _inittab*) reports PLUGIN_INITTAB_FAILED", + "PythonPlugin *plugin = PythonPlugin::instantiate(bad_modules);", + "plugin != nullptr", + "python_plugin->plugin_status() != PLUGIN_INITTAB_FAILED", + "python_plugin->usable()", + 'python_plugin->last_errmsg() != "failed to initialize built-in module"', +]: + if needle not in python_plugin_inittab_probe: + raise SystemExit(f"PythonPlugin inittab probe missing first-caller status assertion: {needle}") for needle in [ "PLUGIN_NO_SECTION", "PLUGIN_BAD_INIFILE", @@ -437,6 +454,7 @@ for phrase in [ "must not execute `bp::exec`, `bp::exec_file`, or", "inactive-DB return values", "negative-status short-circuit behavior", + "first-caller `_inittab` `PLUGIN_INITTAB_FAILED` edge", "preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge", "preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses", "LinuxCNC `rcs_print_error` split entry points", diff --git a/core/wasm_shims/pythonplugin/python_plugin.cc b/core/wasm_shims/pythonplugin/python_plugin.cc index bb8fe7d..857d437 100644 --- a/core/wasm_shims/pythonplugin/python_plugin.cc +++ b/core/wasm_shims/pythonplugin/python_plugin.cc @@ -113,10 +113,27 @@ int check_python_path_entries(const char *iniFilename, } // namespace -PythonPlugin *PythonPlugin::instantiate(struct _inittab *) { +PythonPlugin *PythonPlugin::instantiate(struct _inittab *inittab) { static PythonPlugin plugin; - python_plugin = &plugin; - python_plugin->status = PLUGIN_OK; + if (!python_plugin) { + python_plugin = &plugin; + python_plugin->status = PLUGIN_OK; + if (inittab) { + for (int i = 0; inittab[i].name != nullptr; ++i) { + // LinuxCNC source basis: python_plugin.cc reports + // PLUGIN_INITTAB_FAILED when a built-in module from the + // _inittab cannot be initialized. + if (!inittab[i].initfunc || inittab[i].initfunc() == nullptr) { + python_plugin->error_msg = "failed to initialize built-in module"; + python_plugin->status = PLUGIN_INITTAB_FAILED; + break; + } + } + } + } + if (!python_plugin->usable()) { + return nullptr; + } return python_plugin; } diff --git a/core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc b/core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc new file mode 100644 index 0000000..c41a88b --- /dev/null +++ b/core/wasm_shims/pythonplugin/python_plugin_inittab_probe_main.cc @@ -0,0 +1,39 @@ +#include "pythonplugin/python_plugin.hh" + +namespace { + +PyObject *init_bad_module() { + return nullptr; +} + +} // namespace + +// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc +// PythonPlugin::PythonPlugin(struct _inittab*) reports PLUGIN_INITTAB_FAILED +// when a built-in Python module from the first caller's inittab cannot be +// initialized; instantiate() then returns nullptr for the unusable plugin. +int main() { + _inittab bad_modules[] = { + {"cnc_sim_bad_module", init_bad_module}, + {nullptr, nullptr}, + }; + + PythonPlugin *plugin = PythonPlugin::instantiate(bad_modules); + if (plugin != nullptr) { + return 1; + } + if (python_plugin == nullptr) { + return 2; + } + if (python_plugin->plugin_status() != PLUGIN_INITTAB_FAILED) { + return 3; + } + if (python_plugin->usable()) { + return 4; + } + if (python_plugin->last_errmsg() != "failed to initialize built-in module") { + return 5; + } + + return 0; +} diff --git a/docs/linuxcnc-wasm-shims-source-map.md b/docs/linuxcnc-wasm-shims-source-map.md index d3a29c8..41abbd6 100644 --- a/docs/linuxcnc-wasm-shims-source-map.md +++ b/docs/linuxcnc-wasm-shims-source-map.md @@ -61,8 +61,10 @@ only; it must not add CNC behavior and must not expand the temporary smoke parse `python_plugin.cc` link expectations; it must not call into CPython runtime. - `core/wasm_shims/pythonplugin/python_plugin.cc` must preserve LinuxCNC `PythonPlugin` status edges for `PLUGIN_NO_SECTION`, `PLUGIN_BAD_INIFILE`, - `PLUGIN_BAD_PATH`, `PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, - and `PLUGIN_EXCEPTION` while keeping Python execution disabled. + `PLUGIN_BAD_PATH`, `PLUGIN_INITTAB_FAILED`, + `PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, and + `PLUGIN_EXCEPTION` while keeping Python execution disabled. +- The PythonPlugin shim must preserve the first-caller `_inittab` `PLUGIN_INITTAB_FAILED` edge from `src/emc/pythonplugin/python_plugin.cc` when a built-in module init function fails, while keeping browser-safe probes detached from real Python module execution. - The PythonPlugin shim must preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge from `src/emc/pythonplugin/python_plugin.cc` before reporting the browser-safe disabled Python runtime status. - The PythonPlugin shim must preserve `PATH_PREPEND` and `PATH_APPEND` tilde-expansion failure statuses from `src/emc/pythonplugin/python_plugin.cc` and `src/emc/ini/inifile.cc` without executing Python path mutation in browser-safe probes. - The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or real diff --git a/test-linuxcnc-wasm-python-plugin-link.sh b/test-linuxcnc-wasm-python-plugin-link.sh index a9c9ec9..027145d 100755 --- a/test-linuxcnc-wasm-python-plugin-link.sh +++ b/test-linuxcnc-wasm-python-plugin-link.sh @@ -6,7 +6,9 @@ cd "$(dirname "$0")" # LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and # python_plugin.hh define the PythonPlugin API covered by this wrapper. probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode python-plugin-link) +inittab_probe=$(./build-linuxcnc-wasm-standalone-probe.sh --mode python-plugin-inittab-link) "$probe" +"$inittab_probe" echo "linuxcnc wasm python_plugin link probe passed" diff --git a/test-native.sh b/test-native.sh index e0b3967..99de904 100755 --- a/test-native.sh +++ b/test-native.sh @@ -80,6 +80,7 @@ grep -F 'LinuxCNC source basis: this probe derives required Python C API exports grep -F 'LinuxCNC source basis: this probe compares wasm-safe tooldata runtime stubs' test-linuxcnc-wasm-tooldata-runtime-symbols.sh >/dev/null grep -F 'LinuxCNC source basis: the standalone probe links src/emc/rs274ngc/interp_base.cc' test-linuxcnc-wasm-interp-base-link.sh >/dev/null grep -F 'LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc and' test-linuxcnc-wasm-python-plugin-link.sh >/dev/null +grep -F 'python-plugin-inittab-link' test-linuxcnc-wasm-python-plugin-link.sh >/dev/null grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc is compiled directly' test-linuxcnc-wasm-rs274ngc-pre-object.sh >/dev/null grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides the Interp' test-linuxcnc-wasm-rs274ngc-pre-link.sh >/dev/null grep -F 'LinuxCNC source basis: src/emc/rs274ngc/rs274ngc_pre.cc provides read()' test-linuxcnc-wasm-runtime-link.sh >/dev/null