mirror of
https://github.com/github/codeql.git
synced 2026-08-02 16:32:58 +02:00
274 lines
7.9 KiB
Plaintext
274 lines
7.9 KiB
Plaintext
/**
|
|
* Provides queries to pretty-print a Go AST as a graph.
|
|
*/
|
|
overlay[local]
|
|
module;
|
|
|
|
import go
|
|
|
|
/**
|
|
* A hook to customize the files and functions printed by this module.
|
|
*
|
|
* For an AstNode to be printed, it always requires `shouldPrintFile(f)` to hold
|
|
* for its containing file `f`, and additionally requires `shouldPrintFunction(fun)`
|
|
* to hold if it is, or is a child of, function `fun`.
|
|
*/
|
|
class PrintAstConfiguration extends string {
|
|
/**
|
|
* Restrict to a single string, making this a singleton type.
|
|
*/
|
|
PrintAstConfiguration() { this = "PrintAstConfiguration" }
|
|
|
|
/**
|
|
* Holds if the AST for `func` should be printed. By default, holds for all
|
|
* functions.
|
|
*/
|
|
predicate shouldPrintFunction(FuncDecl func) { any() }
|
|
|
|
/**
|
|
* Holds if the AST for `file` should be printed. By default, holds for all
|
|
* files.
|
|
*/
|
|
predicate shouldPrintFile(File file) { any() }
|
|
|
|
/**
|
|
* Holds if the AST for `file` should include comments. By default, holds for all
|
|
* files.
|
|
*/
|
|
predicate shouldPrintComments(File file) { any() }
|
|
}
|
|
|
|
private predicate shouldPrintFunction(FuncDef func) {
|
|
exists(PrintAstConfiguration config | config.shouldPrintFunction(func))
|
|
}
|
|
|
|
private predicate shouldPrintFile(File file) {
|
|
exists(PrintAstConfiguration config | config.shouldPrintFile(file))
|
|
}
|
|
|
|
private predicate shouldPrintComments(File file) {
|
|
exists(PrintAstConfiguration config | config.shouldPrintComments(file))
|
|
}
|
|
|
|
private FuncDecl getEnclosingFunctionDecl(AstNode n) { result = n.getParent*() }
|
|
|
|
/**
|
|
* An AST node that should be printed.
|
|
*/
|
|
private newtype TPrintAstNode =
|
|
TAstNode(AstNode ast) {
|
|
shouldPrintFile(ast.getFile()) and
|
|
// Do print ast nodes without an enclosing function, e.g. file headers, that are not otherwise excluded
|
|
forall(FuncDecl f | f = getEnclosingFunctionDecl(ast) | shouldPrintFunction(f)) and
|
|
(
|
|
shouldPrintComments(ast.getFile())
|
|
or
|
|
not ast instanceof Comment and not ast instanceof CommentGroup
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A node in the output tree.
|
|
*/
|
|
class PrintAstNode extends TPrintAstNode {
|
|
/**
|
|
* Gets a textual representation of this node.
|
|
*/
|
|
abstract string toString();
|
|
|
|
/**
|
|
* Gets the child node at index `childIndex`. Child indices must be unique,
|
|
* but need not be contiguous.
|
|
*/
|
|
abstract PrintAstNode getChild(int childIndex);
|
|
|
|
/**
|
|
* Holds if this node should be printed in the output. By default, all nodes
|
|
* within a function are printed, but the query can override
|
|
* `PrintAstConfiguration.shouldPrintFunction` to filter the output.
|
|
*/
|
|
predicate shouldPrint() { exists(this.getLocation()) }
|
|
|
|
/**
|
|
* Gets a child of this node.
|
|
*/
|
|
PrintAstNode getAChild() { result = this.getChild(_) }
|
|
|
|
/**
|
|
* Gets the location of this node in the source code.
|
|
*/
|
|
abstract Location getLocation();
|
|
|
|
/**
|
|
* Gets the value of the property of this node, where the name of the property
|
|
* is `key`.
|
|
*/
|
|
string getProperty(string key) {
|
|
key = "semmle.label" and
|
|
result = this.toString()
|
|
}
|
|
|
|
/**
|
|
* Gets the label for the edge from this node to the specified child. By
|
|
* default, this is just the index of the child, but subclasses can override
|
|
* this.
|
|
*/
|
|
string getChildEdgeLabel(int childIndex) {
|
|
exists(this.getChild(childIndex)) and
|
|
result = childIndex.toString()
|
|
}
|
|
|
|
/**
|
|
* Gets the `FuncDef` that contains this node.
|
|
*/
|
|
abstract FuncDef getEnclosingFunction();
|
|
}
|
|
|
|
/**
|
|
* Gets a pretty-printed representation of the QL class(es) for entity `el`.
|
|
*/
|
|
private string qlClass(AstNode el) {
|
|
// This version shows all non-overridden QL classes:
|
|
// result = "[" + concat(el.getAQlClass(), ", ") + "] "
|
|
// Normally we prefer to show just the canonical class:
|
|
result = "[" + concat(el.getAPrimaryQlClass(), ", ") + "] "
|
|
}
|
|
|
|
/**
|
|
* A graph node representing a real AST node.
|
|
*/
|
|
class BaseAstNode extends PrintAstNode, TAstNode {
|
|
AstNode ast;
|
|
|
|
BaseAstNode() { this = TAstNode(ast) }
|
|
|
|
override BaseAstNode getChild(int childIndex) {
|
|
// Note a node can have several results for getChild(n) because some
|
|
// nodes have multiple different types of child (e.g. a File has a
|
|
// child expression, the package name, and child declarations whose
|
|
// indices may clash), so we renumber them:
|
|
result = TAstNode(ast.getUniquelyNumberedChild(childIndex))
|
|
}
|
|
|
|
override string toString() { result = qlClass(ast) + ast }
|
|
|
|
final override Location getLocation() { result = ast.getLocation() }
|
|
|
|
final override FuncDef getEnclosingFunction() {
|
|
result = ast or result = ast.getEnclosingFunction()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A node representing an `Expr`.
|
|
*/
|
|
class ExprNode extends BaseAstNode {
|
|
override Expr ast;
|
|
|
|
override string getProperty(string key) {
|
|
result = super.getProperty(key)
|
|
or
|
|
key = "Value" and
|
|
result = qlClass(ast) + ast.getExactValue()
|
|
or
|
|
key = "Type" and
|
|
not ast.getType() instanceof InvalidType and
|
|
result = ast.getType().pp()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A node representing a `File`
|
|
*/
|
|
class FileNode extends BaseAstNode {
|
|
override File ast;
|
|
|
|
private string getRelativePath() { result = ast.getRelativePath() }
|
|
|
|
private int getSortOrder() {
|
|
rank[result](FileNode fn | any() | fn order by fn.getRelativePath()) = this
|
|
}
|
|
|
|
override string getProperty(string key) {
|
|
result = super.getProperty(key)
|
|
or
|
|
key = "semmle.order" and
|
|
result = this.getSortOrder().toString()
|
|
}
|
|
|
|
/**
|
|
* Gets a child of this node, renumbering `packageNode`, our parent's
|
|
* `oldPackageIndex`th child, as the first child and moving others accordingly.
|
|
*/
|
|
private BaseAstNode getChildPackageFirst(
|
|
int childIndex, BaseAstNode packageNode, int oldPackageIndex
|
|
) {
|
|
super.getChild(oldPackageIndex) = packageNode and
|
|
(
|
|
childIndex = 0 and result = packageNode
|
|
or
|
|
result =
|
|
rank[childIndex](BaseAstNode node, int i |
|
|
node = super.getChild(i) and i != oldPackageIndex
|
|
|
|
|
node order by i
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a child of this node, moving the package-name expression to the front
|
|
* of the list if one exists.
|
|
*/
|
|
override BaseAstNode getChild(int childIndex) {
|
|
if exists(ast.getPackageNameExpr())
|
|
then result = this.getChildPackageFirst(childIndex, TAstNode(ast.getPackageNameExpr()), _)
|
|
else result = super.getChild(childIndex)
|
|
}
|
|
|
|
/**
|
|
* Gets the label for the edge from this node to the specified child. The package name
|
|
* expression is named 'package'; others are numbered as per our parent's implementation
|
|
* of this method.
|
|
*/
|
|
override string getChildEdgeLabel(int childIndex) {
|
|
if this.getChild(childIndex) = TAstNode(ast.getPackageNameExpr())
|
|
then result = "package"
|
|
else result = super.getChildEdgeLabel(childIndex)
|
|
}
|
|
|
|
/**
|
|
* Gets the string representation of this File. Note explicitly using a relative path
|
|
* like this rather than absolute as per default for the File class is a workaround for
|
|
* a bug with codeql run test, which should replace absolute paths but currently does not.
|
|
*/
|
|
override string toString() { result = qlClass(ast) + ast.getRelativePath() }
|
|
}
|
|
|
|
/** Holds if `node` belongs to the output tree, and its property `key` has the given `value`. */
|
|
query predicate nodes(PrintAstNode node, string key, string value) {
|
|
node.shouldPrint() and
|
|
value = node.getProperty(key)
|
|
}
|
|
|
|
/**
|
|
* Holds if `target` is a child of `source` in the AST, and property `key` of the edge has the
|
|
* given `value`.
|
|
*/
|
|
query predicate edges(PrintAstNode source, PrintAstNode target, string key, string value) {
|
|
exists(int childIndex |
|
|
source.shouldPrint() and
|
|
target.shouldPrint() and
|
|
target = source.getChild(childIndex)
|
|
|
|
|
key = "semmle.label" and value = source.getChildEdgeLabel(childIndex)
|
|
or
|
|
key = "semmle.order" and value = childIndex.toString()
|
|
)
|
|
}
|
|
|
|
/** Holds if property `key` of the graph has the given `value`. */
|
|
query predicate graphProperties(string key, string value) {
|
|
key = "semmle.graphKind" and value = "tree"
|
|
}
|