Skip to content

Commit d31ef37

Browse files
authored
Merge pull request #8391 from michaelnebel/csharp/gvn-interface
C#: Deprecate the StructuralComparisonConfiguration interface and use sameGvn instead.
2 parents c891c53 + 138eb48 commit d31ef37

File tree

11 files changed

+118
-262
lines changed

11 files changed

+118
-262
lines changed

csharp/ql/lib/semmle/code/csharp/commons/Constants.qll

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -105,31 +105,12 @@ private module ConstantComparisonOperation {
105105
}
106106
}
107107

108-
private class StructuralComparisonConfig extends StructuralComparison::StructuralComparisonConfiguration {
109-
StructuralComparisonConfig() { this = "CompareIdenticalValues" }
110-
111-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
112-
exists(ComparisonTest ct |
113-
x = ct.getFirstArgument() and
114-
y = ct.getSecondArgument()
115-
)
116-
}
117-
118-
ComparisonTest getComparisonTest() {
119-
exists(Element x, Element y |
120-
result.getFirstArgument() = x and
121-
result.getSecondArgument() = y and
122-
same(x, y)
123-
)
124-
}
125-
}
126-
127108
/**
128109
* Holds if comparison test `ct` compares two structurally identical
129110
* expressions.
130111
*/
131112
predicate comparesIdenticalValues(ComparisonTest ct) {
132-
ct = any(StructuralComparisonConfig c).getComparisonTest()
113+
StructuralComparison::sameGvn(ct.getFirstArgument(), ct.getSecondArgument())
133114
}
134115

135116
/**

csharp/ql/lib/semmle/code/csharp/commons/StructuralComparison.qll

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,18 @@ private import Cached
192192

193193
predicate toGvn = toGvnCached/1;
194194

195+
/**
196+
* Holds if the control flow elements `x` and `y` are structurally equal.
197+
*/
195198
pragma[inline]
196-
private predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
199+
predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
197200
pragma[only_bind_into](toGvn(pragma[only_bind_out](x))) =
198201
pragma[only_bind_into](toGvn(pragma[only_bind_out](y)))
199202
}
200203

