Merge branch 'main' into js/test-suite

This commit is contained in:
Asger F
2025-03-11 13:17:08 +01:00
499 changed files with 16371 additions and 5390 deletions

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added support for TypeScript 5.8.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added support for the `react-relay` library.

View 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

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved the modeling of the `markdown-table` package to ensure it handles nested arrays properly.

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["markdown-table", "", "Argument[0].ArrayElement.ArrayElement", "ReturnValue", "taint"]

View 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"]

View File

@@ -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() }
}
/**

View File

@@ -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 = "?" }

View File

@@ -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.

View File

@@ -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" }
}

View File

@@ -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>

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -0,0 +1,2 @@
description: Add support for quoted string, intersection and subtraction
compatibility: backwards