Files
wasm-simulator/check-linuxcnc-wasm-shims-source-map.sh

476 lines
24 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: this guard ties the documented wasm-safe shim map to
# the linuxcnc_wasm_safe_probe_shims CMake set, the RS274/WASM source manifest,
# and the LinuxCNC sources each shim cites. It validates source coverage only;
# it does not add CNC behavior, expand smoke parsing, or interpret G-code.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
manifest=${1:-linuxcnc-rs274-wasm-source-files.txt}
LINUXCNC_ROOT="$linuxcnc_root" ./check-linuxcnc-inputs-cached.sh "$manifest"
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
grep -F 'LinuxCNC source basis: wasm-safe shim probe sources are limited to the' \
list-linuxcnc-wasm-safe-shims.sh >/dev/null
grep -F 'set(linuxcnc_wasm_safe_probe_shims' core/CMakeLists.txt >/dev/null
grep -F 'LinuxCNC source basis: this guard ties the documented WASM blocker map' \
check-linuxcnc-wasm-blockers-source-map.sh >/dev/null
grep -F 'dlopen(NULL, RTLD_GLOBAL);' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'interp_lib = dlopen(interp_path, RTLD_NOW);' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'Constructor constructor = (Constructor)dlsym(interp_lib, "makeInterp");' \
"$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F 'dlerror()' "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" >/dev/null
grep -F '#include "libintl.h"' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F '#define _(s) gettext(s)' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.hh" >/dev/null
grep -F 'CHKS((!_readers[(int)' "$linuxcnc_root/src/emc/rs274ngc/interp_internal.cc" >/dev/null
grep -F 'Cannot use polar coordinate on a machine lacking X or Y axes' \
"$linuxcnc_root/src/emc/rs274ngc/interp_internal.cc" >/dev/null
grep -F 'int _task = 0;' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'int _task = 1;' "$linuxcnc_root/src/emc/task/emctaskmain.cc" >/dev/null
grep -F '{ "interpreter", PyInit_interpreter },' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F '{ "emccanon", PyInit_emccanon },' "$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'struct _inittab builtin_modules[]' "$linuxcnc_root/src/emc/task/taskclass.cc" >/dev/null
grep -F 'int Interp::fetch_hal_param(' "$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_pin_value_by_name(hal_name, &type, &ptr, &conn)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_signal_value_by_name(hal_name, &type, &ptr, &conn)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'hal_get_param_value_by_name(hal_name, &type, &ptr)' \
"$linuxcnc_root/src/emc/rs274ngc/interp_namedparams.cc" >/dev/null
grep -F 'HAL_TYPE_UNINITIALIZED = 0,' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_pin_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_signal_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'extern int hal_get_param_value_by_name(' "$linuxcnc_root/include/hal.h" >/dev/null
grep -F 'int hal_get_pin_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'int hal_get_signal_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'int hal_get_param_value_by_name(' "$linuxcnc_root/src/hal/hal_lib.c" >/dev/null
grep -F 'wordexp(basename, &exp_result, 0);' "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordexp(settings->program_prefix, &exp_result, 0);' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordexp(settings->subroutines[dct], &exp_result, 0);' \
"$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F 'wordfree(&exp_result);' "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" >/dev/null
grep -F '#include "nml_intf/emc_nml.hh"' "$linuxcnc_root/src/emc/sai/dummyemcstat.cc" >/dev/null
grep -F 'EMC_STAT *emcStatus = new EMC_STAT;' "$linuxcnc_root/src/emc/sai/dummyemcstat.cc" >/dev/null
grep -F 'int NMLmsg::automatically_clear = 1;' \
"$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'NMLmsg::NMLmsg' "$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'void NMLmsg::clear()' "$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'memset((void *) this, 0, size);' \
"$linuxcnc_root/src/libnml/nml/nmlmsg.cc" >/dev/null
grep -F 'RCS_STAT_MSG::RCS_STAT_MSG' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'command_type = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'echo_serial_number = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'status = RCS_STATUS::UNINITIALIZED;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F '_state = -1;' "$linuxcnc_root/src/libnml/nml/stat_msg.cc" >/dev/null
grep -F 'extern int set_print_rcs_error_info(const char *file, int line);' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F 'extern int print_rcs_error_new(const char *_fmt, ...)' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F '#define rcs_print_error set_print_rcs_error_info( __FILE__, __LINE__); print_rcs_error_new' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.hh" >/dev/null
grep -F 'int set_print_rcs_error_info(const char *file, int line)' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'print_rcs_error_new' "$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'retval = rcs_vprint(_fmt, args, 1);' \
"$linuxcnc_root/src/libnml/rcs/rcs_print.cc" >/dev/null
grep -F 'extern int rtapi_snprintf(char *buf, unsigned long int size,' \
"$linuxcnc_root/src/rtapi/rtapi.h" >/dev/null
grep -F 'extern int rtapi_vsnprintf(char *buf, unsigned long size,' \
"$linuxcnc_root/src/rtapi/rtapi.h" >/dev/null
grep -F 'int rtapi_snprintf(char *buffer, unsigned long int size, const char *msg, ...) {' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'result = vsnprintf(buffer, size, msg, args);' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'int rtapi_vsnprintf(char *buffer, unsigned long int size, const char *fmt,' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
grep -F 'return vsnprintf(buffer, size, fmt, args);' \
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
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_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 '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
grep -F 'if (realpath(toplevel, real_path) == NULL) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_BAD_PATH;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'while (auto inistring = inifile.findString(n, "PATH_PREPEND", "PYTHON")) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'while (auto inistring = inifile.findString(n, "PATH_APPEND", "PYTHON")) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_PREPEND;' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'status = PLUGIN_EXCEPTION_DURING_PATH_APPEND;' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'int IniFile::tildeExpand(const std::string &path, std::string &result)' \
"$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F "if(!home)" "$linuxcnc_root/src/emc/ini/inifile.cc" >/dev/null
grep -F 'retval = bp::exec_file(cmd, main_namespace, main_namespace);' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'retval = bp::exec(cmd, main_namespace, main_namespace);' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'PyObject *rv = PyObject_Call(function.ptr(), tupleargs.ptr(), kwargs.ptr());' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F 'PyErr_Fetch(&exc,&val,&tb);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_python.cc" >/dev/null
grep -F 'PyErr_NormalizeException(&exc,&val,&tb);' \
"$linuxcnc_root/src/emc/rs274ngc/interp_python.cc" >/dev/null
grep -F 'if (Py_IsInitialized()) {' \
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
grep -F '#define callmethod(o, m, f, ...) PyObject_CallMethod((o), (char*)(m), (char*)(f), ## __VA_ARGS__)' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject *result = PyObject_GetAttrString(callback, "parameter_file");' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyErr_Format(PyExc_TypeError,' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'return Py_BuildValue("[ddd][ddd][ddd][ddd]",' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject *m = PyModule_Create(&gcode_moduledef);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyType_Ready(&LineCodeType);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyModule_AddObject(m, "linecode", (PyObject*)&LineCodeType);' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F 'PyObject_SetAttrString(m, "MAX_ERROR", PyLong_FromLong(maxerror));' \
"$linuxcnc_root/src/emc/rs274ngc/gcodemodule.cc" >/dev/null
grep -F '#define TOOL_MMAP_FILENAME ".tool.mmap"' "$linuxcnc_root/src/emc/tooldata/tooldata_mmap.cc" >/dev/null
grep -F 'int tool_nml_register(CANON_TOOL_TABLE *tblptr)' "$linuxcnc_root/src/emc/tooldata/tooldata_nml.cc" >/dev/null
grep -F 'int tooldata_db_init(char progname_plus_args[]' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) {return -1;}' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
grep -F 'if (!db_live) return 0;' "$linuxcnc_root/src/emc/tooldata/tooldata_db.cc" >/dev/null
python3 - "$manifest" <<'PY'
from pathlib import Path
import re
import subprocess
import sys
manifest = Path(sys.argv[1])
source_map = Path("docs/linuxcnc-wasm-shims-source-map.md").read_text(encoding="utf-8")
manifest_text = manifest.read_text(encoding="utf-8")
shim_paths = [
line for line in subprocess.check_output(["./list-linuxcnc-wasm-safe-shims.sh"], text=True).splitlines()
if line
]
if len(shim_paths) != 11:
raise SystemExit(f"expected 11 wasm-safe CMake shim sources, found {len(shim_paths)}")
if f"| CMake `linuxcnc_wasm_safe_probe_shims` sources | {len(shim_paths)} |" not in source_map:
raise SystemExit("docs/linuxcnc-wasm-shims-source-map.md shim count mismatch")
expected_shims = {
"core/wasm_shims/dlfcn.cc": ["src/emc/rs274ngc/interp_base.cc"],
"core/wasm_shims/emc_status_shim.cc": ["src/emc/sai/dummyemcstat.cc"],
"core/wasm_shims/gettext_shim.cc": ["src/emc/rs274ngc/interp_internal.hh"],
"core/wasm_shims/linuxcnc_runtime_shim.cc": [
"src/emc/rs274ngc/gcodemodule.cc",
"src/emc/task/emctaskmain.cc",
"src/emc/task/taskclass.cc",
"src/emc/rs274ngc/interp_namedparams.cc",
"include/hal.h",
"src/hal/hal_lib.c",
"src/emc/rs274ngc/rs274ngc_pre.cc",
],
"core/wasm_shims/nml_status_shim.cc": [
"src/libnml/nml/nmlmsg.cc",
"src/libnml/nml/stat_msg.cc",
],
"core/wasm_shims/python_c_api_shim.cc": [
"src/emc/rs274ngc/interp_python.cc",
"src/emc/rs274ngc/interp_namedparams.cc",
"src/emc/rs274ngc/gcodemodule.cc",
"src/emc/pythonplugin/python_plugin.cc",
],
"core/wasm_shims/rcs_print_shim.cc": [
"src/libnml/rcs/rcs_print.cc",
"src/libnml/rcs/rcs_print.hh",
],
"core/wasm_shims/rtapi_compat.cc": [
"src/rtapi/uspace_common.h",
"src/rtapi/rtapi.h",
],
"core/wasm_shims/tooldata/tooldata_mmap_backend.cc": [
"src/emc/tooldata/tooldata_mmap.cc",
],
"core/wasm_shims/tooldata/tooldata_runtime_stubs.cc": [
"src/emc/tooldata/tooldata_nml.cc",
"src/emc/tooldata/tooldata_db.cc",
],
"core/wasm_shims/pythonplugin/python_plugin.cc": [
"src/emc/pythonplugin/python_plugin.cc",
"src/emc/pythonplugin/python_plugin.hh",
"src/emc/ini/inifile.cc",
],
}
if shim_paths != list(expected_shims):
raise SystemExit("wasm-safe shim CMake order no longer matches docs/linuxcnc-wasm-shims-source-map.md")
for shim_path, linuxcnc_sources in expected_shims.items():
shim_text = Path(shim_path).read_text(encoding="utf-8")
if "LinuxCNC source basis:" not in shim_text:
raise SystemExit(f"{shim_path} is missing a LinuxCNC source basis comment")
if f"| `{shim_path}` |" not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing shim row: {shim_path}")
for source in linuxcnc_sources:
if source not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing source {source} for {shim_path}")
if source not in manifest_text:
raise SystemExit(f"{source} is not tracked in {manifest}")
runtime_shim = Path("core/wasm_shims/linuxcnc_runtime_shim.cc").read_text(encoding="utf-8")
runtime_probe = Path("core/wasm_shims/cnc_sim_wasm_runtime_probe_main.cc").read_text(encoding="utf-8")
dlfcn_shim = Path("core/wasm_shims/dlfcn.cc").read_text(encoding="utf-8")
dlfcn_header = Path("core/wasm_shims/dlfcn.h").read_text(encoding="utf-8")
emc_status_shim = Path("core/wasm_shims/emc_status_shim.cc").read_text(encoding="utf-8")
gettext_shim = Path("core/wasm_shims/gettext_shim.cc").read_text(encoding="utf-8")
for needle in [
"#define RTLD_GLOBAL 0x00100",
"#define RTLD_NOW 0x00002",
"void *dlopen(const char *filename, int flags);",
"void *dlsym(void *handle, const char *symbol);",
"char *dlerror(void);",
"int dlclose(void *handle);",
]:
if needle not in dlfcn_header:
raise SystemExit(f"dlfcn shim header missing LinuxCNC loader boundary: {needle}")
for needle in [
"dynamic loading is unavailable in wasm-safe probe",
"void *dlopen(const char *, int)",
"return nullptr;",
"void *dlsym(void *, const char *)",
"char *dlerror(void)",
"return last_error;",
"int dlclose(void *)",
"return 0;",
]:
if needle not in dlfcn_shim:
raise SystemExit(f"dlfcn shim missing unavailable-loader boundary: {needle}")
for needle in [
'#include "nml_intf/emc_nml.hh"',
"EMC_STAT *emcStatus = new EMC_STAT;",
]:
if needle not in emc_status_shim:
raise SystemExit(f"EMC status shim missing detached LinuxCNC status singleton: {needle}")
for needle in [
"extern \"C\" char *gettext(const char *msgid)",
"return const_cast<char *>(msgid ? msgid : \"\");",
]:
if needle not in gettext_shim:
raise SystemExit(f"gettext shim missing diagnostic passthrough boundary: {needle}")
nml_status_shim = Path("core/wasm_shims/nml_status_shim.cc").read_text(encoding="utf-8")
for needle in [
"int NMLmsg::automatically_clear = 1;",
"void NMLmsg::clear()",
"std::memset(static_cast<void *>(this), 0, static_cast<size_t>(size));",
"command_type = -1;",
"echo_serial_number = -1;",
"status = RCS_STATUS::UNINITIALIZED;",
"_state = -1;",
]:
if needle not in nml_status_shim:
raise SystemExit(f"NML status shim missing LinuxCNC constructor boundary: {needle}")
for needle in [
"int _task = 0;",
'extern "C" PyObject *PyInit_interpreter(void)',
'extern "C" PyObject *PyInit_emccanon(void)',
'extern "C" PyObject *PyInit_gcode(void)',
"return nullptr;",
'struct _inittab builtin_modules[] = {',
'{"interpreter", PyInit_interpreter}',
'{"emccanon", PyInit_emccanon}',
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing preview/builtin module boundary: {needle}")
for needle in [
"std::string expand_home_directory(const char *words)",
"std::string expand_environment_variables(const std::string &words)",
"extern \"C\" int wordexp(const char *words, wordexp_t *pwordexp, int)",
"return WRDE_BADVAL;",
"return WRDE_NOSPACE;",
"pwordexp->we_wordc = 1;",
"pwordexp->we_offs = 0;",
"extern \"C\" void wordfree(wordexp_t *pwordexp)",
"pwordexp->we_wordc = 0;",
"pwordexp->we_wordv = nullptr;",
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing limited wordexp boundary: {needle}")
for needle in [
'extern "C" int hal_get_pin_value_by_name',
'extern "C" int hal_get_signal_value_by_name',
'extern "C" int hal_get_param_value_by_name',
"*type = HAL_TYPE_UNINITIALIZED;",
"*data = nullptr;",
"return -1;",
]:
if needle not in runtime_shim:
raise SystemExit(f"linuxcnc runtime shim missing HAL unavailable boundary: {needle}")
for needle in [
"bool hal_lookup_is_unavailable()",
'hal_get_pin_value_by_name("cnc-sim.missing-pin", &type, &data, &connected) != -1',
'hal_get_signal_value_by_name("cnc-sim.missing-signal", &type, &data, &has_writers) != -1',
'hal_get_param_value_by_name("cnc-sim.missing-param", &type, &data) != -1',
"type != HAL_TYPE_UNINITIALIZED",
"data != nullptr",
]:
if needle not in runtime_probe:
raise SystemExit(f"runtime probe missing HAL unavailable assertion: {needle}")
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_c_api_shim = Path("core/wasm_shims/python_c_api_shim.cc").read_text(encoding="utf-8")
for forbidden in [
"bp::exec(",
"bp::exec_file(",
"PyObject_Call(",
]:
if forbidden in python_plugin_shim:
raise SystemExit(f"PythonPlugin wasm shim must not execute Python: {forbidden}")
for needle in [
"python disabled in wasm-safe probe",
'find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)',
"realpath(toplevel.c_str(), resolved_path) == nullptr",
"status = PLUGIN_BAD_PATH;",
'check_python_path_entries(iniFilename,',
"PLUGIN_EXCEPTION_DURING_PATH_PREPEND",
"PLUGIN_EXCEPTION_DURING_PATH_APPEND",
"tilde_expand_path(entry, expanded)",
"status = PLUGIN_PYTHON_NOT_INITIALIZED;",
"return PLUGIN_NO_CALLABLE;",
"status = PLUGIN_EXCEPTION;",
"if (status < PLUGIN_OK)",
]:
if needle not in python_plugin_shim:
raise SystemExit(f"PythonPlugin wasm shim missing disabled-python boundary: {needle}")
for needle in [
"PLUGIN_NO_SECTION",
"PLUGIN_BAD_INIFILE",
"PLUGIN_BAD_PATH",
"PLUGIN_EXCEPTION_DURING_PATH_PREPEND",
"PLUGIN_EXCEPTION_DURING_PATH_APPEND",
"PLUGIN_PYTHON_NOT_INITIALIZED",
"PLUGIN_NO_CALLABLE",
"PLUGIN_EXCEPTION",
"handle_pyerror().empty()",
]:
if needle not in python_plugin_probe:
raise SystemExit(f"PythonPlugin probe missing disabled-python status assertion: {needle}")
for needle in [
"static PyTypeObject none_type = {\"NoneType\"};",
"static PyObject none_object = {&none_type};",
"PyObject *_Py_NoneStructPtr = &none_object;",
"int Py_IsInitialized(void)",
"return 1;",
"PyObject *PyErr_Occurred(void)",
"return nullptr;",
"void PyErr_Fetch(PyObject **exc, PyObject **val, PyObject **tb)",
"*exc = nullptr;",
"*val = nullptr;",
"*tb = nullptr;",
"int PyCallable_Check(PyObject *)",
"return 0;",
"PyObject *PyObject_CallMethod(PyObject *, char *, char *, ...)",
"PyObject *PyObject_GetAttrString(PyObject *, const char *)",
"return &none_object;",
"PyObject *Py_BuildValue(const char *, ...)",
"PyObject *PyModule_Create(PyModuleDef *)",
"int PyModule_AddObject(PyObject *, const char *, PyObject *)",
"int PyType_Ready(PyTypeObject *)",
"void *PyObject_NewShim(std::size_t size, PyTypeObject *type)",
"std::calloc(1, size)",
]:
if needle not in python_c_api_shim:
raise SystemExit(f"Python C API shim missing symbol boundary: {needle}")
rcs_print_shim = Path("core/wasm_shims/rcs_print_shim.cc").read_text(encoding="utf-8")
for needle in [
"set_print_rcs_error_info",
"print_rcs_error_new",
"std::vfprintf(stderr, format, args)",
]:
if needle not in rcs_print_shim:
raise SystemExit(f"RCS print shim missing split print-error boundary: {needle}")
rtapi_compat_shim = Path("core/wasm_shims/rtapi_compat.cc").read_text(encoding="utf-8")
for needle in [
"rtapi_snprintf",
"std::vsnprintf(buf, static_cast<std::size_t>(size), fmt, ap)",
]:
if needle not in rtapi_compat_shim:
raise SystemExit(f"RTAPI compatibility shim missing userspace snprintf boundary: {needle}")
for phrase in [
"must not add CNC behavior",
"must not expand the temporary smoke parser",
"CMake `linuxcnc_wasm_safe_probe_shims` set",
"RS274/WASM manifest",
"browser-safe LinuxCNC adaptations",
"LinuxCNC `interp_base.cc` dynamic loader calls",
"unavailable-loader returns",
"detached LinuxCNC `EMC_STAT` singleton",
"LinuxCNC `_()` diagnostic call sites",
"browser gettext catalogs unavailable",
"LinuxCNC NML status constructors",
"`RCS_STAT_MSG` default status fields",
"dynamic loading unavailable",
"unavailable HAL lookup",
"must leave HAL values unavailable",
"preview `_task=0`",
"builtin module stubs",
"must not switch browser-safe probes into task mode",
"single-word `wordexp()` path expansion",
"`wordfree()` cleanup",
"must not become a shell expansion feature",
"must not become Python execution",
"Python C API shim must return inert objects",
"must not call into CPython runtime",
"keeping Python execution disabled",
"must not execute `bp::exec`, `bp::exec_file`, or",
"inactive-DB return values",
"negative-status short-circuit behavior",
"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",
"without native print routing",
"LinuxCNC userspace `rtapi_snprintf()` formatting",
"`uspace_common.h` `vsnprintf()` implementation path",
"./check-linuxcnc-wasm-shims-source-map.sh",
"does not interpret G-code",
]:
if phrase not in source_map:
raise SystemExit(f"docs/linuxcnc-wasm-shims-source-map.md missing boundary phrase: {phrase}")
for entry in [
"test-native.sh",
"test-linuxcnc-source-link.sh",
"build-wasm.sh",
]:
text = Path(entry).read_text(encoding="utf-8")
if "./check-linuxcnc-wasm-shims-source-map.sh" not in text:
raise SystemExit(f"{entry} must run ./check-linuxcnc-wasm-shims-source-map.sh")
cmake_text = Path("core/CMakeLists.txt").read_text(encoding="utf-8")
for shim_path in shim_paths:
cmake_suffix = shim_path.removeprefix("core/")
if cmake_suffix not in cmake_text:
raise SystemExit(f"{shim_path} is not listed in linuxcnc_wasm_safe_probe_shims")
blocker_map = Path("docs/linuxcnc-wasm-blockers-source-map.md").read_text(encoding="utf-8")
for shim_path in [
"core/wasm_shims/linuxcnc_runtime_shim.cc",
"core/wasm_shims/python_c_api_shim.cc",
"core/wasm_shims/tooldata/tooldata_mmap_backend.cc",
]:
if shim_path not in blocker_map:
raise SystemExit(f"docs/linuxcnc-wasm-blockers-source-map.md no longer references {shim_path}")
PY