Surface JavaScript file evaluation exceptions to native callers - #225
jeremytondo wants to merge 1 commit into
Conversation
Greptile SummaryThis PR makes file-based JavaScript evaluation failures visible to native callers by retaining the JavaScript exception and throwing an
Confidence Score: 4/5The PR appears safe to merge after addressing the non-blocking stale exception-state issue. Native exception propagation and recovery are covered, but a failed file evaluation leaves Files Needing Attention: Hammerspoon 2/Engine/JSEngine.swift Important Files Changed
Prompt To Fix All With AI### Issue 1
Hammerspoon 2/Engine/JSEngine.swift:163-168
**Exception state remains stale**
After converting the JavaScript exception into an `NSError`, this path throws without clearing `context.exception`. A later successful script that checks this shared slot, such as the docs module, can therefore report the previous configuration failure as its own. The stale exception can also escape a nested module evaluation whose Swift error was intentionally swallowed. Preserve the message and stack locally, then clear the slot before throwing.
```suggestion
if let exception = context?.exception {
let message = exception.toString() ?? "JavaScript error"
let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? ""
context?.exception = nil
throw NSError(domain: "Hammerspoon.Configuration", code: 1,
userInfo: [NSLocalizedDescriptionKey: "\(url.path): \(message)\n\(stack)"])
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "Surface JavaScript file evaluation excep..." | Re-trigger Greptile |
| if let exception = context?.exception { | ||
| let message = exception.toString() ?? "JavaScript error" | ||
| let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? "" | ||
| throw NSError(domain: "Hammerspoon.Configuration", code: 1, | ||
| userInfo: [NSLocalizedDescriptionKey: "\(url.path): \(message)\n\(stack)"]) | ||
| } |
There was a problem hiding this comment.
After converting the JavaScript exception into an NSError, this path throws without clearing context.exception. A later successful script that checks this shared slot, such as the docs module, can therefore report the previous configuration failure as its own. The stale exception can also escape a nested module evaluation whose Swift error was intentionally swallowed. Preserve the message and stack locally, then clear the slot before throwing.
| if let exception = context?.exception { | |
| let message = exception.toString() ?? "JavaScript error" | |
| let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? "" | |
| throw NSError(domain: "Hammerspoon.Configuration", code: 1, | |
| userInfo: [NSLocalizedDescriptionKey: "\(url.path): \(message)\n\(stack)"]) | |
| } | |
| if let exception = context?.exception { | |
| let message = exception.toString() ?? "JavaScript error" | |
| let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? "" | |
| context?.exception = nil | |
| throw NSError(domain: "Hammerspoon.Configuration", code: 1, | |
| userInfo: [NSLocalizedDescriptionKey: "\(url.path): \(message)\n\(stack)"]) | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: Hammerspoon 2/Engine/JSEngine.swift
Line: 163-168
Comment:
**Exception state remains stale**
After converting the JavaScript exception into an `NSError`, this path throws without clearing `context.exception`. A later successful script that checks this shared slot, such as the docs module, can therefore report the previous configuration failure as its own. The stale exception can also escape a nested module evaluation whose Swift error was intentionally swallowed. Preserve the message and stack locally, then clear the slot before throwing.
```suggestion
if let exception = context?.exception {
let message = exception.toString() ?? "JavaScript error"
let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? ""
context?.exception = nil
throw NSError(domain: "Hammerspoon.Configuration", code: 1,
userInfo: [NSLocalizedDescriptionKey: "\(url.path): \(message)\n\(stack)"])
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in e6d8ae8. Exception capture is now scoped to file evaluation, the previous handler is restored, and the shared exception is cleared before throwing. The packaged probe now lets an independent timer run before any subsequent evaluation and verifies it keeps firing; the upstream regression tests also assert the context has no stale exception. The rebuilt packaged lifecycle probe passed, including 50 reloads.
|
Sorry about this PR. Was just messing around with something and a rouge agent decided it seemed like a good idea. |
evalFromURLcurrently returns normally when JavaScript evaluation throws, so a native caller cannot distinguish a failed configuration from a successful one. Store the exception in the context's exception handler and throw a native error containing the source path, message, and stack. Clear the previous exception before each file evaluation.The context remains alive: objects created before a runtime exception continue to work, while syntax errors execute nothing. This lets an embedding app present a recoverable configuration error without removing earlier working scripts. Regression tests cover partial execution, syntax errors, and successful evaluation after an error.
Validation: the patched engine builds at
c0bd4d6in Atelier's app target. Its isolated packaged lifecycle probe verifies partial-load recovery, syntax-error handling, relative modules/Spoons, retained independent timers, and 50 context reloads. The new upstream test cases have not been run in the unmodified upstream app host; the equivalent behavior was exercised through that packaged probe.