Compare commits

...

5 Commits

Author SHA1 Message Date
cnc
c81f9d0111 按源验证API百分号文件模式
源依据:LinuxCNC src/emc/rs274ngc/rs274ngc_pre.cc 的 Interp::open() 在首个非空行是 % 时启用 percent-file mode,src/emc/rs274ngc/interp_read.cc read_text() 在匹配 % 处结束读取并忽略后续文本。

结论:native API source-linked smoke 增加 CNC_SIM_RS274_FILE_MODE 下的百分号文件断言,确认 cnc_sim API 文件模式走 LinuxCNC open/read 语义而非逐行桥接或 smoke fallback。

检查:./test-linuxcnc-api-native.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 13:10:02 +08:00
cnc
c75eec1057 按源验证刀库DB参数拆分
源依据:LinuxCNC src/emc/tooldata/tooldata_db.cc 的 tooldata_db_init() 使用 strtok_r 原地拆分 progname_plus_args,再检查 argv[0] 是否可执行。

结论:wasm-safe tooldata runtime probe 增加多参数输入副作用断言,确认缺失 DB 程序返回失败前仍保留 LinuxCNC 的参数拆分边界,减少外部 tooldata DB 替换风险。

检查:./test-linuxcnc-wasm-tooldata-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 13:03:48 +08:00
cnc
23e67bb6cd 按源验证非随机刀表保存边界
源依据:LinuxCNC src/emc/tooldata/tooldata_common.cc 的 tooldata_save() 在 DB_NOTUSED 下按 is_random_toolchanger 选择起始 pocket;非随机换刀器从 pocket 1 开始写出,跳过 spindle pocket 0。

结论:wasm-safe tooldata_common standalone probe 增加非随机保存断言,确认 pocket 0 的 spindle-only 数据不会写入普通刀表,同时 pocket 1 的真实刀具仍会保存,减少 tooldata_common/mmap 后端替换风险。

检查:./test-linuxcnc-wasm-tooldata-common-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:58:57 +08:00
cnc
42e5b0784d 按源验证Python插件路径节边界
源依据:LinuxCNC src/emc/pythonplugin/python_plugin.cc 的 configure() 按调用方 section 查找 TOPLEVEL,但 PATH_APPEND 固定从 PYTHON section 逐项读取并在 tilde expansion 失败时返回 PLUGIN_EXCEPTION_DURING_PATH_APPEND。

结论:wasm-safe python_plugin standalone probe 增加 REMAP/PYTHON section 分离边界,确认禁用 Python 时仍保留 LinuxCNC 的 INI 查找顺序和失败状态,不引入浏览器端 Python 执行。

检查:./test-linuxcnc-wasm-python-plugin-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:53:40 +08:00
cnc
64f336d6f2 按源验证tooldata低索引写入
源依据:LinuxCNC src/emc/tooldata/tooldata_mmap.cc 的 tooldata_put() 仅在 idx > last_index 时返回 IDX_NEW 并更新 last_index,否则返回 IDX_OK。

结论:wasm-safe tooldata mmap probe 增加低于当前 last_index 的写入断言,确认返回 IDX_OK、last_index 不变且数据可读回,减少 tooldata_mmap 后端替换风险。

检查:./test-linuxcnc-wasm-tooldata-link.sh;./test-native.sh;./test-linuxcnc-source-link.sh。
2026-06-06 12:48:33 +08:00
4 changed files with 148 additions and 0 deletions

View File

@@ -303,6 +303,45 @@ int main() {
}
}
{
std::vector<CncSimEvent> percent_events;
CncSimHandle *percent_sim = cnc_sim_create();
cnc_sim_set_event_callback(percent_sim, collect_event, &percent_events);
ScopedEnv file_mode_env("CNC_SIM_RS274_FILE_MODE", "1");
const char percent_program[] =
"%\n"
"G21 G90\n"
"F100\n"
"G1 X1\n"
"%\n"
"G1 X99\n";
bool percent_ok = true;
percent_ok &= expect(cnc_sim_parse_program(percent_sim,
percent_program,
sizeof(percent_program) - 1) == 0,
cnc_sim_last_error(percent_sim));
std::vector<int> feed_lines;
std::vector<double> feed_x;
for (const auto &event : percent_events) {
if (event.type == CNC_SIM_EVENT_LINEAR_FEED) {
feed_lines.push_back(event.line);
feed_x.push_back(event.end.x);
}
}
// LinuxCNC source basis: rs274ngc_pre.cc open() enables percent-file
// mode when the first nonblank line is '%'; interp_read.cc read_text()
// stops at the matching '%', so later program text is ignored.
percent_ok &= expect(feed_lines.size() == 1 &&
feed_lines[0] == 4 &&
feed_x.size() == 1 &&
near(feed_x[0], 1.0),
"expected LinuxCNC percent-file mode to ignore trailing text");
cnc_sim_destroy(percent_sim);
if (!percent_ok) {
return 1;
}
}
const char config[] =
"{\"backend\":\"linuxcnc-rs274\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}";
const char program[] =

View File

