// CWE-248: panic()/abort() in library code can crash the entire program.
// Filters:
// - guard...else{panic/abort}: logically unreachable, standard MoonBit pattern
// - Platform stub files (.wasm.mbt, .js.mbt): platform-specific, won't execute on other targets
// - abort("unreachable") / abort("unimplemented"): developer-annotated dead code
// - Bare panic() with no arguments: typically a match-exhaustiveness sentinel, not a real error
// - Contract assertions: standard library patterns like abort("index out of bounds"),
// abort("division by zero") — these are correct runtime behavior, not security bugs
///|
priv struct Cwe248Visitor {
findings : Array[Finding]
file : String
source : String
severity : Severity
mut in_guard_else : Bool
mut in_unwrap_fn : Bool
}
///|
pub fn cwe248_info() -> RuleInfo {
{
id: { cwe: 248, variant: "panic-reachable" },
name: "Reachable panic/abort in Library Code",
description: "panic() or abort() in library code terminates the program. Callers cannot recover. Use Result/Option or raise an error instead.",
default_severity: Warning,
}
}
///|
pub fn run_cwe248(
impls : @list.List[@syntax.Impl],
file : String,
source : String,
findings : Array[Finding],
severity : Severity,
) -> Unit {
if is_platform_stub_file(file) {
return
}
let visitor : Cwe248Visitor = {
findings,
file,
source,
severity,
in_guard_else: false,
in_unwrap_fn: false,
}
impls.each(fn(impl_) { @syntax.IterVisitor::visit_Impl(visitor, impl_) })
}
///|
fn is_platform_stub_file(file : String) -> Bool {
file.has_suffix(".wasm.mbt") ||
file.has_suffix(".js.mbt") ||
file.has_suffix(".native.mbt")
}
///|
fn get_panic_kind(func : @syntax.Expr) -> String {
match func {
Ident(id~, ..) =>
match id.name {
Ident(name="panic") => "panic"
Ident(name="abort") => "abort"
_ => ""
}
_ => ""
}
}
///|
fn is_unwrap_function(name : String) -> Bool {
name == "unwrap" ||
name == "unwrap_or_error" ||
name == "get_exn" ||
name == "exn"
}
///|
fn is_unreachable_abort(args : @list.List[@syntax.Argument]) -> Bool {
args.any(fn(arg) {
match arg.value {
Constant(c=String(s), ..) => {
let lower = s.to_lower()
lower.contains("unreachable") || lower.contains("unimplemented")
}
_ => false
}
})
}
///|
fn is_bare_panic(kind : String, args : @list.List[@syntax.Argument]) -> Bool {
kind == "panic" && args.is_empty()
}
///|
fn is_contract_assertion(
kind : String,
args : @list.List[@syntax.Argument],
) -> Bool {
if kind != "abort" {
return false
}
args.any(fn(arg) {
match arg.value {
Constant(c=String(s), ..) => {
let lower = s.to_lower()
lower.contains("out of bounds") ||
lower.contains("division by zero") ||
lower.contains("divide by zero") ||
lower.contains("overflow") ||
lower.contains("underflow") ||
lower.contains("negative") ||
lower.contains("empty") ||
lower.contains("length") ||
lower.contains("capacity") ||
lower.contains("not found") ||
lower.contains("invalid")
}
_ => false
}
})
}
///|
impl @syntax.IterVisitor for Cwe248Visitor with fn visit_Impl_TopFuncDef(
env,
fun_decl~,
decl_body~,
where_clause~,
loc~,
) {
let prev = env.in_unwrap_fn
env.in_unwrap_fn = is_unwrap_function(fun_decl.name.name)
@syntax.IterVisitor::base(env).visit_Impl_TopFuncDef(
fun_decl~,
decl_body~,
where_clause~,
loc~,
)
env.in_unwrap_fn = prev
}
///|
impl @syntax.IterVisitor for Cwe248Visitor with fn visit_Expr_Guard(
env,
cond~,
otherwise~,
body~,
exclamation~,
guard_loc~,
loc~,
) {
let _ = (loc, exclamation, guard_loc)
@syntax.IterVisitor::base(env).visit_Expr(cond)
match otherwise {
Some(else_expr) => {
let prev = env.in_guard_else
env.in_guard_else = true
@syntax.IterVisitor::base(env).visit_Expr(else_expr)
env.in_guard_else = prev
}
None => ()
}
@syntax.IterVisitor::base(env).visit_Expr(body)
}
///|
impl @syntax.IterVisitor for Cwe248Visitor with fn visit_Expr_Apply(
env,
func~,
args~,
loc~,
) {
let kind = get_panic_kind(func)
if kind != "" &&
!env.in_guard_else &&
!env.in_unwrap_fn &&
!is_unreachable_abort(args) &&
!is_bare_panic(kind, args) &&
!is_contract_assertion(kind, args) {
env.findings.push(
make_finding(
{ cwe: 248, variant: "panic-reachable" },
env.severity,
Medium,
"panic()/abort() terminates the program. In library code, prefer returning Result or raising an error so callers can handle the failure.",
env.file,
env.source,
loc,
),
)
}
@syntax.IterVisitor::base(env).visit_Expr_Apply(func~, args~, loc~)
}