mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
Merge branch 'main' into shared-taint-tracking
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### New Features
|
||||
|
||||
4
ruby/ql/lib/change-notes/2023-08-08-splat-arguments.md
Normal file
4
ruby/ql/lib/change-notes/2023-08-08-splat-arguments.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Flow between splat arguments (`*args`) and positional parameters is now tracked more precisely.
|
||||
3
ruby/ql/lib/change-notes/released/0.7.2.md
Normal file
3
ruby/ql/lib/change-notes/released/0.7.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.1
|
||||
lastReleaseVersion: 0.7.2
|
||||
|
||||
@@ -48,7 +48,7 @@ class UnaryLogicalOperation extends UnaryOperation, TUnaryLogicalOperation { }
|
||||
* not params.empty?
|
||||
* ```
|
||||
*/
|
||||
class NotExpr extends UnaryLogicalOperation, TNotExpr {
|
||||
class NotExpr extends UnaryLogicalOperation instanceof NotExprImpl {
|
||||
final override string getAPrimaryQlClass() { result = "NotExpr" }
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class ComplementExpr extends UnaryBitwiseOperation, TComplementExpr {
|
||||
* defined? some_method
|
||||
* ```
|
||||
*/
|
||||
class DefinedExpr extends UnaryOperation, TDefinedExpr {
|
||||
class DefinedExpr extends UnaryOperation instanceof DefinedExprImpl {
|
||||
final override string getAPrimaryQlClass() { result = "DefinedExpr" }
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ private module Cached {
|
||||
TConstantWriteAccessSynth(Ast::AstNode parent, int i, string value) {
|
||||
mkSynthChild(ConstantWriteAccessKind(value), parent, i)
|
||||
} or
|
||||
TDefinedExpr(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
|
||||
TDefinedExprReal(Ruby::Unary g) { g instanceof @ruby_unary_definedquestion } or
|
||||
TDefinedExprSynth(Ast::AstNode parent, int i) { mkSynthChild(DefinedExprKind(), parent, i) } or
|
||||
TDelimitedSymbolLiteral(Ruby::DelimitedSymbol g) or
|
||||
TDestructuredLeftAssignment(Ruby::DestructuredLeftAssignment g) {
|
||||
not strictcount(int i | exists(g.getParent().(Ruby::LeftAssignmentList).getChild(i))) = 1
|
||||
@@ -228,7 +229,8 @@ private module Cached {
|
||||
TNilLiteralReal(Ruby::Nil g) or
|
||||
TNilLiteralSynth(Ast::AstNode parent, int i) { mkSynthChild(NilLiteralKind(), parent, i) } or
|
||||
TNoRegExpMatchExpr(Ruby::Binary g) { g instanceof @ruby_binary_bangtilde } or
|
||||
TNotExpr(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
|
||||
TNotExprReal(Ruby::Unary g) { g instanceof @ruby_unary_bang or g instanceof @ruby_unary_not } or
|
||||
TNotExprSynth(Ast::AstNode parent, int i) { mkSynthChild(NotExprKind(), parent, i) } or
|
||||
TOptionalParameter(Ruby::OptionalParameter g) or
|
||||
TPair(Ruby::Pair g) or
|
||||
TParenthesizedExpr(Ruby::ParenthesizedStatements g) or
|
||||
@@ -354,21 +356,21 @@ private module Cached {
|
||||
TBitwiseOrExprReal or TBitwiseXorExprReal or TBlockArgument or TBlockParameter or
|
||||
TBraceBlockReal or TBreakStmt or TCaseEqExpr or TCaseExpr or TCaseMatchReal or
|
||||
TCharacterLiteral or TClassDeclaration or TClassVariableAccessReal or TComplementExpr or
|
||||
TComplexLiteral or TDefinedExpr or TDelimitedSymbolLiteral or TDestructuredLeftAssignment or
|
||||
TDestructuredParameter or TDivExprReal or TDo or TDoBlock or TElementReference or
|
||||
TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or TEnsure or TEqExpr or
|
||||
TExponentExprReal or TFalseLiteral or TFile or TFindPattern or TFloatLiteral or TForExpr or
|
||||
TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or TGlobalVariableAccessReal or
|
||||
THashKeySymbolLiteral or THashLiteral or THashPattern or THashSplatExpr or
|
||||
THashSplatNilParameter or THashSplatParameter or THereDoc or TIdentifierMethodCall or
|
||||
TIfReal or TIfModifierExpr or TInClauseReal or TInstanceVariableAccessReal or
|
||||
TIntegerLiteralReal or TKeywordParameter or TLEExpr or TLShiftExprReal or TLTExpr or
|
||||
TLambda or TLeftAssignmentList or TLine or TLocalVariableAccessReal or
|
||||
TLogicalAndExprReal or TLogicalOrExprReal or TMethod or TMatchPattern or
|
||||
TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or TNextStmt or
|
||||
TNilLiteralReal or TNoRegExpMatchExpr or TNotExpr or TOptionalParameter or TPair or
|
||||
TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or TRangeLiteralReal or
|
||||
TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
|
||||
TComplexLiteral or TDefinedExprReal or TDelimitedSymbolLiteral or
|
||||
TDestructuredLeftAssignment or TDestructuredParameter or TDivExprReal or TDo or TDoBlock or
|
||||
TElementReference or TElseReal or TElsif or TEmptyStmt or TEncoding or TEndBlock or
|
||||
TEnsure or TEqExpr or TExponentExprReal or TFalseLiteral or TFile or TFindPattern or
|
||||
TFloatLiteral or TForExpr or TForwardParameter or TForwardArgument or TGEExpr or TGTExpr or
|
||||
TGlobalVariableAccessReal or THashKeySymbolLiteral or THashLiteral or THashPattern or
|
||||
THashSplatExpr or THashSplatNilParameter or THashSplatParameter or THereDoc or
|
||||
TIdentifierMethodCall or TIfReal or TIfModifierExpr or TInClauseReal or
|
||||
TInstanceVariableAccessReal or TIntegerLiteralReal or TKeywordParameter or TLEExpr or
|
||||
TLShiftExprReal or TLTExpr or TLambda or TLeftAssignmentList or TLine or
|
||||
TLocalVariableAccessReal or TLogicalAndExprReal or TLogicalOrExprReal or TMethod or
|
||||
TMatchPattern or TModuleDeclaration or TModuloExprReal or TMulExprReal or TNEExpr or
|
||||
TNextStmt or TNilLiteralReal or TNoRegExpMatchExpr or TNotExprReal or TOptionalParameter or
|
||||
TPair or TParenthesizedExpr or TParenthesizedPattern or TRShiftExprReal or
|
||||
TRangeLiteralReal or TRationalLiteral or TRedoStmt or TRegExpLiteral or TRegExpMatchExpr or
|
||||
TRegularArrayLiteral or TRegularMethodCall or TRegularStringLiteral or TRegularSuperCall or
|
||||
TRescueClause or TRescueModifierExpr or TRetryStmt or TReturnStmt or
|
||||
TScopeResolutionConstantAccess or TSelfReal or TSimpleParameterReal or
|
||||
@@ -438,7 +440,7 @@ private module Cached {
|
||||
n = TClassVariableAccessReal(result, _) or
|
||||
n = TComplementExpr(result) or
|
||||
n = TComplexLiteral(result) or
|
||||
n = TDefinedExpr(result) or
|
||||
n = TDefinedExprReal(result) or
|
||||
n = TDelimitedSymbolLiteral(result) or
|
||||
n = TDestructuredLeftAssignment(result) or
|
||||
n = TDivExprReal(result) or
|
||||
@@ -495,7 +497,7 @@ private module Cached {
|
||||
n = TNextStmt(result) or
|
||||
n = TNilLiteralReal(result) or
|
||||
n = TNoRegExpMatchExpr(result) or
|
||||
n = TNotExpr(result) or
|
||||
n = TNotExprReal(result) or
|
||||
n = TOptionalParameter(result) or
|
||||
n = TPair(result) or
|
||||
n = TParenthesizedExpr(result) or
|
||||
@@ -585,6 +587,8 @@ private module Cached {
|
||||
or
|
||||
result = TConstantWriteAccessSynth(parent, i, _)
|
||||
or
|
||||
result = TDefinedExprSynth(parent, i)
|
||||
or
|
||||
result = TDivExprSynth(parent, i)
|
||||
or
|
||||
result = TElseSynth(parent, i)
|
||||
@@ -617,6 +621,8 @@ private module Cached {
|
||||
or
|
||||
result = TNilLiteralSynth(parent, i)
|
||||
or
|
||||
result = TNotExprSynth(parent, i)
|
||||
or
|
||||
result = TRangeLiteralSynth(parent, i, _)
|
||||
or
|
||||
result = TRShiftExprSynth(parent, i)
|
||||
@@ -789,10 +795,14 @@ class TNamespace = TClassDeclaration or TModuleDeclaration;
|
||||
|
||||
class TOperation = TUnaryOperation or TBinaryOperation or TAssignment;
|
||||
|
||||
class TDefinedExpr = TDefinedExprReal or TDefinedExprSynth;
|
||||
|
||||
class TUnaryOperation =
|
||||
TUnaryLogicalOperation or TUnaryArithmeticOperation or TUnaryBitwiseOperation or TDefinedExpr or
|
||||
TSplatExpr or THashSplatExpr;
|
||||
|
||||
class TNotExpr = TNotExprReal or TNotExprSynth;
|
||||
|
||||
class TUnaryLogicalOperation = TNotExpr;
|
||||
|
||||
class TUnaryArithmeticOperation = TUnaryPlusExpr or TUnaryMinusExpr;
|
||||
|
||||
@@ -35,6 +35,16 @@ class UnaryOperationGenerated extends UnaryOperationImpl {
|
||||
final override string getOperatorImpl() { result = g.getOperator() }
|
||||
}
|
||||
|
||||
abstract class NotExprImpl extends UnaryOperationImpl, TNotExpr { }
|
||||
|
||||
class NotExprReal extends NotExprImpl, UnaryOperationGenerated, TNotExprReal { }
|
||||
|
||||
class NotExprSynth extends NotExprImpl, TNotExprSynth {
|
||||
final override string getOperatorImpl() { result = "!" }
|
||||
|
||||
final override Expr getOperandImpl() { synthChild(this, 0, result) }
|
||||
}
|
||||
|
||||
class SplatExprReal extends UnaryOperationImpl, TSplatExprReal {
|
||||
private Ruby::SplatArgument g;
|
||||
|
||||
@@ -67,6 +77,16 @@ class HashSplatExprImpl extends UnaryOperationImpl, THashSplatExpr {
|
||||
final override string getOperatorImpl() { result = "**" }
|
||||
}
|
||||
|
||||
abstract class DefinedExprImpl extends UnaryOperationImpl, TDefinedExpr { }
|
||||
|
||||
class DefinedExprReal extends DefinedExprImpl, UnaryOperationGenerated, TDefinedExprReal { }
|
||||
|
||||
class DefinedExprSynth extends DefinedExprImpl, TDefinedExprSynth {
|
||||
final override string getOperatorImpl() { result = "defined?" }
|
||||
|
||||
final override Expr getOperandImpl() { synthChild(this, 0, result) }
|
||||
}
|
||||
|
||||
abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinaryOperation {
|
||||
abstract Stmt getLeftOperandImpl();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ newtype SynthKind =
|
||||
BraceBlockKind() or
|
||||
CaseMatchKind() or
|
||||
ClassVariableAccessKind(ClassVariable v) or
|
||||
DefinedExprKind() or
|
||||
DivExprKind() or
|
||||
ElseKind() or
|
||||
ExponentExprKind() or
|
||||
@@ -40,6 +41,7 @@ newtype SynthKind =
|
||||
ModuloExprKind() or
|
||||
MulExprKind() or
|
||||
NilLiteralKind() or
|
||||
NotExprKind() or
|
||||
RangeLiteralKind(boolean inclusive) { inclusive in [false, true] } or
|
||||
RShiftExprKind() or
|
||||
SimpleParameterKind() or
|
||||
@@ -1258,6 +1260,7 @@ private module HashLiteralDesugar {
|
||||
* ```
|
||||
* desugars to, roughly,
|
||||
* ```rb
|
||||
* if not defined? x then x = nil end
|
||||
* xs.each { |__synth__0| x = __synth__0; <loop_body> }
|
||||
* ```
|
||||
*
|
||||
@@ -1267,58 +1270,160 @@ private module HashLiteralDesugar {
|
||||
* scoped to the synthesized block.
|
||||
*/
|
||||
private module ForLoopDesugar {
|
||||
private Ruby::AstNode getForLoopPatternChild(Ruby::For for) {
|
||||
result = for.getPattern()
|
||||
or
|
||||
result.getParent() = getForLoopPatternChild(for)
|
||||
}
|
||||
|
||||
/** Holds if `n` is an access to variable `v` in the pattern of `for`. */
|
||||
pragma[nomagic]
|
||||
private predicate forLoopVariableAccess(Ruby::For for, Ruby::AstNode n, VariableReal v) {
|
||||
n = getForLoopPatternChild(for) and
|
||||
access(n, v)
|
||||
}
|
||||
|
||||
/** Holds if `v` is the `i`th iteration variable of `for`. */
|
||||
private predicate forLoopVariable(Ruby::For for, VariableReal v, int i) {
|
||||
v =
|
||||
rank[i + 1](VariableReal v0, Ruby::AstNode n, Location l |
|
||||
forLoopVariableAccess(for, n, v0) and
|
||||
l = n.getLocation()
|
||||
|
|
||||
v0 order by l.getStartLine(), l.getStartColumn()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of iteration variables of `for`. */
|
||||
private int forLoopVariableCount(Ruby::For for) {
|
||||
result = count(int j | forLoopVariable(for, _, j))
|
||||
}
|
||||
|
||||
private Ruby::For toTsFor(ForExpr for) { for = TForExpr(result) }
|
||||
|
||||
/**
|
||||
* Synthesizes an assignment
|
||||
* ```rb
|
||||
* if not defined? v then v = nil end
|
||||
* ```
|
||||
* anchored at index `rootIndex` of `root`.
|
||||
*/
|
||||
bindingset[root, rootIndex, v]
|
||||
private predicate nilAssignUndefined(
|
||||
AstNode root, int rootIndex, AstNode parent, int i, Child child, VariableReal v
|
||||
) {
|
||||
parent = root and
|
||||
i = rootIndex and
|
||||
child = SynthChild(IfKind())
|
||||
or
|
||||
exists(AstNode if_ | if_ = TIfSynth(root, rootIndex) |
|
||||
parent = if_ and
|
||||
i = 0 and
|
||||
child = SynthChild(NotExprKind())
|
||||
or
|
||||
exists(AstNode not_ | not_ = TNotExprSynth(if_, 0) |
|
||||
parent = not_ and
|
||||
i = 0 and
|
||||
child = SynthChild(DefinedExprKind())
|
||||
or
|
||||
parent = TDefinedExprSynth(not_, 0) and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessRealKind(v))
|
||||
)
|
||||
or
|
||||
parent = if_ and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
parent = TAssignExprSynth(if_, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessRealKind(v))
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(NilLiteralKind())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate forLoopSynthesis(AstNode parent, int i, Child child) {
|
||||
exists(ForExpr for |
|
||||
// each call
|
||||
parent = for and
|
||||
i = -1 and
|
||||
child = SynthChild(MethodCallKind("each", false, 0))
|
||||
child = SynthChild(StmtSequenceKind())
|
||||
or
|
||||
exists(MethodCall eachCall | eachCall = TMethodCallSynth(for, -1, "each", false, 0) |
|
||||
// receiver
|
||||
parent = eachCall and
|
||||
i = 0 and
|
||||
child = childRef(for.getValue()) // value is the Enumerable
|
||||
exists(AstNode seq | seq = TStmtSequenceSynth(for, -1) |
|
||||
exists(VariableReal v, int j | forLoopVariable(toTsFor(for), v, j) |
|
||||
nilAssignUndefined(seq, j, parent, i, child, v)
|
||||
)
|
||||
or
|
||||
parent = eachCall and
|
||||
i = 1 and
|
||||
child = SynthChild(BraceBlockKind())
|
||||
or
|
||||
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
|
||||
// block params
|
||||
parent = block and
|
||||
i = 0 and
|
||||
child = SynthChild(SimpleParameterKind())
|
||||
exists(int numberOfVars | numberOfVars = forLoopVariableCount(toTsFor(for)) |
|
||||
// each call
|
||||
parent = seq and
|
||||
i = numberOfVars and
|
||||
child = SynthChild(MethodCallKind("each", false, 0))
|
||||
or
|
||||
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
|
||||
parent = param and
|
||||
exists(MethodCall eachCall |
|
||||
eachCall = TMethodCallSynth(seq, numberOfVars, "each", false, 0)
|
||||
|
|
||||
// receiver
|
||||
parent = eachCall and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
child = childRef(for.getValue()) // value is the Enumerable
|
||||
or
|
||||
// assignment to pattern from for loop to synth parameter
|
||||
parent = block and
|
||||
parent = eachCall and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
child = SynthChild(BraceBlockKind())
|
||||
or
|
||||
parent = TAssignExprSynth(block, 1) and
|
||||
(
|
||||
exists(Block block | block = TBraceBlockSynth(eachCall, 1) |
|
||||
// block params
|
||||
parent = block and
|
||||
i = 0 and
|
||||
child = childRef(for.getPattern())
|
||||
child = SynthChild(SimpleParameterKind())
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
exists(SimpleParameter param | param = TSimpleParameterSynth(block, 0) |
|
||||
parent = param and
|
||||
i = 0 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
or
|
||||
// assignment to pattern from for loop to synth parameter
|
||||
parent = block and
|
||||
i = 1 and
|
||||
child = SynthChild(AssignExprKind())
|
||||
or
|
||||
parent = TAssignExprSynth(block, 1) and
|
||||
(
|
||||
i = 0 and
|
||||
child = childRef(for.getPattern())
|
||||
or
|
||||
i = 1 and
|
||||
child = SynthChild(LocalVariableAccessSynthKind(TLocalVariableSynth(param, 0)))
|
||||
)
|
||||
)
|
||||
or
|
||||
// rest of block body
|
||||
parent = block and
|
||||
child = childRef(for.getBody().(Do).getStmt(i - 2))
|
||||
)
|
||||
)
|
||||
or
|
||||
// rest of block body
|
||||
parent = block and
|
||||
child = childRef(for.getBody().(Do).getStmt(i - 2))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isDesugaredInitNode(ForExpr for, Variable v, AstNode n) {
|
||||
exists(StmtSequence seq, AssignExpr ae |
|
||||
seq = for.getDesugared() and
|
||||
n = seq.getStmt(_) and
|
||||
ae = n.(IfExpr).getThen() and
|
||||
v = ae.getLeftOperand().getAVariable()
|
||||
)
|
||||
or
|
||||
isDesugaredInitNode(for, v, n.getParent())
|
||||
}
|
||||
|
||||
private class ForLoopSynthesis extends Synthesis {
|
||||
final override predicate child(AstNode parent, int i, Child child) {
|
||||
forLoopSynthesis(parent, i, child)
|
||||
@@ -1338,6 +1443,14 @@ private module ForLoopDesugar {
|
||||
final override predicate excludeFromControlFlowTree(AstNode n) {
|
||||
n = any(ForExpr for).getBody()
|
||||
}
|
||||
|
||||
final override predicate location(AstNode n, Location l) {
|
||||
exists(ForExpr for, Ruby::AstNode access, Variable v |
|
||||
forLoopVariableAccess(toTsFor(for), access, v) and
|
||||
isDesugaredInitNode(for, v, n) and
|
||||
l = access.getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
private import TreeSitter
|
||||
private import codeql.ruby.AST
|
||||
private import codeql.ruby.CFG
|
||||
private import codeql.ruby.ast.internal.AST
|
||||
private import codeql.ruby.ast.internal.Parameter
|
||||
private import codeql.ruby.ast.internal.Pattern
|
||||
@@ -364,22 +365,11 @@ private module Cached {
|
||||
|
||||
cached
|
||||
predicate isCapturedAccess(LocalVariableAccess access) {
|
||||
exists(Scope scope1, Scope scope2 |
|
||||
exists(Scope scope1, CfgScope scope2 |
|
||||
scope1 = access.getVariable().getDeclaringScope() and
|
||||
scope2 = access.getCfgScope() and
|
||||
scope1 != scope2
|
||||
|
|
||||
if access instanceof SelfVariableAccess
|
||||
then
|
||||
// ```
|
||||
// class C
|
||||
// def self.m // not a captured access
|
||||
// end
|
||||
// end
|
||||
// ```
|
||||
not scope2 instanceof Toplevel or
|
||||
not access = any(SingletonMethod m).getObject()
|
||||
else any()
|
||||
scope1 != scope2 and
|
||||
not scope2 instanceof Toplevel
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -439,6 +439,7 @@ private module Cached {
|
||||
} or
|
||||
THashSplatArgumentPosition() or
|
||||
TSplatAllArgumentPosition() or
|
||||
TSplatArgumentPosition(int pos) { exists(Call c | c.getArgument(pos) instanceof SplatExpr) } or
|
||||
TAnyArgumentPosition() or
|
||||
TAnyKeywordArgumentPosition()
|
||||
|
||||
@@ -468,6 +469,10 @@ private module Cached {
|
||||
// synthetic parameter position.
|
||||
TSynthHashSplatParameterPosition() or
|
||||
TSplatAllParameterPosition() or
|
||||
TSplatParameterPosition(int pos) {
|
||||
exists(Parameter p | p.getPosition() = pos and p instanceof SplatParameter)
|
||||
} or
|
||||
TSynthSplatParameterPosition() or
|
||||
TAnyParameterPosition() or
|
||||
TAnyKeywordParameterPosition()
|
||||
}
|
||||
@@ -1288,8 +1293,12 @@ class ParameterPosition extends TParameterPosition {
|
||||
|
||||
predicate isSynthHashSplat() { this = TSynthHashSplatParameterPosition() }
|
||||
|
||||
predicate isSynthSplat() { this = TSynthSplatParameterPosition() }
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllParameterPosition() }
|
||||
|
||||
predicate isSplat(int n) { this = TSplatParameterPosition(n) }
|
||||
|
||||
/**
|
||||
* Holds if this position represents any parameter, except `self` parameters. This
|
||||
* includes both positional, named, and block parameters.
|
||||
@@ -1320,6 +1329,10 @@ class ParameterPosition extends TParameterPosition {
|
||||
this.isAny() and result = "any"
|
||||
or
|
||||
this.isAnyNamed() and result = "any-named"
|
||||
or
|
||||
this.isSynthSplat() and result = "synthetic *"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,6 +1367,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
|
||||
predicate isSplatAll() { this = TSplatAllArgumentPosition() }
|
||||
|
||||
predicate isSplat(int n) { this = TSplatArgumentPosition(n) }
|
||||
|
||||
/** Gets a textual representation of this position. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -1371,6 +1386,8 @@ class ArgumentPosition extends TArgumentPosition {
|
||||
this.isHashSplat() and result = "**"
|
||||
or
|
||||
this.isSplatAll() and result = "*"
|
||||
or
|
||||
exists(int pos | this.isSplat(pos) and result = "* (position " + pos + ")")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1401,6 +1418,11 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
or
|
||||
ppos.isSplatAll() and apos.isSplatAll()
|
||||
or
|
||||
ppos.isSynthSplat() and apos.isSplatAll()
|
||||
or
|
||||
// Exact splat match
|
||||
exists(int n | apos.isSplat(n) and ppos.isSplat(n))
|
||||
or
|
||||
ppos.isAny() and argumentPositionIsNotSelf(apos)
|
||||
or
|
||||
apos.isAny() and parameterPositionIsNotSelf(ppos)
|
||||
|
||||
@@ -137,22 +137,6 @@ module LocalFlow {
|
||||
nodeTo = getSelfParameterDefNode(nodeFrom.(SelfParameterNodeImpl).getMethod())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom -> nodeTo` is a step from a parameter to a capture entry node for
|
||||
* that parameter.
|
||||
*
|
||||
* This is intended to recover from flow not currently recognised by ordinary capture flow.
|
||||
*/
|
||||
predicate localFlowSsaParamCaptureInput(ParameterNodeImpl nodeFrom, Node nodeTo) {
|
||||
exists(Ssa::CapturedEntryDefinition def |
|
||||
nodeTo.(SsaDefinitionExtNode).getDefinitionExt() = def
|
||||
|
|
||||
nodeFrom.getParameter().(NamedParameter).getVariable() = def.getSourceVariable()
|
||||
or
|
||||
nodeFrom.(SelfParameterNode).getSelfVariable() = def.getSourceVariable()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a local use-use flow step from `nodeFrom` to `nodeTo`
|
||||
* involving SSA definition `def`.
|
||||
@@ -245,6 +229,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
not this.getExpr() instanceof BlockArgument and
|
||||
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
|
||||
not this.getExpr() instanceof HashSplatExpr and
|
||||
not this.getExpr() instanceof SplatExpr and
|
||||
arg.isPositional(i)
|
||||
)
|
||||
or
|
||||
@@ -261,8 +246,15 @@ private class Argument extends CfgNodes::ExprCfgNode {
|
||||
arg.isHashSplat()
|
||||
or
|
||||
this = call.getArgument(0) and
|
||||
not exists(call.getArgument(1)) and
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplatAll()
|
||||
or
|
||||
exists(int pos | pos > 0 or exists(call.getArgument(pos + 1)) |
|
||||
this = call.getArgument(pos) and
|
||||
this.getExpr() instanceof SplatExpr and
|
||||
arg.isSplat(pos)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this expression is the `i`th argument of `c`. */
|
||||
@@ -277,6 +269,53 @@ predicate isNonConstantExpr(CfgNodes::ExprCfgNode n) {
|
||||
not n.getExpr() instanceof ConstantAccess
|
||||
}
|
||||
|
||||
/** Provides logic related to captured variables. */
|
||||
module VariableCapture {
|
||||
class CapturedVariable extends LocalVariable {
|
||||
CapturedVariable() { this.isCaptured() }
|
||||
|
||||
CfgScope getCfgScope() {
|
||||
exists(Scope scope | scope = this.getDeclaringScope() |
|
||||
result = scope
|
||||
or
|
||||
result = scope.(ModuleBase).getCfgScope()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class CapturedSsaDefinitionExt extends SsaImpl::DefinitionExt {
|
||||
CapturedSsaDefinitionExt() { this.getSourceVariable() instanceof CapturedVariable }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is control-flow insensitive data-flow from `node1` to `node2`
|
||||
* involving a captured variable. Only used in type tracking.
|
||||
*/
|
||||
predicate flowInsensitiveStep(Node node1, Node node2) {
|
||||
exists(CapturedSsaDefinitionExt def, CapturedVariable v |
|
||||
// From an assignment or implicit initialization of a captured variable to its flow-insensitive node
|
||||
def = node1.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
def.getSourceVariable() = v and
|
||||
(
|
||||
def instanceof Ssa::WriteDefinition
|
||||
or
|
||||
def instanceof Ssa::SelfDefinition
|
||||
) and
|
||||
node2.(CapturedVariableNode).getVariable() = v
|
||||
or
|
||||
// From a captured variable node to its flow-sensitive capture nodes
|
||||
node1.(CapturedVariableNode).getVariable() = v and
|
||||
def = node2.(SsaDefinitionExtNode).getDefinitionExt() and
|
||||
def.getSourceVariable() = v and
|
||||
(
|
||||
def instanceof Ssa::CapturedCallDefinition
|
||||
or
|
||||
def instanceof Ssa::CapturedEntryDefinition
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A collection of cached types and predicates to be evaluated in the same stage. */
|
||||
cached
|
||||
private module Cached {
|
||||
@@ -288,6 +327,7 @@ private module Cached {
|
||||
TExprNode(CfgNodes::ExprCfgNode n) { TaintTrackingPrivate::forceCachingInSameStage() } or
|
||||
TReturningNode(CfgNodes::ReturningCfgNode n) or
|
||||
TSsaDefinitionExtNode(SsaImpl::DefinitionExt def) or
|
||||
TCapturedVariableNode(VariableCapture::CapturedVariable v) or
|
||||
TNormalParameterNode(Parameter p) {
|
||||
p instanceof SimpleParameter or
|
||||
p instanceof OptionalParameter or
|
||||
@@ -300,6 +340,10 @@ private module Cached {
|
||||
TSynthHashSplatParameterNode(DataFlowCallable c) {
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
|
||||
} or
|
||||
TSynthSplatParameterNode(DataFlowCallable c) {
|
||||
exists(c.asCallable()) and // exclude library callables
|
||||
isParameterNode(_, c, any(ParameterPosition p | p.isPositional(_)))
|
||||
} or
|
||||
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
|
||||
// filter out nodes that clearly don't need post-update nodes
|
||||
isNonConstantExpr(n) and
|
||||
@@ -318,7 +362,7 @@ private module Cached {
|
||||
|
||||
class TSourceParameterNode =
|
||||
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
|
||||
TSynthHashSplatParameterNode;
|
||||
TSynthHashSplatParameterNode or TSynthSplatParameterNode;
|
||||
|
||||
cached
|
||||
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
|
||||
@@ -377,6 +421,8 @@ private module Cached {
|
||||
LocalFlow::localFlowSsaInputFromRead(exprFrom, _, nodeTo) and
|
||||
exprFrom = [nodeFrom.asExpr(), nodeFrom.(PostUpdateNode).getPreUpdateNode().asExpr()]
|
||||
)
|
||||
or
|
||||
VariableCapture::flowInsensitiveStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate entrySsaDefinition(SsaDefinitionExtNode n) {
|
||||
@@ -514,6 +560,8 @@ predicate nodeIsHidden(Node n) {
|
||||
n instanceof SynthHashSplatParameterNode
|
||||
or
|
||||
n instanceof SynthHashSplatArgumentNode
|
||||
or
|
||||
n instanceof SynthSplatParameterNode
|
||||
}
|
||||
|
||||
/** An SSA definition, viewed as a node in a data flow graph. */
|
||||
@@ -536,7 +584,7 @@ class SsaDefinitionExtNode extends NodeImpl, TSsaDefinitionExtNode {
|
||||
}
|
||||
|
||||
/** An SSA definition for a `self` variable. */
|
||||
class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
|
||||
class SsaSelfDefinitionNode extends SsaDefinitionExtNode {
|
||||
private SelfVariable self;
|
||||
|
||||
SsaSelfDefinitionNode() { self = def.getSourceVariable() }
|
||||
@@ -545,6 +593,22 @@ class SsaSelfDefinitionNode extends LocalSourceNode, SsaDefinitionExtNode {
|
||||
Scope getSelfScope() { result = self.getDeclaringScope() }
|
||||
}
|
||||
|
||||
/** A data flow node representing a captured variable. Only used in type tracking. */
|
||||
class CapturedVariableNode extends NodeImpl, TCapturedVariableNode {
|
||||
private VariableCapture::CapturedVariable variable;
|
||||
|
||||
CapturedVariableNode() { this = TCapturedVariableNode(variable) }
|
||||
|
||||
/** Gets the captured variable represented by this node. */
|
||||
VariableCapture::CapturedVariable getVariable() { result = variable }
|
||||
|
||||
override CfgScope getCfgScope() { result = variable.getCfgScope() }
|
||||
|
||||
override Location getLocationImpl() { result = variable.getLocation() }
|
||||
|
||||
override string toStringImpl() { result = "captured " + variable.getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A value returning statement, viewed as a node in a data flow graph.
|
||||
*
|
||||
@@ -610,7 +674,15 @@ private module ParameterNodes {
|
||||
pos.isHashSplat()
|
||||
or
|
||||
parameter = callable.getParameter(0).(SplatParameter) and
|
||||
not exists(callable.getParameter(1)) and
|
||||
pos.isSplatAll()
|
||||
or
|
||||
exists(int n | n > 0 |
|
||||
parameter = callable.getParameter(n).(SplatParameter) and
|
||||
pos.isSplat(n) and
|
||||
// There are no positional parameters after the splat
|
||||
not exists(SimpleParameter p, int m | m > n | p = callable.getParameter(m))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -749,6 +821,70 @@ private module ParameterNodes {
|
||||
final override string toStringImpl() { result = "**kwargs" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthetic data-flow node to allow flow to positional parameters from a splat argument.
|
||||
*
|
||||
* For example, in the following code:
|
||||
*
|
||||
* ```rb
|
||||
* def foo(x, y); end
|
||||
*
|
||||
* foo(*[a, b])
|
||||
* ```
|
||||
*
|
||||
* We want `a` to flow to `x` and `b` to flow to `y`. We do this by constructing
|
||||
* a `SynthSplatParameterNode` for the method `foo`, and matching the splat argument to this
|
||||
* parameter node via `parameterMatch/2`. We then add read steps from this node to parameters
|
||||
* `x` and `y`, for content at indices 0 and 1 respectively (see `readStep`).
|
||||
*
|
||||
* We don't yet correctly handle cases where the splat argument is not the first argument, e.g. in
|
||||
* ```rb
|
||||
* foo(a, *[b])
|
||||
* ```
|
||||
*/
|
||||
class SynthSplatParameterNode extends ParameterNodeImpl, TSynthSplatParameterNode {
|
||||
private DataFlowCallable callable;
|
||||
|
||||
SynthSplatParameterNode() { this = TSynthSplatParameterNode(callable) }
|
||||
|
||||
/**
|
||||
* Gets a parameter which will contain the value given by `c`, assuming
|
||||
* that the method was called with a single splat argument.
|
||||
* For example, if the synth splat parameter is for the following method
|
||||
*
|
||||
* ```rb
|
||||
* def foo(x, y, a:, *rest)
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* Then `getAParameter(element 0) = x` and `getAParameter(element 1) = y`.
|
||||
*/
|
||||
ParameterNode getAParameter(ContentSet c) {
|
||||
exists(int n |
|
||||
isParameterNode(result, callable, (any(ParameterPosition p | p.isPositional(n)))) and
|
||||
(
|
||||
c = getPositionalContent(n)
|
||||
or
|
||||
c.isSingleton(TUnknownElementContent())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final override Parameter getParameter() { none() }
|
||||
|
||||
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
|
||||
c = callable and pos.isSynthSplat()
|
||||
}
|
||||
|
||||
final override CfgScope getCfgScope() { result = callable.asCallable() }
|
||||
|
||||
final override DataFlowCallable getEnclosingCallable() { result = callable }
|
||||
|
||||
final override Location getLocationImpl() { result = callable.getLocation() }
|
||||
|
||||
final override string toStringImpl() { result = "synthetic *args" }
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, FlowSummaryNode {
|
||||
private ParameterPosition pos_;
|
||||
@@ -1077,13 +1213,7 @@ private module OutNodes {
|
||||
|
||||
import OutNodes
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
predicate jumpStepTypeTracker(Node pred, Node succ) {
|
||||
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryJumpStep(pred.(FlowSummaryNode).getSummaryNode(),
|
||||
@@ -1092,6 +1222,16 @@ predicate jumpStep(Node pred, Node succ) {
|
||||
any(AdditionalJumpStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
predicate jumpStep(Node pred, Node succ) {
|
||||
jumpStepTypeTracker(pred, succ)
|
||||
or
|
||||
SsaImpl::captureFlowIn(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
or
|
||||
SsaImpl::captureFlowOut(_, pred.(SsaDefinitionExtNode).getDefinitionExt(),
|
||||
succ.(SsaDefinitionExtNode).getDefinitionExt())
|
||||
}
|
||||
|
||||
private ContentSet getKeywordContent(string name) {
|
||||
exists(ConstantValue::ConstantSymbolValue key |
|
||||
result.isSingleton(TKnownElementContent(key)) and
|
||||
@@ -1099,6 +1239,13 @@ private ContentSet getKeywordContent(string name) {
|
||||
)
|
||||
}
|
||||
|
||||
private ContentSet getPositionalContent(int n) {
|
||||
exists(ConstantValue::ConstantIntegerValue i |
|
||||
result.isSingleton(TKnownElementContent(i)) and
|
||||
i.isInt(n)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subset of `storeStep` that should be shared with type-tracking.
|
||||
*/
|
||||
@@ -1187,6 +1334,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
|
||||
or
|
||||
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
|
||||
or
|
||||
node2 = node1.(SynthSplatParameterNode).getAParameter(c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
|
||||
node2.(FlowSummaryNode).getSummaryNode())
|
||||
}
|
||||
|
||||
@@ -363,10 +363,9 @@ private module Cached {
|
||||
source = sink and
|
||||
source instanceof LocalSourceNode
|
||||
or
|
||||
exists(Node mid | hasLocalSource(mid, source) |
|
||||
exists(Node mid |
|
||||
hasLocalSource(mid, source) and
|
||||
localFlowStepTypeTracker(mid, sink)
|
||||
or
|
||||
LocalFlow::localFlowSsaParamCaptureInput(mid, sink)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
|
||||
*/
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
predicate jumpStep = DataFlowPrivate::jumpStepTypeTracker/2;
|
||||
|
||||
/** Holds if there is direct flow from `param` to a return. */
|
||||
pragma[nomagic]
|
||||
|
||||
@@ -9,14 +9,45 @@
|
||||
|
||||
private import codeql.ruby.controlflow.internal.ControlFlowGraphImpl::TestOutput
|
||||
private import codeql.IDEContextual
|
||||
private import codeql.Locations
|
||||
private import codeql.ruby.controlflow.ControlFlowGraph
|
||||
|
||||
/**
|
||||
* Gets the source file to generate a CFG from.
|
||||
*/
|
||||
external string selectedSourceFile();
|
||||
|
||||
external string selectedSourceLine();
|
||||
|
||||
external string selectedSourceColumn();
|
||||
|
||||
bindingset[file, line, column]
|
||||
private CfgScope smallestEnclosingScope(File file, int line, int column) {
|
||||
result =
|
||||
min(Location loc, CfgScope scope |
|
||||
loc = scope.getLocation() and
|
||||
(
|
||||
loc.getStartLine() < line
|
||||
or
|
||||
loc.getStartLine() = line and loc.getStartColumn() <= column
|
||||
) and
|
||||
(
|
||||
loc.getEndLine() > line
|
||||
or
|
||||
loc.getEndLine() = line and loc.getEndColumn() >= column
|
||||
) and
|
||||
loc.getFile() = file
|
||||
|
|
||||
scope
|
||||
order by
|
||||
loc.getStartLine() desc, loc.getStartColumn() desc, loc.getEndLine(), loc.getEndColumn()
|
||||
)
|
||||
}
|
||||
|
||||
class MyRelevantNode extends RelevantNode {
|
||||
MyRelevantNode() {
|
||||
this.getScope().getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
this.getScope() =
|
||||
smallestEnclosingScope(getFileBySourceArchiveName(selectedSourceFile()),
|
||||
selectedSourceLine().toInt(), selectedSourceColumn().toInt())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/ruby-all
|
||||
version: 0.7.2-dev
|
||||
version: 0.7.3-dev
|
||||
groups: ruby
|
||||
extractor: ruby
|
||||
dbscheme: ruby.dbscheme
|
||||
|
||||
Reference in New Issue
Block a user