diff --git a/third_party/move/move-compiler-v2/src/lib.rs b/third_party/move/move-compiler-v2/src/lib.rs index a527653c1b6..3fe95423c70 100644 --- a/third_party/move/move-compiler-v2/src/lib.rs +++ b/third_party/move/move-compiler-v2/src/lib.rs @@ -625,6 +625,8 @@ pub fn stackless_bytecode_check_pipeline(options: &Options) -> FunctionTargetPip if options.experiment_on(Experiment::LINT_CHECKS) { // Some lint checks need live variable analysis. pipeline.add_processor(Box::new(LiveVarAnalysisProcessor::new(false))); + // Some lint checks need reachability annotations. + pipeline.add_processor(Box::new(UnreachableCodeProcessor {})); pipeline.add_processor(Box::new(LintProcessor {})); } diff --git a/third_party/move/move-model/src/model.rs b/third_party/move/move-model/src/model.rs index c1eeb212c32..8a00d578150 100644 --- a/third_party/move/move-model/src/model.rs +++ b/third_party/move/move-model/src/model.rs @@ -140,6 +140,11 @@ impl Loc { self.inlined_from_loc.is_some() } + /// Returns the immediate `inlined-from` link of this location, if any. + pub fn inlined_from_loc(&self) -> Option<&Loc> { + self.inlined_from_loc.as_deref() + } + // If `self` is an inlined `Loc`, then add the same // inlining info to the parameter `loc`. fn inline_if_needed(&self, loc: Loc) -> Loc { diff --git a/third_party/move/tools/move-linter/src/stackless_bytecode_lints.rs b/third_party/move/tools/move-linter/src/stackless_bytecode_lints.rs index a0bd7e1bbd4..998401c2e8a 100644 --- a/third_party/move/tools/move-linter/src/stackless_bytecode_lints.rs +++ b/third_party/move/tools/move-linter/src/stackless_bytecode_lints.rs @@ -2,11 +2,19 @@ // Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE //! This module (and its submodules) contain various stackless-bytecode-based lint checks. -//! Live variable analysis is a prerequisite for this lint processor. +//! +//! Prerequisite analyses (must be registered before the lint processor in the pipeline): +//! - Live variable analysis. +//! - Reachable state analysis. +//! +//! When adding a new lint check that depends on additional analyses, register +//! those analyses as prerequisites and document them here. +//! //! The lint checks also assume that all the correctness checks have already been performed. mod avoid_copy_on_identity_comparison; mod needless_mutable_reference; +mod unreachable_code; use move_compiler_v2::external_checks::StacklessBytecodeChecker; use std::collections::BTreeMap; @@ -19,6 +27,7 @@ pub fn get_default_linter_pipeline( let checks: Vec> = vec![ Box::new(avoid_copy_on_identity_comparison::AvoidCopyOnIdentityComparison {}), Box::new(needless_mutable_reference::NeedlessMutableReference {}), + Box::new(unreachable_code::UnreachableCode {}), ]; let checks_category = config.get("checks").map_or("default", |s| s.as_str()); if checks_category == "strict" || checks_category == "experimental" { diff --git a/third_party/move/tools/move-linter/src/stackless_bytecode_lints/unreachable_code.rs b/third_party/move/tools/move-linter/src/stackless_bytecode_lints/unreachable_code.rs new file mode 100644 index 00000000000..368891500c2 --- /dev/null +++ b/third_party/move/tools/move-linter/src/stackless_bytecode_lints/unreachable_code.rs @@ -0,0 +1,114 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +//! Lint that warns on user-written code that is definitely unreachable. +//! +//! Prerequisite: `ReachableStateAnnotation`, produced by `UnreachableCodeProcessor` +//! (registered in the lint pipeline as a prereq, alongside live variable analysis). + +use move_binary_format::file_format::CodeOffset; +use move_compiler_v2::{ + external_checks::StacklessBytecodeChecker, + pipeline::unreachable_code_analysis::ReachableStateAnnotation, +}; +use move_model::model::Loc; +use move_stackless_bytecode::function_target::FunctionTarget; +use std::collections::BTreeSet; + +pub struct UnreachableCode {} + +impl StacklessBytecodeChecker for UnreachableCode { + fn get_name(&self) -> String { + "unreachable_code".to_string() + } + + fn check(&self, target: &FunctionTarget) { + let annotation = target + .get_annotations() + .get::() + .expect( + "ReachableStateAnnotation missing: \ + UnreachableCodeProcessor must run before the lint pipeline", + ); + let code = target.get_bytecode(); + + // Two passes: a dead instruction can carry a Loc that encloses a + // *later* reachable Loc (e.g. a synthesized jump using the surrounding + // loop's Loc, emitted before the loop's reachable back-edge target), + // so we need every reachable Loc in hand before applying the filter. + // For inlined instructions, also record their call-site Loc. A + // function whose body is entirely inlined ends with a synthesized + // `Ret` whose Loc is the caller's body; without the call site in + // the reachable set, that Loc encloses nothing reachable (the + // inlinee Locs sit elsewhere) and we'd false-positive. + let mut reachable_locs: BTreeSet = BTreeSet::new(); + for (offset, instr) in code.iter().enumerate() { + if !annotation.is_definitely_not_reachable(offset as CodeOffset) { + let loc = target.get_bytecode_loc(instr.get_attr_id()); + reachable_locs.insert(call_site_loc(&loc)); + reachable_locs.insert(loc); + } + } + + let mut current_run: Vec = Vec::new(); + for (offset, instr) in code.iter().enumerate() { + if annotation.is_definitely_not_reachable(offset as CodeOffset) { + let loc = target.get_bytecode_loc(instr.get_attr_id()); + // The two skip paths below use `continue` rather than flushing the + // current run on purpose: when two dead instructions are split only + // by skipped ones, we still want them reported as a single warning + // instead of two adjacent ones. + // Skip code from inlining — not actionable at this site. + if loc.is_inlined() { + continue; + } + // Skip Locs of compiler-synthesized scaffolding (merge labels, + // back-jumps, trailing `Ret`): they reuse a parent AST node's + // Loc that physically wraps a reachable sibling instruction. + if reachable_locs.iter().any(|r| encloses_by_span(&loc, r)) { + continue; + } + current_run.push(loc); + } else { + self.flush(target, std::mem::take(&mut current_run)); + } + } + self.flush(target, current_run); + } +} + +impl UnreachableCode { + fn flush(&self, target: &FunctionTarget, run: Vec) { + if run.is_empty() { + return; + } + // `Loc::enclosing` takes min-start / max-end, so duplicates and + // unsorted input are fine — many bytecode instructions share one + // statement Loc. + let loc = Loc::enclosing(&run); + self.report(target.global_env(), &loc, "unreachable code"); + } +} + +/// Outermost Loc in the inlined-from chain — the user-visible call site. +fn call_site_loc(loc: &Loc) -> Loc { + let mut cur = loc; + while let Some(next) = cur.inlined_from_loc() { + cur = next; + } + cur.clone() +} + +/// Span-only enclosure (ignores `inlined_from_loc`, unlike `Loc::is_enclosing`). +/// +/// The scaffolding-skip heuristic in `check` relies on a compiler invariant: +/// synthesized bytecode instructions (merge labels, back-jumps, trailing `Ret`) +/// inherit the `Loc` of their enclosing AST node rather than getting a distinct +/// `Loc`. The filter detects such instructions by checking whether a dead +/// instruction's `Loc` physically wraps a reachable instruction's `Loc`. +/// If that compiler invariant ever changes, this heuristic will need updating. +fn encloses_by_span(outer: &Loc, inner: &Loc) -> bool { + outer.file_id() == inner.file_id() + && inner.span().start() >= outer.span().start() + && inner.span().end() <= outer.span().end() +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.exp new file mode 100644 index 00000000000..7b36507246b --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.exp @@ -0,0 +1,10 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_abort_or_return_always.move:8:9 + │ +8 │ 42 + │ ^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.move new file mode 100644 index 00000000000..d0b6a80763f --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_abort_or_return_always.move @@ -0,0 +1,10 @@ +module 0xc0ffee::m { + public fun test(p: bool): u64 { + if (p) { + abort 0 + } else { + return 1 + }; + 42 + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.exp new file mode 100644 index 00000000000..9aeb76a5019 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.exp @@ -0,0 +1,10 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_after_abort.move:4:9 + │ +4 │ 0 + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.move new file mode 100644 index 00000000000..30c4932f807 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_after_abort.move @@ -0,0 +1,6 @@ +module 0xc0ffee::m { + public fun test(): u32 { + abort 0; + 0 + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.exp new file mode 100644 index 00000000000..79ddffac01e --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.exp @@ -0,0 +1,21 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_break_unreachable.move:8:17 + │ +8 │ i = i + 1; // unreachable + │ ^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. + +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_break_unreachable.move:11:17 + │ +11 │ ╭ i = i + 1; // unreachable +12 │ │ }; +13 │ │ i = i + 1; // unreachable + │ ╰─────────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.move new file mode 100644 index 00000000000..a8696870395 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_break_unreachable.move @@ -0,0 +1,16 @@ +module 0xc0ffee::m { + public fun test() { + let i = 0; + loop { + i = i + 1; + if (i == 10) { + break; + i = i + 1; // unreachable + } else { + continue; + i = i + 1; // unreachable + }; + i = i + 1; // unreachable + } + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.exp new file mode 100644 index 00000000000..da32af5d676 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.exp @@ -0,0 +1,21 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_conditional_loop.move:8:17 + │ +8 │ ╭ noop(); // dead region 1 +9 │ │ noop(); + │ ╰──────────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. + +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_conditional_loop.move:13:13 + │ +13 │ ╭ noop(); // dead region 2 +14 │ │ noop(); + │ ╰──────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.move new file mode 100644 index 00000000000..d5d047e56b8 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_conditional_loop.move @@ -0,0 +1,17 @@ +module 0xc0ffee::m { + public fun noop() {} + + public fun test(p: bool, q: bool) { + while (p) { + if (q) { + loop {}; + noop(); // dead region 1 + noop(); + } else { + break; + }; + noop(); // dead region 2 + noop(); + } + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.exp new file mode 100644 index 00000000000..8e0e35e6f1a --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.exp @@ -0,0 +1,17 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_control_exp_as_term.move:8:23 + │ + 8 │ 1 + loop {} + 2; + │ ╭───────────────────────^ + 9 │ │ 1 + return + 0; +10 │ │ +11 │ │ foo(&if (cond) 0 else 1); +12 │ │ foo(&loop {}); +13 │ │ foo(&return); +14 │ │ foo(&abort 0); + │ ╰─────────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.move new file mode 100644 index 00000000000..11b856a9f77 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_control_exp_as_term.move @@ -0,0 +1,16 @@ +#[lint::skip(simpler_numeric_expression)] +module 0x42::M { + fun foo(_: &u64) {} + + #[lint::skip(unused_function)] + public fun t(cond: bool) { + 1 + if (cond) 0 else { 1 } + 2; + 1 + loop {} + 2; + 1 + return + 0; + + foo(&if (cond) 0 else 1); + foo(&loop {}); + foo(&return); + foo(&abort 0); + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.exp new file mode 100644 index 00000000000..144ada2dd20 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.exp @@ -0,0 +1,2 @@ + +No errors or warnings! diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.move new file mode 100644 index 00000000000..6c38e1531b1 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_inlined.move @@ -0,0 +1,23 @@ +// Dead code that originates from an inlined call should NOT warn — the +// synthesized trailing `Ret` is an inlining artifact. +// +// Genuinely dead code inside an inline function body is also not flagged at the +// call site: this is a known false negative. +module 0xc0ffee::m { + inline fun terminator() { + abort 0 + } + + public fun caller() { + terminator(); + } + + inline fun dead_in_body(): u64 { + abort 0; + 42 // genuinely dead, but won't warn because it's inlined at the call site + } + + public fun caller2(): u64 { + dead_in_body() + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.exp new file mode 100644 index 00000000000..6329030fa3b --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.exp @@ -0,0 +1,19 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_loop_labels.move:9:21 + │ +9 │ break + │ ^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. + +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_loop_labels.move:12:13 + │ +12 │ break + │ ^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.move new file mode 100644 index 00000000000..f9282884e8b --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_labels.move @@ -0,0 +1,15 @@ +#[lint::skip(while_true)] +module 0x815::test { + public fun f1() { + 'outer: loop { + // unlabeled loop, but counts in nesting in AST + loop { + 'inner: loop if (true) loop { + if (false) continue 'outer else break 'inner; + break + } else continue 'outer + }; + break + } + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.exp new file mode 100644 index 00000000000..e0c6815f684 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.exp @@ -0,0 +1,10 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_loop_only.move:4:9 + │ +4 │ 42 + │ ^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.move new file mode 100644 index 00000000000..181bb8ecd59 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_loop_only.move @@ -0,0 +1,6 @@ +module 0xc0ffee::m { + public fun test(): u64 { + loop {}; + 42 + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.exp new file mode 100644 index 00000000000..60d0bb33921 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.exp @@ -0,0 +1,23 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_multiple_regions.move:8:21 + │ +8 │ let x = 1; // dead region 1 + │ ╭─────────────────────^ +9 │ │ x + 1 + │ ╰─────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. + +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_multiple_regions.move:12:21 + │ +12 │ let y = 2; // dead region 2 + │ ╭─────────────────────^ +13 │ │ y + 2 + │ ╰─────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.move new file mode 100644 index 00000000000..f850f6e6b5e --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_multiple_regions.move @@ -0,0 +1,16 @@ +// Two disjoint dead regions in the same function — should produce two +// separate warnings, not one merged span. +#[lint::skip(needless_return)] +module 0xc0ffee::m { + public fun test(p: bool): u64 { + if (p) { + return 1; + let x = 1; // dead region 1 + x + 1 + } else { + abort 0; + let y = 2; // dead region 2 + y + 2 + } + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.exp new file mode 100644 index 00000000000..144ada2dd20 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.exp @@ -0,0 +1,2 @@ + +No errors or warnings! diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.move new file mode 100644 index 00000000000..37cf40690e2 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_no_warning.move @@ -0,0 +1,22 @@ +// Functions with no unreachable code — no diagnostics expected. +module 0xc0ffee::m { + public fun early_return(x: u64): u64 { + if (x == 0) { + return 0 + }; + x + 1 + } + + public fun simple(): u64 { + 42 + } + + public fun loops_and_breaks(x: u64): u64 { + let y = 0; + while (y < x) { + y = y + 1; + if (y == 5) break; + }; + y + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.exp new file mode 100644 index 00000000000..394e7e98ae8 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.exp @@ -0,0 +1,17 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_return_in_binop.move:4:19 + │ + 4 │ return >> 0; + │ ╭───────────────────^ + 5 │ │ return << 0; + 6 │ │ return || false; + 7 │ │ return && false; + · │ +17 │ │ return | 0; +18 │ │ return ^ 0; + │ ╰──────────────────^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.move new file mode 100644 index 00000000000..7ae6475d53e --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_return_in_binop.move @@ -0,0 +1,20 @@ +#[lint::skip(simpler_numeric_expression, nonminimal_bool, known_to_abort, unnecessary_numerical_extreme_comparison)] +module 0x42::M { + public fun t() { + return >> 0; + return << 0; + return || false; + return && false; + return + 0; + return % 0; + return / 1; + return < 0; + return > 0; + return <= 0; + return == 0; + return >= 0; + return != 0; + return | 0; + return ^ 0; + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.exp new file mode 100644 index 00000000000..75cdf8e291c --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.exp @@ -0,0 +1,10 @@ + +Diagnostics: +warning: [lint] unreachable code + ┌─ tests/default-only/unreachable_code_skip_function.move:12:9 + │ +12 │ 42 + │ ^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code. diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.move new file mode 100644 index 00000000000..9a0fb42f5df --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_function.move @@ -0,0 +1,14 @@ +// Function-level `#[lint::skip(unreachable_code)]` should suppress the +// unreachable_code lint on `skipped`, but the warning still fires on `not_skipped`. +module 0xc0ffee::m { + #[lint::skip(unreachable_code)] + public fun skipped(): u64 { + abort 0; + 42 + } + + public fun not_skipped(): u64 { + abort 0; + 42 + } +} diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.exp b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.exp new file mode 100644 index 00000000000..144ada2dd20 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.exp @@ -0,0 +1,2 @@ + +No errors or warnings! diff --git a/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.move b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.move new file mode 100644 index 00000000000..3aba62bd596 --- /dev/null +++ b/third_party/move/tools/move-linter/tests/default-only/unreachable_code_skip_module.move @@ -0,0 +1,14 @@ +// Module-level `#[lint::skip(unreachable_code)]` should suppress the lint +// on every function in the module. +#[lint::skip(unreachable_code, needless_return)] +module 0xc0ffee::m { + public fun a(): u64 { + abort 0; + 42 + } + + public fun b(x: u64): u64 { + return x; + x + 1 + } +} diff --git a/third_party/move/tools/move-linter/tests/model_ast_lints/cyclomatic_complexity_warn.exp b/third_party/move/tools/move-linter/tests/model_ast_lints/cyclomatic_complexity_warn.exp index ff219555697..19ceaa190bf 100644 --- a/third_party/move/tools/move-linter/tests/model_ast_lints/cyclomatic_complexity_warn.exp +++ b/third_party/move/tools/move-linter/tests/model_ast_lints/cyclomatic_complexity_warn.exp @@ -179,3 +179,12 @@ warning: [lint] Function `complexity::block_return_4` has cyclomatic complexity │ = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(cyclomatic_complexity)]`. = For more information, see https://aptos.dev/en/build/smart-contracts/linter#cyclomatic_complexity. + +warning: [lint] unreachable code + ┌─ tests/model_ast_lints/cyclomatic_complexity_warn.move:451:9 + │ +451 │ counter + │ ^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(unreachable_code)]`. + = For more information, see https://aptos.dev/en/build/smart-contracts/linter#unreachable_code.