Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 464773d

Browse files
committed
Add initial implementation of PrintAST for Go
Known shortcomings: * Uses getAQlClass rather than tagging AST nodes with a canonical class, as the C++ version of the same query does * Types and go.mod lines are not printed informatively (typically we just get a short description of the node kind, e.g. 'function type') * Children are always named for their child indices; we should give informative names to the edges where an accessor is declared (e.g. IfStmt names its children 'init', 'cond', 'if', 'else')
1 parent a88bf4c commit 464773d

2 files changed

Lines changed: 211 additions & 0 deletions

File tree

ql/src/semmle/go/PrintAst.ql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name Print AST
3+
* @description Outputs a representation of the Abstract Syntax Tree.
4+
* @id go/print-ast
5+
* @kind graph
6+
*/
7+
8+
import go
9+
import PrintAst
10+
11+
/**
12+
* Hook to customize the functions printed by this query.
13+
*/
14+
class Cfg extends PrintAstConfiguration {
15+
override predicate shouldPrintFunction(FuncDef func) { any() }
16+
}

ql/src/semmle/go/PrintAst.qll

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Provides queries to pretty-print a Go AST as a graph.
3+
*/
4+
5+
import go
6+
7+
/**
8+
* Hook to customize the functions printed by this module.
9+
*/
10+
class PrintAstConfiguration extends string {
11+
/**
12+
* Restrict to a single string, making this a singleton type.
13+
*/
14+
PrintAstConfiguration() { this = "PrintAstConfiguration" }
15+
16+
/**
17+
* Holds if the AST for `func` should be printed. By default, holds for all
18+
* functions.
19+
*/
20+
predicate shouldPrintFunction(FuncDef func) { any() }
21+
}
22+
23+
private predicate shouldPrintFunction(FuncDef func) {
24+
exists(PrintAstConfiguration config | config.shouldPrintFunction(func))
25+
}
26+
27+
/**
28+
* An AST node that should be printed.
29+
*/
30+
private newtype TPrintAstNode =
31+
TAstNode(AstNode ast) {
32+
// Do print ast nodes without an enclosing function, e.g. file headers
33+
forall(FuncDef f | f = ast.getEnclosingFunction() | shouldPrintFunction(f))
34+
}
35+
36+
/**
37+
* A node in the output tree.
38+
*/
39+
class PrintAstNode extends TPrintAstNode {
40+
/**
41+
* Gets a textual representation of this node.
42+
*/
43+
abstract string toString();
44+
45+
/**
46+
* Gets the child node at index `childIndex`. Child indices must be unique,
47+
* but need not be contiguous.
48+
*/
49+
abstract PrintAstNode getChild(int childIndex);
50+
51+
/**
52+
* Holds if this node should be printed in the output. By default, all nodes
53+
* within a function are printed, but the query can override
54+
* `PrintAstConfiguration.shouldPrintFunction` to filter the output.
55+
*/
56+
predicate shouldPrint() { exists(getLocation()) }
57+
58+
/**
59+
* Gets a child of this node.
60+
*/
61+
PrintAstNode getAChild() { result = getChild(_) }
62+
63+
/**
64+
* Gets the location of this node in the source code.
65+
*/
66+
abstract Location getLocation();
67+
68+
/**
69+
* Gets the value of the property of this node, where the name of the property
70+
* is `key`.
71+
*/
72+
string getProperty(string key) {
73+
key = "semmle.label" and
74+
result = toString()
75+
}
76+
77+
/**
78+
* Gets the label for the edge from this node to the specified child. By
79+
* default, this is just the index of the child, but subclasses can override
80+
* this.
81+
*/
82+
string getChildEdgeLabel(int childIndex) {
83+
exists(getChild(childIndex)) and
84+
result = childIndex.toString()
85+
}
86+
87+
/**
88+
* Gets the `FuncDef` that contains this node.
89+
*/
90+
abstract FuncDef getEnclosingFunction();
91+
}
92+
93+
/**
94+
* Gets a pretty-printed representation of the QL class(es) for entity `el`.
95+
*/
96+
private string qlClass(AstNode el) { result = "[" + concat(el.getAQlClass(), ", ") + "] " }
97+
98+
/**
99+
* Gets a pretty-printed representation of the QL class(es) for entity `el`.
100+
*/
101+
private string qlClassType(Type el) { result = "[" + concat(el.getAQlClass(), ", ") + "] " }
102+
103+
/**
104+
* Gets a child with the given index and of the given kind, if one exists.
105+
* Note that a given parent can have multiple children with the same index but differing kind.
106+
*/
107+
private AstNode getChildOfKind(AstNode parent, string kind, int i) {
108+
kind = "expr" and result = parent.(ExprParent).getChildExpr(i)
109+
or
110+
kind = "gomodexpr" and result = parent.(GoModExprParent).getChildGoModExpr(i)
111+
or
112+
kind = "stmt" and result = parent.(StmtParent).getChildStmt(i)
113+
or
114+
kind = "decl" and result = parent.(DeclParent).getDecl(i)
115+
or
116+
kind = "spec" and result = parent.(GenDecl).getSpec(i)
117+
or
118+
kind = "field" and fields(result, parent, i)
119+
}
120+
121+
/**
122+
* Get an AstNode child, ordered by child kind and then by index
123+
*/
124+
private AstNode getUniquelyNumberedChild(AstNode node, int index) {
125+
result =
126+
rank[index + 1](AstNode child, string kind, int i |
127+
child = getChildOfKind(node, kind, i)
128+
|
129+
child order by kind, i
130+
)
131+
}
132+
133+
/**
134+
* A graph node representing a real AST node.
135+
*/
136+
class BaseAstNode extends PrintAstNode, TAstNode {
137+
AstNode ast;
138+
139+
BaseAstNode() { this = TAstNode(ast) }
140+
141+
override BaseAstNode getChild(int childIndex) {
142+
// Note a node can have several results for getChild(n) because some
143+
// nodes have multiple different types of child (e.g. a File has a
144+
// child expression, the package name, and child declarations whose
145+
// indices may clash), so we renumber them:
146+
result = TAstNode(getUniquelyNumberedChild(ast, childIndex))
147+
}
148+
149+
override string toString() { result = qlClass(ast) + ast }
150+
151+
final override Location getLocation() { result = ast.getLocation() }
152+
153+
final override FuncDef getEnclosingFunction() {
154+
result = ast or result = ast.getEnclosingFunction()
155+
}
156+
}
157+
158+
/**
159+
* A node representing an `Expr`.
160+
*/
161+
class ExprNode extends BaseAstNode {
162+
override Expr ast;
163+
164+
override string getProperty(string key) {
165+
result = super.getProperty(key)
166+
or
167+
key = "Value" and
168+
result = qlClass(ast) + ast.getExactValue()
169+
or
170+
key = "Type" and
171+
not ast.getType() instanceof InvalidType and
172+
result = ast.getType().pp()
173+
}
174+
}
175+
176+
query predicate nodes(PrintAstNode node, string key, string value) {
177+
node.shouldPrint() and
178+
value = node.getProperty(key)
179+
}
180+
181+
query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
182+
exists(int childIndex |
183+
source.shouldPrint() and
184+
target.shouldPrint() and
185+
target = source.getChild(childIndex)
186+
|
187+
key = "semmle.label" and value = source.getChildEdgeLabel(childIndex)
188+
or
189+
key = "semmle.order" and value = childIndex.toString()
190+
)
191+
}
192+
193+
query predicate graphProperties(string key, string value) {
194+
key = "semmle.graphKind" and value = "tree"
195+
}

0 commit comments

Comments
 (0)