Environment
- treepplr 0.14.0
- tpplc 0.4 (aarch64-darwin)
Summary
treepplr:::compilation() treats only exit code 1 as a compilation failure:
res <- system(paste0("LD_LIBRARY_PATH= ", command), intern = FALSE)
if (res == 1L) {
stop("Compilation failed")
}
return(output_path)
But tpplc uses more than one nonzero exit code. Measured on 0.4:
| failure mode |
exit code |
executable produced |
caught by treepplr |
unknown symbol (symbolize error) |
1 |
no |
yes |
| syntax error |
1 |
no |
yes |
| invalid CLI option |
1 |
no |
yes |
fatal error, e.g. a missing import file |
2 |
no |
NO |
| success |
0 |
yes |
n/a |
So when tpplc dies with Fatal error: exception Sys_error(...) — which is what you get from an unresolvable import — tp_compile() returns a compiled_model_Template whose exe_path points at a file that was never created.
Reproducer
library(treepplr)
writeLines(c('import "does-not-exist.tppl"',
'model function m() => Real { return 1.; }'), "/tmp/f1.tppl")
writeLines("{}", "/tmp/f1data.json")
m <- tp_compile("/tmp/f1.tppl", method = "is")
#> Fatal error: exception Sys_error("/tmp/does-not-exist.tppl: No such file or directory")
# ... and yet:
class(m) #> "compiled_model_Template"
file.exists(m$exe_path) #> FALSE
tp_run(m, "/tmp/f1data.json")
#> sh: /.../<hash>.exe: No such file or directory
#> Warning message:
#> In system(command) : error in running command
#> list()
Why this is worse than a confusing error
The failure is silent in both stages:
tp_compile() reports success for a model that does not exist.
tp_run() on the phantom executable raises only a warning, not an error, and returns list() — an empty list.
Downstream, out[[1]]$normConst then fails with a subscript error far from the real cause, and any code that iterates over the result (for (s in out) ..., lapply(out, ...)) silently does nothing at all and reports no problem.
This is not hypothetical. I hit it while porting a model whose import paths were stale relative to the installed compiler, and briefly believed I had compiled and run something I had not. Any workflow that compiles several model variants in one session and compares their numeric output is exposed to this: one variant fails to build, and the comparison quietly proceeds with a missing or stale result. My scripts now defensively assert file.exists(sampler$exe_path) after every tp_compile(), which should not be the caller's job.
Suggested fix
Two independent guards, both cheap:
res <- system(paste0("LD_LIBRARY_PATH= ", command), intern = FALSE)
if (res != 0L) {
stop("Compilation failed (tpplc exit status ", res, "): ", command)
}
if (!file.exists(output_path)) {
stop("Compilation reported success but produced no executable at ", output_path)
}
Checking res != 0L covers every current and future nonzero code; the file.exists() check is belt-and-braces in case tpplc ever exits 0 without emitting a binary. Including the exit status and the command in the error message would also help a lot here — see also #24 on surfacing tpplc's own stderr rather than a bare "Compilation failed".
Environment
Summary
treepplr:::compilation()treats only exit code 1 as a compilation failure:But
tpplcuses more than one nonzero exit code. Measured on 0.4:symbolizeerror)importfileSo when
tpplcdies withFatal error: exception Sys_error(...)— which is what you get from an unresolvableimport—tp_compile()returns acompiled_model_Templatewhoseexe_pathpoints at a file that was never created.Reproducer
Why this is worse than a confusing error
The failure is silent in both stages:
tp_compile()reports success for a model that does not exist.tp_run()on the phantom executable raises only a warning, not an error, and returnslist()— an empty list.Downstream,
out[[1]]$normConstthen fails with a subscript error far from the real cause, and any code that iterates over the result (for (s in out) ...,lapply(out, ...)) silently does nothing at all and reports no problem.This is not hypothetical. I hit it while porting a model whose
importpaths were stale relative to the installed compiler, and briefly believed I had compiled and run something I had not. Any workflow that compiles several model variants in one session and compares their numeric output is exposed to this: one variant fails to build, and the comparison quietly proceeds with a missing or stale result. My scripts now defensively assertfile.exists(sampler$exe_path)after everytp_compile(), which should not be the caller's job.Suggested fix
Two independent guards, both cheap:
Checking
res != 0Lcovers every current and future nonzero code; thefile.exists()check is belt-and-braces in casetpplcever exits 0 without emitting a binary. Including the exit status and the command in the error message would also help a lot here — see also #24 on surfacingtpplc's own stderr rather than a bare"Compilation failed".