overlay[local] module; import codeql.Locations import ast.Call import ast.Control import ast.Constant import ast.Erb import ast.Expr import ast.Literal import ast.Method import ast.Module import ast.Parameter import ast.Operation import ast.Pattern import ast.Scope import ast.Statement import ast.Variable private import ast.internal.AST private import ast.internal.Scope private import ast.internal.Synthesis private import ast.internal.TreeSitter private import Customizations private import Diagnostics cached private module Cached { cached ModuleBase getEnclosingModule(Scope s) { result = s or not s instanceof ModuleBase and result = getEnclosingModule(s.getOuterScope()) } cached MethodBase getEnclosingMethod(Scope s) { result = s or not s instanceof MethodBase and not s instanceof ModuleBase and result = getEnclosingMethod(s.getOuterScope()) } cached Toplevel getEnclosingToplevel(Scope s) { result = s or result = getEnclosingToplevel(s.getOuterScope()) } } private import Cached /** * A node in the abstract syntax tree. This class is the base class for all Ruby * program elements. */ class AstNode extends TAstNode { /** * 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 getAPrimaryQlClass() { result = "???" } /** * Gets a comma-separated list of the names of the primary CodeQL classes to * which this element belongs. */ final string getPrimaryQlClasses() { result = concat(this.getAPrimaryQlClass(), ",") } /** Gets the enclosing module, if any. */ final ModuleBase getEnclosingModule() { result = getEnclosingModule(scopeOfInclSynth(this)) } /** Gets the enclosing method, if any. */ final MethodBase getEnclosingMethod() { result = getEnclosingMethod(scopeOfInclSynth(this)) } /** Gets the enclosing top-level. */ final Toplevel getEnclosingToplevel() { result = getEnclosingToplevel(scopeOfInclSynth(this)) } /** Gets a textual representation of this node. */ cached string toString() { none() } /** Gets the location of this node. */ Location getLocation() { result = getLocation(this) } /** Gets the file of this node. */ final File getFile() { result = this.getLocation().getFile() } /** Gets a child node of this `AstNode`. */ final AstNode getAChild() { result = this.getAChild(_) } /** Gets the parent of this `AstNode`, if this node is not a root node. */ final AstNode getParent() { result.getAChild() = this } /** * Gets a child of this node, which can also be retrieved using a predicate * named `pred`. */ cached AstNode getAChild(string pred) { pred = "getDesugared" and result = this.getDesugared() } /** * Holds if this node was synthesized to represent an implicit AST node not * present in the source code. In the following example method call, the * receiver is an implicit `self` reference, for which there is a synthesized * `Self` node. * * ```rb * foo(123) * ``` */ final predicate isSynthesized() { this = getSynthChild(_, _) } /** * Gets the desugared version of this AST node, if any. * * For example, the desugared version of * * ```rb * x += y * ``` * * is * * ```rb * x = x + y * ``` * * when `x` is a variable. Whenever an AST node can be desugared, * then the desugared version is used in the control-flow graph. */ final AstNode getDesugared() { result = getSynthChild(this, -1) } } /** A Ruby source file */ class RubyFile extends File { RubyFile() { exists(Location loc | ruby_ast_node_location(_, loc) and this = loc.getFile() ) } /** Gets a token in this file. */ private Ruby::Token getAToken() { result.getLocation().getFile() = this } /** Holds if `line` contains a token. */ private predicate line(int line, boolean comment) { exists(Ruby::Token token, Location l | token = this.getAToken() and l = token.getLocation() and line in [l.getStartLine() .. l.getEndLine()] and if token instanceof @ruby_token_comment then comment = true else comment = false ) } /** Gets the number of lines in this file. */ int getNumberOfLines() { result = max([0, this.getAToken().getLocation().getEndLine()]) } /** Gets the number of lines of code in this file. */ int getNumberOfLinesOfCode() { result = count(int line | this.line(line, false)) } /** Gets the number of lines of comments in this file. */ int getNumberOfLinesOfComments() { result = count(int line | this.line(line, true)) } } /** * A successfully extracted file, that is, a file that was extracted and * contains no extraction errors or warnings. */ class SuccessfullyExtractedFile extends File { SuccessfullyExtractedFile() { not exists(Diagnostic d | d.getLocation().getFile() = this and ( d instanceof ExtractionError or d instanceof ExtractionWarning ) ) } }