@@ -198,6 +198,44 @@ int main() {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(path_append_ini);
const char *section_path_append_ini = "/tmp/cnc_sim_python_plugin_probe_section_path_append.ini";
{
FILE *file = std::fopen(section_path_append_ini, "w");
if (!file) {
std::remove(ini);
return 50;
}
std::fputs("[REMAP]\n"
"[PYTHON]\nTOPLEVEL = /tmp/cnc_sim_missing_python_section_toplevel.py\n"
"PATH_APPEND = ~/linuxcnc-python\n",
file);
std::fclose(file);
}
unsetenv("HOME");
// LinuxCNC source basis: python_plugin.cc configure() looks up TOPLEVEL in
// the caller-provided section, but PATH_APPEND entries are read from the
// fixed PYTHON section before initialize(); a PYTHON TOPLEVEL is ignored
// when the caller configures a different section.
if (plugin->configure(section_path_append_ini, "REMAP") != PLUGIN_EXCEPTION_DURING_PATH_APPEND) {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(section_path_append_ini);
return 51;
}
if (plugin->last_errmsg() != "bad path append") {
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(ini);
std::remove(section_path_append_ini);
return 52;
}
if (saved_home) {
setenv("HOME", saved_home_value.c_str(), 1);
}
std::remove(section_path_append_ini);
const char *plain_path_ini = "/tmp/cnc_sim_python_plugin_probe_plain_path.ini";
{
FILE *file = std::fopen(plain_path_ini, "w");

View File

@@ -123,6 +123,47 @@ int main() {
nonrandom_spindle.offset.tran.z != 4.25) {
return 20;
}
CANON_TOOL_TABLE spindle_only = tooldata_entry_init();
spindle_only.toolno = 99;
spindle_only.pocketno = 0;
spindle_only.offset.tran.z = 9.0;
if (tooldata_put(spindle_only, 0) != IDX_OK) {
return 21;
}
CANON_TOOL_TABLE saved_nonspindle = tooldata_entry_init();
saved_nonspindle.toolno = 7;
saved_nonspindle.pocketno = 5;
saved_nonspindle.offset.tran.z = 4.25;
if (tooldata_put(saved_nonspindle, 1) == IDX_FAIL) {
return 26;
}
std::string nonrandom_save_template = make_temp_template("cnc_tooldata_common_nonrandom_save_XXXXXX");
std::vector<char> nonrandom_save_path(nonrandom_save_template.begin(), nonrandom_save_template.end());
nonrandom_save_path.push_back('\0');
const int nonrandom_save_fd = mkstemp(nonrandom_save_path.data());
if (nonrandom_save_fd < 0) {
return 22;
}
close(nonrandom_save_fd);
// LinuxCNC source basis: src/emc/tooldata/tooldata_common.cc saves
// nonrandom tool tables starting at pocket 1, so spindle pocket 0 is not
// emitted unless DB_ACTIVE selects the one-entry spindle save path.
if (tooldata_save(nonrandom_save_path.data()) != 0) {
std::remove(nonrandom_save_path.data());
return 23;
}
FILE *nonrandom_save_fp = std::fopen(nonrandom_save_path.data(), "r");
if (!nonrandom_save_fp) {
std::remove(nonrandom_save_path.data());
return 24;
}
char nonrandom_saved[CANON_TOOL_ENTRY_LEN * 2] = {};
std::fread(nonrandom_saved, 1, sizeof(nonrandom_saved) - 1, nonrandom_save_fp);
std::fclose(nonrandom_save_fp);
std::remove(nonrandom_save_path.data());
if (std::strstr(nonrandom_saved, "T99") || !std::strstr(nonrandom_saved, "T7")) {
return 25;
}
tooldata_init(true);
tooldata_set_db(DB_ACTIVE);

View File

@@ -203,6 +203,19 @@ int main() {
if (separator == std::string::npos || missing_db_program_with_arg[separator] != 0) {
return 56;
}
char missing_db_program_with_args[] = "/tmp/cnc_tooldata_probe_missing_db_program alpha beta";
if (tooldata_db_init(missing_db_program_with_args, 0) != -1) {
return 83;
}
// LinuxCNC src/emc/tooldata/tooldata_db.cc tokenizes progname_plus_args
// in place with strtok_r() before checking whether argv[0] is executable.
if (missing_db_program_with_args[42] != 0 ||
missing_db_program_with_args[48] != 0 ||
std::strcmp(missing_db_program_with_args, "/tmp/cnc_tooldata_probe_missing_db_program") != 0 ||
std::strcmp(missing_db_program_with_args + 43, "alpha") != 0 ||
std::strcmp(missing_db_program_with_args + 49, "beta") != 0) {
return 84;
}
std::string missing_db_program_string = make_temp_path("cnc_tooldata_probe_missing_db_program");
std::vector<char> missing_db_program(
missing_db_program_string.begin(),
@@ -272,6 +285,23 @@ int main() {
if (tooldata_put(tool, 12) != IDX_NEW) {
return 2;
}
CANON_TOOL_TABLE lower_index_tool = tooldata_entry_init();
lower_index_tool.toolno = 3;
lower_index_tool.pocketno = 5;
// LinuxCNC src/emc/tooldata/tooldata_mmap.cc returns IDX_OK for writes at
// or below the current last_index and leaves last_index unchanged.
if (tooldata_put(lower_index_tool, 5) != IDX_OK) {
return 80;
}
if (tooldata_last_index_get() != 12) {
return 81;
}
CANON_TOOL_TABLE lower_index_loaded = tooldata_entry_init();
if (tooldata_get(&lower_index_loaded, 5) != IDX_OK ||
lower_index_loaded.toolno != 3 ||
lower_index_loaded.pocketno != 5) {
return 82;
}
CANON_TOOL_TABLE loaded = tooldata_entry_init();
if (tooldata_get(&loaded, 12) != IDX_OK) {