Initial work on user-facing AST library

This commit is contained in:
Nick Rolfe
2020-11-25 17:53:13 +00:00
parent 2082171bdf
commit c598dc6b5c
29 changed files with 2184 additions and 1564 deletions

View File

@@ -46,8 +46,8 @@ jobs:
- uses: actions/upload-artifact@v2
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: ruby_ast.qll
path: ql/src/codeql_ruby/ast.qll
name: Generated.qll
path: ql/src/codeql_ruby/Generated.qll
- uses: actions/upload-artifact@v2
with:
name: extractor-${{ matrix.os }}

14
Cargo.lock generated
View File

@@ -64,9 +64,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cc"
version = "1.0.62"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40"
checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15"
[[package]]
name = "cfg-if"
@@ -397,9 +397,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
[[package]]
name = "strsim"
@@ -409,9 +409,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "syn"
version = "1.0.48"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6"
dependencies = [
"proc-macro2",
"quote",
@@ -535,7 +535,7 @@ dependencies = [
[[package]]
name = "tree-sitter-ruby"
version = "0.17.0"
source = "git+https://github.com/tree-sitter/tree-sitter-ruby.git?rev=93632008c63e0577413bbedf120fb92a96397785#93632008c63e0577413bbedf120fb92a96397785"
source = "git+https://github.com/nickrolfe/tree-sitter-ruby.git?branch=refinements#cf76d2c60dac54259912f0c3c4d5c73e78304ace"
dependencies = [
"cc",
"tree-sitter",

View File

@@ -12,13 +12,13 @@ cargo build --release
## Generating the database schema and QL library
The generated `ql/src/ruby.dbscheme` and `ql/src/codeql_ruby/ast.qll` files are included in the repository, but they can be re-generated as follows:
The generated `ql/src/ruby.dbscheme` and `ql/src/codeql_ruby/Generated.qll` files are included in the repository, but they can be re-generated as follows:
```bash
# Run the generator
cargo run --release -p ruby-generator
# Then auto-format the QL library
codeql query format -i ql/src/codeql_ruby/ast.qll
codeql query format -i ql/src/codeql_ruby/Generated.qll
```
## Building a CodeQL database for a Ruby program

View File

@@ -1,7 +1,7 @@
cargo build --release
cargo run --release -p ruby-generator
codeql query format -i ql\src\codeql_ruby\ast.qll
codeql query format -i ql\src\codeql_ruby\Generated.qll
rm -Recurse -Force extractor-pack
mkdir extractor-pack | Out-Null

View File

@@ -13,7 +13,7 @@ fi
cargo build --release
cargo run --release -p ruby-generator
codeql query format -i ql/src/codeql_ruby/ast.qll
codeql query format -i ql/src/codeql_ruby/Generated.qll
rm -rf extractor-pack
mkdir -p extractor-pack

View File

@@ -10,7 +10,7 @@ edition = "2018"
flate2 = "1.0"
node-types = { path = "../node-types" }
tree-sitter = "0.17"
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "93632008c63e0577413bbedf120fb92a96397785" }
tree-sitter-ruby = { git = "https://github.com/nickrolfe/tree-sitter-ruby.git", branch = "refinements" }
clap = "2.33"
tracing = "0.1"
tracing-subscriber = { version = "0.2", features = ["env-filter"] }

View File

@@ -10,4 +10,4 @@ edition = "2018"
node-types = { path = "../node-types" }
tracing = "0.1"
tracing-subscriber = { version = "0.2", features = ["env-filter"] }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "93632008c63e0577413bbedf120fb92a96397785" }
tree-sitter-ruby = { git = "https://github.com/nickrolfe/tree-sitter-ruby.git", branch = "refinements" }

View File

@@ -574,7 +574,7 @@ fn main() {
name: "Ruby".to_owned(),
node_types: tree_sitter_ruby::NODE_TYPES,
dbscheme_path: PathBuf::from("ql/src/ruby.dbscheme"),
ql_library_path: PathBuf::from("ql/src/codeql_ruby/ast.qll"),
ql_library_path: PathBuf::from("ql/src/codeql_ruby/Generated.qll"),
};
match node_types::read_node_types_str(&ruby.node_types) {
Err(e) => {

View File

@@ -214,10 +214,12 @@ pub fn write<'a>(
" * Automatically generated from the tree-sitter grammar; do not edit\n"
)?;
write!(file, " */\n\n")?;
write!(file, "module Generated {{\n")?;
for element in elements {
write!(file, "{}\n\n", &element)?;
}
write!(file, "}}")?;
Ok(())
}

View File

@@ -0,0 +1,40 @@
import codeql_ruby.Method
import codeql_ruby.Parameter
private import codeql_ruby.Generated
class Location = Generated::Location;
/**
* A node in the abstract syntax tree. This class is the base class for all Ruby
* program elements.
*/
class AstNode extends @ast_node {
/**
* Gets the name of a primary CodeQL class to which this node belongs.
*
* This predicate always has a result. If no primary class can be
* determined, the result is `"???"`. If multiple primary classes match,
* this predicate can have multiple results.
*/
string describeQlClass() { result = "???" }
/** Gets a textual representation of this node. */
string toString() { result = "AstNode" }
/** Gets the location if this node. */
Location getLocation() { result = this.(Generated::AstNode).getLocation() }
}
/**
* Models program elements for destructured patterns.
*/
abstract class Pattern extends AstNode {
/** Gets the number of elements in this pattern. */
abstract int getNumberOfElements();
/** Gets the nth element in this pattern. */
abstract AstNode getElement(int n);
/** Gets an element in this pattern. */
AstNode getAnElement() { result = this.getElement(_) }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
import codeql_ruby.AST
private import codeql_ruby.Generated
/** A Ruby method. */
class Method extends @method, AstNode {
Generated::Method generated;
Method() { generated = this }
override string describeQlClass() { result = "Method" }
override string toString() { result = this.getName() }
/** Gets the name of the method. */
string getName() {
result = generated.getName().(Generated::Token).getValue() or
// TODO: use hand-written Symbol class
result = generated.getName().(Generated::Symbol).toString() or
result = generated.getName().(Generated::Setter).getName().getValue() + "="
}
/**
* Holds if this is a setter method, as in the following example:
* ```
* class Person
* def name=(n)
* @name = n
* end
* end
* ```
*/
predicate isSetter() { generated.getName() instanceof Generated::Setter }
/** Gets the number of parameters of this method. */
int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets a parameter of this method. */
Parameter getAParameter() { result = this.getParameter(_) }
/** Gets the nth parameter of this method. */
Parameter getParameter(int n) { result = generated.getParameters().getChild(n) }
}
/**
* A Ruby lambda (anonymous method). For example:
* ```
* -> (x) { x + 1 }
* ```
*/
class Lambda extends @lambda, AstNode {
Generated::Lambda generated;
Lambda() { generated = this }
override string describeQlClass() { result = "Lambda" }
override string toString() { result = "-> { ... }" }
/** Gets the number of parameters of this lambda. */
int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets a parameter of this lambda. */
Parameter getAParameter() { result = this.getParameter(_) }
/** Gets the nth parameter of this lambda. */
Parameter getParameter(int n) { result = generated.getParameters().getChild(n) }
}
/** A Ruby block. */
abstract class Block extends AstNode {
/** Gets the number of parameters of this block. */
abstract int getNumberOfParameters();
/** Gets the nth parameter of this block. */
abstract Parameter getParameter(int n);
/** Gets a parameter of this block. */
Parameter getAParameter() { result = this.getParameter(_) }
// TODO: body/statements
}
/** A Ruby block enclosed within `do` and `end`. */
class DoBlock extends @do_block, Block {
Generated::DoBlock generated;
DoBlock() { generated = this }
override string describeQlClass() { result = "DoBlock" }
override string toString() { result = "| ... |" }
/** Gets the number of parameters of this block. */
override int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets the nth parameter of this block. */
override Parameter getParameter(int n) { result = generated.getParameters().getChild(n) }
}
/**
* A Ruby block defined using curly braces, e.g. in the following code:
* ```
* names.each { |name| puts name }
* ```
*/
class BraceBlock extends @block, Block {
Generated::Block generated;
BraceBlock() { generated = this }
override string describeQlClass() { result = "BraceBlock" }
override string toString() { result = "{ ... }" }
/** Gets the number of parameters of this block. */
override int getNumberOfParameters() { result = count(this.getAParameter()) }
/** Gets the nth parameter of this block. */
override Parameter getParameter(int n) { result = generated.getParameters().getChild(n) }
}

View File

@@ -0,0 +1,182 @@
import codeql_ruby.AST
private import codeql_ruby.Generated
/**
* A parameter to a block, lambda, or method.
*/
abstract class Parameter extends AstNode {
/**
* Gets the position of this parameter in the parent block, lambda, or
* method's parameter list.
*/
int getPosition() {
exists(Method m | m.getParameter(result) = this) or
exists(Block b | b.getParameter(result) = this) or
exists(Lambda l | l.getParameter(result) = this)
}
}
/**
* A parameter that is a block. For example, `&bar` in the following code:
* ```
* def foo(&bar)
* bar.call if block_given?
* end
* ```
*/
class BlockParameter extends @block_parameter, Parameter {
Generated::BlockParameter generated;
BlockParameter() { generated = this }
override string describeQlClass() { result = "BlockParameter" }
override string toString() { result = "&" + this.getName() }
/** Gets the name of the parameter. */
string getName() { result = generated.getName().getValue() }
}
/**
* A parameter that is destructured. For example, the parameter `(a, b)` in the
* following code:
* ```
* pairs.each do |(a, b)|
* puts a + b
* end
* ```
*/
class PatternParameter extends @destructured_parameter, Parameter, Pattern {
Generated::DestructuredParameter generated;
PatternParameter() { generated = this }
override string describeQlClass() { result = "PatternParameter" }
override string toString() { result = "(..., ...)" }
/**
* Gets the number of parameters of this destructuring.
*/
override int getNumberOfElements() { result = count(this.getElement(_)) }
/**
* Gets the nth parameter of this pattern.
*/
override AstNode getElement(int n) { result = generated.getChild(n) }
}
/**
* A hash-splat (or double-splat) parameter. For example, `**options` in the
* following code:
* ```
* def foo(bar, **options)
* ...
* end
* ```
*/
class HashSplatParameter extends @hash_splat_parameter, Parameter {
Generated::HashSplatParameter generated;
HashSplatParameter() { generated = this }
override string describeQlClass() { result = "HashSplatParameter" }
override string toString() { result = "**" + this.getName() }
/** Gets the name of the parameter. */
string getName() { result = generated.getName().getValue() }
}
/**
* TODO
*/
class KeywordParameter extends @keyword_parameter, Parameter {
Generated::KeywordParameter generated;
KeywordParameter() { generated = this }
override string describeQlClass() { result = "KeywordParameter" }
/** Gets the name of the parameter. */
string getName() { result = generated.getName().getValue() }
/**
* Gets the default value, i.e. the value assigned to the parameter when one
* is not provided by the caller. If the parameter is mandatory and does not
* have a default value, this predicate has no result.
* TODO: better return type (Expr?)
*/
AstNode getDefaultValue() { result = generated.getValue() }
override string toString() { result = this.getName() }
}
/**
* An optional parameter. For example, the parameter `name` in the following
* code:
* ```
* def say_hello(name = 'Anon')
* puts "hello #{name}"
* end
* ```
*/
class OptionalParameter extends @optional_parameter, Parameter {
Generated::OptionalParameter generated;
OptionalParameter() { generated = this }
override string describeQlClass() { result = "OptionalParameter" }
override string toString() { result = this.getName() }
/** Gets the name of the parameter. */
string getName() { result = generated.getName().getValue() }
/**
* Gets the default value, i.e. the value assigned to the parameter when one
* is not provided by the caller.
* TODO: better return type (Expr?)
*/
AstNode getDefaultValue() { result = generated.getValue() }
}
/**
* A splat parameter. For example, `*values` in the following code:
* ```
* def foo(bar, *values)
* ...
* end
* ```
*/
class SplatParameter extends @splat_parameter, Parameter {
Generated::SplatParameter generated;
SplatParameter() { generated = this }
override string describeQlClass() { result = "SplatParameter" }
override string toString() { result = this.getName() }
/** Gets the name of the parameter. */
string getName() { result = generated.getName().getValue() }
}
/**
* An identifier that is a parameter in a block, lambda, or method.
*/
class IdentifierParameter extends @token_identifier, Parameter {
IdentifierParameter() {
block_parameters_child(_, _, this) or
destructured_parameter_child(_, _, this) or
lambda_parameters_child(_, _, this) or
method_parameters_child(_, _, this)
}
override string describeQlClass() { result = "IdentifierParameter" }
override string toString() { result = this.getName() }
/** Gets the name of the parameter. */
string getName() { result = this.(Generated::Identifier).getValue() }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/** Provides classes representing basic blocks. */
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import codeql_ruby.controlflow.ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import SuccessorTypes

View File

@@ -1,6 +1,6 @@
/** Provides classes representing the control flow graph. */
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import codeql_ruby.controlflow.BasicBlocks
private import SuccessorTypes
private import internal.ControlFlowGraphImpl

View File

@@ -3,7 +3,7 @@
* will likely be part of the hand-written user-facing AST layer.
*/
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
class LogicalNotAstNode extends Unary {
AstNode operand;

View File

@@ -4,7 +4,7 @@
* A completion represents how a statement or expression terminates.
*/
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import codeql_ruby.controlflow.ControlFlowGraph
private import AstNodes
private import NonReturning

View File

@@ -1,4 +1,4 @@
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import codeql_ruby.controlflow.ControlFlowGraph
private import Completion
private import Splitting

View File

@@ -31,7 +31,7 @@
* caught up by its surrounding loop and turned into a `NormalCompletion`.
*/
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import AstNodes
private import codeql_ruby.controlflow.ControlFlowGraph
private import Completion

View File

@@ -1,6 +1,6 @@
/** Provides a simple analysis for identifying calls that will not return. */
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import Completion
/** A call that definitely does not return (conservative analysis). */

View File

@@ -2,7 +2,7 @@
* Provides classes and predicates relevant for splitting the control flow graph.
*/
private import codeql_ruby.ast
private import codeql_ruby.Generated::Generated
private import AstNodes
private import Completion
private import ControlFlowGraphImpl

View File

@@ -6,7 +6,7 @@
* to hold for only the AST nodes you wish to view.
*/
import codeql_ruby.ast
import codeql_ruby.Generated
/**
* The query can extend this class to control which nodes are printed.
@@ -17,13 +17,13 @@ class PrintAstConfiguration extends string {
/**
* Holds if the given node should be printed.
*/
predicate shouldPrintNode(AstNode n) { any() }
predicate shouldPrintNode(Generated::AstNode n) { any() }
}
/**
* A node in the output tree.
*/
class PrintAstNode extends AstNode {
class PrintAstNode extends Generated::AstNode {
string getProperty(string key) {
key = "semmle.label" and
result = "[" + this.describeQlClass() + "] " + this.toString()
@@ -36,15 +36,15 @@ class PrintAstNode extends AstNode {
*/
predicate shouldPrint() {
(
not this instanceof Token
not this instanceof Generated::Token
or
exists(AstNode parent | parent.getAFieldOrChild() = this)
exists(Generated::AstNode parent | parent.getAFieldOrChild() = this)
) and
shouldPrintNode(this)
}
}
private predicate shouldPrintNode(AstNode n) {
private predicate shouldPrintNode(Generated::AstNode n) {
exists(PrintAstConfiguration config | config.shouldPrintNode(n))
}

View File

@@ -8,6 +8,7 @@
*/
import codeql_ruby.printAst
import codeql.files.FileSystem
/**
* The source file to generate an AST from.
@@ -36,7 +37,7 @@ File getFileBySourceArchiveName(string name) {
* Overrides the configuration to print only nodes in the selected source file.
*/
class Cfg extends PrintAstConfiguration {
override predicate shouldPrintNode(AstNode n) {
override predicate shouldPrintNode(Generated::AstNode n) {
n.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}

View File

@@ -201,7 +201,12 @@ binary_def(
int loc: @location ref
);
@block_child_type = @block_parameters | @token_empty_statement | @underscore_statement
block_parameters(
unique int block: @block ref,
unique int block_parameters: @block_parameters ref
);
@block_child_type = @token_empty_statement | @underscore_statement
#keyset[block, index]
block_child(
@@ -399,7 +404,12 @@ do_def(
int loc: @location ref
);
@do_block_child_type = @block_parameters | @else | @ensure | @rescue | @token_empty_statement | @underscore_statement
do_block_parameters(
unique int do_block: @do_block ref,
unique int block_parameters: @block_parameters ref
);
@do_block_child_type = @else | @ensure | @rescue | @token_empty_statement | @underscore_statement
#keyset[do_block, index]
do_block_child(
@@ -1063,7 +1073,7 @@ setter_def(
unique int id: @setter,
int parent: @ast_node_parent ref,
int parent_index: int ref,
int child: @token_identifier ref,
int name: @token_identifier ref,
int loc: @location ref
);

1
ql/src/ruby.qll Normal file
View File

@@ -0,0 +1 @@
import codeql_ruby.AST

View File

@@ -0,0 +1,139 @@
idParams
| params.rb:4:30:4:32 | foo | foo |
| params.rb:4:35:4:37 | bar | bar |
| params.rb:4:40:4:42 | baz | baz |
| params.rb:9:15:9:17 | key | key |
| params.rb:9:20:9:24 | value | value |
| params.rb:14:11:14:13 | foo | foo |
| params.rb:14:16:14:18 | bar | bar |
| params.rb:17:32:17:32 | a | a |
| params.rb:17:35:17:35 | b | b |
| params.rb:17:38:17:38 | c | c |
| params.rb:22:16:22:16 | a | a |
| params.rb:22:19:22:19 | b | b |
| params.rb:25:24:25:28 | first | first |
| params.rb:25:31:25:36 | second | second |
| params.rb:25:41:25:45 | third | third |
| params.rb:25:48:25:53 | fourth | fourth |
| params.rb:30:23:30:28 | wibble | wibble |
| params.rb:34:16:34:18 | val | val |
| params.rb:38:26:38:26 | x | x |
| params.rb:41:32:41:32 | x | x |
| params.rb:53:34:53:34 | x | x |
| params.rb:58:33:58:36 | val1 | val1 |
| params.rb:65:29:65:32 | name | name |
| params.rb:70:35:70:35 | a | a |
blockParams
| params.rb:46:28:46:33 | &block | block |
| params.rb:62:29:62:34 | &block | block |
patternParams
| params.rb:17:31:17:39 | (..., ...) | params.rb:17:32:17:32 | a | 0 |
| params.rb:17:31:17:39 | (..., ...) | params.rb:17:35:17:35 | b | 1 |
| params.rb:17:31:17:39 | (..., ...) | params.rb:17:38:17:38 | c | 2 |
| params.rb:22:15:22:20 | (..., ...) | params.rb:22:16:22:16 | a | 0 |
| params.rb:22:15:22:20 | (..., ...) | params.rb:22:19:22:19 | b | 1 |
| params.rb:25:23:25:37 | (..., ...) | params.rb:25:24:25:28 | first | 0 |
| params.rb:25:23:25:37 | (..., ...) | params.rb:25:31:25:36 | second | 1 |
| params.rb:25:40:25:54 | (..., ...) | params.rb:25:41:25:45 | third | 0 |
| params.rb:25:40:25:54 | (..., ...) | params.rb:25:48:25:53 | fourth | 1 |
splatParams
| params.rb:30:31:30:36 | splat | splat |
| params.rb:34:21:34:26 | splat | splat |
| params.rb:38:29:38:33 | blah | blah |
hashSplatParams
| params.rb:30:39:30:52 | **double_splat | double_splat |
| params.rb:34:29:34:42 | **double_splat | double_splat |
| params.rb:38:36:38:43 | **wibble | wibble |
keywordParams
| params.rb:41:35:41:38 | foo | foo | (none) |
| params.rb:41:41:41:46 | bar | bar | AstNode |
| params.rb:49:28:49:30 | xx | xx | (none) |
| params.rb:49:33:49:39 | yy | yy | AstNode |
| params.rb:53:37:53:38 | y | y | (none) |
| params.rb:53:41:53:44 | z | z | AstNode |
optionalParams
| params.rb:58:39:58:46 | val2 | val2 | params.rb:58:46:58:46 | AstNode |
| params.rb:58:49:58:58 | val3 | val3 | params.rb:58:56:58:58 | AstNode |
| params.rb:65:35:65:42 | age | age | params.rb:65:41:65:42 | AstNode |
| params.rb:70:38:70:45 | b | b | params.rb:70:42:70:45 | AstNode |
| params.rb:70:48:70:53 | c | c | params.rb:70:52:70:53 | AstNode |
paramsInMethods
| params.rb:4:1:5:3 | identifier_method_params | 0 | params.rb:4:30:4:32 | foo | IdentifierParameter |
| params.rb:4:1:5:3 | identifier_method_params | 1 | params.rb:4:35:4:37 | bar | IdentifierParameter |
| params.rb:4:1:5:3 | identifier_method_params | 2 | params.rb:4:40:4:42 | baz | IdentifierParameter |
| params.rb:17:1:18:3 | destructured_method_param | 0 | params.rb:17:31:17:39 | (..., ...) | PatternParameter |
| params.rb:30:1:31:3 | method_with_splat | 0 | params.rb:30:23:30:28 | wibble | IdentifierParameter |
| params.rb:30:1:31:3 | method_with_splat | 1 | params.rb:30:31:30:36 | splat | SplatParameter |
| params.rb:30:1:31:3 | method_with_splat | 2 | params.rb:30:39:30:52 | **double_splat | HashSplatParameter |
| params.rb:41:1:43:3 | method_with_keyword_params | 0 | params.rb:41:32:41:32 | x | IdentifierParameter |
| params.rb:41:1:43:3 | method_with_keyword_params | 1 | params.rb:41:35:41:38 | foo | KeywordParameter |
| params.rb:41:1:43:3 | method_with_keyword_params | 2 | params.rb:41:41:41:46 | bar | KeywordParameter |
| params.rb:46:1:48:3 | use_block_with_keyword | 0 | params.rb:46:28:46:33 | &block | BlockParameter |
| params.rb:58:1:59:3 | method_with_optional_params | 0 | params.rb:58:33:58:36 | val1 | IdentifierParameter |
| params.rb:58:1:59:3 | method_with_optional_params | 1 | params.rb:58:39:58:46 | val2 | OptionalParameter |
| params.rb:58:1:59:3 | method_with_optional_params | 2 | params.rb:58:49:58:58 | val3 | OptionalParameter |
| params.rb:62:1:64:3 | use_block_with_optional | 0 | params.rb:62:29:62:34 | &block | BlockParameter |
paramsInBlocks
| params.rb:9:11:11:3 | \| ... \| | 0 | params.rb:9:15:9:17 | key | IdentifierParameter |
| params.rb:9:11:11:3 | \| ... \| | 1 | params.rb:9:20:9:24 | value | IdentifierParameter |
| params.rb:22:12:22:32 | { ... } | 0 | params.rb:22:15:22:20 | (..., ...) | PatternParameter |
| params.rb:34:12:35:3 | \| ... \| | 0 | params.rb:34:16:34:18 | val | IdentifierParameter |
| params.rb:34:12:35:3 | \| ... \| | 1 | params.rb:34:21:34:26 | splat | SplatParameter |
| params.rb:34:12:35:3 | \| ... \| | 2 | params.rb:34:29:34:42 | **double_splat | HashSplatParameter |
| params.rb:49:24:51:3 | \| ... \| | 0 | params.rb:49:28:49:30 | xx | KeywordParameter |
| params.rb:49:24:51:3 | \| ... \| | 1 | params.rb:49:33:49:39 | yy | KeywordParameter |
| params.rb:65:25:67:3 | \| ... \| | 0 | params.rb:65:29:65:32 | name | IdentifierParameter |
| params.rb:65:25:67:3 | \| ... \| | 1 | params.rb:65:35:65:42 | age | OptionalParameter |
paramsInLambdas
| params.rb:14:7:14:33 | -> { ... } | 0 | params.rb:14:11:14:13 | foo | IdentifierParameter |
| params.rb:14:7:14:33 | -> { ... } | 1 | params.rb:14:16:14:18 | bar | IdentifierParameter |
| params.rb:25:19:27:1 | -> { ... } | 0 | params.rb:25:23:25:37 | (..., ...) | PatternParameter |
| params.rb:25:19:27:1 | -> { ... } | 1 | params.rb:25:40:25:54 | (..., ...) | PatternParameter |
| params.rb:38:22:38:47 | -> { ... } | 0 | params.rb:38:26:38:26 | x | IdentifierParameter |
| params.rb:38:22:38:47 | -> { ... } | 1 | params.rb:38:29:38:33 | blah | SplatParameter |
| params.rb:38:22:38:47 | -> { ... } | 2 | params.rb:38:36:38:43 | **wibble | HashSplatParameter |
| params.rb:53:30:55:1 | -> { ... } | 0 | params.rb:53:34:53:34 | x | IdentifierParameter |
| params.rb:53:30:55:1 | -> { ... } | 1 | params.rb:53:37:53:38 | y | KeywordParameter |
| params.rb:53:30:55:1 | -> { ... } | 2 | params.rb:53:41:53:44 | z | KeywordParameter |
| params.rb:70:31:70:64 | -> { ... } | 0 | params.rb:70:35:70:35 | a | IdentifierParameter |
| params.rb:70:31:70:64 | -> { ... } | 1 | params.rb:70:38:70:45 | b | OptionalParameter |
| params.rb:70:31:70:64 | -> { ... } | 2 | params.rb:70:48:70:53 | c | OptionalParameter |
#select
| params.rb:4:30:4:32 | foo | 0 | IdentifierParameter |
| params.rb:4:35:4:37 | bar | 1 | IdentifierParameter |
| params.rb:4:40:4:42 | baz | 2 | IdentifierParameter |
| params.rb:9:15:9:17 | key | 0 | IdentifierParameter |
| params.rb:9:20:9:24 | value | 1 | IdentifierParameter |
| params.rb:14:11:14:13 | foo | 0 | IdentifierParameter |
| params.rb:14:16:14:18 | bar | 1 | IdentifierParameter |
| params.rb:17:31:17:39 | (..., ...) | 0 | PatternParameter |
| params.rb:22:15:22:20 | (..., ...) | 0 | PatternParameter |
| params.rb:25:23:25:37 | (..., ...) | 0 | PatternParameter |
| params.rb:25:40:25:54 | (..., ...) | 1 | PatternParameter |
| params.rb:30:23:30:28 | wibble | 0 | IdentifierParameter |
| params.rb:30:31:30:36 | splat | 1 | SplatParameter |
| params.rb:30:39:30:52 | **double_splat | 2 | HashSplatParameter |
| params.rb:34:16:34:18 | val | 0 | IdentifierParameter |
| params.rb:34:21:34:26 | splat | 1 | SplatParameter |
| params.rb:34:29:34:42 | **double_splat | 2 | HashSplatParameter |
| params.rb:38:26:38:26 | x | 0 | IdentifierParameter |
| params.rb:38:29:38:33 | blah | 1 | SplatParameter |
| params.rb:38:36:38:43 | **wibble | 2 | HashSplatParameter |
| params.rb:41:32:41:32 | x | 0 | IdentifierParameter |
| params.rb:41:35:41:38 | foo | 1 | KeywordParameter |
| params.rb:41:41:41:46 | bar | 2 | KeywordParameter |
| params.rb:46:28:46:33 | &block | 0 | BlockParameter |
| params.rb:49:28:49:30 | xx | 0 | KeywordParameter |
| params.rb:49:33:49:39 | yy | 1 | KeywordParameter |
| params.rb:53:34:53:34 | x | 0 | IdentifierParameter |
| params.rb:53:37:53:38 | y | 1 | KeywordParameter |
| params.rb:53:41:53:44 | z | 2 | KeywordParameter |
| params.rb:58:33:58:36 | val1 | 0 | IdentifierParameter |
| params.rb:58:39:58:46 | val2 | 1 | OptionalParameter |
| params.rb:58:49:58:58 | val3 | 2 | OptionalParameter |
| params.rb:62:29:62:34 | &block | 0 | BlockParameter |
| params.rb:65:29:65:32 | name | 0 | IdentifierParameter |
| params.rb:65:35:65:42 | age | 1 | OptionalParameter |
| params.rb:70:35:70:35 | a | 0 | IdentifierParameter |
| params.rb:70:38:70:45 | b | 1 | OptionalParameter |
| params.rb:70:48:70:53 | c | 2 | OptionalParameter |

View File

@@ -0,0 +1,46 @@
import ruby
////////////////////////////////////////////////////////////////////////////////
// Query predicates for various types of parameter
query predicate idParams(IdentifierParameter ip, string name) { name = ip.getName() }
query predicate blockParams(BlockParameter bp, string name) { name = bp.getName() }
query predicate patternParams(PatternParameter pp, Parameter child, int childIndex) {
pp.getElement(childIndex) = child
}
query predicate splatParams(SplatParameter sp, string name) { name = sp.getName() }
query predicate hashSplatParams(HashSplatParameter hsp, string name) { name = hsp.getName() }
query predicate keywordParams(KeywordParameter kp, string name, string defaultValueStr) {
name = kp.getName() and
if exists(kp.getDefaultValue())
then defaultValueStr = kp.getDefaultValue().toString()
else defaultValueStr = "(none)"
}
query predicate optionalParams(OptionalParameter op, string name, AstNode defaultValue) {
name = op.getName() and
defaultValue = op.getDefaultValue()
}
////////////////////////////////////////////////////////////////////////////////
// Query predicates for various contexts of parameters
query predicate paramsInMethods(Method m, int i, Parameter p, string pClass) {
p = m.getParameter(i) and pClass = p.describeQlClass()
}
query predicate paramsInBlocks(Block b, int i, Parameter p, string pClass) {
p = b.getParameter(i) and pClass = p.describeQlClass()
}
query predicate paramsInLambdas(Lambda l, int i, Parameter p, string pClass) {
p = l.getParameter(i) and pClass = p.describeQlClass()
}
////////////////////////////////////////////////////////////////////////////////
// General query selecting all parameters
from Parameter p
select p, p.getPosition(), p.describeQlClass()

View File

@@ -0,0 +1,70 @@
# Tests for the different kinds and contexts of parameters.
# Method containing identifier parameters
def identifier_method_params(foo, bar, baz)
end
# Block containing identifier parameters
hash = {}
hash.each do |key, value|
puts "#{key} -> #{value}"
end
# Lambda containing identifier parameters
sum = -> (foo, bar) { foo + bar }
# Method containing destructured parameters
def destructured_method_param((a, b, c))
end
# Block containing destructured parameters
array = []
array.each { |(a, b)| puts a+b }
# Lambda containing destructured parameters
sum_four_values = -> ((first, second), (third, fourth)) {
first + second + third + fourth
}
# Method containing splat and hash-splat params
def method_with_splat(wibble, *splat, **double_splat)
end
# Block with splat and hash-splat parameter
array.each do |val, *splat, **double_splat|
end
# Lambda with splat and hash-splat
lambda_with_splats = -> (x, *blah, **wibble) {}
# Method containing keyword parameters
def method_with_keyword_params(x, foo:, bar: 7)
x + foo + bar
end
# Block with keyword parameters
def use_block_with_keyword(&block)
puts(block.call bar: 2, foo: 3)
end
use_block_with_keyword do |xx:, yy: 100|
xx + yy
end
lambda_with_keyword_params = -> (x, y:, z: 3) {
x + y + z
}
# Method containing optional parameters
def method_with_optional_params(val1, val2 = 0, val3 = 100)
end
# Block containing optional parameter
def use_block_with_optional(&block)
block.call 'Zeus'
end
use_block_with_optional do |name, age = 99|
puts "#{name} is #{age} years old"
end
# Lambda containing optional parameters
lambda_with_optional_params = -> (a, b = 1000, c = 20) { a+b+c }