diff --git a/rust/ql/consistency-queries/CfgConsistency.ql b/rust/ql/consistency-queries/CfgConsistency.ql index 3a95fde507b4..4e155ca307b0 100644 --- a/rust/ql/consistency-queries/CfgConsistency.ql +++ b/rust/ql/consistency-queries/CfgConsistency.ql @@ -1,37 +1,8 @@ -import rust -import codeql.rust.controlflow.internal.ControlFlowGraphImpl::Consistency as Consistency -import Consistency -import codeql.rust.controlflow.ControlFlowGraph -import codeql.rust.controlflow.internal.ControlFlowGraphImpl as CfgImpl -import codeql.rust.controlflow.internal.Completion - /** - * All `Expr` nodes are `PostOrderTree`s + * @name Control flow graph inconsistencies + * @description Lists the control flow graph inconsistencies in the database. This query is intended for internal use. + * @kind table + * @id rust/diagnostics/cfg-consistency */ -query predicate nonPostOrderExpr(Expr e, string cls) { - cls = e.getPrimaryQlClasses() and - not e instanceof LetExpr and - not e instanceof ParenExpr and - exists(AstNode last, Completion c | - CfgImpl::last(e, last, c) and - last != e and - c instanceof NormalCompletion - ) -} - -query predicate scopeNoFirst(CfgScope scope) { - Consistency::scopeNoFirst(scope) and - not scope = any(Function f | not exists(f.getBody())) and - not scope = any(ClosureExpr c | not exists(c.getBody())) -} - -/** Holds if `be` is the `else` branch of a `let` statement that results in a panic. */ -private predicate letElsePanic(BlockExpr be) { - be = any(LetStmt let).getLetElse().getBlockExpr() and - exists(Completion c | CfgImpl::last(be, _, c) | completionIsNormal(c)) -} -query predicate deadEnd(CfgImpl::Node node) { - Consistency::deadEnd(node) and - not letElsePanic(node.getAstNode()) -} +import codeql.rust.controlflow.internal.CfgConsistency diff --git a/rust/ql/lib/codeql/rust/controlflow/internal/CfgConsistency.qll b/rust/ql/lib/codeql/rust/controlflow/internal/CfgConsistency.qll new file mode 100644 index 000000000000..378062e16b41 --- /dev/null +++ b/rust/ql/lib/codeql/rust/controlflow/internal/CfgConsistency.qll @@ -0,0 +1,94 @@ +/** + * Provides classes for recognizing control flow graph inconsistencies. + */ + +private import rust +private import codeql.rust.controlflow.internal.ControlFlowGraphImpl::Consistency as Consistency +import Consistency +private import codeql.rust.controlflow.ControlFlowGraph +private import codeql.rust.controlflow.internal.ControlFlowGraphImpl as CfgImpl +private import codeql.rust.controlflow.internal.Completion + +/** + * All `Expr` nodes are `PostOrderTree`s + */ +query predicate nonPostOrderExpr(Expr e, string cls) { + cls = e.getPrimaryQlClasses() and + not e instanceof LetExpr and + not e instanceof ParenExpr and + exists(AstNode last, Completion c | + CfgImpl::last(e, last, c) and + last != e and + c instanceof NormalCompletion + ) +} + +/** + * Holds if CFG scope `scope` lacks an initial AST node. Overrides shared consistency predicate. + */ +query predicate scopeNoFirst(CfgScope scope) { + Consistency::scopeNoFirst(scope) and + not scope = any(Function f | not exists(f.getBody())) and + not scope = any(ClosureExpr c | not exists(c.getBody())) +} + +/** Holds if `be` is the `else` branch of a `let` statement that results in a panic. */ +private predicate letElsePanic(BlockExpr be) { + be = any(LetStmt let).getLetElse().getBlockExpr() and + exists(Completion c | CfgImpl::last(be, _, c) | completionIsNormal(c)) +} + +/** + * Holds if `node` is lacking a successor. Overrides shared consistency predicate. + */ +query predicate deadEnd(CfgImpl::Node node) { + Consistency::deadEnd(node) and + not letElsePanic(node.getAstNode()) +} + +/** + * Gets counts of control flow graph inconsistencies of each type. + */ +int getCfgInconsistencyCounts(string type) { + // total results from all the CFG consistency query predicates in: + // - `codeql.rust.controlflow.internal.CfgConsistency` (this file) + // - `shared.controlflow.codeql.controlflow.Cfg` + type = "Non-unique set representation" and + result = count(CfgImpl::Splits ss | nonUniqueSetRepresentation(ss, _) | ss) + or + type = "Splitting invariant 2" and + result = count(AstNode n | breakInvariant2(n, _, _, _, _, _) | n) + or + type = "Splitting invariant 3" and + result = count(AstNode n | breakInvariant3(n, _, _, _, _, _) | n) + or + type = "Splitting invariant 4" and + result = count(AstNode n | breakInvariant4(n, _, _, _, _, _) | n) + or + type = "Splitting invariant 5" and + result = count(AstNode n | breakInvariant5(n, _, _, _, _, _) | n) + or + type = "Multiple successors of the same type" and + result = count(CfgNode n | multipleSuccessors(n, _, _) | n) + or + type = "Simple and normal successors" and + result = count(CfgNode n | simpleAndNormalSuccessors(n, _, _, _, _) | n) + or + type = "Dead end" and + result = count(CfgNode n | deadEnd(n) | n) + or + type = "Non-unique split kind" and + result = count(CfgImpl::SplitImpl si | nonUniqueSplitKind(si, _) | si) + or + type = "Non-unique list order" and + result = count(CfgImpl::SplitKind sk | nonUniqueListOrder(sk, _) | sk) + or + type = "Multiple toStrings" and + result = count(CfgNode n | multipleToString(n, _) | n) + or + type = "CFG scope lacks initial AST node" and + result = count(CfgScope s | scopeNoFirst(s) | s) + or + type = "Non-PostOrderTree Expr node" and + result = count(Expr e | nonPostOrderExpr(e, _) | e) +} diff --git a/rust/ql/src/queries/diagnostics/CfgConsistencyCounts.ql b/rust/ql/src/queries/diagnostics/CfgConsistencyCounts.ql new file mode 100644 index 000000000000..ecc1833176aa --- /dev/null +++ b/rust/ql/src/queries/diagnostics/CfgConsistencyCounts.ql @@ -0,0 +1,15 @@ +/** + * @name Control flow graph inconsistency counts + * @description Counts the number of control flow graph inconsistencies of each type. This query is intended for internal use. + * @kind diagnostic + * @id rust/diagnostics/cfg-consistency-counts + */ + +import rust +import codeql.rust.controlflow.internal.CfgConsistency as Consistency + +// see also `rust/diagnostics/cfg-consistency`, which lists the +// individual inconsistency results. +from string type, int num +where num = Consistency::getCfgInconsistencyCounts(type) +select type, num diff --git a/rust/ql/src/queries/summary/Stats.qll b/rust/ql/src/queries/summary/Stats.qll index 2f3992303f3b..242bb990e04a 100644 --- a/rust/ql/src/queries/summary/Stats.qll +++ b/rust/ql/src/queries/summary/Stats.qll @@ -3,9 +3,23 @@ */ import rust +private import codeql.rust.controlflow.internal.CfgConsistency as CfgConsistency +/** + * Gets a count of the total number of lines of code in the database. + */ int getLinesOfCode() { result = sum(File f | | f.getNumberOfLinesOfCode()) } +/** + * Gets a count of the total number of lines of code from the source code directory in the database. + */ int getLinesOfUserCode() { result = sum(File f | exists(f.getRelativePath()) | f.getNumberOfLinesOfCode()) } + +/** + * Gets a count of the total number of control flow graph inconsistencies in the database. + */ +int getTotalCfgInconsistencies() { + result = sum(string type | | CfgConsistency::getCfgInconsistencyCounts(type)) +} diff --git a/rust/ql/src/queries/summary/SummaryStats.ql b/rust/ql/src/queries/summary/SummaryStats.ql index a3494ccb769c..83429fae6d7d 100644 --- a/rust/ql/src/queries/summary/SummaryStats.ql +++ b/rust/ql/src/queries/summary/SummaryStats.ql @@ -33,4 +33,6 @@ where key = "Lines of code extracted" and value = getLinesOfCode().toString() or key = "Lines of user code extracted" and value = getLinesOfUserCode().toString() + or + key = "Inconsistencies - CFG" and value = getTotalCfgInconsistencies().toString() select key, value diff --git a/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.expected b/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.expected new file mode 100644 index 000000000000..7df2863da9e8 --- /dev/null +++ b/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.expected @@ -0,0 +1,13 @@ +| CFG scope lacks initial AST node | 0 | +| Dead end | 0 | +| Multiple successors of the same type | 0 | +| Multiple toStrings | 0 | +| Non-PostOrderTree Expr node | 0 | +| Non-unique list order | 0 | +| Non-unique set representation | 0 | +| Non-unique split kind | 0 | +| Simple and normal successors | 0 | +| Splitting invariant 2 | 0 | +| Splitting invariant 3 | 0 | +| Splitting invariant 4 | 0 | +| Splitting invariant 5 | 0 | diff --git a/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.qlref b/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.qlref new file mode 100644 index 000000000000..6e7ffa8aaa9d --- /dev/null +++ b/rust/ql/test/query-tests/diagnostics/CfgConsistencyCounts.qlref @@ -0,0 +1 @@ +queries/diagnostics/CfgConsistencyCounts.ql diff --git a/rust/ql/test/query-tests/diagnostics/SummaryStats.expected b/rust/ql/test/query-tests/diagnostics/SummaryStats.expected index 100ade0c0203..0a1312ad5d02 100644 --- a/rust/ql/test/query-tests/diagnostics/SummaryStats.expected +++ b/rust/ql/test/query-tests/diagnostics/SummaryStats.expected @@ -5,5 +5,6 @@ | Files extracted - total | 7 | | Files extracted - with errors | 2 | | Files extracted - without errors | 5 | +| Inconsistencies - CFG | 0 | | Lines of code extracted | 59 | | Lines of user code extracted | 59 |