201204
/**
205+
* DEPRECATED: Use `sameGvn` instead.
206+
*
202207
* A configuration for performing structural comparisons of program elements
203208
* (expressions and statements).
204209
*
@@ -207,7 +212,7 @@ private predicate sameGvn(ControlFlowElement x, ControlFlowElement y) {
207212
*
208213
* Each use of the library is identified by a unique string value.
209214
*/
210-
abstract class StructuralComparisonConfiguration extends string {
215+
abstract deprecated class StructuralComparisonConfiguration extends string {
211216
bindingset[this]
212217
StructuralComparisonConfiguration() { any() }
213218

@@ -235,55 +240,3 @@ abstract class StructuralComparisonConfiguration extends string {
235240
*/
236241
predicate same(ControlFlowElement x, ControlFlowElement y) { candidate(x, y) and sameGvn(x, y) }
237242
}
238-
239-
/**
240-
* INTERNAL: Do not use.
241-
*
242-
* A verbatim copy of the class `StructuralComparisonConfiguration` for internal
243-
* use.
244-
*
245-
* A copy is needed in order to use structural comparison within the standard
246-
* library without running into caching issues.
247-
*/
248-
module Internal {
249-
// Import all uses of the internal library to make sure caching works
250-
private import semmle.code.csharp.controlflow.Guards as G
251-
252-
/**
253-
* A configuration for performing structural comparisons of program elements
254-
* (expressions and statements).
255-
*
256-
* The predicate `candidate()` must be overridden, in order to identify the
257-
* elements for which to perform structural comparison.
258-
*
259-
* Each use of the library is identified by a unique string value.
260-
*/
261-
abstract class InternalStructuralComparisonConfiguration extends string {
262-
bindingset[this]
263-
InternalStructuralComparisonConfiguration() { any() }
264-
265-
/**
266-
* Holds if elements `x` and `y` are candidates for testing structural
267-
* equality.
268-
*
269-
* Subclasses are expected to override this predicate to identify the
270-
* top-level elements which they want to compare. Care should be
271-
* taken to avoid identifying too many pairs of elements, as in general
272-
* there are very many structurally equal subtrees in a program, and
273-
* in order to keep the computation feasible we must focus attention.
274-
*
275-
* Note that this relation is not expected to be symmetric -- it's
276-
* fine to include a pair `(x, y)` but not `(y, x)`.
277-
* In fact, not including the symmetrically implied fact will save
278-
* half the computation time on the structural comparison.
279-
*/
280-
abstract predicate candidate(ControlFlowElement x, ControlFlowElement y);
281-
282-
/**
283-
* Holds if elements `x` and `y` structurally equal. `x` and `y` must be
284-
* flagged as candidates for structural equality, that is,
285-
* `candidate(x, y)` must hold.
286-
*/
287-
predicate same(ControlFlowElement x, ControlFlowElement y) { candidate(x, y) and sameGvn(x, y) }
288-
}
289-
}

csharp/ql/lib/semmle/code/csharp/controlflow/Guards.qll

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ private import dotnet
88
private import ControlFlow::SuccessorTypes
99
private import semmle.code.csharp.commons.Assertions
1010
private import semmle.code.csharp.commons.ComparisonTest
11-
private import semmle.code.csharp.commons.StructuralComparison::Internal
11+
private import semmle.code.csharp.commons.StructuralComparison as SC
1212
private import semmle.code.csharp.controlflow.BasicBlocks
1313
private import semmle.code.csharp.controlflow.internal.Completion
1414
private import semmle.code.csharp.frameworks.System
@@ -1798,32 +1798,30 @@ module Internal {
17981798
}
17991799

18001800
/**
1801-
* A helper class for calculating structurally equal access/call expressions.
1801+
* Holds if access/call expression `e` (targeting declaration `target`)
1802+
* is a sub expression of a guard that controls whether basic block
1803+
* `bb` is reached.
18021804
*/
1803-
private class ConditionOnExprComparisonConfig extends InternalStructuralComparisonConfiguration {
1804-
ConditionOnExprComparisonConfig() { this = "ConditionOnExprComparisonConfig" }
1805-
1806-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
1807-
exists(BasicBlock bb, Declaration d |
1808-
this.candidateAux(x, d, bb) and
1809-
y =
1810-
any(AccessOrCallExpr e |
1811-
e.getAControlFlowNode().getBasicBlock() = bb and
1812-
e.getTarget() = d
1813-
)
1814-
)
1815-
}
1805+
pragma[noinline]
1806+
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
1807+
target = e.getTarget() and
1808+
guardControlsSub(_, bb, e)
1809+
}
18161810

1817-
/**
1818-
* Holds if access/call expression `e` (targeting declaration `target`)
1819-
* is a sub expression of a guard that controls whether basic block
1820-
* `bb` is reached.
1821-
*/
1822-
pragma[noinline]
1823-
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
1824-
target = e.getTarget() and
1825-
guardControlsSub(_, bb, e)
1826-
}
1811+
private predicate candidate(AccessOrCallExpr x, AccessOrCallExpr y) {
1812+
exists(BasicBlock bb, Declaration d |
1813+
candidateAux(x, d, bb) and
1814+
y =
1815+
any(AccessOrCallExpr e |
1816+
e.getAControlFlowNode().getBasicBlock() = bb and
1817+
e.getTarget() = d
1818+
)
1819+
)
1820+
}
1821+
1822+
private predicate same(AccessOrCallExpr x, AccessOrCallExpr y) {
1823+
candidate(x, y) and
1824+
SC::sameGvn(x, y)
18271825
}
18281826

