Skip to content

Commit 18c427c

Browse files
authored
QL: Merge pull request #77 from github/erik-krogh/qlpacks
add pretty AST for YAML and a QLPack utility class
2 parents 238fba9 + 4958b8b commit 18c427c

4 files changed

Lines changed: 292 additions & 4 deletions

File tree

ql/src/codeql_ql/ast/Ast.qll

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,45 @@ class Predicate extends TPredicate, AstNode, Declaration {
232232
override string getAPrimaryQlClass() { result = "Predicate" }
233233
}
234234

235+
/**
236+
* A relation in the database.
237+
*/
238+
class Relation extends TDBRelation, AstNode, Declaration {
239+
Generated::DbTable table;
240+
241+
Relation() { this = TDBRelation(table) }
242+
243+
/**
244+
* Gets the name of the relation.
245+
*/
246+
override string getName() { result = table.getTableName().getChild().getValue() }
247+
248+
private Generated::DbColumn getColumn(int i) {
249+
result =
250+
rank[i + 1](Generated::DbColumn column, int child |
251+
table.getChild(child) = column
252+
|
253+
column order by child
254+
)
255+
}
256+
257+
/** Gets the `i`th parameter name */
258+
string getParameterName(int i) { result = getColumn(i).getColName().getValue() }
259+
260+
/** Gets the `i`th parameter type */
261+
string getParameterType(int i) {
262+
// TODO: This is just using the name of the type, not the actual type. Checkout Type.qll
263+
result = getColumn(i).getColType().getChild().(Generated::Token).getValue()
264+
}
265+
266+
/**
267+
* Gets the number of parameters.
268+
*/
269+
int getArity() { result = count(getColumn(_)) }
270+
271+
override string getAPrimaryQlClass() { result = "Relation" }
272+
}
273+
235274
/**
236275
* An expression that refers to a predicate, e.g. `BasicBlock::succ/2`.
237276
*/
@@ -2030,3 +2069,204 @@ class ModuleExpr extends TModuleExpr, ModuleRef {
20302069
pred = directMember("getQualifier") and result = this.getQualifier()
20312070
}
20322071
}
2072+
2073+
/**
2074+
* Classes modelling YAML AST nodes.
2075+
*/
2076+
module YAML {
2077+
/** A node in a YAML file */
2078+
class YAMLNode extends TYAMLNode, AstNode {
2079+
/** Holds if the predicate is a root node (has no parent) */
2080+
predicate isRoot() { not exists(getParent()) }
2081+
}
2082+
2083+
/** A YAML comment. */
2084+
class YAMLComment extends TYamlCommemt, YAMLNode {
2085+
Generated::YamlComment yamlcomment;
2086+
2087+
YAMLComment() { this = TYamlCommemt(yamlcomment) }
2088+
2089+
override string getAPrimaryQlClass() { result = "YAMLComment" }
2090+
}
2091+
2092+
/** A YAML entry. */
2093+
class YAMLEntry extends TYamlEntry, YAMLNode {
2094+
Generated::YamlEntry yamle;
2095+
2096+
YAMLEntry() { this = TYamlEntry(yamle) }
2097+
2098+
/** Gets the key of this YAML entry. */
2099+
YAMLKey getKey() {
2100+
exists(Generated::YamlKeyvaluepair pair |
2101+
pair.getParent() = yamle and
2102+
result = TYamlKey(pair.getKey())
2103+
)
2104+
}
2105+
2106+
/** Gets the value of this YAML entry. */
2107+
YAMLValue getValue() {
2108+
exists(Generated::YamlKeyvaluepair pair |
2109+
pair.getParent() = yamle and
2110+
result = TYamlValue(pair.getValue())
2111+
)
2112+
}
2113+
2114+
override string getAPrimaryQlClass() { result = "YAMLEntry" }
2115+
}
2116+
2117+
/** A YAML key. */
2118+
class YAMLKey extends TYamlKey, YAMLNode {
2119+
Generated::YamlKey yamlkey;
2120+
2121+
YAMLKey() { this = TYamlKey(yamlkey) }
2122+
2123+
/**
2124+
* Gets the value of this YAML key.
2125+
*/
2126+
YAMLValue getValue() {
2127+
exists(Generated::YamlKeyvaluepair pair |
2128+
pair.getKey() = yamlkey and result = TYamlValue(pair.getValue())
2129+
)
2130+
}
2131+
2132+
override string getAPrimaryQlClass() { result = "YAMLKey" }
2133+
2134+
/** Gets the value of this YAML value. */
2135+
string getNamePart(int i) {
2136+
i = 0 and result = yamlkey.getChild(0).(Generated::SimpleId).getValue()
2137+
or
2138+
exists(YAMLKey child |
2139+
child = TYamlKey(yamlkey.getChild(1)) and
2140+
result = child.getNamePart(i - 1)
2141+
)
2142+
}
2143+
2144+
/**
2145+
* Gets all the name parts of this YAML key concatenated with `/`.
2146+
* Dashes are replaced with `/` (because we don't have that information in the generated AST).
2147+
*/
2148+
string getQualifiedName() {
2149+
result = concat(string part, int i | part = getNamePart(i) | part, "/" order by i)
2150+
}
2151+
}
2152+
2153+
/** A YAML list item. */
2154+
class YAMLListItem extends TYamlListitem, YAMLNode {
2155+
Generated::YamlListitem yamllistitem;
2156+
2157+
YAMLListItem() { this = TYamlListitem(yamllistitem) }
2158+
2159+
/**
2160+
* Gets the value of this YAML list item.
2161+
*/
2162+
YAMLValue getValue() { result = TYamlValue(yamllistitem.getChild()) }
2163+
2164+
override string getAPrimaryQlClass() { result = "YAMLListItem" }
2165+
}
2166+
2167+
/** A YAML value. */
2168+
class YAMLValue extends TYamlValue, YAMLNode {
2169+
Generated::YamlValue yamlvalue;
2170+
2171+
YAMLValue() { this = TYamlValue(yamlvalue) }
2172+
2173+
override string getAPrimaryQlClass() { result = "YAMLValue" }
2174+
2175+
/** Gets the value of this YAML value. */
2176+
string getValue() { result = yamlvalue.getValue() }
2177+
}
2178+
2179+
// to not expose the entire `File` API on `QlPack`.
2180+
private newtype TQLPack = MKQlPack(File file) { file.getBaseName() = "qlpack.yml" }
2181+
2182+
YAMLEntry test() { not result.isRoot() }
2183+
2184+
/**
2185+
* A `qlpack.yml` file.
2186+
*/
2187+
class QLPack extends MKQlPack {
2188+
File file;
2189+
2190+
QLPack() { this = MKQlPack(file) }
2191+
2192+
private string getProperty(string name) {
2193+
exists(YAMLEntry entry |
2194+
entry.isRoot() and
2195+
entry.getKey().getQualifiedName() = name and
2196+
result = entry.getValue().getValue().trim() and
2197+
entry.getLocation().getFile() = file
2198+
)
2199+
}
2200+
2201+
/** Gets the name of this qlpack */
2202+
string getName() { result = getProperty("name") }
2203+
2204+
/** Gets the version of this qlpack */
2205+
string getVersion() { result = getProperty("version") }
2206+
2207+
/** Gets the extractor of this qlpack */
2208+
string getExtractor() { result = getProperty("extractor") }
2209+
2210+
string toString() { result = getName() }
2211+
2212+
/** Gets the file that this `QLPack` represents. */
2213+
File getFile() { result = file }
2214+
2215+
private predicate isADependency(YAMLEntry entry) {
2216+
exists(YAMLEntry deps |
2217+
deps.getLocation().getFile() = file and entry.getLocation().getFile() = file
2218+
|
2219+
deps.isRoot() and
2220+
deps.getKey().getQualifiedName() = "dependencies" and
2221+
entry.getLocation().getStartLine() = 1 + deps.getLocation().getStartLine() and
2222+
entry.getLocation().getStartColumn() > deps.getLocation().getStartColumn()
2223+
)
2224+
or
2225+
exists(YAMLEntry prev | isADependency(prev) |
2226+
prev.getLocation().getFile() = file and
2227+
entry.getLocation().getFile() = file and
2228+
entry.getLocation().getStartLine() = 1 + prev.getLocation().getStartLine() and
2229+
entry.getLocation().getStartColumn() = prev.getLocation().getStartColumn()
2230+
)
2231+
}
2232+
2233+
predicate hasDependency(string name, string version) {
2234+
exists(YAMLEntry entry | isADependency(entry) |
2235+
entry.getKey().getQualifiedName() = name and
2236+
entry.getValue().getValue() = version
2237+
)
2238+
}
2239+
2240+
/** Gets the database scheme of this qlpack */
2241+
File getDBScheme() {
2242+
result.getBaseName() = getProperty("dbscheme") and
2243+
result = file.getParentContainer().getFile(any(string s | s.matches("%.dbscheme")))
2244+
}
2245+
2246+
pragma[noinline]
2247+
Container getAFileInPack() {
2248+
result.getParentContainer() = file.getParentContainer()
2249+
or
2250+
result = getAFileInPack().(Folder).getAChildContainer()
2251+
}
2252+
2253+
/**
2254+
* Gets a QLPack that this QLPack depends on.
2255+
*/
2256+
QLPack getADependency() {
2257+
exists(string name | hasDependency(name, _) | result.getName().replaceAll("-", "/") = name)
2258+
}
2259+
2260+
Location getLocation() {
2261+
// hacky, just pick the first node in the file.
2262+
result =
2263+
min(YAMLNode entry, Location l, File f |
2264+
entry.getLocation().getFile() = file and
2265+
f = file and
2266+
l = entry.getLocation()
2267+
|
2268+
entry order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
2269+
).getLocation()
2270+
}
2271+
}
2272+
}

