fix(doctrenderer): don't abort the process when a script fails to compile - #138
Conversation
…pile CCacheDataScript::Compile()'s cache-generation path called ToLocalChecked() on the result of ScriptCompiler::Compile() without checking it first, unlike the three sibling compile paths in the same function. A JS syntax error therefore became a V8 CHECK failure -- an abort (SIGILL/SIGTRAP) whose only trace is "Fatal error in v8::ToLocalChecked / Empty MaybeLocal", with the underlying SyntaxError never reported anywhere. Two changes: - Check the MaybeLocal before unwrapping it and return the empty script, so the caller's CJSTryCatch surfaces the real error instead of the process dying. - Create the .cache file only once CreateCodeCache() has actually produced data. Creating it up front left a zero-length .cache behind whenever the compile aborted; the next run then took the Exists(Path) branch and handed that empty buffer to kConsumeCodeCache. Surfaced by Euro-Office/sdkjs#80, where Terser-emitted supplementary-plane identifiers made the sdkjs bundles unparsable for this no-ICU V8 build (see Common/3dParty/v8/tools/8.9/*/nc-build.sh, v8_enable_i18n_support=false). The bundles are fixed on the sdkjs side; this makes any future occurrence diagnosable rather than fatal. The pre-V8-8.9 branch (#else) carries the same unguarded pattern. It is not built by this repo's toolchain, so it is left untouched rather than changed without a way to compile-test it. Assisted-by: ClaudeCode:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
|
TL;DR: Fix is correct and safe — verified the caller graph, the Full reviewWhat's right
What's missing
v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();Same unguarded pattern, same abort. Reachable via Minor, non-blocking
Cross-checked with an independent cold read against source (not just the diff); both passes converged on the caller-safety conclusion and independently landed on the |
Review of #138 (thanks @moodyjmz) pointed out that CJSContext::generateSnapshot() carries the same unguarded ToLocalChecked() this PR fixed in CCacheDataScript::Compile(), and compiles the very same GetAllScript() bundle via GenerateEditorSnapshot(). V8_SUPPORT_SNAPSHOTS is defined unconditionally (DesktopEditor/doctrenderer/CMakeLists.txt:236), so it is live code and the process would still abort on the same class of input. Not applied as the suggested early return, though: returning from inside that block leaves the scope without ever calling SetDefaultContext(), and destroys the SnapshotCreator without a blob having been created -- which its destructor expects. The compile result is carried in bCompiled instead, the creator's lifecycle is completed either way, and the snapshot file is simply not written when the script did not compile. Writing one built from a context the script never ran in would be worse than writing none, since the next start would consume it happily. Assisted-by: ClaudeCode:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
|
Thanks for the fast follow-up — the One thing left before this fully closes the loop, though:
And the caller drops the ball too: Net effect: a bad bundle now produces zero diagnostic output and a silently-missing snapshot, instead of the SIGTRAP abort. That's a real improvement (no more process death), but it's the opposite of the PR's stated goal — "the underlying SyntaxError never reported anywhere" is still true here, just without the crash to flag that something went wrong. Suggested fix: call |
…wing them Follow-up to the review of #138 (thanks @moodyjmz). The previous commit stopped generateSnapshot() aborting the process, but traded the crash for silence: CV8TryCatch's destructor is empty (v8_base.h:811), so a caught exception is discarded unless Check() is called, and nothing called it here. A bundle the engine could not parse produced no diagnostic at all and a quietly missing snapshot -- the opposite of this PR's stated goal. - v8_base.cpp: call try_catch.Check() after the compile/run block, while the Context::Scope is still alive. It prints the message, line and stack trace to stderr exactly as the cache path does via runScript(), and is a no-op when nothing was caught. It deliberately does not feed back into bCompiled. A runtime throw out of Run() did not stop the snapshot being written before this PR, and silently changing that could withhold snapshots that are fine today; only a compile failure suppresses the write. - doctrenderer.cpp: the caller discarded GenerateEditorSnapshot()'s bool, which was defensible while failure meant the process died anyway. Now that it survives, a failure means the editor runs without its snapshot, so log it. Assisted-by: ClaudeCode:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Christoph Schaefer <christoph.schaefer@nextcloud.com>
|
Verified One thing worth a note for whoever eventually tests this rather than a blocker: No other findings. LGTM. |
moodyjmz
left a comment
There was a problem hiding this comment.
Reviewed across three iterations (crash fix, snapshot-path guard, exception reporting) — verified each against source, not just commit messages. No open findings.
CCacheDataScript::Compile()'s cache-generation path called ToLocalChecked() on the result of ScriptCompiler::Compile() without checking it first, unlike the three sibling compile paths in the same function. A JS syntax error therefore became a V8 CHECK failure -- an abort (SIGILL/SIGTRAP) whose only trace is "Fatal error in v8::ToLocalChecked / Empty MaybeLocal", with the underlying SyntaxError never reported anywhere.
Two changes:
Surfaced by Euro-Office/sdkjs#80, where Terser-emitted supplementary-plane identifiers made the sdkjs bundles unparsable for this no-ICU V8 build (see Common/3dParty/v8/tools/8.9/*/nc-build.sh, v8_enable_i18n_support=false). The bundles are fixed on the sdkjs side; this makes any future occurrence diagnosable rather than fatal.
The pre-V8-8.9 branch (#else) carries the same unguarded pattern. It is not built by this repo's toolchain, so it is left untouched rather than changed without a way to compile-test it.
Assisted-by: ClaudeCode:claude-opus-5