mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Merge remote-tracking branch 'origin/main' into smowton/admin/merge-rc317-into-main
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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added support for the `@tanstack/angular-query-experimental` package.
|
||||
* Improved support for the `@angular/common/http` package, detecting outgoing HTTP requests in more cases.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added support for the `@tanstack/vue-query` package.
|
||||
4
javascript/ql/lib/change-notes/2025-03-13-unescape.md
Normal file
4
javascript/ql/lib/change-notes/2025-03-13-unescape.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint-steps for `unescape()`.
|
||||
4
javascript/ql/lib/change-notes/2025-03-14-escape.md
Normal file
4
javascript/ql/lib/change-notes/2025-03-14-escape.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added additional flow step for `unescape()` and `escape()`.
|
||||
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"]
|
||||
11
javascript/ql/lib/ext/tanstack.model.yml
Normal file
11
javascript/ql/lib/ext/tanstack.model.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ["@tanstack/angular-query-experimental", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
|
||||
- ["@tanstack/angular-query", "Member[injectQuery]", "Argument[0].ReturnValue.Member[queryFn].ReturnValue", "ReturnValue.Member[data].Awaited", "value"]
|
||||
- ["@tanstack/vue-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
|
||||
- ["@tanstack/vue-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
|
||||
- ["@tanstack/react-query", "Member[useQueries]", "Argument[0].Member[queries].ArrayElement.Member[queryFn].ReturnValue.Awaited", "ReturnValue.ArrayElement.Member[data]", "value"]
|
||||
- ["@tanstack/react-query", "Member[useQuery]", "Argument[0].Member[queryFn].ReturnValue.Awaited", "ReturnValue.Member[data]", "value"]
|
||||
@@ -139,7 +139,6 @@ import semmle.javascript.frameworks.Webix
|
||||
import semmle.javascript.frameworks.WebSocket
|
||||
import semmle.javascript.frameworks.XmlParsers
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
import semmle.javascript.frameworks.Tanstack
|
||||
import semmle.javascript.linters.ESLint
|
||||
import semmle.javascript.linters.JSLint
|
||||
import semmle.javascript.linters.Linting
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import semmle.javascript.dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.sharedlib.SummaryTypeTracker as SummaryTypeTracker
|
||||
private import semmle.javascript.dataflow.internal.Contents::Private as ContentPrivate
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import internal.CachedStages
|
||||
|
||||
/**
|
||||
@@ -220,15 +224,53 @@ module API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing a member of this API component where the name of the member is
|
||||
* not known statically.
|
||||
* DEPRECATED. Use either `getArrayElement()` or `getAMember()` instead.
|
||||
*/
|
||||
deprecated Node getUnknownMember() { result = this.getArrayElement() }
|
||||
|
||||
/**
|
||||
* Gets an array element of unknown index.
|
||||
*/
|
||||
cached
|
||||
Node getUnknownMember() {
|
||||
Node getUnknownArrayElement() {
|
||||
Stages::ApiStage::ref() and
|
||||
result = this.getASuccessor(Label::unknownMember())
|
||||
result = this.getASuccessor(Label::content(ContentPrivate::MkArrayElementUnknown()))
|
||||
}
|
||||
|
||||
cached
|
||||
private Node getContentRaw(DataFlow::Content content) {
|
||||
Stages::ApiStage::ref() and
|
||||
result = this.getASuccessor(Label::content(content))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a representative for the `content` of this value.
|
||||
*
|
||||
* When possible, it is preferrable to use one of the specialized variants of this predicate, such as `getMember`.
|
||||
*/
|
||||
pragma[inline]
|
||||
Node getContent(DataFlow::Content content) {
|
||||
result = this.getContentRaw(content)
|
||||
or
|
||||
result = this.getMember(content.asPropertyName())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a representative for the `contents` of this value.
|
||||
*/
|
||||
bindingset[contents]
|
||||
pragma[inline_late]
|
||||
private Node getContents(DataFlow::ContentSet contents) {
|
||||
// We always use getAStoreContent when generating content edges, and we always use getAReadContent when querying the graph.
|
||||
result = this.getContent(contents.getAReadContent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node representing an arbitrary array element in the array represented by this node.
|
||||
*/
|
||||
cached
|
||||
Node getArrayElement() { result = this.getContents(DataFlow::ContentSet::arrayElement()) }
|
||||
|
||||
/**
|
||||
* Gets a node representing a member of this API component where the name of the member may
|
||||
* or may not be known statically.
|
||||
@@ -238,7 +280,7 @@ module API {
|
||||
Stages::ApiStage::ref() and
|
||||
result = this.getMember(_)
|
||||
or
|
||||
result = this.getUnknownMember()
|
||||
result = this.getUnknownArrayElement()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -790,6 +832,11 @@ module API {
|
||||
not DataFlow::PseudoProperties::isPseudoProperty(prop)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ContentSet contents |
|
||||
SummaryTypeTracker::basicStoreStep(rhs, pred.getALocalUse(), contents) and
|
||||
lbl = Label::content(contents.getAStoreContent())
|
||||
)
|
||||
or
|
||||
exists(DataFlow::FunctionNode fn |
|
||||
fn = pred and
|
||||
lbl = Label::return()
|
||||
@@ -982,6 +1029,11 @@ module API {
|
||||
// avoid generating member edges like "$arrayElement$"
|
||||
not DataFlow::PseudoProperties::isPseudoProperty(prop)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ContentSet contents |
|
||||
SummaryTypeTracker::basicLoadStep(pred.getALocalUse(), ref, contents) and
|
||||
lbl = Label::content(contents.getAStoreContent())
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
|
||||
@@ -1199,8 +1251,6 @@ module API {
|
||||
t = useStep(nd, promisified, boundArgs, prop, result)
|
||||
}
|
||||
|
||||
private import semmle.javascript.dataflow.internal.StepSummary
|
||||
|
||||
/**
|
||||
* Holds if `nd`, which is a use of an API-graph node, flows in zero or more potentially
|
||||
* inter-procedural steps to some intermediate node, and then from that intermediate node to
|
||||
@@ -1458,8 +1508,21 @@ module API {
|
||||
bindingset[result]
|
||||
LabelMember member(string m) { result.getProperty() = m }
|
||||
|
||||
/** Gets the `member` edge label for the unknown member. */
|
||||
LabelUnknownMember unknownMember() { any() }
|
||||
/** Gets the `content` edge label for content `c`. */
|
||||
LabelContent content(ContentPrivate::Content c) { result.getContent() = c }
|
||||
|
||||
/**
|
||||
* Gets the edge label for an unknown member.
|
||||
*
|
||||
* Currently this is represented the same way as an unknown array element, but this may
|
||||
* change in the future.
|
||||
*/
|
||||
ApiLabel unknownMember() { result = arrayElement() }
|
||||
|
||||
/**
|
||||
* Gets the edge label for an unknown array element.
|
||||
*/
|
||||
LabelContent arrayElement() { result.getContent().isUnknownArrayElement() }
|
||||
|
||||
/**
|
||||
* Gets a property name referred to by the given dynamic property access,
|
||||
@@ -1482,6 +1545,11 @@ module API {
|
||||
result = unique(string s | s = getAnIndirectPropName(ref))
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isEnumeratedPropName(DataFlow::Node node) {
|
||||
node.getAPredecessor*() instanceof EnumeratedPropName
|
||||
}
|
||||
|
||||
/** Gets the `member` edge label for the given property reference. */
|
||||
ApiLabel memberFromRef(DataFlow::PropRef pr) {
|
||||
exists(string pn | pn = pr.getPropertyName() or pn = getIndirectPropName(pr) |
|
||||
@@ -1493,7 +1561,9 @@ module API {
|
||||
or
|
||||
not exists(pr.getPropertyName()) and
|
||||
not exists(getIndirectPropName(pr)) and
|
||||
result = unknownMember()
|
||||
// Avoid assignments in an extend-like pattern
|
||||
not isEnumeratedPropName(pr.getPropertyNameExpr().flow()) and
|
||||
result = arrayElement()
|
||||
}
|
||||
|
||||
/** Gets the `instance` edge label. */
|
||||
@@ -1516,10 +1586,10 @@ module API {
|
||||
LabelForwardingFunction forwardingFunction() { any() }
|
||||
|
||||
/** Gets the `promised` edge label connecting a promise to its contained value. */
|
||||
LabelPromised promised() { any() }
|
||||
LabelContent promised() { result.getContent() = ContentPrivate::MkPromiseValue() }
|
||||
|
||||
/** Gets the `promisedError` edge label connecting a promise to its rejected value. */
|
||||
LabelPromisedError promisedError() { any() }
|
||||
LabelContent promisedError() { result.getContent() = ContentPrivate::MkPromiseError() }
|
||||
|
||||
/** Gets the label for an edge leading from a value `D` to any class that has `D` as a decorator. */
|
||||
LabelDecoratedClass decoratedClass() { any() }
|
||||
@@ -1542,18 +1612,12 @@ module API {
|
||||
exists(Impl::MkModuleImport(mod))
|
||||
} or
|
||||
MkLabelInstance() or
|
||||
MkLabelMember(string prop) {
|
||||
exports(_, prop, _) or
|
||||
exists(any(DataFlow::ClassNode c).getInstanceMethod(prop)) or
|
||||
prop = "exports" or
|
||||
prop = any(CanonicalName c).getName() or
|
||||
prop = any(DataFlow::PropRef p).getPropertyName() or
|
||||
exists(Impl::MkTypeUse(_, prop)) or
|
||||
exists(any(Module m).getAnExportedValue(prop)) or
|
||||
PreCallGraphStep::loadStep(_, _, prop) or
|
||||
PreCallGraphStep::storeStep(_, _, prop)
|
||||
MkLabelContent(DataFlow::Content content) or
|
||||
MkLabelMember(string name) {
|
||||
name instanceof PropertyName
|
||||
or
|
||||
exists(Impl::MkTypeUse(_, name))
|
||||
} or
|
||||
MkLabelUnknownMember() or
|
||||
MkLabelParameter(int i) {
|
||||
i =
|
||||
[0 .. max(int args |
|
||||
@@ -1564,8 +1628,6 @@ module API {
|
||||
} or
|
||||
MkLabelReceiver() or
|
||||
MkLabelReturn() or
|
||||
MkLabelPromised() or
|
||||
MkLabelPromisedError() or
|
||||
MkLabelDecoratedClass() or
|
||||
MkLabelDecoratedMember() or
|
||||
MkLabelDecoratedParameter() or
|
||||
@@ -1585,13 +1647,13 @@ module API {
|
||||
}
|
||||
|
||||
/** A label that gets a promised value. */
|
||||
class LabelPromised extends ApiLabel, MkLabelPromised {
|
||||
override string toString() { result = "getPromised()" }
|
||||
deprecated class LabelPromised extends ApiLabel {
|
||||
LabelPromised() { this = MkLabelContent(ContentPrivate::MkPromiseValue()) }
|
||||
}
|
||||
|
||||
/** A label that gets a rejected promise. */
|
||||
class LabelPromisedError extends ApiLabel, MkLabelPromisedError {
|
||||
override string toString() { result = "getPromisedError()" }
|
||||
deprecated class LabelPromisedError extends ApiLabel {
|
||||
LabelPromisedError() { this = MkLabelContent(ContentPrivate::MkPromiseError()) }
|
||||
}
|
||||
|
||||
/** A label that gets the return value of a function. */
|
||||
@@ -1617,9 +1679,39 @@ module API {
|
||||
override string toString() { result = "getInstance()" }
|
||||
}
|
||||
|
||||
/** A label for a content. */
|
||||
class LabelContent extends ApiLabel, MkLabelContent {
|
||||
private DataFlow::Content content;
|
||||
|
||||
LabelContent() {
|
||||
this = MkLabelContent(content) and
|
||||
// Property names are represented by LabelMember to ensure additional property
|
||||
// names from PreCallGraph step are included, as well as those from MkTypeUse.
|
||||
not content instanceof ContentPrivate::MkPropertyContent
|
||||
}
|
||||
|
||||
/** Gets the content associated with this label. */
|
||||
DataFlow::Content getContent() { result = content }
|
||||
|
||||
private string specialisedToString() {
|
||||
content instanceof ContentPrivate::MkPromiseValue and result = "getPromised()"
|
||||
or
|
||||
content instanceof ContentPrivate::MkPromiseError and result = "getPromisedError()"
|
||||
or
|
||||
content instanceof ContentPrivate::MkArrayElementUnknown and result = "getArrayElement()"
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = this.specialisedToString()
|
||||
or
|
||||
not exists(this.specialisedToString()) and
|
||||
result = "getContent(" + content + ")"
|
||||
}
|
||||
}
|
||||
|
||||
/** A label for the member named `prop`. */
|
||||
class LabelMember extends ApiLabel, MkLabelMember {
|
||||
string prop;
|
||||
private string prop;
|
||||
|
||||
LabelMember() { this = MkLabelMember(prop) }
|
||||
|
||||
@@ -1630,10 +1722,8 @@ module API {
|
||||
}
|
||||
|
||||
/** A label for a member with an unknown name. */
|
||||
class LabelUnknownMember extends ApiLabel, MkLabelUnknownMember {
|
||||
LabelUnknownMember() { this = MkLabelUnknownMember() }
|
||||
|
||||
override string toString() { result = "getUnknownMember()" }
|
||||
deprecated class LabelUnknownMember extends LabelContent {
|
||||
LabelUnknownMember() { this.getContent().isUnknownArrayElement() }
|
||||
}
|
||||
|
||||
/** A label for parameter `i`. */
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import javascript
|
||||
private import dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps
|
||||
|
||||
/**
|
||||
* A call to the `Promise` constructor, such as `new Promise((resolve, reject) => { ... })`.
|
||||
@@ -397,6 +398,17 @@ module PromiseFlow {
|
||||
value = call.getCallback(0).getExceptionalReturn() and
|
||||
obj = call
|
||||
)
|
||||
or
|
||||
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
|
||||
// ordinary return
|
||||
prop = valueProp() and
|
||||
value = f.getAReturn() and
|
||||
obj = f.getReturnNode()
|
||||
or
|
||||
// exceptional return
|
||||
prop = errorProp() and
|
||||
localExceptionStepWithAsyncFlag(value, obj, true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,30 +537,6 @@ private class PromiseTaintStep extends TaintTracking::LegacyTaintStep {
|
||||
* Defines flow steps for return on async functions.
|
||||
*/
|
||||
private module AsyncReturnSteps {
|
||||
private predicate valueProp = Promises::valueProp/0;
|
||||
|
||||
private predicate errorProp = Promises::errorProp/0;
|
||||
|
||||
private import semmle.javascript.dataflow.internal.FlowSteps
|
||||
|
||||
/**
|
||||
* A data-flow step for ordinary and exceptional returns from async functions.
|
||||
*/
|
||||
private class AsyncReturn extends LegacyPreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
|
||||
// ordinary return
|
||||
prop = valueProp() and
|
||||
pred = f.getAReturn() and
|
||||
succ = f.getReturnNode()
|
||||
or
|
||||
// exceptional return
|
||||
prop = errorProp() and
|
||||
localExceptionStepWithAsyncFlag(pred, succ, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow step for ordinary return from an async function in a taint configuration.
|
||||
*/
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
|
||||
@@ -494,7 +494,8 @@ module TaintTracking {
|
||||
succ = c and
|
||||
c =
|
||||
DataFlow::globalVarRef([
|
||||
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent"
|
||||
"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent", "unescape",
|
||||
"escape"
|
||||
]).getACall() and
|
||||
pred = c.getArgument(0)
|
||||
)
|
||||
|
||||
@@ -57,6 +57,16 @@ module Private {
|
||||
this = getAPreciseArrayIndex().toString()
|
||||
or
|
||||
isAccessPathTokenPresent("Member", this)
|
||||
or
|
||||
this = any(ImportSpecifier spec).getImportedName()
|
||||
or
|
||||
this = any(ExportSpecifier n).getExportedName()
|
||||
or
|
||||
this = any(ExportNamedDeclaration d).getAnExportedDecl().getName()
|
||||
or
|
||||
this = any(MemberDefinition m).getName()
|
||||
or
|
||||
this = ["exports", "default"]
|
||||
}
|
||||
|
||||
/** Gets the array index corresponding to this property name. */
|
||||
|
||||
@@ -372,10 +372,11 @@ class CastNode extends DataFlow::Node {
|
||||
cached
|
||||
newtype TDataFlowCallable =
|
||||
MkSourceCallable(StmtContainer container) or
|
||||
MkLibraryCallable(LibraryCallable callable)
|
||||
MkLibraryCallable(LibraryCallable callable) or
|
||||
MkFileCallable(File file)
|
||||
|
||||
/**
|
||||
* A callable entity. This is a wrapper around either a `StmtContainer` or a `LibraryCallable`.
|
||||
* A callable entity. This is a wrapper around either a `StmtContainer`, `LibraryCallable`, or `File`.
|
||||
*/
|
||||
class DataFlowCallable extends TDataFlowCallable {
|
||||
/** Gets a string representation of this callable. */
|
||||
@@ -383,14 +384,21 @@ class DataFlowCallable extends TDataFlowCallable {
|
||||
result = this.asSourceCallable().toString()
|
||||
or
|
||||
result = this.asLibraryCallable()
|
||||
or
|
||||
result = this.asFileCallable().toString()
|
||||
}
|
||||
|
||||
/** Gets the location of this callable, if it is present in the source code. */
|
||||
Location getLocation() { result = this.asSourceCallable().getLocation() }
|
||||
Location getLocation() {
|
||||
result = this.asSourceCallable().getLocation() or result = this.asFileCallable().getLocation()
|
||||
}
|
||||
|
||||
/** Gets the corresponding `StmtContainer` if this is a source callable. */
|
||||
StmtContainer asSourceCallable() { this = MkSourceCallable(result) }
|
||||
|
||||
/** Gets the corresponding `File` if this is a file representing a callable. */
|
||||
File asFileCallable() { this = MkFileCallable(result) }
|
||||
|
||||
/** Gets the corresponding `StmtContainer` if this is a source callable. */
|
||||
pragma[nomagic]
|
||||
StmtContainer asSourceCallableNotExterns() {
|
||||
@@ -537,6 +545,10 @@ DataFlowCallable nodeGetEnclosingCallable(Node node) {
|
||||
result.asLibraryCallable() = node.(FlowSummaryDefaultExceptionalReturn).getSummarizedCallable()
|
||||
or
|
||||
node = TGenericSynthesizedNode(_, _, result)
|
||||
or
|
||||
node instanceof DataFlow::HtmlAttributeNode and result.asFileCallable() = node.getFile()
|
||||
or
|
||||
node instanceof DataFlow::XmlAttributeNode and result.asFileCallable() = node.getFile()
|
||||
}
|
||||
|
||||
newtype TDataFlowType =
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -190,13 +190,16 @@ module Angular2 {
|
||||
result.hasUnderlyingType("@angular/common/http", "HttpClient")
|
||||
}
|
||||
|
||||
/** Gets a reference to an `HttpClient` object using the API graph. */
|
||||
API::Node httpClientApiNode() { result = API::Node::ofType("@angular/common/http", "HttpClient") }
|
||||
|
||||
private class AngularClientRequest extends ClientRequest::Range, DataFlow::MethodCallNode {
|
||||
int argumentOffset;
|
||||
|
||||
AngularClientRequest() {
|
||||
this = httpClient().getAMethodCall("request") and argumentOffset = 1
|
||||
this = httpClientApiNode().getMember("request").getACall() and argumentOffset = 1
|
||||
or
|
||||
this = httpClient().getAMethodCall() and
|
||||
this = httpClientApiNode().getAMember().getACall() and
|
||||
not this.getMethodName() = "request" and
|
||||
argumentOffset = 0
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ module D3 {
|
||||
or
|
||||
this = d3Selection().getMember("node").getReturn().asSource()
|
||||
or
|
||||
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource()
|
||||
this = d3Selection().getMember("nodes").getReturn().getArrayElement().asSource()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,7 @@ module Puppeteer {
|
||||
or
|
||||
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
|
||||
or
|
||||
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
|
||||
result = [browser(), context()].getMember("pages").getReturn().getPromised().getArrayElement()
|
||||
or
|
||||
result = target().getMember("page").getReturn().getPromised()
|
||||
}
|
||||
@@ -45,7 +45,7 @@ module Puppeteer {
|
||||
or
|
||||
result = [page(), browser()].getMember("target").getReturn()
|
||||
or
|
||||
result = context().getMember("targets").getReturn().getUnknownMember()
|
||||
result = context().getMember("targets").getReturn().getArrayElement()
|
||||
or
|
||||
result = target().getMember("opener").getReturn()
|
||||
}
|
||||
@@ -58,7 +58,7 @@ module Puppeteer {
|
||||
or
|
||||
result = [page(), target()].getMember("browserContext").getReturn()
|
||||
or
|
||||
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
|
||||
result = browser().getMember("browserContexts").getReturn().getArrayElement()
|
||||
or
|
||||
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
|
||||
or
|
||||
|
||||
@@ -221,7 +221,10 @@ private module Postgres {
|
||||
|
||||
/** Gets a value that is plugged into a raw placeholder variable, making it a sink for SQL injection. */
|
||||
private DataFlow::Node getARawValue() {
|
||||
result = this.getValues() and this.getARawParameterName() = "1" // Special case: if the argument is not an array or object, it's just plugged into $1
|
||||
result = this.getValues() and
|
||||
this.getARawParameterName() = "1" and // Special case: if the argument is not an array or object, it's just plugged into $1
|
||||
not result instanceof DataFlow::ArrayCreationNode and
|
||||
not result instanceof DataFlow::ObjectLiteralNode
|
||||
or
|
||||
exists(DataFlow::SourceNode values | values = this.getValues().getALocalSource() |
|
||||
result = values.getAPropertyWrite(this.getARawParameterName()).getRhs()
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Provides classes and predicates modeling the Tanstack/react-query library.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
/**
|
||||
* An additional flow step that propagates data from the return value of the query function,
|
||||
* defined in a useQuery call from the '@tanstack/react-query' module, to the 'data' property.
|
||||
*/
|
||||
private class TanstackStep extends DataFlow::AdditionalFlowStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(API::CallNode useQuery |
|
||||
useQuery = useQueryCall() and
|
||||
node1 = useQuery.getParameter(0).getMember("queryFn").getReturn().getPromised().asSink() and
|
||||
node2 = useQuery.getReturn().getMember("data").asSource()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a call node representing a useQuery invocation from the '@tanstack/react-query' module.
|
||||
*/
|
||||
private API::CallNode useQueryCall() {
|
||||
result = API::moduleImport("@tanstack/react-query").getMember("useQuery").getACall()
|
||||
}
|
||||
@@ -421,3 +421,22 @@ private module ClosureLibraryUri {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class QueryStringStringification extends DataFlow::SummarizedCallable {
|
||||
QueryStringStringification() { this = "query-string stringification" }
|
||||
|
||||
override DataFlow::InvokeNode getACall() {
|
||||
result =
|
||||
API::moduleImport(["querystring", "query-string", "querystringify", "qs"])
|
||||
.getMember("stringify")
|
||||
.getACall() or
|
||||
result = API::moduleImport("url-parse").getMember("qs").getMember("stringify").getACall() or
|
||||
result = API::moduleImport("parseqs").getMember("encode").getACall()
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
preservesValue = false and
|
||||
input = ["Argument[0]", "Argument[0].AnyMemberDeep"] and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,7 @@ module Vuex {
|
||||
storeName = this.getNamespace() + localName
|
||||
or
|
||||
// mapGetters(['foo', 'bar'])
|
||||
this.getLastParameter().getUnknownMember().getAValueReachingSink().getStringValue() =
|
||||
localName and
|
||||
this.getLastParameter().getArrayElement().getAValueReachingSink().getStringValue() = localName and
|
||||
storeName = this.getNamespace() + localName
|
||||
or
|
||||
// mapGetters({foo: 'bar'})
|
||||
|
||||
@@ -162,8 +162,8 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
|
||||
token.getName() = "Awaited" and
|
||||
result = node.getPromised()
|
||||
or
|
||||
token.getName() = "ArrayElement" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::arrayElement())
|
||||
token.getName() = ["ArrayElement", "Element"] and
|
||||
result = node.getArrayElement()
|
||||
or
|
||||
token.getName() = "Element" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::arrayLikeElement())
|
||||
@@ -172,11 +172,6 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
|
||||
token.getName() = "MapValue" and
|
||||
result = node.getMember(DataFlow::PseudoProperties::mapValueAll())
|
||||
or
|
||||
// Currently we need to include the "unknown member" for ArrayElement and Element since
|
||||
// API graphs do not use store/load steps for arrays
|
||||
token.getName() = ["ArrayElement", "Element"] and
|
||||
result = node.getUnknownMember()
|
||||
or
|
||||
token.getName() = "Parameter" and
|
||||
token.getAnArgument() = "this" and
|
||||
result = node.getReceiver()
|
||||
@@ -373,7 +368,7 @@ bindingset[pred]
|
||||
predicate apiGraphHasEdge(API::Node pred, string path, API::Node succ) {
|
||||
exists(string name | succ = pred.getMember(name) and path = "Member[" + name + "]")
|
||||
or
|
||||
succ = pred.getUnknownMember() and path = "AnyMember"
|
||||
succ = pred.getUnknownArrayElement() and path = "ArrayElement"
|
||||
or
|
||||
succ = pred.getInstance() and path = "Instance"
|
||||
or
|
||||
|
||||
@@ -297,13 +297,12 @@ module Stages {
|
||||
exists(
|
||||
API::moduleImport("foo")
|
||||
.getMember("bar")
|
||||
.getUnknownMember()
|
||||
.getArrayElement()
|
||||
.getAMember()
|
||||
.getAParameter()
|
||||
.getPromised()
|
||||
.getReturn()
|
||||
.getParameter(2)
|
||||
.getUnknownMember()
|
||||
.getInstance()
|
||||
.getReceiver()
|
||||
.getForwardingFunction()
|
||||
|
||||
@@ -96,7 +96,8 @@ class ArrayConstructorSummary extends SummarizedCallable {
|
||||
ArrayConstructorSummary() { this = "Array constructor" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = arrayConstructorRef().getAnInvocation()
|
||||
result = arrayConstructorRef().getAnInvocation() and
|
||||
result.getNumArgument() > 1
|
||||
}
|
||||
|
||||
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
|
||||
|
||||
@@ -179,7 +179,6 @@ module ExternalApiUsedWithUntrustedData {
|
||||
or
|
||||
exists(string member |
|
||||
node = base.getMember(member) and
|
||||
not node = base.getUnknownMember() and
|
||||
not isNumericString(member) and
|
||||
not (member = "default" and base = API::moduleImport(_)) and
|
||||
not member = "then" // use the 'promised' edges for .then callbacks
|
||||
@@ -189,10 +188,7 @@ module ExternalApiUsedWithUntrustedData {
|
||||
else result = basename + "['" + member.regexpReplaceAll("'", "\\'") + "']"
|
||||
)
|
||||
or
|
||||
(
|
||||
node = base.getUnknownMember() or
|
||||
node = base.getMember(any(string s | isNumericString(s)))
|
||||
) and
|
||||
node = base.getArrayElement() and
|
||||
result = basename + "[]"
|
||||
or
|
||||
// just collapse promises
|
||||
|
||||
@@ -20,7 +20,11 @@ module ServerSideUrlRedirectConfig implements DataFlow::ConfigSig {
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer
|
||||
or
|
||||
node = HostnameSanitizerGuard::getABarrierNode()
|
||||
}
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) { hostnameSanitizingPrefixEdge(node, _) }
|
||||
|
||||
@@ -69,10 +73,12 @@ deprecated class Configuration extends TaintTracking::Configuration {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. This is no longer used as a sanitizer guard.
|
||||
*
|
||||
* A call to a function called `isLocalUrl` or similar, which is
|
||||
* considered to sanitize a variable for purposes of URL redirection.
|
||||
*/
|
||||
class LocalUrlSanitizingGuard extends DataFlow::CallNode {
|
||||
deprecated class LocalUrlSanitizingGuard extends DataFlow::CallNode {
|
||||
LocalUrlSanitizingGuard() { this.getCalleeName().regexpMatch("(?i)(is_?)?local_?url") }
|
||||
|
||||
/** DEPRECATED. Use `blocksExpr` instead. */
|
||||
|
||||
@@ -892,7 +892,13 @@ module TaintedPath {
|
||||
TaintTracking::uriStep(node1, node2)
|
||||
or
|
||||
exists(DataFlow::CallNode decode |
|
||||
decode.getCalleeName() = "decodeURIComponent" or decode.getCalleeName() = "decodeURI"
|
||||
decode =
|
||||
DataFlow::globalVarRef([
|
||||
"decodeURIComponent",
|
||||
"decodeURI",
|
||||
"escape",
|
||||
"unescape"
|
||||
]).getACall()
|
||||
|
|
||||
node1 = decode.getArgument(0) and
|
||||
node2 = decode
|
||||
|
||||
@@ -53,7 +53,7 @@ module Shared {
|
||||
class UriEncodingSanitizer extends Sanitizer, DataFlow::CallNode {
|
||||
UriEncodingSanitizer() {
|
||||
exists(string name | this = DataFlow::globalVarRef(name).getACall() |
|
||||
name = "encodeURI" or name = "encodeURIComponent"
|
||||
name in ["encodeURI", "encodeURIComponent", "escape"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1194,6 +1194,18 @@
|
||||
<v>12</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@regexp_quoted_string</k>
|
||||
<v>12</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@regexp_intersection</k>
|
||||
<v>12</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@regexp_subtraction</k>
|
||||
<v>12</v>
|
||||
</e>
|
||||
<e>
|
||||
<k>@regexp_parse_error</k>
|
||||
<v>122</v>
|
||||
</e>
|
||||
|
||||
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
|
||||
@@ -4,14 +4,22 @@ private import codeql.util.test.InlineExpectationsTest
|
||||
module Impl implements InlineExpectationsTestSig {
|
||||
private import javascript
|
||||
|
||||
final private class LineCommentFinal = LineComment;
|
||||
final class ExpectationComment = ExpectationCommentImpl;
|
||||
|
||||
class ExpectationComment extends LineCommentFinal {
|
||||
string getContents() { result = this.getText() }
|
||||
class Location = JS::Location;
|
||||
|
||||
abstract private class ExpectationCommentImpl extends Locatable {
|
||||
abstract string getContents();
|
||||
|
||||
/** Gets this element's location. */
|
||||
Location getLocation() { result = super.getLocation() }
|
||||
}
|
||||
|
||||
class Location = JS::Location;
|
||||
private class JSComment extends ExpectationCommentImpl instanceof Comment {
|
||||
override string getContents() { result = super.getText() }
|
||||
}
|
||||
|
||||
private class HtmlComment extends ExpectationCommentImpl instanceof HTML::CommentNode {
|
||||
override string getContents() { result = super.getText() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user