C#: Improve ConstantCondition.ql

This commit is contained in:
Anders Schack-Mulligen
2025-09-26 16:28:32 +02:00
parent 587901bc8a
commit 64810f6fb5
2 changed files with 172 additions and 3 deletions

View File

@@ -16,16 +16,90 @@
import csharp
import semmle.code.csharp.commons.Assertions
import semmle.code.csharp.commons.Constants
import semmle.code.csharp.controlflow.BasicBlocks
import semmle.code.csharp.controlflow.Guards as Guards
import codeql.controlflow.queries.ConstantCondition as ConstCond
module ConstCondInput implements ConstCond::InputSig<ControlFlow::BasicBlock> {
class SsaDefinition = Ssa::Definition;
class GuardValue = Guards::GuardValue;
class Guard = Guards::Guards::Guard;
predicate ssaControlsBranchEdge(SsaDefinition def, BasicBlock bb1, BasicBlock bb2, GuardValue v) {
Guards::Guards::ssaControlsBranchEdge(def, bb1, bb2, v)
}
import Guards::Guards::InternalUtil
}
module ConstCondImpl = ConstCond::Make<Location, Cfg, ConstCondInput>;
predicate nullCheck(Expr e, boolean direct) {
exists(QualifiableExpr qe | qe.isConditional() and qe.getQualifier() = e and direct = true)
or
exists(NullCoalescingExpr nce | nce.getLeftOperand() = e and direct = true)
or
exists(ConditionalExpr ce | ce.getThen() = e or ce.getElse() = e |
nullCheck(ce, _) and direct = false
)
}
predicate constantGuard(
Guards::Guards::Guard g, string msg, Guards::Guards::Guard reason, string reasonMsg
) {
ConstCondImpl::problems(g, msg, reason, reasonMsg) and
// conditional qualified expressions sit at an akward place in the CFG, which
// leads to FPs
not g.(QualifiableExpr).getQualifier() = reason and
// and if they're extension method calls, the syntactic qualifier is actually argument 0
not g.(ExtensionMethodCall).getArgument(0) = reason and
// if a logical connective is constant, one of its operands is constant, so
// we report that instead
not g instanceof LogicalNotExpr and
not g instanceof LogicalAndExpr and
not g instanceof LogicalOrExpr and
// if a logical connective is a reason for another condition to be constant,
// then one of its operands is a more precise reason
not reason instanceof LogicalNotExpr and
not reason instanceof LogicalAndExpr and
not reason instanceof LogicalOrExpr and
// don't report double-checked locking
not exists(LockStmt ls, BasicBlock bb |
bb = ls.getBasicBlock() and
reason.getBasicBlock().strictlyDominates(bb) and
bb.dominates(g.getBasicBlock())
) and
// exclude indirect null checks like `x` in `(b ? x : null)?.Foo()`
not nullCheck(g, false)
}
/** A constant condition. */
abstract class ConstantCondition extends Expr {
abstract class ConstantCondition extends Guards::Guards::Guard {
/** Gets the alert message for this constant condition. */
abstract string getMessage();
predicate hasReason(Guards::Guards::Guard reason, string reasonMsg) {
// dummy value, overridden when message has a placeholder
reason = this and reasonMsg = "dummy"
}
/** Holds if this constant condition is white-listed. */
predicate isWhiteListed() { none() }
}
/** A constant guard. */
class ConstantGuard extends ConstantCondition {
ConstantGuard() { constantGuard(this, _, _, _) }
override string getMessage() { constantGuard(this, result, _, _) }
override predicate hasReason(Guards::Guards::Guard reason, string reasonMsg) {
constantGuard(this, _, reason, reasonMsg)
}
}
/** A constant Boolean condition. */
class ConstantBooleanCondition extends ConstantCondition {
boolean b;
@@ -111,6 +185,7 @@ class ConstantMatchingCondition extends ConstantCondition {
boolean b;
ConstantMatchingCondition() {
this instanceof Expr and
forex(ControlFlow::Node cfn | cfn = this.getAControlFlowNode() |
exists(ControlFlow::MatchingSuccessor t | exists(cfn.getASuccessorByType(t)) |
b = t.getValue()
@@ -138,9 +213,10 @@ class ConstantMatchingCondition extends ConstantCondition {
}
}
from ConstantCondition c, string msg
from ConstantCondition c, string msg, Guards::Guards::Guard reason, string reasonMsg
where
msg = c.getMessage() and
c.hasReason(reason, reasonMsg) and
not c.isWhiteListed() and
not isExprInAssertion(c)
select c, msg
select c, msg, reason, reasonMsg