ql/src/codeql_ql/ast/internal/AstNodes.qll

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ newtype TAstNode =
1010
TClass(Generated::Dataclass dc) or
1111
TCharPred(Generated::Charpred pred) or
1212
TClassPredicate(Generated::MemberPredicate pred) or
13+
TDBRelation(Generated::DbTable table) or
1314
TSelect(Generated::Select sel) or
1415
TModule(Generated::Module mod) or
1516
TNewType(Generated::Datatype dt) or
@@ -57,7 +58,12 @@ newtype TAstNode =
5758
TUnaryExpr(Generated::UnaryExpr unaryexpr) or
5859
TDontCare(Generated::Underscore dontcare) or
5960
TModuleExpr(Generated::ModuleExpr me) or
60-
TPredicateExpr(Generated::PredicateExpr pe)
61+
TPredicateExpr(Generated::PredicateExpr pe) or
62+
TYamlCommemt(Generated::YamlComment yc) or
63+
TYamlEntry(Generated::YamlEntry ye) or
64+
TYamlKey(Generated::YamlKey yk) or
65+
TYamlListitem(Generated::YamlListitem yli) or
66+
TYamlValue(Generated::YamlValue yv)
6167

6268
class TFormula =
6369
TDisjunction or TConjunction or TComparisonFormula or TQuantifier or TNegation or TIfFormula or
@@ -75,6 +81,8 @@ class TCall = TPredicateCall or TMemberCall or TNoneCall or TAnyCall;
7581

