diff --git a/ql/src/codeql_ruby/ast/Expr.qll b/ql/src/codeql_ruby/ast/Expr.qll index e48f091dfd1..a46d4f33627 100644 --- a/ql/src/codeql_ruby/ast/Expr.qll +++ b/ql/src/codeql_ruby/ast/Expr.qll @@ -110,9 +110,30 @@ class StringLiteral extends Literal, @string__ { class SymbolLiteral extends Literal { final override SymbolLiteral::Range range; + SymbolLiteral() { + not any(UndefStmt u).getAMethodName() = this and + not any(AliasStmt a).getNewName() = this and + not any(AliasStmt a).getOldName() = this + } + final override string getAPrimaryQlClass() { result = "SymbolLiteral" } } +/** + * A method name literal. For example: + * ```rb + * - method_name # a normal name + * - + # an operator + * - :method_name # a symbol + * - :"eval_#{name}" # a complex symbol + * ``` + */ +class MethodName extends Literal { + final override MethodName::Range range; + + final override string getAPrimaryQlClass() { result = "MethodName" } +} + /** A sequence of expressions. */ class StmtSequence extends Expr { override StmtSequence::Range range; diff --git a/ql/src/codeql_ruby/ast/Statement.qll b/ql/src/codeql_ruby/ast/Statement.qll index e7af8a952a0..e30f0c15fd7 100644 --- a/ql/src/codeql_ruby/ast/Statement.qll +++ b/ql/src/codeql_ruby/ast/Statement.qll @@ -59,6 +59,46 @@ class EndBlock extends StmtSequence, @end_block { final override string getAPrimaryQlClass() { result = "EndBlock" } } +/** + * An `undef` statement. For example: + * ```rb + * - undef method_name + * - undef &&, :method_name + * - undef :"method_#{ name }" + * ``` + */ +class UndefStmt extends Stmt, @undef { + final override UndefStmt::Range range; + + /** Gets the `n`th method name to undefine. */ + final MethodName getMethodName(int n) { result = range.getMethodName(n) } + + /** Gets a method name to undefine. */ + final MethodName getAMethodName() { result = getMethodName(_) } + + final override string getAPrimaryQlClass() { result = "UndefStmt" } +} + +/** + * An `alias` statement. For example: + * ```rb + * - alias alias_name method_name + * - alias foo :method_name + * - alias bar :"method_#{ name }" + * ``` + */ +class AliasStmt extends Stmt, @alias { + final override AliasStmt::Range range; + + /** Gets the new method name. */ + final MethodName getNewName() { result = range.getNewName() } + + /** Gets the original method name. */ + final MethodName getOldName() { result = range.getOldName() } + + final override string getAPrimaryQlClass() { result = "AliasStmt" } +} + /** * A statement that may return a value: `return`, `break` and `next`. * diff --git a/ql/src/codeql_ruby/ast/internal/AST.qll b/ql/src/codeql_ruby/ast/internal/AST.qll index 618aea0313a..d0a4bc556a3 100644 --- a/ql/src/codeql_ruby/ast/internal/AST.qll +++ b/ql/src/codeql_ruby/ast/internal/AST.qll @@ -31,8 +31,6 @@ module AstNode { or this = any(Generated::RestAssignment ra).getChild() or - this instanceof Generated::Alias - or this instanceof Generated::SymbolArray or this instanceof Generated::Interpolation @@ -55,10 +53,6 @@ module AstNode { or this instanceof Generated::Character or - this = any(Generated::Alias a).getName() - or - this = any(Generated::Alias a).getAlias() - or this instanceof Generated::HeredocBody or this instanceof Generated::HeredocBeginning @@ -72,10 +66,6 @@ module AstNode { this instanceof Generated::RescueModifier or this instanceof Generated::Subshell - or - this instanceof Generated::Undef - or - this = any(Generated::Undef u).getChild(_) } override string toString() { result = "AstNode" } diff --git a/ql/src/codeql_ruby/ast/internal/Expr.qll b/ql/src/codeql_ruby/ast/internal/Expr.qll index 9a7dfe22758..68bc63e180d 100644 --- a/ql/src/codeql_ruby/ast/internal/Expr.qll +++ b/ql/src/codeql_ruby/ast/internal/Expr.qll @@ -172,6 +172,36 @@ module SymbolLiteral { } } +module MethodName { + private class TokenTypes = + @setter or @token_class_variable or @token_constant or @token_global_variable or + @token_identifier or @token_instance_variable or @token_operator; + + abstract class Range extends Literal::Range, @underscore_method_name { + Range() { + exists(Generated::Undef u | u.getChild(_) = generated) + or + exists(Generated::Alias a | a.getName() = generated or a.getAlias() = generated) + } + } + + private class TokenMethodName extends MethodName::Range, TokenTypes { + final override Generated::UnderscoreMethodName generated; + + final override string getValueText() { + result = generated.(Generated::Token).getValue() + or + result = generated.(Generated::Setter).getName().getValue() + "=" + } + } + + private class SimpleSymbolMethodName extends MethodName::Range, SymbolLiteral::SimpleSymbolRange, + @token_simple_symbol { } + + private class DelimitedSymbolMethodName extends MethodName::Range, + SymbolLiteral::DelimitedSymbolRange, @delimited_symbol { } +} + module StmtSequence { abstract class Range extends Expr::Range { abstract Stmt getStmt(int n); diff --git a/ql/src/codeql_ruby/ast/internal/Method.qll b/ql/src/codeql_ruby/ast/internal/Method.qll index ae3462af950..4d0bd75cb08 100644 --- a/ql/src/codeql_ruby/ast/internal/Method.qll +++ b/ql/src/codeql_ruby/ast/internal/Method.qll @@ -17,7 +17,6 @@ module Method { string getName() { result = generated.getName().(Generated::Token).getValue() or - result = generated.getName().(SymbolLiteral).getValueText() or result = generated.getName().(Generated::Setter).getName().getValue() + "=" } diff --git a/ql/src/codeql_ruby/ast/internal/Statement.qll b/ql/src/codeql_ruby/ast/internal/Statement.qll index 5d94ddc0684..7c0aee758bd 100644 --- a/ql/src/codeql_ruby/ast/internal/Statement.qll +++ b/ql/src/codeql_ruby/ast/internal/Statement.qll @@ -25,6 +25,28 @@ module EndBlock { } } +module UndefStmt { + class Range extends Stmt::Range, @undef { + final override Generated::Undef generated; + + final MethodName getMethodName(int n) { result = generated.getChild(n) } + + final override string toString() { result = "undef ..." } + } +} + +module AliasStmt { + class Range extends Stmt::Range, @alias { + final override Generated::Alias generated; + + final MethodName getNewName() { result = generated.getName() } + + final MethodName getOldName() { result = generated.getAlias() } + + final override string toString() { result = "alias ..." } + } +} + module ReturningStmt { abstract class Range extends Stmt::Range { abstract Generated::ArgumentList getArgumentList(); diff --git a/ql/test/library-tests/ast/misc/misc.expected b/ql/test/library-tests/ast/misc/misc.expected new file mode 100644 index 00000000000..3f2c98f43fd --- /dev/null +++ b/ql/test/library-tests/ast/misc/misc.expected @@ -0,0 +1,21 @@ +undef +| misc.rb:3:1:3:30 | undef ... | 0 | misc.rb:3:7:3:9 | foo | foo | MethodName | +| misc.rb:3:1:3:30 | undef ... | 1 | misc.rb:3:12:3:15 | :foo | foo | MethodName | +| misc.rb:3:1:3:30 | undef ... | 2 | misc.rb:3:18:3:21 | foo= | foo= | MethodName | +| misc.rb:3:1:3:30 | undef ... | 3 | misc.rb:3:24:3:25 | [] | [] | MethodName | +| misc.rb:3:1:3:30 | undef ... | 4 | misc.rb:3:28:3:30 | []= | []= | MethodName | +| misc.rb:4:1:4:19 | undef ... | 0 | misc.rb:4:7:4:19 | :"foo_#{...}" | (none) | MethodName | +| misc.rb:5:1:5:35 | undef ... | 0 | misc.rb:5:7:5:9 | nil | nil | MethodName | +| misc.rb:5:1:5:35 | undef ... | 1 | misc.rb:5:12:5:15 | true | true | MethodName | +| misc.rb:5:1:5:35 | undef ... | 2 | misc.rb:5:18:5:22 | false | false | MethodName | +| misc.rb:5:1:5:35 | undef ... | 3 | misc.rb:5:25:5:29 | super | super | MethodName | +| misc.rb:5:1:5:35 | undef ... | 4 | misc.rb:5:32:5:35 | self | self | MethodName | +alias +| misc.rb:7:1:7:14 | alias ... | new | misc.rb:7:7:7:9 | new | new | MethodName | +| misc.rb:7:1:7:14 | alias ... | old | misc.rb:7:11:7:14 | :old | old | MethodName | +| misc.rb:8:1:8:14 | alias ... | new | misc.rb:8:7:8:10 | foo= | foo= | MethodName | +| misc.rb:8:1:8:14 | alias ... | old | misc.rb:8:12:8:14 | []= | []= | MethodName | +| misc.rb:9:1:9:16 | alias ... | new | misc.rb:9:7:9:11 | super | super | MethodName | +| misc.rb:9:1:9:16 | alias ... | old | misc.rb:9:13:9:16 | self | self | MethodName | +| misc.rb:10:1:10:24 | alias ... | new | misc.rb:10:7:10:17 | :"\\n#{...}" | (none) | MethodName | +| misc.rb:10:1:10:24 | alias ... | old | misc.rb:10:19:10:24 | :foo | foo | MethodName | diff --git a/ql/test/library-tests/ast/misc/misc.ql b/ql/test/library-tests/ast/misc/misc.ql new file mode 100644 index 00000000000..788a8593cdd --- /dev/null +++ b/ql/test/library-tests/ast/misc/misc.ql @@ -0,0 +1,23 @@ +import ruby + +private string getValueText(MethodName m) { + result = m.getValueText() + or + not exists(m.getValueText()) and result = "(none)" +} + +query predicate undef(UndefStmt u, int i, MethodName m, string name, string pClass) { + pClass = m.getAPrimaryQlClass() and + u.getMethodName(i) = m and + name = getValueText(m) +} + +query predicate alias(AliasStmt a, string prop, MethodName m, string name, string pClass) { + pClass = m.getAPrimaryQlClass() and + name = getValueText(m) and + ( + a.getOldName() = m and prop = "old" + or + a.getNewName() = m and prop = "new" + ) +} diff --git a/ql/test/library-tests/ast/misc/misc.rb b/ql/test/library-tests/ast/misc/misc.rb new file mode 100644 index 00000000000..6f7fabec245 --- /dev/null +++ b/ql/test/library-tests/ast/misc/misc.rb @@ -0,0 +1,10 @@ +bar = "bar" + +undef foo, :foo, foo=, [], []= +undef :"foo_#{bar}" +undef nil, true, false, super, self + +alias new :old +alias foo= []= +alias super self +alias :"\n#{bar}" :"foo" diff --git a/ql/test/library-tests/controlflow/graph/Cfg.expected b/ql/test/library-tests/controlflow/graph/Cfg.expected index 98762e036bd..aa36014772f 100644 --- a/ql/test/library-tests/controlflow/graph/Cfg.expected +++ b/ql/test/library-tests/controlflow/graph/Cfg.expected @@ -566,12 +566,12 @@ case.rb: cfg.rb: # 1| bar -#-----| -> Alias +#-----| -> alias ... # 1| bar #-----| -> bar -# 3| Alias +# 3| alias ... #-----| -> foo # 3| foo @@ -1793,7 +1793,7 @@ cfg.rb: #-----| -> ! ... # 165| ... = ... -#-----| -> Undef +#-----| -> undef ... # 165| x #-----| -> ... = ... @@ -1804,7 +1804,7 @@ cfg.rb: # 165| 42 #-----| -> - ... -# 167| Undef +# 167| undef ... #-----| -> two_parameters # 167| two_parameters