18291827
cached
@@ -1849,7 +1847,7 @@ module Internal {
18491847
pragma[nomagic]
18501848
private predicate guardControlsSubSame(Guard g, BasicBlock bb, ControlGuardDescendant sub) {
18511849
guardControlsSub(g, bb, sub) and
1852-
any(ConditionOnExprComparisonConfig c).same(sub, _)
1850+
same(sub, _)
18531851
}
18541852

18551853
pragma[nomagic]
@@ -1862,7 +1860,7 @@ module Internal {
18621860
guardedBB = guardedCfn.getBasicBlock() and
18631861
guardControls(g, guardedBB, v) and
18641862
guardControlsSubSame(g, guardedBB, sub) and
1865-
any(ConditionOnExprComparisonConfig c).same(sub, guarded)
1863+
same(sub, guarded)
18661864
}
18671865

18681866
pragma[nomagic]

csharp/ql/src/Concurrency/UnsafeLazyInitialization.ql

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,12 @@
1414
import csharp
1515
import semmle.code.csharp.commons.StructuralComparison
1616

17-
class DoubleCheckedLock extends StructuralComparisonConfiguration {
18-
DoubleCheckedLock() { this = "double checked lock" }
19-
20-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
21-
exists(IfStmt unlockedIf, IfStmt lockedIf, LockStmt lock |
22-
x = unlockedIf.getCondition() and
23-
y = lockedIf.getCondition() and
24-
lock = unlockedIf.getThen().stripSingletonBlocks() and
25-
lockedIf.getParent*() = lock.getBlock()
26-
)
27-
}
28-
}
29-
30-
predicate doubleCheckedLock(Field field, IfStmt ifs) {
31-
exists(DoubleCheckedLock config, LockStmt lock, Expr eq1, Expr eq2 | ifs.getCondition() = eq1 |
32-
lock = ifs.getThen().stripSingletonBlocks() and
33-
config.same(eq1, eq2) and
34-
field.getAnAccess() = eq1.getAChildExpr*()
17+
predicate doubleCheckedLock(Field field, IfStmt unlockedIf) {
18+
exists(LockStmt lock, IfStmt lockedIf |
19+
lock = unlockedIf.getThen().stripSingletonBlocks() and
20+
lockedIf.getParent*() = lock.getBlock() and
21+
sameGvn(unlockedIf.getCondition(), lockedIf.getCondition()) and
22+
field.getAnAccess() = unlockedIf.getCondition().getAChildExpr*()
3523
)
3624
}
3725

csharp/ql/src/Language Abuse/MissedTernaryOpportunity.ql

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,8 @@
1212
import csharp
1313
import semmle.code.csharp.commons.StructuralComparison
1414

15-
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
16-
StructuralComparisonConfig() { this = "MissedTernaryOpportunity" }
17-
18-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
19-
exists(IfStmt is, AssignExpr ae1 |
20-
ae1 = is.getThen().stripSingletonBlocks().(ExprStmt).getExpr()
21-
|
22-
x = ae1.getLValue() and
23-
exists(AssignExpr ae2 | ae2 = is.getElse().stripSingletonBlocks().(ExprStmt).getExpr() |
24-
y = ae2.getLValue()
25-
)
26-
)
27-
}
28-
29-
IfStmt getIfStmt() {
30-
exists(AssignExpr ae | ae = result.getThen().stripSingletonBlocks().(ExprStmt).getExpr() |
31-
same(ae.getLValue(), _)
32-
)
33-
}
15+
private Expr getAssignedExpr(Stmt stmt) {
16+
result = stmt.stripSingletonBlocks().(ExprStmt).getExpr().(AssignExpr).getLValue()
3417
}
3518

3619
from IfStmt is, string what
@@ -40,10 +23,8 @@ where
4023
is.getElse().stripSingletonBlocks() instanceof ReturnStmt and
4124
what = "return"
4225
or
43-
exists(StructuralComparisonConfig c |
44-
is = c.getIfStmt() and
45-
what = "write to the same variable"
46-
)
26+
sameGvn(getAssignedExpr(is.getThen()), getAssignedExpr(is.getElse())) and
27+
what = "write to the same variable"
4728
) and
4829
not exists(IfStmt other | is = other.getElse())
4930
select is,

csharp/ql/src/Language Abuse/UselessIsBeforeAs.ql

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,26 @@
1313
import csharp
1414
import semmle.code.csharp.commons.StructuralComparison
1515

16-
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
17-
StructuralComparisonConfig() { this = "UselessIsBeforeAs" }
18-
19-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
20-
exists(IfStmt is, AsExpr ae, IsExpr ie, TypeAccessPatternExpr tape |
21-
ie = is.getCondition().getAChild*() and
22-
tape = ie.getPattern() and
23-
ae.getTargetType() = tape.getTarget() and
24-
x = ie.getExpr() and
25-
y = ae.getExpr()
26-
|
27-
ae = is.getThen().getAChild*()
28-
or
29-
ae = is.getElse().getAChild*()
30-
)
31-
}
16+
private predicate candidate(AsExpr ae, IsExpr ie) {
17+
exists(IfStmt is, TypeAccessPatternExpr tape |
18+
ie = is.getCondition().getAChild*() and
19+
tape = ie.getPattern() and
20+
ae.getTargetType() = tape.getTarget()
21+
|
22+
ae = is.getThen().getAChild*()
23+
or
24+
ae = is.getElse().getAChild*()
25+
)
26+
}
3227