7682
class TModuleRef = TImport or TModuleExpr;
7783

84+
class TYAMLNode = TYamlCommemt or TYamlEntry or TYamlKey or TYamlListitem or TYamlValue;
85+
7886
private Generated::AstNode toGeneratedFormula(AST::AstNode n) {
7987
n = TConjunction(result) or
8088
n = TDisjunction(result) or
@@ -105,6 +113,14 @@ private Generated::AstNode toGeneratedExpr(AST::AstNode n) {
105113
n = TDontCare(result)
106114
}
107115

116+
private Generated::AstNode toGenerateYAML(AST::AstNode n) {
117+
n = TYamlCommemt(result) or
118+
n = TYamlEntry(result) or
119+
n = TYamlKey(result) or
120+
n = TYamlListitem(result) or
121+
n = TYamlValue(result)
122+
}
123+
108124
/**
109125
* Gets the underlying TreeSitter entity for a given AST node.
110126
*/
@@ -113,6 +129,8 @@ Generated::AstNode toGenerated(AST::AstNode n) {
113129
or
114130
result = toGeneratedFormula(n)
115131
or
132+
result = toGenerateYAML(n)
133+
or
116134
result.(Generated::ParExpr).getChild() = toGenerated(n)
117135
or
118136
result =
@@ -135,6 +153,8 @@ Generated::AstNode toGenerated(AST::AstNode n) {
135153
or
136154
n = TClassPredicate(result)
137155
or
156+
n = TDBRelation(result)
157+
or
138158
n = TSelect(result)
139159
or
140160
n = TModule(result)
@@ -166,7 +186,7 @@ Generated::AstNode toGenerated(AST::AstNode n) {
166186
n = TSuper(result)
167187
}
168188

169-
class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate;
189+
class TPredicate = TCharPred or TClasslessPredicate or TClassPredicate or TDBRelation;
170190

171191
class TModuleMember = TModuleDeclaration or TImport or TSelect or TQLDoc;
172192

ql/src/codeql_ql/ast/internal/Module.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) {
100100
exists(Container c, Container parent |
101101
// should ideally look at `qlpack.yml` files
102102
parent = imp.getLocation().getFile().getParentContainer+() and
103-
exists(parent.getFile("qlpack.yml")) and
104-
c.getParentContainer() = parent and
103+
exists(YAML::QLPack pack |
104+
pack.getFile().getParentContainer() = parent and
105+
c.getParentContainer() = pack.getADependency*().getFile().getParentContainer()
106+
) and
105107
q = m.getName()
106108
|
107109
m = TFile(c)

ql/src/codeql_ql/ast/internal/Predicate.qll

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,37 @@ private module Cached {
8888
)
8989
}
9090

91+
pragma[noinline]
92+
private predicate candidate(Relation rel, PredicateCall pc) {
93+
rel.getName() = pc.getPredicateName()
94+
}
95+
96+
private predicate resolveDBRelation(PredicateCall pc, DefinedPredicate p) {
97+
exists(Relation rel | p = TPred(rel) |
98+
candidate(rel, pc) and
99+
rel.getArity() = pc.getNumberOfArguments() and
100+
(
101+
exists(YAML::QLPack libPack, YAML::QLPack qlPack |
102+
rel.getLocation().getFile() = libPack.getDBScheme() and
103+
qlPack.getADependency*() = libPack and
104+
qlPack.getAFileInPack() = pc.getLocation().getFile()
105+
)
106+
or
107+
// upgrade scripts don't have a qlpack
108+
rel.getLocation().getFile().getParentContainer() =
109+
pc.getLocation().getFile().getParentContainer()
110+
)
111+
)
112+
}
113+
91114
cached
92115
predicate resolveCall(Call c, PredicateOrBuiltin p) {
93116
resolvePredicateCall(c, p)
94117
or
95118
resolveMemberCall(c, p)
119+
or
120+
not resolvePredicateCall(c, _) and
121+
resolveDBRelation(c, p)
96122
}
97123

98124
cached

0 commit comments

Comments
 (0)