Implement basic printAst query

This commit is contained in:
Nick Rolfe
2020-11-03 13:47:54 +00:00
parent 65c1f2c359
commit 41dcb19cd5
6 changed files with 907 additions and 609 deletions

View File

@@ -123,7 +123,7 @@ fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<dbscheme::Entry> {
create_containerparent_table(),
create_source_location_prefix_table(),
];
let mut top_members: Vec<String> = Vec::new();
let mut ast_node_members: Vec<String> = Vec::new();
for node in nodes {
match &node {
@@ -162,7 +162,7 @@ fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<dbscheme::Entry> {
}],
keysets: None,
};
top_members.push(node_types::escape_name(&name));
ast_node_members.push(node_types::escape_name(&name));
// If the type also has fields or children, then we create either
// auxiliary tables or columns in the defining table for them.
@@ -198,8 +198,8 @@ fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<dbscheme::Entry> {
// Create a union of all database types.
entries.push(dbscheme::Entry::Union(dbscheme::Union {
name: "top".to_string(),
members: top_members,
name: "ast_node".to_string(),
members: ast_node_members,
}));
entries

View File

@@ -76,7 +76,7 @@ fn create_supertype_map(nodes: &[node_types::Entry]) -> SupertypeMap {
}
fn get_base_classes(name: &str, supertype_map: &SupertypeMap) -> Vec<ql::Type> {
let mut base_classes: Vec<ql::Type> = vec![ql::Type::Normal("Top".to_owned())];
let mut base_classes: Vec<ql::Type> = vec![ql::Type::Normal("AstNode".to_owned())];
if let Some(supertypes) = supertype_map.get(name) {
base_classes.extend(
@@ -89,10 +89,24 @@ fn get_base_classes(name: &str, supertype_map: &SupertypeMap) -> Vec<ql::Type> {
base_classes
}
/// Creates the hard-coded `Top` class that acts as a supertype of all classes we
/// generate.
fn create_top_class() -> ql::Class {
let to_string = create_none_predicate("toString", false, Some(ql::Type::String), vec![]);
/// Creates the hard-coded `AstNode` class that acts as a supertype of all
/// classes we generate.
fn create_ast_node_class() -> ql::Class {
// Default implementation of `toString` calls `this.describeQlClass()`
let to_string = ql::Predicate {
name: "toString".to_owned(),
overridden: false,
return_type: Some(ql::Type::String),
formal_parameters: vec![],
body: ql::Expression::Equals(
Box::new(ql::Expression::Var("result".to_owned())),
Box::new(ql::Expression::Dot(
Box::new(ql::Expression::Var("this".to_owned())),
"describeQlClass".to_owned(),
vec![],
)),
),
};
let get_location = create_none_predicate(
"getLocation",
false,
@@ -102,15 +116,30 @@ fn create_top_class() -> ql::Class {
let get_a_field_or_child = create_none_predicate(
"getAFieldOrChild",
false,
Some(ql::Type::Normal("Top".to_owned())),
Some(ql::Type::Normal("AstNode".to_owned())),
vec![],
);
let describe_ql_class = ql::Predicate {
name: "describeQlClass".to_owned(),
overridden: false,
return_type: Some(ql::Type::String),
formal_parameters: vec![],
body: ql::Expression::Equals(
Box::new(ql::Expression::Var("result".to_owned())),
Box::new(ql::Expression::String("???".to_owned())),
),
};
ql::Class {
name: "Top".to_owned(),
name: "AstNode".to_owned(),
is_abstract: false,
supertypes: vec![ql::Type::AtType("top".to_owned())],
supertypes: vec![ql::Type::AtType("ast_node".to_owned())],
characteristic_predicate: None,
predicates: vec![to_string, get_location, get_a_field_or_child],
predicates: vec![
to_string,
get_location,
get_a_field_or_child,
describe_ql_class,
],
}
}
@@ -159,7 +188,7 @@ fn create_field_class(
]
.concat(),
characteristic_predicate: None,
predicates: vec![],
predicates: vec![create_describe_ql_class(&class_name)],
}));
field_union_name
}
@@ -188,16 +217,17 @@ fn dbscheme_name_to_class_name(dbscheme_name: &str) -> String {
.join("")
}
/// Creates a `toString` predicate that returns the given text.
fn create_to_string_predicate(text: &str) -> ql::Predicate {
/// Creates an overridden `describeQlClass` predicate that returns the given
/// name.
fn create_describe_ql_class(class_name: &str) -> ql::Predicate {
ql::Predicate {
name: "toString".to_owned(),
name: "describeQlClass".to_owned(),
overridden: true,
return_type: Some(ql::Type::String),
formal_parameters: vec![],
body: ql::Expression::Equals(
Box::new(ql::Expression::Var("result".to_owned())),
Box::new(ql::Expression::String(text.to_owned())),
Box::new(ql::Expression::String(class_name.to_owned())),
),
}
}
@@ -385,7 +415,7 @@ pub fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<ql::TopLevel> {
let mut classes: Vec<ql::TopLevel> = vec![
ql::TopLevel::Import("codeql.files.FileSystem".to_owned()),
ql::TopLevel::Import("codeql.Locations".to_owned()),
ql::TopLevel::Class(create_top_class()),
ql::TopLevel::Class(create_ast_node_class()),
];
for node in nodes {
@@ -410,7 +440,7 @@ pub fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<ql::TopLevel> {
]
.concat(),
characteristic_predicate: None,
predicates: vec![],
predicates: vec![create_describe_ql_class(&class_name)],
}));
}
node_types::Entry::Table { type_name, fields } => {
@@ -444,7 +474,7 @@ pub fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<ql::TopLevel> {
.concat(),
characteristic_predicate: None,
predicates: vec![
create_to_string_predicate(&main_class_name),
create_describe_ql_class(&main_class_name),
create_get_location_predicate(&main_table_name, main_table_arity),
],
};
@@ -479,7 +509,7 @@ pub fn convert_nodes(nodes: &Vec<node_types::Entry>) -> Vec<ql::TopLevel> {
main_class.predicates.push(ql::Predicate {
name: "getAFieldOrChild".to_owned(),
overridden: true,
return_type: Some(ql::Type::Normal("Top".to_owned())),
return_type: Some(ql::Type::Normal("AstNode".to_owned())),
formal_parameters: vec![],
body: ql::Expression::Or(get_child_exprs),
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
/**
* Provides queries to pretty-print a Ruby abstract syntax tree as a graph.
*
* By default, this will print the AST for all nodes in the database. To change
* this behavior, extend `PrintASTConfiguration` and override `shouldPrintNode`
* to hold for only the AST nodes you wish to view.
*/
import codeql_ruby.ast
/**
* The query can extend this class to control which nodes are printed.
*/
class PrintAstConfiguration extends string {
PrintAstConfiguration() { this = "PrintAstConfiguration" }
/**
* Holds if the given node should be printed.
*/
predicate shouldPrintNode(AstNode n) { any() }
}
/**
* A node in the output tree.
*/
class PrintAstNode extends AstNode {
string getProperty(string key) {
key = "semmle.label" and
result = this.toString()
}
/**
* Gets a textual representation of this node in the PrintAST output tree.
*/
override string toString() { result = "[" + this.describeQlClass() + "] " + super.toString() }
/**
* Holds if this node should be printed in the output. By default, all nodes
* are printed, but the query can override
* `PrintAstConfiguration.shouldPrintNode` to filter the output.
*/
predicate shouldPrint() { shouldPrintNode(this) }
}
private predicate shouldPrintNode(AstNode n) {
exists(PrintAstConfiguration config | config.shouldPrintNode(n))
}
/**
* 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) {
source.shouldPrint() and
target.shouldPrint() and
target = source.getAFieldOrChild() and
key = "semmle.label" and
value = "edge"
}
/**
* Holds if property `key` of the graph has the given `value`.
*/
query predicate graphProperties(string key, string value) {
key = "semmle.graphKind" and value = "tree"
}

32
ql/src/printAst.ql Normal file
View File

@@ -0,0 +1,32 @@
/**
* @name Print AST
* @description Produces a representation of a file's Abstract Syntax Tree.
* This query is used by the VS Code extension.
* @id ruby/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/
import codeql_ruby.printAst
/**
* The source file to generate an AST from.
*/
external string selectedSourceFile();
/**
* Returns an appropriately encoded version of a filename `name`
* passed by the VS Code extension in order to coincide with the
* output of `.getFile()` on locatable entities.
*/
cached
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }
/**
* Overrides the configuration to print only nodes in the selected source file.
*/
class Cfg extends PrintAstConfiguration {
override predicate shouldPrintNode(AstNode n) {
n.getLocation().getFile() = getEncodedFile(selectedSourceFile())
}
}

View File

@@ -1856,5 +1856,5 @@ tilde_unnamed_def(
int loc: @location ref
);
@top = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @empty_statement | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_call | @method_parameters | @module | @next | @operator | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol | @symbol_array | @then | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield | @bang_unnamed | @bangequal_unnamed | @bangtilde_unnamed | @dquote_unnamed | @hashlbrace_unnamed | @percent_unnamed | @percentequal_unnamed | @percentilparen_unnamed | @percentwlparen_unnamed | @ampersand_unnamed | @ampersandampersand_unnamed | @ampersandampersandequal_unnamed | @ampersanddot_unnamed | @ampersandequal_unnamed | @lparen_unnamed | @rparen_unnamed | @star_unnamed | @starstar_unnamed | @starstarequal_unnamed | @starequal_unnamed | @plus_unnamed | @plusequal_unnamed | @plusat_unnamed | @comma_unnamed | @minus_unnamed | @minusequal_unnamed | @minusrangle_unnamed | @minusat_unnamed | @dot_unnamed | @dotdot_unnamed | @dotdotdot_unnamed | @slash_unnamed | @slashequal_unnamed | @colon_unnamed | @colondquote_unnamed | @coloncolon_unnamed | @semicolon_unnamed | @langle_unnamed | @langlelangle_unnamed | @langlelangleequal_unnamed | @langleequal_unnamed | @langleequalrangle_unnamed | @equal_unnamed | @equalequal_unnamed | @equalequalequal_unnamed | @equalrangle_unnamed | @equaltilde_unnamed | @rangle_unnamed | @rangleequal_unnamed | @ranglerangle_unnamed | @ranglerangleequal_unnamed | @question_unnamed | @b_e_g_i_n__unnamed | @e_n_d__unnamed | @lbracket_unnamed | @lbracketrbracket_unnamed | @lbracketrbracketequal_unnamed | @rbracket_unnamed | @caret_unnamed | @caretequal_unnamed | @underscore__e_n_d____unnamed | @backtick_unnamed | @alias_unnamed | @and_unnamed | @begin_unnamed | @break_unnamed | @case_unnamed | @character | @class_unnamed | @class_variable | @comment | @complex | @constant | @def_unnamed | @definedquestion_unnamed | @do_unnamed | @else_unnamed | @elsif_unnamed | @end_unnamed | @ensure_unnamed | @escape_sequence | @false | @float__ | @for_unnamed | @global_variable | @heredoc_beginning | @heredoc_end | @identifier | @if_unnamed | @in_unnamed | @instance_variable | @integer | @module_unnamed | @next_unnamed | @nil | @not_unnamed | @or_unnamed | @r_unnamed | @redo_unnamed | @rescue_unnamed | @retry_unnamed | @return_unnamed | @self | @super | @then_unnamed | @true | @undef_unnamed | @uninterpreted | @unless_unnamed | @until_unnamed | @when_unnamed | @while_unnamed | @yield_unnamed | @lbrace_unnamed | @pipe_unnamed | @pipeequal_unnamed | @pipepipe_unnamed | @pipepipeequal_unnamed | @rbrace_unnamed | @tilde_unnamed
@ast_node = @alias | @argument_list | @array | @assignment | @bare_string | @bare_symbol | @begin | @begin_block | @binary | @block | @block_argument | @block_parameter | @block_parameters | @break | @call | @case__ | @chained_string | @class | @conditional | @destructured_left_assignment | @destructured_parameter | @do | @do_block | @element_reference | @else | @elsif | @empty_statement | @end_block | @ensure | @exception_variable | @exceptions | @for | @hash | @hash_splat_argument | @hash_splat_parameter | @heredoc_body | @if | @if_modifier | @in | @interpolation | @keyword_parameter | @lambda | @lambda_parameters | @left_assignment_list | @method | @method_call | @method_parameters | @module | @next | @operator | @operator_assignment | @optional_parameter | @pair | @parenthesized_statements | @pattern | @program | @range | @rational | @redo | @regex | @rescue | @rescue_modifier | @rest_assignment | @retry | @return | @right_assignment_list | @scope_resolution | @setter | @singleton_class | @singleton_method | @splat_argument | @splat_parameter | @string__ | @string_array | @subshell | @superclass | @symbol | @symbol_array | @then | @unary | @undef | @unless | @unless_modifier | @until | @until_modifier | @when | @while | @while_modifier | @yield | @bang_unnamed | @bangequal_unnamed | @bangtilde_unnamed | @dquote_unnamed | @hashlbrace_unnamed | @percent_unnamed | @percentequal_unnamed | @percentilparen_unnamed | @percentwlparen_unnamed | @ampersand_unnamed | @ampersandampersand_unnamed | @ampersandampersandequal_unnamed | @ampersanddot_unnamed | @ampersandequal_unnamed | @lparen_unnamed | @rparen_unnamed | @star_unnamed | @starstar_unnamed | @starstarequal_unnamed | @starequal_unnamed | @plus_unnamed | @plusequal_unnamed | @plusat_unnamed | @comma_unnamed | @minus_unnamed | @minusequal_unnamed | @minusrangle_unnamed | @minusat_unnamed | @dot_unnamed | @dotdot_unnamed | @dotdotdot_unnamed | @slash_unnamed | @slashequal_unnamed | @colon_unnamed | @colondquote_unnamed | @coloncolon_unnamed | @semicolon_unnamed | @langle_unnamed | @langlelangle_unnamed | @langlelangleequal_unnamed | @langleequal_unnamed | @langleequalrangle_unnamed | @equal_unnamed | @equalequal_unnamed | @equalequalequal_unnamed | @equalrangle_unnamed | @equaltilde_unnamed | @rangle_unnamed | @rangleequal_unnamed | @ranglerangle_unnamed | @ranglerangleequal_unnamed | @question_unnamed | @b_e_g_i_n__unnamed | @e_n_d__unnamed | @lbracket_unnamed | @lbracketrbracket_unnamed | @lbracketrbracketequal_unnamed | @rbracket_unnamed | @caret_unnamed | @caretequal_unnamed | @underscore__e_n_d____unnamed | @backtick_unnamed | @alias_unnamed | @and_unnamed | @begin_unnamed | @break_unnamed | @case_unnamed | @character | @class_unnamed | @class_variable | @comment | @complex | @constant | @def_unnamed | @definedquestion_unnamed | @do_unnamed | @else_unnamed | @elsif_unnamed | @end_unnamed | @ensure_unnamed | @escape_sequence | @false | @float__ | @for_unnamed | @global_variable | @heredoc_beginning | @heredoc_end | @identifier | @if_unnamed | @in_unnamed | @instance_variable | @integer | @module_unnamed | @next_unnamed | @nil | @not_unnamed | @or_unnamed | @r_unnamed | @redo_unnamed | @rescue_unnamed | @retry_unnamed | @return_unnamed | @self | @super | @then_unnamed | @true | @undef_unnamed | @uninterpreted | @unless_unnamed | @until_unnamed | @when_unnamed | @while_unnamed | @yield_unnamed | @lbrace_unnamed | @pipe_unnamed | @pipeequal_unnamed | @pipepipe_unnamed | @pipepipeequal_unnamed | @rbrace_unnamed | @tilde_unnamed