Skip to content

Commit 6632b7d

Browse files
committed
QL: Add FrameworkCoverage query
1 parent 4b74fa6 commit 6632b7d

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* An experimental and incomplete query for measuring framework coverage
3+
* for models implemented in CodeQL.
4+
*
5+
* Currently only supports JavaScript models, and simply lists the package names
6+
* alongside the named features accessed on such a package.
7+
*/
8+
9+
import ql
10+
import codeql_ql.ast.internal.AstNodes
11+
import codeql_ql.dataflow.DataFlow as DataFlow
12+
13+
predicate isExcludedFile(File file) {
14+
file.getAbsolutePath().matches(["%ql/experimental/%", "%ql/test/%"])
15+
}
16+
17+
class PackageImportCall extends PredicateCall {
18+
PackageImportCall() {
19+
this.getQualifier().getName() = ["API", "DataFlow"] and
20+
this.getPredicateName() = ["moduleImport", "moduleMember"] and
21+
not isExcludedFile(getLocation().getFile())
22+
}
23+
24+
/** Gets the name of a package referenced by this call */
25+
string getAPackageName() { result = DataFlow::superNode(getArgument(0)).getAStringValueNoCall() }
26+
}
27+
28+
/** Gets a reference to `package` or any transitive member thereof. */
29+
DataFlow::SuperNode getADerivedRef(string package, DataFlow::Tracker t) {
30+
t.start() and
31+
result.asAstNode().(PackageImportCall).getAPackageName() = package
32+
or
33+
exists(DataFlow::Tracker t2 | result = getADerivedRef(package, t2).track(t2, t))
34+
or
35+
result.asAstNode() = getADerivedCall(package, t)
36+
}
37+
38+
/** Gets a call which models some aspect of `package`. */
39+
MemberCall getADerivedCall(string package, DataFlow::Tracker t) {
40+
result = getADerivedRef(package, t).getALocalMemberCall() and
41+
not result.(Expr).getType().getName() = ["int", "string"]
42+
}
43+
44+
/**
45+
* Gets an expression whose string-value is the name of a member accessed from `package`,
46+
* where the underlying package node was tracked here using `t`.
47+
*/
48+
Expr getAFeatureUse(string package, DataFlow::Tracker t) {
49+
exists(MemberCall call | call = getADerivedCall(package, t) |
50+
call.getMemberName() =
51+
[
52+
"getMember", "getAPropertyRead", "getAPropertyWrite", "getAPropertyReference",
53+
"getAPropertySource", "getAMethodCall", "getAMemberCall"
54+
] and
55+
result = call.getArgument(0)
56+
or
57+
call.getMemberName() = "getOptionArgument" and
58+
result = call.getArgument(1)
59+
)
60+
or
61+
t.start() and
62+
exists(PackageImportCall call |
63+
call.getAPackageName() = package and
64+
call.getPredicateName() = "moduleMember" and
65+
result = call.getArgument(1)
66+
)
67+
}
68+
69+
/**
70+
* Gets the name of a feature accessed as `use`.
71+
*/
72+
string getAFeatureName(string package, Expr use) {
73+
exists(DataFlow::Tracker t |
74+
use = getAFeatureUse(package, t) and
75+
result = DataFlow::superNode(use).getAStringValueForContext(t)
76+
)
77+
}
78+
79+
query predicate packageFeatures(string package, string features) {
80+
// TODO: 'express' still missing features from request objects - likely subclassing-related
81+
package = any(PackageImportCall call).getAPackageName() and
82+
features = concat(getAFeatureName(package, _), ", ")
83+
}
84+
85+
/** Holds if `cls` extends an abstract class from another file. */
86+
predicate isCrossFileContribution(Class cls) {
87+
exists(Class sup |
88+
cls.getASuperType().getResolvedType().getDeclaration() = sup and
89+
sup.isAbstract() and
90+
sup.getLocation().getFile() != cls.getLocation().getFile()
91+
)
92+
}
93+
94+
query predicate packageConcepts(string package, Class concept) {
95+
package = any(PackageImportCall call).getAPackageName() and
96+
getADerivedRef(package, DataFlow::Tracker::endNoCall()).getANode() =
97+
DataFlow::thisNode(concept.getCharPred()) and
98+
isCrossFileContribution(concept)
99+
}
100+
101+
query predicate importWithoutPackageName(PackageImportCall call, string path) {
102+
not exists(call.getAPackageName()) and
103+
path = call.getLocation().getFile().getRelativePath()
104+
}

0 commit comments

Comments
 (0)