mirror of
https://github.com/github/codeql.git
synced 2026-05-02 04:05:14 +02:00
Merge branch 'main' into js/test-suite
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added support for TypeScript 5.8.
|
||||
4
javascript/ql/lib/change-notes/2025-02-25-react-relay.md
Normal file
4
javascript/ql/lib/change-notes/2025-02-25-react-relay.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added support for the `react-relay` library.
|
||||
7
javascript/ql/lib/change-notes/2025-03-03-regex-v.md
Normal file
7
javascript/ql/lib/change-notes/2025-03-03-regex-v.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Extraction now supports regular expressions with the `v` flag, using the new operators:
|
||||
- Intersection `&&`
|
||||
- Subtraction `--`
|
||||
- `\q` quoted string
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved the modeling of the `markdown-table` package to ensure it handles nested arrays properly.
|
||||
6
javascript/ql/lib/ext/markdown-table.model.yml
Normal file
6
javascript/ql/lib/ext/markdown-table.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ["markdown-table", "", "Argument[0].ArrayElement.ArrayElement", "ReturnValue", "taint"]
|
||||
15
javascript/ql/lib/ext/react-relay-threat.model.yml
Normal file
15
javascript/ql/lib/ext/react-relay-threat.model.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: sourceModel
|
||||
data:
|
||||
- ["react-relay", "Member[useFragment].ReturnValue", "response"]
|
||||
- ["react-relay", "Member[useLazyLoadQuery].ReturnValue", "response"]
|
||||
- ["react-relay", "Member[usePreloadedQuery].ReturnValue", "response"]
|
||||
- ["react-relay", "Member[useClientQuery].ReturnValue", "response"]
|
||||
- ["react-relay", "Member[useRefetchableFragment].ReturnValue.Member[0]", "response"]
|
||||
- ["react-relay", "Member[usePaginationFragment].ReturnValue.Member[data]", "response"]
|
||||
- ["react-relay", "Member[useMutation].ReturnValue.Member[0].Argument[0].Member[onCompleted].Parameter[0]", "response"]
|
||||
- ["react-relay", "Member[useSubscription].Argument[0].Member[onNext].Parameter[0]", "response"]
|
||||
- ["react-relay", "Member[fetchQuery].ReturnValue.Member[subscribe].Argument[0].Member[next].Parameter[0]", "response"]
|
||||
- ["relay-runtime", "Member[readFragment].ReturnValue", "response"]
|
||||
@@ -140,22 +140,17 @@ module MembershipCandidate {
|
||||
EnumerationRegExp() {
|
||||
this.isRootTerm() and
|
||||
RegExp::isFullyAnchoredTerm(this) and
|
||||
exists(RegExpTerm child | this.getAChild*() = child |
|
||||
child instanceof RegExpSequence or
|
||||
child instanceof RegExpCaret or
|
||||
child instanceof RegExpDollar or
|
||||
child instanceof RegExpConstant or
|
||||
child instanceof RegExpAlt or
|
||||
child instanceof RegExpGroup
|
||||
) and
|
||||
// exclude "length matches" that match every string
|
||||
not this.getAChild*() instanceof RegExpDot
|
||||
not exists(RegExpTerm child | child.getRootTerm() = this |
|
||||
child instanceof RegExpDot or
|
||||
child instanceof RegExpCharacterClass or
|
||||
child instanceof RegExpUnicodePropertyEscape
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string matched by this regular expression.
|
||||
*/
|
||||
string getAMember() { result = this.getAChild*().getAMatchedString() }
|
||||
string getAMember() { result = any(RegExpTerm t | t.getRootTerm() = this).getAMatchedString() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -301,6 +301,51 @@ class RegExpAlt extends RegExpTerm, @regexp_alt {
|
||||
override string getAPrimaryQlClass() { result = "RegExpAlt" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An intersection term, that is, a term of the form `[[a]&&[ab]]`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* /[[abc]&&[bcd]]/v - which matches 'b' and 'c' only.
|
||||
* ```
|
||||
*/
|
||||
class RegExpIntersection extends RegExpTerm, @regexp_intersection {
|
||||
/** Gets an intersected term of this term. */
|
||||
RegExpTerm getAnElement() { result = this.getAChild() }
|
||||
|
||||
/** Gets the number of intersected terms of this term. */
|
||||
int getNumIntersectedTerm() { result = this.getNumChild() }
|
||||
|
||||
override predicate isNullable() { this.getAnElement().isNullable() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpIntersection" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A subtraction term, that is, a term of the form `[[a]--[ab]]`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* /[[abc]--[bc]]/v - which matches 'a' only.
|
||||
* ```
|
||||
*/
|
||||
class RegExpSubtraction extends RegExpTerm, @regexp_subtraction {
|
||||
/** Gets the minuend (left operand) of this subtraction. */
|
||||
RegExpTerm getFirstTerm() { result = this.getChild(0) }
|
||||
|
||||
/** Gets the number of subtractions terms of this term. */
|
||||
int getNumSubtractedTerm() { result = this.getNumChild() - 1 }
|
||||
|
||||
/** Gets a subtrahend (right operand) of this subtraction. */
|
||||
RegExpTerm getASubtractedTerm() { exists(int i | i > 0 and result = this.getChild(i)) }
|
||||
|
||||
override predicate isNullable() { none() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpSubtraction" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sequence term.
|
||||
*
|
||||
@@ -1142,6 +1187,28 @@ private class StringConcatRegExpPatternSource extends RegExpPatternSource {
|
||||
override RegExpTerm getRegExpTerm() { result = this.asExpr().(AddExpr).asRegExp() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A quoted string escape in a regular expression, using the `\q` syntax.
|
||||
* The only operation supported inside a quoted string is alternation, using `|`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* \q{foo}
|
||||
* \q{a|b|c}
|
||||
* ```
|
||||
*/
|
||||
class RegExpQuotedString extends RegExpTerm, @regexp_quoted_string {
|
||||
/** Gets the term representing the contents of this quoted string. */
|
||||
RegExpTerm getTerm() { result = this.getAChild() }
|
||||
|
||||
override predicate isNullable() { none() }
|
||||
|
||||
override string getAMatchedString() { result = this.getTerm().getAMatchedString() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "RegExpQuotedString" }
|
||||
}
|
||||
|
||||
module RegExp {
|
||||
/** Gets the string `"?"` used to represent a regular expression whose flags are unknown. */
|
||||
string unknownFlag() { result = "?" }
|
||||
|
||||
@@ -188,27 +188,35 @@ module Routing {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path prefix needed to reach this node from the given ancestor, that is, the concatenation
|
||||
* of all relative paths between this node and the ancestor.
|
||||
*
|
||||
* To restrict the size of the predicate, this is only available for the ancestors that are "fork" nodes,
|
||||
* that is, a node that has siblings (i.e. multiple children).
|
||||
*/
|
||||
private string getPathFromFork(Node fork) {
|
||||
private string getPathFromForkInternal(Node fork) {
|
||||
this.isFork() and
|
||||
this = fork and
|
||||
result = ""
|
||||
or
|
||||
exists(Node parent | parent = this.getParent() |
|
||||
not exists(parent.getRelativePath()) and
|
||||
result = parent.getPathFromFork(fork)
|
||||
result = parent.getPathFromForkInternal(fork)
|
||||
or
|
||||
result = parent.getPathFromFork(fork) + parent.getRelativePath() and
|
||||
result = parent.getPathFromForkInternal(fork) + parent.getRelativePath() and
|
||||
result.length() < 100
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path prefix needed to reach this node from the given ancestor, that is, the concatenation
|
||||
* of all relative paths between this node and the ancestor.
|
||||
*
|
||||
* To restrict the size of the predicate, this is only available for the ancestors that are "fork" nodes,
|
||||
* that is, a node that has siblings (i.e. multiple children).
|
||||
* And only a single (shortest) path is returned, even if there are multiple paths
|
||||
* leading to this node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private string getPathFromFork(Node fork) {
|
||||
result =
|
||||
min(string res | res = this.getPathFromForkInternal(fork) | res order by res.length(), res)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an HTTP method required to reach this node from the given ancestor, or `*` if any method
|
||||
* can be used.
|
||||
|
||||
@@ -773,6 +773,17 @@ class LocalTypeAccess extends @local_type_access, TypeAccess, Identifier, Lexica
|
||||
*/
|
||||
LocalTypeName getLocalTypeName() { result.getAnAccess() = this }
|
||||
|
||||
private TypeAliasDeclaration getAlias() {
|
||||
this.getLocalTypeName().getADeclaration() = result.getIdentifier()
|
||||
}
|
||||
|
||||
override TypeExpr getAnUnderlyingType() {
|
||||
result = this.getAlias().getDefinition().getAnUnderlyingType()
|
||||
or
|
||||
not exists(this.getAlias()) and
|
||||
result = this
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "LocalTypeAccess" }
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,19 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
|
||||
class Guard extends js::ControlFlowNode {
|
||||
Guard() { this = any(js::ConditionGuardNode g).getTest() }
|
||||
|
||||
predicate hasCfgNode(js::BasicBlock bb, int i) { this = bb.getNode(i) }
|
||||
/**
|
||||
* Holds if the control flow branching from `bb1` is dependent on this guard,
|
||||
* and that the edge from `bb1` to `bb2` corresponds to the evaluation of this
|
||||
* guard to `branch`.
|
||||
*/
|
||||
predicate controlsBranchEdge(js::BasicBlock bb1, js::BasicBlock bb2, boolean branch) {
|
||||
exists(js::ConditionGuardNode g |
|
||||
g.getTest() = this and
|
||||
bb1 = this.getBasicBlock() and
|
||||
bb2 = g.getBasicBlock() and
|
||||
branch = g.getOutcome()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
@@ -92,14 +104,6 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
|
||||
branch = g.getOutcome()
|
||||
)
|
||||
}
|
||||
|
||||
js::BasicBlock getAConditionalBasicBlockSuccessor(js::BasicBlock bb, boolean branch) {
|
||||
exists(js::ConditionGuardNode g |
|
||||
bb = g.getTest().getBasicBlock() and
|
||||
result = g.getBasicBlock() and
|
||||
branch = g.getOutcome()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import DataFlowIntegration<SsaDataflowInput>
|
||||
|
||||
@@ -46,19 +46,6 @@ module Markdown {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step for the `markdown-table` library.
|
||||
*/
|
||||
private class MarkdownTableStep extends MarkdownStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() |
|
||||
// TODO: needs a flow summary to ensure ArrayElement content is unfolded
|
||||
succ = call and
|
||||
pred = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint step for the `showdown` library.
|
||||
*/
|
||||
|
||||
@@ -859,7 +859,10 @@ case @regexpterm.kind of
|
||||
| 24 = @regexp_char_range
|
||||
| 25 = @regexp_positive_lookbehind
|
||||
| 26 = @regexp_negative_lookbehind
|
||||
| 27 = @regexp_unicode_property_escape;
|
||||
| 27 = @regexp_unicode_property_escape
|
||||
| 28 = @regexp_quoted_string
|
||||
| 29 = @regexp_intersection
|
||||
| 30 = @regexp_subtraction;
|
||||
|
||||
regexp_parse_errors (unique int id: @regexp_parse_error,
|
||||
int regexp: @regexpterm ref,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Add support for quoted string, intersection and subtraction
|
||||
compatibility: backwards
|
||||
Reference in New Issue
Block a user