33-
predicate uselessIsBeforeAs(AsExpr ae, IsExpr ie) {
34-
exists(Expr x, Expr y |
35-
same(x, y) and
36-
ie.getExpr() = x and
37-
ae.getExpr() = y
38-
)
39-
}
28+
private predicate uselessIsBeforeAs(AsExpr ae, IsExpr ie) {
29+
candidate(ae, ie) and
30+
sameGvn(ie.getExpr(), ae.getExpr())
4031
}
4132

4233
from AsExpr ae, IsExpr ie
4334
where
44-
exists(StructuralComparisonConfig c | c.uselessIsBeforeAs(ae, ie)) and
35+
uselessIsBeforeAs(ae, ie) and
4536
not exists(MethodCall mc | ae = mc.getAnArgument().getAChildExpr*())
4637
select ae,
4738
"This 'as' expression performs a type test - it should be directly compared against null, rendering the 'is' $@ potentially redundant.",

csharp/ql/src/Language Abuse/UselessNullCoalescingExpression.ql

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,22 @@
1414
import csharp
1515
import semmle.code.csharp.commons.StructuralComparison
1616

17-
class StructuralComparisonConfig extends StructuralComparisonConfiguration {
18-
StructuralComparisonConfig() { this = "UselessNullCoalescingExpression" }
19-
20-
override predicate candidate(ControlFlowElement x, ControlFlowElement y) {
21-
exists(NullCoalescingExpr nce |
22-
x.(Access) = nce.getLeftOperand() and
23-
y.(Access) = nce.getRightOperand().getAChildExpr*()
24-
)
25-
}
17+
pragma[noinline]
18+
private predicate same(AssignableAccess x, AssignableAccess y) {
19+
exists(NullCoalescingExpr nce |
20+
x = nce.getLeftOperand() and
21+
y = nce.getRightOperand().getAChildExpr*()
22+
) and
23+
sameGvn(x, y)
24+
}
2625

27-
NullCoalescingExpr getUselessNullCoalescingExpr() {
28-
exists(AssignableAccess x |
29-
result.getLeftOperand() = x and
30-
forex(AssignableAccess y | same(x, y) | y instanceof AssignableRead and not y.isRefArgument())
31-
)
32-
}
26+
private predicate uselessNullCoalescingExpr(NullCoalescingExpr nce) {
27+
exists(AssignableAccess x |
28+
nce.getLeftOperand() = x and
29+
forex(AssignableAccess y | same(x, y) | y instanceof AssignableRead and not y.isRefArgument())
30+
)
3331
}
3432

35-
from StructuralComparisonConfig c, NullCoalescingExpr nce
36-
where nce = c.getUselessNullCoalescingExpr()
33+
from NullCoalescingExpr nce
34+
where uselessNullCoalescingExpr(nce)
3735
select nce, "Both operands of this null-coalescing expression access the same variable or property."

csharp/ql/src/Likely Bugs/NestedLoopsSameVariable.ql

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@ import csharp
1515
import semmle.code.csharp.commons.ComparisonTest
1616
import semmle.code.csharp.commons.StructuralComparison as SC
1717

18-
/** A structural comparison configuration for comparing the conditions of nested `for` loops. */
19-
class NestedForConditions extends SC::StructuralComparisonConfiguration {
20-
NestedForConditions() { this = "Compare nested for conditions" }
21-
22-
override predicate candidate(ControlFlowElement e1, ControlFlowElement e2) {
23-
exists(NestedForLoopSameVariable nested |
24-
e1 = nested.getInnerForStmt().getCondition() and
25-
e2 = nested.getOuterForStmt().getCondition()
26-
)
27-
}
28-
}
29-
3018
private predicate hasChild(Stmt outer, Element child) {
3119
outer = child.getParent() and
3220
(outer instanceof ForStmt or outer = any(ForStmt f).getBody())
@@ -61,9 +49,7 @@ class NestedForLoopSameVariable extends ForStmt {
6149
}
6250

6351
private predicate haveSameCondition() {
64-
exists(NestedForConditions config |
65-
config.same(this.getInnerForStmt().getCondition(), this.getOuterForStmt().getCondition())
66-
)
52+
SC::sameGvn(this.getInnerForStmt().getCondition(), this.getOuterForStmt().getCondition())
6753
}
6854

6955
private predicate haveSameUpdate() {

0 commit comments

Comments
 (0)