Skip to content

Commit e4e52e7

Browse files
committed
QL4QL: Add query to warn about name clashes between summarized callables
1 parent 0592c8b commit e4e52e7

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* @name Name clash in summarized callable
3+
* @description Two summarized callables with the same name may apply to each others' call sites
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id ql/name-clash-in-summarized-callable
7+
* @tags correctness
8+
* maintainability
9+
* @precision high
10+
*/
11+
12+
import ql
13+
14+
/** A non-abstract subclass of `SummarizedCallable`. */
15+
class SummarizedCallableImpl extends Class {
16+
SummarizedCallableImpl() {
17+
this.getType().getASuperType+().getName() = "SummarizedCallable" and
18+
not this.isAbstract()
19+
}
20+
21+
/** Gets an expression bound to `this` in the charpred. */
22+
Expr getAThisBoundExpr() {
23+
exists(ThisAccess thisExpr |
24+
thisExpr.getEnclosingPredicate() = this.getCharPred() and
25+
any(ComparisonFormula eq | eq.getOperator() = "=").hasOperands(thisExpr, result)
26+
)
27+
}
28+
29+
/** Gets a string value bound to `this` in the charpred. */
30+
string getAThisBoundString() { result = getStringValue(this.getAThisBoundExpr()) }
31+
32+
/** Holds if this class appears to apply call site filtering. */
33+
predicate hasConditions() {
34+
exists(Conjunction expr | expr.getEnclosingPredicate() = this.getCharPred())
35+
or
36+
exists(this.getClassPredicate(["getACall", "getACallSimple"]))
37+
}
38+
}
39+
40+
/** Holds if we should compute the string values of `e`. */
41+
predicate needsStringValue(Expr e) {
42+
e = any(SummarizedCallableImpl impl).getAThisBoundExpr()
43+
or
44+
exists(Expr parent | needsStringValue(parent) |
45+
e = parent.(BinOpExpr).getAnOperand()
46+
or
47+
e = parent.(Set).getAnElement()
48+
)
49+
}
50+
51+
/** Gets the string values of `e`. */
52+
string getStringValue(Expr e) {
53+
needsStringValue(e) and
54+
(
55+
result = e.(String).getValue()
56+
or
57+
exists(BinOpExpr op |
58+
e = op and
59+
op.getOperator() = "+" and
60+
result = getStringValue(op.getLeftOperand()) + getStringValue(op.getRightOperand())
61+
)
62+
or
63+
result = getStringValue(e.(Set).getAnElement())
64+
)
65+
}
66+
67+
/** Gets the enclosing `qlpack.yml` file in `folder` */
68+
File getQLPackFromFolder(Folder folder) {
69+
result = folder.getFile("qlpack.yml")
70+
or
71+
not exists(folder.getFile("qlpack.yml")) and
72+
result = getQLPackFromFolder(folder.getParentContainer())
73+
}
74+
75+
/** Gets a summarised callables in the given qlpack with the given this-value */
76+
SummarizedCallableImpl getASummarizedCallableByNameAndPack(string name, File qlpack) {
77+
name = result.getAThisBoundString() and
78+
qlpack = getQLPackFromFolder(result.getFile().getParentContainer())
79+
}
80+
81+
/** Holds if the given classes have a name clash. */
82+
predicate hasClash(SummarizedCallableImpl class1, SummarizedCallableImpl class2, string name) {
83+
exists(File qlpack |
84+
class1 = getASummarizedCallableByNameAndPack(name, qlpack) and
85+
class2 = getASummarizedCallableByNameAndPack(name, qlpack) and
86+
class1 != class2 and
87+
class1.hasConditions()
88+
|
89+
// One of the classes is unconditional, implying that it disables the condition in the other
90+
not class2.hasConditions()
91+
or
92+
// Always report classes from different files, as it is considered too subtle of an interaction.
93+
class1.getFile() != class2.getFile()
94+
)
95+
}
96+
97+
/** Like `hasClash` but tries to avoid duplicates. */
98+
predicate hasClashBreakSymmetry(
99+
SummarizedCallableImpl class1, SummarizedCallableImpl class2, string name
100+
) {
101+
hasClash(class1, class2, name) and
102+
hasClash(class2, class1, name) and
103+
// try to break symmetry arbitrarily
104+
class1.getName() <= class2.getName()
105+
or
106+
hasClash(class1, class2, name) and
107+
not hasClash(class2, class1, name)
108+
}
109+
110+
from SummarizedCallableImpl class1, SummarizedCallableImpl class2, string name
111+
where hasClashBreakSymmetry(class1, class2, name)
112+
select class1,
113+
"$@ and $@ both bind 'this' to the string \"" + name +
114+
"\". They may accidentally apply to each others' call sites.", class1, class1.getName(), class2,
115+
class2.getName()

0 commit comments

Comments
 (0)