diff --git a/change_notes/2026-06-19-fix-fp-rule-0-0-1-orphan-blocks.md b/change_notes/2026-06-19-fix-fp-rule-0-0-1-orphan-blocks.md new file mode 100644 index 0000000000..b3fb927374 --- /dev/null +++ b/change_notes/2026-06-19-fix-fp-rule-0-0-1-orphan-blocks.md @@ -0,0 +1,2 @@ +- `RULE-0-0-1` - `UnreachableStatement.ql`: + - Fixed false positives caused by orphan BasicBlock nodes (disconnected from the CFG) that represent expressions rather than statements. These artifacts are generated by the GCC extractor for functions involving virtual calls returning `std::variant`-based types with non-trivially-destructible alternatives. diff --git a/cpp/misra/src/rules/RULE-0-0-1/UnreachableStatement.ql b/cpp/misra/src/rules/RULE-0-0-1/UnreachableStatement.ql index 09efabd3a9..38b4ccad54 100644 --- a/cpp/misra/src/rules/RULE-0-0-1/UnreachableStatement.ql +++ b/cpp/misra/src/rules/RULE-0-0-1/UnreachableStatement.ql @@ -61,12 +61,28 @@ predicate isReachable(BasicBlock bb) { ) } +/** + * Holds if `bb` is an orphan basic block that is disconnected from the CFG and + * does not represent a statement. These are artifacts of the extractor/CFG + * construction (e.g., variable access expressions, function name nodes, or + * temporary object reuse nodes) and not genuine unreachable statements. + * A block with no predecessors that is a Stmt is legitimately unreachable code + * (e.g., code after an infinite loop or noreturn call). + */ +predicate isDisconnectedNonStmtBlock(BasicBlock bb) { + not exists(bb.getAPredecessor()) and + not exists(bb.getASuccessor()) and + not bb = any(Function f).getEntryPoint() and + not bb instanceof Stmt +} + from BasicBlock bb where not isExcluded(bb, DeadCode3Package::unreachableStatementQuery()) and not isReachable(bb) and not isCompilerGenerated(bb) and - not affectedByMacro(bb) + not affectedByMacro(bb) and + not isDisconnectedNonStmtBlock(bb) // Note that the location of a BasicBlock will in some cases have an incorrect end location, often // preceding the end and including live code. We cast the block to an `Element` to get locations // that are not broken. diff --git a/cpp/misra/test/rules/RULE-0-0-1/test.cpp b/cpp/misra/test/rules/RULE-0-0-1/test.cpp index be42794b89..84e36d0611 100644 --- a/cpp/misra/test/rules/RULE-0-0-1/test.cpp +++ b/cpp/misra/test/rules/RULE-0-0-1/test.cpp @@ -247,4 +247,40 @@ void f12() { int l1 = 0; // NON-COMPLIANT } } +} + +// Regression test: virtual call returning variant-based expected type should not +// produce false positives from disconnected CFG nodes. With real GCC headers and +// codeql/cpp-all <= 5.0.0, the extractor creates orphan BasicBlock nodes (0 +// predecessors, 0 successors) for expressions inside functions that use virtual +// calls returning std::variant-based types with non-trivially-destructible +// alternatives (e.g. containing std::string). The isDisconnectedNonStmtBlock +// predicate filters these artifacts. This test validates the pattern compiles +// and is not flagged; the actual orphan block generation requires real stdlib. +#include + +struct TestError { + int code; +}; + +template class test_expected_void { + std::variant storage_; + +public: + test_expected_void() {} + bool has_value() const noexcept; +}; + +class ITestService { +public: + virtual test_expected_void DoWork(int x) const noexcept = 0; + virtual ~ITestService() = default; +}; + +bool f13(ITestService &svc) { // COMPLIANT + const auto result = svc.DoWork(42); + if (!result.has_value()) { + return false; // COMPLIANT + } + return true; // COMPLIANT } \ No newline at end of file