mirror of
https://github.com/github/codeql.git
synced 2026-04-23 07:45:17 +02:00
Merge remote-tracking branch 'origin/main' into nickrolfe/misspelling
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
"rust-lang.rust",
|
||||
"bungcip.better-toml",
|
||||
"github.vscode-codeql",
|
||||
"hbenl.vscode-test-explorer",
|
||||
"ms-vscode.test-adapter-converter",
|
||||
"slevesque.vscode-zipexplorer"
|
||||
],
|
||||
"settings": {
|
||||
|
||||
@@ -475,20 +475,23 @@
|
||||
"python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/internal/SensitiveDataHeuristics.qll"
|
||||
],
|
||||
"ReDoS Util Python/JS/Ruby": [
|
||||
"ReDoS Util Python/JS/Ruby/Java": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll"
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll",
|
||||
"java/ql/lib/semmle/code/java/security/performance/ReDoSUtil.qll"
|
||||
],
|
||||
"ReDoS Exponential Python/JS/Ruby": [
|
||||
"ReDoS Exponential Python/JS/Ruby/Java": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/ExponentialBackTracking.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll"
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ExponentialBackTracking.qll",
|
||||
"java/ql/lib/semmle/code/java/security/performance/ExponentialBackTracking.qll"
|
||||
],
|
||||
"ReDoS Polynomial Python/JS/Ruby": [
|
||||
"ReDoS Polynomial Python/JS/Ruby/Java": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/SuperlinearBackTracking.qll"
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/SuperlinearBackTracking.qll",
|
||||
"java/ql/lib/semmle/code/java/security/performance/SuperlinearBackTracking.qll"
|
||||
],
|
||||
"BadTagFilterQuery Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/BadTagFilterQuery.qll",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
class Element extends @element {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Expr extends @expr {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Stmt extends @stmt {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
predicate isStmtWithInitializer(Stmt stmt) {
|
||||
exists(int kind | stmts(stmt, kind, _) | kind = 2 or kind = 11 or kind = 35)
|
||||
}
|
||||
|
||||
from Expr child, int index, int index_new, Element parent
|
||||
where
|
||||
exprparents(child, index, parent) and
|
||||
if isStmtWithInitializer(parent) then index_new = index - 1 else index_new = index
|
||||
select child, index_new, parent
|
||||
2111
cpp/downgrades/cf72c8898d19eb1b3374432cf79d8276cb07ad43/old.dbscheme
Normal file
2111
cpp/downgrades/cf72c8898d19eb1b3374432cf79d8276cb07ad43/old.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
class Element extends @element {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Stmt extends @stmt {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
predicate isStmtWithInitializer(Stmt stmt) {
|
||||
exists(int kind | stmts(stmt, kind, _) | kind = 2 or kind = 11 or kind = 35)
|
||||
}
|
||||
|
||||
from Stmt child, int index, int index_new, Element parent
|
||||
where
|
||||
stmtparents(child, index, parent) and
|
||||
(
|
||||
not isStmtWithInitializer(parent)
|
||||
or
|
||||
index > 0
|
||||
) and
|
||||
if isStmtWithInitializer(parent) then index_new = index - 1 else index_new = index
|
||||
select child, index_new, parent
|
||||
@@ -0,0 +1,6 @@
|
||||
description: Support C++17 if and switch initializers
|
||||
compatibility: partial
|
||||
if_initialization.rel: delete
|
||||
switch_initialization.rel: delete
|
||||
exprparents.rel: run exprparents.qlo
|
||||
stmtparents.rel: run stmtparents.qlo
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* A `getInitialization` predicate was added to the `ConstexprIfStmt`, `IfStmt`, and `SwitchStmt` classes that yields the C++17-style initializer of the `if` or `switch` statement when it exists.
|
||||
@@ -663,18 +663,24 @@ private predicate namedStmtChildPredicates(Locatable s, Element e, string pred)
|
||||
or
|
||||
s.(ComputedGotoStmt).getExpr() = e and pred = "getExpr()"
|
||||
or
|
||||
s.(ConstexprIfStmt).getInitialization() = e and pred = "getInitialization()"
|
||||
or
|
||||
s.(ConstexprIfStmt).getCondition() = e and pred = "getCondition()"
|
||||
or
|
||||
s.(ConstexprIfStmt).getThen() = e and pred = "getThen()"
|
||||
or
|
||||
s.(ConstexprIfStmt).getElse() = e and pred = "getElse()"
|
||||
or
|
||||
s.(IfStmt).getInitialization() = e and pred = "getInitialization()"
|
||||
or
|
||||
s.(IfStmt).getCondition() = e and pred = "getCondition()"
|
||||
or
|
||||
s.(IfStmt).getThen() = e and pred = "getThen()"
|
||||
or
|
||||
s.(IfStmt).getElse() = e and pred = "getElse()"
|
||||
or
|
||||
s.(SwitchStmt).getInitialization() = e and pred = "getInitialization()"
|
||||
or
|
||||
s.(SwitchStmt).getExpr() = e and pred = "getExpr()"
|
||||
or
|
||||
s.(SwitchStmt).getStmt() = e and pred = "getStmt()"
|
||||
|
||||
@@ -708,30 +708,33 @@ private predicate straightLineSparse(Node scope, int i, Node ni, Spec spec) {
|
||||
or
|
||||
scope =
|
||||
any(SwitchStmt s |
|
||||
// SwitchStmt [-> init] -> expr
|
||||
i = -1 and ni = s and spec.isAt()
|
||||
or
|
||||
i = 0 and ni = s.getExpr() and spec.isAround()
|
||||
i = 0 and ni = s.getInitialization() and spec.isAround()
|
||||
or
|
||||
i = 1 and ni = s.getExpr() and spec.isAround()
|
||||
or
|
||||
// If the switch body is not a block then this step is skipped, and the
|
||||
// expression jumps directly to the cases.
|
||||
i = 1 and ni = s.getStmt().(BlockStmt) and spec.isAt()
|
||||
i = 2 and ni = s.getStmt().(BlockStmt) and spec.isAt()
|
||||
or
|
||||
i = 2 and ni = s.getASwitchCase() and spec.isBefore()
|
||||
i = 3 and ni = s.getASwitchCase() and spec.isBefore()
|
||||
or
|
||||
// If there is no default case, we can jump to after the block. Note: `i`
|
||||
// is same value as above.
|
||||
not s.getASwitchCase() instanceof DefaultCase and
|
||||
i = 2 and
|
||||
i = 3 and
|
||||
ni = s.getStmt() and
|
||||
spec.isAfter()
|
||||
or
|
||||
i = 3 and /* BARRIER */ ni = s and spec.isBarrier()
|
||||
i = 4 and /* BARRIER */ ni = s and spec.isBarrier()
|
||||
or
|
||||
i = 4 and ni = s.getStmt() and spec.isAfter()
|
||||
i = 5 and ni = s.getStmt() and spec.isAfter()
|
||||
or
|
||||
i = 5 and ni = s and spec.isAroundDestructors()
|
||||
i = 6 and ni = s and spec.isAroundDestructors()
|
||||
or
|
||||
i = 6 and ni = s and spec.isAfter()
|
||||
i = 7 and ni = s and spec.isAfter()
|
||||
)
|
||||
or
|
||||
scope =
|
||||
@@ -836,8 +839,15 @@ private predicate subEdge(Pos p1, Node n1, Node n2, Pos p2) {
|
||||
p2.nodeAt(n2, f)
|
||||
)
|
||||
or
|
||||
// IfStmt -> condition ; { then, else } ->
|
||||
// IfStmt -> [ init -> ] condition ; { then, else } ->
|
||||
exists(IfStmt s |
|
||||
p1.nodeAt(n1, s) and
|
||||
p2.nodeBefore(n2, s.getInitialization())
|
||||
or
|
||||
p1.nodeAfter(n1, s.getInitialization()) and
|
||||
p2.nodeBefore(n2, s.getCondition())
|
||||
or
|
||||
not exists(s.getInitialization()) and
|
||||
p1.nodeAt(n1, s) and
|
||||
p2.nodeBefore(n2, s.getCondition())
|
||||
or
|
||||
@@ -851,8 +861,15 @@ private predicate subEdge(Pos p1, Node n1, Node n2, Pos p2) {
|
||||
p2.nodeAfter(n2, s)
|
||||
)
|
||||
or
|
||||
// ConstexprIfStmt -> condition ; { then, else } -> // same as IfStmt
|
||||
// ConstexprIfStmt -> [ init -> ] condition ; { then, else } -> // same as IfStmt
|
||||
exists(ConstexprIfStmt s |
|
||||
p1.nodeAt(n1, s) and
|
||||
p2.nodeBefore(n2, s.getInitialization())
|
||||
or
|
||||
p1.nodeAfter(n1, s.getInitialization()) and
|
||||
p2.nodeBefore(n2, s.getCondition())
|
||||
or
|
||||
not exists(s.getInitialization()) and
|
||||
p1.nodeAt(n1, s) and
|
||||
p2.nodeBefore(n2, s.getCondition())
|
||||
or
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -421,20 +421,36 @@ class TranslatedCatchAnyHandler extends TranslatedHandler {
|
||||
class TranslatedIfStmt extends TranslatedStmt, ConditionContext {
|
||||
override IfStmt stmt;
|
||||
|
||||
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
|
||||
override Instruction getFirstInstruction() {
|
||||
if hasInitialization()
|
||||
then result = getInitialization().getFirstInstruction()
|
||||
else result = getFirstConditionInstruction()
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) {
|
||||
id = 0 and result = getCondition()
|
||||
id = 0 and result = getInitialization()
|
||||
or
|
||||
id = 1 and result = getThen()
|
||||
id = 1 and result = getCondition()
|
||||
or
|
||||
id = 2 and result = getElse()
|
||||
id = 2 and result = getThen()
|
||||
or
|
||||
id = 3 and result = getElse()
|
||||
}
|
||||
|
||||
private predicate hasInitialization() { exists(stmt.getInitialization()) }
|
||||
|
||||
private TranslatedStmt getInitialization() {
|
||||
result = getTranslatedStmt(stmt.getInitialization())
|
||||
}
|
||||
|
||||
private TranslatedCondition getCondition() {
|
||||
result = getTranslatedCondition(stmt.getCondition().getFullyConverted())
|
||||
}
|
||||
|
||||
private Instruction getFirstConditionInstruction() {
|
||||
result = getCondition().getFirstInstruction()
|
||||
}
|
||||
|
||||
private TranslatedStmt getThen() { result = getTranslatedStmt(stmt.getThen()) }
|
||||
|
||||
private TranslatedStmt getElse() { result = getTranslatedStmt(stmt.getElse()) }
|
||||
@@ -456,6 +472,9 @@ class TranslatedIfStmt extends TranslatedStmt, ConditionContext {
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
child = getInitialization() and
|
||||
result = getFirstConditionInstruction()
|
||||
or
|
||||
(child = getThen() or child = getElse()) and
|
||||
result = getParent().getChildSuccessor(this)
|
||||
}
|
||||
@@ -698,14 +717,28 @@ class TranslatedSwitchStmt extends TranslatedStmt {
|
||||
result = getTranslatedExpr(stmt.getExpr().getFullyConverted())
|
||||
}
|
||||
|
||||
private Instruction getFirstExprInstruction() { result = getExpr().getFirstInstruction() }
|
||||
|
||||
private TranslatedStmt getBody() { result = getTranslatedStmt(stmt.getStmt()) }
|
||||
|
||||
override Instruction getFirstInstruction() { result = getExpr().getFirstInstruction() }
|
||||
override Instruction getFirstInstruction() {
|
||||
if hasInitialization()
|
||||
then result = getInitialization().getFirstInstruction()
|
||||
else result = getFirstExprInstruction()
|
||||
}
|
||||
|
||||
override TranslatedElement getChild(int id) {
|
||||
id = 0 and result = getExpr()
|
||||
id = 0 and result = getInitialization()
|
||||
or
|
||||
id = 1 and result = getBody()
|
||||
id = 1 and result = getExpr()
|
||||
or
|
||||
id = 2 and result = getBody()
|
||||
}
|
||||
|
||||
private predicate hasInitialization() { exists(stmt.getInitialization()) }
|
||||
|
||||
private TranslatedStmt getInitialization() {
|
||||
result = getTranslatedStmt(stmt.getInitialization())
|
||||
}
|
||||
|
||||
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
|
||||
@@ -735,6 +768,8 @@ class TranslatedSwitchStmt extends TranslatedStmt {
|
||||
}
|
||||
|
||||
override Instruction getChildSuccessor(TranslatedElement child) {
|
||||
child = getInitialization() and result = getFirstExprInstruction()
|
||||
or
|
||||
child = getExpr() and result = getInstruction(SwitchBranchTag())
|
||||
or
|
||||
child = getBody() and result = getParent().getChildSuccessor(this)
|
||||
|
||||
@@ -213,6 +213,26 @@ class ConditionalStmt extends ControlStructure, TConditionalStmt { }
|
||||
class IfStmt extends ConditionalStmt, @stmt_if {
|
||||
override string getAPrimaryQlClass() { result = "IfStmt" }
|
||||
|
||||
/**
|
||||
* Gets the initialization statement of this 'if' statement, if any.
|
||||
*
|
||||
* For example, for
|
||||
* ```
|
||||
* if (int x = y; b) { f(); }
|
||||
* ```
|
||||
* the result is `int x = y;`.
|
||||
*
|
||||
* Does not hold if the initialization statement is missing or an empty statement, as in
|
||||
* ```
|
||||
* if (b) { f(); }
|
||||
* ```
|
||||
* or
|
||||
* ```
|
||||
* if (; b) { f(); }
|
||||
* ```
|
||||
*/
|
||||
Stmt getInitialization() { if_initialization(underlyingElement(this), unresolveElement(result)) }
|
||||
|
||||
/**
|
||||
* Gets the condition expression of this 'if' statement.
|
||||
*
|
||||
@@ -222,7 +242,7 @@ class IfStmt extends ConditionalStmt, @stmt_if {
|
||||
* ```
|
||||
* the result is `b`.
|
||||
*/
|
||||
Expr getCondition() { result = this.getChild(0) }
|
||||
Expr getCondition() { result = this.getChild(1) }
|
||||
|
||||
override Expr getControllingExpr() { result = this.getCondition() }
|
||||
|
||||
@@ -299,6 +319,28 @@ class IfStmt extends ConditionalStmt, @stmt_if {
|
||||
class ConstexprIfStmt extends ConditionalStmt, @stmt_constexpr_if {
|
||||
override string getAPrimaryQlClass() { result = "ConstexprIfStmt" }
|
||||
|
||||
/**
|
||||
* Gets the initialization statement of this 'constexpr if' statement, if any.
|
||||
*
|
||||
* For example, for
|
||||
* ```
|
||||
* if constexpr (int x = y; b) { f(); }
|
||||
* ```
|
||||
* the result is `int x = y;`.
|
||||
*
|
||||
* Does not hold if the initialization statement is missing or an empty statement, as in
|
||||
* ```
|
||||
* if constexpr (b) { f(); }
|
||||
* ```
|
||||
* or
|
||||
* ```
|
||||
* if constexpr (; b) { f(); }
|
||||
* ```
|
||||
*/
|
||||
Stmt getInitialization() {
|
||||
constexpr_if_initialization(underlyingElement(this), unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the condition expression of this 'constexpr if' statement.
|
||||
*
|
||||
@@ -308,7 +350,7 @@ class ConstexprIfStmt extends ConditionalStmt, @stmt_constexpr_if {
|
||||
* ```
|
||||
* the result is `b`.
|
||||
*/
|
||||
Expr getCondition() { result = this.getChild(0) }
|
||||
Expr getCondition() { result = this.getChild(1) }
|
||||
|
||||
override Expr getControllingExpr() { result = this.getCondition() }
|
||||
|
||||
@@ -926,7 +968,7 @@ class ForStmt extends Loop, @stmt_for {
|
||||
*
|
||||
* Does not hold if the initialization statement is an empty statement, as in
|
||||
* ```
|
||||
* for (; i < 10; i++) { j++ }
|
||||
* for (; i < 10; i++) { j++; }
|
||||
* ```
|
||||
*/
|
||||
Stmt getInitialization() { for_initialization(underlyingElement(this), unresolveElement(result)) }
|
||||
@@ -1470,6 +1512,28 @@ class DefaultCase extends SwitchCase {
|
||||
class SwitchStmt extends ConditionalStmt, @stmt_switch {
|
||||
override string getAPrimaryQlClass() { result = "SwitchStmt" }
|
||||
|
||||
/**
|
||||
* Gets the initialization statement of this 'switch' statement, if any.
|
||||
*
|
||||
* For example, for
|
||||
* ```
|
||||
* switch (int x = y; b) { }
|
||||
* ```
|
||||
* the result is `int x = y;`.
|
||||
*
|
||||
* Does not hold if the initialization statement is missing or an empty statement, as in
|
||||
* ```
|
||||
* switch (b) { }
|
||||
* ```
|
||||
* or
|
||||
* ```
|
||||
* switch (; b) { }
|
||||
* ```
|
||||
*/
|
||||
Stmt getInitialization() {
|
||||
switch_initialization(underlyingElement(this), unresolveElement(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expression that this 'switch' statement switches on.
|
||||
*
|
||||
@@ -1485,7 +1549,7 @@ class SwitchStmt extends ConditionalStmt, @stmt_switch {
|
||||
* ```
|
||||
* the result is `i`.
|
||||
*/
|
||||
Expr getExpr() { result = this.getChild(0) }
|
||||
Expr getExpr() { result = this.getChild(1) }
|
||||
|
||||
override Expr getControllingExpr() { result = this.getExpr() }
|
||||
|
||||
|
||||
@@ -1863,6 +1863,11 @@ variable_vla(
|
||||
int decl: @stmt_vla_decl ref
|
||||
);
|
||||
|
||||
if_initialization(
|
||||
unique int if_stmt: @stmt_if ref,
|
||||
int init_id: @stmt ref
|
||||
);
|
||||
|
||||
if_then(
|
||||
unique int if_stmt: @stmt_if ref,
|
||||
int then_id: @stmt ref
|
||||
@@ -1873,6 +1878,11 @@ if_else(
|
||||
int else_id: @stmt ref
|
||||
);
|
||||
|
||||
constexpr_if_initialization(
|
||||
unique int constexpr_if_stmt: @stmt_constexpr_if ref,
|
||||
int init_id: @stmt ref
|
||||
);
|
||||
|
||||
constexpr_if_then(
|
||||
unique int constexpr_if_stmt: @stmt_constexpr_if ref,
|
||||
int then_id: @stmt ref
|
||||
@@ -1893,6 +1903,11 @@ do_body(
|
||||
int body_id: @stmt ref
|
||||
);
|
||||
|
||||
switch_initialization(
|
||||
unique int switch_stmt: @stmt_switch ref,
|
||||
int init_id: @stmt ref
|
||||
);
|
||||
|
||||
#keyset[switch_stmt, index]
|
||||
switch_case(
|
||||
int switch_stmt: @stmt_switch ref,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
class Element extends @element {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Expr extends @expr {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Stmt extends @stmt {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
predicate isStmtWithInitializer(Stmt stmt) {
|
||||
exists(int kind | stmts(stmt, kind, _) | kind = 2 or kind = 11 or kind = 35)
|
||||
}
|
||||
|
||||
from Expr child, int index, int index_new, Element parent
|
||||
where
|
||||
exprparents(child, index, parent) and
|
||||
if isStmtWithInitializer(parent) then index_new = index + 1 else index_new = index
|
||||
select child, index_new, parent
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
class Element extends @element {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Stmt extends @stmt {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
predicate isStmtWithInitializer(Stmt stmt) {
|
||||
exists(int kind | stmts(stmt, kind, _) | kind = 2 or kind = 11 or kind = 35)
|
||||
}
|
||||
|
||||
from Stmt child, int index, int index_new, Element parent
|
||||
where
|
||||
stmtparents(child, index, parent) and
|
||||
if isStmtWithInitializer(parent) then index_new = index + 1 else index_new = index
|
||||
select child, index_new, parent
|
||||
@@ -0,0 +1,4 @@
|
||||
description: Support C++17 if and switch initializers
|
||||
compatibility: partial
|
||||
exprparents.rel: run exprparents.qlo
|
||||
stmtparents.rel: run stmtparents.qlo
|
||||
@@ -57,6 +57,5 @@ where
|
||||
not declarationHasSideEffects(v) and
|
||||
not exists(AsmStmt s | f = s.getEnclosingFunction()) and
|
||||
not v.getAnAttribute().getName() = "unused" and
|
||||
not any(ErrorExpr e).getEnclosingFunction() = f and // unextracted expr may use `v`
|
||||
not any(ConditionDeclExpr cde).getEnclosingFunction() = f // this case can be removed when the `if (a = b; a)` and `switch (a = b; a)` test cases don't depend on this exclusion
|
||||
not any(ErrorExpr e).getEnclosingFunction() = f // unextracted expr may use `v`
|
||||
select v, "Variable " + v.getName() + " is not used"
|
||||
|
||||
@@ -57,6 +57,13 @@ class XercesDOMParserClass extends Class {
|
||||
XercesDOMParserClass() { this.hasName("XercesDOMParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMLSParser` class.
|
||||
*/
|
||||
class DomLSParserClass extends Class {
|
||||
DomLSParserClass() { this.hasName("DOMLSParser") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `SAXParser` class.
|
||||
*/
|
||||
@@ -217,12 +224,71 @@ class SetFeatureTranformer extends XXEFlowStateTranformer {
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AbstractDOMParser.parse`, `SAXParser.parse` or `SAX2XMLReader.parse`
|
||||
* method.
|
||||
* The `DOMLSParser.getDomConfig` function.
|
||||
*/
|
||||
class GetDomConfig extends Function {
|
||||
GetDomConfig() { this.getClassAndName("getDomConfig") instanceof DomLSParserClass }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DOMConfiguration.setParameter` function.
|
||||
*/
|
||||
class DomConfigurationSetParameter extends Function {
|
||||
DomConfigurationSetParameter() {
|
||||
this.getClassAndName("setParameter").getName() = "DOMConfiguration"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow state transformer for a call to `DOMConfiguration.setParameter`
|
||||
* specifying the feature `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
* This is a slightly more complex transformer because the qualifier is a
|
||||
* `DOMConfiguration` pointer returned by `DOMLSParser.getDomConfig` - and it
|
||||
* is *that* qualifier we want to transform the flow state of.
|
||||
*/
|
||||
class DomConfigurationSetParameterTranformer extends XXEFlowStateTranformer {
|
||||
Expr newValue;
|
||||
|
||||
DomConfigurationSetParameterTranformer() {
|
||||
exists(FunctionCall getDomConfigCall, FunctionCall setParameterCall |
|
||||
// this is the qualifier of a call to `DOMLSParser.getDomConfig`.
|
||||
getDomConfigCall.getTarget() instanceof GetDomConfig and
|
||||
this = getDomConfigCall.getQualifier() and
|
||||
// `setParameterCall` is a call to `setParameter` on the return value of
|
||||
// the same call to `DOMLSParser.getDomConfig`.
|
||||
setParameterCall.getTarget() instanceof DomConfigurationSetParameter and
|
||||
globalValueNumber(setParameterCall.getQualifier()).getAnExpr() = getDomConfigCall and
|
||||
// the parameter being set is
|
||||
// `XMLUni::fgXercesDisableDefaultEntityResolution`.
|
||||
globalValueNumber(setParameterCall.getArgument(0)).getAnExpr().(VariableAccess).getTarget()
|
||||
instanceof FeatureDisableDefaultEntityResolution and
|
||||
// the value being set is `newValue`.
|
||||
newValue = setParameterCall.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
final override XXEFlowState transform(XXEFlowState flowstate) {
|
||||
exists(int createEntityReferenceNodes |
|
||||
encodeXercesFlowState(flowstate, _, createEntityReferenceNodes) and
|
||||
(
|
||||
globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // true
|
||||
encodeXercesFlowState(result, 1, createEntityReferenceNodes)
|
||||
or
|
||||
not globalValueNumber(newValue).getAnExpr().getValue().toInt() = 1 and // false or unknown
|
||||
encodeXercesFlowState(result, 0, createEntityReferenceNodes)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AbstractDOMParser.parse`, `DOMLSParserClass.parse`, `SAXParser.parse`
|
||||
* or `SAX2XMLReader.parse` method.
|
||||
*/
|
||||
class ParseFunction extends Function {
|
||||
ParseFunction() {
|
||||
this.getClassAndName("parse") instanceof AbstractDOMParserClass or
|
||||
this.getClassAndName("parse") instanceof DomLSParserClass or
|
||||
this.getClassAndName("parse") instanceof SaxParserClass or
|
||||
this.getClassAndName("parse") instanceof Sax2XmlReader
|
||||
}
|
||||
@@ -235,7 +301,7 @@ class ParseFunction extends Function {
|
||||
class CreateLSParser extends Function {
|
||||
CreateLSParser() {
|
||||
this.hasName("createLSParser") and
|
||||
this.getUnspecifiedType().(PointerType).getBaseType().getName() = "DOMLSParser" // returns a `DOMLSParser *`.
|
||||
this.getUnspecifiedType().(PointerType).getBaseType() instanceof DomLSParserClass // returns a `DOMLSParser *`.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `cpp/unused-local-variable` no longer ignores functions that include `if` and `switch` statements with C++17-style initializers.
|
||||
@@ -13559,6 +13559,422 @@ ir.cpp:
|
||||
# 1754| Type = [SpecifiedType] const CopyConstructorTestVirtualClass
|
||||
# 1754| ValueCategory = lvalue
|
||||
# 1755| getStmt(2): [ReturnStmt] return ...
|
||||
# 1757| [TopLevelFunction] void if_initialization(int)
|
||||
# 1757| <params>:
|
||||
# 1757| getParameter(0): [Parameter] x
|
||||
# 1757| Type = [IntType] int
|
||||
# 1757| getEntryPoint(): [BlockStmt] { ... }
|
||||
# 1758| getStmt(0): [IfStmt] if (...) ...
|
||||
# 1758| getInitialization(): [DeclStmt] declaration
|
||||
# 1758| getDeclarationEntry(0): [VariableDeclarationEntry] definition of y
|
||||
# 1758| Type = [IntType] int
|
||||
# 1758| getVariable().getInitializer(): [Initializer] initializer for y
|
||||
# 1758| getExpr(): [VariableAccess] x
|
||||
# 1758| Type = [IntType] int
|
||||
# 1758| ValueCategory = prvalue(load)
|
||||
# 1758| getCondition(): [AddExpr] ... + ...
|
||||
# 1758| Type = [IntType] int
|
||||
# 1758| ValueCategory = prvalue
|
||||
# 1758| getLeftOperand(): [VariableAccess] x
|
||||
# 1758| Type = [IntType] int
|
||||
# 1758| ValueCategory = prvalue(load)
|
||||
# 1758| getRightOperand(): [Literal] 1
|
||||
# 1758| Type = [IntType] int
|
||||
# 1758| Value = [Literal] 1
|
||||
# 1758| ValueCategory = prvalue
|
||||
# 1758| getThen(): [BlockStmt] { ... }
|
||||
# 1759| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1759| getExpr(): [AssignExpr] ... = ...
|
||||
# 1759| Type = [IntType] int
|
||||
# 1759| ValueCategory = lvalue
|
||||
# 1759| getLValue(): [VariableAccess] x
|
||||
# 1759| Type = [IntType] int
|
||||
# 1759| ValueCategory = lvalue
|
||||
# 1759| getRValue(): [AddExpr] ... + ...
|
||||
# 1759| Type = [IntType] int
|
||||
# 1759| ValueCategory = prvalue
|
||||
# 1759| getLeftOperand(): [VariableAccess] x
|
||||
# 1759| Type = [IntType] int
|
||||
# 1759| ValueCategory = prvalue(load)
|
||||
# 1759| getRightOperand(): [VariableAccess] y
|
||||
# 1759| Type = [IntType] int
|
||||
# 1759| ValueCategory = prvalue(load)
|
||||
# 1758| getCondition().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1758| Conversion = [BoolConversion] conversion to bool
|
||||
# 1758| Type = [BoolType] bool
|
||||
# 1758| ValueCategory = prvalue
|
||||
# 1762| getStmt(1): [DeclStmt] declaration
|
||||
# 1762| getDeclarationEntry(0): [VariableDeclarationEntry] definition of w
|
||||
# 1762| Type = [IntType] int
|
||||
# 1763| getStmt(2): [IfStmt] if (...) ...
|
||||
# 1763| getInitialization(): [ExprStmt] ExprStmt
|
||||
# 1763| getExpr(): [AssignExpr] ... = ...
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| ValueCategory = lvalue
|
||||
# 1763| getLValue(): [VariableAccess] w
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| ValueCategory = lvalue
|
||||
# 1763| getRValue(): [VariableAccess] x
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| ValueCategory = prvalue(load)
|
||||
# 1763| getCondition(): [AddExpr] ... + ...
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| ValueCategory = prvalue
|
||||
# 1763| getLeftOperand(): [VariableAccess] x
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| ValueCategory = prvalue(load)
|
||||
# 1763| getRightOperand(): [Literal] 1
|
||||
# 1763| Type = [IntType] int
|
||||
# 1763| Value = [Literal] 1
|
||||
# 1763| ValueCategory = prvalue
|
||||
# 1763| getThen(): [BlockStmt] { ... }
|
||||
# 1764| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1764| getExpr(): [AssignExpr] ... = ...
|
||||
# 1764| Type = [IntType] int
|
||||
# 1764| ValueCategory = lvalue
|
||||
# 1764| getLValue(): [VariableAccess] x
|
||||
# 1764| Type = [IntType] int
|
||||
# 1764| ValueCategory = lvalue
|
||||
# 1764| getRValue(): [AddExpr] ... + ...
|
||||
# 1764| Type = [IntType] int
|
||||
# 1764| ValueCategory = prvalue
|
||||
# 1764| getLeftOperand(): [VariableAccess] x
|
||||
# 1764| Type = [IntType] int
|
||||
# 1764| ValueCategory = prvalue(load)
|
||||
# 1764| getRightOperand(): [VariableAccess] w
|
||||
# 1764| Type = [IntType] int
|
||||
# 1764| ValueCategory = prvalue(load)
|
||||
# 1763| getCondition().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1763| Conversion = [BoolConversion] conversion to bool
|
||||
# 1763| Type = [BoolType] bool
|
||||
# 1763| ValueCategory = prvalue
|
||||
# 1767| getStmt(3): [IfStmt] if (...) ...
|
||||
# 1767| getInitialization(): [ExprStmt] ExprStmt
|
||||
# 1767| getExpr(): [AssignExpr] ... = ...
|
||||
# 1767| Type = [IntType] int
|
||||
# 1767| ValueCategory = lvalue
|
||||
# 1767| getLValue(): [VariableAccess] w
|
||||
# 1767| Type = [IntType] int
|
||||
# 1767| ValueCategory = lvalue
|
||||
# 1767| getRValue(): [VariableAccess] x
|
||||
# 1767| Type = [IntType] int
|
||||
# 1767| ValueCategory = prvalue(load)
|
||||
# 1767| getCondition(): [ConditionDeclExpr] (condition decl)
|
||||
# 1767| Type = [BoolType] bool
|
||||
# 1767| ValueCategory = prvalue
|
||||
# 1767| getVariableAccess(): [VariableAccess] w2
|
||||
# 1767| Type = [IntType] int
|
||||
# 1767| ValueCategory = prvalue(load)
|
||||
# 1767| getVariableAccess().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1767| Conversion = [BoolConversion] conversion to bool
|
||||
# 1767| Type = [BoolType] bool
|
||||
# 1767| ValueCategory = prvalue
|
||||
# 1767| getThen(): [BlockStmt] { ... }
|
||||
# 1768| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1768| getExpr(): [AssignExpr] ... = ...
|
||||
# 1768| Type = [IntType] int
|
||||
# 1768| ValueCategory = lvalue
|
||||
# 1768| getLValue(): [VariableAccess] x
|
||||
# 1768| Type = [IntType] int
|
||||
# 1768| ValueCategory = lvalue
|
||||
# 1768| getRValue(): [AddExpr] ... + ...
|
||||
# 1768| Type = [IntType] int
|
||||
# 1768| ValueCategory = prvalue
|
||||
# 1768| getLeftOperand(): [VariableAccess] x
|
||||
# 1768| Type = [IntType] int
|
||||
# 1768| ValueCategory = prvalue(load)
|
||||
# 1768| getRightOperand(): [VariableAccess] w
|
||||
# 1768| Type = [IntType] int
|
||||
# 1768| ValueCategory = prvalue(load)
|
||||
# 1771| getStmt(4): [IfStmt] if (...) ...
|
||||
# 1771| getInitialization(): [DeclStmt] declaration
|
||||
# 1771| getDeclarationEntry(0): [VariableDeclarationEntry] definition of v
|
||||
# 1771| Type = [IntType] int
|
||||
# 1771| getVariable().getInitializer(): [Initializer] initializer for v
|
||||
# 1771| getExpr(): [VariableAccess] x
|
||||
# 1771| Type = [IntType] int
|
||||
# 1771| ValueCategory = prvalue(load)
|
||||
# 1771| getCondition(): [ConditionDeclExpr] (condition decl)
|
||||
# 1771| Type = [BoolType] bool
|
||||
# 1771| ValueCategory = prvalue
|
||||
# 1771| getVariableAccess(): [VariableAccess] v2
|
||||
# 1771| Type = [IntType] int
|
||||
# 1771| ValueCategory = prvalue(load)
|
||||
# 1771| getVariableAccess().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1771| Conversion = [BoolConversion] conversion to bool
|
||||
# 1771| Type = [BoolType] bool
|
||||
# 1771| ValueCategory = prvalue
|
||||
# 1771| getThen(): [BlockStmt] { ... }
|
||||
# 1772| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1772| getExpr(): [AssignExpr] ... = ...
|
||||
# 1772| Type = [IntType] int
|
||||
# 1772| ValueCategory = lvalue
|
||||
# 1772| getLValue(): [VariableAccess] x
|
||||
# 1772| Type = [IntType] int
|
||||
# 1772| ValueCategory = lvalue
|
||||
# 1772| getRValue(): [AddExpr] ... + ...
|
||||
# 1772| Type = [IntType] int
|
||||
# 1772| ValueCategory = prvalue
|
||||
# 1772| getLeftOperand(): [VariableAccess] x
|
||||
# 1772| Type = [IntType] int
|
||||
# 1772| ValueCategory = prvalue(load)
|
||||
# 1772| getRightOperand(): [VariableAccess] v
|
||||
# 1772| Type = [IntType] int
|
||||
# 1772| ValueCategory = prvalue(load)
|
||||
# 1775| getStmt(5): [DeclStmt] declaration
|
||||
# 1775| getDeclarationEntry(0): [VariableDeclarationEntry] definition of z
|
||||
# 1775| Type = [IntType] int
|
||||
# 1775| getVariable().getInitializer(): [Initializer] initializer for z
|
||||
# 1775| getExpr(): [VariableAccess] x
|
||||
# 1775| Type = [IntType] int
|
||||
# 1775| ValueCategory = prvalue(load)
|
||||
# 1776| getStmt(6): [IfStmt] if (...) ...
|
||||
# 1776| getCondition(): [VariableAccess] z
|
||||
# 1776| Type = [IntType] int
|
||||
# 1776| ValueCategory = prvalue(load)
|
||||
# 1776| getThen(): [BlockStmt] { ... }
|
||||
# 1777| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1777| getExpr(): [AssignExpr] ... = ...
|
||||
# 1777| Type = [IntType] int
|
||||
# 1777| ValueCategory = lvalue
|
||||
# 1777| getLValue(): [VariableAccess] x
|
||||
# 1777| Type = [IntType] int
|
||||
# 1777| ValueCategory = lvalue
|
||||
# 1777| getRValue(): [AddExpr] ... + ...
|
||||
# 1777| Type = [IntType] int
|
||||
# 1777| ValueCategory = prvalue
|
||||
# 1777| getLeftOperand(): [VariableAccess] x
|
||||
# 1777| Type = [IntType] int
|
||||
# 1777| ValueCategory = prvalue(load)
|
||||
# 1777| getRightOperand(): [VariableAccess] z
|
||||
# 1777| Type = [IntType] int
|
||||
# 1777| ValueCategory = prvalue(load)
|
||||
# 1776| getCondition().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1776| Conversion = [BoolConversion] conversion to bool
|
||||
# 1776| Type = [BoolType] bool
|
||||
# 1776| ValueCategory = prvalue
|
||||
# 1780| getStmt(7): [IfStmt] if (...) ...
|
||||
# 1780| getCondition(): [ConditionDeclExpr] (condition decl)
|
||||
# 1780| Type = [BoolType] bool
|
||||
# 1780| ValueCategory = prvalue
|
||||
# 1780| getVariableAccess(): [VariableAccess] z2
|
||||
# 1780| Type = [IntType] int
|
||||
# 1780| ValueCategory = prvalue(load)
|
||||
# 1780| getVariableAccess().getFullyConverted(): [CStyleCast] (bool)...
|
||||
# 1780| Conversion = [BoolConversion] conversion to bool
|
||||
# 1780| Type = [BoolType] bool
|
||||
# 1780| ValueCategory = prvalue
|
||||
# 1780| getThen(): [BlockStmt] { ... }
|
||||
# 1781| getStmt(0): [ExprStmt] ExprStmt
|
||||
# 1781| getExpr(): [AssignAddExpr] ... += ...
|
||||
# 1781| Type = [IntType] int
|
||||
# 1781| ValueCategory = lvalue
|
||||
# 1781| getLValue(): [VariableAccess] x
|
||||
# 1781| Type = [IntType] int
|
||||
# 1781| ValueCategory = lvalue
|
||||
# 1781| getRValue(): [VariableAccess] z2
|
||||
# 1781| Type = [IntType] int
|
||||
# 1781| ValueCategory = prvalue(load)
|
||||
# 1783| getStmt(8): [ReturnStmt] return ...
|
||||
# 1785| [TopLevelFunction] void switch_initialization(int)
|
||||
# 1785| <params>:
|
||||
# 1785| getParameter(0): [Parameter] x
|
||||
# 1785| Type = [IntType] int
|
||||
# 1785| getEntryPoint(): [BlockStmt] { ... }
|
||||
# 1786| getStmt(0): [SwitchStmt] switch (...) ...
|
||||
# 1786| getInitialization(): [DeclStmt] declaration
|
||||
# 1786| getDeclarationEntry(0): [VariableDeclarationEntry] definition of y
|
||||
# 1786| Type = [IntType] int
|
||||
# 1786| getVariable().getInitializer(): [Initializer] initializer for y
|
||||
# 1786| getExpr(): [VariableAccess] x
|
||||
# 1786| Type = [IntType] int
|
||||
# 1786| ValueCategory = prvalue(load)
|
||||
# 1786| getExpr(): [AddExpr] ... + ...
|
||||
# 1786| Type = [IntType] int
|
||||
# 1786| ValueCategory = prvalue
|
||||
# 1786| getLeftOperand(): [VariableAccess] x
|
||||
# 1786| Type = [IntType] int
|
||||
# 1786| ValueCategory = prvalue(load)
|
||||
# 1786| getRightOperand(): [Literal] 1
|
||||
# 1786| Type = [IntType] int
|
||||
# 1786| Value = [Literal] 1
|
||||
# 1786| ValueCategory = prvalue
|
||||
# 1786| getStmt(): [BlockStmt] { ... }
|
||||
# 1787| getStmt(0): [SwitchCase] default:
|
||||
# 1788| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1788| getExpr(): [AssignExpr] ... = ...
|
||||
# 1788| Type = [IntType] int
|
||||
# 1788| ValueCategory = lvalue
|
||||
# 1788| getLValue(): [VariableAccess] x
|
||||
# 1788| Type = [IntType] int
|
||||
# 1788| ValueCategory = lvalue
|
||||
# 1788| getRValue(): [AddExpr] ... + ...
|
||||
# 1788| Type = [IntType] int
|
||||
# 1788| ValueCategory = prvalue
|
||||
# 1788| getLeftOperand(): [VariableAccess] x
|
||||
# 1788| Type = [IntType] int
|
||||
# 1788| ValueCategory = prvalue(load)
|
||||
# 1788| getRightOperand(): [VariableAccess] y
|
||||
# 1788| Type = [IntType] int
|
||||
# 1788| ValueCategory = prvalue(load)
|
||||
# 1791| getStmt(1): [DeclStmt] declaration
|
||||
# 1791| getDeclarationEntry(0): [VariableDeclarationEntry] definition of w
|
||||
# 1791| Type = [IntType] int
|
||||
# 1792| getStmt(2): [SwitchStmt] switch (...) ...
|
||||
# 1792| getInitialization(): [ExprStmt] ExprStmt
|
||||
# 1792| getExpr(): [AssignExpr] ... = ...
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| ValueCategory = lvalue
|
||||
# 1792| getLValue(): [VariableAccess] w
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| ValueCategory = lvalue
|
||||
# 1792| getRValue(): [VariableAccess] x
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| ValueCategory = prvalue(load)
|
||||
# 1792| getExpr(): [AddExpr] ... + ...
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| ValueCategory = prvalue
|
||||
# 1792| getLeftOperand(): [VariableAccess] x
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| ValueCategory = prvalue(load)
|
||||
# 1792| getRightOperand(): [Literal] 1
|
||||
# 1792| Type = [IntType] int
|
||||
# 1792| Value = [Literal] 1
|
||||
# 1792| ValueCategory = prvalue
|
||||
# 1792| getStmt(): [BlockStmt] { ... }
|
||||
# 1793| getStmt(0): [SwitchCase] default:
|
||||
# 1794| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1794| getExpr(): [AssignExpr] ... = ...
|
||||
# 1794| Type = [IntType] int
|
||||
# 1794| ValueCategory = lvalue
|
||||
# 1794| getLValue(): [VariableAccess] x
|
||||
# 1794| Type = [IntType] int
|
||||
# 1794| ValueCategory = lvalue
|
||||
# 1794| getRValue(): [AddExpr] ... + ...
|
||||
# 1794| Type = [IntType] int
|
||||
# 1794| ValueCategory = prvalue
|
||||
# 1794| getLeftOperand(): [VariableAccess] x
|
||||
# 1794| Type = [IntType] int
|
||||
# 1794| ValueCategory = prvalue(load)
|
||||
# 1794| getRightOperand(): [VariableAccess] w
|
||||
# 1794| Type = [IntType] int
|
||||
# 1794| ValueCategory = prvalue(load)
|
||||
# 1797| getStmt(3): [SwitchStmt] switch (...) ...
|
||||
# 1797| getInitialization(): [ExprStmt] ExprStmt
|
||||
# 1797| getExpr(): [AssignExpr] ... = ...
|
||||
# 1797| Type = [IntType] int
|
||||
# 1797| ValueCategory = lvalue
|
||||
# 1797| getLValue(): [VariableAccess] w
|
||||
# 1797| Type = [IntType] int
|
||||
# 1797| ValueCategory = lvalue
|
||||
# 1797| getRValue(): [VariableAccess] x
|
||||
# 1797| Type = [IntType] int
|
||||
# 1797| ValueCategory = prvalue(load)
|
||||
# 1797| getExpr(): [ConditionDeclExpr] (condition decl)
|
||||
# 1797| Type = [IntType] int
|
||||
# 1797| ValueCategory = prvalue
|
||||
# 1797| getVariableAccess(): [VariableAccess] w2
|
||||
# 1797| Type = [IntType] int
|
||||
# 1797| ValueCategory = prvalue(load)
|
||||
# 1797| getStmt(): [BlockStmt] { ... }
|
||||
# 1798| getStmt(0): [SwitchCase] default:
|
||||
# 1799| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1799| getExpr(): [AssignExpr] ... = ...
|
||||
# 1799| Type = [IntType] int
|
||||
# 1799| ValueCategory = lvalue
|
||||
# 1799| getLValue(): [VariableAccess] x
|
||||
# 1799| Type = [IntType] int
|
||||
# 1799| ValueCategory = lvalue
|
||||
# 1799| getRValue(): [AddExpr] ... + ...
|
||||
# 1799| Type = [IntType] int
|
||||
# 1799| ValueCategory = prvalue
|
||||
# 1799| getLeftOperand(): [VariableAccess] x
|
||||
# 1799| Type = [IntType] int
|
||||
# 1799| ValueCategory = prvalue(load)
|
||||
# 1799| getRightOperand(): [VariableAccess] w
|
||||
# 1799| Type = [IntType] int
|
||||
# 1799| ValueCategory = prvalue(load)
|
||||
# 1802| getStmt(4): [SwitchStmt] switch (...) ...
|
||||
# 1802| getInitialization(): [DeclStmt] declaration
|
||||
# 1802| getDeclarationEntry(0): [VariableDeclarationEntry] definition of v
|
||||
# 1802| Type = [IntType] int
|
||||
# 1802| getVariable().getInitializer(): [Initializer] initializer for v
|
||||
# 1802| getExpr(): [VariableAccess] x
|
||||
# 1802| Type = [IntType] int
|
||||
# 1802| ValueCategory = prvalue(load)
|
||||
# 1802| getExpr(): [ConditionDeclExpr] (condition decl)
|
||||
# 1802| Type = [IntType] int
|
||||
# 1802| ValueCategory = prvalue
|
||||
# 1802| getVariableAccess(): [VariableAccess] v2
|
||||
# 1802| Type = [IntType] int
|
||||
# 1802| ValueCategory = prvalue(load)
|
||||
# 1802| getStmt(): [BlockStmt] { ... }
|
||||
# 1803| getStmt(0): [SwitchCase] default:
|
||||
# 1804| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1804| getExpr(): [AssignExpr] ... = ...
|
||||
# 1804| Type = [IntType] int
|
||||
# 1804| ValueCategory = lvalue
|
||||
# 1804| getLValue(): [VariableAccess] x
|
||||
# 1804| Type = [IntType] int
|
||||
# 1804| ValueCategory = lvalue
|
||||
# 1804| getRValue(): [AddExpr] ... + ...
|
||||
# 1804| Type = [IntType] int
|
||||
# 1804| ValueCategory = prvalue
|
||||
# 1804| getLeftOperand(): [VariableAccess] x
|
||||
# 1804| Type = [IntType] int
|
||||
# 1804| ValueCategory = prvalue(load)
|
||||
# 1804| getRightOperand(): [VariableAccess] v
|
||||
# 1804| Type = [IntType] int
|
||||
# 1804| ValueCategory = prvalue(load)
|
||||
# 1807| getStmt(5): [DeclStmt] declaration
|
||||
# 1807| getDeclarationEntry(0): [VariableDeclarationEntry] definition of z
|
||||
# 1807| Type = [IntType] int
|
||||
# 1807| getVariable().getInitializer(): [Initializer] initializer for z
|
||||
# 1807| getExpr(): [VariableAccess] x
|
||||
# 1807| Type = [IntType] int
|
||||
# 1807| ValueCategory = prvalue(load)
|
||||
# 1808| getStmt(6): [SwitchStmt] switch (...) ...
|
||||
# 1808| getExpr(): [VariableAccess] z
|
||||
# 1808| Type = [IntType] int
|
||||
# 1808| ValueCategory = prvalue(load)
|
||||
# 1808| getStmt(): [BlockStmt] { ... }
|
||||
# 1809| getStmt(0): [SwitchCase] default:
|
||||
# 1810| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1810| getExpr(): [AssignExpr] ... = ...
|
||||
# 1810| Type = [IntType] int
|
||||
# 1810| ValueCategory = lvalue
|
||||
# 1810| getLValue(): [VariableAccess] x
|
||||
# 1810| Type = [IntType] int
|
||||
# 1810| ValueCategory = lvalue
|
||||
# 1810| getRValue(): [AddExpr] ... + ...
|
||||
# 1810| Type = [IntType] int
|
||||
# 1810| ValueCategory = prvalue
|
||||
# 1810| getLeftOperand(): [VariableAccess] x
|
||||
# 1810| Type = [IntType] int
|
||||
# 1810| ValueCategory = prvalue(load)
|
||||
# 1810| getRightOperand(): [VariableAccess] z
|
||||
# 1810| Type = [IntType] int
|
||||
# 1810| ValueCategory = prvalue(load)
|
||||
# 1813| getStmt(7): [SwitchStmt] switch (...) ...
|
||||
# 1813| getExpr(): [ConditionDeclExpr] (condition decl)
|
||||
# 1813| Type = [IntType] int
|
||||
# 1813| ValueCategory = prvalue
|
||||
# 1813| getVariableAccess(): [VariableAccess] z2
|
||||
# 1813| Type = [IntType] int
|
||||
# 1813| ValueCategory = prvalue(load)
|
||||
# 1813| getStmt(): [BlockStmt] { ... }
|
||||
# 1814| getStmt(0): [SwitchCase] default:
|
||||
# 1815| getStmt(1): [ExprStmt] ExprStmt
|
||||
# 1815| getExpr(): [AssignAddExpr] ... += ...
|
||||
# 1815| Type = [IntType] int
|
||||
# 1815| ValueCategory = lvalue
|
||||
# 1815| getLValue(): [VariableAccess] x
|
||||
# 1815| Type = [IntType] int
|
||||
# 1815| ValueCategory = lvalue
|
||||
# 1815| getRValue(): [VariableAccess] z2
|
||||
# 1815| Type = [IntType] int
|
||||
# 1815| ValueCategory = prvalue(load)
|
||||
# 1817| getStmt(8): [ReturnStmt] return ...
|
||||
perf-regression.cpp:
|
||||
# 4| [CopyAssignmentOperator] Big& Big::operator=(Big const&)
|
||||
# 4| <params>:
|
||||
|
||||
@@ -1754,4 +1754,66 @@ int implicit_copy_constructor_test(
|
||||
CopyConstructorTestVirtualClass cy = y;
|
||||
}
|
||||
|
||||
void if_initialization(int x) {
|
||||
if (int y = x; x + 1) {
|
||||
x = x + y;
|
||||
}
|
||||
|
||||
int w;
|
||||
if (w = x; x + 1) {
|
||||
x = x + w;
|
||||
}
|
||||
|
||||
if (w = x; int w2 = w) {
|
||||
x = x + w;
|
||||
}
|
||||
|
||||
if (int v = x; int v2 = v) {
|
||||
x = x + v;
|
||||
}
|
||||
|
||||
int z = x;
|
||||
if (z) {
|
||||
x = x + z;
|
||||
}
|
||||
|
||||
if (int z2 = z) {
|
||||
x += z2;
|
||||
}
|
||||
}
|
||||
|
||||
void switch_initialization(int x) {
|
||||
switch (int y = x; x + 1) {
|
||||
default:
|
||||
x = x + y;
|
||||
}
|
||||
|
||||
int w;
|
||||
switch (w = x; x + 1) {
|
||||
default:
|
||||
x = x + w;
|
||||
}
|
||||
|
||||
switch (w = x; int w2 = w) {
|
||||
default:
|
||||
x = x + w;
|
||||
}
|
||||
|
||||
switch (int v = x; int v2 = v) {
|
||||
default:
|
||||
x = x + v;
|
||||
}
|
||||
|
||||
int z = x;
|
||||
switch (z) {
|
||||
default:
|
||||
x = x + z;
|
||||
}
|
||||
|
||||
switch (int z2 = z) {
|
||||
default:
|
||||
x += z2;
|
||||
}
|
||||
}
|
||||
|
||||
// semmle-extractor-options: -std=c++17 --clang
|
||||
|
||||
@@ -8215,6 +8215,248 @@
|
||||
| ir.cpp:1754:42:1754:42 | SideEffect | ~m1752_4 |
|
||||
| ir.cpp:1754:42:1754:42 | Unary | r1754_5 |
|
||||
| ir.cpp:1754:42:1754:42 | Unary | r1754_6 |
|
||||
| ir.cpp:1757:6:1757:22 | ChiPartial | partial:m1757_3 |
|
||||
| ir.cpp:1757:6:1757:22 | ChiTotal | total:m1757_2 |
|
||||
| ir.cpp:1757:6:1757:22 | SideEffect | m1757_3 |
|
||||
| ir.cpp:1757:28:1757:28 | Address | &:r1757_5 |
|
||||
| ir.cpp:1758:13:1758:13 | Address | &:r1758_1 |
|
||||
| ir.cpp:1758:17:1758:17 | Address | &:r1758_2 |
|
||||
| ir.cpp:1758:17:1758:17 | Load | m1757_6 |
|
||||
| ir.cpp:1758:17:1758:17 | StoreValue | r1758_3 |
|
||||
| ir.cpp:1758:20:1758:20 | Address | &:r1758_5 |
|
||||
| ir.cpp:1758:20:1758:20 | Left | r1758_6 |
|
||||
| ir.cpp:1758:20:1758:20 | Load | m1757_6 |
|
||||
| ir.cpp:1758:20:1758:24 | Condition | r1758_10 |
|
||||
| ir.cpp:1758:20:1758:24 | Left | r1758_8 |
|
||||
| ir.cpp:1758:20:1758:24 | Right | r1758_9 |
|
||||
| ir.cpp:1758:24:1758:24 | Right | r1758_7 |
|
||||
| ir.cpp:1759:9:1759:9 | Address | &:r1759_6 |
|
||||
| ir.cpp:1759:13:1759:13 | Address | &:r1759_1 |
|
||||
| ir.cpp:1759:13:1759:13 | Left | r1759_2 |
|
||||
| ir.cpp:1759:13:1759:13 | Load | m1757_6 |
|
||||
| ir.cpp:1759:13:1759:17 | StoreValue | r1759_5 |
|
||||
| ir.cpp:1759:17:1759:17 | Address | &:r1759_3 |
|
||||
| ir.cpp:1759:17:1759:17 | Load | m1758_4 |
|
||||
| ir.cpp:1759:17:1759:17 | Right | r1759_4 |
|
||||
| ir.cpp:1762:9:1762:9 | Address | &:r1762_2 |
|
||||
| ir.cpp:1762:9:1762:9 | Phi | from 0:m1757_6 |
|
||||
| ir.cpp:1762:9:1762:9 | Phi | from 1:m1759_7 |
|
||||
| ir.cpp:1763:9:1763:9 | Address | &:r1763_3 |
|
||||
| ir.cpp:1763:13:1763:13 | Address | &:r1763_1 |
|
||||
| ir.cpp:1763:13:1763:13 | Load | m1762_1 |
|
||||
| ir.cpp:1763:13:1763:13 | StoreValue | r1763_2 |
|
||||
| ir.cpp:1763:16:1763:16 | Address | &:r1763_5 |
|
||||
| ir.cpp:1763:16:1763:16 | Left | r1763_6 |
|
||||
| ir.cpp:1763:16:1763:16 | Load | m1762_1 |
|
||||
| ir.cpp:1763:16:1763:20 | Condition | r1763_10 |
|
||||
| ir.cpp:1763:16:1763:20 | Left | r1763_8 |
|
||||
| ir.cpp:1763:16:1763:20 | Right | r1763_9 |
|
||||
| ir.cpp:1763:20:1763:20 | Right | r1763_7 |
|
||||
| ir.cpp:1764:9:1764:9 | Address | &:r1764_6 |
|
||||
| ir.cpp:1764:13:1764:13 | Address | &:r1764_1 |
|
||||
| ir.cpp:1764:13:1764:13 | Left | r1764_2 |
|
||||
| ir.cpp:1764:13:1764:13 | Load | m1762_1 |
|
||||
| ir.cpp:1764:13:1764:17 | StoreValue | r1764_5 |
|
||||
| ir.cpp:1764:17:1764:17 | Address | &:r1764_3 |
|
||||
| ir.cpp:1764:17:1764:17 | Load | m1763_4 |
|
||||
| ir.cpp:1764:17:1764:17 | Right | r1764_4 |
|
||||
| ir.cpp:1767:9:1767:9 | Address | &:r1767_4 |
|
||||
| ir.cpp:1767:13:1767:13 | Address | &:r1767_2 |
|
||||
| ir.cpp:1767:13:1767:13 | Load | m1767_1 |
|
||||
| ir.cpp:1767:13:1767:13 | Phi | from 2:m1762_1 |
|
||||
| ir.cpp:1767:13:1767:13 | Phi | from 3:m1764_7 |
|
||||
| ir.cpp:1767:13:1767:13 | StoreValue | r1767_3 |
|
||||
| ir.cpp:1767:14:1767:25 | Address | &:r1767_6 |
|
||||
| ir.cpp:1767:14:1767:25 | Condition | r1767_14 |
|
||||
| ir.cpp:1767:20:1767:21 | Address | &:r1767_10 |
|
||||
| ir.cpp:1767:20:1767:21 | Left | r1767_11 |
|
||||
| ir.cpp:1767:20:1767:21 | Load | m1767_9 |
|
||||
| ir.cpp:1767:20:1767:21 | Right | r1767_12 |
|
||||
| ir.cpp:1767:20:1767:21 | Unary | r1767_13 |
|
||||
| ir.cpp:1767:25:1767:25 | Address | &:r1767_7 |
|
||||
| ir.cpp:1767:25:1767:25 | Load | m1767_5 |
|
||||
| ir.cpp:1767:25:1767:25 | StoreValue | r1767_8 |
|
||||
| ir.cpp:1768:9:1768:9 | Address | &:r1768_6 |
|
||||
| ir.cpp:1768:13:1768:13 | Address | &:r1768_1 |
|
||||
| ir.cpp:1768:13:1768:13 | Left | r1768_2 |
|
||||
| ir.cpp:1768:13:1768:13 | Load | m1767_1 |
|
||||
| ir.cpp:1768:13:1768:17 | StoreValue | r1768_5 |
|
||||
| ir.cpp:1768:17:1768:17 | Address | &:r1768_3 |
|
||||
| ir.cpp:1768:17:1768:17 | Load | m1767_5 |
|
||||
| ir.cpp:1768:17:1768:17 | Right | r1768_4 |
|
||||
| ir.cpp:1771:9:1771:29 | Address | &:r1771_6 |
|
||||
| ir.cpp:1771:9:1771:29 | Condition | r1771_14 |
|
||||
| ir.cpp:1771:13:1771:13 | Address | &:r1771_2 |
|
||||
| ir.cpp:1771:13:1771:13 | Phi | from 4:m1767_1 |
|
||||
| ir.cpp:1771:13:1771:13 | Phi | from 5:m1768_7 |
|
||||
| ir.cpp:1771:17:1771:17 | Address | &:r1771_3 |
|
||||
| ir.cpp:1771:17:1771:17 | Load | m1771_1 |
|
||||
| ir.cpp:1771:17:1771:17 | StoreValue | r1771_4 |
|
||||
| ir.cpp:1771:24:1771:25 | Address | &:r1771_10 |
|
||||
| ir.cpp:1771:24:1771:25 | Left | r1771_11 |
|
||||
| ir.cpp:1771:24:1771:25 | Load | m1771_9 |
|
||||
| ir.cpp:1771:24:1771:25 | Right | r1771_12 |
|
||||
| ir.cpp:1771:24:1771:25 | Unary | r1771_13 |
|
||||
| ir.cpp:1771:29:1771:29 | Address | &:r1771_7 |
|
||||
| ir.cpp:1771:29:1771:29 | Load | m1771_5 |
|
||||
| ir.cpp:1771:29:1771:29 | StoreValue | r1771_8 |
|
||||
| ir.cpp:1772:9:1772:9 | Address | &:r1772_6 |
|
||||
| ir.cpp:1772:13:1772:13 | Address | &:r1772_1 |
|
||||
| ir.cpp:1772:13:1772:13 | Left | r1772_2 |
|
||||
| ir.cpp:1772:13:1772:13 | Load | m1771_1 |
|
||||
| ir.cpp:1772:13:1772:17 | StoreValue | r1772_5 |
|
||||
| ir.cpp:1772:17:1772:17 | Address | &:r1772_3 |
|
||||
| ir.cpp:1772:17:1772:17 | Load | m1771_5 |
|
||||
| ir.cpp:1772:17:1772:17 | Right | r1772_4 |
|
||||
| ir.cpp:1775:9:1775:9 | Address | &:r1775_2 |
|
||||
| ir.cpp:1775:9:1775:9 | Phi | from 6:m1771_1 |
|
||||
| ir.cpp:1775:9:1775:9 | Phi | from 7:m1772_7 |
|
||||
| ir.cpp:1775:13:1775:13 | Address | &:r1775_3 |
|
||||
| ir.cpp:1775:13:1775:13 | Load | m1775_1 |
|
||||
| ir.cpp:1775:13:1775:13 | StoreValue | r1775_4 |
|
||||
| ir.cpp:1776:9:1776:9 | Address | &:r1776_1 |
|
||||
| ir.cpp:1776:9:1776:9 | Condition | r1776_4 |
|
||||
| ir.cpp:1776:9:1776:9 | Left | r1776_2 |
|
||||
| ir.cpp:1776:9:1776:9 | Load | m1775_5 |
|
||||
| ir.cpp:1776:9:1776:9 | Right | r1776_3 |
|
||||
| ir.cpp:1777:9:1777:9 | Address | &:r1777_6 |
|
||||
| ir.cpp:1777:13:1777:13 | Address | &:r1777_1 |
|
||||
| ir.cpp:1777:13:1777:13 | Left | r1777_2 |
|
||||
| ir.cpp:1777:13:1777:13 | Load | m1775_1 |
|
||||
| ir.cpp:1777:13:1777:17 | StoreValue | r1777_5 |
|
||||
| ir.cpp:1777:17:1777:17 | Address | &:r1777_3 |
|
||||
| ir.cpp:1777:17:1777:17 | Load | m1775_5 |
|
||||
| ir.cpp:1777:17:1777:17 | Right | r1777_4 |
|
||||
| ir.cpp:1780:9:1780:18 | Address | &:r1780_2 |
|
||||
| ir.cpp:1780:9:1780:18 | Condition | r1780_10 |
|
||||
| ir.cpp:1780:9:1780:18 | Phi | from 8:m1775_1 |
|
||||
| ir.cpp:1780:9:1780:18 | Phi | from 9:m1777_7 |
|
||||
| ir.cpp:1780:13:1780:14 | Address | &:r1780_6 |
|
||||
| ir.cpp:1780:13:1780:14 | Left | r1780_7 |
|
||||
| ir.cpp:1780:13:1780:14 | Load | m1780_5 |
|
||||
| ir.cpp:1780:13:1780:14 | Right | r1780_8 |
|
||||
| ir.cpp:1780:13:1780:14 | Unary | r1780_9 |
|
||||
| ir.cpp:1780:18:1780:18 | Address | &:r1780_3 |
|
||||
| ir.cpp:1780:18:1780:18 | Load | m1775_5 |
|
||||
| ir.cpp:1780:18:1780:18 | StoreValue | r1780_4 |
|
||||
| ir.cpp:1781:9:1781:9 | Address | &:r1781_3 |
|
||||
| ir.cpp:1781:9:1781:9 | Address | &:r1781_3 |
|
||||
| ir.cpp:1781:9:1781:9 | Left | r1781_4 |
|
||||
| ir.cpp:1781:9:1781:9 | Load | m1780_1 |
|
||||
| ir.cpp:1781:9:1781:15 | StoreValue | r1781_5 |
|
||||
| ir.cpp:1781:14:1781:15 | Address | &:r1781_1 |
|
||||
| ir.cpp:1781:14:1781:15 | Load | m1780_5 |
|
||||
| ir.cpp:1781:14:1781:15 | Right | r1781_2 |
|
||||
| ir.cpp:1785:6:1785:26 | ChiPartial | partial:m1785_3 |
|
||||
| ir.cpp:1785:6:1785:26 | ChiTotal | total:m1785_2 |
|
||||
| ir.cpp:1785:6:1785:26 | SideEffect | m1785_3 |
|
||||
| ir.cpp:1785:32:1785:32 | Address | &:r1785_5 |
|
||||
| ir.cpp:1786:17:1786:17 | Address | &:r1786_1 |
|
||||
| ir.cpp:1786:21:1786:21 | Address | &:r1786_2 |
|
||||
| ir.cpp:1786:21:1786:21 | Load | m1785_6 |
|
||||
| ir.cpp:1786:21:1786:21 | StoreValue | r1786_3 |
|
||||
| ir.cpp:1786:24:1786:24 | Address | &:r1786_5 |
|
||||
| ir.cpp:1786:24:1786:24 | Left | r1786_6 |
|
||||
| ir.cpp:1786:24:1786:24 | Load | m1785_6 |
|
||||
| ir.cpp:1786:24:1786:28 | Condition | r1786_8 |
|
||||
| ir.cpp:1786:28:1786:28 | Right | r1786_7 |
|
||||
| ir.cpp:1788:9:1788:9 | Address | &:r1788_6 |
|
||||
| ir.cpp:1788:13:1788:13 | Address | &:r1788_1 |
|
||||
| ir.cpp:1788:13:1788:13 | Left | r1788_2 |
|
||||
| ir.cpp:1788:13:1788:13 | Load | m1785_6 |
|
||||
| ir.cpp:1788:13:1788:17 | StoreValue | r1788_5 |
|
||||
| ir.cpp:1788:17:1788:17 | Address | &:r1788_3 |
|
||||
| ir.cpp:1788:17:1788:17 | Load | m1786_4 |
|
||||
| ir.cpp:1788:17:1788:17 | Right | r1788_4 |
|
||||
| ir.cpp:1791:9:1791:9 | Address | &:r1791_1 |
|
||||
| ir.cpp:1792:13:1792:13 | Address | &:r1792_3 |
|
||||
| ir.cpp:1792:17:1792:17 | Address | &:r1792_1 |
|
||||
| ir.cpp:1792:17:1792:17 | Load | m1788_7 |
|
||||
| ir.cpp:1792:17:1792:17 | StoreValue | r1792_2 |
|
||||
| ir.cpp:1792:20:1792:20 | Address | &:r1792_5 |
|
||||
| ir.cpp:1792:20:1792:20 | Left | r1792_6 |
|
||||
| ir.cpp:1792:20:1792:20 | Load | m1788_7 |
|
||||
| ir.cpp:1792:20:1792:24 | Condition | r1792_8 |
|
||||
| ir.cpp:1792:24:1792:24 | Right | r1792_7 |
|
||||
| ir.cpp:1794:9:1794:9 | Address | &:r1794_6 |
|
||||
| ir.cpp:1794:13:1794:13 | Address | &:r1794_1 |
|
||||
| ir.cpp:1794:13:1794:13 | Left | r1794_2 |
|
||||
| ir.cpp:1794:13:1794:13 | Load | m1788_7 |
|
||||
| ir.cpp:1794:13:1794:17 | StoreValue | r1794_5 |
|
||||
| ir.cpp:1794:17:1794:17 | Address | &:r1794_3 |
|
||||
| ir.cpp:1794:17:1794:17 | Load | m1792_4 |
|
||||
| ir.cpp:1794:17:1794:17 | Right | r1794_4 |
|
||||
| ir.cpp:1797:13:1797:13 | Address | &:r1797_3 |
|
||||
| ir.cpp:1797:17:1797:17 | Address | &:r1797_1 |
|
||||
| ir.cpp:1797:17:1797:17 | Load | m1794_7 |
|
||||
| ir.cpp:1797:17:1797:17 | StoreValue | r1797_2 |
|
||||
| ir.cpp:1797:18:1797:29 | Address | &:r1797_5 |
|
||||
| ir.cpp:1797:18:1797:29 | Condition | r1797_11 |
|
||||
| ir.cpp:1797:24:1797:25 | Address | &:r1797_9 |
|
||||
| ir.cpp:1797:24:1797:25 | Load | m1797_8 |
|
||||
| ir.cpp:1797:24:1797:25 | Unary | r1797_10 |
|
||||
| ir.cpp:1797:29:1797:29 | Address | &:r1797_6 |
|
||||
| ir.cpp:1797:29:1797:29 | Load | m1797_4 |
|
||||
| ir.cpp:1797:29:1797:29 | StoreValue | r1797_7 |
|
||||
| ir.cpp:1799:9:1799:9 | Address | &:r1799_6 |
|
||||
| ir.cpp:1799:13:1799:13 | Address | &:r1799_1 |
|
||||
| ir.cpp:1799:13:1799:13 | Left | r1799_2 |
|
||||
| ir.cpp:1799:13:1799:13 | Load | m1794_7 |
|
||||
| ir.cpp:1799:13:1799:17 | StoreValue | r1799_5 |
|
||||
| ir.cpp:1799:17:1799:17 | Address | &:r1799_3 |
|
||||
| ir.cpp:1799:17:1799:17 | Load | m1797_4 |
|
||||
| ir.cpp:1799:17:1799:17 | Right | r1799_4 |
|
||||
| ir.cpp:1802:13:1802:33 | Address | &:r1802_5 |
|
||||
| ir.cpp:1802:13:1802:33 | Condition | r1802_11 |
|
||||
| ir.cpp:1802:17:1802:17 | Address | &:r1802_1 |
|
||||
| ir.cpp:1802:21:1802:21 | Address | &:r1802_2 |
|
||||
| ir.cpp:1802:21:1802:21 | Load | m1799_7 |
|
||||
| ir.cpp:1802:21:1802:21 | StoreValue | r1802_3 |
|
||||
| ir.cpp:1802:28:1802:29 | Address | &:r1802_9 |
|
||||
| ir.cpp:1802:28:1802:29 | Load | m1802_8 |
|
||||
| ir.cpp:1802:28:1802:29 | Unary | r1802_10 |
|
||||
| ir.cpp:1802:33:1802:33 | Address | &:r1802_6 |
|
||||
| ir.cpp:1802:33:1802:33 | Load | m1802_4 |
|
||||
| ir.cpp:1802:33:1802:33 | StoreValue | r1802_7 |
|
||||
| ir.cpp:1804:9:1804:9 | Address | &:r1804_6 |
|
||||
| ir.cpp:1804:13:1804:13 | Address | &:r1804_1 |
|
||||
| ir.cpp:1804:13:1804:13 | Left | r1804_2 |
|
||||
| ir.cpp:1804:13:1804:13 | Load | m1799_7 |
|
||||
| ir.cpp:1804:13:1804:17 | StoreValue | r1804_5 |
|
||||
| ir.cpp:1804:17:1804:17 | Address | &:r1804_3 |
|
||||
| ir.cpp:1804:17:1804:17 | Load | m1802_4 |
|
||||
| ir.cpp:1804:17:1804:17 | Right | r1804_4 |
|
||||
| ir.cpp:1807:9:1807:9 | Address | &:r1807_1 |
|
||||
| ir.cpp:1807:13:1807:13 | Address | &:r1807_2 |
|
||||
| ir.cpp:1807:13:1807:13 | Load | m1804_7 |
|
||||
| ir.cpp:1807:13:1807:13 | StoreValue | r1807_3 |
|
||||
| ir.cpp:1808:13:1808:13 | Address | &:r1808_1 |
|
||||
| ir.cpp:1808:13:1808:13 | Condition | r1808_2 |
|
||||
| ir.cpp:1808:13:1808:13 | Load | m1807_4 |
|
||||
| ir.cpp:1810:9:1810:9 | Address | &:r1810_6 |
|
||||
| ir.cpp:1810:13:1810:13 | Address | &:r1810_1 |
|
||||
| ir.cpp:1810:13:1810:13 | Left | r1810_2 |
|
||||
| ir.cpp:1810:13:1810:13 | Load | m1804_7 |
|
||||
| ir.cpp:1810:13:1810:17 | StoreValue | r1810_5 |
|
||||
| ir.cpp:1810:17:1810:17 | Address | &:r1810_3 |
|
||||
| ir.cpp:1810:17:1810:17 | Load | m1807_4 |
|
||||
| ir.cpp:1810:17:1810:17 | Right | r1810_4 |
|
||||
| ir.cpp:1813:13:1813:22 | Address | &:r1813_1 |
|
||||
| ir.cpp:1813:13:1813:22 | Condition | r1813_7 |
|
||||
| ir.cpp:1813:17:1813:18 | Address | &:r1813_5 |
|
||||
| ir.cpp:1813:17:1813:18 | Load | m1813_4 |
|
||||
| ir.cpp:1813:17:1813:18 | Unary | r1813_6 |
|
||||
| ir.cpp:1813:22:1813:22 | Address | &:r1813_2 |
|
||||
| ir.cpp:1813:22:1813:22 | Load | m1807_4 |
|
||||
| ir.cpp:1813:22:1813:22 | StoreValue | r1813_3 |
|
||||
| ir.cpp:1815:9:1815:9 | Address | &:r1815_3 |
|
||||
| ir.cpp:1815:9:1815:9 | Address | &:r1815_3 |
|
||||
| ir.cpp:1815:9:1815:9 | Left | r1815_4 |
|
||||
| ir.cpp:1815:9:1815:9 | Load | m1810_7 |
|
||||
| ir.cpp:1815:9:1815:15 | StoreValue | r1815_5 |
|
||||
| ir.cpp:1815:14:1815:15 | Address | &:r1815_1 |
|
||||
| ir.cpp:1815:14:1815:15 | Load | m1813_4 |
|
||||
| ir.cpp:1815:14:1815:15 | Right | r1815_2 |
|
||||
| perf-regression.cpp:6:3:6:5 | Address | &:r6_5 |
|
||||
| perf-regression.cpp:6:3:6:5 | Address | &:r6_5 |
|
||||
| perf-regression.cpp:6:3:6:5 | Address | &:r6_7 |
|
||||
|
||||
@@ -9418,6 +9418,308 @@ ir.cpp:
|
||||
# 1750| v1750_6(void) = AliasedUse : ~m?
|
||||
# 1750| v1750_7(void) = ExitFunction :
|
||||
|
||||
# 1757| void if_initialization(int)
|
||||
# 1757| Block 0
|
||||
# 1757| v1757_1(void) = EnterFunction :
|
||||
# 1757| mu1757_2(unknown) = AliasedDefinition :
|
||||
# 1757| mu1757_3(unknown) = InitializeNonLocal :
|
||||
# 1757| r1757_4(glval<int>) = VariableAddress[x] :
|
||||
# 1757| mu1757_5(int) = InitializeParameter[x] : &:r1757_4
|
||||
# 1758| r1758_1(glval<int>) = VariableAddress[y] :
|
||||
# 1758| r1758_2(glval<int>) = VariableAddress[x] :
|
||||
# 1758| r1758_3(int) = Load[x] : &:r1758_2, ~m?
|
||||
# 1758| mu1758_4(int) = Store[y] : &:r1758_1, r1758_3
|
||||
# 1758| r1758_5(glval<int>) = VariableAddress[x] :
|
||||
# 1758| r1758_6(int) = Load[x] : &:r1758_5, ~m?
|
||||
# 1758| r1758_7(int) = Constant[1] :
|
||||
# 1758| r1758_8(int) = Add : r1758_6, r1758_7
|
||||
# 1758| r1758_9(int) = Constant[0] :
|
||||
# 1758| r1758_10(bool) = CompareNE : r1758_8, r1758_9
|
||||
# 1758| v1758_11(void) = ConditionalBranch : r1758_10
|
||||
#-----| False -> Block 2
|
||||
#-----| True -> Block 1
|
||||
|
||||
# 1759| Block 1
|
||||
# 1759| r1759_1(glval<int>) = VariableAddress[x] :
|
||||
# 1759| r1759_2(int) = Load[x] : &:r1759_1, ~m?
|
||||
# 1759| r1759_3(glval<int>) = VariableAddress[y] :
|
||||
# 1759| r1759_4(int) = Load[y] : &:r1759_3, ~m?
|
||||
# 1759| r1759_5(int) = Add : r1759_2, r1759_4
|
||||
# 1759| r1759_6(glval<int>) = VariableAddress[x] :
|
||||
# 1759| mu1759_7(int) = Store[x] : &:r1759_6, r1759_5
|
||||
#-----| Goto -> Block 2
|
||||
|
||||
# 1762| Block 2
|
||||
# 1762| r1762_1(glval<int>) = VariableAddress[w] :
|
||||
# 1762| mu1762_2(int) = Uninitialized[w] : &:r1762_1
|
||||
# 1763| r1763_1(glval<int>) = VariableAddress[x] :
|
||||
# 1763| r1763_2(int) = Load[x] : &:r1763_1, ~m?
|
||||
# 1763| r1763_3(glval<int>) = VariableAddress[w] :
|
||||
# 1763| mu1763_4(int) = Store[w] : &:r1763_3, r1763_2
|
||||
# 1763| r1763_5(glval<int>) = VariableAddress[x] :
|
||||
# 1763| r1763_6(int) = Load[x] : &:r1763_5, ~m?
|
||||
# 1763| r1763_7(int) = Constant[1] :
|
||||
# 1763| r1763_8(int) = Add : r1763_6, r1763_7
|
||||
# 1763| r1763_9(int) = Constant[0] :
|
||||
# 1763| r1763_10(bool) = CompareNE : r1763_8, r1763_9
|
||||
# 1763| v1763_11(void) = ConditionalBranch : r1763_10
|
||||
#-----| False -> Block 4
|
||||
#-----| True -> Block 3
|
||||
|
||||
# 1764| Block 3
|
||||
# 1764| r1764_1(glval<int>) = VariableAddress[x] :
|
||||
# 1764| r1764_2(int) = Load[x] : &:r1764_1, ~m?
|
||||
# 1764| r1764_3(glval<int>) = VariableAddress[w] :
|
||||
# 1764| r1764_4(int) = Load[w] : &:r1764_3, ~m?
|
||||
# 1764| r1764_5(int) = Add : r1764_2, r1764_4
|
||||
# 1764| r1764_6(glval<int>) = VariableAddress[x] :
|
||||
# 1764| mu1764_7(int) = Store[x] : &:r1764_6, r1764_5
|
||||
#-----| Goto -> Block 4
|
||||
|
||||
# 1767| Block 4
|
||||
# 1767| r1767_1(glval<int>) = VariableAddress[x] :
|
||||
# 1767| r1767_2(int) = Load[x] : &:r1767_1, ~m?
|
||||
# 1767| r1767_3(glval<int>) = VariableAddress[w] :
|
||||
# 1767| mu1767_4(int) = Store[w] : &:r1767_3, r1767_2
|
||||
# 1767| r1767_5(glval<int>) = VariableAddress[w2] :
|
||||
# 1767| r1767_6(glval<int>) = VariableAddress[w] :
|
||||
# 1767| r1767_7(int) = Load[w] : &:r1767_6, ~m?
|
||||
# 1767| mu1767_8(int) = Store[w2] : &:r1767_5, r1767_7
|
||||
# 1767| r1767_9(glval<int>) = VariableAddress[w2] :
|
||||
# 1767| r1767_10(int) = Load[w2] : &:r1767_9, ~m?
|
||||
# 1767| r1767_11(int) = Constant[0] :
|
||||
# 1767| r1767_12(bool) = CompareNE : r1767_10, r1767_11
|
||||
# 1767| r1767_13(bool) = CopyValue : r1767_12
|
||||
# 1767| v1767_14(void) = ConditionalBranch : r1767_13
|
||||
#-----| False -> Block 6
|
||||
#-----| True -> Block 5
|
||||
|
||||
# 1768| Block 5
|
||||
# 1768| r1768_1(glval<int>) = VariableAddress[x] :
|
||||
# 1768| r1768_2(int) = Load[x] : &:r1768_1, ~m?
|
||||
# 1768| r1768_3(glval<int>) = VariableAddress[w] :
|
||||
# 1768| r1768_4(int) = Load[w] : &:r1768_3, ~m?
|
||||
# 1768| r1768_5(int) = Add : r1768_2, r1768_4
|
||||
# 1768| r1768_6(glval<int>) = VariableAddress[x] :
|
||||
# 1768| mu1768_7(int) = Store[x] : &:r1768_6, r1768_5
|
||||
#-----| Goto -> Block 6
|
||||
|
||||
# 1771| Block 6
|
||||
# 1771| r1771_1(glval<int>) = VariableAddress[v] :
|
||||
# 1771| r1771_2(glval<int>) = VariableAddress[x] :
|
||||
# 1771| r1771_3(int) = Load[x] : &:r1771_2, ~m?
|
||||
# 1771| mu1771_4(int) = Store[v] : &:r1771_1, r1771_3
|
||||
# 1771| r1771_5(glval<int>) = VariableAddress[v2] :
|
||||
# 1771| r1771_6(glval<int>) = VariableAddress[v] :
|
||||
# 1771| r1771_7(int) = Load[v] : &:r1771_6, ~m?
|
||||
# 1771| mu1771_8(int) = Store[v2] : &:r1771_5, r1771_7
|
||||
# 1771| r1771_9(glval<int>) = VariableAddress[v2] :
|
||||
# 1771| r1771_10(int) = Load[v2] : &:r1771_9, ~m?
|
||||
# 1771| r1771_11(int) = Constant[0] :
|
||||
# 1771| r1771_12(bool) = CompareNE : r1771_10, r1771_11
|
||||
# 1771| r1771_13(bool) = CopyValue : r1771_12
|
||||
# 1771| v1771_14(void) = ConditionalBranch : r1771_13
|
||||
#-----| False -> Block 8
|
||||
#-----| True -> Block 7
|
||||
|
||||
# 1772| Block 7
|
||||
# 1772| r1772_1(glval<int>) = VariableAddress[x] :
|
||||
# 1772| r1772_2(int) = Load[x] : &:r1772_1, ~m?
|
||||
# 1772| r1772_3(glval<int>) = VariableAddress[v] :
|
||||
# 1772| r1772_4(int) = Load[v] : &:r1772_3, ~m?
|
||||
# 1772| r1772_5(int) = Add : r1772_2, r1772_4
|
||||
# 1772| r1772_6(glval<int>) = VariableAddress[x] :
|
||||
# 1772| mu1772_7(int) = Store[x] : &:r1772_6, r1772_5
|
||||
#-----| Goto -> Block 8
|
||||
|
||||
# 1775| Block 8
|
||||
# 1775| r1775_1(glval<int>) = VariableAddress[z] :
|
||||
# 1775| r1775_2(glval<int>) = VariableAddress[x] :
|
||||
# 1775| r1775_3(int) = Load[x] : &:r1775_2, ~m?
|
||||
# 1775| mu1775_4(int) = Store[z] : &:r1775_1, r1775_3
|
||||
# 1776| r1776_1(glval<int>) = VariableAddress[z] :
|
||||
# 1776| r1776_2(int) = Load[z] : &:r1776_1, ~m?
|
||||
# 1776| r1776_3(int) = Constant[0] :
|
||||
# 1776| r1776_4(bool) = CompareNE : r1776_2, r1776_3
|
||||
# 1776| v1776_5(void) = ConditionalBranch : r1776_4
|
||||
#-----| False -> Block 10
|
||||
#-----| True -> Block 9
|
||||
|
||||
# 1777| Block 9
|
||||
# 1777| r1777_1(glval<int>) = VariableAddress[x] :
|
||||
# 1777| r1777_2(int) = Load[x] : &:r1777_1, ~m?
|
||||
# 1777| r1777_3(glval<int>) = VariableAddress[z] :
|
||||
# 1777| r1777_4(int) = Load[z] : &:r1777_3, ~m?
|
||||
# 1777| r1777_5(int) = Add : r1777_2, r1777_4
|
||||
# 1777| r1777_6(glval<int>) = VariableAddress[x] :
|
||||
# 1777| mu1777_7(int) = Store[x] : &:r1777_6, r1777_5
|
||||
#-----| Goto -> Block 10
|
||||
|
||||
# 1780| Block 10
|
||||
# 1780| r1780_1(glval<int>) = VariableAddress[z2] :
|
||||
# 1780| r1780_2(glval<int>) = VariableAddress[z] :
|
||||
# 1780| r1780_3(int) = Load[z] : &:r1780_2, ~m?
|
||||
# 1780| mu1780_4(int) = Store[z2] : &:r1780_1, r1780_3
|
||||
# 1780| r1780_5(glval<int>) = VariableAddress[z2] :
|
||||
# 1780| r1780_6(int) = Load[z2] : &:r1780_5, ~m?
|
||||
# 1780| r1780_7(int) = Constant[0] :
|
||||
# 1780| r1780_8(bool) = CompareNE : r1780_6, r1780_7
|
||||
# 1780| r1780_9(bool) = CopyValue : r1780_8
|
||||
# 1780| v1780_10(void) = ConditionalBranch : r1780_9
|
||||
#-----| False -> Block 12
|
||||
#-----| True -> Block 11
|
||||
|
||||
# 1781| Block 11
|
||||
# 1781| r1781_1(glval<int>) = VariableAddress[z2] :
|
||||
# 1781| r1781_2(int) = Load[z2] : &:r1781_1, ~m?
|
||||
# 1781| r1781_3(glval<int>) = VariableAddress[x] :
|
||||
# 1781| r1781_4(int) = Load[x] : &:r1781_3, ~m?
|
||||
# 1781| r1781_5(int) = Add : r1781_4, r1781_2
|
||||
# 1781| mu1781_6(int) = Store[x] : &:r1781_3, r1781_5
|
||||
#-----| Goto -> Block 12
|
||||
|
||||
# 1783| Block 12
|
||||
# 1783| v1783_1(void) = NoOp :
|
||||
# 1757| v1757_6(void) = ReturnVoid :
|
||||
# 1757| v1757_7(void) = AliasedUse : ~m?
|
||||
# 1757| v1757_8(void) = ExitFunction :
|
||||
|
||||
# 1785| void switch_initialization(int)
|
||||
# 1785| Block 0
|
||||
# 1785| v1785_1(void) = EnterFunction :
|
||||
# 1785| mu1785_2(unknown) = AliasedDefinition :
|
||||
# 1785| mu1785_3(unknown) = InitializeNonLocal :
|
||||
# 1785| r1785_4(glval<int>) = VariableAddress[x] :
|
||||
# 1785| mu1785_5(int) = InitializeParameter[x] : &:r1785_4
|
||||
# 1786| r1786_1(glval<int>) = VariableAddress[y] :
|
||||
# 1786| r1786_2(glval<int>) = VariableAddress[x] :
|
||||
# 1786| r1786_3(int) = Load[x] : &:r1786_2, ~m?
|
||||
# 1786| mu1786_4(int) = Store[y] : &:r1786_1, r1786_3
|
||||
# 1786| r1786_5(glval<int>) = VariableAddress[x] :
|
||||
# 1786| r1786_6(int) = Load[x] : &:r1786_5, ~m?
|
||||
# 1786| r1786_7(int) = Constant[1] :
|
||||
# 1786| r1786_8(int) = Add : r1786_6, r1786_7
|
||||
# 1786| v1786_9(void) = Switch : r1786_8
|
||||
#-----| Default -> Block 1
|
||||
|
||||
# 1787| Block 1
|
||||
# 1787| v1787_1(void) = NoOp :
|
||||
# 1788| r1788_1(glval<int>) = VariableAddress[x] :
|
||||
# 1788| r1788_2(int) = Load[x] : &:r1788_1, ~m?
|
||||
# 1788| r1788_3(glval<int>) = VariableAddress[y] :
|
||||
# 1788| r1788_4(int) = Load[y] : &:r1788_3, ~m?
|
||||
# 1788| r1788_5(int) = Add : r1788_2, r1788_4
|
||||
# 1788| r1788_6(glval<int>) = VariableAddress[x] :
|
||||
# 1788| mu1788_7(int) = Store[x] : &:r1788_6, r1788_5
|
||||
# 1791| r1791_1(glval<int>) = VariableAddress[w] :
|
||||
# 1791| mu1791_2(int) = Uninitialized[w] : &:r1791_1
|
||||
# 1792| r1792_1(glval<int>) = VariableAddress[x] :
|
||||
# 1792| r1792_2(int) = Load[x] : &:r1792_1, ~m?
|
||||
# 1792| r1792_3(glval<int>) = VariableAddress[w] :
|
||||
# 1792| mu1792_4(int) = Store[w] : &:r1792_3, r1792_2
|
||||
# 1792| r1792_5(glval<int>) = VariableAddress[x] :
|
||||
# 1792| r1792_6(int) = Load[x] : &:r1792_5, ~m?
|
||||
# 1792| r1792_7(int) = Constant[1] :
|
||||
# 1792| r1792_8(int) = Add : r1792_6, r1792_7
|
||||
# 1792| v1792_9(void) = Switch : r1792_8
|
||||
#-----| Default -> Block 2
|
||||
|
||||
# 1793| Block 2
|
||||
# 1793| v1793_1(void) = NoOp :
|
||||
# 1794| r1794_1(glval<int>) = VariableAddress[x] :
|
||||
# 1794| r1794_2(int) = Load[x] : &:r1794_1, ~m?
|
||||
# 1794| r1794_3(glval<int>) = VariableAddress[w] :
|
||||
# 1794| r1794_4(int) = Load[w] : &:r1794_3, ~m?
|
||||
# 1794| r1794_5(int) = Add : r1794_2, r1794_4
|
||||
# 1794| r1794_6(glval<int>) = VariableAddress[x] :
|
||||
# 1794| mu1794_7(int) = Store[x] : &:r1794_6, r1794_5
|
||||
# 1797| r1797_1(glval<int>) = VariableAddress[x] :
|
||||
# 1797| r1797_2(int) = Load[x] : &:r1797_1, ~m?
|
||||
# 1797| r1797_3(glval<int>) = VariableAddress[w] :
|
||||
# 1797| mu1797_4(int) = Store[w] : &:r1797_3, r1797_2
|
||||
# 1797| r1797_5(glval<int>) = VariableAddress[w2] :
|
||||
# 1797| r1797_6(glval<int>) = VariableAddress[w] :
|
||||
# 1797| r1797_7(int) = Load[w] : &:r1797_6, ~m?
|
||||
# 1797| mu1797_8(int) = Store[w2] : &:r1797_5, r1797_7
|
||||
# 1797| r1797_9(glval<int>) = VariableAddress[w2] :
|
||||
# 1797| r1797_10(int) = Load[w2] : &:r1797_9, ~m?
|
||||
# 1797| r1797_11(int) = CopyValue : r1797_10
|
||||
# 1797| v1797_12(void) = Switch : r1797_11
|
||||
#-----| Default -> Block 3
|
||||
|
||||
# 1798| Block 3
|
||||
# 1798| v1798_1(void) = NoOp :
|
||||
# 1799| r1799_1(glval<int>) = VariableAddress[x] :
|
||||
# 1799| r1799_2(int) = Load[x] : &:r1799_1, ~m?
|
||||
# 1799| r1799_3(glval<int>) = VariableAddress[w] :
|
||||
# 1799| r1799_4(int) = Load[w] : &:r1799_3, ~m?
|
||||
# 1799| r1799_5(int) = Add : r1799_2, r1799_4
|
||||
# 1799| r1799_6(glval<int>) = VariableAddress[x] :
|
||||
# 1799| mu1799_7(int) = Store[x] : &:r1799_6, r1799_5
|
||||
# 1802| r1802_1(glval<int>) = VariableAddress[v] :
|
||||
# 1802| r1802_2(glval<int>) = VariableAddress[x] :
|
||||
# 1802| r1802_3(int) = Load[x] : &:r1802_2, ~m?
|
||||
# 1802| mu1802_4(int) = Store[v] : &:r1802_1, r1802_3
|
||||
# 1802| r1802_5(glval<int>) = VariableAddress[v2] :
|
||||
# 1802| r1802_6(glval<int>) = VariableAddress[v] :
|
||||
# 1802| r1802_7(int) = Load[v] : &:r1802_6, ~m?
|
||||
# 1802| mu1802_8(int) = Store[v2] : &:r1802_5, r1802_7
|
||||
# 1802| r1802_9(glval<int>) = VariableAddress[v2] :
|
||||
# 1802| r1802_10(int) = Load[v2] : &:r1802_9, ~m?
|
||||
# 1802| r1802_11(int) = CopyValue : r1802_10
|
||||
# 1802| v1802_12(void) = Switch : r1802_11
|
||||
#-----| Default -> Block 4
|
||||
|
||||
# 1803| Block 4
|
||||
# 1803| v1803_1(void) = NoOp :
|
||||
# 1804| r1804_1(glval<int>) = VariableAddress[x] :
|
||||
# 1804| r1804_2(int) = Load[x] : &:r1804_1, ~m?
|
||||
# 1804| r1804_3(glval<int>) = VariableAddress[v] :
|
||||
# 1804| r1804_4(int) = Load[v] : &:r1804_3, ~m?
|
||||
# 1804| r1804_5(int) = Add : r1804_2, r1804_4
|
||||
# 1804| r1804_6(glval<int>) = VariableAddress[x] :
|
||||
# 1804| mu1804_7(int) = Store[x] : &:r1804_6, r1804_5
|
||||
# 1807| r1807_1(glval<int>) = VariableAddress[z] :
|
||||
# 1807| r1807_2(glval<int>) = VariableAddress[x] :
|
||||
# 1807| r1807_3(int) = Load[x] : &:r1807_2, ~m?
|
||||
# 1807| mu1807_4(int) = Store[z] : &:r1807_1, r1807_3
|
||||
# 1808| r1808_1(glval<int>) = VariableAddress[z] :
|
||||
# 1808| r1808_2(int) = Load[z] : &:r1808_1, ~m?
|
||||
# 1808| v1808_3(void) = Switch : r1808_2
|
||||
#-----| Default -> Block 5
|
||||
|
||||
# 1809| Block 5
|
||||
# 1809| v1809_1(void) = NoOp :
|
||||
# 1810| r1810_1(glval<int>) = VariableAddress[x] :
|
||||
# 1810| r1810_2(int) = Load[x] : &:r1810_1, ~m?
|
||||
# 1810| r1810_3(glval<int>) = VariableAddress[z] :
|
||||
# 1810| r1810_4(int) = Load[z] : &:r1810_3, ~m?
|
||||
# 1810| r1810_5(int) = Add : r1810_2, r1810_4
|
||||
# 1810| r1810_6(glval<int>) = VariableAddress[x] :
|
||||
# 1810| mu1810_7(int) = Store[x] : &:r1810_6, r1810_5
|
||||
# 1813| r1813_1(glval<int>) = VariableAddress[z2] :
|
||||
# 1813| r1813_2(glval<int>) = VariableAddress[z] :
|
||||
# 1813| r1813_3(int) = Load[z] : &:r1813_2, ~m?
|
||||
# 1813| mu1813_4(int) = Store[z2] : &:r1813_1, r1813_3
|
||||
# 1813| r1813_5(glval<int>) = VariableAddress[z2] :
|
||||
# 1813| r1813_6(int) = Load[z2] : &:r1813_5, ~m?
|
||||
# 1813| r1813_7(int) = CopyValue : r1813_6
|
||||
# 1813| v1813_8(void) = Switch : r1813_7
|
||||
#-----| Default -> Block 6
|
||||
|
||||
# 1814| Block 6
|
||||
# 1814| v1814_1(void) = NoOp :
|
||||
# 1815| r1815_1(glval<int>) = VariableAddress[z2] :
|
||||
# 1815| r1815_2(int) = Load[z2] : &:r1815_1, ~m?
|
||||
# 1815| r1815_3(glval<int>) = VariableAddress[x] :
|
||||
# 1815| r1815_4(int) = Load[x] : &:r1815_3, ~m?
|
||||
# 1815| r1815_5(int) = Add : r1815_4, r1815_2
|
||||
# 1815| mu1815_6(int) = Store[x] : &:r1815_3, r1815_5
|
||||
# 1817| v1817_1(void) = NoOp :
|
||||
# 1785| v1785_6(void) = ReturnVoid :
|
||||
# 1785| v1785_7(void) = AliasedUse : ~m?
|
||||
# 1785| v1785_8(void) = ExitFunction :
|
||||
|
||||
perf-regression.cpp:
|
||||
# 6| void Big::Big()
|
||||
# 6| Block 0
|
||||
|
||||
@@ -4,6 +4,15 @@ edges
|
||||
| tests3.cpp:23:21:23:53 | call to createXMLReader | tests3.cpp:25:2:25:2 | p |
|
||||
| tests3.cpp:60:21:60:53 | call to createXMLReader | tests3.cpp:63:2:63:2 | p |
|
||||
| tests3.cpp:67:21:67:53 | call to createXMLReader | tests3.cpp:70:2:70:2 | p |
|
||||
| tests5.cpp:27:25:27:38 | call to createLSParser | tests5.cpp:29:2:29:2 | p |
|
||||
| tests5.cpp:40:25:40:38 | call to createLSParser | tests5.cpp:43:2:43:2 | p |
|
||||
| tests5.cpp:55:25:55:38 | call to createLSParser | tests5.cpp:59:2:59:2 | p |
|
||||
| tests5.cpp:81:25:81:38 | call to createLSParser | tests5.cpp:83:2:83:2 | p |
|
||||
| tests5.cpp:81:25:81:38 | call to createLSParser | tests5.cpp:83:2:83:2 | p |
|
||||
| tests5.cpp:83:2:83:2 | p | tests5.cpp:85:2:85:2 | p |
|
||||
| tests5.cpp:85:2:85:2 | p | tests5.cpp:86:2:86:2 | p |
|
||||
| tests5.cpp:86:2:86:2 | p | tests5.cpp:88:2:88:2 | p |
|
||||
| tests5.cpp:88:2:88:2 | p | tests5.cpp:89:2:89:2 | p |
|
||||
| tests.cpp:15:23:15:43 | XercesDOMParser output argument | tests.cpp:17:2:17:2 | p |
|
||||
| tests.cpp:28:23:28:43 | XercesDOMParser output argument | tests.cpp:31:2:31:2 | p |
|
||||
| tests.cpp:35:19:35:19 | VariableAddress [post update] | tests.cpp:37:2:37:2 | p |
|
||||
@@ -46,6 +55,19 @@ nodes
|
||||
| tests4.cpp:46:34:46:68 | ... \| ... | semmle.label | ... \| ... |
|
||||
| tests4.cpp:77:34:77:38 | flags | semmle.label | flags |
|
||||
| tests4.cpp:130:39:130:55 | (int)... | semmle.label | (int)... |
|
||||
| tests5.cpp:27:25:27:38 | call to createLSParser | semmle.label | call to createLSParser |
|
||||
| tests5.cpp:29:2:29:2 | p | semmle.label | p |
|
||||
| tests5.cpp:40:25:40:38 | call to createLSParser | semmle.label | call to createLSParser |
|
||||
| tests5.cpp:43:2:43:2 | p | semmle.label | p |
|
||||
| tests5.cpp:55:25:55:38 | call to createLSParser | semmle.label | call to createLSParser |
|
||||
| tests5.cpp:59:2:59:2 | p | semmle.label | p |
|
||||
| tests5.cpp:81:25:81:38 | call to createLSParser | semmle.label | call to createLSParser |
|
||||
| tests5.cpp:83:2:83:2 | p | semmle.label | p |
|
||||
| tests5.cpp:83:2:83:2 | p | semmle.label | p |
|
||||
| tests5.cpp:85:2:85:2 | p | semmle.label | p |
|
||||
| tests5.cpp:86:2:86:2 | p | semmle.label | p |
|
||||
| tests5.cpp:88:2:88:2 | p | semmle.label | p |
|
||||
| tests5.cpp:89:2:89:2 | p | semmle.label | p |
|
||||
| tests.cpp:15:23:15:43 | XercesDOMParser output argument | semmle.label | XercesDOMParser output argument |
|
||||
| tests.cpp:17:2:17:2 | p | semmle.label | p |
|
||||
| tests.cpp:28:23:28:43 | XercesDOMParser output argument | semmle.label | XercesDOMParser output argument |
|
||||
@@ -93,6 +115,11 @@ subpaths
|
||||
| tests4.cpp:46:34:46:68 | ... \| ... | tests4.cpp:46:34:46:68 | ... \| ... | tests4.cpp:46:34:46:68 | ... \| ... | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests4.cpp:46:34:46:68 | ... \| ... | XML parser |
|
||||
| tests4.cpp:77:34:77:38 | flags | tests4.cpp:77:34:77:38 | flags | tests4.cpp:77:34:77:38 | flags | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests4.cpp:77:34:77:38 | flags | XML parser |
|
||||
| tests4.cpp:130:39:130:55 | (int)... | tests4.cpp:130:39:130:55 | (int)... | tests4.cpp:130:39:130:55 | (int)... | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests4.cpp:130:39:130:55 | (int)... | XML parser |
|
||||
| tests5.cpp:29:2:29:2 | p | tests5.cpp:27:25:27:38 | call to createLSParser | tests5.cpp:29:2:29:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests5.cpp:27:25:27:38 | call to createLSParser | XML parser |
|
||||
| tests5.cpp:43:2:43:2 | p | tests5.cpp:40:25:40:38 | call to createLSParser | tests5.cpp:43:2:43:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests5.cpp:40:25:40:38 | call to createLSParser | XML parser |
|
||||
| tests5.cpp:59:2:59:2 | p | tests5.cpp:55:25:55:38 | call to createLSParser | tests5.cpp:59:2:59:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests5.cpp:55:25:55:38 | call to createLSParser | XML parser |
|
||||
| tests5.cpp:83:2:83:2 | p | tests5.cpp:81:25:81:38 | call to createLSParser | tests5.cpp:83:2:83:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests5.cpp:81:25:81:38 | call to createLSParser | XML parser |
|
||||
| tests5.cpp:89:2:89:2 | p | tests5.cpp:81:25:81:38 | call to createLSParser | tests5.cpp:89:2:89:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests5.cpp:81:25:81:38 | call to createLSParser | XML parser |
|
||||
| tests.cpp:17:2:17:2 | p | tests.cpp:15:23:15:43 | XercesDOMParser output argument | tests.cpp:17:2:17:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests.cpp:15:23:15:43 | XercesDOMParser output argument | XML parser |
|
||||
| tests.cpp:31:2:31:2 | p | tests.cpp:28:23:28:43 | XercesDOMParser output argument | tests.cpp:31:2:31:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests.cpp:28:23:28:43 | XercesDOMParser output argument | XML parser |
|
||||
| tests.cpp:39:2:39:2 | p | tests.cpp:35:23:35:43 | XercesDOMParser output argument | tests.cpp:39:2:39:2 | p | This $@ is not configured to prevent an XML external entity (XXE) attack. | tests.cpp:35:23:35:43 | XercesDOMParser output argument | XML parser |
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void test5_1(DOMImplementationLS *impl, InputSource &data) {
|
||||
DOMLSParser *p = impl->createLSParser();
|
||||
|
||||
p->parse(data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
p->parse(data); // BAD (parser not correctly configured)
|
||||
}
|
||||
|
||||
void test5_2(DOMImplementationLS *impl, InputSource &data) {
|
||||
@@ -40,7 +40,7 @@ void test5_3(DOMImplementationLS *impl, InputSource &data) {
|
||||
DOMLSParser *p = impl->createLSParser();
|
||||
|
||||
p->getDomConfig()->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, false);
|
||||
p->parse(data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
p->parse(data); // BAD (parser not correctly configured)
|
||||
}
|
||||
|
||||
void test5_4(DOMImplementationLS *impl, InputSource &data) {
|
||||
@@ -56,7 +56,7 @@ void test5_5(DOMImplementationLS *impl, InputSource &data) {
|
||||
DOMConfiguration *cfg = p->getDomConfig();
|
||||
|
||||
cfg->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, false);
|
||||
p->parse(data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
p->parse(data); // BAD (parser not correctly configured)
|
||||
}
|
||||
|
||||
DOMImplementationLS *g_impl;
|
||||
@@ -76,3 +76,28 @@ void test5_6() {
|
||||
g_p1->parse(*g_data); // GOOD
|
||||
g_p2->parse(*g_data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
}
|
||||
|
||||
void test5_7(DOMImplementationLS *impl, InputSource &data) {
|
||||
DOMLSParser *p = impl->createLSParser();
|
||||
|
||||
p->parse(data); // BAD (parser not correctly configured)
|
||||
|
||||
p->getDomConfig()->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, true);
|
||||
p->parse(data); // GOOD
|
||||
|
||||
p->getDomConfig()->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, false);
|
||||
p->parse(data); // BAD (parser not correctly configured)
|
||||
}
|
||||
|
||||
void test5_8(DOMImplementationLS *impl, InputSource &data) {
|
||||
DOMLSParser *p = impl->createLSParser();
|
||||
DOMConfiguration *cfg = p->getDomConfig();
|
||||
|
||||
p->parse(data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
|
||||
cfg->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, true);
|
||||
p->parse(data); // GOOD
|
||||
|
||||
cfg->setParameter(XMLUni::fgXercesDisableDefaultEntityResolution, false);
|
||||
p->parse(data); // BAD (parser not correctly configured) [NOT DETECTED]
|
||||
}
|
||||
|
||||
8
cpp/ql/test/successor-tests/ifstmt/ifstmt/ifstmt.cpp
Normal file
8
cpp/ql/test/successor-tests/ifstmt/ifstmt/ifstmt.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
void normal(int x, int y) {
|
||||
if(int z = y; x == z) {
|
||||
l1:;
|
||||
}
|
||||
l2:;
|
||||
}
|
||||
|
||||
// semmle-extractor-options: -std=c++17
|
||||
@@ -1 +1,2 @@
|
||||
| ifstmt.c:28:6:28:11 | ... == ... | l2 |
|
||||
| ifstmt.cpp:2:17:2:22 | ... == ... | l2 |
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
| ifstmt.c:28:6:28:11 | ... == ... | l1 |
|
||||
| ifstmt.cpp:2:17:2:22 | ... == ... | l1 |
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
| ifstmt.c:29:8:29:8 | ; | l2 |
|
||||
| ifstmt.cpp:3:8:3:8 | ; | l2 |
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/**
|
||||
* @name ifstmt05
|
||||
* @description Every if statement has its condition or one of the condition's descendants as its unique successor.
|
||||
* @description Every if statement with an initialization has the initialization or one of the
|
||||
* initialization's descendants as its unique successor. Every if statement without
|
||||
* and initialization has its condition or one of the condition's descendants as
|
||||
* its unique successor.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
@@ -8,7 +11,11 @@ import cpp
|
||||
from IfStmt is
|
||||
where
|
||||
not (
|
||||
is.getASuccessor() = is.getCondition().getAChild*() and
|
||||
(
|
||||
if exists(is.getInitialization())
|
||||
then is.getASuccessor() = is.getInitialization().getAChild*()
|
||||
else is.getASuccessor() = is.getCondition().getAChild*()
|
||||
) and
|
||||
count(is.getASuccessor()) = 1
|
||||
)
|
||||
select is
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
| 0 | ifstmt.c:27:27:32:1 | { ... } | 1 | ifstmt.c:28:3:30:3 | if (...) ... |
|
||||
| 0 | ifstmt.cpp:1:27:6:1 | { ... } | 1 | ifstmt.cpp:2:3:4:3 | if (...) ... |
|
||||
| 1 | ifstmt.c:28:3:30:3 | if (...) ... | 1 | ifstmt.c:28:6:28:6 | x |
|
||||
| 1 | ifstmt.c:28:6:28:6 | x | 1 | ifstmt.c:28:11:28:11 | y |
|
||||
| 1 | ifstmt.c:28:6:28:11 | ... == ... | 1 | ifstmt.c:28:14:30:3 | { ... } |
|
||||
| 1 | ifstmt.c:28:6:28:11 | ... == ... | 4 | ifstmt.c:31:3:31:5 | label ...: |
|
||||
| 1 | ifstmt.c:28:11:28:11 | y | 1 | ifstmt.c:28:6:28:11 | ... == ... |
|
||||
| 1 | ifstmt.c:28:14:30:3 | { ... } | 2 | ifstmt.c:29:5:29:7 | label ...: |
|
||||
| 1 | ifstmt.cpp:2:3:4:3 | if (...) ... | 1 | ifstmt.cpp:2:6:2:6 | declaration |
|
||||
| 1 | ifstmt.cpp:2:6:2:6 | declaration | 1 | ifstmt.cpp:2:13:2:14 | initializer for z |
|
||||
| 1 | ifstmt.cpp:2:13:2:14 | initializer for z | 1 | ifstmt.cpp:2:14:2:14 | y |
|
||||
| 1 | ifstmt.cpp:2:14:2:14 | y | 1 | ifstmt.cpp:2:17:2:17 | x |
|
||||
| 1 | ifstmt.cpp:2:17:2:17 | x | 1 | ifstmt.cpp:2:22:2:22 | z |
|
||||
| 1 | ifstmt.cpp:2:17:2:22 | ... == ... | 1 | ifstmt.cpp:2:25:4:3 | { ... } |
|
||||
| 1 | ifstmt.cpp:2:17:2:22 | ... == ... | 4 | ifstmt.cpp:5:3:5:5 | label ...: |
|
||||
| 1 | ifstmt.cpp:2:22:2:22 | z | 1 | ifstmt.cpp:2:17:2:22 | ... == ... |
|
||||
| 1 | ifstmt.cpp:2:25:4:3 | { ... } | 2 | ifstmt.cpp:3:5:3:7 | label ...: |
|
||||
| 2 | ifstmt.c:29:5:29:7 | label ...: | 2 | ifstmt.c:29:8:29:8 | ; |
|
||||
| 2 | ifstmt.c:29:8:29:8 | ; | 4 | ifstmt.c:31:3:31:5 | label ...: |
|
||||
| 2 | ifstmt.cpp:3:5:3:7 | label ...: | 2 | ifstmt.cpp:3:8:3:8 | ; |
|
||||
| 2 | ifstmt.cpp:3:8:3:8 | ; | 4 | ifstmt.cpp:5:3:5:5 | label ...: |
|
||||
| 4 | ifstmt.c:31:3:31:5 | label ...: | 4 | ifstmt.c:31:6:31:6 | ; |
|
||||
| 4 | ifstmt.c:31:6:31:6 | ; | 5 | ifstmt.c:32:1:32:1 | return ... |
|
||||
| 4 | ifstmt.cpp:5:3:5:5 | label ...: | 4 | ifstmt.cpp:5:6:5:6 | ; |
|
||||
| 4 | ifstmt.cpp:5:6:5:6 | ; | 5 | ifstmt.cpp:6:1:6:1 | return ... |
|
||||
| 5 | ifstmt.c:32:1:32:1 | return ... | 0 | ifstmt.c:27:6:27:11 | normal |
|
||||
| 5 | ifstmt.cpp:6:1:6:1 | return ... | 0 | ifstmt.cpp:1:6:1:11 | normal |
|
||||
|
||||
15
cpp/ql/test/successor-tests/ifstmt/ifstmt/ifstmt11.ql
Normal file
15
cpp/ql/test/successor-tests/ifstmt/ifstmt/ifstmt11.ql
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @name ifstmt11
|
||||
* @description If an initialization exists, then the condition is a successor of the initialization.
|
||||
*/
|
||||
|
||||
import cpp
|
||||
|
||||
from IfStmt is, Expr e, Stmt s, ControlFlowNode n
|
||||
where
|
||||
s = is.getInitialization() and
|
||||
e = is.getCondition() and
|
||||
n = s.getASuccessor*() and
|
||||
not exists(ControlFlowNode m | m = e.getASuccessor*() | m = n) and
|
||||
not exists(ControlFlowNode m | m = e.getAPredecessor*() | m = n)
|
||||
select is
|
||||
@@ -12,3 +12,20 @@
|
||||
| switchstmt | switchstmt.c:1:6:1:6 | f | 7 | 8 | switchstmt.c:7:5:7:5 | switchstmt.c:7:5:7:5 | switchstmt.c:7:5:7:5 | ; | 8: return ... |
|
||||
| switchstmt | switchstmt.c:1:6:1:6 | f | 8 | 9 | switchstmt.c:8:1:8:1 | switchstmt.c:8:1:8:1 | switchstmt.c:8:1:8:1 | return ... | 8: f |
|
||||
| switchstmt | switchstmt.c:1:6:1:6 | f | 8 | 10 | switchstmt.c:1:6:1:6 | switchstmt.c:1:6:1:6 | switchstmt.c:1:6:1:6 | f | <none> |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 3 | 1 | switchstmt.cpp:3:15:12:1 | switchstmt.cpp:3:15:12:1 | switchstmt.cpp:3:15:12:1 | { ... } | 4: switch (...) ... |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 4 | 2 | switchstmt.cpp:4:6:10:5 | switchstmt.cpp:4:6:10:5 | switchstmt.cpp:4:6:10:5 | switch (...) ... | 5: declaration |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 5 | 3 | switchstmt.cpp:5:10:5:10 | switchstmt.cpp:5:10:5:10 | switchstmt.cpp:5:10:5:10 | declaration | 5: initializer for y |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 5 | 4 | switchstmt.cpp:5:17:5:18 | switchstmt.cpp:5:17:5:18 | switchstmt.cpp:5:17:5:18 | initializer for y | 5: x |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 5 | 5 | switchstmt.cpp:5:18:5:18 | switchstmt.cpp:5:18:5:18 | switchstmt.cpp:5:18:5:18 | x | 6: y |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 6 | 6 | switchstmt.cpp:6:10:6:10 | switchstmt.cpp:6:10:6:10 | switchstmt.cpp:6:10:6:10 | y | 6: { ... } |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 6 | 7 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | { ... } | 7: case ...: |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 6 | 7 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | { ... } | 8: case ...: |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 6 | 7 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | switchstmt.cpp:6:13:10:5 | { ... } | 9: default: |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 7 | 1 | switchstmt.cpp:7:14:7:14 | switchstmt.cpp:7:14:7:14 | switchstmt.cpp:7:14:7:14 | 1 | <none> |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 7 | 8 | switchstmt.cpp:7:9:7:15 | switchstmt.cpp:7:9:7:15 | switchstmt.cpp:7:9:7:15 | case ...: | 8: case ...: |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 8 | 1 | switchstmt.cpp:8:14:8:14 | switchstmt.cpp:8:14:8:14 | switchstmt.cpp:8:14:8:14 | 2 | <none> |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 8 | 9 | switchstmt.cpp:8:9:8:15 | switchstmt.cpp:8:9:8:15 | switchstmt.cpp:8:9:8:15 | case ...: | 9: default: |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 9 | 10 | switchstmt.cpp:9:9:9:16 | switchstmt.cpp:9:9:9:16 | switchstmt.cpp:9:9:9:16 | default: | 11: ; |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 11 | 11 | switchstmt.cpp:11:5:11:5 | switchstmt.cpp:11:5:11:5 | switchstmt.cpp:11:5:11:5 | ; | 12: return ... |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 12 | 12 | switchstmt.cpp:12:1:12:1 | switchstmt.cpp:12:1:12:1 | switchstmt.cpp:12:1:12:1 | return ... | 12: g |
|
||||
| switchstmt | switchstmt.cpp:3:6:3:6 | g | 12 | 13 | switchstmt.cpp:3:6:3:6 | switchstmt.cpp:3:6:3:6 | switchstmt.cpp:3:6:3:6 | g | <none> |
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// semmle-extractor-options: -std=c++17
|
||||
|
||||
void g(int x) {
|
||||
switch (
|
||||
int y = x;
|
||||
y) {
|
||||
case 1:
|
||||
case 2:
|
||||
default:
|
||||
}
|
||||
;
|
||||
}
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.regex.RegexTreeView
|
||||
|
||||
private newtype TPrintAstConfiguration = MkPrintAstConfiguration()
|
||||
|
||||
@@ -132,6 +133,9 @@ private newtype TPrintAstNode =
|
||||
} or
|
||||
TImportsNode(CompilationUnit cu) {
|
||||
shouldPrint(cu, _) and exists(Import i | i.getCompilationUnit() = cu)
|
||||
} or
|
||||
TRegExpTermNode(RegExpTerm term) {
|
||||
exists(StringLiteral str | term.getRootTerm() = getParsedRegExp(str) and shouldPrint(str, _))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,6 +168,19 @@ class PrintAstNode extends TPrintAstNode {
|
||||
*/
|
||||
Location getLocation() { none() }
|
||||
|
||||
/**
|
||||
* Holds if this node is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the property of this node, where the name of the property
|
||||
* is `key`.
|
||||
@@ -290,6 +307,47 @@ final class AnnotationPartNode extends ExprStmtNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node representing a `StringLiteral`.
|
||||
* If it is used as a regular expression, then it has a single child, the root of the parsed regular expression.
|
||||
*/
|
||||
final class StringLiteralNode extends ExprStmtNode {
|
||||
StringLiteralNode() { element instanceof StringLiteral }
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
childIndex = 0 and
|
||||
result.(RegExpTermNode).getTerm() = getParsedRegExp(element)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node representing a regular expression term.
|
||||
*/
|
||||
class RegExpTermNode extends TRegExpTermNode, PrintAstNode {
|
||||
RegExpTerm term;
|
||||
|
||||
RegExpTermNode() { this = TRegExpTermNode(term) }
|
||||
|
||||
/** Gets the `RegExpTerm` for this node. */
|
||||
RegExpTerm getTerm() { result = term }
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
result.(RegExpTermNode).getTerm() = term.getChild(childIndex)
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "[" + strictconcat(term.getPrimaryQLClass(), " | ") + "] " + term.toString()
|
||||
}
|
||||
|
||||
override Location getLocation() { result = term.getLocation() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
term.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A node representing a `LocalVariableDeclExpr`.
|
||||
*/
|
||||
|
||||
@@ -142,6 +142,7 @@ private module Frameworks {
|
||||
private import semmle.code.java.frameworks.jOOQ
|
||||
private import semmle.code.java.frameworks.JMS
|
||||
private import semmle.code.java.frameworks.RabbitMQ
|
||||
private import semmle.code.java.regex.RegexFlowModels
|
||||
}
|
||||
|
||||
private predicate sourceModelCsv(string row) {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
@@ -170,6 +170,14 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
|
||||
/**
|
||||
* Holds if there is a partial data flow path from `source` to `node`. The
|
||||
* approximate distance between `node` and the closest source is `dist` and
|
||||
@@ -3868,11 +3876,14 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
predicate isHidden() {
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
not this.getConfiguration().includeHiddenNodes() and
|
||||
(
|
||||
hiddenNode(this.getNodeEx().asNode()) and
|
||||
not this.isSource() and
|
||||
not this instanceof PathNodeSink
|
||||
or
|
||||
this.getNodeEx() instanceof TNodeImplicitRead
|
||||
)
|
||||
}
|
||||
|
||||
private string ppAp() {
|
||||
|
||||
193
java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
Normal file
193
java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Defines configurations and steps for handling regexes
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.DataFlow2
|
||||
private import RegexFlowModels
|
||||
private import semmle.code.java.security.SecurityTests
|
||||
|
||||
private class ExploitableStringLiteral extends StringLiteral {
|
||||
ExploitableStringLiteral() { this.getValue().matches(["%+%", "%*%", "%{%}%"]) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `kind` is an external sink kind that is relevant for regex flow.
|
||||
* `full` is true if sinks with this kind match against the full string of its input.
|
||||
* `strArg` is the index of the argument to methods with this sink kind that contan the string to be matched against,
|
||||
* where -1 is the qualifier; or -2 if no such argument exists.
|
||||
*/
|
||||
private predicate regexSinkKindInfo(string kind, boolean full, int strArg) {
|
||||
sinkModel(_, _, _, _, _, _, _, kind, _) and
|
||||
exists(string fullStr, string strArgStr |
|
||||
(
|
||||
full = true and fullStr = "f"
|
||||
or
|
||||
full = false and fullStr = ""
|
||||
) and
|
||||
(
|
||||
strArgStr.toInt() = strArg
|
||||
or
|
||||
strArg = -2 and
|
||||
strArgStr = ""
|
||||
)
|
||||
|
|
||||
kind = "regex-use[" + fullStr + strArgStr + "]"
|
||||
)
|
||||
}
|
||||
|
||||
/** A sink that is relevant for regex flow. */
|
||||
private class RegexFlowSink extends DataFlow::Node {
|
||||
boolean full;
|
||||
int strArg;
|
||||
|
||||
RegexFlowSink() {
|
||||
exists(string kind |
|
||||
regexSinkKindInfo(kind, full, strArg) and
|
||||
sinkNode(this, kind)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a regex that flows here is matched against a full string (rather than a substring). */
|
||||
predicate matchesFullString() { full = true }
|
||||
|
||||
/** Gets the string expression that a regex that flows here is matched against, if any. */
|
||||
Expr getStringArgument() {
|
||||
exists(MethodAccess ma |
|
||||
this.asExpr() = argOf(ma, _) and
|
||||
result = argOf(ma, strArg)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private Expr argOf(MethodAccess ma, int arg) {
|
||||
arg = -1 and result = ma.getQualifier()
|
||||
or
|
||||
result = ma.getArgument(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional regex flow steps.
|
||||
*
|
||||
* Extend this class to add additional flow steps that should apply to regex flow configurations.
|
||||
*/
|
||||
class RegexAdditionalFlowStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `node1` to `node2` should be considered a flow
|
||||
* step for regex flow configurations.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
|
||||
}
|
||||
|
||||
// TODO: This may be able to be done with models-as-data if query-specific flow steps beome supported.
|
||||
private class JdkRegexFlowStep extends RegexAdditionalFlowStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m, string package, string type, string name, int arg |
|
||||
ma.getMethod().getSourceDeclaration().overrides*(m) and
|
||||
m.hasQualifiedName(package, type, name) and
|
||||
node1.asExpr() = argOf(ma, arg) and
|
||||
node2.asExpr() = ma
|
||||
|
|
||||
package = "java.util.regex" and
|
||||
type = "Pattern" and
|
||||
(
|
||||
name = ["asMatchPredicate", "asPredicate", "matcher"] and
|
||||
arg = -1
|
||||
or
|
||||
name = "compile" and
|
||||
arg = 0
|
||||
)
|
||||
or
|
||||
package = "java.util.function" and
|
||||
type = "Predicate" and
|
||||
name = ["and", "or", "not", "negate"] and
|
||||
arg = [-1, 0]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class GuavaRegexFlowStep extends RegexAdditionalFlowStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(MethodAccess ma, Method m, string package, string type, string name, int arg |
|
||||
ma.getMethod().getSourceDeclaration().overrides*(m) and
|
||||
m.hasQualifiedName(package, type, name) and
|
||||
node1.asExpr() = argOf(ma, arg) and
|
||||
node2.asExpr() = ma
|
||||
|
|
||||
package = "com.google.common.base" and
|
||||
type = "Splitter" and
|
||||
(
|
||||
name = "on" and
|
||||
m.getParameterType(0).(RefType).hasQualifiedName("java.util.regex", "Pattern") and
|
||||
arg = 0
|
||||
or
|
||||
name = "withKeyValueSeparator" and
|
||||
m.getParameterType(0).(RefType).hasQualifiedName("com.google.common.base", "Splitter") and
|
||||
arg = 0
|
||||
or
|
||||
name = "onPattern" and
|
||||
arg = 0
|
||||
or
|
||||
name = ["limit", "omitEmptyStrings", "trimResults", "withKeyValueSeparator"] and
|
||||
arg = -1
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class RegexFlowConf extends DataFlow2::Configuration {
|
||||
RegexFlowConf() { this = "RegexFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) {
|
||||
node.asExpr() instanceof ExploitableStringLiteral
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node node) { node instanceof RegexFlowSink }
|
||||
|
||||
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
any(RegexAdditionalFlowStep s).step(node1, node2)
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `regex` is used as a regex, with the mode `mode` (if known).
|
||||
* If regex mode is not known, `mode` will be `"None"`.
|
||||
*
|
||||
* As an optimisation, only regexes containing an infinite repitition quatifier (`+`, `*`, or `{x,}`)
|
||||
* and therefore may be relevant for ReDoS queries are considered.
|
||||
*/
|
||||
predicate usedAsRegex(StringLiteral regex, string mode, boolean match_full_string) {
|
||||
any(RegexFlowConf c).hasFlow(DataFlow2::exprNode(regex), _) and
|
||||
mode = "None" and // TODO: proper mode detection
|
||||
(if matchesFullString(regex) then match_full_string = true else match_full_string = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `regex` is used as a regular expression that is matched against a full string,
|
||||
* as though it was implicitly surrounded by ^ and $.
|
||||
*/
|
||||
private predicate matchesFullString(StringLiteral regex) {
|
||||
exists(RegexFlowConf c, RegexFlowSink sink |
|
||||
sink.matchesFullString() and
|
||||
c.hasFlow(DataFlow2::exprNode(regex), sink)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the string literal `regex` is a regular expression that is matched against the expression `str`.
|
||||
*
|
||||
* As an optimisation, only regexes containing an infinite repitition quatifier (`+`, `*`, or `{x,}`)
|
||||
* and therefore may be relevant for ReDoS queries are considered.
|
||||
*/
|
||||
predicate regexMatchedAgainst(StringLiteral regex, Expr str) {
|
||||
exists(RegexFlowConf c, RegexFlowSink sink |
|
||||
str = sink.getStringArgument() and
|
||||
c.hasFlow(DataFlow2::exprNode(regex), sink)
|
||||
)
|
||||
}
|
||||
32
java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
Normal file
32
java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
Normal file
@@ -0,0 +1,32 @@
|
||||
/** Definitions of data flow steps for determining flow of regular expressions. */
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
private class RegexSinkCsv extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
//"namespace;type;subtypes;name;signature;ext;input;kind"
|
||||
"java.util.regex;Matcher;false;matches;();;Argument[-1];regex-use[f]",
|
||||
"java.util.regex;Pattern;false;asMatchPredicate;();;Argument[-1];regex-use[f]",
|
||||
"java.util.regex;Pattern;false;compile;(String);;Argument[0];regex-use[]",
|
||||
"java.util.regex;Pattern;false;compile;(String,int);;Argument[0];regex-use[]",
|
||||
"java.util.regex;Pattern;false;matcher;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
"java.util.regex;Pattern;false;matches;(String,CharSequence);;Argument[0];regex-use[f1]",
|
||||
"java.util.regex;Pattern;false;split;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
"java.util.regex;Pattern;false;split;(CharSequence,int);;Argument[-1];regex-use[0]",
|
||||
"java.util.regex;Pattern;false;splitAsStream;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
"java.util.function;Predicate;false;test;(Object);;Argument[-1];regex-use[0]",
|
||||
"java.lang;String;false;matches;(String);;Argument[0];regex-use[f-1]",
|
||||
"java.lang;String;false;split;(String);;Argument[0];regex-use[-1]",
|
||||
"java.lang;String;false;split;(String,int);;Argument[0];regex-use[-1]",
|
||||
"java.lang;String;false;replaceAll;(String,String);;Argument[0];regex-use[-1]",
|
||||
"java.lang;String;false;replaceFirst;(String,String);;Argument[0];regex-use[-1]",
|
||||
"com.google.common.base;Splitter;false;onPattern;(String);;Argument[0];regex-use[]",
|
||||
"com.google.common.base;Splitter;false;split;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
"com.google.common.base;Splitter;false;splitToList;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
"com.google.common.base;Splitter$MapSplitter;false;split;(CharSequence);;Argument[-1];regex-use[0]",
|
||||
]
|
||||
}
|
||||
}
|
||||
1084
java/ql/lib/semmle/code/java/regex/RegexTreeView.qll
Normal file
1084
java/ql/lib/semmle/code/java/regex/RegexTreeView.qll
Normal file
File diff suppressed because it is too large
Load Diff
917
java/ql/lib/semmle/code/java/regex/regex.qll
Normal file
917
java/ql/lib/semmle/code/java/regex/regex.qll
Normal file
@@ -0,0 +1,917 @@
|
||||
/**
|
||||
* Definitions for parsing regular expressions.
|
||||
*/
|
||||
|
||||
import java
|
||||
private import RegexFlowConfigs
|
||||
|
||||
// In all ranges handled by this library, `start` is inclusive and `end` is exclusive.
|
||||
/**
|
||||
* A string literal that is used as a regular expression.
|
||||
*/
|
||||
abstract class RegexString extends StringLiteral {
|
||||
/** Gets the text of this regex */
|
||||
string getText() { result = this.(StringLiteral).getValue() }
|
||||
|
||||
/** Gets the `i`th character of this regex. */
|
||||
string getChar(int i) { result = this.getText().charAt(i) }
|
||||
|
||||
/** Holds if the regex failed to parse. */
|
||||
predicate failedToParse(int i) {
|
||||
exists(this.getChar(i)) and
|
||||
not exists(int start, int end |
|
||||
this.topLevel(start, end) and
|
||||
start <= i and
|
||||
end > i
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `escapingChar`.
|
||||
* In order to avoid negative recursion, we return a boolean.
|
||||
* This way, we can refer to `escaping(pos - 1).booleanNot()`
|
||||
* rather than to a negated version of `escaping(pos)`.
|
||||
*/
|
||||
private boolean escaping(int pos) {
|
||||
pos = -1 and result = false
|
||||
or
|
||||
this.getChar(pos) = "\\" and
|
||||
(
|
||||
if this.getChar(pos - 1) = "c" // in `\c\`, the latter `\` isn't escaping
|
||||
then result = this.escaping(pos - 2).booleanNot()
|
||||
else result = this.escaping(pos - 1).booleanNot()
|
||||
)
|
||||
or
|
||||
this.getChar(pos) != "\\" and result = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `quote`.
|
||||
* Holds if the char at `pos` could be the beginning of a quote delimiter, i.e. `\Q` (non-escaped) or `\E` (escaping not checked, as quote sequences turn off escapes).
|
||||
* Result is `true` for `\Q` and `false` for `\E`.
|
||||
*/
|
||||
private boolean quoteDelimiter(int pos) {
|
||||
result = true and
|
||||
this.escaping(pos) = true and
|
||||
this.getChar(pos + 1) = "Q"
|
||||
or
|
||||
result = false and
|
||||
this.getChar(pos) = "\\" and
|
||||
this.getChar(pos + 1) = "E"
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `quote`.
|
||||
* Holds if the char at `pos` is the one-based `index`th occurence of a quote delimiter (`\Q` or `\E`)
|
||||
* Result is `true` for `\Q` and `false` for `\E`.
|
||||
*/
|
||||
private boolean quoteDelimiter(int index, int pos) {
|
||||
result = this.quoteDelimiter(pos) and
|
||||
pos = rank[index](int p | exists(this.quoteDelimiter(p)))
|
||||
}
|
||||
|
||||
/** Holds if a quoted sequence is found between `start` and `end` */
|
||||
predicate quote(int start, int end) { this.quote(start, end, _, _) }
|
||||
|
||||
/** Holds if a quoted sequence is found between `start` and `end`, with content found between `inner_start` and `inner_end`. */
|
||||
predicate quote(int start, int end, int inner_start, int inner_end) {
|
||||
exists(int index |
|
||||
this.quoteDelimiter(index, start) = true and
|
||||
(
|
||||
index = 1
|
||||
or
|
||||
this.quoteDelimiter(index - 1, _) = false
|
||||
) and
|
||||
inner_start = start + 2 and
|
||||
inner_end = end - 2 and
|
||||
inner_end =
|
||||
min(int end_delimiter |
|
||||
this.quoteDelimiter(end_delimiter) = false and end_delimiter > inner_start
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the character at `pos` is a "\" that is actually escaping what comes after. */
|
||||
predicate escapingChar(int pos) {
|
||||
this.escaping(pos) = true and
|
||||
not exists(int x, int y | this.quote(x, y) and pos in [x .. y - 1])
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a control sequence, `\cx`, between `start` and `end`.
|
||||
* `x` may be any ascii character including special characters.
|
||||
*/
|
||||
predicate controlEscape(int start, int end) {
|
||||
this.escapingChar(start) and
|
||||
this.getChar(start + 1) = "c" and
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
private predicate namedBackreference(int start, int end, string name) {
|
||||
this.escapingChar(start) and
|
||||
this.getChar(start + 1) = "k" and
|
||||
this.getChar(start + 2) = "<" and
|
||||
end = min(int i | i > start + 2 and this.getChar(i) = ">") + 1 and
|
||||
name = this.getText().substring(start + 3, end - 1)
|
||||
}
|
||||
|
||||
private predicate numberedBackreference(int start, int end, int value) {
|
||||
this.escapingChar(start) and
|
||||
// starting with 0 makes it an octal escape
|
||||
not this.getChar(start + 1) = "0" and
|
||||
exists(string text, string svalue, int len |
|
||||
end = start + len and
|
||||
text = this.getText() and
|
||||
len in [2 .. 3]
|
||||
|
|
||||
svalue = text.substring(start + 1, start + len) and
|
||||
value = svalue.toInt() and
|
||||
// value is composed of digits
|
||||
forall(int i | i in [start + 1 .. start + len - 1] | this.getChar(i) = [0 .. 9].toString()) and
|
||||
// a longer reference is not possible
|
||||
not (
|
||||
len = 2 and
|
||||
exists(text.substring(start + 1, start + len + 1).toInt())
|
||||
) and
|
||||
// 3 octal digits makes it an octal escape
|
||||
not forall(int i | i in [start + 1 .. start + 4] | this.isOctal(i))
|
||||
// TODO: Inside a character set, all numeric escapes are treated as characters.
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the text in the range start,end is a back reference */
|
||||
predicate backreference(int start, int end) {
|
||||
this.numberedBackreference(start, end, _)
|
||||
or
|
||||
this.namedBackreference(start, end, _)
|
||||
}
|
||||
|
||||
/** Gets the number of the back reference in start,end */
|
||||
int getBackrefNumber(int start, int end) { this.numberedBackreference(start, end, result) }
|
||||
|
||||
/** Gets the name, if it has one, of the back reference in start,end */
|
||||
string getBackrefName(int start, int end) { this.namedBackreference(start, end, result) }
|
||||
|
||||
pragma[inline]
|
||||
private predicate isOctal(int index) { this.getChar(index) = [0 .. 7].toString() }
|
||||
|
||||
/** An escape sequence that includes braces, such as named characters (\N{degree sign}), named classes (\p{Lower}), or hex values (\x{h..h}) */
|
||||
private predicate escapedBraces(int start, int end) {
|
||||
this.escapingChar(start) and
|
||||
this.getChar(start + 1) = ["N", "p", "P", "x"] and
|
||||
this.getChar(start + 2) = "{" and
|
||||
end = min(int i | start + 2 < i and this.getChar(i - 1) = "}")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an escaped character is found between `start` and `end`.
|
||||
* Escaped characters include hex values, octal values and named escapes,
|
||||
* but excludes backreferences.
|
||||
*/
|
||||
predicate escapedCharacter(int start, int end) {
|
||||
this.escapingChar(start) and
|
||||
not this.backreference(start, _) and
|
||||
(
|
||||
// hex value \xhh
|
||||
this.getChar(start + 1) = "x" and
|
||||
this.getChar(start + 2) != "{" and
|
||||
end = start + 4
|
||||
or
|
||||
// octal value \0o, \0oo, or \0ooo. Max of 0377.
|
||||
this.getChar(start + 1) = "0" and
|
||||
this.isOctal(start + 2) and
|
||||
(
|
||||
if this.isOctal(start + 3)
|
||||
then
|
||||
if this.isOctal(start + 4) and this.getChar(start + 2) in ["0", "1", "2", "3"]
|
||||
then end = start + 5
|
||||
else end = start + 4
|
||||
else end = start + 3
|
||||
)
|
||||
or
|
||||
// 16-bit hex value \uhhhh
|
||||
this.getChar(start + 1) = "u" and end = start + 6
|
||||
or
|
||||
this.escapedBraces(start, end)
|
||||
or
|
||||
// Boundary matchers \b, \b{g}
|
||||
this.getChar(start + 1) = "b" and
|
||||
(
|
||||
if this.getText().substring(start + 2, start + 5) = "{g}"
|
||||
then end = start + 5
|
||||
else end = start + 2
|
||||
)
|
||||
or
|
||||
this.controlEscape(start, end)
|
||||
or
|
||||
// escape not handled above, update when adding a new case
|
||||
not this.getChar(start + 1) in ["x", "0", "u", "p", "P", "N", "b", "c"] and
|
||||
not exists(this.getChar(start + 1).toInt()) and
|
||||
end = start + 2
|
||||
)
|
||||
}
|
||||
|
||||
private string nonEscapedCharAt(int i) {
|
||||
result = this.getChar(i) and
|
||||
not exists(int x, int y | this.escapedCharacter(x, y) and i in [x .. y - 1]) and
|
||||
not exists(int x, int y | this.quote(x, y) and i in [x .. y - 1])
|
||||
}
|
||||
|
||||
/** Holds if a character set starts between `start` and `end`, including any negation character (`^`). */
|
||||
private predicate charSetStart0(int start, int end) {
|
||||
this.nonEscapedCharAt(start) = "[" and
|
||||
(if this.getChar(start + 1) = "^" then end = start + 2 else end = start + 1)
|
||||
}
|
||||
|
||||
/** Holds if the character at `pos` marks the end of a character class. */
|
||||
private predicate charSetEnd0(int pos) {
|
||||
this.nonEscapedCharAt(pos) = "]" and
|
||||
/* special case: `[]]` and `[^]]` are valid char classes. */
|
||||
not this.charSetStart0(_, pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character at `pos` starts a character set delimiter.
|
||||
* Result is 1 for `[` and -1 for `]`.
|
||||
*/
|
||||
private int charSetDelimiter(int pos) {
|
||||
result = 1 and this.charSetStart0(pos, _)
|
||||
or
|
||||
result = -1 and this.charSetEnd0(pos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the char at `pos` is the one-based `index`th occourence of a character set delimiter (`[` or `]`).
|
||||
* Result is 1 for `[` and -1 for `]`.
|
||||
*/
|
||||
private int charSetDelimiter(int index, int pos) {
|
||||
result = this.charSetDelimiter(pos) and
|
||||
pos = rank[index](int p | exists(this.charSetDelimiter(p)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the nesting depth of character classes after position `pos`,
|
||||
* where `pos` is the position of a character set delimiter.
|
||||
*/
|
||||
private int charSetDepth(int index, int pos) {
|
||||
index = 1 and result = 0.maximum(this.charSetDelimiter(index, pos))
|
||||
or
|
||||
result = 0.maximum(this.charSetDelimiter(index, pos) + this.charSetDepth(index - 1, _))
|
||||
}
|
||||
|
||||
/** Hold if a top-level character set starts between `start` and `end`. */
|
||||
predicate charSetStart(int start, int end) {
|
||||
this.charSetStart0(start, end) and
|
||||
this.charSetDepth(_, start) = 1
|
||||
}
|
||||
|
||||
/** Holds if a top-level character set ends at `pos`. */
|
||||
predicate charSetEnd(int pos) {
|
||||
this.charSetEnd0(pos) and
|
||||
this.charSetDepth(_, pos) = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a top-level character class beginning at `start` (inclusive) and ending at `end` (exclusive)
|
||||
*
|
||||
* For now, nested character classes are approximated by only considering the top-level class for parsing.
|
||||
* This leads to very similar results for ReDoS queries.
|
||||
*/
|
||||
predicate charSet(int start, int end) {
|
||||
exists(int inner_start, int inner_end | this.charSetStart(start, inner_start) |
|
||||
end = inner_end + 1 and
|
||||
inner_end =
|
||||
min(int end_delimiter | this.charSetEnd(end_delimiter) and end_delimiter > inner_start)
|
||||
)
|
||||
}
|
||||
|
||||
/** Either a char or a - */
|
||||
private predicate charSetToken(int charset_start, int start, int end) {
|
||||
this.charSetStart(charset_start, start) and
|
||||
(
|
||||
this.escapedCharacter(start, end)
|
||||
or
|
||||
exists(this.nonEscapedCharAt(start)) and end = start + 1
|
||||
or
|
||||
this.quote(start, end)
|
||||
)
|
||||
or
|
||||
this.charSetToken(charset_start, _, start) and
|
||||
(
|
||||
this.escapedCharacter(start, end)
|
||||
or
|
||||
exists(this.nonEscapedCharAt(start)) and
|
||||
end = start + 1 and
|
||||
not this.charSetEnd(start)
|
||||
or
|
||||
this.quote(start, end)
|
||||
)
|
||||
}
|
||||
|
||||
/** An indexed version of `charSetToken/3` */
|
||||
private predicate charSetToken(int charset_start, int index, int token_start, int token_end) {
|
||||
token_start = rank[index](int start | this.charSetToken(charset_start, start, _) | start) and
|
||||
this.charSetToken(charset_start, token_start, token_end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for `charRange`.
|
||||
* We can determine where character ranges end by a left to right sweep.
|
||||
*
|
||||
* To avoid negative recursion we return a boolean. See `escaping`,
|
||||
* the helper for `escapingChar`, for a clean use of this pattern.
|
||||
*/
|
||||
private boolean charRangeEnd(int charset_start, int index) {
|
||||
this.charSetToken(charset_start, index, _, _) and
|
||||
(
|
||||
index in [1, 2] and result = false
|
||||
or
|
||||
index > 2 and
|
||||
exists(int connector_start |
|
||||
this.charSetToken(charset_start, index - 1, connector_start, _) and
|
||||
this.nonEscapedCharAt(connector_start) = "-" and
|
||||
result =
|
||||
this.charRangeEnd(charset_start, index - 2)
|
||||
.booleanNot()
|
||||
.booleanAnd(this.charRangeEnd(charset_start, index - 1).booleanNot())
|
||||
)
|
||||
or
|
||||
not exists(int connector_start |
|
||||
this.charSetToken(charset_start, index - 1, connector_start, _) and
|
||||
this.nonEscapedCharAt(connector_start) = "-"
|
||||
) and
|
||||
result = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character set starting at `charset_start` contains a character range
|
||||
* with lower bound found between `start` and `lower_end`
|
||||
* and upper bound found between `upper_start` and `end`.
|
||||
*/
|
||||
predicate charRange(int charset_start, int start, int lower_end, int upper_start, int end) {
|
||||
exists(int index |
|
||||
this.charRangeEnd(charset_start, index) = true and
|
||||
this.charSetToken(charset_start, index - 2, start, lower_end) and
|
||||
this.charSetToken(charset_start, index, upper_start, end)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the character set starting at `charset_start` contains either
|
||||
* a character or a range found between `start` and `end`.
|
||||
*/
|
||||
predicate charSetChild(int charset_start, int start, int end) {
|
||||
this.charSetToken(charset_start, start, end) and
|
||||
not exists(int range_start, int range_end |
|
||||
this.charRange(charset_start, range_start, _, _, range_end) and
|
||||
range_start <= start and
|
||||
range_end >= end
|
||||
)
|
||||
or
|
||||
this.charRange(charset_start, start, _, _, end)
|
||||
}
|
||||
|
||||
/** Holds if `index` is inside a character set. */
|
||||
predicate inCharSet(int index) {
|
||||
exists(int x, int y | this.charSet(x, y) and index in [x + 1 .. y - 2])
|
||||
}
|
||||
|
||||
/**
|
||||
* 'simple' characters are any that don't alter the parsing of the regex.
|
||||
*/
|
||||
private predicate simpleCharacter(int start, int end) {
|
||||
end = start + 1 and
|
||||
not this.charSet(start, _) and
|
||||
not this.charSet(_, start + 1) and
|
||||
exists(string c | c = this.getChar(start) |
|
||||
exists(int x, int y, int z |
|
||||
this.charSet(x, z) and
|
||||
this.charSetStart(x, y)
|
||||
|
|
||||
start = y
|
||||
or
|
||||
start = z - 2
|
||||
or
|
||||
start > y and start < z - 2 and not this.charRange(_, _, start, end, _)
|
||||
)
|
||||
or
|
||||
not this.inCharSet(start) and
|
||||
not c = "(" and
|
||||
not c = "[" and
|
||||
not c = ")" and
|
||||
not c = "|" and
|
||||
not c = "{" and
|
||||
not exists(int qstart, int qend | this.quantifier(qstart, qend, _, _) |
|
||||
qstart <= start and start < qend
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate character(int start, int end) {
|
||||
(
|
||||
this.simpleCharacter(start, end) and
|
||||
not exists(int x, int y | this.escapedCharacter(x, y) and x <= start and y >= end) and
|
||||
not exists(int x, int y | this.quote(x, y) and x <= start and y >= end)
|
||||
or
|
||||
this.escapedCharacter(start, end)
|
||||
) and
|
||||
not exists(int x, int y | this.groupStart(x, y) and x <= start and y >= end) and
|
||||
not exists(int x, int y | this.backreference(x, y) and x <= start and y >= end)
|
||||
}
|
||||
|
||||
/** Holds if a normal character or escape sequence is between `start` and `end`. */
|
||||
predicate normalCharacter(int start, int end) {
|
||||
this.character(start, end) and
|
||||
not this.specialCharacter(start, end, _)
|
||||
}
|
||||
|
||||
/** Holds if a special character `char` is between `start` and `end`. */
|
||||
predicate specialCharacter(int start, int end, string char) {
|
||||
this.character(start, end) and
|
||||
end = start + 1 and
|
||||
char = this.getChar(start) and
|
||||
(char = "$" or char = "^" or char = ".") and
|
||||
not this.inCharSet(start)
|
||||
}
|
||||
|
||||
private predicate isGroupEnd(int i) { this.nonEscapedCharAt(i) = ")" and not this.inCharSet(i) }
|
||||
|
||||
private predicate isGroupStart(int i) { this.nonEscapedCharAt(i) = "(" and not this.inCharSet(i) }
|
||||
|
||||
/** Holds if an empty group is found between `start` and `end`. */
|
||||
predicate emptyGroup(int start, int end) {
|
||||
exists(int endm1 | end = endm1 + 1 |
|
||||
this.groupStart(start, endm1) and
|
||||
this.isGroupEnd(endm1)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate nonCapturingGroupStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = ":" and
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
private predicate simpleGroupStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) != "?" and
|
||||
end = start + 1
|
||||
}
|
||||
|
||||
private predicate namedGroupStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = "<" and
|
||||
not this.getChar(start + 3) = "=" and
|
||||
not this.getChar(start + 3) = "!" and
|
||||
exists(int name_end |
|
||||
name_end = min(int i | i > start + 3 and this.getChar(i) = ">") and
|
||||
end = name_end + 1
|
||||
)
|
||||
}
|
||||
|
||||
private predicate flagGroupStart(int start, int end, string c) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
end = start + 3 and
|
||||
c = this.getChar(start + 2) and
|
||||
c in ["i", "m", "s", "u", "x", "U"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mode of this regular expression string if
|
||||
* it is defined by a prefix.
|
||||
*/
|
||||
string getModeFromPrefix() {
|
||||
exists(string c | this.flagGroupStart(_, _, c) |
|
||||
c = "i" and result = "IGNORECASE"
|
||||
or
|
||||
c = "m" and result = "MULTILINE"
|
||||
or
|
||||
c = "s" and result = "DOTALL"
|
||||
or
|
||||
c = "u" and result = "UNICODE"
|
||||
or
|
||||
c = "x" and result = "VERBOSE"
|
||||
or
|
||||
c = "U" and result = "UNICODECLASS"
|
||||
)
|
||||
}
|
||||
|
||||
private predicate lookaheadAssertionStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = "=" and
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
private predicate negativeLookaheadAssertionStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = "!" and
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
private predicate lookbehindAssertionStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = "<" and
|
||||
this.getChar(start + 3) = "=" and
|
||||
end = start + 4
|
||||
}
|
||||
|
||||
private predicate negativeLookbehindAssertionStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = "<" and
|
||||
this.getChar(start + 3) = "!" and
|
||||
end = start + 4
|
||||
}
|
||||
|
||||
private predicate atomicGroupStart(int start, int end) {
|
||||
this.isGroupStart(start) and
|
||||
this.getChar(start + 1) = "?" and
|
||||
this.getChar(start + 2) = ">" and
|
||||
end = start + 3
|
||||
}
|
||||
|
||||
private predicate groupStart(int start, int end) {
|
||||
this.nonCapturingGroupStart(start, end)
|
||||
or
|
||||
this.flagGroupStart(start, end, _)
|
||||
or
|
||||
this.namedGroupStart(start, end)
|
||||
or
|
||||
this.lookaheadAssertionStart(start, end)
|
||||
or
|
||||
this.negativeLookaheadAssertionStart(start, end)
|
||||
or
|
||||
this.lookbehindAssertionStart(start, end)
|
||||
or
|
||||
this.negativeLookbehindAssertionStart(start, end)
|
||||
or
|
||||
this.atomicGroupStart(start, end)
|
||||
or
|
||||
this.simpleGroupStart(start, end)
|
||||
}
|
||||
|
||||
/** Holds if the text in the range start,end is a group with contents in the range in_start,in_end */
|
||||
predicate groupContents(int start, int end, int in_start, int in_end) {
|
||||
this.groupStart(start, in_start) and
|
||||
end = in_end + 1 and
|
||||
this.topLevel(in_start, in_end) and
|
||||
this.isGroupEnd(in_end)
|
||||
}
|
||||
|
||||
/** Holds if the text in the range start,end is a group */
|
||||
predicate group(int start, int end) {
|
||||
this.groupContents(start, end, _, _)
|
||||
or
|
||||
this.emptyGroup(start, end)
|
||||
}
|
||||
|
||||
/** Gets the number of the group in start,end */
|
||||
int getGroupNumber(int start, int end) {
|
||||
this.group(start, end) and
|
||||
result =
|
||||
count(int i | this.group(i, _) and i < start and not this.nonCapturingGroupStart(i, _)) + 1
|
||||
}
|
||||
|
||||
/** Gets the name, if it has one, of the group in start,end */
|
||||
string getGroupName(int start, int end) {
|
||||
this.group(start, end) and
|
||||
exists(int name_end |
|
||||
this.namedGroupStart(start, name_end) and
|
||||
result = this.getText().substring(start + 3, name_end - 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a negative lookahead is found between `start` and `end` */
|
||||
predicate negativeLookaheadAssertionGroup(int start, int end) {
|
||||
exists(int in_start | this.negativeLookaheadAssertionStart(start, in_start) |
|
||||
this.groupContents(start, end, in_start, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a negative lookbehind is found between `start` and `end` */
|
||||
predicate negativeLookbehindAssertionGroup(int start, int end) {
|
||||
exists(int in_start | this.negativeLookbehindAssertionStart(start, in_start) |
|
||||
this.groupContents(start, end, in_start, _)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate negativeAssertionGroup(int start, int end) {
|
||||
exists(int in_start |
|
||||
this.negativeLookaheadAssertionStart(start, in_start)
|
||||
or
|
||||
this.negativeLookbehindAssertionStart(start, in_start)
|
||||
|
|
||||
this.groupContents(start, end, in_start, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a positive lookahead is found between `start` and `end` */
|
||||
predicate positiveLookaheadAssertionGroup(int start, int end) {
|
||||
exists(int in_start | this.lookaheadAssertionStart(start, in_start) |
|
||||
this.groupContents(start, end, in_start, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a positive lookbehind is found between `start` and `end` */
|
||||
predicate positiveLookbehindAssertionGroup(int start, int end) {
|
||||
exists(int in_start | this.lookbehindAssertionStart(start, in_start) |
|
||||
this.groupContents(start, end, in_start, _)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the text in the range start, end is a group and can match the empty string. */
|
||||
predicate zeroWidthMatch(int start, int end) {
|
||||
this.emptyGroup(start, end)
|
||||
or
|
||||
this.negativeAssertionGroup(start, end)
|
||||
or
|
||||
this.positiveLookaheadAssertionGroup(start, end)
|
||||
or
|
||||
this.positiveLookbehindAssertionGroup(start, end)
|
||||
}
|
||||
|
||||
private predicate baseItem(int start, int end) {
|
||||
this.character(start, end) and
|
||||
not exists(int x, int y | this.charSet(x, y) and x <= start and y >= end)
|
||||
or
|
||||
this.group(start, end)
|
||||
or
|
||||
this.charSet(start, end)
|
||||
or
|
||||
this.backreference(start, end)
|
||||
or
|
||||
this.quote(start, end)
|
||||
}
|
||||
|
||||
private predicate shortQuantifier(
|
||||
int start, int end, boolean maybe_empty, boolean may_repeat_forever
|
||||
) {
|
||||
(
|
||||
this.getChar(start) = "+" and maybe_empty = false and may_repeat_forever = true
|
||||
or
|
||||
this.getChar(start) = "*" and maybe_empty = true and may_repeat_forever = true
|
||||
or
|
||||
this.getChar(start) = "?" and maybe_empty = true and may_repeat_forever = false
|
||||
) and
|
||||
end = start + 1
|
||||
or
|
||||
exists(string lower, string upper |
|
||||
this.multiples(start, end, lower, upper) and
|
||||
(if lower = "" or lower.toInt() = 0 then maybe_empty = true else maybe_empty = false) and
|
||||
if upper = "" then may_repeat_forever = true else may_repeat_forever = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a repetition quantifier is found between `start` and `end`,
|
||||
* with the given lower and upper bounds. If a bound is omitted, the corresponding
|
||||
* string is empty.
|
||||
*/
|
||||
predicate multiples(int start, int end, string lower, string upper) {
|
||||
exists(string text, string match, string inner |
|
||||
text = this.getText() and
|
||||
end = start + match.length() and
|
||||
inner = match.substring(1, match.length() - 1)
|
||||
|
|
||||
match = text.regexpFind("\\{[0-9]+\\}", _, start) and
|
||||
lower = inner and
|
||||
upper = lower
|
||||
or
|
||||
match = text.regexpFind("\\{[0-9]*,[0-9]*\\}", _, start) and
|
||||
exists(int commaIndex |
|
||||
commaIndex = inner.indexOf(",") and
|
||||
lower = inner.prefix(commaIndex) and
|
||||
upper = inner.suffix(commaIndex + 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate quantifier(int start, int end, boolean maybe_empty, boolean may_repeat_forever) {
|
||||
this.shortQuantifier(start, end, maybe_empty, may_repeat_forever) and
|
||||
not this.getChar(end) = ["?", "+"]
|
||||
or
|
||||
exists(int short_end | this.shortQuantifier(start, short_end, maybe_empty, may_repeat_forever) |
|
||||
if this.getChar(short_end) = ["?", "+"] then end = short_end + 1 else end = short_end
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a quantified part is found between `start` and `part_end` and the quantifier is
|
||||
* found between `part_end` and `end`.
|
||||
*
|
||||
* `maybe_empty` is true if the part is optional.
|
||||
* `may_repeat_forever` is true if the part may be repeated unboundedly.
|
||||
*/
|
||||
predicate quantifiedPart(
|
||||
int start, int part_end, int end, boolean maybe_empty, boolean may_repeat_forever
|
||||
) {
|
||||
this.baseItem(start, part_end) and
|
||||
this.quantifier(part_end, end, maybe_empty, may_repeat_forever)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the text in the range start,end is a quantified item, where item is a character,
|
||||
* a character set or a group.
|
||||
*/
|
||||
predicate quantifiedItem(int start, int end, boolean maybe_empty, boolean may_repeat_forever) {
|
||||
this.quantifiedPart(start, _, end, maybe_empty, may_repeat_forever)
|
||||
}
|
||||
|
||||
/** Holds if the range `start`, `end` contains a character, a quantifier, a character set or a group. */
|
||||
predicate item(int start, int end) {
|
||||
this.quantifiedItem(start, end, _, _)
|
||||
or
|
||||
this.baseItem(start, end) and not this.quantifier(end, _, _, _)
|
||||
}
|
||||
|
||||
private predicate itemStart(int start) {
|
||||
this.character(start, _) or
|
||||
this.isGroupStart(start) or
|
||||
this.charSet(start, _) or
|
||||
this.backreference(start, _) or
|
||||
this.quote(start, _)
|
||||
}
|
||||
|
||||
private predicate itemEnd(int end) {
|
||||
this.character(_, end)
|
||||
or
|
||||
exists(int endm1 | this.isGroupEnd(endm1) and end = endm1 + 1)
|
||||
or
|
||||
this.charSet(_, end)
|
||||
or
|
||||
this.quantifier(_, end, _, _)
|
||||
or
|
||||
this.quote(_, end)
|
||||
}
|
||||
|
||||
private predicate isOptionDivider(int i) { this.nonEscapedCharAt(i) = "|" }
|
||||
|
||||
private predicate subsequence(int start, int end) {
|
||||
(
|
||||
start = 0 or
|
||||
this.groupStart(_, start) or
|
||||
this.isOptionDivider(start - 1)
|
||||
) and
|
||||
this.item(start, end)
|
||||
or
|
||||
exists(int mid |
|
||||
this.subsequence(start, mid) and
|
||||
this.item(mid, end)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate sequenceOrQuantified(int start, int end) {
|
||||
this.subsequence(start, end) and
|
||||
not this.itemStart(end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the text in the range start,end is a sequence of 1 or more items, where an item is a character,
|
||||
* a character set or a group.
|
||||
*/
|
||||
predicate sequence(int start, int end) {
|
||||
this.sequenceOrQuantified(start, end) and
|
||||
not this.quantifiedItem(start, end, _, _)
|
||||
}
|
||||
|
||||
private predicate subalternation(int start, int end, int itemStart) {
|
||||
this.sequenceOrQuantified(start, end) and
|
||||
not this.isOptionDivider(start - 1) and
|
||||
itemStart = start
|
||||
or
|
||||
start = end and
|
||||
not this.itemEnd(start) and
|
||||
this.isOptionDivider(end) and
|
||||
itemStart = start
|
||||
or
|
||||
exists(int mid |
|
||||
this.subalternation(start, mid, _) and
|
||||
this.isOptionDivider(mid) and
|
||||
itemStart = mid + 1
|
||||
|
|
||||
this.sequenceOrQuantified(itemStart, end)
|
||||
or
|
||||
not this.itemStart(end) and end = itemStart
|
||||
)
|
||||
}
|
||||
|
||||
private predicate topLevel(int start, int end) {
|
||||
not this.inCharSet(start) and
|
||||
this.subalternation(start, end, _) and
|
||||
not this.isOptionDivider(end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the text in the range start,end is an alternation
|
||||
*/
|
||||
predicate alternation(int start, int end) {
|
||||
this.topLevel(start, end) and
|
||||
exists(int less | this.subalternation(start, less, _) and less < end)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the text in the range start,end is an alternation and the text in part_start, part_end is one of the
|
||||
* options in that alternation.
|
||||
*/
|
||||
predicate alternationOption(int start, int end, int part_start, int part_end) {
|
||||
this.alternation(start, end) and
|
||||
this.subalternation(start, part_end, part_start)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th character of this literal as it was written in the source code.
|
||||
*/
|
||||
string getSourceChar(int i) { result = this.(StringLiteral).getLiteral().charAt(i) }
|
||||
|
||||
/**
|
||||
* Helper predicate for `sourceEscapingChar` that
|
||||
* results in a boolean in order to avoid negative recursion.
|
||||
*/
|
||||
private boolean sourceEscaping(int pos) {
|
||||
pos = -1 and result = false
|
||||
or
|
||||
this.getSourceChar(pos) = "\\" and
|
||||
result = this.sourceEscaping(pos - 1).booleanNot()
|
||||
or
|
||||
this.getSourceChar(pos) != "\\" and result = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent of `escapingChar` for the literal source rather than the string value.
|
||||
* Holds if the character at position `pos` in the source literal is a '\' that is
|
||||
* actually escaping what comes after it.
|
||||
*/
|
||||
private predicate sourceEcapingChar(int pos) { this.sourceEscaping(pos) = true }
|
||||
|
||||
/**
|
||||
* Holds if an escaped character exists between `start` and `end` in the source iteral.
|
||||
*/
|
||||
private predicate sourceEscapedCharacter(int start, int end) {
|
||||
this.sourceEcapingChar(start) and
|
||||
(if this.getSourceChar(start + 1) = "u" then end = start + 6 else end = start + 2)
|
||||
}
|
||||
|
||||
private predicate sourceNonEscapedCharacter(int i) {
|
||||
exists(this.getSourceChar(i)) and
|
||||
not exists(int x, int y | this.sourceEscapedCharacter(x, y) and i in [x .. y - 1])
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a character is represented between `start` and `end` in the source literal.
|
||||
*/
|
||||
private predicate sourceCharacter(int start, int end) {
|
||||
this.sourceEscapedCharacter(start, end)
|
||||
or
|
||||
this.sourceNonEscapedCharacter(start) and
|
||||
end = start + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th character of the string is represented between offsets
|
||||
* `start` (inclusive) and `end` (exclusive) in the source code of this literal.
|
||||
* This only gives correct results if the literal is written as a normal single-line string literal;
|
||||
* without compile-time concatenation involved.
|
||||
*/
|
||||
predicate sourceCharacter(int pos, int start, int end) {
|
||||
exists(this.getChar(pos)) and
|
||||
this.sourceCharacter(start, end) and
|
||||
start = rank[pos + 2](int s | this.sourceCharacter(s, _))
|
||||
}
|
||||
}
|
||||
|
||||
/** A string literal used as a regular expression */
|
||||
class Regex extends RegexString {
|
||||
boolean matches_full_string;
|
||||
|
||||
Regex() { usedAsRegex(this, _, matches_full_string) }
|
||||
|
||||
/**
|
||||
* Gets a mode (if any) of this regular expression. Can be any of:
|
||||
* DEBUG
|
||||
* IGNORECASE
|
||||
* MULTILINE
|
||||
* DOTALL
|
||||
* UNICODE
|
||||
* VERBOSE
|
||||
* UNICODECLASS
|
||||
*/
|
||||
string getAMode() {
|
||||
result != "None" and
|
||||
usedAsRegex(this, result, _)
|
||||
or
|
||||
result = this.getModeFromPrefix()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this regex is used to match against a full string,
|
||||
* as though it was implicitly surrounded by ^ and $.
|
||||
*/
|
||||
predicate matchesFullString() { matches_full_string = true }
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* This library implements the analysis described in the following two papers:
|
||||
*
|
||||
* James Kirrage, Asiri Rathnayake, Hayo Thielecke: Static Analysis for
|
||||
* Regular Expression Denial-of-Service Attacks. NSS 2013.
|
||||
* (http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf)
|
||||
* Asiri Rathnayake, Hayo Thielecke: Static Analysis for Regular Expression
|
||||
* Exponential Runtime via Substructural Logics. 2014.
|
||||
* (https://www.cs.bham.ac.uk/~hxt/research/redos_full.pdf)
|
||||
*
|
||||
* The basic idea is to search for overlapping cycles in the NFA, that is,
|
||||
* states `q` such that there are two distinct paths from `q` to itself
|
||||
* that consume the same word `w`.
|
||||
*
|
||||
* For any such state `q`, an attack string can be constructed as follows:
|
||||
* concatenate a prefix `v` that takes the NFA to `q` with `n` copies of
|
||||
* the word `w` that leads back to `q` along two different paths, followed
|
||||
* by a suffix `x` that is _not_ accepted in state `q`. A backtracking
|
||||
* implementation will need to explore at least 2^n different ways of going
|
||||
* from `q` back to itself while trying to match the `n` copies of `w`
|
||||
* before finally giving up.
|
||||
*
|
||||
* Now in order to identify overlapping cycles, all we have to do is find
|
||||
* pumpable forks, that is, states `q` that can transition to two different
|
||||
* states `r1` and `r2` on the same input symbol `c`, such that there are
|
||||
* paths from both `r1` and `r2` to `q` that consume the same word. The latter
|
||||
* condition is equivalent to saying that `(q, q)` is reachable from `(r1, r2)`
|
||||
* in the product NFA.
|
||||
*
|
||||
* This is what the library does. It makes a simple attempt to construct a
|
||||
* prefix `v` leading into `q`, but only to improve the alert message.
|
||||
* And the library tries to prove the existence of a suffix that ensures
|
||||
* rejection. This check might fail, which can cause false positives.
|
||||
*
|
||||
* Finally, sometimes it depends on the translation whether the NFA generated
|
||||
* for a regular expression has a pumpable fork or not. We implement one
|
||||
* particular translation, which may result in false positives or negatives
|
||||
* relative to some particular JavaScript engine.
|
||||
*
|
||||
* More precisely, the library constructs an NFA from a regular expression `r`
|
||||
* as follows:
|
||||
*
|
||||
* * Every sub-term `t` gives rise to an NFA state `Match(t,i)`, representing
|
||||
* the state of the automaton before attempting to match the `i`th character in `t`.
|
||||
* * There is one accepting state `Accept(r)`.
|
||||
* * There is a special `AcceptAnySuffix(r)` state, which accepts any suffix string
|
||||
* by using an epsilon transition to `Accept(r)` and an any transition to itself.
|
||||
* * Transitions between states may be labelled with epsilon, or an abstract
|
||||
* input symbol.
|
||||
* * Each abstract input symbol represents a set of concrete input characters:
|
||||
* either a single character, a set of characters represented by a
|
||||
* character class, or the set of all characters.
|
||||
* * The product automaton is constructed lazily, starting with pair states
|
||||
* `(q, q)` where `q` is a fork, and proceding along an over-approximate
|
||||
* step relation.
|
||||
* * The over-approximate step relation allows transitions along pairs of
|
||||
* abstract input symbols where the symbols have overlap in the characters they accept.
|
||||
* * Once a trace of pairs of abstract input symbols that leads from a fork
|
||||
* back to itself has been identified, we attempt to construct a concrete
|
||||
* string corresponding to it, which may fail.
|
||||
* * Lastly we ensure that any state reached by repeating `n` copies of `w` has
|
||||
* a suffix `x` (possible empty) that is most likely __not__ accepted.
|
||||
*/
|
||||
|
||||
import ReDoSUtil
|
||||
|
||||
/**
|
||||
* Holds if state `s` might be inside a backtracking repetition.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate stateInsideBacktracking(State s) {
|
||||
s.getRepr().getParent*() instanceof MaybeBacktrackingRepetition
|
||||
}
|
||||
|
||||
/**
|
||||
* A infinitely repeating quantifier that might backtrack.
|
||||
*/
|
||||
private class MaybeBacktrackingRepetition extends InfiniteRepetitionQuantifier {
|
||||
MaybeBacktrackingRepetition() {
|
||||
exists(RegExpTerm child |
|
||||
child instanceof RegExpAlt or
|
||||
child instanceof RegExpQuantifier
|
||||
|
|
||||
child.getParent+() = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
*/
|
||||
private newtype TStatePair =
|
||||
/**
|
||||
* We lazily only construct those states that we are actually
|
||||
* going to need: `(q, q)` for every fork state `q`, and any
|
||||
* pair of states that can be reached from a pair that we have
|
||||
* already constructed. To cut down on the number of states,
|
||||
* we only represent states `(q1, q2)` where `q1` is lexicographically
|
||||
* no bigger than `q2`.
|
||||
*
|
||||
* States are only constructed if both states in the pair are
|
||||
* inside a repetition that might backtrack.
|
||||
*/
|
||||
MkStatePair(State q1, State q2) {
|
||||
isFork(q1, _, _, _, _) and q2 = q1
|
||||
or
|
||||
(step(_, _, _, q1, q2) or step(_, _, _, q2, q1)) and
|
||||
rankState(q1) <= rankState(q2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique number for a `state`.
|
||||
* Is used to create an ordering of states, where states with the same `toString()` will be ordered differently.
|
||||
*/
|
||||
private int rankState(State state) {
|
||||
state =
|
||||
rank[result](State s, Location l |
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s order by l.getStartLine(), l.getStartColumn(), s.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
*/
|
||||
private class StatePair extends TStatePair {
|
||||
State q1;
|
||||
State q2;
|
||||
|
||||
StatePair() { this = MkStatePair(q1, q2) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "(" + q1 + ", " + q2 + ")" }
|
||||
|
||||
/** Gets the first component of the state pair. */
|
||||
State getLeft() { result = q1 }
|
||||
|
||||
/** Gets the second component of the state pair. */
|
||||
State getRight() { result = q2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds for all constructed state pairs.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
*/
|
||||
private predicate isStatePair(StatePair p) { any() }
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r`.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
*/
|
||||
private predicate delta2(StatePair q, StatePair r) { step(q, _, _, r) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `q` to `r` in the
|
||||
* product automaton.
|
||||
*/
|
||||
private int statePairDist(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePair/1, delta2/2)(q, r, result)
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from `q` to `r1` and from `q` to `r2`
|
||||
* labelled with `s1` and `s2`, respectively, where `s1` and `s2` do not
|
||||
* trivially have an empty intersection.
|
||||
*
|
||||
* This predicate only holds for states associated with regular expressions
|
||||
* that have at least one repetition quantifier in them (otherwise the
|
||||
* expression cannot be vulnerable to ReDoS attacks anyway).
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate isFork(State q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
|
||||
stateInsideBacktracking(q) and
|
||||
exists(State q1, State q2 |
|
||||
q1 = epsilonSucc*(q) and
|
||||
delta(q1, s1, r1) and
|
||||
q2 = epsilonSucc*(q) and
|
||||
delta(q2, s2, r2) and
|
||||
// Use pragma[noopt] to prevent intersect(s1,s2) from being the starting point of the join.
|
||||
// From (s1,s2) it would find a huge number of intermediate state pairs (q1,q2) originating from different literals,
|
||||
// and discover at the end that no `q` can reach both `q1` and `q2` by epsilon transitions.
|
||||
exists(intersect(s1, s2))
|
||||
|
|
||||
s1 != s2
|
||||
or
|
||||
r1 != r2
|
||||
or
|
||||
r1 = r2 and q1 != q2
|
||||
or
|
||||
// If q can reach itself by epsilon transitions, then there are two distinct paths to the q1/q2 state:
|
||||
// one that uses the loop and one that doesn't. The engine will separately attempt to match with each path,
|
||||
// despite ending in the same state. The "fork" thus arises from the choice of whether to use the loop or not.
|
||||
// To avoid every state in the loop becoming a fork state,
|
||||
// we arbitrarily pick the InfiniteRepetitionQuantifier state as the canonical fork state for the loop
|
||||
// (every epsilon-loop must contain such a state).
|
||||
//
|
||||
// We additionally require that the there exists another InfiniteRepetitionQuantifier `mid` on the path from `q` to itself.
|
||||
// This is done to avoid flagging regular expressions such as `/(a?)*b/` - that only has polynomial runtime, and is detected by `js/polynomial-redos`.
|
||||
// The below code is therefore a heuritic, that only flags regular expressions such as `/(a*)*b/`,
|
||||
// and does not flag regular expressions such as `/(a?b?)c/`, but the latter pattern is not used frequently.
|
||||
r1 = r2 and
|
||||
q1 = q2 and
|
||||
epsilonSucc+(q) = q and
|
||||
exists(RegExpTerm term | term = q.getRepr() | term instanceof InfiniteRepetitionQuantifier) and
|
||||
// One of the mid states is an infinite quantifier itself
|
||||
exists(State mid, RegExpTerm term |
|
||||
mid = epsilonSucc+(q) and
|
||||
term = mid.getRepr() and
|
||||
term instanceof InfiniteRepetitionQuantifier and
|
||||
q = epsilonSucc+(mid) and
|
||||
not mid = q
|
||||
)
|
||||
) and
|
||||
stateInsideBacktracking(r1) and
|
||||
stateInsideBacktracking(r2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state pair `(q1, q2)` or `(q2, q1)`; note that only
|
||||
* one or the other is defined.
|
||||
*/
|
||||
private StatePair mkStatePair(State q1, State q2) {
|
||||
result = MkStatePair(q1, q2) or result = MkStatePair(q2, q1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r` labelled with `s1` and `s2`, respectively.
|
||||
*/
|
||||
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, StatePair r) {
|
||||
exists(State r1, State r2 | step(q, s1, s2, r1, r2) and r = mkStatePair(r1, r2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to `r1` and `r2`
|
||||
* labelled with `s1` and `s2`, respectively.
|
||||
*
|
||||
* We only consider transitions where the resulting states `(r1, r2)` are both
|
||||
* inside a repetition that might backtrack.
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, State r2) {
|
||||
exists(State q1, State q2 | q.getLeft() = q1 and q.getRight() = q2 |
|
||||
deltaClosed(q1, s1, r1) and
|
||||
deltaClosed(q2, s2, r2) and
|
||||
// use noopt to force the join on `intersect` to happen last.
|
||||
exists(intersect(s1, s2))
|
||||
) and
|
||||
stateInsideBacktracking(r1) and
|
||||
stateInsideBacktracking(r2)
|
||||
}
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) {
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(_, p, t, _) and
|
||||
step(p, s1, s2, _)
|
||||
)
|
||||
or
|
||||
t = Nil() and isFork(_, s1, s2, _, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of pairs of input symbols that describe a path in the product automaton
|
||||
* starting from some fork state.
|
||||
*/
|
||||
private class Trace extends TTrace {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
this = Nil() and result = "Nil()"
|
||||
or
|
||||
exists(InputSymbol s1, InputSymbol s2, Trace t | this = Step(s1, s2, t) |
|
||||
result = "Step(" + s1 + ", " + s2 + ", " + t + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `r` is reachable from `(fork, fork)` under input `w`, and there is
|
||||
* a path from `r` back to `(fork, fork)` with `rem` steps.
|
||||
*/
|
||||
private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
|
||||
// base case
|
||||
exists(InputSymbol s1, InputSymbol s2, State q1, State q2 |
|
||||
isFork(fork, s1, s2, q1, q2) and
|
||||
r = MkStatePair(q1, q2) and
|
||||
w = Step(s1, s2, Nil()) and
|
||||
rem = statePairDist(r, MkStatePair(fork, fork))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 |
|
||||
isReachableFromFork(fork, p, v, rem + 1) and
|
||||
step(p, s1, s2, r) and
|
||||
w = Step(s1, s2, v) and
|
||||
rem >= statePairDist(r, MkStatePair(fork, fork))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state in the product automaton from which `(fork, fork)` is
|
||||
* reachable in zero or more epsilon transitions.
|
||||
*/
|
||||
private StatePair getAForkPair(State fork) {
|
||||
isFork(fork, _, _, _, _) and
|
||||
result = MkStatePair(epsilonPred*(fork), epsilonPred*(fork))
|
||||
}
|
||||
|
||||
private predicate hasSuffix(Trace suffix, Trace t, int i) {
|
||||
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
|
||||
// recursive case, so instead we check it explicitly here.
|
||||
t instanceof RelevantTrace and
|
||||
i = 0 and
|
||||
suffix = t
|
||||
or
|
||||
hasSuffix(Step(_, _, suffix), t, i - 1)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasTuple(InputSymbol s1, InputSymbol s2, Trace t, int i) {
|
||||
hasSuffix(Step(s1, s2, _), t, i)
|
||||
}
|
||||
|
||||
private class RelevantTrace extends Trace, Step {
|
||||
RelevantTrace() {
|
||||
exists(State fork, StatePair q |
|
||||
isReachableFromFork(fork, q, this, _) and
|
||||
q = getAForkPair(fork)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private string intersect(int i) {
|
||||
exists(InputSymbol s1, InputSymbol s2 |
|
||||
hasTuple(s1, s2, this, i) and
|
||||
result = intersect(s1, s2)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a string corresponding to this trace. */
|
||||
// the pragma is needed for the case where `intersect(s1, s2)` has multiple values,
|
||||
// not for recursion
|
||||
language[monotonicAggregates]
|
||||
string concretise() {
|
||||
result = strictconcat(int i | hasTuple(_, _, this, i) | this.intersect(i) order by i desc)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fork` is a pumpable fork with word `w`.
|
||||
*/
|
||||
private predicate isPumpable(State fork, string w) {
|
||||
exists(StatePair q, RelevantTrace t |
|
||||
isReachableFromFork(fork, q, t, _) and
|
||||
q = getAForkPair(fork) and
|
||||
w = t.concretise()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of `ReDoSConfiguration` for exponential backtracking.
|
||||
*/
|
||||
class ExponentialReDoSConfiguration extends ReDoSConfiguration {
|
||||
ExponentialReDoSConfiguration() { this = "ExponentialReDoSConfiguration" }
|
||||
|
||||
override predicate isReDoSCandidate(State state, string pump) { isPumpable(state, pump) }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/** Definitions and configurations for the Polynomial ReDoS query */
|
||||
|
||||
import semmle.code.java.security.performance.SuperlinearBackTracking
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.regex.RegexTreeView
|
||||
import semmle.code.java.regex.RegexFlowConfigs
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/** A sink for polynomial redos queries, where a regex is matched. */
|
||||
class PolynomialRedosSink extends DataFlow::Node {
|
||||
RegExpLiteral reg;
|
||||
|
||||
PolynomialRedosSink() { regexMatchedAgainst(reg.getRegex(), this.asExpr()) }
|
||||
|
||||
/** Gets the regex that is matched against this node. */
|
||||
RegExpTerm getRegExp() { result.getParent() = reg }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method whose result typically has a limited length,
|
||||
* such as HTTP headers, and values derrived from them.
|
||||
*/
|
||||
private class LengthRestrictedMethod extends Method {
|
||||
LengthRestrictedMethod() {
|
||||
this.getName().toLowerCase().matches(["%header%", "%requesturi%", "%requesturl%", "%cookie%"])
|
||||
or
|
||||
this.getDeclaringType().getName().toLowerCase().matches("%cookie%") and
|
||||
this.getName().matches("get%")
|
||||
or
|
||||
this.getDeclaringType().getName().toLowerCase().matches("%request%") and
|
||||
this.getName().toLowerCase().matches(["%get%path%", "get%user%", "%querystring%"])
|
||||
}
|
||||
}
|
||||
|
||||
/** A configuration for Polynomial ReDoS queries. */
|
||||
class PolynomialRedosConfig extends TaintTracking::Configuration {
|
||||
PolynomialRedosConfig() { this = "PolynomialRedosConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof PolynomialRedosSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node.getType() instanceof PrimitiveType or
|
||||
node.getType() instanceof BoxedType or
|
||||
node.asExpr().(MethodAccess).getMethod() instanceof LengthRestrictedMethod
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if there is flow from `source` to `sink` that is matched against the regexp term `regexp` that is vulnerable to Polynomial ReDoS. */
|
||||
predicate hasPolynomialReDoSResult(
|
||||
DataFlow::PathNode source, DataFlow::PathNode sink, PolynomialBackTrackingTerm regexp
|
||||
) {
|
||||
any(PolynomialRedosConfig config).hasFlowPath(source, sink) and
|
||||
regexp.getRootTerm() = sink.getNode().(PolynomialRedosSink).getRegExp()
|
||||
}
|
||||
1198
java/ql/lib/semmle/code/java/security/performance/ReDoSUtil.qll
Normal file
1198
java/ql/lib/semmle/code/java/security/performance/ReDoSUtil.qll
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* This module should provide a class hierarchy corresponding to a parse tree of regular expressions.
|
||||
* This is the interface to the shared ReDoS library.
|
||||
*/
|
||||
|
||||
private import java
|
||||
import semmle.code.FileSystem
|
||||
import semmle.code.java.regex.RegexTreeView
|
||||
|
||||
/**
|
||||
* Holds if `term` is an escape class representing e.g. `\d`.
|
||||
* `clazz` is which character class it represents, e.g. "d" for `\d`.
|
||||
*/
|
||||
predicate isEscapeClass(RegExpTerm term, string clazz) {
|
||||
term.(RegExpCharacterClassEscape).getValue() = clazz
|
||||
or
|
||||
term.(RegExpNamedProperty).getBackslashEquivalent() = clazz
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` is a possessive quantifier, e.g. `a*+`.
|
||||
*/
|
||||
predicate isPossessive(RegExpQuantifier term) { term.isPossessive() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any leading prefix of the input it's matched against.
|
||||
*/
|
||||
predicate matchesAnyPrefix(RegExpTerm term) { not term.getRegex().matchesFullString() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any trailing suffix of the input it's matched against.
|
||||
*/
|
||||
predicate matchesAnySuffix(RegExpTerm term) { not term.getRegex().matchesFullString() }
|
||||
|
||||
/**
|
||||
* Holds if the regular expression should not be considered.
|
||||
*
|
||||
* We make the pragmatic performance optimization to ignore regular expressions in files
|
||||
* that do not belong to the project code (such as installed dependencies).
|
||||
*/
|
||||
predicate isExcluded(RegExpParent parent) {
|
||||
not exists(parent.getRegex().getLocation().getFile().getRelativePath())
|
||||
or
|
||||
// Regexes with many occurrences of ".*" may cause the polynomial ReDoS computation to explode, so
|
||||
// we explicitly exclude these.
|
||||
strictcount(int i | exists(parent.getRegex().getText().regexpFind("\\.\\*", i, _)) | i) > 10
|
||||
}
|
||||
|
||||
/**
|
||||
* A module containing predicates for determining which flags a regular expression have.
|
||||
*/
|
||||
module RegExpFlags {
|
||||
/**
|
||||
* Holds if `root` has the `i` flag for case-insensitive matching.
|
||||
*/
|
||||
predicate isIgnoreCase(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isIgnoreCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flags for `root`, or the empty string if `root` has no flags.
|
||||
*/
|
||||
string getFlags(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
result = root.getLiteral().getFlags()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `root` has the `s` flag for multi-line matching.
|
||||
*/
|
||||
predicate isDotAll(RegExpTerm root) {
|
||||
root.isRootTerm() and
|
||||
root.getLiteral().isDotAll()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/**
|
||||
* Provides classes for working with regular expressions that can
|
||||
* perform backtracking in superlinear time.
|
||||
*/
|
||||
|
||||
import ReDoSUtil
|
||||
|
||||
/*
|
||||
* This module implements the analysis described in the paper:
|
||||
* Valentin Wustholz, Oswaldo Olivo, Marijn J. H. Heule, and Isil Dillig:
|
||||
* Static Detection of DoS Vulnerabilities in
|
||||
* Programs that use Regular Expressions
|
||||
* (Extended Version).
|
||||
* (https://arxiv.org/pdf/1701.04045.pdf)
|
||||
*
|
||||
* Theorem 3 from the paper describes the basic idea.
|
||||
*
|
||||
* The following explains the idea using variables and predicate names that are used in the implementation:
|
||||
* We consider a pair of repetitions, which we will call `pivot` and `succ`.
|
||||
*
|
||||
* We create a product automaton of 3-tuples of states (see `StateTuple`).
|
||||
* There exists a transition `(a,b,c) -> (d,e,f)` in the product automaton
|
||||
* iff there exists three transitions in the NFA `a->d, b->e, c->f` where those three
|
||||
* transitions all match a shared character `char`. (see `getAThreewayIntersect`)
|
||||
*
|
||||
* We start a search in the product automaton at `(pivot, pivot, succ)`,
|
||||
* and search for a series of transitions (a `Trace`), such that we end
|
||||
* at `(pivot, succ, succ)` (see `isReachableFromStartTuple`).
|
||||
*
|
||||
* For example, consider the regular expression `/^\d*5\w*$/`.
|
||||
* The search will start at the tuple `(\d*, \d*, \w*)` and search
|
||||
* for a path to `(\d*, \w*, \w*)`.
|
||||
* This path exists, and consists of a single transition in the product automaton,
|
||||
* where the three corresponding NFA edges all match the character `"5"`.
|
||||
*
|
||||
* The start-state in the NFA has an any-transition to itself, this allows us to
|
||||
* flag regular expressions such as `/a*$/` - which does not have a start anchor -
|
||||
* and can thus start matching anywhere.
|
||||
*
|
||||
* The implementation is not perfect.
|
||||
* It has the same suffix detection issue as the `js/redos` query, which can cause false positives.
|
||||
* It also doesn't find all transitions in the product automaton, which can cause false negatives.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An instantiaion of `ReDoSConfiguration` for superlinear ReDoS.
|
||||
*/
|
||||
class SuperLinearReDoSConfiguration extends ReDoSConfiguration {
|
||||
SuperLinearReDoSConfiguration() { this = "SuperLinearReDoSConfiguration" }
|
||||
|
||||
override predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any root (start) state of a regular expression.
|
||||
*/
|
||||
private State getRootState() { result = mkMatch(any(RegExpRoot r)) }
|
||||
|
||||
private newtype TStateTuple =
|
||||
MkStateTuple(State q1, State q2, State q3) {
|
||||
// starts at (pivot, pivot, succ)
|
||||
isStartLoops(q1, q3) and q1 = q2
|
||||
or
|
||||
step(_, _, _, _, q1, q2, q3) and FeasibleTuple::isFeasibleTuple(q1, q2, q3)
|
||||
}
|
||||
|
||||
/**
|
||||
* A state in the product automaton.
|
||||
* The product automaton contains 3-tuples of states.
|
||||
*
|
||||
* We lazily only construct those states that we are actually
|
||||
* going to need.
|
||||
* Either a start state `(pivot, pivot, succ)`, or a state
|
||||
* where there exists a transition from an already existing state.
|
||||
*
|
||||
* The exponential variant of this query (`js/redos`) uses an optimization
|
||||
* trick where `q1 <= q2`. This trick cannot be used here as the order
|
||||
* of the elements matter.
|
||||
*/
|
||||
class StateTuple extends TStateTuple {
|
||||
State q1;
|
||||
State q2;
|
||||
State q3;
|
||||
|
||||
StateTuple() { this = MkStateTuple(q1, q2, q3) }
|
||||
|
||||
/**
|
||||
* Gest a string repesentation of this tuple.
|
||||
*/
|
||||
string toString() { result = "(" + q1 + ", " + q2 + ", " + q3 + ")" }
|
||||
|
||||
/**
|
||||
* Holds if this tuple is `(r1, r2, r3)`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate isTuple(State r1, State r2, State r3) { r1 = q1 and r2 = q2 and r3 = q3 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for determining feasible tuples for the product automaton.
|
||||
*
|
||||
* The implementation is split into many predicates for performance reasons.
|
||||
*/
|
||||
private module FeasibleTuple {
|
||||
/**
|
||||
* Holds if the tuple `(r1, r2, r3)` might be on path from a start-state to an end-state in the product automaton.
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate isFeasibleTuple(State r1, State r2, State r3) {
|
||||
// The first element is either inside a repetition (or the start state itself)
|
||||
isRepetitionOrStart(r1) and
|
||||
// The last element is inside a repetition
|
||||
stateInsideRepetition(r3) and
|
||||
// The states are reachable in the NFA in the order r1 -> r2 -> r3
|
||||
delta+(r1) = r2 and
|
||||
delta+(r2) = r3 and
|
||||
// The first element can reach a beginning (the "pivot" state in a `(pivot, succ)` pair).
|
||||
canReachABeginning(r1) and
|
||||
// The last element can reach a target (the "succ" state in a `(pivot, succ)` pair).
|
||||
canReachATarget(r3)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `s` is either inside a repetition, or is the start state (which is a repetition).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate isRepetitionOrStart(State s) { stateInsideRepetition(s) or s = getRootState() }
|
||||
|
||||
/**
|
||||
* Holds if state `s` might be inside a backtracking repetition.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate stateInsideRepetition(State s) {
|
||||
s.getRepr().getParent*() instanceof InfiniteRepetitionQuantifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a path in the NFA from `s` to a "pivot" state
|
||||
* (from a `(pivot, succ)` pair that starts the search).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate canReachABeginning(State s) {
|
||||
delta+(s) = any(State pivot | isStartLoops(pivot, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a path in the NFA from `s` to a "succ" state
|
||||
* (from a `(pivot, succ)` pair that starts the search).
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate canReachATarget(State s) { delta+(s) = any(State succ | isStartLoops(_, succ)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pivot` and `succ` are a pair of loops that could be the beginning of a quadratic blowup.
|
||||
*
|
||||
* There is a slight implementation difference compared to the paper: this predicate requires that `pivot != succ`.
|
||||
* The case where `pivot = succ` causes exponential backtracking and is handled by the `js/redos` query.
|
||||
*/
|
||||
predicate isStartLoops(State pivot, State succ) {
|
||||
pivot != succ and
|
||||
succ.getRepr() instanceof InfiniteRepetitionQuantifier and
|
||||
delta+(pivot) = succ and
|
||||
(
|
||||
pivot.getRepr() instanceof InfiniteRepetitionQuantifier
|
||||
or
|
||||
pivot = mkMatch(any(RegExpRoot root))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state for which there exists a transition in the NFA from `s'.
|
||||
*/
|
||||
State delta(State s) { delta(s, _, result) }
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r` labelled with `s1`, `s2`, and `s3`, respectively.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate step(StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, StateTuple r) {
|
||||
exists(State r1, State r2, State r3 |
|
||||
step(q, s1, s2, s3, r1, r2, r3) and r = MkStateTuple(r1, r2, r3)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to `r1`, `r2`, and `r3
|
||||
* labelled with `s1`, `s2`, and `s3`, respectively.
|
||||
*/
|
||||
pragma[noopt]
|
||||
predicate step(
|
||||
StateTuple q, InputSymbol s1, InputSymbol s2, InputSymbol s3, State r1, State r2, State r3
|
||||
) {
|
||||
exists(State q1, State q2, State q3 | q.isTuple(q1, q2, q3) |
|
||||
deltaClosed(q1, s1, r1) and
|
||||
deltaClosed(q2, s2, r2) and
|
||||
deltaClosed(q3, s3, r3) and
|
||||
// use noopt to force the join on `getAThreewayIntersect` to happen last.
|
||||
exists(getAThreewayIntersect(s1, s2, s3))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a char that is matched by all the edges `s1`, `s2`, and `s3`.
|
||||
*
|
||||
* The result is not complete, and might miss some combination of edges that share some character.
|
||||
*/
|
||||
pragma[noinline]
|
||||
string getAThreewayIntersect(InputSymbol s1, InputSymbol s2, InputSymbol s3) {
|
||||
result = minAndMaxIntersect(s1, s2) and result = [intersect(s2, s3), intersect(s1, s3)]
|
||||
or
|
||||
result = minAndMaxIntersect(s1, s3) and result = [intersect(s2, s3), intersect(s1, s2)]
|
||||
or
|
||||
result = minAndMaxIntersect(s2, s3) and result = [intersect(s1, s2), intersect(s1, s3)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum and maximum characters that intersect between `a` and `b`.
|
||||
* This predicate is used to limit the size of `getAThreewayIntersect`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
string minAndMaxIntersect(InputSymbol a, InputSymbol b) {
|
||||
result = [min(intersect(a, b)), max(intersect(a, b))]
|
||||
}
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, InputSymbol s3, TTrace t) {
|
||||
exists(StateTuple p |
|
||||
isReachableFromStartTuple(_, _, p, t, _) and
|
||||
step(p, s1, s2, s3, _)
|
||||
)
|
||||
or
|
||||
exists(State pivot, State succ | isStartLoops(pivot, succ) |
|
||||
t = Nil() and step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, _)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of tuples of input symbols that describe a path in the product automaton
|
||||
* starting from some start state.
|
||||
*/
|
||||
class Trace extends TTrace {
|
||||
/**
|
||||
* Gets a string representation of this Trace that can be used for debug purposes.
|
||||
*/
|
||||
string toString() {
|
||||
this = Nil() and result = "Nil()"
|
||||
or
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t | this = Step(s1, s2, s3, t) |
|
||||
result = "Step(" + s1 + ", " + s2 + ", " + s3 + ", " + t + ")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a transition from `r` to `q` in the product automaton.
|
||||
* Notice that the arguments are flipped, and thus the direction is backwards.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate tupleDeltaBackwards(StateTuple q, StateTuple r) { step(r, _, _, _, q) }
|
||||
|
||||
/**
|
||||
* Holds if `tuple` is an end state in our search.
|
||||
* That means there exists a pair of loops `(pivot, succ)` such that `tuple = (pivot, succ, succ)`.
|
||||
*/
|
||||
predicate isEndTuple(StateTuple tuple) { tuple = getAnEndTuple(_, _) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `r` to some an end state `end`.
|
||||
*
|
||||
* The implementation searches backwards from the end-tuple.
|
||||
* This approach was chosen because it is way more efficient if the first predicate given to `shortestDistances` is small.
|
||||
* The `end` argument must always be an end state.
|
||||
*/
|
||||
int distBackFromEnd(StateTuple r, StateTuple end) =
|
||||
shortestDistances(isEndTuple/1, tupleDeltaBackwards/2)(end, r, result)
|
||||
|
||||
/**
|
||||
* Holds if there exists a pair of repetitions `(pivot, succ)` in the regular expression such that:
|
||||
* `tuple` is reachable from `(pivot, pivot, succ)` in the product automaton,
|
||||
* and there is a distance of `dist` from `tuple` to the nearest end-tuple `(pivot, succ, succ)`,
|
||||
* and a path from a start-state to `tuple` follows the transitions in `trace`.
|
||||
*/
|
||||
predicate isReachableFromStartTuple(State pivot, State succ, StateTuple tuple, Trace trace, int dist) {
|
||||
// base case. The first step is inlined to start the search after all possible 1-steps, and not just the ones with the shortest path.
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3, State q1, State q2, State q3 |
|
||||
isStartLoops(pivot, succ) and
|
||||
step(MkStateTuple(pivot, pivot, succ), s1, s2, s3, tuple) and
|
||||
tuple = MkStateTuple(q1, q2, q3) and
|
||||
trace = Step(s1, s2, s3, Nil()) and
|
||||
dist = distBackFromEnd(tuple, MkStateTuple(pivot, succ, succ))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StateTuple p, Trace v, InputSymbol s1, InputSymbol s2, InputSymbol s3 |
|
||||
isReachableFromStartTuple(pivot, succ, p, v, dist + 1) and
|
||||
dist = isReachableFromStartTupleHelper(pivot, succ, tuple, p, s1, s2, s3) and
|
||||
trace = Step(s1, s2, s3, v)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate for the recursive case in `isReachableFromStartTuple`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private int isReachableFromStartTupleHelper(
|
||||
State pivot, State succ, StateTuple r, StateTuple p, InputSymbol s1, InputSymbol s2,
|
||||
InputSymbol s3
|
||||
) {
|
||||
result = distBackFromEnd(r, MkStateTuple(pivot, succ, succ)) and
|
||||
step(p, s1, s2, s3, r)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tuple `(pivot, succ, succ)` from the product automaton.
|
||||
*/
|
||||
StateTuple getAnEndTuple(State pivot, State succ) {
|
||||
isStartLoops(pivot, succ) and
|
||||
result = MkStateTuple(pivot, succ, succ)
|
||||
}
|
||||
|
||||
private predicate hasSuffix(Trace suffix, Trace t, int i) {
|
||||
// Declaring `t` to be a `RelevantTrace` currently causes a redundant check in the
|
||||
// recursive case, so instead we check it explicitly here.
|
||||
t instanceof RelevantTrace and
|
||||
i = 0 and
|
||||
suffix = t
|
||||
or
|
||||
hasSuffix(Step(_, _, _, suffix), t, i - 1)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate hasTuple(InputSymbol s1, InputSymbol s2, InputSymbol s3, Trace t, int i) {
|
||||
hasSuffix(Step(s1, s2, s3, _), t, i)
|
||||
}
|
||||
|
||||
private class RelevantTrace extends Trace, Step {
|
||||
RelevantTrace() {
|
||||
exists(State pivot, State succ, StateTuple q |
|
||||
isReachableFromStartTuple(pivot, succ, q, this, _) and
|
||||
q = getAnEndTuple(pivot, succ)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private string getAThreewayIntersect(int i) {
|
||||
exists(InputSymbol s1, InputSymbol s2, InputSymbol s3 |
|
||||
hasTuple(s1, s2, s3, this, i) and
|
||||
result = getAThreewayIntersect(s1, s2, s3)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a string corresponding to this trace. */
|
||||
// the pragma is needed for the case where `getAThreewayIntersect(s1, s2, s3)` has multiple values,
|
||||
// not for recursion
|
||||
language[monotonicAggregates]
|
||||
string concretise() {
|
||||
result =
|
||||
strictconcat(int i |
|
||||
hasTuple(_, _, _, this, i)
|
||||
|
|
||||
this.getAThreewayIntersect(i) order by i desc
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if matching repetitions of `pump` can:
|
||||
* 1) Transition from `pivot` back to `pivot`.
|
||||
* 2) Transition from `pivot` to `succ`.
|
||||
* 3) Transition from `succ` to `succ`.
|
||||
*
|
||||
* From theorem 3 in the paper linked in the top of this file we can therefore conclude that
|
||||
* the regular expression has polynomial backtracking - if a rejecting suffix exists.
|
||||
*
|
||||
* This predicate is used by `SuperLinearReDoSConfiguration`, and the final results are
|
||||
* available in the `hasReDoSResult` predicate.
|
||||
*/
|
||||
predicate isPumpable(State pivot, State succ, string pump) {
|
||||
exists(StateTuple q, RelevantTrace t |
|
||||
isReachableFromStartTuple(pivot, succ, q, t, _) and
|
||||
q = getAnEndTuple(pivot, succ) and
|
||||
pump = t.concretise()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if repetitions of `pump` at `t` will cause polynomial backtracking.
|
||||
*/
|
||||
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
|
||||
exists(State s, State pivot |
|
||||
hasReDoSResult(t, pump, s, prefixMsg) and
|
||||
isPumpable(pivot, s, _) and
|
||||
prev = pivot.getRepr()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a message for why `term` can cause polynomial backtracking.
|
||||
*/
|
||||
string getReasonString(RegExpTerm term, string pump, string prefixMsg, RegExpTerm prev) {
|
||||
polynimalReDoS(term, pump, prefixMsg, prev) and
|
||||
result =
|
||||
"Strings " + prefixMsg + "with many repetitions of '" + pump +
|
||||
"' can start matching anywhere after the start of the preceeding " + prev
|
||||
}
|
||||
|
||||
/**
|
||||
* A term that may cause a regular expression engine to perform a
|
||||
* polynomial number of match attempts, relative to the input length.
|
||||
*/
|
||||
class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier {
|
||||
string reason;
|
||||
string pump;
|
||||
string prefixMsg;
|
||||
RegExpTerm prev;
|
||||
|
||||
PolynomialBackTrackingTerm() {
|
||||
reason = getReasonString(this, pump, prefixMsg, prev) and
|
||||
// there might be many reasons for this term to have polynomial backtracking - we pick the shortest one.
|
||||
reason = min(string msg | msg = getReasonString(this, _, _, _) | msg order by msg.length(), msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if all non-empty successors to the polynomial backtracking term matches the end of the line.
|
||||
*/
|
||||
predicate isAtEndLine() {
|
||||
forall(RegExpTerm succ | this.getSuccessor+() = succ and not matchesEpsilon(succ) |
|
||||
succ instanceof RegExpDollar
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the string that should be repeated to cause this regular expression to perform polynomially.
|
||||
*/
|
||||
string getPumpString() { result = pump }
|
||||
|
||||
/**
|
||||
* Gets a message for which prefix a matching string must start with for this term to cause polynomial backtracking.
|
||||
*/
|
||||
string getPrefixMessage() { result = prefixMsg }
|
||||
|
||||
/**
|
||||
* Gets a predecessor to `this`, which also loops on the pump string, and thereby causes polynomial backtracking.
|
||||
*/
|
||||
RegExpTerm getPreviousLoop() { result = prev }
|
||||
|
||||
/**
|
||||
* Gets the reason for the number of match attempts.
|
||||
*/
|
||||
string getReason() { result = reason }
|
||||
}
|
||||
108
java/ql/src/Security/CWE/CWE-730/PolynomialReDoS.qhelp
Normal file
108
java/ql/src/Security/CWE/CWE-730/PolynomialReDoS.qhelp
Normal file
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
|
||||
<qhelp>
|
||||
|
||||
<include src="ReDoSIntroduction.inc.qhelp" />
|
||||
|
||||
<example>
|
||||
<p>
|
||||
|
||||
Consider this use of a regular expression, which removes
|
||||
all leading and trailing whitespace in a string:
|
||||
|
||||
</p>
|
||||
|
||||
<sample language="java">
|
||||
Pattern.compile("^\\s+|\\s+$").matcher(text).replaceAll("") // BAD
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
|
||||
The sub-expression <code>"\\s+$"</code> will match the
|
||||
whitespace characters in <code>text</code> from left to right, but it
|
||||
can start matching anywhere within a whitespace sequence. This is
|
||||
problematic for strings that do <strong>not</strong> end with a whitespace
|
||||
character. Such a string will force the regular expression engine to
|
||||
process each whitespace sequence once per whitespace character in the
|
||||
sequence.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
This ultimately means that the time cost of trimming a
|
||||
string is quadratic in the length of the string. So a string like
|
||||
<code>"a b"</code> will take milliseconds to process, but a similar
|
||||
string with a million spaces instead of just one will take several
|
||||
minutes.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
Avoid this problem by rewriting the regular expression to
|
||||
not contain the ambiguity about when to start matching whitespace
|
||||
sequences. For instance, by using a negative look-behind
|
||||
(<code>"^\\s+|(?<!\\s)\\s+$"</code>), or just by using the built-in trim
|
||||
method (<code>text.trim()</code>).
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
Note that the sub-expression <code>"^\\s+"</code> is
|
||||
<strong>not</strong> problematic as the <code>^</code> anchor restricts
|
||||
when that sub-expression can start matching, and as the regular
|
||||
expression engine matches from left to right.
|
||||
|
||||
</p>
|
||||
|
||||
</example>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
As a similar, but slightly subtler problem, consider the
|
||||
regular expression that matches lines with numbers, possibly written
|
||||
using scientific notation:
|
||||
</p>
|
||||
|
||||
<sample language="java">
|
||||
"^0\\.\\d+E?\\d+$""
|
||||
</sample>
|
||||
|
||||
<p>
|
||||
|
||||
The problem with this regular expression is in the
|
||||
sub-expression <code>\d+E?\d+</code> because the second
|
||||
<code>\d+</code> can start matching digits anywhere after the first
|
||||
match of the first <code>\d+</code> if there is no <code>E</code> in
|
||||
the input string.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
This is problematic for strings that do <strong>not</strong>
|
||||
end with a digit. Such a string will force the regular expression
|
||||
engine to process each digit sequence once per digit in the sequence,
|
||||
again leading to a quadratic time complexity.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
To make the processing faster, the regular expression
|
||||
should be rewritten such that the two <code>\d+</code> sub-expressions
|
||||
do not have overlapping matches: <code>"^0\\.\\d+(E\\d+)?$"</code>.
|
||||
|
||||
</p>
|
||||
|
||||
</example>
|
||||
|
||||
<include src="ReDoSReferences.inc.qhelp"/>
|
||||
|
||||
</qhelp>
|
||||
24
java/ql/src/Security/CWE/CWE-730/PolynomialReDoS.ql
Normal file
24
java/ql/src/Security/CWE/CWE-730/PolynomialReDoS.ql
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @name Polynomial regular expression used on uncontrolled data
|
||||
* @description A regular expression that can require polynomial time
|
||||
* to match may be vulnerable to denial-of-service attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id java/polynomial-redos
|
||||
* @tags security
|
||||
* external/cwe/cwe-730
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.security.performance.PolynomialReDoSQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, PolynomialBackTrackingTerm regexp
|
||||
where hasPolynomialReDoSResult(source, sink, regexp)
|
||||
select sink, source, sink,
|
||||
"This $@ that depends on $@ may run slow on strings " + regexp.getPrefixMessage() +
|
||||
"with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression",
|
||||
source.getNode(), "a user-provided value"
|
||||
34
java/ql/src/Security/CWE/CWE-730/ReDoS.qhelp
Normal file
34
java/ql/src/Security/CWE/CWE-730/ReDoS.qhelp
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
|
||||
<qhelp>
|
||||
|
||||
<include src="ReDoSIntroduction.inc.qhelp" />
|
||||
|
||||
<example>
|
||||
<p>
|
||||
Consider this regular expression:
|
||||
</p>
|
||||
<sample language="java">
|
||||
^_(__|.)+_$
|
||||
</sample>
|
||||
<p>
|
||||
Its sub-expression <code>"(__|.)+?"</code> can match the string <code>"__"</code> either by the
|
||||
first alternative <code>"__"</code> to the left of the <code>"|"</code> operator, or by two
|
||||
repetitions of the second alternative <code>"."</code> to the right. Thus, a string consisting
|
||||
of an odd number of underscores followed by some other character will cause the regular
|
||||
expression engine to run for an exponential amount of time before rejecting the input.
|
||||
</p>
|
||||
<p>
|
||||
This problem can be avoided by rewriting the regular expression to remove the ambiguity between
|
||||
the two branches of the alternative inside the repetition:
|
||||
</p>
|
||||
<sample language="java">
|
||||
^_(__|[^_])+_$
|
||||
</sample>
|
||||
</example>
|
||||
|
||||
<include src="ReDoSReferences.inc.qhelp"/>
|
||||
|
||||
</qhelp>
|
||||
26
java/ql/src/Security/CWE/CWE-730/ReDoS.ql
Normal file
26
java/ql/src/Security/CWE/CWE-730/ReDoS.ql
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @name Inefficient regular expression
|
||||
* @description A regular expression that requires exponential time to match certain inputs
|
||||
* can be a performance bottleneck, and may be vulnerable to denial-of-service
|
||||
* attacks.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id java/redos
|
||||
* @tags security
|
||||
* external/cwe/cwe-730
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.security.performance.ExponentialBackTracking
|
||||
|
||||
from RegExpTerm t, string pump, State s, string prefixMsg
|
||||
where
|
||||
hasReDoSResult(t, pump, s, prefixMsg) and
|
||||
// exclude verbose mode regexes for now
|
||||
not t.getRegex().getAMode() = "VERBOSE"
|
||||
select t,
|
||||
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
|
||||
"containing many repetitions of '" + pump + "'."
|
||||
61
java/ql/src/Security/CWE/CWE-730/ReDoSIntroduction.inc.qhelp
Normal file
61
java/ql/src/Security/CWE/CWE-730/ReDoSIntroduction.inc.qhelp
Normal file
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
Some regular expressions take a long time to match certain
|
||||
input strings to the point where the time it takes to match a string
|
||||
of length <i>n</i> is proportional to <i>n<sup>k</sup></i> or even
|
||||
<i>2<sup>n</sup></i>. Such regular expressions can negatively affect
|
||||
performance, or even allow a malicious user to perform a Denial of
|
||||
Service ("DoS") attack by crafting an expensive input string for the
|
||||
regular expression to match.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
The regular expression engine provided by Java uses a backtracking non-deterministic finite
|
||||
automata to implement regular expression matching. While this approach
|
||||
is space-efficient and allows supporting advanced features like
|
||||
capture groups, it is not time-efficient in general. The worst-case
|
||||
time complexity of such an automaton can be polynomial or even
|
||||
exponential, meaning that for strings of a certain shape, increasing
|
||||
the input length by ten characters may make the automaton about 1000
|
||||
times slower.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
Typically, a regular expression is affected by this
|
||||
problem if it contains a repetition of the form <code>r*</code> or
|
||||
<code>r+</code> where the sub-expression <code>r</code> is ambiguous
|
||||
in the sense that it can match some string in multiple ways. More
|
||||
information about the precise circumstances can be found in the
|
||||
references.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that Java versions 9 and above have some mitigations against ReDoS; however they aren't perfect
|
||||
and more complex regular expressions can still be affected by this problem.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
Modify the regular expression to remove the ambiguity, or
|
||||
ensure that the strings matched with the regular expression are short
|
||||
enough that the time-complexity does not matter.
|
||||
|
||||
Alternatively, an alternate regex library that guarantees linear time execution, such as Google's RE2J, may be used.
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
</qhelp>
|
||||
16
java/ql/src/Security/CWE/CWE-730/ReDoSReferences.inc.qhelp
Normal file
16
java/ql/src/Security/CWE/CWE-730/ReDoSReferences.inc.qhelp
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
|
||||
</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
|
||||
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
|
||||
<a href="http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
6
java/ql/src/change-notes/2022-03-03-redos.md
Normal file
6
java/ql/src/change-notes/2022-03-03-redos.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
|
||||
* Two new queries "Inefficient regular expression" (`java/redos`) and "Polynomial regular expression used on uncontrolled data" (`java/polynomial-redos`) have been added.
|
||||
These queries help find instances of Regular Expression Denial of Service vulnerabilities.
|
||||
@@ -73,6 +73,11 @@ jdk/StringMatch.java:
|
||||
# 5| 0: [MethodAccess] matches(...)
|
||||
# 5| -1: [VarAccess] STR
|
||||
# 5| 0: [StringLiteral] "[a-z]+"
|
||||
# 5| 0: [RegExpPlus] [a-z]+
|
||||
# 5| 0: [RegExpCharacterClass] [a-z]
|
||||
# 5| 0: [RegExpCharacterRange] a-z
|
||||
# 5| 0: [RegExpConstant | RegExpNormalChar] a
|
||||
# 5| 1: [RegExpConstant | RegExpNormalChar] z
|
||||
# 8| 5: [Method] b
|
||||
# 8| 3: [TypeAccess] void
|
||||
# 8| 5: [BlockStmt] { ... }
|
||||
|
||||
@@ -101,4 +101,4 @@ public class Test {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
207
java/ql/test/library-tests/regex/parser/RegexParseTests.expected
Normal file
207
java/ql/test/library-tests/regex/parser/RegexParseTests.expected
Normal file
@@ -0,0 +1,207 @@
|
||||
parseFailures
|
||||
#select
|
||||
| Test.java:5:10:5:17 | [A-Z\\d] | [RegExpCharacterClass] |
|
||||
| Test.java:5:10:5:19 | [A-Z\\d]++ | [RegExpPlus] |
|
||||
| Test.java:5:11:5:11 | A | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:5:11:5:13 | A-Z | [RegExpCharacterRange] |
|
||||
| Test.java:5:13:5:13 | Z | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:5:14:5:16 | \\d | [RegExpCharacterClassEscape] |
|
||||
| Test.java:6:10:6:42 | \\Q hello world [ *** \\Q ) ( \\E | [RegExpConstant,RegExpQuote] |
|
||||
| Test.java:6:10:6:43 | \\Q hello world [ *** \\Q ) ( \\E+ | [RegExpPlus] |
|
||||
| Test.java:7:10:7:23 | [\\Q hi ] \\E] | [RegExpCharacterClass] |
|
||||
| Test.java:7:10:7:24 | [\\Q hi ] \\E]+ | [RegExpPlus] |
|
||||
| Test.java:7:11:7:22 | \\Q hi ] \\E | [RegExpConstant,RegExpQuote] |
|
||||
| Test.java:8:10:8:12 | []] | [RegExpCharacterClass] |
|
||||
| Test.java:8:10:8:13 | []]+ | [RegExpPlus] |
|
||||
| Test.java:8:11:8:11 | ] | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:9:10:9:13 | [^]] | [RegExpCharacterClass] |
|
||||
| Test.java:9:10:9:14 | [^]]+ | [RegExpPlus] |
|
||||
| Test.java:9:12:9:12 | ] | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:10:10:20 | [abc[defg]] | [RegExpCharacterClass] |
|
||||
| Test.java:10:10:10:21 | [abc[defg]]+ | [RegExpPlus] |
|
||||
| Test.java:10:11:10:11 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:12:10:12 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:13:10:13 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:14:10:14 | [ | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:15:10:15 | d | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:16:10:16 | e | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:17:10:17 | f | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:18:10:18 | g | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:10:19:10:19 | ] | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:10:11:57 | [abc&&[\\W\\p{Lower}\\P{Space}\\N{degree sign}]] | [RegExpCharacterClass] |
|
||||
| Test.java:11:10:11:69 | [abc&&[\\W\\p{Lower}\\P{Space}\\N{degree sign}]]\\b7\\b{g}8+ | [RegExpSequence] |
|
||||
| Test.java:11:11:11:11 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:12:11:12 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:13:11:13 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:14:11:14 | & | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:15:11:15 | & | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:16:11:16 | [ | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:17:11:19 | \\W | [RegExpCharacterClassEscape] |
|
||||
| Test.java:11:20:11:29 | \\p{Lower} | [RegExpCharacterClassEscape] |
|
||||
| Test.java:11:30:11:39 | \\P{Space} | [RegExpCharacterClassEscape] |
|
||||
| Test.java:11:40:11:55 | \\N{degree sign} | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:11:56:11:56 | ] | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:58:11:60 | \\b | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:11:61:11:61 | 7 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:62:11:67 | \\b{g} | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:11:68:11:68 | 8 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:11:68:11:69 | 8+ | [RegExpPlus] |
|
||||
| Test.java:12:10:12:13 | \\cA | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:12:10:12:14 | \\cA+ | [RegExpPlus] |
|
||||
| Test.java:13:10:13:13 | \\c( | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:13:10:13:14 | \\c(+ | [RegExpPlus] |
|
||||
| Test.java:14:10:14:14 | \\c\\ | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:14:10:14:19 | \\c\\(ab)+ | [RegExpSequence] |
|
||||
| Test.java:14:15:14:18 | (ab) | [RegExpGroup] |
|
||||
| Test.java:14:15:14:19 | (ab)+ | [RegExpPlus] |
|
||||
| Test.java:14:16:14:16 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:14:16:14:17 | ab | [RegExpSequence] |
|
||||
| Test.java:14:17:14:17 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:10:15:15 | (?>hi) | [RegExpGroup] |
|
||||
| Test.java:15:10:15:45 | (?>hi)(?<name>hell*?o*+)123\\k<name> | [RegExpSequence] |
|
||||
| Test.java:15:13:15:13 | h | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:13:15:14 | hi | [RegExpSequence] |
|
||||
| Test.java:15:14:15:14 | i | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:16:15:33 | (?<name>hell*?o*+) | [RegExpGroup] |
|
||||
| Test.java:15:24:15:24 | h | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:24:15:32 | hell*?o*+ | [RegExpSequence] |
|
||||
| Test.java:15:25:15:25 | e | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:26:15:26 | l | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:27:15:27 | l | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:27:15:29 | l*? | [RegExpStar] |
|
||||
| Test.java:15:30:15:30 | o | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:30:15:32 | o*+ | [RegExpStar] |
|
||||
| Test.java:15:34:15:34 | 1 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:35:15:35 | 2 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:36:15:36 | 3 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:15:37:15:45 | \\k<name> | [RegExpBackRef] |
|
||||
| Test.java:16:10:16:10 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:10:16:11 | a+ | [RegExpPlus] |
|
||||
| Test.java:16:10:16:108 | a+b*c?d{2}e{3,4}f{,5}g{6,}h+?i*?j??k{7}?l{8,9}?m{,10}?n{11,}?o++p*+q?+r{12}+s{13,14}+t{,15}+u{16,}+ | [RegExpSequence] |
|
||||
| Test.java:16:12:16:12 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:12:16:13 | b* | [RegExpStar] |
|
||||
| Test.java:16:14:16:14 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:14:16:15 | c? | [RegExpOpt] |
|
||||
| Test.java:16:16:16:16 | d | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:16:16:19 | d{2} | [RegExpRange] |
|
||||
| Test.java:16:20:16:20 | e | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:20:16:25 | e{3,4} | [RegExpRange] |
|
||||
| Test.java:16:26:16:26 | f | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:26:16:30 | f{,5} | [RegExpRange] |
|
||||
| Test.java:16:31:16:31 | g | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:31:16:35 | g{6,} | [RegExpRange] |
|
||||
| Test.java:16:36:16:36 | h | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:36:16:38 | h+? | [RegExpPlus] |
|
||||
| Test.java:16:39:16:39 | i | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:39:16:41 | i*? | [RegExpStar] |
|
||||
| Test.java:16:42:16:42 | j | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:42:16:44 | j?? | [RegExpOpt] |
|
||||
| Test.java:16:45:16:45 | k | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:45:16:49 | k{7}? | [RegExpQuantifier] |
|
||||
| Test.java:16:50:16:50 | l | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:50:16:56 | l{8,9}? | [RegExpQuantifier] |
|
||||
| Test.java:16:57:16:57 | m | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:57:16:63 | m{,10}? | [RegExpQuantifier] |
|
||||
| Test.java:16:64:16:64 | n | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:64:16:70 | n{11,}? | [RegExpQuantifier] |
|
||||
| Test.java:16:71:16:71 | o | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:71:16:73 | o++ | [RegExpPlus] |
|
||||
| Test.java:16:74:16:74 | p | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:74:16:76 | p*+ | [RegExpStar] |
|
||||
| Test.java:16:77:16:77 | q | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:77:16:79 | q?+ | [RegExpOpt] |
|
||||
| Test.java:16:80:16:80 | r | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:80:16:85 | r{12}+ | [RegExpQuantifier] |
|
||||
| Test.java:16:86:16:86 | s | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:86:16:94 | s{13,14}+ | [RegExpQuantifier] |
|
||||
| Test.java:16:95:16:95 | t | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:95:16:101 | t{,15}+ | [RegExpQuantifier] |
|
||||
| Test.java:16:102:16:102 | u | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:16:102:16:108 | u{16,}+ | [RegExpQuantifier] |
|
||||
| Test.java:17:10:17:13 | (?i) | [RegExpZeroWidthMatch] |
|
||||
| Test.java:17:10:17:36 | (?i)(?=a)(?!b)(?<=c)(?<!d)+ | [RegExpSequence] |
|
||||
| Test.java:17:14:17:18 | (?=a) | [RegExpPositiveLookahead] |
|
||||
| Test.java:17:17:17:17 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:17:19:17:23 | (?!b) | [RegExpNegativeLookahead] |
|
||||
| Test.java:17:22:17:22 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:17:24:17:29 | (?<=c) | [RegExpPositiveLookbehind] |
|
||||
| Test.java:17:28:17:28 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:17:30:17:35 | (?<!d) | [RegExpNegativeLookbehind] |
|
||||
| Test.java:17:30:17:36 | (?<!d)+ | [RegExpPlus] |
|
||||
| Test.java:17:34:17:34 | d | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:10:18:10 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:10:18:25 | a\|\|b\|c(d\|e\|)f\|g+ | [RegExpAlt] |
|
||||
| Test.java:18:12:18:25 | \|b\|c(d\|e\|)f\|g+ | [RegExpAlt] |
|
||||
| Test.java:18:13:18:13 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:15:18:15 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:15:18:22 | c(d\|e\|)f | [RegExpSequence] |
|
||||
| Test.java:18:16:18:21 | (d\|e\|) | [RegExpGroup] |
|
||||
| Test.java:18:17:18:17 | d | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:17:18:20 | d\|e\| | [RegExpAlt] |
|
||||
| Test.java:18:19:18:19 | e | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:22:18:22 | f | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:24:18:24 | g | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:18:24:18:25 | g+ | [RegExpPlus] |
|
||||
| Test.java:19:10:19:13 | \\01 | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:19:10:19:38 | \\018\\033\\0377\\0777\u1337+ | [RegExpSequence] |
|
||||
| Test.java:19:14:19:14 | 8 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:19:15:19:19 | \\033 | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:19:20:19:25 | \\0377 | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:19:26:19:30 | \\077 | [RegExpConstant,RegExpEscape] |
|
||||
| Test.java:19:31:19:31 | 7 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:19:32:19:37 | \u1337 | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:19:32:19:38 | \u1337+ | [RegExpPlus] |
|
||||
| Test.java:20:10:20:12 | [\|] | [RegExpCharacterClass] |
|
||||
| Test.java:20:10:20:13 | [\|]+ | [RegExpPlus] |
|
||||
| Test.java:20:11:20:11 | \| | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:10:21:37 | (a(a(a(a(a(a((((c))))a)))))) | [RegExpGroup] |
|
||||
| Test.java:21:10:21:68 | (a(a(a(a(a(a((((c))))a))))))((((((b(((((d)))))b)b)b)b)b)b)+ | [RegExpSequence] |
|
||||
| Test.java:21:11:21:11 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:11:21:36 | a(a(a(a(a(a((((c))))a))))) | [RegExpSequence] |
|
||||
| Test.java:21:12:21:36 | (a(a(a(a(a((((c))))a))))) | [RegExpGroup] |
|
||||
| Test.java:21:13:21:13 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:13:21:35 | a(a(a(a(a((((c))))a)))) | [RegExpSequence] |
|
||||
| Test.java:21:14:21:35 | (a(a(a(a((((c))))a)))) | [RegExpGroup] |
|
||||
| Test.java:21:15:21:15 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:15:21:34 | a(a(a(a((((c))))a))) | [RegExpSequence] |
|
||||
| Test.java:21:16:21:34 | (a(a(a((((c))))a))) | [RegExpGroup] |
|
||||
| Test.java:21:17:21:17 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:17:21:33 | a(a(a((((c))))a)) | [RegExpSequence] |
|
||||
| Test.java:21:18:21:33 | (a(a((((c))))a)) | [RegExpGroup] |
|
||||
| Test.java:21:19:21:19 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:19:21:32 | a(a((((c))))a) | [RegExpSequence] |
|
||||
| Test.java:21:20:21:32 | (a((((c))))a) | [RegExpGroup] |
|
||||
| Test.java:21:21:21:21 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:21:21:31 | a((((c))))a | [RegExpSequence] |
|
||||
| Test.java:21:22:21:30 | ((((c)))) | [RegExpGroup] |
|
||||
| Test.java:21:23:21:29 | (((c))) | [RegExpGroup] |
|
||||
| Test.java:21:24:21:28 | ((c)) | [RegExpGroup] |
|
||||
| Test.java:21:25:21:27 | (c) | [RegExpGroup] |
|
||||
| Test.java:21:26:21:26 | c | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:31:21:31 | a | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:38:21:67 | ((((((b(((((d)))))b)b)b)b)b)b) | [RegExpGroup] |
|
||||
| Test.java:21:38:21:68 | ((((((b(((((d)))))b)b)b)b)b)b)+ | [RegExpPlus] |
|
||||
| Test.java:21:39:21:65 | (((((b(((((d)))))b)b)b)b)b) | [RegExpGroup] |
|
||||
| Test.java:21:39:21:66 | (((((b(((((d)))))b)b)b)b)b)b | [RegExpSequence] |
|
||||
| Test.java:21:40:21:63 | ((((b(((((d)))))b)b)b)b) | [RegExpGroup] |
|
||||
| Test.java:21:40:21:64 | ((((b(((((d)))))b)b)b)b)b | [RegExpSequence] |
|
||||
| Test.java:21:41:21:61 | (((b(((((d)))))b)b)b) | [RegExpGroup] |
|
||||
| Test.java:21:41:21:62 | (((b(((((d)))))b)b)b)b | [RegExpSequence] |
|
||||
| Test.java:21:42:21:59 | ((b(((((d)))))b)b) | [RegExpGroup] |
|
||||
| Test.java:21:42:21:60 | ((b(((((d)))))b)b)b | [RegExpSequence] |
|
||||
| Test.java:21:43:21:57 | (b(((((d)))))b) | [RegExpGroup] |
|
||||
| Test.java:21:43:21:58 | (b(((((d)))))b)b | [RegExpSequence] |
|
||||
| Test.java:21:44:21:44 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:44:21:56 | b(((((d)))))b | [RegExpSequence] |
|
||||
| Test.java:21:45:21:55 | (((((d))))) | [RegExpGroup] |
|
||||
| Test.java:21:46:21:54 | ((((d)))) | [RegExpGroup] |
|
||||
| Test.java:21:47:21:53 | (((d))) | [RegExpGroup] |
|
||||
| Test.java:21:48:21:52 | ((d)) | [RegExpGroup] |
|
||||
| Test.java:21:49:21:51 | (d) | [RegExpGroup] |
|
||||
| Test.java:21:50:21:50 | d | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:56:21:56 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:58:21:58 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:60:21:60 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:62:21:62 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:64:21:64 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
| Test.java:21:66:21:66 | b | [RegExpConstant,RegExpNormalChar] |
|
||||
10
java/ql/test/library-tests/regex/parser/RegexParseTests.ql
Normal file
10
java/ql/test/library-tests/regex/parser/RegexParseTests.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
import java
|
||||
import semmle.code.java.regex.RegexTreeView
|
||||
import semmle.code.java.regex.regex
|
||||
|
||||
string getQLClases(RegExpTerm t) { result = "[" + strictconcat(t.getPrimaryQLClass(), ",") + "]" }
|
||||
|
||||
query predicate parseFailures(Regex r, int i) { r.failedToParse(i) }
|
||||
|
||||
from RegExpTerm t
|
||||
select t, getQLClases(t)
|
||||
29
java/ql/test/library-tests/regex/parser/Test.java
Normal file
29
java/ql/test/library-tests/regex/parser/Test.java
Normal file
@@ -0,0 +1,29 @@
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class Test {
|
||||
static String[] regs = {
|
||||
"[A-Z\\d]++",
|
||||
"\\Q hello world [ *** \\Q ) ( \\E+",
|
||||
"[\\Q hi ] \\E]+",
|
||||
"[]]+",
|
||||
"[^]]+",
|
||||
"[abc[defg]]+",
|
||||
"[abc&&[\\W\\p{Lower}\\P{Space}\\N{degree sign}]]\\b7\\b{g}8+",
|
||||
"\\cA+",
|
||||
"\\c(+",
|
||||
"\\c\\(ab)+",
|
||||
"(?>hi)(?<name>hell*?o*+)123\\k<name>",
|
||||
"a+b*c?d{2}e{3,4}f{,5}g{6,}h+?i*?j??k{7}?l{8,9}?m{,10}?n{11,}?o++p*+q?+r{12}+s{13,14}+t{,15}+u{16,}+",
|
||||
"(?i)(?=a)(?!b)(?<=c)(?<!d)+",
|
||||
"a||b|c(d|e|)f|g+",
|
||||
"\\018\\033\\0377\\0777\u1337+",
|
||||
"[|]+",
|
||||
"(a(a(a(a(a(a((((c))))a))))))((((((b(((((d)))))b)b)b)b)b)b)+"
|
||||
};
|
||||
|
||||
void test() {
|
||||
for (int i = 0; i < regs.length; i++) {
|
||||
Pattern.compile(regs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
439
java/ql/test/query-tests/security/CWE-730/ExpRedosTest.java
Normal file
439
java/ql/test/query-tests/security/CWE-730/ExpRedosTest.java
Normal file
@@ -0,0 +1,439 @@
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class ExpRedosTest {
|
||||
static String[] regs = {
|
||||
|
||||
// NOT GOOD; attack: "_" + "__".repeat(100)
|
||||
// Adapted from marked (https://github.com/markedjs/marked), which is licensed
|
||||
// under the MIT license; see file marked-LICENSE.
|
||||
"^\\b_((?:__|[\\s\\S])+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
// Adapted from marked (https://github.com/markedjs/marked), which is licensed
|
||||
// under the MIT license; see file marked-LICENSE.
|
||||
"^\\b_((?:__|[^_])+?)_\\b|^\\*((?:\\*\\*|[^*])+?)\\*(?!\\*)",
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
// Adapted from brace-expansion (https://github.com/juliangruber/brace-expansion),
|
||||
// which is licensed under the MIT license; see file brace-expansion-LICENSE.
|
||||
"(.*,)+.+",
|
||||
|
||||
// NOT GOOD; attack: " '" + "\\\\".repeat(100)
|
||||
// Adapted from CodeMirror (https://github.com/codemirror/codemirror),
|
||||
// which is licensed under the MIT license; see file CodeMirror-LICENSE.
|
||||
"^(?:\\s+(?:\"(?:[^\"\\\\]|\\\\\\\\|\\\\.)+\"|'(?:[^'\\\\]|\\\\\\\\|\\\\.)+'|\\((?:[^)\\\\]|\\\\\\\\|\\\\.)+\\)))?", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
// Adapted from lulucms2 (https://github.com/yiifans/lulucms2).
|
||||
"\\(\\*(?:[\\s\\S]*?\\(\\*[\\s\\S]*?\\*\\))*[\\s\\S]*?\\*\\)",
|
||||
|
||||
// GOOD
|
||||
// Adapted from jest (https://github.com/facebook/jest), which is licensed
|
||||
// under the MIT license; see file jest-LICENSE.
|
||||
"^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)\\n*",
|
||||
|
||||
// NOT GOOD, variant of good3; attack: "a|\n:|\n" + "||\n".repeat(100)
|
||||
"^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)a", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD; attack: "/" + "\\/a".repeat(100)
|
||||
// Adapted from ANodeBlog (https://github.com/gefangshuai/ANodeBlog),
|
||||
// which is licensed under the Apache License 2.0; see file ANodeBlog-LICENSE.
|
||||
"\\/(?![ *])(\\\\\\/|.)*?\\/[gim]*(?=\\W|$)", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD; attack: "##".repeat(100) + "\na"
|
||||
// Adapted from CodeMirror (https://github.com/codemirror/codemirror),
|
||||
// which is licensed under the MIT license; see file CodeMirror-LICENSE.
|
||||
"^([\\s\\[\\{\\(]|#.*)*$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(\\r\\n|\\r|\\n)+",
|
||||
|
||||
// BAD - PoC: `node -e "/((?:[^\"\']|\".*?\"|\'.*?\')*?)([(,)]|$)/.test(\"'''''''''''''''''''''''''''''''''''''''''''''\\\"\");"`. It's complicated though, because the regexp still matches something, it just matches the empty-string after the attack string.
|
||||
|
||||
// NOT GOOD; attack: "a" + "[]".repeat(100) + ".b\n"
|
||||
// Adapted from Knockout (https://github.com/knockout/knockout), which is
|
||||
// licensed under the MIT license; see file knockout-LICENSE
|
||||
"^[\\_$a-z][\\_$a-z0-9]*(\\[.*?\\])*(\\.[\\_$a-z][\\_$a-z0-9]*(\\[.*?\\])*)*$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a|.)*",
|
||||
|
||||
// Testing the NFA - only some of the below are detected.
|
||||
"^([a-z]+)+$", // $ hasExpRedos
|
||||
"^([a-z]*)*$", // $ hasExpRedos
|
||||
"^([a-zA-Z0-9])(([\\\\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$", // $ hasExpRedos
|
||||
"^(([a-z])+.)+[A-Z]([a-z])+$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD; attack: "[" + "][".repeat(100) + "]!"
|
||||
// Adapted from Prototype.js (https://github.com/prototypejs/prototype), which
|
||||
// is licensed under the MIT license; see file Prototype.js-LICENSE.
|
||||
"(([\\w#:.~>+()\\s-]+|\\*|\\[.*?\\])+)\\s*(,|$)", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD; attack: "'" + "\\a".repeat(100) + '"'
|
||||
// Adapted from Prism (https://github.com/PrismJS/prism), which is licensed
|
||||
// under the MIT license; see file Prism-LICENSE.
|
||||
"(\"|')(\\\\?.)*?\\1", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(b|a?b)*c", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(a|aa?)*b", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(.|\\n)*!",
|
||||
|
||||
// NOT GOOD; attack: "\n".repeat(100) + "."
|
||||
"(?s)(.|\\n)*!", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"([\\w.]+)*",
|
||||
|
||||
// NOT GOOD
|
||||
"(a|aa?)*b", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(([\\s\\S]|[^a])*)\"", // $ hasExpRedos
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
"([^\"']+)*",
|
||||
|
||||
// NOT GOOD
|
||||
"((.|[^a])*)\"", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"((a|[^a])*)\"",
|
||||
|
||||
// NOT GOOD
|
||||
"((b|[^a])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((G|[^a])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(([0-9]|[^a])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(?:=(?:([!#\\$%&'\\*\\+\\-\\.\\^_`\\|~0-9A-Za-z]+)|\"((?:\\\\[\\x00-\\x7f]|[^\\x00-\\x08\\x0a-\\x1f\\x7f\"])*)\"))?", // $ MISSING: hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"\"((?:\\\\[\\x00-\\x7f]|[^\\x00-\\x08\\x0a-\\x1f\\x7f\"])*)\"", // $ MISSING: hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"\"((?:\\\\[\\x00-\\x7f]|[^\\x00-\\x08\\x0a-\\x1f\\x7f\"\\\\])*)\"",
|
||||
|
||||
// NOT GOOD
|
||||
"(([a-z]|[d-h])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(([^a-z]|[^0-9])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\d|[0-9])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\s|\\s)*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\w|G)*)\"", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"((\\s|\\d)*)\"",
|
||||
|
||||
// NOT GOOD
|
||||
"((\\d|\\w)*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\d|5)*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\s|[\\f])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD - but not detected (likely because \v is a character class in Java rather than a specific character in other langs)
|
||||
"((\\s|[\\v]|\\\\v)*)\"", // $ MISSING: hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\f|[\\f])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\W|\\D)*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\S|\\w)*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((\\S|[\\w])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((1s|[\\da-z])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((0|[\\d])*)\"", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(([\\d]+)*)\"", // $ hasExpRedos
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
"(\\d+(X\\d+)?)+",
|
||||
|
||||
// GOOD - there is no witness in the end that could cause the regexp to not match
|
||||
"([0-9]+(X[0-9]*)?)*",
|
||||
|
||||
// GOOD
|
||||
"^([^>]+)*(>|$)",
|
||||
|
||||
// NOT GOOD
|
||||
"^([^>a]+)*(>|$)", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(\\n\\s*)+$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"^(?:\\s+|#.*|\\(\\?#[^)]*\\))*(?:[?*+]|\\{\\d+(?:,\\d*)?})", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"\\{\\[\\s*([a-zA-Z]+)\\(([a-zA-Z]+)\\)((\\s*([a-zA-Z]+)\\: ?([ a-zA-Z{}]+),?)+)*\\s*\\]\\}", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(a+|b+|c+)*c", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(((a+a?)*)+b+)", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(a+)+bbbb", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a+)+aaaaa*a+",
|
||||
|
||||
// NOT GOOD
|
||||
"(a+)+aaaaa$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(\\n+)+\\n\\n",
|
||||
|
||||
// NOT GOOD
|
||||
"(\\n+)+\\n\\n$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"([^X]+)*$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(([^X]b)+)*$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(([^X]b)+)*($|[^X]b)",
|
||||
|
||||
// NOT GOOD
|
||||
"(([^X]b)+)*($|[^X]c)", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"((ab)+)*ababab",
|
||||
|
||||
// GOOD
|
||||
"((ab)+)*abab(ab)*(ab)+",
|
||||
|
||||
// GOOD
|
||||
"((ab)+)*",
|
||||
|
||||
// NOT GOOD
|
||||
"((ab)+)*$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"((ab)+)*[a1][b1][a2][b2][a3][b3]",
|
||||
|
||||
// NOT GOOD
|
||||
"([\\n\\s]+)*(.)", // $ hasExpRedos
|
||||
|
||||
// GOOD - any witness passes through the accept state.
|
||||
"(A*A*X)*",
|
||||
|
||||
// GOOD
|
||||
"([^\\\\\\]]+)*",
|
||||
|
||||
// NOT GOOD
|
||||
"(\\w*foobarbaz\\w*foobarbaz\\w*foobarbaz\\w*foobarbaz\\s*foobarbaz\\d*foobarbaz\\w*)+-", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(.thisisagoddamnlongstringforstresstestingthequery|\\sthisisagoddamnlongstringforstresstestingthequery)*-", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(thisisagoddamnlongstringforstresstestingthequery|this\\w+query)*-", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(thisisagoddamnlongstringforstresstestingthequery|imanotherbutunrelatedstringcomparedtotheotherstring)*-",
|
||||
|
||||
// GOOD (but false positive caused by the extractor converting all four unpaired surrogates to \uFFFD)
|
||||
"foo([\uDC66\uDC67]|[\uDC68\uDC69])*foo", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// GOOD (but false positive caused by the extractor converting all four unpaired surrogates to \uFFFD)
|
||||
"foo((\uDC66|\uDC67)|(\uDC68|\uDC69))*foo", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// NOT GOOD (but cannot currently construct a prefix)
|
||||
"a{2,3}(b+)+X", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD (and a good prefix test)
|
||||
"^<(\\w+)((?:\\s+\\w+(?:\\s*=\\s*(?:(?:\"[^\"]*\")|(?:'[^']*')|[^>\\s]+))?)*)\\s*(\\/?)>", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a+)*[\\s\\S][\\s\\S][\\s\\S]?",
|
||||
|
||||
// GOOD - but we fail to see that repeating the attack string ends in the "accept any" state (due to not parsing the range `[\s\S]{2,3}`).
|
||||
"(a+)*[\\s\\S]{2,3}", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists (due to not parsing the range `[\s\S]{2,}` when constructing the NFA).
|
||||
"(a+)*([\\s\\S]{2,}|X)$", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a+)*([\\s\\S]*|X)$",
|
||||
|
||||
// NOT GOOD
|
||||
"((a+)*$|[\\s\\S]+)", // $ hasExpRedos
|
||||
|
||||
// GOOD - but still flagged. The only change compared to the above is the order of alternatives, which we don't model.
|
||||
"([\\s\\S]+|(a+)*$)", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"((;|^)a+)+$",
|
||||
|
||||
// NOT GOOD (a good prefix test)
|
||||
"(^|;)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(0|1)(e+)+f", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"^ab(c+)+$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(\\d(\\s+)*){20}", // $ hasExpRedos
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists.
|
||||
"(([^/]|X)+)(\\/[\\s\\S]*)*$", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// GOOD - but we spuriously conclude that a rejecting suffix exists.
|
||||
"^((x([^Y]+)?)*(Y|$))", // $ SPURIOUS: hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(a*)+b", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"foo([\\w-]*)+bar", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"((ab)*)+c", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(a?a?)*b", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a?)*b",
|
||||
|
||||
// NOT GOOD - but not detected
|
||||
"(c?a?)*b", // $ MISSING: hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"(?:a|a?)+b", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD - but not detected.
|
||||
"(a?b?)*$", // $ MISSING: hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"PRE(([a-c]|[c-d])T(e?e?e?e?|X))+(cTcT|cTXcTX$)", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"^((a)+\\w)+$", // $ hasExpRedos
|
||||
|
||||
// NOT GOOD
|
||||
"^(b+.)+$", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"a*b",
|
||||
|
||||
// All 4 bad combinations of nested * and +
|
||||
"(a*)*b", // $ hasExpRedos
|
||||
"(a+)*b", // $ hasExpRedos
|
||||
"(a*)+b", // $ hasExpRedos
|
||||
"(a+)+b", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"(a|b)+",
|
||||
"(?:[\\s;,\"'<>(){}|\\[\\]@=+*]|:(?![/\\\\]))+",
|
||||
|
||||
"^((?:a{|-)|\\w\\{)+X$", // $ hasParseFailure
|
||||
"^((?:a{0|-)|\\w\\{\\d)+X$", // $ hasParseFailure
|
||||
"^((?:a{0,|-)|\\w\\{\\d,)+X$", // $ hasParseFailure
|
||||
"^((?:a{0,2|-)|\\w\\{\\d,\\d)+X$", // $ hasParseFailure
|
||||
|
||||
// GOOD
|
||||
"^((?:a{0,2}|-)|\\w\\{\\d,\\d\\})+X$",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\u0061|a)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\u0061|b)+Y",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\x61|a)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\x61|b)+Y",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\x{061}|a)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\x{061}|b)+Y",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\p{Digit}|7)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\p{Digit}|b)+Y",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\P{Digit}|b)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\P{Digit}|7)+Y",
|
||||
|
||||
// NOT GOOD
|
||||
"X(\\p{IsDigit}|7)*Y", // $ hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\p{IsDigit}|b)+Y",
|
||||
|
||||
// NOT GOOD - but not detected
|
||||
"X(\\p{Alpha}|a)*Y", // $ MISSING: hasExpRedos
|
||||
|
||||
// GOOD
|
||||
"X(\\p{Alpha}|7)+Y",
|
||||
|
||||
// GOOD
|
||||
"(\"[^\"]*?\"|[^\"\\s]+)+(?=\\s*|\\s*$)",
|
||||
|
||||
// BAD
|
||||
"/(\"[^\"]*?\"|[^\"\\s]+)+(?=\\s*|\\s*$)X", // $ hasExpRedos
|
||||
"/(\"[^\"]*?\"|[^\"\\s]+)+(?=X)", // $ hasExpRedos
|
||||
|
||||
// BAD
|
||||
"\\A(\\d|0)*x", // $ hasExpRedos
|
||||
"(\\d|0)*\\Z", // $ hasExpRedos
|
||||
"\\b(\\d|0)*x", // $ hasExpRedos
|
||||
|
||||
// GOOD - possessive quantifiers don't backtrack
|
||||
"(a*+)*+b",
|
||||
"(a*)*+b",
|
||||
"(a*+)*b",
|
||||
|
||||
// BAD
|
||||
"(a*)*b", // $ hasExpRedos
|
||||
|
||||
// BAD - but not detected due to the way possessive quantifiers are approximated
|
||||
"((aa|a*+)b)*c" // $ MISSING: hasExpRedos
|
||||
};
|
||||
|
||||
void test() {
|
||||
for (int i = 0; i < regs.length; i++) {
|
||||
Pattern.compile(regs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
java/ql/test/query-tests/security/CWE-730/PolyRedosTest.java
Normal file
84
java/ql/test/query-tests/security/CWE-730/PolyRedosTest.java
Normal file
@@ -0,0 +1,84 @@
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.function.Predicate;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
class PolyRedosTest {
|
||||
void test(HttpServletRequest request) {
|
||||
String tainted = request.getParameter("inp");
|
||||
String reg = "0\\.\\d+E?\\d+!";
|
||||
Predicate<String> dummyPred = (s -> s.length() % 7 == 0);
|
||||
|
||||
tainted.matches(reg); // $ hasPolyRedos
|
||||
tainted.split(reg); // $ hasPolyRedos
|
||||
tainted.split(reg, 7); // $ hasPolyRedos
|
||||
tainted.replaceAll(reg, "a"); // $ hasPolyRedos
|
||||
tainted.replaceFirst(reg, "a"); // $ hasPolyRedos
|
||||
Pattern.matches(reg, tainted); // $ hasPolyRedos
|
||||
Pattern.compile(reg).matcher(tainted).matches(); // $ hasPolyRedos
|
||||
Pattern.compile(reg).split(tainted); // $ hasPolyRedos
|
||||
Pattern.compile(reg, Pattern.DOTALL).split(tainted); // $ hasPolyRedos
|
||||
Pattern.compile(reg).split(tainted, 7); // $ hasPolyRedos
|
||||
Pattern.compile(reg).splitAsStream(tainted); // $ hasPolyRedos
|
||||
Pattern.compile(reg).asPredicate().test(tainted); // $ hasPolyRedos
|
||||
Pattern.compile(reg).asMatchPredicate().negate().and(dummyPred).or(dummyPred).test(tainted); // $ hasPolyRedos
|
||||
Predicate.not(dummyPred.and(dummyPred.or(Pattern.compile(reg).asPredicate()))).test(tainted); // $ hasPolyRedos
|
||||
|
||||
Splitter.on(Pattern.compile(reg)).split(tainted); // $ hasPolyRedos
|
||||
Splitter.on(reg).split(tainted);
|
||||
Splitter.onPattern(reg).split(tainted); // $ hasPolyRedos
|
||||
Splitter.onPattern(reg).splitToList(tainted); // $ hasPolyRedos
|
||||
Splitter.onPattern(reg).limit(7).omitEmptyStrings().trimResults().split(tainted); // $ hasPolyRedos
|
||||
Splitter.onPattern(reg).withKeyValueSeparator(" => ").split(tainted); // $ hasPolyRedos
|
||||
Splitter.on(";").withKeyValueSeparator(reg).split(tainted);
|
||||
Splitter.on(";").withKeyValueSeparator(Splitter.onPattern(reg)).split(tainted); // $ hasPolyRedos
|
||||
|
||||
}
|
||||
|
||||
void test2(HttpServletRequest request) {
|
||||
String tainted = request.getParameter("inp");
|
||||
|
||||
Pattern p1 = Pattern.compile(".*a");
|
||||
Pattern p2 = Pattern.compile(".*b");
|
||||
|
||||
p1.matcher(tainted).matches();
|
||||
p2.matcher(tainted).find(); // $ hasPolyRedos
|
||||
}
|
||||
|
||||
void test3(HttpServletRequest request) {
|
||||
String tainted = request.getParameter("inp");
|
||||
|
||||
Pattern p1 = Pattern.compile("ab*b*");
|
||||
Pattern p2 = Pattern.compile("cd*d*");
|
||||
|
||||
p1.matcher(tainted).matches(); // $ hasPolyRedos
|
||||
p2.matcher(tainted).find();
|
||||
}
|
||||
|
||||
void test4(HttpServletRequest request) {
|
||||
String tainted = request.getParameter("inp");
|
||||
|
||||
tainted.matches(".*a");
|
||||
tainted.replaceAll(".*b", "c"); // $ hasPolyRedos
|
||||
}
|
||||
|
||||
static Pattern p3 = Pattern.compile(".*a");
|
||||
static Pattern p4 = Pattern.compile(".*b");
|
||||
|
||||
|
||||
void test5(HttpServletRequest request) {
|
||||
String tainted = request.getParameter("inp");
|
||||
|
||||
p3.asMatchPredicate().test(tainted);
|
||||
p4.asPredicate().test(tainted); // $ hasPolyRedos
|
||||
}
|
||||
|
||||
void test6(HttpServletRequest request) {
|
||||
Pattern p = Pattern.compile("^a*a*$");
|
||||
|
||||
p.matcher(request.getParameter("inp")).matches(); // $ hasPolyRedos
|
||||
p.matcher(request.getHeader("If-None-Match")).matches();
|
||||
p.matcher(request.getRequestURI()).matches();
|
||||
p.matcher(request.getCookies()[0].getName()).matches();
|
||||
}
|
||||
}
|
||||
19
java/ql/test/query-tests/security/CWE-730/PolynomialReDoS.ql
Normal file
19
java/ql/test/query-tests/security/CWE-730/PolynomialReDoS.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
import java
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.code.java.security.performance.PolynomialReDoSQuery
|
||||
|
||||
class HasPolyRedos extends InlineExpectationsTest {
|
||||
HasPolyRedos() { this = "HasPolyRedos" }
|
||||
|
||||
override string getARelevantTag() { result = "hasPolyRedos" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasPolyRedos" and
|
||||
exists(DataFlow::PathNode source, DataFlow::PathNode sink, PolynomialBackTrackingTerm regexp |
|
||||
hasPolynomialReDoSResult(source, sink, regexp) and
|
||||
location = sink.getNode().getLocation() and
|
||||
element = sink.getNode().toString() and
|
||||
value = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
29
java/ql/test/query-tests/security/CWE-730/ReDoS.ql
Normal file
29
java/ql/test/query-tests/security/CWE-730/ReDoS.ql
Normal file
@@ -0,0 +1,29 @@
|
||||
import java
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.code.java.security.performance.ExponentialBackTracking
|
||||
import semmle.code.java.regex.regex
|
||||
|
||||
class HasExpRedos extends InlineExpectationsTest {
|
||||
HasExpRedos() { this = "HasExpRedos" }
|
||||
|
||||
override string getARelevantTag() { result = ["hasExpRedos", "hasParseFailure"] }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasExpRedos" and
|
||||
exists(RegExpTerm t, string pump, State s, string prefixMsg |
|
||||
hasReDoSResult(t, pump, s, prefixMsg) and
|
||||
not t.getRegex().getAMode() = "VERBOSE" and
|
||||
value = "" and
|
||||
location = t.getLocation() and
|
||||
element = t.toString()
|
||||
)
|
||||
or
|
||||
tag = "hasParseFailure" and
|
||||
exists(Regex r |
|
||||
r.failedToParse(_) and
|
||||
value = "" and
|
||||
location = r.getLocation() and
|
||||
element = r.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
1
java/ql/test/query-tests/security/CWE-730/options
Normal file
1
java/ql/test/query-tests/security/CWE-730/options
Normal file
@@ -0,0 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/guava-30.0
|
||||
53
java/ql/test/stubs/guava-30.0/com/google/common/base/CharMatcher.java
generated
Normal file
53
java/ql/test/stubs/guava-30.0/com/google/common/base/CharMatcher.java
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
// Generated automatically from com.google.common.base.CharMatcher for testing purposes
|
||||
|
||||
package com.google.common.base;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
abstract public class CharMatcher implements Predicate<Character>
|
||||
{
|
||||
protected CharMatcher(){}
|
||||
public CharMatcher and(CharMatcher p0){ return null; }
|
||||
public CharMatcher negate(){ return null; }
|
||||
public CharMatcher or(CharMatcher p0){ return null; }
|
||||
public CharMatcher precomputed(){ return null; }
|
||||
public String collapseFrom(CharSequence p0, char p1){ return null; }
|
||||
public String removeFrom(CharSequence p0){ return null; }
|
||||
public String replaceFrom(CharSequence p0, CharSequence p1){ return null; }
|
||||
public String replaceFrom(CharSequence p0, char p1){ return null; }
|
||||
public String retainFrom(CharSequence p0){ return null; }
|
||||
public String toString(){ return null; }
|
||||
public String trimAndCollapseFrom(CharSequence p0, char p1){ return null; }
|
||||
public String trimFrom(CharSequence p0){ return null; }
|
||||
public String trimLeadingFrom(CharSequence p0){ return null; }
|
||||
public String trimTrailingFrom(CharSequence p0){ return null; }
|
||||
public abstract boolean matches(char p0);
|
||||
public boolean apply(Character p0){ return false; }
|
||||
public boolean matchesAllOf(CharSequence p0){ return false; }
|
||||
public boolean matchesAnyOf(CharSequence p0){ return false; }
|
||||
public boolean matchesNoneOf(CharSequence p0){ return false; }
|
||||
public int countIn(CharSequence p0){ return 0; }
|
||||
public int indexIn(CharSequence p0){ return 0; }
|
||||
public int indexIn(CharSequence p0, int p1){ return 0; }
|
||||
public int lastIndexIn(CharSequence p0){ return 0; }
|
||||
public static CharMatcher any(){ return null; }
|
||||
public static CharMatcher anyOf(CharSequence p0){ return null; }
|
||||
public static CharMatcher ascii(){ return null; }
|
||||
public static CharMatcher breakingWhitespace(){ return null; }
|
||||
public static CharMatcher digit(){ return null; }
|
||||
public static CharMatcher forPredicate(Predicate<? super Character> p0){ return null; }
|
||||
public static CharMatcher inRange(char p0, char p1){ return null; }
|
||||
public static CharMatcher invisible(){ return null; }
|
||||
public static CharMatcher is(char p0){ return null; }
|
||||
public static CharMatcher isNot(char p0){ return null; }
|
||||
public static CharMatcher javaDigit(){ return null; }
|
||||
public static CharMatcher javaIsoControl(){ return null; }
|
||||
public static CharMatcher javaLetter(){ return null; }
|
||||
public static CharMatcher javaLetterOrDigit(){ return null; }
|
||||
public static CharMatcher javaLowerCase(){ return null; }
|
||||
public static CharMatcher javaUpperCase(){ return null; }
|
||||
public static CharMatcher none(){ return null; }
|
||||
public static CharMatcher noneOf(CharSequence p0){ return null; }
|
||||
public static CharMatcher singleWidth(){ return null; }
|
||||
public static CharMatcher whitespace(){ return null; }
|
||||
}
|
||||
@@ -1,48 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Guava Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
// Generated automatically from com.google.common.base.Splitter for testing purposes
|
||||
|
||||
package com.google.common.base;
|
||||
|
||||
import java.util.Iterator;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class Splitter {
|
||||
|
||||
public static Splitter on(final String separator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Splitter omitEmptyStrings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterable<String> split(final CharSequence sequence) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<String> splitToList(CharSequence sequence) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public MapSplitter withKeyValueSeparator(String separator) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class MapSplitter {
|
||||
public Map<String, String> split(CharSequence sequence) {
|
||||
return null;
|
||||
public class Splitter
|
||||
{
|
||||
protected Splitter() {}
|
||||
public Iterable<String> split(CharSequence p0){ return null; }
|
||||
public List<String> splitToList(CharSequence p0){ return null; }
|
||||
public Splitter limit(int p0){ return null; }
|
||||
public Splitter omitEmptyStrings(){ return null; }
|
||||
public Splitter trimResults(){ return null; }
|
||||
public Splitter trimResults(CharMatcher p0){ return null; }
|
||||
public Splitter.MapSplitter withKeyValueSeparator(Splitter p0){ return null; }
|
||||
public Splitter.MapSplitter withKeyValueSeparator(String p0){ return null; }
|
||||
public Splitter.MapSplitter withKeyValueSeparator(char p0){ return null; }
|
||||
public Stream<String> splitToStream(CharSequence p0){ return null; }
|
||||
public static Splitter fixedLength(int p0){ return null; }
|
||||
public static Splitter on(CharMatcher p0){ return null; }
|
||||
public static Splitter on(Pattern p0){ return null; }
|
||||
public static Splitter on(String p0){ return null; }
|
||||
public static Splitter on(char p0){ return null; }
|
||||
public static Splitter onPattern(String p0){ return null; }
|
||||
static public class MapSplitter
|
||||
{
|
||||
protected MapSplitter() {}
|
||||
public Map<String, String> split(CharSequence p0){ return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
252
javascript/ql/lib/semmle/javascript/Actions.qll
Normal file
252
javascript/ql/lib/semmle/javascript/Actions.qll
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Libraries for modeling GitHub Actions workflow files written in YAML.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Libraries for modeling GitHub Actions workflow files written in YAML.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
module Actions {
|
||||
/** A YAML node in a GitHub Actions workflow file. */
|
||||
private class Node extends YAMLNode {
|
||||
Node() {
|
||||
this.getLocation()
|
||||
.getFile()
|
||||
.getRelativePath()
|
||||
.regexpMatch("(^|.*/)\\.github/workflows/.*\\.yml$")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
class Workflow extends Node, YAMLDocument, YAMLMapping {
|
||||
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
||||
YAMLMapping getJobs() { result = this.lookup("jobs") }
|
||||
|
||||
/** Gets the name of the workflow file. */
|
||||
string getFileName() { result = this.getFile().getBaseName() }
|
||||
|
||||
/** Gets the `on:` in this workflow. */
|
||||
On getOn() { result = this.lookup("on") }
|
||||
|
||||
/** Gets the job within this workflow with the given job ID. */
|
||||
Job getJob(string jobId) { result.getWorkflow() = this and result.getId() = jobId }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions On trigger within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
|
||||
*/
|
||||
class On extends YAMLNode, YAMLMappingLikeNode {
|
||||
Workflow workflow;
|
||||
|
||||
On() { workflow.lookup("on") = this }
|
||||
|
||||
/** Gets the workflow that this trigger is in. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions job within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
||||
*/
|
||||
class Job extends YAMLNode, YAMLMapping {
|
||||
string jobId;
|
||||
Workflow workflow;
|
||||
|
||||
Job() { this = workflow.getJobs().lookup(jobId) }
|
||||
|
||||
/**
|
||||
* Gets the ID of this job, as a string.
|
||||
* This is the job's key within the `jobs` mapping.
|
||||
*/
|
||||
string getId() { result = jobId }
|
||||
|
||||
/**
|
||||
* Gets the ID of this job, as a YAML scalar node.
|
||||
* This is the job's key within the `jobs` mapping.
|
||||
*/
|
||||
YAMLString getIdNode() { workflow.getJobs().maps(result, this) }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a string. */
|
||||
string getName() { result = this.getNameNode().getValue() }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
|
||||
YAMLString getNameNode() { result = this.lookup("name") }
|
||||
|
||||
/** Gets the step at the given index within this job. */
|
||||
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
||||
|
||||
/** Gets the sequence of `steps` within this job. */
|
||||
YAMLSequence getSteps() { result = this.lookup("steps") }
|
||||
|
||||
/** Gets the workflow this job belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
|
||||
/** Gets the value of the `if` field in this job, if any. */
|
||||
JobIf getIf() { result.getJob() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `if` within a job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
|
||||
*/
|
||||
class JobIf extends YAMLNode, YAMLScalar {
|
||||
Job job;
|
||||
|
||||
JobIf() { job.lookup("if") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Job getJob() { result = job }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step within an Actions job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
|
||||
*/
|
||||
class Step extends YAMLNode, YAMLMapping {
|
||||
int index;
|
||||
Job job;
|
||||
|
||||
Step() { this = job.getSteps().getElement(index) }
|
||||
|
||||
/** Gets the 0-based position of this step within the sequence of `steps`. */
|
||||
int getIndex() { result = index }
|
||||
|
||||
/** Gets the job this step belongs to. */
|
||||
Job getJob() { result = job }
|
||||
|
||||
/** Gets the value of the `uses` field in this step, if any. */
|
||||
Uses getUses() { result.getStep() = this }
|
||||
|
||||
/** Gets the value of the `run` field in this step, if any. */
|
||||
Run getRun() { result.getStep() = this }
|
||||
|
||||
/** Gets the value of the `if` field in this step, if any. */
|
||||
StepIf getIf() { result.getStep() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `if` within a step.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
|
||||
*/
|
||||
class StepIf extends YAMLNode, YAMLScalar {
|
||||
Step step;
|
||||
|
||||
StepIf() { step.lookup("if") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression that parses an `owner/repo@version` reference within a `uses` field in an Actions job step.
|
||||
* The capture groups are:
|
||||
* 1: The owner of the repository where the Action comes from, e.g. `actions` in `actions/checkout@v2`
|
||||
* 2: The name of the repository where the Action comes from, e.g. `checkout` in `actions/checkout@v2`.
|
||||
* 3: The version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`.
|
||||
*/
|
||||
private string usesParser() { result = "([^/]+)/([^/@]+)@(.+)" }
|
||||
|
||||
/**
|
||||
* A `uses` field within an Actions job step, which references an action as a reusable unit of code.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/checkout@v2
|
||||
* ```
|
||||
*
|
||||
* Does not handle local repository references, e.g. `.github/actions/action-name`.
|
||||
*/
|
||||
class Uses extends YAMLNode, YAMLScalar {
|
||||
Step step;
|
||||
|
||||
Uses() { step.lookup("uses") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
|
||||
/** Gets the owner and name of the repository where the Action comes from, e.g. `actions/checkout` in `actions/checkout@v2`. */
|
||||
string getGitHubRepository() {
|
||||
result =
|
||||
this.getValue().regexpCapture(usesParser(), 1) + "/" +
|
||||
this.getValue().regexpCapture(usesParser(), 2)
|
||||
}
|
||||
|
||||
/** Gets the version reference used when checking out the Action, e.g. `v2` in `actions/checkout@v2`. */
|
||||
string getVersion() { result = this.getValue().regexpCapture(usesParser(), 3) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with` field within an Actions job step, which references an action as a reusable unit of code.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* with:
|
||||
* arg1: 1
|
||||
* arg2: abc
|
||||
* ```
|
||||
*/
|
||||
class With extends YAMLNode, YAMLMapping {
|
||||
Step step;
|
||||
|
||||
With() { step.lookup("with") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ref:` field within an Actions `with:` specific to `actions/checkout` action.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/checkout@v2
|
||||
* with:
|
||||
* ref: ${{ github.event.pull_request.head.sha }}
|
||||
* ```
|
||||
*/
|
||||
class Ref extends YAMLNode, YAMLString {
|
||||
With with;
|
||||
|
||||
Ref() { with.lookup("ref") = this }
|
||||
|
||||
/** Gets the `with` field this field belongs to. */
|
||||
With getWith() { result = with }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
*/
|
||||
class Run extends YAMLNode, YAMLString {
|
||||
Step step;
|
||||
|
||||
Run() { step.lookup("run") = this }
|
||||
|
||||
/** Gets the step that executes this `run` command. */
|
||||
Step getStep() { result = step }
|
||||
|
||||
/**
|
||||
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this `run` command.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
|
||||
* Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
|
||||
* Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
|
||||
*/
|
||||
string getASimpleReferenceExpression() {
|
||||
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
|
||||
// not just the last (greedy match) or first (reluctant match).
|
||||
result =
|
||||
this.getValue()
|
||||
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\.\\-]+\\s*\\}\\}", _, _)
|
||||
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\.\\-]+)\\s*\\}\\}", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ File resolveMainModule(PackageJson pkg, int priority) {
|
||||
or
|
||||
result = tryExtensions(main.resolve(), "index", priority)
|
||||
or
|
||||
not exists(main.resolve()) and
|
||||
not main.resolve() instanceof File and
|
||||
exists(int n | n = main.getNumComponent() |
|
||||
result = tryExtensions(main.resolveUpTo(n - 1), getStem(main.getComponent(n - 1)), priority)
|
||||
)
|
||||
|
||||
@@ -441,3 +441,74 @@ class YAMLParseError extends @yaml_error, Error {
|
||||
|
||||
override string toString() { result = this.getMessage() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A YAML node that may contain sub-nodes that can be identified by a name.
|
||||
* I.e. a mapping, sequence, or scalar.
|
||||
*
|
||||
* Is used in e.g. GithHub Actions, which is quite flexible in parsing YAML.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* on: pull_request
|
||||
* ```
|
||||
* and
|
||||
* ```
|
||||
* on: [pull_request]
|
||||
* ```
|
||||
* and
|
||||
* ```
|
||||
* on:
|
||||
* pull_request:
|
||||
* ```
|
||||
*
|
||||
* are equivalent.
|
||||
*/
|
||||
class YAMLMappingLikeNode extends YAMLNode {
|
||||
YAMLMappingLikeNode() {
|
||||
this instanceof YAMLMapping
|
||||
or
|
||||
this instanceof YAMLSequence
|
||||
or
|
||||
this instanceof YAMLScalar
|
||||
}
|
||||
|
||||
/** Gets sub-name identified by `name`. */
|
||||
YAMLNode getNode(string name) {
|
||||
exists(YAMLMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.lookup(name)
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence, YAMLNode node |
|
||||
sequence = this and
|
||||
sequence.getAChildNode() = node and
|
||||
node.eval().toString() = name and
|
||||
result = node
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
scalar = this and
|
||||
scalar.getValue() = name and
|
||||
result = scalar
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of elements in this mapping or sequence. */
|
||||
int getElementCount() {
|
||||
exists(YAMLMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.getNumChild() / 2
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence |
|
||||
sequence = this and
|
||||
result = sequence.getNumChild()
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
scalar = this and
|
||||
result = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,16 +610,23 @@ State after(RegExpTerm t) {
|
||||
or
|
||||
exists(RegExpGroup grp | t = grp.getAChild() | result = after(grp))
|
||||
or
|
||||
exists(EffectivelyStar star | t = star.getAChild() | result = before(star))
|
||||
exists(EffectivelyStar star | t = star.getAChild() |
|
||||
not isPossessive(star) and
|
||||
result = before(star)
|
||||
)
|
||||
or
|
||||
exists(EffectivelyPlus plus | t = plus.getAChild() |
|
||||
result = before(plus) or
|
||||
not isPossessive(plus) and
|
||||
result = before(plus)
|
||||
or
|
||||
result = after(plus)
|
||||
)
|
||||
or
|
||||
exists(EffectivelyQuestion opt | t = opt.getAChild() | result = after(opt))
|
||||
or
|
||||
exists(RegExpRoot root | t = root | result = AcceptAnySuffix(root))
|
||||
exists(RegExpRoot root | t = root |
|
||||
if matchesAnySuffix(root) then result = AcceptAnySuffix(root) else result = Accept(root)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -690,7 +697,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) {
|
||||
lbl = Epsilon() and q2 = Accept(root)
|
||||
)
|
||||
or
|
||||
exists(RegExpRoot root | q1 = Match(root, 0) | lbl = Any() and q2 = q1)
|
||||
exists(RegExpRoot root | q1 = Match(root, 0) | matchesAnyPrefix(root) and lbl = Any() and q2 = q1)
|
||||
or
|
||||
exists(RegExpDollar dollar | q1 = before(dollar) |
|
||||
lbl = Epsilon() and q2 = Accept(getRoot(dollar))
|
||||
|
||||
@@ -12,6 +12,24 @@ predicate isEscapeClass(RegExpTerm term, string clazz) {
|
||||
exists(RegExpCharacterClassEscape escape | term = escape | escape.getValue() = clazz)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` is a possessive quantifier.
|
||||
* As javascript's regexes do not support possessive quantifiers, this never holds, but is used by the shared library.
|
||||
*/
|
||||
predicate isPossessive(RegExpQuantifier term) { none() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any leading prefix of the input it's matched against.
|
||||
* Not yet implemented for Javascript.
|
||||
*/
|
||||
predicate matchesAnyPrefix(RegExpTerm term) { any() }
|
||||
|
||||
/**
|
||||
* Holds if the regex that `term` is part of is used in a way that ignores any trailing suffix of the input it's matched against.
|
||||
* Not yet implemented for Javascript.
|
||||
*/
|
||||
predicate matchesAnySuffix(RegExpTerm term) { any() }
|
||||
|
||||
/**
|
||||
* Holds if the regular expression should not be considered.
|
||||
*
|
||||
|
||||
@@ -2,53 +2,47 @@
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
|
||||
Using user-controlled input in GitHub Actions may lead to
|
||||
code injection in contexts like <i>run:</i> or <i>script:</i>.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Code injection in GitHub Actions may allow an attacker to
|
||||
exfiltrate the temporary GitHub repository authorization token.
|
||||
The token might have write access to the repository, allowing an attacker
|
||||
to use the token to make changes to the repository.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
The best practice to avoid code injection vulnerabilities
|
||||
in GitHub workflows is to set the untrusted input value of the expression
|
||||
to an intermediate environment variable.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It is also recommended to limit the permissions of any tokens used
|
||||
by a workflow such as the the GITHUB_TOKEN.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example lets a user inject an arbitrary shell command:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/comment_issue_bad.yml" />
|
||||
|
||||
<p>
|
||||
|
||||
The following example uses shell syntax to read
|
||||
the environment variable and will prevent the attack:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/comment_issue_good.yml" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-untrusted-input">Keeping your GitHub Actions and workflows secure: Untrusted input</a>.</li>
|
||||
<li>GitHub Docs: <a href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions">Security hardening for GitHub Actions</a>.</li>
|
||||
<li>GitHub Docs: <a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token">Permissions for the GITHUB_TOKEN</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -3,7 +3,8 @@
|
||||
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
|
||||
* user to inject code into the GitHub action.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id js/actions/injection
|
||||
* @tags actions
|
||||
@@ -12,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.semmle.javascript.Actions
|
||||
import semmle.javascript.Actions
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledIssue(string context) {
|
||||
@@ -22,14 +23,18 @@ private predicate isExternalUserControlledIssue(string context) {
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledPullRequest(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b")
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b",
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
@@ -51,18 +56,20 @@ private predicate isExternalUserControlledGollum(string context) {
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledCommit(string context) {
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*head_ref\\b")
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*head_ref\\b"
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
@@ -73,7 +80,7 @@ private predicate isExternalUserControlledDiscussion(string context) {
|
||||
|
||||
from Actions::Run run, string context, Actions::On on
|
||||
where
|
||||
run.getAReferencedExpression() = context and
|
||||
run.getASimpleReferenceExpression() = context and
|
||||
run.getStep().getJob().getWorkflow().getOn() = on and
|
||||
(
|
||||
exists(on.getNode("issues")) and
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user