Skip to content

Commit 236989f

Browse files
committed
QL: add pretty AST for YAML and a QLPack utility class
1 parent d2222ee commit 236989f

2 files changed

Lines changed: 203 additions & 1 deletion

File tree

ql/src/codeql_ql/ast/Ast.qll

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,3 +2019,187 @@ class ModuleExpr extends TModuleExpr, ModuleRef {
20192019
pred = directMember("getQualifier") and result = this.getQualifier()
20202020
}
20212021
}
2022+
2023+
/**
2024+
* Classes modelling YAML AST nodes.
2025+
*/
2026+
module YAML {
2027+
/** A node in a YAML file */
2028+
class YAMLNode extends TYAMLNode, AstNode {
2029+
/** Holds if the predicate is a root node (has no parent) */
2030+
predicate isRoot() { not exists(getParent()) }
2031+
}
2032+
2033+
/** A YAML comment. */
2034+
class YAMLComment extends TYamlCommemt, YAMLNode {
2035+
Generated::YamlComment yamlcomment;
2036+
2037+
YAMLComment() { this = TYamlCommemt(yamlcomment) }
2038+
2039+
override string getAPrimaryQlClass() { result = "YAMLComment" }
2040+
}
2041+
2042+
/** A YAML entry. */
2043+
class YAMLEntry extends TYamlEntry, YAMLNode {
2044+
Generated::YamlEntry yamle;
2045+
2046+
YAMLEntry() { this = TYamlEntry(yamle) }
2047+
2048+
/** Gets the key of this YAML entry. */
2049+
YAMLKey getKey() {
2050+
exists(Generated::YamlKeyvaluepair pair |
2051+
pair.getParent() = yamle and
2052+
result = TYamlKey(pair.getKey())
2053+
)
2054+
}
2055+
2056+
/** Gets the value of this YAML entry. */
2057+
YAMLValue getValue() {
2058+
exists(Generated::YamlKeyvaluepair pair |
2059+
pair.getParent() = yamle and
2060+
result = TYamlValue(pair.getValue())
2061+
)
2062+
}
2063+
2064+
override string getAPrimaryQlClass() { result = "YAMLEntry" }
2065+
}
2066+
2067+
/** A YAML key. */
2068+
class YAMLKey extends TYamlKey, YAMLNode {
2069+
Generated::YamlKey yamlkey;
2070+
2071+
YAMLKey() { this = TYamlKey(yamlkey) }
2072+
2073+
/**
2074+
* Gets the value of this YAML key.
2075+
*/
2076+
YAMLValue getValue() {
2077+
exists(Generated::YamlKeyvaluepair pair |
2078+
pair.getKey() = yamlkey and result = TYamlValue(pair.getValue())
2079+
)
2080+
}
2081+
2082+
override string getAPrimaryQlClass() { result = "YAMLKey" }
2083+
2084+
/** Gets the value of this YAML value. */
2085+
string getNamePart(int i) {
2086+
i = 0 and result = yamlkey.getChild(0).(Generated::SimpleId).getValue()
2087+
or
2088+
exists(YAMLKey child |
2089+
child = TYamlKey(yamlkey.getChild(1)) and
2090+
result = child.getNamePart(i - 1)
2091+
)
2092+
}
2093+
2094+
/**
2095+
* Gets all the name parts of this YAML key concatenated with `/`.
2096+
* Dashes are replaced with `/` (because we don't have that information in the generated AST).
2097+
*/
2098+
string getQualifiedName() {
2099+
result = concat(string part, int i | part = getNamePart(i) | part, "/" order by i)
2100+
}
2101+
}
2102+
2103+
/** A YAML list item. */
2104+
class YAMLListItem extends TYamlListitem, YAMLNode {
2105+
Generated::YamlListitem yamllistitem;
2106+
2107+
YAMLListItem() { this = TYamlListitem(yamllistitem) }
2108+
2109+
/**
2110+
* Gets the value of this YAML list item.
2111+
*/
2112+
YAMLValue getValue() { result = TYamlValue(yamllistitem.getChild()) }
2113+
2114+
override string getAPrimaryQlClass() { result = "YAMLListItem" }
2115+
}
2116+
2117+
/** A YAML value. */
2118+
class YAMLValue extends TYamlValue, YAMLNode {
2119+
Generated::YamlValue yamlvalue;
2120+
2121+
YAMLValue() { this = TYamlValue(yamlvalue) }
2122+
2123+
override string getAPrimaryQlClass() { result = "YAMLValue" }
2124+
2125+
/** Gets the value of this YAML value. */
2126+
string getValue() { result = yamlvalue.getValue() }
2127+
}
2128+
2129+
// to not expose the entire `File` API on `QlPack`.
2130+
private newtype TQLPack = MKQlPack(File file) { file.getBaseName() = "qlpack.yml" }
2131+
2132+
YAMLEntry test() { not result.isRoot() }
2133+
2134+
/**
2135+
* A `qlpack.yml` file.
2136+
*/
2137+
class QLPack extends MKQlPack {
2138+
File file;
2139+
2140+
QLPack() { this = MKQlPack(file) }
2141+
2142+
private string getProperty(string name) {
2143+
exists(YAMLEntry entry |
2144+
entry.isRoot() and
2145+
entry.getKey().getQualifiedName() = name and
2146+
result = entry.getValue().getValue() and
2147+
entry.getLocation().getFile() = file
2148+
)
2149+
}
2150+
2151+
/** Gets the name of this qlpack */
2152+
string getName() { result = getProperty("name") }
2153+
2154+
/** Gets the version of this qlpack */
2155+
string getVersion() { result = getProperty("version") }
2156+
2157+
/** Gets the database scheme of this qlpack */
2158+
string getDbScheme() { result = getProperty("dbscheme") }
2159+
2160+
/** Gets the extractor of this qlpack */
2161+
string getExtractor() { result = getProperty("extractor") }
2162+
2163+
string toString() { result = getName() }
2164+
2165+
/** Gets the file that this `QLPack` represents. */
2166+
File getFile() { result = file }
2167+
2168+
private predicate isADependency(YAMLEntry entry) {
2169+
exists(YAMLEntry deps |
2170+
deps.getLocation().getFile() = file and entry.getLocation().getFile() = file
2171+
|
2172+
deps.isRoot() and
2173+
deps.getKey().getQualifiedName() = "dependencies" and
2174+
entry.getLocation().getStartLine() = 1 + deps.getLocation().getStartLine() and
2175+
entry.getLocation().getStartColumn() > deps.getLocation().getStartColumn()
2176+
)
2177+
or
2178+
exists(YAMLEntry prev | isADependency(prev) |
2179+
prev.getLocation().getFile() = file and
2180+
entry.getLocation().getFile() = file and
2181+
entry.getLocation().getStartLine() = 1 + prev.getLocation().getStartLine() and
2182+
entry.getLocation().getStartColumn() = prev.getLocation().getStartColumn()
2183+
)
2184+
}
2185+
2186+
predicate hasDependency(string name, string version) {
2187+
exists(YAMLEntry entry | isADependency(entry) |
2188+
entry.getKey().getQualifiedName() = name and
2189+
entry.getValue().getValue() = version
2190+
)
2191+
}
2192+
2193+
Location getLocation() {
2194+
// hacky, just pick the first node in the file.
2195+
result =
2196+
min(YAMLNode entry, Location l, File f |
2197+
entry.getLocation().getFile() = file and
2198+
f = file and
2199+
l = entry.getLocation()
2200+
|
2201+
entry order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
2202+
).getLocation()
2203+
}
2204+
}
2205+
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ newtype TAstNode =
5757
TUnaryExpr(Generated::UnaryExpr unaryexpr) or
5858
TDontCare(Generated::Underscore dontcare) or
5959
TModuleExpr(Generated::ModuleExpr me) or
60-
TPredicateExpr(Generated::PredicateExpr pe)
60+
TPredicateExpr(Generated::PredicateExpr pe) or
61+
TYamlCommemt(Generated::YamlComment yc) or
62+
TYamlEntry(Generated::YamlEntry ye) or
63+
TYamlKey(Generated::YamlKey yk) or
64+
TYamlListitem(Generated::YamlListitem yli) or
65+
TYamlValue(Generated::YamlValue yv)
6166

6267
class TFormula =
6368
TDisjunction or TConjunction or TComparisonFormula or TQuantifier or TNegation or TIfFormula or
@@ -75,6 +80,9 @@ class TCall = TPredicateCall or TMemberCall or TNoneCall or TAnyCall;
7580

7681
class TModuleRef = TImport or TModuleExpr;
7782

83+
class TYAMLNode = TYamlCommemt or TYamlEntry or TYamlKey or TYamlListitem or
84+
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 =

0 commit comments

Comments
 (0)