Skip to content

Commit 1cc30d2

Browse files
committed
No CFG nodes for subexprs of const exprs
In Go, sub-expressions of a constant expression are folded at compile time and never evaluated at runtime, so they shouldn't get evaluation nodes.
1 parent 6dfa1b4 commit 1cc30d2

5 files changed

Lines changed: 62 additions & 127 deletions

File tree

go/ql/lib/semmle/go/controlflow/ControlFlowGraphShared.qll

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ module CfgImpl {
3030
Input::implicitFieldSelection(e, index, implicitField)
3131
}
3232

33+
/**
34+
* Holds if `root` is a constant root: a constant expression (with any
35+
* enclosing parentheses stripped) whose parent expression is not itself
36+
* constant. The strict sub-expressions of a constant root are folded at
37+
* compile time and are not evaluated at run time, so they get no evaluation
38+
* node; the constant root itself is evaluated as a single leaf value.
39+
*/
40+
private predicate constantRoot(Go::Expr root) {
41+
exists(Go::Expr c |
42+
c.isConst() and
43+
not c.getParent().(Go::Expr).isConst() and
44+
root = c.stripParens()
45+
)
46+
}
47+
3348
/** Provides an implementation of the AST signature for Go. */
3449
private module Ast implements CfgLib::AstSig<Go::Location> {
3550
class AstNode = Go::AstNode;
@@ -78,6 +93,10 @@ module CfgImpl {
7893
// the switch expression (see `Switch.getExpr`), so the wrapping
7994
// statement must not introduce its own assignment or expression nodes.
8095
e = any(Go::TypeSwitchStmt ts).getTest()
96+
or
97+
// The strict sub-expressions of a constant expression are not evaluated
98+
// at run time, so they must not get their own evaluation nodes.
99+
constantRoot(e.(Go::Expr).getParent+())
81100
}
82101

83102
AstNode getChild(AstNode n, int index) {
@@ -345,17 +364,29 @@ module CfgImpl {
345364

346365
class BinaryExpr = Go::BinaryExpr;
347366

348-
class LogicalAndExpr = Go::LandExpr;
367+
// Constant short-circuiting operators are folded at compile time and their
368+
// operands are not evaluated at run time, so they are not treated as
369+
// logical operators here (which would give their operands their own
370+
// evaluation nodes via `getLeftOperand`/`getRightOperand`/`getOperand`,
371+
// bypassing `skipCfg`). Instead they are handled as constant-root leaf
372+
// value nodes (see `postOrInOrder`).
373+
class LogicalAndExpr extends Go::LandExpr {
374+
LogicalAndExpr() { not this.isConst() }
375+
}
349376

350-
class LogicalOrExpr = Go::LorExpr;
377+
class LogicalOrExpr extends Go::LorExpr {
378+
LogicalOrExpr() { not this.isConst() }
379+
}
351380

352381
class NullCoalescingExpr extends BinaryExpr {
353382
NullCoalescingExpr() { none() }
354383
}
355384

356385
class UnaryExpr = Go::UnaryExpr;
357386

358-
class LogicalNotExpr = Go::NotExpr;
387+
class LogicalNotExpr extends Go::NotExpr {
388+
LogicalNotExpr() { not this.isConst() }
389+
}
359390

360391
class BooleanLiteral extends Expr {
361392
boolean val;
@@ -473,6 +504,13 @@ module CfgImpl {
473504
// needs an explicit in-order (allocation) node.
474505
n instanceof Go::CompositeLit
475506
or
507+
// A constant expression is folded at compile time and its sub-expressions
508+
// are not evaluated (they are pruned by `skipCfg`), so the constant root
509+
// has no CFG children. It therefore needs an explicit in-order node to
510+
// remain a single value-producing leaf (e.g. `unsafe.Sizeof(test())`,
511+
// `1 << 10`, or `!d` for constant `d`).
512+
constantRoot(n)
513+
or
476514
// Statements/declarations that compute a value or perform an operation and
477515
// are not among the statements the shared library makes post-order by
478516
// default.

go/ql/lib/semmle/go/controlflow/IR.qll

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,16 @@ module IR {
243243
// `defer-invoke` node that models the call at function exit.
244244
this.isAdditional(e, "defer-invoke")
245245
or
246-
// `NotExpr` and `LogicalBinaryExpr` are not in `postOrInOrder`, so they
247-
// don't have an `isIn` node. Only use the after-node when the
248-
// expression is not in a conditional context; otherwise the value is
249-
// split across `TAfterValueNode`s per branch and should not be exposed
250-
// as a single value-producing instruction.
246+
// Non-constant `NotExpr` and `LogicalBinaryExpr` are not in
247+
// `postOrInOrder`, so they don't have an `isIn` node; their value is
248+
// produced by the after-node. (Constant ones are folded and get a leaf
249+
// `isIn` node via `constRoot`, handled by the first disjunct above, so
250+
// they are excluded here to avoid a duplicate value node.) Only use the
251+
// after-node when the expression is not in a conditional context;
252+
// otherwise the value is split across `TAfterValueNode`s per branch and
253+
// should not be exposed as a single value-producing instruction.
251254
(e instanceof NotExpr or e instanceof LogicalBinaryExpr) and
255+
not e.isConst() and
252256
not isInBooleanCondContext(e) and
253257
this.isAfter(e)
254258
}

go/ql/test/library-tests/semmle/go/controlflow/ControlFlowGraph/CONSISTENCY/CfgConsistency.expected

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
consistencyOverview
22
| deadEnd | 1 |
3-
| multipleSuccessors | 20 |
3+
| multipleSuccessors | 10 |
44
deadEnd
55
| stmts.go:61:2:61:10 | select statement |
66
multipleSuccessors
7-
| epilogues.go:61:2:61:38 | call to Println | successor | epilogues.go:60:8:60:33 | defer-invoke call to log |
8-
| epilogues.go:61:2:61:38 | call to Println | successor | epilogues.go:61:2:61:38 | After call to Println |
9-
| epilogues.go:70:2:70:20 | call to Println | successor | epilogues.go:69:8:69:25 | defer-invoke call to log |
10-
| epilogues.go:70:2:70:20 | call to Println | successor | epilogues.go:70:2:70:20 | After call to Println |
11-
| epilogues.go:81:2:81:23 | call to Println | successor | epilogues.go:78:8:80:15 | defer-invoke function call |
12-
| epilogues.go:81:2:81:23 | call to Println | successor | epilogues.go:81:2:81:23 | After call to Println |
137
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:17:2:17:15 | comm clause |
148
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:18:2:19:10 | comm clause |
159
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:20:2:24:10 | comm clause |
1610
| stmts2.go:16:2:26:2 | select statement | successor | stmts2.go:25:2:25:18 | comm clause |
17-
| stmts7.go:29:2:29:31 | call to Println | successor | stmts7.go:28:8:28:23 | defer-invoke call to fn |
18-
| stmts7.go:29:2:29:31 | call to Println | successor | stmts7.go:29:2:29:31 | After call to Println |
19-
| stmts7.go:54:6:54:18 | index expression | successor | stmts7.go:53:8:53:21 | defer-invoke call to recoverPanic |
20-
| stmts7.go:54:6:54:18 | index expression | successor | stmts7.go:54:6:54:18 | After index expression |
2111
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:51:2:52:31 | comm clause |
2212
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:53:2:55:16 | comm clause |
2313
| stmts.go:50:2:59:2 | select statement | successor | stmts.go:56:2:57:15 | comm clause |

0 commit comments

Comments
 (0)