diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql new file mode 100644 index 00000000000..5311d118576 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/AnnotationHasCfgNode.ql @@ -0,0 +1,16 @@ +/** + * Checks that every timer annotation has a corresponding CFG node. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where annotationWithoutCfgNode(ann) +select ann, "Annotation in $@ has no CFG node", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql new file mode 100644 index 00000000000..4b1d82e27e6 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgAnnotationHasCfgNode.ql @@ -0,0 +1,18 @@ +/** + * New-CFG version of AnnotationHasCfgNode. + * + * Checks that every timer annotation has a corresponding CFG node. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils::CfgTests + +from TimerAnnotation ann +where annotationWithoutCfgNode(ann) +select ann, "Annotation in $@ has no CFG node", ann.getTestFunction(), + ann.getTestFunction().getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected index e69de29bb2d..bce948bb58a 100644 --- a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgConsecutiveTimestamps.expected @@ -0,0 +1 @@ +| test_if.py:51:9:51:9 | IntegerLiteral | $@ in $@ has no consecutive successor (expected 6) | test_if.py:51:15:51:15 | IntegerLiteral | Timestamp 5 | test_if.py:43:1:43:31 | Function test_if_elif_else_first | test_if_elif_else_first | diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll index cb968c6fb60..97c6a9c043f 100644 --- a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgImpl.qll @@ -14,9 +14,12 @@ private class NewBasicBlock = CfgImpl::BasicBlock; /** New (shared) CFG implementation of the evaluation-order signature. */ module NewCfg implements EvalOrderCfgSig { class CfgNode instanceof NewControlFlowNode { - // Only include the unique representative node for each AST node, - // filtering out synthetic before/after/entry/exit/additional nodes. - CfgNode() { NewControlFlowNode.super.injects(_) } + // Use the post-order representative for each AST node: the "after" node. + // For simple leaf nodes this is the merged before/after node. For + // post-order expressions this is the TAstNode. For pre-order expressions + // (and/or/not/ternary) this uses an AfterValueNode, which places the + // expression after its operands — matching the timer test expectations. + CfgNode() { NewControlFlowNode.super.isAfter(_) } string toString() { result = NewControlFlowNode.super.toString() } diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql new file mode 100644 index 00000000000..e07890f7250 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NewCfgNoBasicBlock.ql @@ -0,0 +1,18 @@ +/** + * New-CFG version of NoBasicBlock. + * + * Checks that every annotated CFG node belongs to a basic block. + */ + +import python +import TimerUtils +import NewCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from CfgNode n, TestFunction f +where noBasicBlock(n, f) +select n, "CFG node in $@ does not belong to any basic block", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.expected @@ -0,0 +1 @@ + diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql new file mode 100644 index 00000000000..5568bd2a9a4 --- /dev/null +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/NoBasicBlock.ql @@ -0,0 +1,16 @@ +/** + * Checks that every annotated CFG node belongs to a basic block. + */ + +import python +import TimerUtils +import OldCfgImpl + +private module Utils = EvalOrderCfgUtils; + +private import Utils +private import Utils::CfgTests + +from CfgNode n, TestFunction f +where noBasicBlock(n, f) +select n, "CFG node in $@ does not belong to any basic block", f, f.getName() diff --git a/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll b/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll index 7d9329155b5..dc46f00f6f5 100644 --- a/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll +++ b/python/ql/test/library-tests/ControlFlow/evaluation-order/TimerUtils.qll @@ -92,7 +92,7 @@ class TimerAnnotation extends TTimerAnnotation { abstract Expr getAnnotatedExpr(); /** Gets the enclosing annotation expression (the `BinaryExpr` or `Call`). */ - abstract Expr getExpr(); + abstract Expr getTimerExpr(); /** Holds if this is a dead-code annotation (`t.dead[n]`). */ predicate isDead() { this instanceof DeadTimerAnnotation } @@ -100,9 +100,9 @@ class TimerAnnotation extends TTimerAnnotation { /** Holds if this is a never-evaluated annotation (`t.never`). */ predicate isNever() { this instanceof NeverTimerAnnotation } - string toString() { result = this.getExpr().toString() } + string toString() { result = this.getAnnotatedExpr().toString() } - Location getLocation() { result = this.getExpr().getLocation() } + Location getLocation() { result = this.getAnnotatedExpr().getLocation() } } /** A matmul-based timer annotation: `expr @ t[n]`. */ @@ -119,7 +119,7 @@ class MatmulTimerAnnotation extends TMatmulAnnotation, TimerAnnotation { override Expr getAnnotatedExpr() { result = annotated } - override BinaryExpr getExpr() { result.getLeft() = annotated } + override BinaryExpr getTimerExpr() { result.getLeft() = annotated } } /** A call-based timer annotation: `t(expr, n)`. */ @@ -136,7 +136,7 @@ class CallTimerAnnotation extends TCallAnnotation, TimerAnnotation { override Expr getAnnotatedExpr() { result = annotated } - override Call getExpr() { result.getArg(0) = annotated } + override Call getTimerExpr() { result.getArg(0) = annotated } } /** A dead-code timer annotation: `expr @ t.dead[n]`. */ @@ -153,7 +153,7 @@ class DeadTimerAnnotation extends TDeadAnnotation, TimerAnnotation { override Expr getAnnotatedExpr() { result = annotated } - override BinaryExpr getExpr() { result.getLeft() = annotated } + override BinaryExpr getTimerExpr() { result.getLeft() = annotated } } /** A never-evaluated annotation: `expr @ t.never`. */ @@ -169,7 +169,7 @@ class NeverTimerAnnotation extends TNeverAnnotation, TimerAnnotation { override Expr getAnnotatedExpr() { result = annotated } - override BinaryExpr getExpr() { result.getLeft() = annotated } + override BinaryExpr getTimerExpr() { result.getLeft() = annotated } } /** @@ -240,7 +240,7 @@ module EvalOrderCfgUtils { class TimerCfgNode extends CfgNode { private TimerAnnotation annot; - TimerCfgNode() { annot.getExpr() = this.getNode() } + TimerCfgNode() { annot.getAnnotatedExpr() = this.getNode() } /** Gets a timestamp value from this annotation. */ int getATimestamp() { result = annot.getATimestamp() } @@ -322,7 +322,7 @@ module EvalOrderCfgUtils { private predicate hasNestedScopeAnnotation(TestFunction f) { exists(TimerAnnotation a | a.getTestFunction() = f and - a.getExpr().getScope() != f + a.getAnnotatedExpr().getScope() != f ) } @@ -335,7 +335,7 @@ module EvalOrderCfgUtils { not ann.isDead() and a = ann.getATimestamp() and not exists(TimerCfgNode x, TimerCfgNode y | - ann.getExpr() = x.getNode() and + ann.getAnnotatedExpr() = x.getNode() and nextTimerAnnotation(x, y) and (a + 1) = y.getATimestamp() ) and @@ -354,7 +354,7 @@ module EvalOrderCfgUtils { */ predicate neverReachable(NeverTimerAnnotation ann) { exists(CfgNode n, Scope s | - n.getNode() = ann.getExpr() and + n.getNode() = ann.getAnnotatedExpr() and s = n.getScope() and ( // Reachable via inter-block path (includes same block) @@ -417,6 +417,27 @@ module EvalOrderCfgUtils { minB = min(b.getATimestamp()) and maxA >= minB } + + /** + * Holds if CFG node `n` in test function `f` does not belong to any basic block. + */ + predicate noBasicBlock(CfgNode n, TestFunction f) { + n.getScope() = f and + not exists(n.getBasicBlock()) + } + + /** + * Holds if non-dead annotation `ann` has no corresponding CFG node. + */ + predicate annotationWithoutCfgNode(TimerAnnotation ann) { + not ann.isDead() and + not ann.isNever() and + not exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr()) + } + + predicate annotationWithCfgNode(TimerAnnotation ann) { + exists(CfgNode n | n.getNode() = ann.getAnnotatedExpr()) + } } } @@ -427,7 +448,7 @@ module EvalOrderCfgUtils { predicate isTimerMechanism(Expr e, TestFunction f) { exists(TimerAnnotation a | a.getTestFunction() = f and - e = a.getExpr().getASubExpression*() + e = a.getTimerExpr().getASubExpression*() ) }