#!/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 '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 '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_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 '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 '#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", ], } 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") gettext_shim = Path("core/wasm_shims/gettext_shim.cc").read_text(encoding="utf-8") for needle in [ "extern \"C\" char *gettext(const char *msgid)", "return const_cast(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(this), 0, static_cast(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 [ '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", "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_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 [ "Py_IsInitialized", "PyObject_CallMethod", "Py_BuildValue", "PyModule_Create", "PyType_Ready", ]: 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(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 `_()` 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", "must not switch browser-safe probes into task mode", "must not become a shell expansion feature", "must not become Python execution", "keeping Python execution disabled", "must not execute `bp::exec`, `bp::exec_file`, or", "inactive-DB return values", "negative-status short-circuit behavior", "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