Skip to content

Commit 7b2e075

Browse files
Claudeowen-mc
andcommitted
Go: instantiate the shared guards library
Agent-Logs-Url: https://github.com/github/codeql/sessions/d505ffcf-a693-4b67-9dc3-54fa92e27896 Co-authored-by: owen-mc <62447351+owen-mc@users.noreply.github.com>
1 parent 4e52189 commit 7b2e075

2 files changed

Lines changed: 323 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: feature
3+
---
4+
* Added `semmle.go.controlflow.Guards`, an instantiation of the shared guards library for Go. It provides the `Guard` class, which will replace `ControlFlow::ConditionGuardNode`.
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
/**
2+
* Provides classes and predicates for reasoning about guards and the control
3+
* flow elements controlled by those guards.
4+
*
5+
* This is an instantiation of the shared guards library for Go.
6+
*/
7+
overlay[local?]
8+
module;
9+
10+
private import go
11+
private import semmle.go.controlflow.ControlFlowGraphShared
12+
private import semmle.go.dataflow.SSA as GoSsa
13+
private import semmle.go.dataflow.SsaImpl as SsaImpl
14+
private import codeql.controlflow.Guards as SharedGuards
15+
16+
private module GuardsInput implements
17+
SharedGuards::InputSig<Location, CfgImpl::Cfg::ControlFlowNode, CfgImpl::Cfg::BasicBlock>
18+
{
19+
private import go as G
20+
21+
/**
22+
* A control flow node indicating normal termination of a function or file.
23+
*/
24+
class NormalExitNode extends CfgImpl::Cfg::ControlFlowNode {
25+
NormalExitNode() { this instanceof CfgImpl::ControlFlow::NormalExitNode }
26+
}
27+
28+
class AstNode = G::AstNode;
29+
30+
class Expr extends G::Expr {
31+
/** Gets the associated control flow node. */
32+
CfgImpl::Cfg::ControlFlowNode getControlFlowNode() { result = IR::evalExprInstruction(this) }
33+
34+
/** Gets the basic block containing this expression. */
35+
CfgImpl::Cfg::BasicBlock getBasicBlock() { result = this.getControlFlowNode().getBasicBlock() }
36+
}
37+
38+
private newtype TConstantValue = TStringValue(string s) { s = any(G::Expr e).getStringValue() }
39+
40+
class ConstantValue extends TConstantValue {
41+
/** Gets a textual representation of this constant value. */
42+
string toString() { this = TStringValue(result) }
43+
}
44+
45+
abstract class ConstantExpr extends Expr {
46+
predicate isNull() { none() }
47+
48+
boolean asBooleanValue() { none() }
49+
50+
int asIntegerValue() { none() }
51+
52+
ConstantValue asConstantValue() { none() }
53+
}
54+
55+
private class NilConstant extends ConstantExpr {
56+
NilConstant() { exprRefersToNil(this) }
57+
58+
override predicate isNull() { any() }
59+
}
60+
61+
private class BooleanConstant extends ConstantExpr {
62+
BooleanConstant() { exists(this.getBoolValue()) }
63+
64+
override boolean asBooleanValue() { result = this.getBoolValue() }
65+
}
66+
67+
private class IntegerConstant extends ConstantExpr {
68+
IntegerConstant() { exists(this.getIntValue()) }
69+
70+
override int asIntegerValue() { result = this.getIntValue() }
71+
}
72+
73+
private class StringConstant extends ConstantExpr {
74+
StringConstant() { exists(this.getStringValue()) }
75+
76+
override ConstantValue asConstantValue() { result = TStringValue(this.getStringValue()) }
77+
}
78+
79+
/**
80+
* An expression that is known not to be `nil`.
81+
*/
82+
class NonNullExpr extends Expr {
83+
NonNullExpr() {
84+
this instanceof G::CompositeLit
85+
or
86+
this instanceof G::FuncLit
87+
or
88+
this instanceof G::AddressExpr
89+
}
90+
}
91+
92+
/**
93+
* A `switch` case.
94+
*
95+
* Go's control flow graph does not currently produce matching successor
96+
* edges for `switch` statements: an expression switch with a tag desugars
97+
* into ordinary comparisons, and a tagless expression switch already
98+
* produces Boolean successors for each case expression. Hence there is
99+
* nothing for this class to model.
100+
*/
101+
class Case extends AstNode {
102+
Case() { none() }
103+
104+
Expr getSwitchExpr() { none() }
105+
106+
predicate isDefaultCase() { none() }
107+
108+
ConstantExpr asConstantCase() { none() }
109+
110+
predicate matchEdge(CfgImpl::Cfg::BasicBlock bb1, CfgImpl::Cfg::BasicBlock bb2) { none() }
111+
112+
predicate nonMatchEdge(CfgImpl::Cfg::BasicBlock bb1, CfgImpl::Cfg::BasicBlock bb2) { none() }
113+
}
114+
115+
class AndExpr extends Expr instanceof G::LandExpr {
116+
/** Gets an operand of this expression. */
117+
Expr getAnOperand() { result = super.getAnOperand() }
118+
}
119+
120+
class OrExpr extends Expr instanceof G::LorExpr {
121+
/** Gets an operand of this expression. */
122+
Expr getAnOperand() { result = super.getAnOperand() }
123+
}
124+
125+
class NotExpr extends Expr instanceof G::NotExpr {
126+
/** Gets the operand of this expression. */
127+
Expr getOperand() { result = super.getOperand() }
128+
}
129+
130+
/**
131+
* An expression that has the same value as a specific sub-expression, that
132+
* is, a parenthesized expression or a type conversion.
133+
*/
134+
class IdExpr extends Expr {
135+
IdExpr() { this instanceof G::ParenExpr or this instanceof G::ConversionExpr }
136+
137+
Expr getEqualChildExpr() {
138+
result = this.(G::ParenExpr).getExpr()
139+
or
140+
result = this.(G::ConversionExpr).getOperand()
141+
}
142+
}
143+
144+
/**
145+
* Holds if `eqtest` is an equality or inequality test between `left` and
146+
* `right`. The `polarity` indicates whether this is an equality test (true)
147+
* or inequality test (false).
148+
*/
149+
pragma[nomagic]
150+
predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) {
151+
exists(G::EqualityTestExpr eq | eq = eqtest |
152+
left = eq.getLeftOperand() and
153+
right = eq.getRightOperand() and
154+
polarity = eq.getPolarity()
155+
)
156+
}
157+
158+
/**
159+
* A conditional expression. Go has no such expression, so this class is
160+
* empty.
161+
*/
162+
class ConditionalExpr extends Expr {
163+
ConditionalExpr() { none() }
164+
165+
/** Gets the condition of this expression. */
166+
Expr getCondition() { none() }
167+
168+
/** Gets the true branch of this expression. */
169+
Expr getThen() { none() }
170+
171+
/** Gets the false branch of this expression. */
172+
Expr getElse() { none() }
173+
}
174+
175+
class Parameter = G::Parameter;
176+
177+
private int parameterPosition() { result = any(Parameter p).getIndex() }
178+
179+
/** A parameter position represented by an integer. */
180+
class ParameterPosition extends int {
181+
ParameterPosition() { this = parameterPosition() }
182+
}
183+
184+
/** An argument position represented by an integer. */
185+
class ArgumentPosition extends int {
186+
ArgumentPosition() { this = parameterPosition() }
187+
}
188+
189+
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
190+
pragma[inline]
191+
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
192+
193+
final private class FinalFunction = G::Function;
194+
195+
/**
196+
* A function whose calls always dispatch to that same function.
197+
*
198+
* Methods are excluded, since a call to a method may dispatch to a different
199+
* implementation via an interface.
200+
*/
201+
class NonOverridableMethod extends FinalFunction {
202+
NonOverridableMethod() {
203+
not this instanceof G::Method and
204+
exists(super.getFuncDecl()) and
205+
super.getNumResult() = 1
206+
}
207+
208+
Parameter getParameter(ParameterPosition ppos) { result = super.getParameter(ppos) }
209+
210+
/** Gets an expression being returned by this function. */
211+
Expr getAReturnExpr() {
212+
exists(G::ReturnStmt ret |
213+
ret.getEnclosingFunction() = super.getFuncDecl() and
214+
result = ret.getExpr()
215+
)
216+
}
217+
}
218+
219+
private predicate nonOverridableCall(G::CallExpr call, NonOverridableMethod m) {
220+
call = m.getACall().asExpr()
221+
}
222+
223+
class NonOverridableMethodCall extends Expr instanceof G::CallExpr {
224+
NonOverridableMethodCall() { nonOverridableCall(this, _) }
225+
226+
NonOverridableMethod getMethod() { nonOverridableCall(this, result) }
227+
228+
Expr getArgument(ArgumentPosition apos) { result = super.getArgument(apos) }
229+
}
230+
}
231+
232+
private module GuardsImpl = SharedGuards::Make<Location, CfgImpl::Cfg, GuardsInput>;
233+
234+
private module LogicInput implements GuardsImpl::LogicInputSig {
235+
final private class FinalSsaDefinition = GoSsa::SsaDefinition;
236+
237+
class SsaDefinition extends FinalSsaDefinition {
238+
GuardsInput::Expr getARead() {
239+
result = super.getVariable().getAUse().(IR::EvalInstruction).getExpr()
240+
}
241+
}
242+
243+
class SsaExplicitWrite extends SsaDefinition instanceof GoSsa::SsaExplicitDefinition {
244+
GuardsInput::Expr getValue() { result = super.getRhs().(IR::EvalInstruction).getExpr() }
245+
}
246+
247+
class SsaPhiDefinition extends SsaDefinition instanceof GoSsa::SsaPhiNode {
248+
/** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */
249+
predicate hasInputFromBlock(SsaDefinition inp, BasicBlock bb) {
250+
SsaImpl::phiHasInputFromBlock(this, inp, bb)
251+
}
252+
}
253+
254+
class SsaParameterInit extends SsaDefinition {
255+
SsaParameterInit() {
256+
this.(GoSsa::SsaExplicitDefinition).getInstruction() instanceof IR::InitParameterInstruction
257+
}
258+
259+
GuardsInput::Parameter getParameter() {
260+
this.(GoSsa::SsaExplicitDefinition).getInstruction() = IR::initParamInstruction(result)
261+
}
262+
}
263+
264+
/**
265+
* Holds if `guard` evaluating to `val` ensures that:
266+
* `e <= k` when `upper = true`
267+
* `e >= k` when `upper = false`
268+
*/
269+
predicate rangeGuard(
270+
GuardsImpl::PreGuard guard, GuardValue val, GuardsInput::Expr e, int k, boolean upper
271+
) {
272+
exists(RelationalComparisonExpr rel, int strictnessAdjustment |
273+
guard = rel and
274+
val.asBooleanValue() = true and
275+
(if rel.isStrict() then strictnessAdjustment = 1 else strictnessAdjustment = 0)
276+
|
277+
// `e < k` or `e <= k`
278+
e = rel.getLesserOperand() and
279+
upper = true and
280+
k = rel.getGreaterOperand().getIntValue() - strictnessAdjustment
281+
or
282+
// `k < e` or `k <= e`
283+
e = rel.getGreaterOperand() and
284+
upper = false and
285+
k = rel.getLesserOperand().getIntValue() + strictnessAdjustment
286+
)
287+
}
288+
}
289+
290+
/** An abstract value that a `Guard` may evaluate to. */
291+
class GuardValue = GuardsImpl::GuardValue;
292+
293+
private module GuardsLogic = GuardsImpl::Logic<LogicInput>;
294+
295+
/**
296+
* A guard. This is an expression whose value determines subsequent control
297+
* flow.
298+
*/
299+
final class Guard extends GuardsLogic::Guard {
300+
/** Gets the innermost function or file to which this guard belongs. */
301+
ControlFlow::Root getRoot() { result.isRootOf(this) }
302+
}
303+
304+
/**
305+
* Provides a set of barrier nodes for a guard that validates an expression.
306+
*/
307+
module ValidationWrapper<GuardsLogic::guardChecksSig/3 guardChecks> {
308+
import GuardsLogic::ValidationWrapper<guardChecks/3>
309+
}
310+
311+
/**
312+
* Holds if `bb` can only be reached when the expression `e` evaluates to `b`.
313+
*
314+
* This is the replacement for the old
315+
* `ConditionGuardNode.ensures(e, b) and ConditionGuardNode.dominates(bb)`
316+
* idiom.
317+
*/
318+
pragma[inline]
319+
predicate guardEnsures(Expr e, boolean b, BasicBlock bb) { e.(Guard).controls(bb, b) }

0 commit comments

Comments
 (0)