oxc-project/oxc · warning · OxcDiagnostic
Unsafe `finally` block.
Error message
Unsafe `finally` block.
What it means
Diagnostic from oxlint's no-unsafe-finally rule. A break/continue/return/throw inside a finally block overwrites the control flow already in flight from try/catch; for example a return in finally silently swallows an exception thrown in try. The rule flags these control-flow statements when their enclosing block is a finally.
Source
Thrown at crates/oxc_linter/src/rules/eslint/no_unsafe_finally.rs:12
use oxc_ast::{
AstKind,
ast::{BreakStatement, ContinueStatement},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use crate::{AstNode, context::LintContext, rule::Rule};
fn no_unsafe_finally_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Unsafe `finally` block.")
.with_help(
"Control flow inside `try` or `catch` blocks will be overwritten by this statement.",
)
.with_label(span)
}
#[derive(Debug, Default, Clone)]
pub struct NoUnsafeFinally;
declare_oxc_lint!(
/// ### What it does
///
/// Disallow control flow statements in `finally` blocks.
///
/// ### Why is this bad?
///
/// JavaScript suspends the control flow statements of `try` and `catch`
/// blocks until the execution of a `finally` block finishes.View on GitHub (pinned to e1e7af627c)
Solutions
- Move the return/throw out of the finally block into code after the try statement.
- Record the outcome in a variable inside try/catch and act on it after the try statement.
- Keep finally strictly to release/rollback logic with no branching transfer statements.
- For break/continue, hoist the loop decision out or use a flag the loop checks.
Example fix
// before
function f() {
try { return compute(); }
finally { return fallback; }
}
// after
function f() {
let result;
try { result = compute(); }
finally { cleanup(); }
return result;
} Defensive patterns
Strategy: validation
Prevention
- Treat finally as cleanup-only: never return, throw, break, or continue inside it.
- Communicate outcomes via variables declared before the try and acted on after it.
- Keep the rule enabled (correctness category) so CI catches swallowed errors early.
When it happens
Trigger: try { return 1; } finally { return 2; }; finally { break label; }; finally { continue; }; finally { throw e2; } masking the original error from try or catch.
Common situations: Cleanup helpers that 'return a status' from finally; error masking when cleanup throws; labeled control flow leaking out of finally; copy-pasted cleanup blocks that branch.
Related errors
- Lacked a call of `super()` in some code paths.
- Expected a `break` statement before `case`.
- Expected a `break` statement before `default`.
- Expected to always call `super()` before `this`/`super` prop
- '{name}' is not modified in this loop.
AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20).
Data as JSON: /api/errors/398b3c1f1a1c4e9b.
Report an issue: GitHub.