mirror of
https://github.com/github/codeql.git
synced 2026-07-21 03:08:25 +02:00
Merge branch 'main' into codeql-ci/js/ml-powered-pack-release-0.3.3
This commit is contained in:
@@ -14,7 +14,7 @@ import DataFlow::PathGraph
|
||||
/**
|
||||
* Gets the name of an unescaped placeholder in a lodash template.
|
||||
*
|
||||
* For example, the string `<h1><%= title %></h1>` contains the placeholder `title`.
|
||||
* For example, the string `"<h1><%= title %></h1>"` contains the placeholder "title".
|
||||
*/
|
||||
bindingset[s]
|
||||
string getAPlaceholderInString(string s) {
|
||||
|
||||
@@ -175,7 +175,7 @@ predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
|
||||
or
|
||||
n instanceof CryptographicKey and reason instanceof CryptographicKeyReason
|
||||
or
|
||||
any(CryptographicOperation op).getInput().flow() = n and
|
||||
any(CryptographicOperation op).getInput() = n and
|
||||
reason instanceof CryptographicOperationFlowReason
|
||||
or
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
|
||||
@@ -144,9 +144,9 @@ private module AccessPaths {
|
||||
not param = base.getReceiver()
|
||||
|
|
||||
result = param and
|
||||
name = param.getAnImmediateUse().asExpr().(Parameter).getName()
|
||||
name = param.asSource().(DataFlow::ParameterNode).getName()
|
||||
or
|
||||
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
|
||||
param.asSource().asExpr() instanceof DestructuringPattern and
|
||||
result = param.getMember(name)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ module SinkEndpointFilter {
|
||||
result = "modeled database access"
|
||||
or
|
||||
// Remove calls to APIs that aren't relevant to NoSQL injection
|
||||
call.getReceiver().asExpr() instanceof HTTP::RequestExpr and
|
||||
call.getReceiver() instanceof HTTP::RequestNode and
|
||||
result = "receiver is a HTTP request expression"
|
||||
or
|
||||
call.getReceiver().asExpr() instanceof HTTP::ResponseExpr and
|
||||
call.getReceiver() instanceof HTTP::ResponseNode and
|
||||
result = "receiver is a HTTP response expression"
|
||||
)
|
||||
or
|
||||
@@ -115,7 +115,7 @@ predicate isBaseAdditionalFlowStep(
|
||||
inlbl = TaintedObject::label() and
|
||||
outlbl = TaintedObject::label() and
|
||||
exists(NoSql::Query query, DataFlow::SourceNode queryObj |
|
||||
queryObj.flowsToExpr(query) and
|
||||
queryObj.flowsTo(query) and
|
||||
queryObj.flowsTo(trg) and
|
||||
src = queryObj.getAPropertyWrite().getRhs()
|
||||
)
|
||||
|
||||
@@ -15,16 +15,16 @@ import extraction.ExtractEndpointData
|
||||
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
|
||||
query instanceof NosqlInjectionQuery and
|
||||
result = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof SqlInjectionQuery and
|
||||
result = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof TaintedPathQuery and
|
||||
result = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof XssQuery and
|
||||
result = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for a particular dataflow config.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import evaluation.EndToEndEvaluation
|
||||
|
||||
query predicate countAlertsAndSinks(int numAlerts, int numSinks) {
|
||||
numAlerts =
|
||||
count(DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink |
|
||||
cfg.hasFlow(source, sink) and not isFlowExcluded(source, sink)
|
||||
) and
|
||||
numSinks =
|
||||
count(DataFlow::Node sink |
|
||||
exists(DataFlow::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `CodeInjection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.CodeInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `NosqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `SqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `TaintedPath` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `DomBasedXss` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `XssThroughDom` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.XssThroughDomQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.NosqlInjection
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.SqlInjection
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.TaintedPath
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery as TaintedPath
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.DomBasedXss
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery as DomBasedXss
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -14,10 +14,26 @@ import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
|
||||
import experimental.adaptivethreatmodeling.EndpointScoring as EndpointScoring
|
||||
import experimental.adaptivethreatmodeling.EndpointTypes
|
||||
import experimental.adaptivethreatmodeling.FilteringReasons
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
|
||||
/** DEPRECATED: Alias for NosqlInjectionAtm */
|
||||
deprecated module NosqlInjectionATM = NosqlInjectionAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
|
||||
/** DEPRECATED: Alias for SqlInjectionAtm */
|
||||
deprecated module SqlInjectionATM = SqlInjectionAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
|
||||
/** DEPRECATED: Alias for TaintedPathAtm */
|
||||
deprecated module TaintedPathATM = TaintedPathAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
|
||||
/** DEPRECATED: Alias for XssAtm */
|
||||
deprecated module XssATM = XssAtm;
|
||||
|
||||
import Labels
|
||||
import NoFeaturizationRestrictionsConfig
|
||||
import Queries
|
||||
@@ -25,13 +41,13 @@ import Queries
|
||||
/** Gets the ATM configuration object for the specified query. */
|
||||
AtmConfig getAtmCfg(Query query) {
|
||||
query instanceof NosqlInjectionQuery and
|
||||
result instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
|
||||
result instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
|
||||
or
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::SqlInjectionAtmConfig
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::SqlInjectionAtmConfig
|
||||
or
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathAtmConfig
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::TaintedPathAtmConfig
|
||||
or
|
||||
query instanceof XssQuery and result instanceof XssATM::DomBasedXssAtmConfig
|
||||
query instanceof XssQuery and result instanceof XssAtm::DomBasedXssAtmConfig
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getAtmCfg */
|
||||
@@ -39,13 +55,13 @@ deprecated ATMConfig getATMCfg(Query query) { result = getAtmCfg(query) }
|
||||
|
||||
/** Gets the ATM data flow configuration for the specified query. */
|
||||
DataFlow::Configuration getDataFlowCfg(Query query) {
|
||||
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionATM::Configuration
|
||||
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionAtm::Configuration
|
||||
or
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::Configuration
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::Configuration
|
||||
or
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::Configuration
|
||||
or
|
||||
query instanceof XssQuery and result instanceof XssATM::Configuration
|
||||
query instanceof XssQuery and result instanceof XssAtm::Configuration
|
||||
}
|
||||
|
||||
/** Gets a known sink for the specified query. */
|
||||
@@ -188,10 +204,10 @@ module FlowFromSource {
|
||||
|
||||
Query getQuery() { result = q }
|
||||
|
||||
/** The sinks are the endpoints we're extracting. */
|
||||
/** Holds if `sink` is an endpoint we're extracting. */
|
||||
override predicate isSink(DataFlow::Node sink) { sink = getAnEndpoint(q) }
|
||||
|
||||
/** The sinks are the endpoints we're extracting. */
|
||||
/** Holds if `sink` is an endpoint we're extracting. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
|
||||
sink = getAnEndpoint(q) and exists(lbl)
|
||||
}
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
* Maps ML-powered queries to their `EndpointType` for clearer labelling while evaluating ML model during training.
|
||||
*/
|
||||
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
|
||||
|
||||
from string queryName, AtmConfig c, EndpointType e
|
||||
where
|
||||
(
|
||||
queryName = "SqlInjection" and
|
||||
c instanceof SqlInjectionATM::SqlInjectionAtmConfig
|
||||
c instanceof SqlInjectionAtm::SqlInjectionAtmConfig
|
||||
or
|
||||
queryName = "NosqlInjection" and
|
||||
c instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
|
||||
c instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
|
||||
or
|
||||
queryName = "TaintedPath" and
|
||||
c instanceof TaintedPathATM::TaintedPathAtmConfig
|
||||
c instanceof TaintedPathAtm::TaintedPathAtmConfig
|
||||
or
|
||||
queryName = "Xss" and c instanceof XssATM::DomBasedXssAtmConfig
|
||||
queryName = "Xss" and c instanceof XssAtm::DomBasedXssAtmConfig
|
||||
) and
|
||||
e = c.getASinkEndpointType()
|
||||
select queryName, e.getEncoding() as label
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
|
||||
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
|
||||
import extraction.NoFeaturizationRestrictionsConfig
|
||||
|
||||
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
|
||||
(
|
||||
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
StandardEndpointFilters::isArgumentToModeledFunction(endpoint)
|
||||
) and
|
||||
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* This test checks several components of the endpoint filters for each query to see whether they
|
||||
* filter out any known sinks. It explicitly does not check the endpoint filtering step that's based
|
||||
* on whether the endpoint is an argument to a modelled function, since this necessarily filters out
|
||||
* on whether the endpoint is an argument to a modeled function, since this necessarily filters out
|
||||
* all known sinks. However, we can test all the other filtering steps against the set of known
|
||||
* sinks.
|
||||
*
|
||||
@@ -17,31 +17,31 @@ import semmle.javascript.security.dataflow.SqlInjectionCustomizations
|
||||
import semmle.javascript.security.dataflow.TaintedPathCustomizations
|
||||
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
|
||||
query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof NosqlInjection::Sink and
|
||||
reason = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
not reason = ["argument to modeled function", "modeled sink", "modeled database access"]
|
||||
}
|
||||
|
||||
query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof SqlInjection::Sink and
|
||||
reason = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof TaintedPath::Sink and
|
||||
reason = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof DomBasedXss::Sink and
|
||||
reason = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import javascript
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
|
||||
query predicate effectiveSinks(DataFlow::Node node) {
|
||||
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(node))
|
||||
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(node))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,50 @@
|
||||
## 0.2.4
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
|
||||
The previous files still exist as deprecated aliases.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
## 0.2.2
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `chownr` library is now modeled as a sink for the `js/path-injection` query.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
* The `gray-matter` library is now modeled as a sink for the `js/code-injection` query.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 4.7.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* All new ECMAScript 2022 features are now supported.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
@@ -37,6 +37,8 @@ predicate inVoidContext(Expr e) {
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
or
|
||||
exists(ConditionalExpr cond | e = cond.getABranch() | inVoidContext(cond))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
57
javascript/ql/lib/change-notes/2022-04-04-dataflow-models.md
Normal file
57
javascript/ql/lib/change-notes/2022-04-04-dataflow-models.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* Many library models have been rewritten to use dataflow nodes instead of the AST.
|
||||
The types of some classes have been changed, and these changes may break existing code.
|
||||
Other classes and predicates have been renamed, in these cases the old name is still available as a deprecated feature.
|
||||
|
||||
* The basetype of the following list of classes has changed from an expression to a dataflow node, and thus code using these classes might break.
|
||||
The fix to these breakages is usually to use `asExpr()` to get an expression from a dataflow node, or to use `.flow()` to get a dataflow node from an expression.
|
||||
- DOM.qll#WebStorageWrite
|
||||
- CryptoLibraries.qll#CryptographicOperation
|
||||
- Express.qll#Express::RequestBodyAccess
|
||||
- HTTP.qll#HTTP::ResponseBody
|
||||
- HTTP.qll#HTTP::CookieDefinition
|
||||
- HTTP.qll#HTTP::ServerDefinition
|
||||
- HTTP.qll#HTTP::RouteSetup
|
||||
- NoSQL.qll#NoSql::Query
|
||||
- SQL.qll#SQL::SqlString
|
||||
- SQL.qll#SQL::SqlSanitizer
|
||||
- HTTP.qll#ResponseBody
|
||||
- HTTP.qll#CookieDefinition
|
||||
- HTTP.qll#ServerDefinition
|
||||
- HTTP.qll#RouteSetup
|
||||
- HTTP.qll#HTTP::RedirectInvocation
|
||||
- HTTP.qll#RedirectInvocation
|
||||
- Express.qll#Express::RouterDefinition
|
||||
- AngularJSCore.qll#LinkFunction
|
||||
- Connect.qll#Connect::StandardRouteHandler
|
||||
- CryptoLibraries.qll#CryptographicKeyCredentialsExpr
|
||||
- AWS.qll#AWS::Credentials
|
||||
- Azure.qll#Azure::Credentials
|
||||
- Connect.qll#Connect::Credentials
|
||||
- DigitalOcean.qll#DigitalOcean::Credentials
|
||||
- Express.qll#Express::Credentials
|
||||
- NodeJSLib.qll#NodeJSLib::Credentials
|
||||
- PkgCloud.qll#PkgCloud::Credentials
|
||||
- Request.qll#Request::Credentials
|
||||
- ServiceDefinitions.qll#InjectableFunctionServiceRequest
|
||||
- SensitiveActions.qll#SensitiveVariableAccess
|
||||
- SensitiveActions.qll#CleartextPasswordExpr
|
||||
- Connect.qll#Connect::ServerDefinition
|
||||
- Restify.qll#Restify::ServerDefinition
|
||||
- Connect.qll#Connect::RouteSetup
|
||||
- Express.qll#Express::RouteSetup
|
||||
- Fastify.qll#Fastify::RouteSetup
|
||||
- Hapi.qll#Hapi::RouteSetup
|
||||
- Koa.qll#Koa::RouteSetup
|
||||
- Restify.qll#Restify::RouteSetup
|
||||
- NodeJSLib.qll#NodeJSLib::RouteSetup
|
||||
- Express.qll#Express::StandardRouteHandler
|
||||
- Express.qll#Express::SetCookie
|
||||
- Hapi.qll#Hapi::RouteHandler
|
||||
- HTTP.qll#HTTP::Servers::StandardHeaderDefinition
|
||||
- HTTP.qll#Servers::StandardHeaderDefinition
|
||||
- Hapi.qll#Hapi::ServerDefinition
|
||||
- Koa.qll#Koa::AppDefinition
|
||||
- SensitiveActions.qll#SensitiveCall
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added support for TypeScript 4.8.
|
||||
4
javascript/ql/lib/change-notes/2022-08-09-mermaid.md
Normal file
4
javascript/ql/lib/change-notes/2022-08-09-mermaid.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* A model for the `mermaid` library has been added. XSS queries can now detect flow through the `render` method of the `mermaid` library.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.4
|
||||
9
javascript/ql/lib/change-notes/released/0.2.0.md
Normal file
9
javascript/ql/lib/change-notes/released/0.2.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 0.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 4.7.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* All new ECMAScript 2022 features are now supported.
|
||||
7
javascript/ql/lib/change-notes/released/0.2.1.md
Normal file
7
javascript/ql/lib/change-notes/released/0.2.1.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## 0.2.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `chownr` library is now modeled as a sink for the `js/path-injection` query.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
* The `gray-matter` library is now modeled as a sink for the `js/code-injection` query.
|
||||
1
javascript/ql/lib/change-notes/released/0.2.2.md
Normal file
1
javascript/ql/lib/change-notes/released/0.2.2.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.2.2
|
||||
1
javascript/ql/lib/change-notes/released/0.2.3.md
Normal file
1
javascript/ql/lib/change-notes/released/0.2.3.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.2.3
|
||||
16
javascript/ql/lib/change-notes/released/0.2.4.md
Normal file
16
javascript/ql/lib/change-notes/released/0.2.4.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 0.2.4
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
|
||||
The previous files still exist as deprecated aliases.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.2
|
||||
lastReleaseVersion: 0.2.4
|
||||
|
||||
@@ -45,7 +45,7 @@ private predicate variableDefLookup(VarAccess va, AstNode def, string kind) {
|
||||
|
||||
/**
|
||||
* Holds if variable access `va` is of kind `kind` and refers to the
|
||||
* variable declaration.
|
||||
* variable declaration `decl`.
|
||||
*
|
||||
* For example, in the statement `var x = 42, y = x;`, the initializing
|
||||
* expression of `y` is a variable access `x` of kind `"V"` that refers to
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.1.3-dev
|
||||
version: 0.2.5-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import Expressions.ExprHasNoEffect
|
||||
|
||||
/**
|
||||
* An AMD `define` call.
|
||||
@@ -26,7 +27,7 @@ private import semmle.javascript.internal.CachedStages
|
||||
*/
|
||||
class AmdModuleDefinition extends CallExpr {
|
||||
AmdModuleDefinition() {
|
||||
getParent() instanceof ExprStmt and
|
||||
inVoidContext(this) and
|
||||
getCallee().(GlobalVarAccess).getName() = "define" and
|
||||
exists(int n | n = getNumArgument() |
|
||||
n = 1
|
||||
@@ -202,13 +203,22 @@ private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString
|
||||
override string getValue() { result = getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nd` is nested inside an AMD module definition.
|
||||
*/
|
||||
private predicate inAmdModuleDefinition(AstNode nd) {
|
||||
nd.getParent() instanceof AmdModuleDefinition
|
||||
or
|
||||
inAmdModuleDefinition(nd.getParent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is an AMD module definition in `tl` which is not
|
||||
* nested inside another module definition.
|
||||
*/
|
||||
private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
|
||||
def.getTopLevel() = tl and
|
||||
not def.getParent+() instanceof AmdModuleDefinition
|
||||
not inAmdModuleDefinition(def)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import javascript
|
||||
*/
|
||||
module Actions {
|
||||
/** A YAML node in a GitHub Actions workflow file. */
|
||||
private class Node extends YAMLNode {
|
||||
private class Node extends YamlNode {
|
||||
Node() {
|
||||
this.getLocation()
|
||||
.getFile()
|
||||
@@ -24,9 +24,12 @@ module Actions {
|
||||
* 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 {
|
||||
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") }
|
||||
YamlMapping getJobs() { result = this.lookup("jobs") }
|
||||
|
||||
/** Gets the name of the workflow. */
|
||||
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
||||
|
||||
/** Gets the name of the workflow file. */
|
||||
string getFileName() { result = this.getFile().getBaseName() }
|
||||
@@ -42,7 +45,7 @@ module Actions {
|
||||
* 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 {
|
||||
class On extends YamlNode, YamlMappingLikeNode {
|
||||
Workflow workflow;
|
||||
|
||||
On() { workflow.lookup("on") = this }
|
||||
@@ -55,7 +58,7 @@ module Actions {
|
||||
* 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 {
|
||||
class Job extends YamlNode, YamlMapping {
|
||||
string jobId;
|
||||
Workflow workflow;
|
||||
|
||||
@@ -71,19 +74,19 @@ module Actions {
|
||||
* 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) }
|
||||
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") }
|
||||
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") }
|
||||
YamlSequence getSteps() { result = this.lookup("steps") }
|
||||
|
||||
/** Gets the workflow this job belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
@@ -96,7 +99,7 @@ module Actions {
|
||||
* 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 {
|
||||
class JobIf extends YamlNode, YamlScalar {
|
||||
Job job;
|
||||
|
||||
JobIf() { job.lookup("if") = this }
|
||||
@@ -109,7 +112,7 @@ module Actions {
|
||||
* 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 {
|
||||
class Step extends YamlNode, YamlMapping {
|
||||
int index;
|
||||
Job job;
|
||||
|
||||
@@ -129,13 +132,16 @@ module Actions {
|
||||
|
||||
/** Gets the value of the `if` field in this step, if any. */
|
||||
StepIf getIf() { result.getStep() = this }
|
||||
|
||||
/** Gets the ID of this step, if any. */
|
||||
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
class StepIf extends YamlNode, YamlScalar {
|
||||
Step step;
|
||||
|
||||
StepIf() { step.lookup("if") = this }
|
||||
@@ -164,7 +170,7 @@ module Actions {
|
||||
*
|
||||
* Does not handle local repository references, e.g. `.github/actions/action-name`.
|
||||
*/
|
||||
class Uses extends YAMLNode, YAMLScalar {
|
||||
class Uses extends YamlNode, YamlScalar {
|
||||
Step step;
|
||||
|
||||
Uses() { step.lookup("uses") = this }
|
||||
@@ -194,7 +200,7 @@ module Actions {
|
||||
* arg2: abc
|
||||
* ```
|
||||
*/
|
||||
class With extends YAMLNode, YAMLMapping {
|
||||
class With extends YamlNode, YamlMapping {
|
||||
Step step;
|
||||
|
||||
With() { step.lookup("with") = this }
|
||||
@@ -213,7 +219,7 @@ module Actions {
|
||||
* ref: ${{ github.event.pull_request.head.sha }}
|
||||
* ```
|
||||
*/
|
||||
class Ref extends YAMLNode, YAMLString {
|
||||
class Ref extends YamlNode, YamlString {
|
||||
With with;
|
||||
|
||||
Ref() { with.lookup("ref") = this }
|
||||
@@ -226,7 +232,7 @@ module Actions {
|
||||
* 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 {
|
||||
class Run extends YamlNode, YamlString {
|
||||
Step step;
|
||||
|
||||
Run() { step.lookup("run") = this }
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
* Provides an implementation of _API graphs_, which are an abstract representation of the API
|
||||
* surface used and/or defined by a code base.
|
||||
*
|
||||
* The nodes of the API graph represent definitions and uses of API components. The edges are
|
||||
* directed and labeled; they specify how the components represented by nodes relate to each other.
|
||||
* For example, if one of the nodes represents a definition of an API function, then there
|
||||
* will be nodes corresponding to the function's parameters, which are connected to the function
|
||||
* node by edges labeled `parameter <i>`.
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -14,50 +10,159 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import internal.CachedStages
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with APIs defined or used in a database.
|
||||
* Provides classes and predicates for working with the API boundary between the current
|
||||
* codebase and external libraries.
|
||||
*
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
module API {
|
||||
/**
|
||||
* An abstract representation of a definition or use of an API component such as a function
|
||||
* exported by an npm package, a parameter of such a function, or its result.
|
||||
* A node in the API graph, representing a value that has crossed the boundary between this
|
||||
* codebase and an external library (or in general, any external codebase).
|
||||
*
|
||||
* ### Basic usage
|
||||
*
|
||||
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
||||
* whose implementation is not necessarily part of the current codebase.
|
||||
*
|
||||
* The most basic use of API graphs is typically as follows:
|
||||
* 1. Start with `API::moduleImport` for the relevant library.
|
||||
* 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function.
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get arguments to `underscore.extend` would be
|
||||
* ```ql
|
||||
* API::moduleImport("underscore").getMember("extend").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
* The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`.
|
||||
*
|
||||
* ### API graph nodes
|
||||
*
|
||||
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
||||
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
||||
* (The current codebase is "using" a value that came from the library).
|
||||
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
||||
* (The current codebase "defines" the value seen by the library).
|
||||
*
|
||||
* API graph nodes are associated with data-flow nodes in the current codebase.
|
||||
* (Since external libraries are not part of the database, there is no way to associate with concrete
|
||||
* data-flow nodes from the external library).
|
||||
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
||||
* such as the return value of a call to an external function.
|
||||
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
||||
* such as an argument passed in a call to an external function.
|
||||
*
|
||||
*
|
||||
* ### Access paths and edge labels
|
||||
*
|
||||
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
||||
* that may be performed to obtain that value.
|
||||
*
|
||||
* For example, the access path `API::moduleImport("lodash").getMember("extend")` represents the action of
|
||||
* importing `lodash` and then accessing the member `extend` on the resulting object.
|
||||
* It would be associated with an expression such as `require("lodash").extend`.
|
||||
*
|
||||
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
||||
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
||||
* the result:
|
||||
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
||||
* came from a library.
|
||||
* - An edge starting from a def-node describes what the external library might do to a value that
|
||||
* came from the current codebase.
|
||||
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
||||
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
||||
* the place where it was "last seen" in the current codebase before flowing out)
|
||||
*
|
||||
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
||||
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
||||
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
||||
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
||||
*
|
||||
* For example, given this snippet:
|
||||
* ```js
|
||||
* require('foo')(x => { doSomething(x) })
|
||||
* ```
|
||||
* A callback is passed to the external function `foo`. We can't know if `foo` will actually invoke this callback.
|
||||
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
||||
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo`.
|
||||
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`.
|
||||
*
|
||||
* ### Thinking in operations versus code patterns
|
||||
*
|
||||
* Treating edges as "operations" helps avoid a pitfall in which library models become overly specific to certain code patterns.
|
||||
* Consider the following two equivalent calls to `foo`:
|
||||
* ```js
|
||||
* const foo = require('foo');
|
||||
*
|
||||
* foo({
|
||||
* myMethod(x) {...}
|
||||
* });
|
||||
*
|
||||
* foo({
|
||||
* get myMethod() {
|
||||
* return function(x) {...}
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
* If `foo` calls `myMethod` on its first parameter, either of the `myMethod` implementations will be invoked.
|
||||
* And indeed, the access path `API::moduleImport("foo").getParameter(0).getMember("myMethod").getParameter(0)` correctly
|
||||
* identifies both `x` parameters.
|
||||
*
|
||||
* Observe how `getMember("myMethod")` behaves when the member is defined via a getter. When thinking in code patterns,
|
||||
* it might seem obvious that `getMember` should have obtained a reference to the getter method itself.
|
||||
* But when seeing it as an access to `myMethod` performed by the library, we can deduce that the relevant expression
|
||||
* on the client side is actually the return-value of the getter.
|
||||
*
|
||||
* Although one may think of API graphs as a tool to find certain program elements in the codebase,
|
||||
* it can lead to some situations where intuition does not match what works best in practice.
|
||||
*/
|
||||
class Node extends Impl::TApiNode {
|
||||
/**
|
||||
* Gets a data-flow node corresponding to a use of the API component represented by this node.
|
||||
* Get a data-flow node where this value may flow after entering the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a use of the function `readFileSync` from the
|
||||
* `fs` module, and `require('fs').readFileSync(file)` is a use of the return of that function.
|
||||
*
|
||||
* This includes indirect uses found via data flow, meaning that in
|
||||
* `f(obj.foo); function f(x) {};` both `obj.foo` and `x` are uses of the `foo` member from `obj`.
|
||||
*
|
||||
* As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to
|
||||
* `x` are uses of the first parameter of `plusOne`.
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAUse() {
|
||||
exists(DataFlow::SourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
)
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
Impl::trackUseNode(this.asSource()).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
* Get a data-flow node where this value enters the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a an immediate use of the `readFileSync` member
|
||||
* from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // API::moduleImport("fs").asSource()
|
||||
* require('fs');
|
||||
*
|
||||
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
|
||||
* found via data flow. This means that in `const x = fs.readFile` only `fs.readFile` is a reference
|
||||
* to the `readFile` member of `fs`, neither `x` nor any node that `x` flows to is a reference to
|
||||
* this API component.
|
||||
* // API::moduleImport("fs").getMember("readFile").asSource()
|
||||
* require('fs').readFile;
|
||||
*
|
||||
* // API::moduleImport("fs").getMember("readFile").getReturn().asSource()
|
||||
* require('fs').readFile();
|
||||
*
|
||||
* require('fs').readFile(
|
||||
* filename,
|
||||
* // 'y' matched by API::moduleImport("fs").getMember("readFile").getParameter(1).getParameter(0).asSource()
|
||||
* y => {
|
||||
* ...
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::SourceNode getAnImmediateUse() { Impl::use(this, result) }
|
||||
DataFlow::SourceNode asSource() { Impl::use(this, result) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSource`. */
|
||||
deprecated DataFlow::SourceNode getAnImmediateUse() { result = this.asSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource`. */
|
||||
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component.
|
||||
*/
|
||||
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
CallNode getACall() { result = this.getReturn().asSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component,
|
||||
@@ -72,7 +177,7 @@ module API {
|
||||
/**
|
||||
* Gets a `new` call to the function represented by this API component.
|
||||
*/
|
||||
NewNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
|
||||
NewNode getAnInstantiation() { result = this.getInstance().asSource() }
|
||||
|
||||
/**
|
||||
* Gets an invocation (with our without `new`) to the function represented by this API component.
|
||||
@@ -80,26 +185,38 @@ module API {
|
||||
InvokeNode getAnInvocation() { result = this.getACall() or result = this.getAnInstantiation() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
|
||||
* component represented by this node.
|
||||
* Get a data-flow node where this value leaves the current codebase and flows into an
|
||||
* external library (or in general, any external codebase).
|
||||
*
|
||||
* For example, in the assignment `exports.plusOne = (x) => x+1`, the function expression
|
||||
* `(x) => x+1` is the right-hand side of the definition of the member `plusOne` of
|
||||
* the enclosing module, and the expression `x+1` is the right-had side of the definition of
|
||||
* its result.
|
||||
* Concretely, this is either an argument passed to a call to external code,
|
||||
* or the right-hand side of a property write on an object flowing into such a call.
|
||||
*
|
||||
* Note that for parameters, it is the arguments flowing into that parameter that count as
|
||||
* right-hand sides of the definition, not the declaration of the parameter itself.
|
||||
* Consequently, in `require('fs').readFileSync(file)`, `file` is the right-hand
|
||||
* side of a definition of the first parameter of `readFileSync` from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).asSink()
|
||||
* require('foo')(x);
|
||||
*
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).getMember("prop").asSink()
|
||||
* require('foo')({
|
||||
* prop: x
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::rhs(this, result) }
|
||||
DataFlow::Node asSink() { Impl::rhs(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
|
||||
* of the API component represented by this node.
|
||||
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
||||
*
|
||||
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
||||
* See `asSink()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSink`. */
|
||||
deprecated DataFlow::Node getARhs() { result = this.asSink() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink`. */
|
||||
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -334,7 +451,7 @@ module API {
|
||||
* In other words, the value of a use of `that` may flow into the right-hand side of a
|
||||
* definition of this node.
|
||||
*/
|
||||
predicate refersTo(Node that) { this.getARhs() = that.getAUse() }
|
||||
predicate refersTo(Node that) { this.asSink() = that.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets the data-flow node that gives rise to this node, if any.
|
||||
@@ -416,8 +533,9 @@ module API {
|
||||
|
||||
/** Gets a node corresponding to an import of module `m`. */
|
||||
Node moduleImport(string m) {
|
||||
result = Impl::MkModuleImport(m) or
|
||||
result = Impl::MkModuleImport(m).(Node).getMember("default")
|
||||
result = Internal::getAModuleImportRaw(m)
|
||||
or
|
||||
result = ModelOutput::getATypeNode(m, "")
|
||||
}
|
||||
|
||||
/** Gets a node corresponding to an export of module `m`. */
|
||||
@@ -427,6 +545,22 @@ module API {
|
||||
module Node {
|
||||
/** Gets a node whose type has the given qualified name. */
|
||||
Node ofType(string moduleName, string exportedName) {
|
||||
result = Internal::getANodeOfTypeRaw(moduleName, exportedName)
|
||||
or
|
||||
result = ModelOutput::getATypeNode(moduleName, exportedName)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides access to API graph nodes without taking into account types from models. */
|
||||
module Internal {
|
||||
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
|
||||
Node getAModuleImportRaw(string m) {
|
||||
result = Impl::MkModuleImport(m) or
|
||||
result = Impl::MkModuleImport(m).(Node).getMember("default")
|
||||
}
|
||||
|
||||
/** Gets a node whose type has the given qualified name, not including types from models. */
|
||||
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
|
||||
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
|
||||
}
|
||||
}
|
||||
@@ -445,11 +579,17 @@ module API {
|
||||
bindingset[this]
|
||||
EntryPoint() { any() }
|
||||
|
||||
/** Gets a data-flow node that uses this entry point. */
|
||||
abstract DataFlow::SourceNode getAUse();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASource`. */
|
||||
deprecated DataFlow::SourceNode getAUse() { none() }
|
||||
|
||||
/** Gets a data-flow node that defines this entry point. */
|
||||
abstract DataFlow::Node getARhs();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASink`. */
|
||||
deprecated DataFlow::SourceNode getARhs() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
|
||||
DataFlow::SourceNode getASource() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value leaves the current codebase through this entry-point. */
|
||||
DataFlow::Node getASink() { none() }
|
||||
|
||||
/** Gets an API-node for this entry point. */
|
||||
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
||||
@@ -523,7 +663,14 @@ module API {
|
||||
or
|
||||
any(Type t).hasUnderlyingType(m, _)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
|
||||
MkClassInstance(DataFlow::ClassNode cls) {
|
||||
hasSemantics(cls) and
|
||||
(
|
||||
cls = trackDefNode(_)
|
||||
or
|
||||
cls.getAnInstanceReference() = trackDefNode(_)
|
||||
)
|
||||
} or
|
||||
MkAsyncFuncResult(DataFlow::FunctionNode f) {
|
||||
f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f)
|
||||
} or
|
||||
@@ -567,7 +714,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
rhs = e.getARhs()
|
||||
rhs = e.getASink()
|
||||
)
|
||||
or
|
||||
exists(string m, string prop |
|
||||
@@ -615,16 +762,6 @@ module API {
|
||||
.getStaticMember(name, DataFlow::MemberKind::getter())
|
||||
.getAReturn()
|
||||
)
|
||||
or
|
||||
// If `new C()` escapes, generate edges to its instance members
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
pred = cls.getAClassReference().getAnInstantiation() and
|
||||
lbl = Label::member(name)
|
||||
|
|
||||
rhs = cls.getInstanceMethod(name)
|
||||
or
|
||||
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
@@ -744,7 +881,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
ref = e.getAUse()
|
||||
ref = e.getASource()
|
||||
)
|
||||
or
|
||||
// property reads
|
||||
@@ -1113,9 +1250,13 @@ module API {
|
||||
succ = MkUse(ref)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node rhs |
|
||||
rhs(pred, lbl, rhs) and
|
||||
exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) |
|
||||
succ = MkDef(rhs)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
cls.getAnInstanceReference() = rhs and
|
||||
succ = MkClassInstance(cls)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def |
|
||||
@@ -1178,8 +1319,8 @@ module API {
|
||||
API::Node callee;
|
||||
|
||||
InvokeNode() {
|
||||
this = callee.getReturn().getAnImmediateUse() or
|
||||
this = callee.getInstance().getAnImmediateUse() or
|
||||
this = callee.getReturn().asSource() or
|
||||
this = callee.getInstance().asSource() or
|
||||
this = Impl::getAPromisifiedInvocation(callee, _, _)
|
||||
}
|
||||
|
||||
@@ -1194,7 +1335,7 @@ module API {
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) }
|
||||
private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
@@ -1205,13 +1346,13 @@ module API {
|
||||
/** Gets the API node for the return value of this call. */
|
||||
Node getReturn() {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
|
||||
/** Gets the API node for the object constructed by this invocation. */
|
||||
Node getInstance() {
|
||||
result = callee.getInstance() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,7 +1495,7 @@ module API {
|
||||
/** Gets the EntryPoint associated with this label. */
|
||||
API::EntryPoint getEntryPoint() { result = e }
|
||||
|
||||
override string toString() { result = "getASuccessor(Label::entryPoint(\"" + e + "\"))" }
|
||||
override string toString() { result = "entryPoint(\"" + e + "\")" }
|
||||
}
|
||||
|
||||
/** A label that gets a promised value. */
|
||||
|
||||
@@ -36,12 +36,18 @@ module ArrayTaintTracking {
|
||||
succ = call
|
||||
)
|
||||
or
|
||||
// `array.filter(x => x)` keeps the taint
|
||||
// `array.filter(x => x)` and `array.filter(x => !!x)` keeps the taint
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "filter" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call and
|
||||
exists(DataFlow::FunctionNode callback | callback = call.getArgument(0).getAFunctionValue() |
|
||||
callback.getParameter(0).getALocalUse() = callback.getAReturn()
|
||||
exists(DataFlow::FunctionNode callback, DataFlow::Node param, DataFlow::Node ret |
|
||||
callback = call.getArgument(0).getAFunctionValue() and
|
||||
param = callback.getParameter(0).getALocalUse() and
|
||||
ret = callback.getAReturn()
|
||||
|
|
||||
param = ret
|
||||
or
|
||||
param = DataFlow::exprNode(ret.asExpr().(LogNotExpr).getOperand().(LogNotExpr).getOperand())
|
||||
)
|
||||
or
|
||||
// `array.reduce` with tainted value in callback
|
||||
@@ -75,7 +81,7 @@ module ArrayTaintTracking {
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and
|
||||
succ = call
|
||||
or
|
||||
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
|
||||
@@ -199,13 +205,13 @@ private module ArrayDataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
|
||||
* A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`.
|
||||
* E.g. `array.pop()`.
|
||||
*/
|
||||
private class ArrayPopStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["pop", "shift"] and
|
||||
call.getMethodName() = ["pop", "shift", "at"] and
|
||||
prop = arrayElement() and
|
||||
obj = call.getReceiver() and
|
||||
element = call
|
||||
@@ -255,14 +261,12 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for creating an array and storing the elements in the array.
|
||||
*/
|
||||
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayCreationStep extends PreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::ArrayCreationNode array, int i |
|
||||
element = array.getElement(i) and
|
||||
obj = array and
|
||||
if array = any(PromiseAllCreation c).getArrayNode()
|
||||
then prop = arrayElement(i)
|
||||
else prop = arrayElement()
|
||||
prop = arrayElement(i)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -340,6 +344,14 @@ private module ArrayLibraries {
|
||||
result = DataFlow::globalVarRef("Array").getAMemberCall("from")
|
||||
or
|
||||
result = DataFlow::moduleImport("array-from").getACall()
|
||||
or
|
||||
// Array.prototype.slice.call acts the same as Array.from, and is sometimes used with e.g. the arguments object.
|
||||
result =
|
||||
DataFlow::globalVarRef("Array")
|
||||
.getAPropertyRead("prototype")
|
||||
.getAPropertyRead("slice")
|
||||
.getAMethodCall("call") and
|
||||
result.getNumArgument() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -146,7 +146,7 @@ class BasicBlock extends @cfg_node, NodeInStmtContainer {
|
||||
/** Holds if this basic block uses variable `v` in its `i`th node `u`. */
|
||||
predicate useAt(int i, Variable v, VarUse u) { useAt(this, i, v, u) }
|
||||
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `u`. */
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `d`. */
|
||||
predicate defAt(int i, Variable v, VarDef d) { defAt(this, i, v, d) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,7 @@ module CharacterEscapes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character in `n` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
* Gets a character in `src` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
*
|
||||
* The character is the `i`th character of the raw string value of `rawStringNode`.
|
||||
*/
|
||||
|
||||
@@ -172,7 +172,7 @@ class ClassDefinition extends @class_definition, ClassOrInterface, AST::ValueNod
|
||||
/** Gets the expression denoting the super class of the defined class, if any. */
|
||||
override Expr getSuperClass() { result = this.getChildExpr(1) }
|
||||
|
||||
/** Gets the `n`th type from the `implements` clause of this class, starting at 0. */
|
||||
/** Gets the `i`th type from the `implements` clause of this class, starting at 0. */
|
||||
override TypeExpr getSuperInterface(int i) {
|
||||
// AST indices for super interfaces: -1, -4, -7, ...
|
||||
exists(int astIndex | typeexprs(result, _, this, astIndex, _) |
|
||||
@@ -493,6 +493,9 @@ class MemberDeclaration extends @property, Documentable {
|
||||
*/
|
||||
predicate isStatic() { is_static(this) }
|
||||
|
||||
/** Gets a boolean indicating if this member is static. */
|
||||
boolean getStaticAsBool() { if this.isStatic() then result = true else result = false }
|
||||
|
||||
/**
|
||||
* Holds if this member is abstract.
|
||||
*
|
||||
@@ -694,10 +697,10 @@ class MethodDeclaration extends MemberDeclaration {
|
||||
* the overload index is defined as if only one of them was concrete.
|
||||
*/
|
||||
int getOverloadIndex() {
|
||||
exists(ClassOrInterface type, string name |
|
||||
exists(ClassOrInterface type, string name, boolean static |
|
||||
this =
|
||||
rank[result + 1](MethodDeclaration method, int i |
|
||||
methodDeclaredInType(type, name, i, method)
|
||||
methodDeclaredInType(type, name, static, i, method)
|
||||
|
|
||||
method order by i
|
||||
)
|
||||
@@ -718,10 +721,11 @@ class MethodDeclaration extends MemberDeclaration {
|
||||
* Holds if the `index`th member of `type` is `method`, which has the given `name`.
|
||||
*/
|
||||
private predicate methodDeclaredInType(
|
||||
ClassOrInterface type, string name, int index, MethodDeclaration method
|
||||
ClassOrInterface type, string name, boolean static, int index, MethodDeclaration method
|
||||
) {
|
||||
not method instanceof ConstructorDeclaration and // distinguish methods named "constructor" from the constructor
|
||||
type.getMemberByIndex(index) = method and
|
||||
static = method.getStaticAsBool() and
|
||||
method.getName() = name
|
||||
}
|
||||
|
||||
|
||||
@@ -9,118 +9,6 @@ private import semmle.javascript.dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import DataFlow::PseudoProperties
|
||||
|
||||
/**
|
||||
* DEPRECATED. Exists only to support other deprecated elements.
|
||||
*
|
||||
* Type-tracking now automatically determines the set of pseudo-properties to include
|
||||
* ased on which properties are contributed by `SharedTaintStep`s.
|
||||
*/
|
||||
deprecated private class PseudoProperty extends string {
|
||||
PseudoProperty() {
|
||||
this = [arrayLikeElement(), "1"] or // the "1" is required for the `ForOfStep`.
|
||||
this =
|
||||
[
|
||||
mapValue(any(DataFlow::CallNode c | c.getCalleeName() = "set").getArgument(0)),
|
||||
mapValueAll()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `SharedFlowStep` or `SharedTaintTrackingStep` instead.
|
||||
*/
|
||||
abstract deprecated class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate step(
|
||||
DataFlow::Node p, DataFlow::Node s, DataFlow::FlowLabel pl, DataFlow::FlowLabel sl
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
predicate load(DataFlow::Node pred, DataFlow::Node succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.load(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
*/
|
||||
predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
this.store(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.loadStore(pred, succ, prop, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty loadProp, PseudoProperty storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
this.loadStore(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. These steps are now included in the default type tracking steps,
|
||||
* in most cases one can simply use those instead.
|
||||
*/
|
||||
deprecated module CollectionsTypeTracking {
|
||||
/**
|
||||
* Gets the result from a single step through a collection, from `pred` to `result` summarized by `summary`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::SourceNode collectionStep(DataFlow::Node pred, StepSummary summary) {
|
||||
exists(PseudoProperty field |
|
||||
summary = LoadStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::loadStep(pred, result, field) and
|
||||
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
|
||||
or
|
||||
summary = StoreStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::storeStep(pred, result, field)
|
||||
or
|
||||
summary = CopyStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field)
|
||||
or
|
||||
exists(PseudoProperty toField | summary = LoadStoreStep(field, toField) |
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field, toField)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result from a single step through a collection, from `pred` with tracker `t2` to `result` with tracker `t`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::SourceNode collectionStep(
|
||||
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
|
||||
) {
|
||||
exists(DataFlow::Node mid, StepSummary summary | pred.flowsTo(mid) and t = t2.append(summary) |
|
||||
result = collectionStep(mid, summary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for data-flow steps related standard library collection implementations.
|
||||
*/
|
||||
|
||||
@@ -54,7 +54,7 @@ private predicate hasNamedExports(ES2015Module mod) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this module contains a `default` export.
|
||||
* Holds if this module contains a default export.
|
||||
*/
|
||||
private predicate hasDefaultExport(ES2015Module mod) {
|
||||
// export default foo;
|
||||
@@ -337,7 +337,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given bulk export should not re-export `name` because there is an explicit export
|
||||
* Holds if the given bulk export `reExport` should not re-export `name` because there is an explicit export
|
||||
* of that name in the same module.
|
||||
*
|
||||
* At compile time, shadowing works across declaration spaces.
|
||||
|
||||
@@ -167,7 +167,7 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
|
||||
/**
|
||||
* Holds if this expression may refer to the initial value of parameter `p`.
|
||||
*/
|
||||
predicate mayReferToParameter(Parameter p) { this.flow().mayReferToParameter(p) }
|
||||
predicate mayReferToParameter(Parameter p) { DataFlow::parameterNode(p).flowsToExpr(this) }
|
||||
|
||||
/**
|
||||
* Gets the static type of this expression, as determined by the TypeScript type system.
|
||||
|
||||
@@ -175,6 +175,15 @@ class Folder extends Container, @folder {
|
||||
result.getExtension() = extension
|
||||
}
|
||||
|
||||
/** Like `getFile` except `d.ts` is treated as a single extension. */
|
||||
private File getFileLongExtension(string stem, string extension) {
|
||||
not (stem.matches("%.d") and extension = "ts") and
|
||||
result = this.getFile(stem, extension)
|
||||
or
|
||||
extension = "d.ts" and
|
||||
result = this.getFile(stem + ".d", "ts")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file in this folder that has the given `stem` and any of the supported JavaScript extensions.
|
||||
*
|
||||
@@ -188,7 +197,11 @@ class Folder extends Container, @folder {
|
||||
*/
|
||||
File getJavaScriptFile(string stem) {
|
||||
result =
|
||||
min(int p, string ext | p = getFileExtensionPriority(ext) | this.getFile(stem, ext) order by p)
|
||||
min(int p, string ext |
|
||||
p = getFileExtensionPriority(ext)
|
||||
|
|
||||
this.getFileLongExtension(stem, ext) order by p
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a subfolder contained in this folder. */
|
||||
@@ -221,8 +234,6 @@ class File extends Container, @file {
|
||||
/** Gets a toplevel piece of JavaScript code in this file. */
|
||||
TopLevel getATopLevel() { result.getFile() = this }
|
||||
|
||||
override string toString() { result = Container.super.toString() }
|
||||
|
||||
/** Gets the URL of this file. */
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ predicate isGeneratedFileName(File f) {
|
||||
predicate isGenerated(TopLevel tl) {
|
||||
tl.isMinified() or
|
||||
isBundle(tl) or
|
||||
tl instanceof GWTGeneratedTopLevel or
|
||||
tl instanceof GwtGeneratedTopLevel or
|
||||
tl instanceof DartGeneratedTopLevel or
|
||||
exists(GeneratedCodeMarkerComment gcmc | tl = gcmc.getTopLevel()) or
|
||||
hasManyInvocations(tl) or
|
||||
|
||||
@@ -54,7 +54,9 @@ class JsonValue extends @json_value, Locatable {
|
||||
int getIntValue() { result = this.(JsonNumber).getValue().toInt() }
|
||||
|
||||
/** If this is a boolean constant, gets its boolean value. */
|
||||
boolean getBooleanValue() { result.toString() = this.(JsonBoolean).getValue() }
|
||||
boolean getBooleanValue() {
|
||||
result.toString() = this.(JsonBoolean).getValue() and result = [true, false]
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "JsonValue" }
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ private class PlainJsonParserCall extends JsonParserCall {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"parse") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").asSource() or
|
||||
callee = DataFlow::moduleImport("parse-json") or
|
||||
callee = DataFlow::moduleImport("json-parse-better-errors") or
|
||||
callee = DataFlow::moduleImport("json-safe-parse") or
|
||||
|
||||
@@ -134,7 +134,7 @@ module JsonSchema {
|
||||
.ref()
|
||||
.getMember(["addSchema", "validate", "compile", "compileAsync"])
|
||||
.getParameter(0)
|
||||
.getARhs()
|
||||
.asSink()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ module JsonSchema {
|
||||
override boolean getPolarity() { none() }
|
||||
|
||||
override DataFlow::Node getAValidationResultAccess(boolean polarity) {
|
||||
result = this.getReturn().getMember("error").getAnImmediateUse() and
|
||||
result = this.getReturn().getMember("error").asSource() and
|
||||
polarity = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"stringify") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").asSource() or
|
||||
callee =
|
||||
DataFlow::moduleImport([
|
||||
"json-stringify-safe", "json-stable-stringify", "stringify-object",
|
||||
@@ -43,7 +43,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
|
||||
/**
|
||||
* A taint step through the [`json2csv`](https://www.npmjs.com/package/json2csv) library.
|
||||
*/
|
||||
class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
|
||||
class Json2CsvTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(API::CallNode call |
|
||||
call =
|
||||
@@ -59,6 +59,9 @@ class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Json2CsvTaintStep */
|
||||
deprecated class JSON2CSVTaintStep = Json2CsvTaintStep;
|
||||
|
||||
/**
|
||||
* A step through the [`prettyjson`](https://www.npmjs.com/package/prettyjson) library.
|
||||
* This is not quite a `JSON.stringify` call, as it e.g. does not wrap keys in double quotes.
|
||||
|
||||
@@ -229,10 +229,10 @@ module MembershipCandidate {
|
||||
membersNode = inExpr.getRightOperand()
|
||||
)
|
||||
or
|
||||
exists(MethodCallExpr hasOwn |
|
||||
this = hasOwn.getArgument(0).flow() and
|
||||
test = hasOwn and
|
||||
hasOwn.calls(membersNode, "hasOwnProperty")
|
||||
exists(HasOwnPropertyCall hasOwn |
|
||||
this = hasOwn.getProperty() and
|
||||
test = hasOwn.asExpr() and
|
||||
membersNode = hasOwn.getObject().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,23 @@ class PackageJson extends JsonObject {
|
||||
/** Gets a file for this package. */
|
||||
string getAFile() { result = this.getFiles().getElementStringValue(_) }
|
||||
|
||||
/** Gets the main module of this package. */
|
||||
string getMain() { result = MainModulePath::of(this).getValue() }
|
||||
/**
|
||||
* Gets the main module of this package.
|
||||
*
|
||||
* This can be given by the `main` or `module` property, or via the
|
||||
* `exports` property with the relative path `"."`.
|
||||
*/
|
||||
string getMain() { result = this.getExportedPath(".") }
|
||||
|
||||
/**
|
||||
* Gets the path to the file exported with the given relative path.
|
||||
*
|
||||
* This can be given by the `exports` property, but also considers `main` and
|
||||
* `module` paths to be exported under the relative path `"."`.
|
||||
*/
|
||||
string getExportedPath(string relativePath) {
|
||||
result = MainModulePath::of(this, relativePath).getValue()
|
||||
}
|
||||
|
||||
/** Gets the path of a command defined for this package. */
|
||||
string getBin(string cmd) {
|
||||
@@ -153,18 +168,24 @@ class PackageJson extends JsonObject {
|
||||
JsonArray getCPUs() { result = this.getPropValue("cpu") }
|
||||
|
||||
/** Gets a platform supported by this package. */
|
||||
string getWhitelistedCPU() {
|
||||
string getWhitelistedCpu() {
|
||||
result = this.getCPUs().getElementStringValue(_) and
|
||||
not result.matches("!%")
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getWhitelistedCpu */
|
||||
deprecated string getWhitelistedCPU() { result = this.getWhitelistedCpu() }
|
||||
|
||||
/** Gets a platform not supported by this package. */
|
||||
string getBlacklistedCPU() {
|
||||
string getBlacklistedCpu() {
|
||||
exists(string str | str = this.getCPUs().getElementStringValue(_) |
|
||||
result = str.regexpCapture("!(.*)", 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getBlacklistedCpu */
|
||||
deprecated string getBlacklistedCPU() { result = this.getBlacklistedCpu() }
|
||||
|
||||
/** Holds if this package prefers to be installed globally. */
|
||||
predicate isPreferGlobal() { this.getPropValue("preferGlobal").(JsonBoolean).getValue() = "true" }
|
||||
|
||||
@@ -180,6 +201,47 @@ class PackageJson extends JsonObject {
|
||||
Module getMainModule() {
|
||||
result = min(Module m, int prio | m.getFile() = resolveMainModule(this, prio) | m order by prio)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module exported under the given relative path.
|
||||
*
|
||||
* The main module is considered exported under the path `"."`.
|
||||
*/
|
||||
Module getExportedModule(string relativePath) {
|
||||
relativePath = "." and
|
||||
result = this.getMainModule()
|
||||
or
|
||||
result.getFile() = MainModulePath::of(this, relativePath).resolve()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `types` or `typings` field of this package.
|
||||
*/
|
||||
string getTypings() { result = this.getPropStringValue(["types", "typings"]) }
|
||||
|
||||
/**
|
||||
* Gets the file containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
File getTypingsFile() {
|
||||
result =
|
||||
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
|
||||
or
|
||||
not exists(TypingsModulePathString::of(this)) and
|
||||
exists(File mainFile |
|
||||
mainFile = this.getMainModule().getFile() and
|
||||
result =
|
||||
mainFile
|
||||
.getParentContainer()
|
||||
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
Module getTypingsModule() { result.getFile() = this.getTypingsFile() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for PackageJson */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import javascript
|
||||
private import NodeModuleResolutionImpl
|
||||
private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
* A Node.js module.
|
||||
@@ -113,6 +114,7 @@ class NodeModule extends Module {
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
Stages::Imports::ref() and
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getBase().asExpr() = this.getModuleVariable().getAnAccess() and
|
||||
write.getPropertyName() = "exports" and
|
||||
|
||||
@@ -33,6 +33,8 @@ int getFileExtensionPriority(string ext) {
|
||||
ext = "json" and result = 8
|
||||
or
|
||||
ext = "node" and result = 9
|
||||
or
|
||||
ext = "d.ts" and result = 10
|
||||
}
|
||||
|
||||
int prioritiesPerCandidate() { result = 3 * (numberOfExtensions() + 1) }
|
||||
@@ -88,10 +90,11 @@ bindingset[name]
|
||||
private string getStem(string name) { result = name.regexpCapture("(.+?)(?:\\.([^.]+))?", 1) }
|
||||
|
||||
/**
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
* Gets a file that a main module from `pkg` exported as `mainPath` with the given `priority`.
|
||||
* `mainPath` is "." if it's the main module of the package.
|
||||
*/
|
||||
File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg) |
|
||||
private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg, mainPath) |
|
||||
result = main.resolve() and priority = 0
|
||||
or
|
||||
result = tryExtensions(main.resolve(), "index", priority)
|
||||
@@ -100,6 +103,26 @@ File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(int n | n = main.getNumComponent() |
|
||||
result = tryExtensions(main.resolveUpTo(n - 1), getStem(main.getComponent(n - 1)), priority)
|
||||
)
|
||||
or
|
||||
// assuming the files get moved from one dir to another during compilation:
|
||||
not exists(main.resolve()) and // didn't resolve
|
||||
count(int i, string comp | comp = main.getComponent(i) and not comp = "." | i) = 2 and // is down one folder
|
||||
exists(Folder subFolder | subFolder = pkg.getFile().getParentContainer().getAFolder() |
|
||||
// is in one folder below the package.json, and has the right basename
|
||||
result =
|
||||
tryExtensions(subFolder, getStem(main.getComponent(main.getNumComponent() - 1)),
|
||||
priority - 999) // very high priority, to make sure everything else is tried first
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
*/
|
||||
File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(int subPriority, string mainPath |
|
||||
result = resolveMainPath(pkg, mainPath, subPriority) and
|
||||
if mainPath = "." then subPriority = priority else priority = subPriority + 1000
|
||||
)
|
||||
or
|
||||
exists(Folder folder, Folder child |
|
||||
@@ -140,17 +163,28 @@ File resolveMainModule(PackageJson pkg, int priority) {
|
||||
private string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
|
||||
|
||||
/**
|
||||
* A JSON string in a `package.json` file specifying the path of the main
|
||||
* module of the package.
|
||||
* A JSON string in a `package.json` file specifying the path of one of the exported
|
||||
* modules of the package.
|
||||
*/
|
||||
class MainModulePath extends PathExpr, @json_string {
|
||||
PackageJson pkg;
|
||||
|
||||
MainModulePath() { this = pkg.getPropValue(["main", "module"]) }
|
||||
MainModulePath() {
|
||||
this = pkg.getPropValue(["main", "module"])
|
||||
or
|
||||
this = getAPartOfExportsSection(pkg)
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file in which this path occurs. */
|
||||
PackageJson getPackageJson() { result = pkg }
|
||||
|
||||
/** Gets the relative path under which this is exported, usually starting with a `.`. */
|
||||
string getRelativePath() {
|
||||
result = getExportRelativePath(this)
|
||||
or
|
||||
not exists(getExportRelativePath(this)) and result = "."
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getPackageJson */
|
||||
deprecated PackageJSON getPackageJSON() { result = getPackageJson() }
|
||||
|
||||
@@ -162,8 +196,38 @@ class MainModulePath extends PathExpr, @json_string {
|
||||
}
|
||||
}
|
||||
|
||||
private JsonValue getAPartOfExportsSection(PackageJson pkg) {
|
||||
result = pkg.getPropValue("exports")
|
||||
or
|
||||
result = getAPartOfExportsSection(pkg).getPropValue(_)
|
||||
}
|
||||
|
||||
/** Gets the text of one of the conditions or paths enclosing the given `part` of an `exports` section. */
|
||||
private string getAnEnclosingExportProperty(JsonValue part) {
|
||||
exists(JsonObject parent, string prop |
|
||||
parent = getAPartOfExportsSection(_) and
|
||||
part = parent.getPropValue(prop)
|
||||
|
|
||||
result = prop
|
||||
or
|
||||
result = getAnEnclosingExportProperty(parent)
|
||||
)
|
||||
}
|
||||
|
||||
private string getExportRelativePath(JsonValue part) {
|
||||
result = getAnEnclosingExportProperty(part) and
|
||||
result.matches(".%")
|
||||
}
|
||||
|
||||
module MainModulePath {
|
||||
MainModulePath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
/** Gets the path to the main entry point of `pkg`. */
|
||||
MainModulePath of(PackageJson pkg) { result = of(pkg, ".") }
|
||||
|
||||
/** Gets the path to the file exported from `pkg` as `relativePath`. */
|
||||
MainModulePath of(PackageJson pkg, string relativePath) {
|
||||
result.getPackageJson() = pkg and
|
||||
result.getRelativePath() = relativePath
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +240,7 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
|
||||
FilesPath() {
|
||||
this = pkg.getPropValue("files").(JsonArray).getElementValue(_) and
|
||||
not exists(MainModulePath::of(pkg))
|
||||
not exists(MainModulePath::of(pkg, _))
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file in which this path occurs. */
|
||||
@@ -196,3 +260,29 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
private module FilesPath {
|
||||
FilesPath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON string in a `package.json` file specifying the path of the
|
||||
* TypeScript typings entry point.
|
||||
*/
|
||||
class TypingsModulePathString extends PathString {
|
||||
PackageJson pkg;
|
||||
|
||||
TypingsModulePathString() {
|
||||
this = pkg.getTypings()
|
||||
or
|
||||
not exists(pkg.getTypings()) and
|
||||
this = pkg.getMain().regexpReplaceAll("\\.[mc]?js$", ".d.ts")
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file containing this path. */
|
||||
PackageJson getPackageJson() { result = pkg }
|
||||
|
||||
override Folder getARootFolder() { result = pkg.getFile().getParentContainer() }
|
||||
}
|
||||
|
||||
/** Companion module to the `TypingsModulePathString` class. */
|
||||
module TypingsModulePathString {
|
||||
/** Get the typings path for the given `package.json` file. */
|
||||
TypingsModulePathString of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
@@ -11,36 +11,14 @@ private import semmle.javascript.internal.CachedStages
|
||||
* Gets a parameter that is a library input to a top-level package.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getALibraryInputParameter() {
|
||||
DataFlow::Node getALibraryInputParameter() {
|
||||
Stages::Taint::ref() and
|
||||
exists(int bound, DataFlow::FunctionNode func |
|
||||
func = getAValueExportedByPackage().getABoundFunctionValue(bound)
|
||||
|
|
||||
result = func.getParameter(any(int arg | arg >= bound))
|
||||
or
|
||||
result = getAnArgumentsRead(func.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getAnArgumentsRead(Function func) {
|
||||
exists(DataFlow::PropRead read |
|
||||
not read.getPropertyName() = "length" and
|
||||
result = read
|
||||
|
|
||||
read.getBase() = func.getArgumentsVariable().getAnAccess().flow()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call =
|
||||
DataFlow::globalVarRef("Array")
|
||||
.getAPropertyRead("prototype")
|
||||
.getAPropertyRead("slice")
|
||||
.getAMethodCall("call")
|
||||
or
|
||||
call = DataFlow::globalVarRef("Array").getAMethodCall("from")
|
||||
|
|
||||
call.getArgument(0) = func.getArgumentsVariable().getAnAccess().flow() and
|
||||
call.flowsTo(read.getBase())
|
||||
)
|
||||
result = func.getFunction().getArgumentsVariable().getAnAccess().flow()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,7 +30,7 @@ private import NodeModuleResolutionImpl as NodeModule
|
||||
private DataFlow::Node getAValueExportedByPackage() {
|
||||
// The base case, an export from a named `package.json` file.
|
||||
result =
|
||||
getAnExportFromModule(any(PackageJson pack | exists(pack.getPackageName())).getMainModule())
|
||||
getAnExportFromModule(any(PackageJson pack | exists(pack.getPackageName())).getExportedModule(_))
|
||||
or
|
||||
// module.exports.bar.baz = result;
|
||||
exists(DataFlow::PropWrite write |
|
||||
@@ -74,6 +52,16 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
not isPrivateMethodDeclaration(result)
|
||||
)
|
||||
or
|
||||
// module.exports.foo = function () {
|
||||
// return new Foo(); // <- result
|
||||
// };
|
||||
exists(DataFlow::FunctionNode func, DataFlow::NewNode inst, DataFlow::ClassNode clz |
|
||||
func = getAValueExportedByPackage().getALocalSource() and inst = unique( | | func.getAReturn())
|
||||
|
|
||||
clz.getAnInstanceReference() = inst and
|
||||
result = clz.getAnInstanceMember(_)
|
||||
)
|
||||
or
|
||||
result = getAValueExportedByPackage().getALocalSource()
|
||||
or
|
||||
// Nested property reads.
|
||||
|
||||
@@ -180,7 +180,7 @@ private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Gets the `n`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Supports that the root directory might be compiled output from TypeScript.
|
||||
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
|
||||
*/
|
||||
@@ -227,7 +227,7 @@ private module TypeScriptOutDir {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
|
||||
* Gets the "outDir" option from a `tsconfig` file from the folder `parent`.
|
||||
*/
|
||||
private string getOutDir(JsonObject tsconfig, Folder parent) {
|
||||
tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and
|
||||
|
||||
@@ -36,7 +36,7 @@ private predicate isNotNeeded(Locatable el) {
|
||||
el.getLocation().getStartLine() = 0 and
|
||||
el.getLocation().getStartColumn() = 0
|
||||
or
|
||||
// relaxing aggresive type inference.
|
||||
// relaxing aggressive type inference.
|
||||
none()
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ private newtype TPrintAstNode =
|
||||
// JSON
|
||||
TJsonNode(JsonValue value) { shouldPrint(value, _) and not isNotNeeded(value) } or
|
||||
// YAML
|
||||
TYamlNode(YAMLNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
|
||||
TYamlMappingNode(YAMLMapping mapping, int i) {
|
||||
TYamlNode(YamlNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
|
||||
TYamlMappingNode(YamlMapping mapping, int i) {
|
||||
shouldPrint(mapping, _) and not isNotNeeded(mapping) and exists(mapping.getKeyNode(i))
|
||||
} or
|
||||
// HTML
|
||||
@@ -195,7 +195,7 @@ private module PrintJavaScript {
|
||||
* Gets the `i`th child of `element`.
|
||||
* Can be overridden in subclasses to get more specific behavior for `getChild()`.
|
||||
*/
|
||||
AstNode getChildNode(int childIndex) { result = getLocationSortedChild(element, childIndex) }
|
||||
AstNode getChildNode(int i) { result = getLocationSortedChild(element, i) }
|
||||
}
|
||||
|
||||
/** Provides predicates for pretty printing `AstNode`s. */
|
||||
@@ -628,7 +628,7 @@ module PrintYaml {
|
||||
* A print node representing a YAML value in a .yml file.
|
||||
*/
|
||||
class YamlNodeNode extends PrintAstNode, TYamlNode {
|
||||
YAMLNode node;
|
||||
YamlNode node;
|
||||
|
||||
YamlNodeNode() { this = TYamlNode(node) }
|
||||
|
||||
@@ -639,10 +639,10 @@ module PrintYaml {
|
||||
/**
|
||||
* Gets the `YAMLNode` represented by this node.
|
||||
*/
|
||||
final YAMLNode getValue() { result = node }
|
||||
final YamlNode getValue() { result = node }
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
exists(YAMLNode child | result.(YamlNodeNode).getValue() = child |
|
||||
exists(YamlNode child | result.(YamlNodeNode).getValue() = child |
|
||||
child = node.getChildNode(childIndex)
|
||||
)
|
||||
}
|
||||
@@ -657,7 +657,7 @@ module PrintYaml {
|
||||
* Each child of this node aggregates the key and value of a mapping.
|
||||
*/
|
||||
class YamlMappingNode extends YamlNodeNode {
|
||||
override YAMLMapping node;
|
||||
override YamlMapping node;
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
exists(YamlMappingMapNode map | map = result | map.maps(node, childIndex))
|
||||
@@ -671,21 +671,21 @@ module PrintYaml {
|
||||
* A print node representing the `i`th mapping in `mapping`.
|
||||
*/
|
||||
class YamlMappingMapNode extends PrintAstNode, TYamlMappingNode {
|
||||
YAMLMapping mapping;
|
||||
YamlMapping mapping;
|
||||
int i;
|
||||
|
||||
YamlMappingMapNode() { this = TYamlMappingNode(mapping, i) }
|
||||
|
||||
override string toString() {
|
||||
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YAMLScalar).getValue())
|
||||
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YamlScalar).getValue())
|
||||
or
|
||||
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YAMLScalar).getValue() + ":"
|
||||
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YamlScalar).getValue() + ":"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this print node represents the `index`th mapping of `m`.
|
||||
*/
|
||||
predicate maps(YAMLMapping m, int index) {
|
||||
predicate maps(YamlMapping m, int index) {
|
||||
m = mapping and
|
||||
index = i
|
||||
}
|
||||
|
||||
@@ -189,6 +189,13 @@ module Promises {
|
||||
* Gets the pseudo-field used to describe rejected values in a promise.
|
||||
*/
|
||||
string errorProp() { result = "$PromiseRejectField$" }
|
||||
|
||||
/** A property set containing the pseudo-properites of a promise object. */
|
||||
class PromiseProps extends DataFlow::PropertySet {
|
||||
PromiseProps() { this = "PromiseProps" }
|
||||
|
||||
override string getAProperty() { result = [valueProp(), errorProp()] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +281,24 @@ private class PromiseStep extends PreCallGraphStep {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step from `p -> await p` for the case where `p` is not a promise.
|
||||
*
|
||||
* In this case, `await p` just returns `p` itself. We block flow of the promise-related
|
||||
* pseudo properties through this edge.
|
||||
*/
|
||||
private class RawAwaitStep extends DataFlow::SharedTypeTrackingStep {
|
||||
override predicate withoutPropStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::PropertySet props
|
||||
) {
|
||||
exists(AwaitExpr await |
|
||||
pred = await.getOperand().flow() and
|
||||
succ = await.flow() and
|
||||
props instanceof Promises::PromiseProps
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module defines how data-flow propagates into and out of a Promise.
|
||||
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
|
||||
|
||||
@@ -260,7 +260,7 @@ module RangeAnalysis {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given comparison can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* Holds if the given `comparison` can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* and `A` is `a * asign` and likewise `B` is `b * bsign`.
|
||||
*/
|
||||
predicate linearComparison(
|
||||
@@ -310,18 +310,18 @@ module RangeAnalysis {
|
||||
* Holds if `guard` asserts that the outcome of `A <op> B + bias` is true, where `<op>` is a comparison operator.
|
||||
*/
|
||||
predicate linearComparisonGuard(
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string operator, DataFlow::Node b,
|
||||
int bsign, Bias bias
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string op, DataFlow::Node b, int bsign,
|
||||
Bias bias
|
||||
) {
|
||||
exists(Comparison compare |
|
||||
compare = guard.getTest().flow().getImmediatePredecessor*().asExpr() and
|
||||
linearComparison(compare, a, asign, b, bsign, bias) and
|
||||
(
|
||||
guard.getOutcome() = true and operator = compare.getOperator()
|
||||
guard.getOutcome() = true and op = compare.getOperator()
|
||||
or
|
||||
not hasNaNIndicator(guard.getContainer()) and
|
||||
guard.getOutcome() = false and
|
||||
operator = negateOperator(compare.getOperator())
|
||||
op = negateOperator(compare.getOperator())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -657,13 +657,13 @@ module RangeAnalysis {
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate reachableByNegativeEdges(
|
||||
DataFlow::Node a, int asign, DataFlow::Node b, int bsign, ControlFlowNode cfg
|
||||
DataFlow::Node src, int asign, DataFlow::Node dst, int bsign, ControlFlowNode cfg
|
||||
) {
|
||||
negativeEdge(a, asign, b, bsign, cfg)
|
||||
negativeEdge(src, asign, dst, bsign, cfg)
|
||||
or
|
||||
exists(DataFlow::Node mid, int midx, ControlFlowNode midcfg |
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = midcfg and
|
||||
bb.getNode(j) = cfg and
|
||||
@@ -676,8 +676,8 @@ module RangeAnalysis {
|
||||
DataFlow::Node mid, int midx, ControlFlowNode midcfg, BasicBlock midBB,
|
||||
ReachableBasicBlock midRBB, BasicBlock cfgBB
|
||||
|
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
midBB = midcfg.getBasicBlock() and
|
||||
midRBB = midBB.(ReachableBasicBlock) and
|
||||
cfgBB = cfg.getBasicBlock() and
|
||||
|
||||
@@ -1005,7 +1005,10 @@ module RegExpPatterns {
|
||||
* Gets a pattern that matches common top-level domain names in lower case.
|
||||
* DEPRECATED: use `getACommonTld` instead
|
||||
*/
|
||||
deprecated predicate commonTLD = getACommonTld/0;
|
||||
deprecated predicate commonTld = getACommonTld/0;
|
||||
|
||||
/** DEPRECATED: Alias for commonTld */
|
||||
deprecated predicate commonTLD = commonTld/0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,6 +148,18 @@ module Routing {
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `mayResumeDispatch` but without the assumption that functions with an unknown
|
||||
* implementation invoke their continuation.
|
||||
*/
|
||||
predicate definitelyResumesDispatch() {
|
||||
this.getLastChild().definitelyResumesDispatch()
|
||||
or
|
||||
exists(this.(RouteHandler).getAContinuationInvocation())
|
||||
or
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/** Gets the parent of this node, provided that this node may invoke its continuation. */
|
||||
private Node getContinuationParent() {
|
||||
result = this.getParent() and
|
||||
@@ -229,11 +241,11 @@ module Routing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` has processed the incoming request strictly prior to this node.
|
||||
* Holds if `guard` has processed the incoming request strictly prior to this node.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate isGuardedByNodeInternal(Node guard) {
|
||||
// Look for a common ancestor `fork` whose child leading to `guard` ("base1") preceeds
|
||||
// Look for a common ancestor `fork` whose child leading to `guard` ("base1") precedes
|
||||
// the child leading to `this` ("base2").
|
||||
//
|
||||
// Schematically:
|
||||
|
||||
@@ -501,7 +501,7 @@ class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
|
||||
}
|
||||
|
||||
/** This SSA definition corresponds to the definition of `v` at `def`. */
|
||||
predicate defines(VarDef d, SsaSourceVariable v) { this = TExplicitDef(_, _, d, v) }
|
||||
predicate defines(VarDef def, SsaSourceVariable v) { this = TExplicitDef(_, _, def, v) }
|
||||
|
||||
/** Gets the variable definition wrapped by this SSA definition. */
|
||||
VarDef getDef() { this = TExplicitDef(_, _, result, _) }
|
||||
|
||||
@@ -192,3 +192,35 @@ class StringSplitCall extends DataFlow::MethodCallNode {
|
||||
bindingset[i]
|
||||
DataFlow::Node getASubstringRead(int i) { result = this.getAPropertyRead(i.toString()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Object.prototype.hasOwnProperty`, `Object.hasOwn`, or a library that implements
|
||||
* the same functionality.
|
||||
*/
|
||||
class HasOwnPropertyCall extends DataFlow::Node instanceof DataFlow::CallNode {
|
||||
DataFlow::Node object;
|
||||
DataFlow::Node property;
|
||||
|
||||
HasOwnPropertyCall() {
|
||||
// Make sure we handle reflective calls since libraries love to do that.
|
||||
super.getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() =
|
||||
"hasOwnProperty" and
|
||||
object = super.getReceiver() and
|
||||
property = super.getArgument(0)
|
||||
or
|
||||
this =
|
||||
[
|
||||
DataFlow::globalVarRef("Object").getAMemberCall("hasOwn"), //
|
||||
DataFlow::moduleImport("has").getACall(), //
|
||||
LodashUnderscore::member("has").getACall()
|
||||
] and
|
||||
object = super.getArgument(0) and
|
||||
property = super.getArgument(1)
|
||||
}
|
||||
|
||||
/** Gets the object whose property is being checked. */
|
||||
DataFlow::Node getObject() { result = object }
|
||||
|
||||
/** Gets the property being checked. */
|
||||
DataFlow::Node getProperty() { result = property }
|
||||
}
|
||||
|
||||
@@ -291,10 +291,13 @@ class StrictModeDecl extends KnownDirective {
|
||||
* "use asm";
|
||||
* ```
|
||||
*/
|
||||
class ASMJSDirective extends KnownDirective {
|
||||
ASMJSDirective() { this.getDirectiveText() = "use asm" }
|
||||
class AsmJSDirective extends KnownDirective {
|
||||
AsmJSDirective() { this.getDirectiveText() = "use asm" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for AsmJSDirective */
|
||||
deprecated class ASMJSDirective = AsmJSDirective;
|
||||
|
||||
/**
|
||||
* A Babel directive.
|
||||
*
|
||||
|
||||
@@ -751,7 +751,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a suitable name for the library imported by `import`.
|
||||
* Gets a suitable name for the library imported by `imprt`.
|
||||
*
|
||||
* For relative imports, this is the snapshot-relative path to the imported module.
|
||||
* For non-relative imports, it is the import path itself.
|
||||
@@ -896,21 +896,28 @@ class ArrayTypeExpr extends @array_typeexpr, TypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "ArrayTypeExpr" }
|
||||
}
|
||||
|
||||
private class RawUnionOrIntersectionTypeExpr = @union_typeexpr or @intersection_typeexpr;
|
||||
|
||||
/**
|
||||
* A union type, such as `string|number|boolean`.
|
||||
* A union or intersection type, such as `string|number|boolean` or `A & B`.
|
||||
*/
|
||||
class UnionTypeExpr extends @union_typeexpr, TypeExpr {
|
||||
/** Gets the `n`th type in the union, starting at 0. */
|
||||
class UnionOrIntersectionTypeExpr extends RawUnionOrIntersectionTypeExpr, TypeExpr {
|
||||
/** Gets the `n`th type in the union or intersection, starting at 0. */
|
||||
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
|
||||
|
||||
/** Gets any of the types in the union. */
|
||||
/** Gets any of the types in the union or intersection. */
|
||||
TypeExpr getAnElementType() { result = this.getElementType(_) }
|
||||
|
||||
/** Gets the number of types in the union. This is always at least two. */
|
||||
/** Gets the number of types in the union or intersection. This is always at least two. */
|
||||
int getNumElementType() { result = count(this.getAnElementType()) }
|
||||
|
||||
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A union type, such as `string|number|boolean`.
|
||||
*/
|
||||
class UnionTypeExpr extends @union_typeexpr, UnionOrIntersectionTypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "UnionTypeExpr" }
|
||||
}
|
||||
|
||||
@@ -932,18 +939,7 @@ class IndexedAccessTypeExpr extends @indexed_access_typeexpr, TypeExpr {
|
||||
*
|
||||
* In general, there are can more than two operands to an intersection type.
|
||||
*/
|
||||
class IntersectionTypeExpr extends @intersection_typeexpr, TypeExpr {
|
||||
/** Gets the `n`th operand of the intersection type, starting at 0. */
|
||||
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
|
||||
|
||||
/** Gets any of the operands to the intersection type. */
|
||||
TypeExpr getAnElementType() { result = this.getElementType(_) }
|
||||
|
||||
/** Gets the number of operands to the intersection type. This is always at least two. */
|
||||
int getNumElementType() { result = count(this.getAnElementType()) }
|
||||
|
||||
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
|
||||
|
||||
class IntersectionTypeExpr extends @intersection_typeexpr, UnionOrIntersectionTypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "IntersectionTypeExpr" }
|
||||
}
|
||||
|
||||
@@ -1286,6 +1282,8 @@ class ExpressionWithTypeArguments extends @expression_with_type_arguments, Expr
|
||||
override ControlFlowNode getFirstControlFlowNode() {
|
||||
result = this.getExpression().getFirstControlFlowNode()
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ExpressionWithTypeArguments" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -873,6 +873,18 @@ class DeclarationSpace extends string {
|
||||
DeclarationSpace() { this = "variable" or this = "type" or this = "namespace" }
|
||||
}
|
||||
|
||||
/** Module containing the `DeclarationSpace` constants. */
|
||||
module DeclarationSpace {
|
||||
/** Gets the declaration space for variables/values. */
|
||||
DeclarationSpace variable() { result = "variable" }
|
||||
|
||||
/** Gets the declaration space for types. */
|
||||
DeclarationSpace type() { result = "type" }
|
||||
|
||||
/** Gets the declaration space for namespaces. */
|
||||
DeclarationSpace namespace() { result = "namespace" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A name that is declared in a particular scope.
|
||||
*
|
||||
|
||||
107
javascript/ql/lib/semmle/javascript/XML.qll
Executable file → Normal file
107
javascript/ql/lib/semmle/javascript/XML.qll
Executable file → Normal file
@@ -8,7 +8,7 @@ private class TXmlLocatable =
|
||||
@xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters;
|
||||
|
||||
/** An XML element that has a location. */
|
||||
class XMLLocatable extends @xmllocatable, TXmlLocatable {
|
||||
class XmlLocatable extends @xmllocatable, TXmlLocatable {
|
||||
/** Gets the source location for this element. */
|
||||
Location getLocation() { xmllocations(this, result) }
|
||||
|
||||
@@ -32,13 +32,16 @@ class XMLLocatable extends @xmllocatable, TXmlLocatable {
|
||||
string toString() { none() } // overridden in subclasses
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlLocatable */
|
||||
deprecated class XMLLocatable = XmlLocatable;
|
||||
|
||||
/**
|
||||
* An `XMLParent` is either an `XMLElement` or an `XMLFile`,
|
||||
* An `XmlParent` is either an `XmlElement` or an `XmlFile`,
|
||||
* both of which can contain other elements.
|
||||
*/
|
||||
class XMLParent extends @xmlparent {
|
||||
XMLParent() {
|
||||
// explicitly restrict `this` to be either an `XMLElement` or an `XMLFile`;
|
||||
class XmlParent extends @xmlparent {
|
||||
XmlParent() {
|
||||
// explicitly restrict `this` to be either an `XmlElement` or an `XmlFile`;
|
||||
// the type `@xmlparent` currently also includes non-XML files
|
||||
this instanceof @xmlelement or xmlEncoding(this, _)
|
||||
}
|
||||
@@ -50,28 +53,28 @@ class XMLParent extends @xmlparent {
|
||||
string getName() { none() } // overridden in subclasses
|
||||
|
||||
/** Gets the file to which this XML parent belongs. */
|
||||
XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) }
|
||||
XmlFile getFile() { result = this or xmlElements(this, _, _, _, result) }
|
||||
|
||||
/** Gets the child element at a specified index of this XML parent. */
|
||||
XMLElement getChild(int index) { xmlElements(result, _, this, index, _) }
|
||||
XmlElement getChild(int index) { xmlElements(result, _, this, index, _) }
|
||||
|
||||
/** Gets a child element of this XML parent. */
|
||||
XMLElement getAChild() { xmlElements(result, _, this, _, _) }
|
||||
XmlElement getAChild() { xmlElements(result, _, this, _, _) }
|
||||
|
||||
/** Gets a child element of this XML parent with the given `name`. */
|
||||
XMLElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
|
||||
XmlElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
|
||||
|
||||
/** Gets a comment that is a child of this XML parent. */
|
||||
XMLComment getAComment() { xmlComments(result, _, this, _) }
|
||||
XmlComment getAComment() { xmlComments(result, _, this, _) }
|
||||
|
||||
/** Gets a character sequence that is a child of this XML parent. */
|
||||
XMLCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
|
||||
XmlCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
|
||||
|
||||
/** Gets the depth in the tree. (Overridden in XMLElement.) */
|
||||
/** Gets the depth in the tree. (Overridden in XmlElement.) */
|
||||
int getDepth() { result = 0 }
|
||||
|
||||
/** Gets the number of child XML elements of this XML parent. */
|
||||
int getNumberOfChildren() { result = count(XMLElement e | xmlElements(e, _, this, _, _)) }
|
||||
int getNumberOfChildren() { result = count(XmlElement e | xmlElements(e, _, this, _, _)) }
|
||||
|
||||
/** Gets the number of places in the body of this XML parent where text occurs. */
|
||||
int getNumberOfCharacterSets() { result = count(int pos | xmlChars(_, _, this, pos, _, _)) }
|
||||
@@ -92,9 +95,12 @@ class XMLParent extends @xmlparent {
|
||||
string toString() { result = this.getName() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlParent */
|
||||
deprecated class XMLParent = XmlParent;
|
||||
|
||||
/** An XML file. */
|
||||
class XMLFile extends XMLParent, File {
|
||||
XMLFile() { xmlEncoding(this, _) }
|
||||
class XmlFile extends XmlParent, File {
|
||||
XmlFile() { xmlEncoding(this, _) }
|
||||
|
||||
/** Gets a printable representation of this XML file. */
|
||||
override string toString() { result = this.getName() }
|
||||
@@ -120,15 +126,21 @@ class XMLFile extends XMLParent, File {
|
||||
string getEncoding() { xmlEncoding(this, result) }
|
||||
|
||||
/** Gets the XML file itself. */
|
||||
override XMLFile getFile() { result = this }
|
||||
override XmlFile getFile() { result = this }
|
||||
|
||||
/** Gets a top-most element in an XML file. */
|
||||
XMLElement getARootElement() { result = this.getAChild() }
|
||||
XmlElement getARootElement() { result = this.getAChild() }
|
||||
|
||||
/** Gets a DTD associated with this XML file. */
|
||||
XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) }
|
||||
XmlDtd getADtd() { xmlDTDs(result, _, _, _, this) }
|
||||
|
||||
/** DEPRECATED: Alias for getADtd */
|
||||
deprecated XmlDtd getADTD() { result = this.getADtd() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlFile */
|
||||
deprecated class XMLFile = XmlFile;
|
||||
|
||||
/**
|
||||
* An XML document type definition (DTD).
|
||||
*
|
||||
@@ -140,7 +152,7 @@ class XMLFile extends XMLParent, File {
|
||||
* <!ELEMENT lastName (#PCDATA)>
|
||||
* ```
|
||||
*/
|
||||
class XMLDTD extends XMLLocatable, @xmldtd {
|
||||
class XmlDtd extends XmlLocatable, @xmldtd {
|
||||
/** Gets the name of the root element of this DTD. */
|
||||
string getRoot() { xmlDTDs(this, result, _, _, _) }
|
||||
|
||||
@@ -154,7 +166,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
|
||||
predicate isPublic() { not xmlDTDs(this, _, "", _, _) }
|
||||
|
||||
/** Gets the parent of this DTD. */
|
||||
XMLParent getParent() { xmlDTDs(this, _, _, _, result) }
|
||||
XmlParent getParent() { xmlDTDs(this, _, _, _, result) }
|
||||
|
||||
override string toString() {
|
||||
this.isPublic() and
|
||||
@@ -165,6 +177,9 @@ class XMLDTD extends XMLLocatable, @xmldtd {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlDtd */
|
||||
deprecated class XMLDTD = XmlDtd;
|
||||
|
||||
/**
|
||||
* An XML element in an XML file.
|
||||
*
|
||||
@@ -176,7 +191,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
|
||||
* </manifest>
|
||||
* ```
|
||||
*/
|
||||
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
class XmlElement extends @xmlelement, XmlParent, XmlLocatable {
|
||||
/** Holds if this XML element has the given `name`. */
|
||||
predicate hasName(string name) { name = this.getName() }
|
||||
|
||||
@@ -184,10 +199,10 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
override string getName() { xmlElements(this, result, _, _, _) }
|
||||
|
||||
/** Gets the XML file in which this XML element occurs. */
|
||||
override XMLFile getFile() { xmlElements(this, _, _, _, result) }
|
||||
override XmlFile getFile() { xmlElements(this, _, _, _, result) }
|
||||
|
||||
/** Gets the parent of this XML element. */
|
||||
XMLParent getParent() { xmlElements(this, _, result, _, _) }
|
||||
XmlParent getParent() { xmlElements(this, _, result, _, _) }
|
||||
|
||||
/** Gets the index of this XML element among its parent's children. */
|
||||
int getIndex() { xmlElements(this, _, _, result, _) }
|
||||
@@ -196,7 +211,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
predicate hasNamespace() { xmlHasNs(this, _, _) }
|
||||
|
||||
/** Gets the namespace of this XML element, if any. */
|
||||
XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
|
||||
/** Gets the index of this XML element among its parent's children. */
|
||||
int getElementPositionIndex() { xmlElements(this, _, _, result, _) }
|
||||
@@ -205,10 +220,10 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
override int getDepth() { result = this.getParent().getDepth() + 1 }
|
||||
|
||||
/** Gets an XML attribute of this XML element. */
|
||||
XMLAttribute getAnAttribute() { result.getElement() = this }
|
||||
XmlAttribute getAnAttribute() { result.getElement() = this }
|
||||
|
||||
/** Gets the attribute with the specified `name`, if any. */
|
||||
XMLAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
|
||||
XmlAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
|
||||
|
||||
/** Holds if this XML element has an attribute with the specified `name`. */
|
||||
predicate hasAttribute(string name) { exists(this.getAttribute(name)) }
|
||||
@@ -220,6 +235,9 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
override string toString() { result = this.getName() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlElement */
|
||||
deprecated class XMLElement = XmlElement;
|
||||
|
||||
/**
|
||||
* An attribute that occurs inside an XML element.
|
||||
*
|
||||
@@ -230,18 +248,18 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
|
||||
* android:versionCode="1"
|
||||
* ```
|
||||
*/
|
||||
class XMLAttribute extends @xmlattribute, XMLLocatable {
|
||||
class XmlAttribute extends @xmlattribute, XmlLocatable {
|
||||
/** Gets the name of this attribute. */
|
||||
string getName() { xmlAttrs(this, _, result, _, _, _) }
|
||||
|
||||
/** Gets the XML element to which this attribute belongs. */
|
||||
XMLElement getElement() { xmlAttrs(this, result, _, _, _, _) }
|
||||
XmlElement getElement() { xmlAttrs(this, result, _, _, _, _) }
|
||||
|
||||
/** Holds if this attribute has a namespace. */
|
||||
predicate hasNamespace() { xmlHasNs(this, _, _) }
|
||||
|
||||
/** Gets the namespace of this attribute, if any. */
|
||||
XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
|
||||
|
||||
/** Gets the value of this attribute. */
|
||||
string getValue() { xmlAttrs(this, _, _, result, _, _) }
|
||||
@@ -250,6 +268,9 @@ class XMLAttribute extends @xmlattribute, XMLLocatable {
|
||||
override string toString() { result = this.getName() + "=" + this.getValue() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlAttribute */
|
||||
deprecated class XMLAttribute = XmlAttribute;
|
||||
|
||||
/**
|
||||
* A namespace used in an XML file.
|
||||
*
|
||||
@@ -259,23 +280,29 @@ class XMLAttribute extends @xmlattribute, XMLLocatable {
|
||||
* xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
* ```
|
||||
*/
|
||||
class XMLNamespace extends XMLLocatable, @xmlnamespace {
|
||||
class XmlNamespace extends XmlLocatable, @xmlnamespace {
|
||||
/** Gets the prefix of this namespace. */
|
||||
string getPrefix() { xmlNs(this, result, _, _) }
|
||||
|
||||
/** Gets the URI of this namespace. */
|
||||
string getURI() { xmlNs(this, _, result, _) }
|
||||
string getUri() { xmlNs(this, _, result, _) }
|
||||
|
||||
/** DEPRECATED: Alias for getUri */
|
||||
deprecated string getURI() { result = this.getUri() }
|
||||
|
||||
/** Holds if this namespace has no prefix. */
|
||||
predicate isDefault() { this.getPrefix() = "" }
|
||||
|
||||
override string toString() {
|
||||
this.isDefault() and result = this.getURI()
|
||||
this.isDefault() and result = this.getUri()
|
||||
or
|
||||
not this.isDefault() and result = this.getPrefix() + ":" + this.getURI()
|
||||
not this.isDefault() and result = this.getPrefix() + ":" + this.getUri()
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlNamespace */
|
||||
deprecated class XMLNamespace = XmlNamespace;
|
||||
|
||||
/**
|
||||
* A comment in an XML file.
|
||||
*
|
||||
@@ -285,17 +312,20 @@ class XMLNamespace extends XMLLocatable, @xmlnamespace {
|
||||
* <!-- This is a comment. -->
|
||||
* ```
|
||||
*/
|
||||
class XMLComment extends @xmlcomment, XMLLocatable {
|
||||
class XmlComment extends @xmlcomment, XmlLocatable {
|
||||
/** Gets the text content of this XML comment. */
|
||||
string getText() { xmlComments(this, result, _, _) }
|
||||
|
||||
/** Gets the parent of this XML comment. */
|
||||
XMLParent getParent() { xmlComments(this, _, result, _) }
|
||||
XmlParent getParent() { xmlComments(this, _, result, _) }
|
||||
|
||||
/** Gets a printable representation of this XML comment. */
|
||||
override string toString() { result = this.getText() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlComment */
|
||||
deprecated class XMLComment = XmlComment;
|
||||
|
||||
/**
|
||||
* A sequence of characters that occurs between opening and
|
||||
* closing tags of an XML element, excluding other elements.
|
||||
@@ -306,12 +336,12 @@ class XMLComment extends @xmlcomment, XMLLocatable {
|
||||
* <content>This is a sequence of characters.</content>
|
||||
* ```
|
||||
*/
|
||||
class XMLCharacters extends @xmlcharacters, XMLLocatable {
|
||||
class XmlCharacters extends @xmlcharacters, XmlLocatable {
|
||||
/** Gets the content of this character sequence. */
|
||||
string getCharacters() { xmlChars(this, result, _, _, _, _) }
|
||||
|
||||
/** Gets the parent of this character sequence. */
|
||||
XMLParent getParent() { xmlChars(this, _, result, _, _, _) }
|
||||
XmlParent getParent() { xmlChars(this, _, result, _, _, _) }
|
||||
|
||||
/** Holds if this character sequence is CDATA. */
|
||||
predicate isCDATA() { xmlChars(this, _, _, _, 1, _) }
|
||||
@@ -319,3 +349,6 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable {
|
||||
/** Gets a printable representation of this XML character sequence. */
|
||||
override string toString() { result = this.getCharacters() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlCharacters */
|
||||
deprecated class XMLCharacters = XmlCharacters;
|
||||
|
||||
@@ -20,13 +20,13 @@ import javascript
|
||||
* << : *DEFAULTS # an alias node referring to anchor `DEFAULTS`
|
||||
* ```
|
||||
*/
|
||||
class YAMLNode extends @yaml_node, Locatable {
|
||||
class YamlNode extends @yaml_node, Locatable {
|
||||
override Location getLocation() { yaml_locations(this, result) }
|
||||
|
||||
/**
|
||||
* Gets the parent node of this node, which is always a collection.
|
||||
*/
|
||||
YAMLCollection getParentNode() { yaml(this, _, result, _, _, _) }
|
||||
YamlCollection getParentNode() { yaml(this, _, result, _, _, _) }
|
||||
|
||||
/**
|
||||
* Gets the `i`th child node of this node.
|
||||
@@ -34,12 +34,12 @@ class YAMLNode extends @yaml_node, Locatable {
|
||||
* _Note_: The index of a child node relative to its parent is considered
|
||||
* an implementation detail and may change between versions of the extractor.
|
||||
*/
|
||||
YAMLNode getChildNode(int i) { yaml(result, _, this, i, _, _) }
|
||||
YamlNode getChildNode(int i) { yaml(result, _, this, i, _, _) }
|
||||
|
||||
/**
|
||||
* Gets a child node of this node.
|
||||
*/
|
||||
YAMLNode getAChildNode() { result = this.getChildNode(_) }
|
||||
YamlNode getAChildNode() { result = this.getChildNode(_) }
|
||||
|
||||
/**
|
||||
* Gets the number of child nodes of this node.
|
||||
@@ -49,12 +49,12 @@ class YAMLNode extends @yaml_node, Locatable {
|
||||
/**
|
||||
* Gets the `i`th child of this node, as a YAML value.
|
||||
*/
|
||||
YAMLValue getChild(int i) { result = this.getChildNode(i).eval() }
|
||||
YamlValue getChild(int i) { result = this.getChildNode(i).eval() }
|
||||
|
||||
/**
|
||||
* Gets a child of this node, as a YAML value.
|
||||
*/
|
||||
YAMLValue getAChild() { result = this.getChild(_) }
|
||||
YamlValue getAChild() { result = this.getChild(_) }
|
||||
|
||||
/**
|
||||
* Gets the tag of this node.
|
||||
@@ -79,16 +79,19 @@ class YAMLNode extends @yaml_node, Locatable {
|
||||
/**
|
||||
* Gets the toplevel document to which this node belongs.
|
||||
*/
|
||||
YAMLDocument getDocument() { result = this.getParentNode*() }
|
||||
YamlDocument getDocument() { result = this.getParentNode*() }
|
||||
|
||||
/**
|
||||
* Gets the YAML value this node corresponds to after resolving aliases and includes.
|
||||
*/
|
||||
YAMLValue eval() { result = this }
|
||||
YamlValue eval() { result = this }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "YAMLNode" }
|
||||
override string getAPrimaryQlClass() { result = "YamlNode" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlNode */
|
||||
deprecated class YAMLNode = YamlNode;
|
||||
|
||||
/**
|
||||
* A YAML value; that is, either a scalar or a collection.
|
||||
*
|
||||
@@ -102,7 +105,10 @@ class YAMLNode extends @yaml_node, Locatable {
|
||||
* - sequence
|
||||
* ```
|
||||
*/
|
||||
abstract class YAMLValue extends YAMLNode { }
|
||||
abstract class YamlValue extends YamlNode { }
|
||||
|
||||
/** DEPRECATED: Alias for YamlValue */
|
||||
deprecated class YAMLValue = YamlValue;
|
||||
|
||||
/**
|
||||
* A YAML scalar.
|
||||
@@ -118,7 +124,7 @@ abstract class YAMLValue extends YAMLNode { }
|
||||
* "hello"
|
||||
* ```
|
||||
*/
|
||||
class YAMLScalar extends YAMLValue, @yaml_scalar_node {
|
||||
class YamlScalar extends YamlValue, @yaml_scalar_node {
|
||||
/**
|
||||
* Gets the style of this scalar, which is one of the following:
|
||||
*
|
||||
@@ -147,9 +153,12 @@ class YAMLScalar extends YAMLValue, @yaml_scalar_node {
|
||||
*/
|
||||
string getValue() { yaml_scalars(this, _, result) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "YAMLScalar" }
|
||||
override string getAPrimaryQlClass() { result = "YamlScalar" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlScalar */
|
||||
deprecated class YAMLScalar = YamlScalar;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing an integer value.
|
||||
*
|
||||
@@ -160,8 +169,8 @@ class YAMLScalar extends YAMLValue, @yaml_scalar_node {
|
||||
* 0xffff
|
||||
* ```
|
||||
*/
|
||||
class YAMLInteger extends YAMLScalar {
|
||||
YAMLInteger() { this.hasStandardTypeTag("int") }
|
||||
class YamlInteger extends YamlScalar {
|
||||
YamlInteger() { this.hasStandardTypeTag("int") }
|
||||
|
||||
/**
|
||||
* Gets the value of this scalar, as an integer.
|
||||
@@ -169,6 +178,9 @@ class YAMLInteger extends YAMLScalar {
|
||||
int getIntValue() { result = this.getValue().toInt() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlInteger */
|
||||
deprecated class YAMLInteger = YamlInteger;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing a floating point value.
|
||||
*
|
||||
@@ -179,8 +191,8 @@ class YAMLInteger extends YAMLScalar {
|
||||
* 6.626e-34
|
||||
* ```
|
||||
*/
|
||||
class YAMLFloat extends YAMLScalar {
|
||||
YAMLFloat() { this.hasStandardTypeTag("float") }
|
||||
class YamlFloat extends YamlScalar {
|
||||
YamlFloat() { this.hasStandardTypeTag("float") }
|
||||
|
||||
/**
|
||||
* Gets the value of this scalar, as a floating point number.
|
||||
@@ -188,6 +200,9 @@ class YAMLFloat extends YAMLScalar {
|
||||
float getFloatValue() { result = this.getValue().toFloat() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlFloat */
|
||||
deprecated class YAMLFloat = YamlFloat;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing a time stamp.
|
||||
*
|
||||
@@ -197,8 +212,8 @@ class YAMLFloat extends YAMLScalar {
|
||||
* 2001-12-15T02:59:43.1Z
|
||||
* ```
|
||||
*/
|
||||
class YAMLTimestamp extends YAMLScalar {
|
||||
YAMLTimestamp() { this.hasStandardTypeTag("timestamp") }
|
||||
class YamlTimestamp extends YamlScalar {
|
||||
YamlTimestamp() { this.hasStandardTypeTag("timestamp") }
|
||||
|
||||
/**
|
||||
* Gets the value of this scalar, as a date.
|
||||
@@ -206,6 +221,9 @@ class YAMLTimestamp extends YAMLScalar {
|
||||
date getDateValue() { result = this.getValue().toDate() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlTimestamp */
|
||||
deprecated class YAMLTimestamp = YamlTimestamp;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing a Boolean value.
|
||||
*
|
||||
@@ -215,8 +233,8 @@ class YAMLTimestamp extends YAMLScalar {
|
||||
* true
|
||||
* ```
|
||||
*/
|
||||
class YAMLBool extends YAMLScalar {
|
||||
YAMLBool() { this.hasStandardTypeTag("bool") }
|
||||
class YamlBool extends YamlScalar {
|
||||
YamlBool() { this.hasStandardTypeTag("bool") }
|
||||
|
||||
/**
|
||||
* Gets the value of this scalar, as a Boolean.
|
||||
@@ -224,6 +242,9 @@ class YAMLBool extends YAMLScalar {
|
||||
boolean getBoolValue() { if this.getValue() = "true" then result = true else result = false }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlBool */
|
||||
deprecated class YAMLBool = YamlBool;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing the null value.
|
||||
*
|
||||
@@ -233,10 +254,13 @@ class YAMLBool extends YAMLScalar {
|
||||
* null
|
||||
* ```
|
||||
*/
|
||||
class YAMLNull extends YAMLScalar {
|
||||
YAMLNull() { this.hasStandardTypeTag("null") }
|
||||
class YamlNull extends YamlScalar {
|
||||
YamlNull() { this.hasStandardTypeTag("null") }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlNull */
|
||||
deprecated class YAMLNull = YamlNull;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing a string value.
|
||||
*
|
||||
@@ -246,10 +270,13 @@ class YAMLNull extends YAMLScalar {
|
||||
* "hello"
|
||||
* ```
|
||||
*/
|
||||
class YAMLString extends YAMLScalar {
|
||||
YAMLString() { this.hasStandardTypeTag("str") }
|
||||
class YamlString extends YamlScalar {
|
||||
YamlString() { this.hasStandardTypeTag("str") }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlString */
|
||||
deprecated class YAMLString = YamlString;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing a merge key.
|
||||
*
|
||||
@@ -260,10 +287,13 @@ class YAMLString extends YAMLScalar {
|
||||
* << : *DEFAULTS # merge key
|
||||
* ```
|
||||
*/
|
||||
class YAMLMergeKey extends YAMLScalar {
|
||||
YAMLMergeKey() { this.hasStandardTypeTag("merge") }
|
||||
class YamlMergeKey extends YamlScalar {
|
||||
YamlMergeKey() { this.hasStandardTypeTag("merge") }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlMergeKey */
|
||||
deprecated class YAMLMergeKey = YamlMergeKey;
|
||||
|
||||
/**
|
||||
* A YAML scalar representing an `!include` directive.
|
||||
*
|
||||
@@ -271,11 +301,11 @@ class YAMLMergeKey extends YAMLScalar {
|
||||
* !include common.yaml
|
||||
* ```
|
||||
*/
|
||||
class YAMLInclude extends YAMLScalar {
|
||||
YAMLInclude() { this.getTag() = "!include" }
|
||||
class YamlInclude extends YamlScalar {
|
||||
YamlInclude() { this.getTag() = "!include" }
|
||||
|
||||
override YAMLValue eval() {
|
||||
exists(YAMLDocument targetDoc |
|
||||
override YamlValue eval() {
|
||||
exists(YamlDocument targetDoc |
|
||||
targetDoc.getFile().getAbsolutePath() = this.getTargetPath() and
|
||||
result = targetDoc.eval()
|
||||
)
|
||||
@@ -293,6 +323,9 @@ class YAMLInclude extends YAMLScalar {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlInclude */
|
||||
deprecated class YAMLInclude = YamlInclude;
|
||||
|
||||
/**
|
||||
* A YAML collection, that is, either a mapping or a sequence.
|
||||
*
|
||||
@@ -310,10 +343,13 @@ class YAMLInclude extends YAMLScalar {
|
||||
* - -blue
|
||||
* ```
|
||||
*/
|
||||
class YAMLCollection extends YAMLValue, @yaml_collection_node {
|
||||
override string getAPrimaryQlClass() { result = "YAMLCollection" }
|
||||
class YamlCollection extends YamlValue, @yaml_collection_node {
|
||||
override string getAPrimaryQlClass() { result = "YamlCollection" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlCollection */
|
||||
deprecated class YAMLCollection = YamlCollection;
|
||||
|
||||
/**
|
||||
* A YAML mapping.
|
||||
*
|
||||
@@ -324,11 +360,11 @@ class YAMLCollection extends YAMLValue, @yaml_collection_node {
|
||||
* y: 1
|
||||
* ```
|
||||
*/
|
||||
class YAMLMapping extends YAMLCollection, @yaml_mapping_node {
|
||||
class YamlMapping extends YamlCollection, @yaml_mapping_node {
|
||||
/**
|
||||
* Gets the `i`th key of this mapping.
|
||||
*/
|
||||
YAMLNode getKeyNode(int i) {
|
||||
YamlNode getKeyNode(int i) {
|
||||
i >= 0 and
|
||||
exists(int j | i = j - 1 and result = this.getChildNode(j))
|
||||
}
|
||||
@@ -336,7 +372,7 @@ class YAMLMapping extends YAMLCollection, @yaml_mapping_node {
|
||||
/**
|
||||
* Gets the `i`th value of this mapping.
|
||||
*/
|
||||
YAMLNode getValueNode(int i) {
|
||||
YamlNode getValueNode(int i) {
|
||||
i >= 0 and
|
||||
exists(int j | i = -j - 1 and result = this.getChildNode(j))
|
||||
}
|
||||
@@ -344,30 +380,33 @@ class YAMLMapping extends YAMLCollection, @yaml_mapping_node {
|
||||
/**
|
||||
* Gets the `i`th key of this mapping, as a YAML value.
|
||||
*/
|
||||
YAMLValue getKey(int i) { result = this.getKeyNode(i).eval() }
|
||||
YamlValue getKey(int i) { result = this.getKeyNode(i).eval() }
|
||||
|
||||
/**
|
||||
* Gets the `i`th value of this mapping, as a YAML value.
|
||||
*/
|
||||
YAMLValue getValue(int i) { result = this.getValueNode(i).eval() }
|
||||
YamlValue getValue(int i) { result = this.getValueNode(i).eval() }
|
||||
|
||||
/**
|
||||
* Holds if this mapping maps `key` to `value`.
|
||||
*/
|
||||
predicate maps(YAMLValue key, YAMLValue value) {
|
||||
predicate maps(YamlValue key, YamlValue value) {
|
||||
exists(int i | key = this.getKey(i) and value = this.getValue(i))
|
||||
or
|
||||
exists(YAMLMergeKey merge, YAMLMapping that | this.maps(merge, that) | that.maps(key, value))
|
||||
exists(YamlMergeKey merge, YamlMapping that | this.maps(merge, that) | that.maps(key, value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value that this mapping maps `key` to.
|
||||
*/
|
||||
YAMLValue lookup(string key) { exists(YAMLScalar s | s.getValue() = key | this.maps(s, result)) }
|
||||
YamlValue lookup(string key) { exists(YamlScalar s | s.getValue() = key | this.maps(s, result)) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "YAMLMapping" }
|
||||
override string getAPrimaryQlClass() { result = "YamlMapping" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlMapping */
|
||||
deprecated class YAMLMapping = YamlMapping;
|
||||
|
||||
/**
|
||||
* A YAML sequence.
|
||||
*
|
||||
@@ -379,20 +418,23 @@ class YAMLMapping extends YAMLCollection, @yaml_mapping_node {
|
||||
* - blue
|
||||
* ```
|
||||
*/
|
||||
class YAMLSequence extends YAMLCollection, @yaml_sequence_node {
|
||||
class YamlSequence extends YamlCollection, @yaml_sequence_node {
|
||||
/**
|
||||
* Gets the `i`th element in this sequence.
|
||||
*/
|
||||
YAMLNode getElementNode(int i) { result = this.getChildNode(i) }
|
||||
YamlNode getElementNode(int i) { result = this.getChildNode(i) }
|
||||
|
||||
/**
|
||||
* Gets the `i`th element in this sequence, as a YAML value.
|
||||
*/
|
||||
YAMLValue getElement(int i) { result = this.getElementNode(i).eval() }
|
||||
YamlValue getElement(int i) { result = this.getElementNode(i).eval() }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "YAMLSequence" }
|
||||
override string getAPrimaryQlClass() { result = "YamlSequence" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlSequence */
|
||||
deprecated class YAMLSequence = YamlSequence;
|
||||
|
||||
/**
|
||||
* A YAML alias node referring to a target anchor.
|
||||
*
|
||||
@@ -402,8 +444,8 @@ class YAMLSequence extends YAMLCollection, @yaml_sequence_node {
|
||||
* *DEFAULTS
|
||||
* ```
|
||||
*/
|
||||
class YAMLAliasNode extends YAMLNode, @yaml_alias_node {
|
||||
override YAMLValue eval() {
|
||||
class YamlAliasNode extends YamlNode, @yaml_alias_node {
|
||||
override YamlValue eval() {
|
||||
result.getAnchor() = this.getTarget() and
|
||||
result.getDocument() = this.getDocument()
|
||||
}
|
||||
@@ -413,9 +455,12 @@ class YAMLAliasNode extends YAMLNode, @yaml_alias_node {
|
||||
*/
|
||||
string getTarget() { yaml_aliases(this, result) }
|
||||
|
||||
override string getAPrimaryQlClass() { result = "YAMLAliasNode" }
|
||||
override string getAPrimaryQlClass() { result = "YamlAliasNode" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlAliasNode */
|
||||
deprecated class YAMLAliasNode = YamlAliasNode;
|
||||
|
||||
/**
|
||||
* A YAML document.
|
||||
*
|
||||
@@ -427,14 +472,17 @@ class YAMLAliasNode extends YAMLNode, @yaml_alias_node {
|
||||
* y: 1
|
||||
* ```
|
||||
*/
|
||||
class YAMLDocument extends YAMLNode {
|
||||
YAMLDocument() { not exists(this.getParentNode()) }
|
||||
class YamlDocument extends YamlNode {
|
||||
YamlDocument() { not exists(this.getParentNode()) }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlDocument */
|
||||
deprecated class YAMLDocument = YamlDocument;
|
||||
|
||||
/**
|
||||
* An error message produced by the YAML parser while processing a YAML file.
|
||||
*/
|
||||
class YAMLParseError extends @yaml_error, Error {
|
||||
class YamlParseError extends @yaml_error, Error {
|
||||
override Location getLocation() { yaml_locations(this, result) }
|
||||
|
||||
override string getMessage() { yaml_errors(this, result) }
|
||||
@@ -442,6 +490,9 @@ class YAMLParseError extends @yaml_error, Error {
|
||||
override string toString() { result = this.getMessage() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlParseError */
|
||||
deprecated class YAMLParseError = YamlParseError;
|
||||
|
||||
/**
|
||||
* A YAML node that may contain sub-nodes that can be identified by a name.
|
||||
* I.e. a mapping, sequence, or scalar.
|
||||
@@ -464,30 +515,30 @@ class YAMLParseError extends @yaml_error, Error {
|
||||
*
|
||||
* are equivalent.
|
||||
*/
|
||||
class YAMLMappingLikeNode extends YAMLNode {
|
||||
YAMLMappingLikeNode() {
|
||||
this instanceof YAMLMapping
|
||||
class YamlMappingLikeNode extends YamlNode {
|
||||
YamlMappingLikeNode() {
|
||||
this instanceof YamlMapping
|
||||
or
|
||||
this instanceof YAMLSequence
|
||||
this instanceof YamlSequence
|
||||
or
|
||||
this instanceof YAMLScalar
|
||||
this instanceof YamlScalar
|
||||
}
|
||||
|
||||
/** Gets sub-name identified by `name`. */
|
||||
YAMLNode getNode(string name) {
|
||||
exists(YAMLMapping mapping |
|
||||
YamlNode getNode(string name) {
|
||||
exists(YamlMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.lookup(name)
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence, YAMLNode node |
|
||||
exists(YamlSequence sequence, YamlNode node |
|
||||
sequence = this and
|
||||
sequence.getAChildNode() = node and
|
||||
node.eval().toString() = name and
|
||||
result = node
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
exists(YamlScalar scalar |
|
||||
scalar = this and
|
||||
scalar.getValue() = name and
|
||||
result = scalar
|
||||
@@ -496,19 +547,22 @@ class YAMLMappingLikeNode extends YAMLNode {
|
||||
|
||||
/** Gets the number of elements in this mapping or sequence. */
|
||||
int getElementCount() {
|
||||
exists(YAMLMapping mapping |
|
||||
exists(YamlMapping mapping |
|
||||
mapping = this and
|
||||
result = mapping.getNumChild() / 2
|
||||
)
|
||||
or
|
||||
exists(YAMLSequence sequence |
|
||||
exists(YamlSequence sequence |
|
||||
sequence = this and
|
||||
result = sequence.getNumChild()
|
||||
)
|
||||
or
|
||||
exists(YAMLScalar scalar |
|
||||
exists(YamlScalar scalar |
|
||||
scalar = this and
|
||||
result = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for YamlMappingLikeNode */
|
||||
deprecated class YAMLMappingLikeNode = YamlMappingLikeNode;
|
||||
|
||||
@@ -353,7 +353,7 @@ abstract class BarrierGuardNode extends DataFlow::Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow.
|
||||
* Holds if data flow node `guard` acts as a barrier for data flow.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
*/
|
||||
@@ -382,7 +382,7 @@ private predicate barrierGuardIsRelevant(BarrierGuardNode guard) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
|
||||
* Holds if data flow node `guard` acts as a barrier for data flow due to aliasing through
|
||||
* an access path.
|
||||
*
|
||||
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
|
||||
@@ -523,74 +523,6 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
|
||||
override predicate blocks(boolean outcome, Expr e) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Subclasses should extend `SharedFlowStep` instead, unless the subclass
|
||||
* is part of a query, in which case it should be moved into the `isAdditionalFlowStep` predicate
|
||||
* of the relevant data-flow configuration.
|
||||
* Other uses of the predicate in this class should instead reference the predicates in the
|
||||
* `SharedFlowStep::` module, such as `SharedFlowStep::step`.
|
||||
*
|
||||
* A data flow edge that should be added to all data flow configurations in
|
||||
* addition to standard data flow edges.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. Override `Configuration::isAdditionalFlowStep`
|
||||
* for analysis-specific flow steps.
|
||||
*/
|
||||
deprecated class AdditionalFlowStep = LegacyAdditionalFlowStep;
|
||||
|
||||
// Internal version of AdditionalFlowStep that we can reference without deprecation warnings.
|
||||
abstract private class LegacyAdditionalFlowStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge.
|
||||
*/
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a data flow edge
|
||||
* transforming values with label `predlbl` to have label `succlbl`.
|
||||
*/
|
||||
predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
|
||||
*/
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL. This API may change in the future.
|
||||
*
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow edge that should be added to all data flow configurations in
|
||||
* addition to standard data flow edges.
|
||||
@@ -713,40 +645,6 @@ module SharedFlowStep {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contributes subclasses of `AdditionalFlowStep` to `SharedFlowStep`.
|
||||
*/
|
||||
private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(LegacyAdditionalFlowStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate step(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
|
||||
DataFlow::FlowLabel succlbl
|
||||
) {
|
||||
any(LegacyAdditionalFlowStep s).step(pred, succ, predlbl, succlbl)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of pseudo-properties that are used in multiple files.
|
||||
*
|
||||
@@ -1155,7 +1053,7 @@ private predicate appendStep(
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a function invoked at `invk` may return an expression into which `input`,
|
||||
* Holds if a function invoked at `output` may return an expression into which `input`,
|
||||
* which is either an argument or a definition captured by the function, flows under
|
||||
* configuration `cfg`, possibly through callees.
|
||||
*/
|
||||
@@ -1369,56 +1267,47 @@ private predicate loadStep(
|
||||
* If `onlyRelevantInCall` is true, the `base` object will not be propagated out of return edges, because
|
||||
* the flow that originally reached `base.startProp` used a call edge.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
pragma[noopt]
|
||||
private predicate reachableFromStoreBase(
|
||||
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
|
||||
DataFlow::Configuration cfg, PathSummary summary, boolean onlyRelevantInCall
|
||||
DataFlow::Configuration cfg, TPathSummary summary, boolean onlyRelevantInCall
|
||||
) {
|
||||
exists(PathSummary s1, PathSummary s2, DataFlow::Node rhs |
|
||||
reachableFromSource(rhs, cfg, s1) and
|
||||
onlyRelevantInCall = s1.hasCall()
|
||||
or
|
||||
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
|
||||
|
|
||||
exists(TPathSummary s1, TPathSummary s2, DataFlow::Node rhs |
|
||||
storeStep(rhs, nd, startProp, cfg, s2) and
|
||||
endProp = startProp and
|
||||
base = nd and
|
||||
summary =
|
||||
MkPathSummary(false, s2.hasCall(), DataFlow::FlowLabel::data(), DataFlow::FlowLabel::data())
|
||||
exists(boolean hasCall, DataFlow::FlowLabel data |
|
||||
hasCall = hasCall(s2) and
|
||||
data = DataFlow::FlowLabel::data() and
|
||||
summary = MkPathSummary(false, hasCall, data, data)
|
||||
)
|
||||
|
|
||||
reachableFromSource(rhs, cfg, s1) and
|
||||
onlyRelevantInCall = hasCall(s1)
|
||||
or
|
||||
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
|
||||
)
|
||||
or
|
||||
exists(PathSummary newSummary, PathSummary oldSummary |
|
||||
reachableFromStoreBaseStep(startProp, endProp, base, nd, cfg, oldSummary, newSummary,
|
||||
onlyRelevantInCall) and
|
||||
summary = oldSummary.appendValuePreserving(newSummary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `base` is the base of a write to property `prop`, and `nd` is reachable
|
||||
* from `base` under configuration `cfg` (possibly through callees) along a path whose
|
||||
* last step is summarized by `newSummary`, and the previous steps are summarized
|
||||
* by `oldSummary`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate reachableFromStoreBaseStep(
|
||||
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
|
||||
DataFlow::Configuration cfg, PathSummary oldSummary, PathSummary newSummary,
|
||||
boolean onlyRelevantInCall
|
||||
) {
|
||||
exists(DataFlow::Node mid |
|
||||
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
|
||||
reachableFromStoreBase(startProp, endProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
|
||||
flowStep(mid, cfg, nd, newSummary) and
|
||||
onlyRelevantInCall.booleanAnd(newSummary.hasReturn()) = false
|
||||
exists(boolean hasReturn |
|
||||
hasReturn = newSummary.hasReturn() and
|
||||
onlyRelevantInCall.booleanAnd(hasReturn) = false
|
||||
)
|
||||
or
|
||||
exists(string midProp |
|
||||
reachableFromStoreBase(startProp, midProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
|
||||
isAdditionalLoadStoreStep(mid, nd, midProp, endProp, cfg) and
|
||||
newSummary = PathSummary::level()
|
||||
)
|
||||
|
|
||||
summary = oldSummary.appendValuePreserving(newSummary)
|
||||
)
|
||||
}
|
||||
|
||||
private boolean hasCall(PathSummary summary) { result = summary.hasCall() }
|
||||
|
||||
/**
|
||||
* Holds if the value of `pred` is written to a property of some base object, and that base
|
||||
* object may flow into the base of property read `succ` under configuration `cfg` along
|
||||
@@ -1758,7 +1647,7 @@ class PathNode extends TPathNode {
|
||||
this = MkSinkNode(nd, cfg)
|
||||
}
|
||||
|
||||
/** Holds if this path node wraps data-flow node `nd` and configuration `c`. */
|
||||
/** Holds if this path node wraps data-flow node `n` and configuration `c`. */
|
||||
predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c }
|
||||
|
||||
/** Gets the underlying configuration of this path node. */
|
||||
@@ -1873,7 +1762,7 @@ class MidPathNode extends PathNode, MkMidNode {
|
||||
|
||||
MidPathNode() { this = MkMidNode(nd, cfg, summary) }
|
||||
|
||||
/** Holds if this path node wraps data-flow node `nd`, configuration `c` and summary `s`. */
|
||||
/** Holds if this path node wraps data-flow node `n`, configuration `c` and summary `s`. */
|
||||
predicate wraps(DataFlow::Node n, DataFlow::Configuration c, PathSummary s) {
|
||||
nd = n and cfg = c and summary = s
|
||||
}
|
||||
@@ -2018,6 +1907,7 @@ private class BarrierGuardFunction extends Function {
|
||||
BarrierGuardNode guard;
|
||||
boolean guardOutcome;
|
||||
string label;
|
||||
int paramIndex;
|
||||
|
||||
BarrierGuardFunction() {
|
||||
barrierGuardIsRelevant(guard) and
|
||||
@@ -2041,8 +1931,7 @@ private class BarrierGuardFunction extends Function {
|
||||
sanitizedParameter.flowsToExpr(e) and
|
||||
barrierGuardBlocksExpr(guard, guardOutcome, e, label)
|
||||
) and
|
||||
getNumParameter() = 1 and
|
||||
sanitizedParameter.getParameter() = getParameter(0)
|
||||
sanitizedParameter.getParameter() = getParameter(paramIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2050,10 +1939,10 @@ private class BarrierGuardFunction extends Function {
|
||||
*/
|
||||
predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) {
|
||||
exists(DataFlow::Node arg |
|
||||
argumentPassing(pragma[only_bind_into](call), pragma[only_bind_into](arg),
|
||||
pragma[only_bind_into](this), pragma[only_bind_into](sanitizedParameter)) and
|
||||
arg.asExpr() = e and
|
||||
arg = call.getArgument(0) and
|
||||
call.getNumArgument() = 1 and
|
||||
argumentPassing(call, arg, this, sanitizedParameter) and
|
||||
arg = call.getArgument(paramIndex) and
|
||||
outcome = guardOutcome and
|
||||
lbl = label
|
||||
)
|
||||
|
||||
@@ -139,9 +139,12 @@ module DataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `DataFlow::ParameterNode::flowsTo()` instead.
|
||||
* Holds if this expression may refer to the initial value of parameter `p`.
|
||||
*/
|
||||
predicate mayReferToParameter(Parameter p) { parameterNode(p).(SourceNode).flowsTo(this) }
|
||||
deprecated predicate mayReferToParameter(Parameter p) {
|
||||
parameterNode(p).(SourceNode).flowsTo(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
@@ -1029,6 +1032,32 @@ module DataFlow {
|
||||
override File getFile() { result = function.getFile() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node representing the arguments object given to a function.
|
||||
*/
|
||||
class ReflectiveParametersNode extends DataFlow::Node, TReflectiveParametersNode {
|
||||
Function function;
|
||||
|
||||
ReflectiveParametersNode() { this = TReflectiveParametersNode(function) }
|
||||
|
||||
override string toString() { result = "'arguments' object of " + function.describe() }
|
||||
|
||||
override predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
function.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
override BasicBlock getBasicBlock() { result = function.getEntry().getBasicBlock() }
|
||||
|
||||
/**
|
||||
* Gets the function whose `arguments` object is represented by this node.
|
||||
*/
|
||||
Function getFunction() { result = function }
|
||||
|
||||
override File getFile() { result = function.getFile() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node representing the exceptions thrown by the callee of an invocation.
|
||||
*/
|
||||
@@ -1627,6 +1656,35 @@ module DataFlow {
|
||||
exists(Function f | not f.isAsyncOrGenerator() |
|
||||
DataFlow::functionReturnNode(succ, f) and pred = valueNode(f.getAReturnedExpr())
|
||||
)
|
||||
or
|
||||
// from a reflective params node to a reference to the arguments object.
|
||||
exists(DataFlow::ReflectiveParametersNode params, Function f | f = params.getFunction() |
|
||||
succ = f.getArgumentsVariable().getAnAccess().flow() and
|
||||
pred = params
|
||||
)
|
||||
}
|
||||
|
||||
/** A load step from a reflective parameter node to each parameter. */
|
||||
private class ReflectiveParamsStep extends PreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
|
||||
f.getFunction() = params.getFunction() and
|
||||
obj = params and
|
||||
prop = i + "" and
|
||||
element = f.getParameter(i)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint step from the reflective parameters node to any parameter. */
|
||||
private class ReflectiveParamsTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
|
||||
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
|
||||
f.getFunction() = params.getFunction() and
|
||||
obj = params and
|
||||
element = f.getAParameter()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1653,7 +1711,7 @@ module DataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the flow information for this node is incomplete.
|
||||
* Holds if the flow information for the node `nd`.
|
||||
*
|
||||
* This predicate holds if there may be a source flow node from which data flows into
|
||||
* this node, but that node is not a result of `getALocalSource()` due to analysis incompleteness.
|
||||
|
||||
@@ -472,6 +472,12 @@ class FunctionNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
/** Gets a parameter of this function. */
|
||||
ParameterNode getAParameter() { result = this.getParameter(_) }
|
||||
|
||||
/** Gets the parameter named `name` of this function, if any. */
|
||||
DataFlow::ParameterNode getParameterByName(string name) {
|
||||
result = this.getAParameter() and
|
||||
result.getName() = name
|
||||
}
|
||||
|
||||
/** Gets the number of parameters declared on this function. */
|
||||
int getNumParameter() { result = count(astNode.getAParameter()) }
|
||||
|
||||
@@ -556,6 +562,14 @@ class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
DataFlow::FunctionNode getPropertySetter(string name) {
|
||||
result = astNode.getPropertyByName(name).(PropertySetter).getInit().flow()
|
||||
}
|
||||
|
||||
/** Gets the value of a computed property name of this object literal, such as `x` in `{[x]: 1}` */
|
||||
DataFlow::Node getAComputedPropertyName() {
|
||||
exists(Property prop | prop = astNode.getAProperty() |
|
||||
prop.isComputed() and
|
||||
result = prop.getNameExpr().flow()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -498,7 +498,7 @@ private module ReturnPortal {
|
||||
invk = callee.getAnExitNode(isRemote).getAnInvocation()
|
||||
}
|
||||
|
||||
/** Holds if `ret` is a return node of a function flowing through `callee`. */
|
||||
/** Holds if `ret` is a return node of a function flowing through `base`. */
|
||||
predicate returns(Portal base, DataFlow::Node ret, boolean escapes) {
|
||||
ret = base.getAnEntryNode(escapes).getALocalSource().(DataFlow::FunctionNode).getAReturn()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ private import javascript
|
||||
private import semmle.javascript.dataflow.TypeTracking
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
* An alias for `SourceNode`.
|
||||
*/
|
||||
class LocalSourceNode = SourceNode;
|
||||
|
||||
/**
|
||||
* A source node for local data flow, that is, a node from which local data flow is tracked.
|
||||
*
|
||||
@@ -33,13 +38,7 @@ private import semmle.javascript.internal.CachedStages
|
||||
* import("fs")
|
||||
* ```
|
||||
*/
|
||||
class SourceNode extends DataFlow::Node {
|
||||
SourceNode() {
|
||||
this instanceof SourceNode::Range
|
||||
or
|
||||
none() and this instanceof SourceNode::Internal::RecursionGuard
|
||||
}
|
||||
|
||||
class SourceNode extends DataFlow::Node instanceof SourceNode::Range {
|
||||
/**
|
||||
* Holds if this node flows into `sink` in zero or more local (that is,
|
||||
* intra-procedural) steps.
|
||||
@@ -338,14 +337,10 @@ module SourceNode {
|
||||
or
|
||||
// Include return nodes because they model the implicit Promise creation in async functions.
|
||||
DataFlow::functionReturnNode(this, _)
|
||||
or
|
||||
this instanceof DataFlow::ReflectiveParametersNode
|
||||
}
|
||||
}
|
||||
|
||||
/** INTERNAL. DO NOT USE. */
|
||||
module Internal {
|
||||
/** An empty class that some tests are using to enforce that SourceNode is non-recursive. */
|
||||
abstract class RecursionGuard extends DataFlow::Node { }
|
||||
}
|
||||
}
|
||||
|
||||
private class NodeModuleSourcesNodes extends SourceNode::Range {
|
||||
|
||||
@@ -320,14 +320,6 @@ module TaintTracking {
|
||||
any(SharedTaintStep step).heuristicStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred -> succ` is an edge contributed by an `AdditionalTaintStep` instance.
|
||||
*/
|
||||
cached
|
||||
predicate legacyAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(InternalAdditionalTaintStep step).step(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* Public taint step relations.
|
||||
*/
|
||||
@@ -441,7 +433,6 @@ module TaintTracking {
|
||||
* Holds if `pred -> succ` is an edge used by all taint-tracking configurations.
|
||||
*/
|
||||
predicate sharedTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
Cached::legacyAdditionalTaintStep(pred, succ) or
|
||||
Cached::genericStep(pred, succ) or
|
||||
Cached::heuristicStep(pred, succ) or
|
||||
uriStep(pred, succ) or
|
||||
@@ -456,31 +447,6 @@ module TaintTracking {
|
||||
promiseStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Subclasses should extend `SharedTaintStep` instead, unless the subclass
|
||||
* is part of a query, in which case it should be moved into the `isAdditionalTaintStep` predicate
|
||||
* of the relevant taint-tracking configuration.
|
||||
* Other uses of the `step` relation in this class should instead use the `TaintTracking::sharedTaintStep`
|
||||
* predicate.
|
||||
*
|
||||
* A taint-propagating data flow edge that should be added to all taint tracking
|
||||
* configurations in addition to standard data flow edges.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. Override `Configuration::isAdditionalTaintStep`
|
||||
* for analysis-specific taint steps.
|
||||
*/
|
||||
deprecated class AdditionalTaintStep = InternalAdditionalTaintStep;
|
||||
|
||||
/** Internal version of `AdditionalTaintStep` that won't trigger deprecation warnings. */
|
||||
abstract private class InternalAdditionalTaintStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if `pred` → `succ` should be considered a taint-propagating
|
||||
* data flow edge.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node pred, DataFlow::Node succ);
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to the client side URL. */
|
||||
private DataFlow::SourceNode clientSideUrlRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
@@ -516,17 +482,13 @@ module TaintTracking {
|
||||
*/
|
||||
private class HeapTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Expr e, Expr f | e = succ.asExpr() and f = pred.asExpr() |
|
||||
exists(Property prop | e.(ObjectExpr).getAProperty() = prop |
|
||||
prop.isComputed() and f = prop.getNameExpr()
|
||||
)
|
||||
or
|
||||
// spreading a tainted object into an object literal gives a tainted object
|
||||
e.(ObjectExpr).getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand() = f
|
||||
or
|
||||
// spreading a tainted value into an array literal gives a tainted array
|
||||
e.(ArrayExpr).getAnElement().(SpreadElement).getOperand() = f
|
||||
)
|
||||
succ.(DataFlow::ObjectLiteralNode).getAComputedPropertyName() = pred
|
||||
or
|
||||
// spreading a tainted object into an object literal gives a tainted object
|
||||
succ.(DataFlow::ObjectLiteralNode).getASpreadProperty() = pred
|
||||
or
|
||||
// spreading a tainted value into an array literal gives a tainted array
|
||||
succ.(DataFlow::ArrayCreationNode).getASpreadArgument() = pred
|
||||
or
|
||||
// arrays with tainted elements and objects with tainted property names are tainted
|
||||
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
|
||||
@@ -580,16 +542,16 @@ module TaintTracking {
|
||||
*/
|
||||
private class ComputedPropWriteTaintStep extends SharedTaintStep {
|
||||
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(AssignExpr assgn, IndexExpr idx, DataFlow::SourceNode obj |
|
||||
assgn.getTarget() = idx and
|
||||
obj.flowsToExpr(idx.getBase()) and
|
||||
not exists(idx.getPropertyName()) and
|
||||
pred = DataFlow::valueNode(assgn.getRhs()) and
|
||||
exists(DataFlow::PropWrite assgn, DataFlow::SourceNode obj |
|
||||
not exists(assgn.getPropertyName()) and
|
||||
not assgn.getWriteNode() instanceof Property and // not a write inside an object literal
|
||||
pred = assgn.getRhs() and
|
||||
assgn = obj.getAPropertyWrite() and
|
||||
succ = obj
|
||||
|
|
||||
obj instanceof DataFlow::ObjectLiteralNode
|
||||
or
|
||||
obj.getAPropertyRead("length").flowsToExpr(idx.getPropertyNameExpr())
|
||||
obj.getAPropertyRead("length").flowsToExpr(assgn.getPropertyNameExpr())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -614,8 +576,8 @@ module TaintTracking {
|
||||
override predicate stringManipulationStep(DataFlow::Node pred, DataFlow::Node target) {
|
||||
exists(DataFlow::ValueNode succ | target = succ |
|
||||
// string operations that propagate taint
|
||||
exists(string name | name = succ.getAstNode().(MethodCallExpr).getMethodName() |
|
||||
pred.asExpr() = succ.getAstNode().(MethodCallExpr).getReceiver() and
|
||||
exists(string name | name = succ.(DataFlow::MethodCallNode).getMethodName() |
|
||||
pred = succ.(DataFlow::MethodCallNode).getReceiver() and
|
||||
(
|
||||
// sorted, interesting, properties of String.prototype
|
||||
name =
|
||||
@@ -634,7 +596,7 @@ module TaintTracking {
|
||||
name = "join"
|
||||
)
|
||||
or
|
||||
exists(int i | pred.asExpr() = succ.getAstNode().(MethodCallExpr).getArgument(i) |
|
||||
exists(int i | pred = succ.(DataFlow::MethodCallNode).getArgument(i) |
|
||||
name = "concat"
|
||||
or
|
||||
name = ["replace", "replaceAll"] and i = 1
|
||||
@@ -649,10 +611,10 @@ module TaintTracking {
|
||||
)
|
||||
or
|
||||
// String.fromCharCode and String.fromCodePoint
|
||||
exists(int i, MethodCallExpr mce |
|
||||
mce = succ.getAstNode() and
|
||||
pred.asExpr() = mce.getArgument(i) and
|
||||
(mce.getMethodName() = "fromCharCode" or mce.getMethodName() = "fromCodePoint")
|
||||
exists(int i, DataFlow::MethodCallNode mcn |
|
||||
mcn = succ and
|
||||
pred = mcn.getArgument(i) and
|
||||
mcn.getMethodName() = ["fromCharCode", "fromCodePoint"]
|
||||
)
|
||||
or
|
||||
// `(encode|decode)URI(Component)?` propagate taint
|
||||
@@ -778,11 +740,11 @@ module TaintTracking {
|
||||
* the parameters in `input`.
|
||||
*/
|
||||
predicate isUrlSearchParams(DataFlow::SourceNode params, DataFlow::Node input) {
|
||||
exists(DataFlow::GlobalVarRefNode urlSearchParams, NewExpr newUrlSearchParams |
|
||||
exists(DataFlow::GlobalVarRefNode urlSearchParams, DataFlow::NewNode newUrlSearchParams |
|
||||
urlSearchParams.getName() = "URLSearchParams" and
|
||||
newUrlSearchParams = urlSearchParams.getAnInstantiation().asExpr() and
|
||||
params.asExpr() = newUrlSearchParams and
|
||||
input.asExpr() = newUrlSearchParams.getArgument(0)
|
||||
newUrlSearchParams = urlSearchParams.getAnInstantiation() and
|
||||
params = newUrlSearchParams and
|
||||
input = newUrlSearchParams.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -831,7 +793,7 @@ module TaintTracking {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `loadStep` should be copied from the object `pred` to the property `storeStep` of object `succ`.
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*
|
||||
* This step is used to copy the value of our pseudo-property that can later be accessed using a `get` or `getAll` call.
|
||||
* For an expression `url.searchParams`, the property `hiddenUrlPseudoProperty()` from the `url` object is stored in the property `getableUrlPseudoProperty()` on `url.searchParams`.
|
||||
@@ -1027,18 +989,16 @@ module TaintTracking {
|
||||
class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode,
|
||||
DataFlow::MethodCallNode {
|
||||
WhitelistContainmentCallSanitizer() {
|
||||
exists(string name |
|
||||
name = "contains" or
|
||||
name = "has" or
|
||||
name = "hasOwnProperty"
|
||||
|
|
||||
this.getMethodName() = name
|
||||
)
|
||||
this.getMethodName() = ["contains", "has", "hasOwnProperty", "hasOwn"]
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = this.getArgument(0).asExpr()
|
||||
exists(int propertyIndex |
|
||||
if this.getMethodName() = "hasOwn" then propertyIndex = 1 else propertyIndex = 0
|
||||
|
|
||||
outcome = true and
|
||||
e = this.getArgument(propertyIndex).asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate appliesTo(Configuration cfg) { any() }
|
||||
@@ -1128,6 +1088,19 @@ module TaintTracking {
|
||||
)
|
||||
}
|
||||
|
||||
/** A test for the value of `typeof x`, restricting the potential types of `x`. */
|
||||
predicate isStringTypeGuard(EqualityTest test, Expr operand, boolean polarity) {
|
||||
exists(TypeofTag tag | TaintTracking::isTypeofGuard(test, operand, tag) |
|
||||
// typeof x === "string" sanitizes `x` when it evaluates to false
|
||||
tag = "string" and
|
||||
polarity = test.getPolarity().booleanNot()
|
||||
or
|
||||
// typeof x === "object" sanitizes `x` when it evaluates to true
|
||||
tag != "string" and
|
||||
polarity = test.getPolarity()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `guard` is a test that checks if `operand` is a number. */
|
||||
predicate isNumberGuard(DataFlow::Node guard, Expr operand, boolean polarity) {
|
||||
exists(DataFlow::CallNode isNaN |
|
||||
|
||||
@@ -70,6 +70,12 @@ class TypeTracker extends TTypeTracker {
|
||||
step = LoadStep(prop) and result = MkTypeTracker(hasCall, "")
|
||||
or
|
||||
exists(string p | step = StoreStep(p) and prop = "" and result = MkTypeTracker(hasCall, p))
|
||||
or
|
||||
exists(PropertySet props |
|
||||
step = WithoutPropStep(props) and
|
||||
not prop = props.getAProperty() and
|
||||
result = this
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
@@ -306,7 +312,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
* result = < some API call >.getArgument(< n >)
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* t = t2.smallstep(result, myType(t2))
|
||||
* t2 = t.smallstep(result, myType(t2))
|
||||
* )
|
||||
* }
|
||||
*
|
||||
@@ -373,6 +379,26 @@ class SharedTypeTrackingStep extends Unit {
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ` but block flow of `props` through here.
|
||||
*
|
||||
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
|
||||
*/
|
||||
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A representative for a set of property names.
|
||||
*
|
||||
* Currently this is used to denote a set of properties in `withoutPropStep`.
|
||||
*/
|
||||
abstract class PropertySet extends string {
|
||||
bindingset[this]
|
||||
PropertySet() { any() }
|
||||
|
||||
/** Gets a property contained in this property set. */
|
||||
abstract string getAProperty();
|
||||
}
|
||||
|
||||
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
|
||||
@@ -413,59 +439,13 @@ module SharedTypeTrackingStep {
|
||||
) {
|
||||
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `SharedTypeTrackingStep` instead.
|
||||
*
|
||||
* A data flow edge that should be followed by type tracking.
|
||||
*
|
||||
* Unlike `AdditionalFlowStep`, this type of edge does not affect
|
||||
* the local data flow graph, and is not used by data-flow configurations.
|
||||
*
|
||||
* Note: For performance reasons, all subclasses of this class should be part
|
||||
* of the standard library. For query-specific steps, consider including the
|
||||
* custom steps in the type-tracking predicate itself.
|
||||
*/
|
||||
deprecated class AdditionalTypeTrackingStep = LegacyTypeTrackingStep;
|
||||
|
||||
// Internal version of AdditionalTypeTrackingStep that we can reference without deprecation warnings.
|
||||
abstract private class LegacyTypeTrackingStep extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ`.
|
||||
*/
|
||||
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
|
||||
* Holds if type-tracking should step from `pred` to `succ` but block flow of `prop` through here.
|
||||
*
|
||||
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
|
||||
*/
|
||||
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
|
||||
*/
|
||||
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
|
||||
*/
|
||||
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
|
||||
}
|
||||
|
||||
private class LegacyStepAsSharedTypeTrackingStep extends SharedTypeTrackingStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
any(LegacyTypeTrackingStep s).step(pred, succ)
|
||||
}
|
||||
|
||||
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).storeStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).loadStep(pred, succ, prop)
|
||||
}
|
||||
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
any(LegacyTypeTrackingStep s).loadStoreStep(pred, succ, prop)
|
||||
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) {
|
||||
any(SharedTypeTrackingStep s).withoutPropStep(pred, succ, props)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,18 @@ module CallGraph {
|
||||
result = getAFunctionReference(outer, 0, t.continue()).getAnInvocation() and
|
||||
locallyReturnedFunction(outer, function)
|
||||
)
|
||||
or
|
||||
// dynamic dispatch to unknown property of an object
|
||||
exists(DataFlow::ObjectLiteralNode obj, DataFlow::PropRead read |
|
||||
getAFunctionReference(function, 0, t.continue()) = obj.getAPropertySource() and
|
||||
obj.getAPropertyRead() = read and
|
||||
not exists(read.getPropertyName()) and
|
||||
result = read and
|
||||
// there exists only local reads of the object, nothing else.
|
||||
forex(DataFlow::Node ref | ref = obj.getALocalUse() and exists(ref.asExpr()) |
|
||||
ref = [obj, any(DataFlow::PropRead r).getBase()]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate locallyReturnedFunction(
|
||||
@@ -190,7 +202,7 @@ module CallGraph {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ref` installs an accessor on an object. Such property writes should not
|
||||
* Holds if `write` installs an accessor on an object. Such property writes should not
|
||||
* be considered calls to an accessor.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
|
||||
@@ -31,4 +31,5 @@ newtype TNode =
|
||||
TExceptionalFunctionReturnNode(Function f) or
|
||||
TExceptionalInvocationReturnNode(InvokeExpr e) or
|
||||
TGlobalAccessPathRoot() or
|
||||
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag)
|
||||
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag) or
|
||||
TReflectiveParametersNode(Function f)
|
||||
|
||||
@@ -224,6 +224,14 @@ private module CachedSteps {
|
||||
or
|
||||
arg = invk.(DataFlow::PropWrite).getRhs() and
|
||||
parm = DataFlow::parameterNode(f.getParameter(0))
|
||||
or
|
||||
calls(invk, f) and
|
||||
exists(MethodCallExpr apply |
|
||||
invk = DataFlow::reflectiveCallNode(apply) and
|
||||
apply.getMethodName() = "apply" and
|
||||
arg = apply.getArgument(1).flow()
|
||||
) and
|
||||
parm.(DataFlow::ReflectiveParametersNode).getFunction() = f
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node callback, int i, Parameter p, Function target |
|
||||
|
||||
@@ -171,10 +171,10 @@ abstract class CallWithNonLocalAnalyzedReturnFlow extends DataFlow::AnalyzedValu
|
||||
/**
|
||||
* Flow analysis for the return value of IIFEs.
|
||||
*/
|
||||
private class IIFEWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
|
||||
private class IifeWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
|
||||
ImmediatelyInvokedFunctionExpr iife;
|
||||
|
||||
IIFEWithAnalyzedReturnFlow() { astNode = iife.getInvocation() }
|
||||
IifeWithAnalyzedReturnFlow() { astNode = iife.getInvocation() }
|
||||
|
||||
override AnalyzedFunction getACallee() { result = iife.analyze() }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ private import AbstractPropertiesImpl
|
||||
private import AbstractValuesImpl
|
||||
|
||||
/**
|
||||
* Flow analysis for property reads, either explicitly (`x.p` or `x[e]`) or
|
||||
* An analyzed property read, either explicitly (`x.p` or `x[e]`) or
|
||||
* implicitly.
|
||||
*/
|
||||
abstract class AnalyzedPropertyRead extends DataFlow::AnalyzedNode {
|
||||
@@ -86,7 +86,7 @@ pragma[noinline]
|
||||
private predicate isTrackedPropertyName(string prop) { exists(MkAbstractProperty(_, prop)) }
|
||||
|
||||
/**
|
||||
* Flow analysis for property writes, including exports (which are
|
||||
* An analyzed property write, including exports (which are
|
||||
* modeled as assignments to `module.exports`).
|
||||
*/
|
||||
abstract class AnalyzedPropertyWrite extends DataFlow::Node {
|
||||
|
||||
@@ -45,7 +45,8 @@ private module Cached {
|
||||
CopyStep(PropertyName prop) or
|
||||
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
|
||||
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
|
||||
}
|
||||
} or
|
||||
WithoutPropStep(PropertySet props) { SharedTypeTrackingStep::withoutPropStep(_, _, props) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +111,11 @@ private module Cached {
|
||||
summary = CopyStep(prop)
|
||||
)
|
||||
or
|
||||
exists(PropertySet props |
|
||||
SharedTypeTrackingStep::withoutPropStep(pred, succ, props) and
|
||||
summary = WithoutPropStep(props)
|
||||
)
|
||||
or
|
||||
exists(string fromProp, string toProp |
|
||||
SharedTypeTrackingStep::loadStoreStep(pred, succ, fromProp, toProp) and
|
||||
summary = LoadStoreStep(fromProp, toProp)
|
||||
@@ -194,6 +200,8 @@ class StepSummary extends TStepSummary {
|
||||
or
|
||||
exists(string prop | this = CopyStep(prop) | result = "copy " + prop)
|
||||
or
|
||||
exists(string prop | this = WithoutPropStep(prop) | result = "without " + prop)
|
||||
or
|
||||
exists(string fromProp, string toProp | this = LoadStoreStep(fromProp, toProp) |
|
||||
result = "load " + fromProp + " and store to " + toProp
|
||||
)
|
||||
|
||||
@@ -692,10 +692,10 @@ abstract private class CallWithAnalyzedParameters extends FunctionWithAnalyzedPa
|
||||
/**
|
||||
* Flow analysis for simple parameters of IIFEs.
|
||||
*/
|
||||
private class IIFEWithAnalyzedParameters extends CallWithAnalyzedParameters {
|
||||
private class IifeWithAnalyzedParameters extends CallWithAnalyzedParameters {
|
||||
ImmediatelyInvokedFunctionExpr iife;
|
||||
|
||||
IIFEWithAnalyzedParameters() {
|
||||
IifeWithAnalyzedParameters() {
|
||||
this = iife and
|
||||
iife.getInvocationKind() = "direct"
|
||||
}
|
||||
|
||||
@@ -276,15 +276,15 @@ class ExternalScriptDependency extends ScriptDependency, @xmlattribute {
|
||||
/**
|
||||
* A dependency on GWT indicated by a GWT header script.
|
||||
*/
|
||||
private class GWTDependency extends ScriptDependency {
|
||||
GWTDependency() { this instanceof GWTHeader }
|
||||
private class GwtDependency extends ScriptDependency {
|
||||
GwtDependency() { this instanceof GwtHeader }
|
||||
|
||||
override predicate info(string id, string v) {
|
||||
id = "gwt" and
|
||||
exists(GWTHeader h | h = this |
|
||||
v = h.getGWTVersion()
|
||||
exists(GwtHeader h | h = this |
|
||||
v = h.getGwtVersion()
|
||||
or
|
||||
not exists(h.getGWTVersion()) and v = "unknown"
|
||||
not exists(h.getGwtVersion()) and v = "unknown"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -904,8 +904,8 @@ private class SinonJS extends FrameworkLibraryWithGenericUrl, FrameworkLibraryWi
|
||||
/**
|
||||
* The TinyMCE framework.
|
||||
*/
|
||||
private class TinyMCE extends FrameworkLibraryWithGenericUrl {
|
||||
TinyMCE() { this = "tinymce" }
|
||||
private class TinyMce extends FrameworkLibraryWithGenericUrl {
|
||||
TinyMce() { this = "tinymce" }
|
||||
|
||||
override string getAnAlias() { result = "jquery.tinymce" or result = "tinymce.jquery" }
|
||||
}
|
||||
|
||||
@@ -8,19 +8,19 @@ module AWS {
|
||||
/**
|
||||
* Holds if the `i`th argument of `invk` is an object hash for `AWS.Config`.
|
||||
*/
|
||||
private predicate takesConfigurationObject(InvokeExpr invk, int i) {
|
||||
private predicate takesConfigurationObject(DataFlow::InvokeNode invk, int i) {
|
||||
exists(DataFlow::ModuleImportNode mod | mod.getPath() = "aws-sdk" |
|
||||
// `AWS.config.update(nd)`
|
||||
invk = mod.getAPropertyRead("config").getAMemberCall("update").asExpr() and
|
||||
invk = mod.getAPropertyRead("config").getAMemberCall("update") and
|
||||
i = 0
|
||||
or
|
||||
exists(DataFlow::SourceNode cfg | cfg = mod.getAConstructorInvocation("Config") |
|
||||
// `new AWS.Config(nd)`
|
||||
invk = cfg.asExpr() and
|
||||
invk = cfg and
|
||||
i = 0
|
||||
or
|
||||
// `var config = new AWS.Config(...); config.update(nd);`
|
||||
invk = cfg.getAMemberCall("update").asExpr() and
|
||||
invk = cfg.getAMemberCall("update") and
|
||||
i = 0
|
||||
)
|
||||
)
|
||||
@@ -29,13 +29,13 @@ module AWS {
|
||||
/**
|
||||
* An expression that is used as an AWS config value: `{ accessKeyId: <user>, secretAccessKey: <password>}`.
|
||||
*/
|
||||
class Credentials extends CredentialsExpr {
|
||||
class Credentials extends CredentialsNode {
|
||||
string kind;
|
||||
|
||||
Credentials() {
|
||||
exists(string prop, InvokeExpr invk, int i |
|
||||
exists(string prop, DataFlow::InvokeNode invk, int i |
|
||||
takesConfigurationObject(invk, i) and
|
||||
invk.hasOptionArgument(i, prop, this)
|
||||
this = invk.getOptionArgument(i, prop)
|
||||
|
|
||||
prop = "accessKeyId" and kind = "user name"
|
||||
or
|
||||
|
||||
@@ -68,7 +68,7 @@ private class TrackStringsInAngularCode extends DataFlow::SourceNode::Range, Dat
|
||||
*/
|
||||
private DataFlow::CallNode angularModuleCall(string name) {
|
||||
result = angular().getAMemberCall("module") and
|
||||
result.getArgument(0).asExpr().mayHaveStringValue(name)
|
||||
result.getArgument(0).mayHaveStringValue(name)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,8 +105,8 @@ class AngularModule extends TAngularModule {
|
||||
/**
|
||||
* Get the array of dependencies from this module's definition.
|
||||
*/
|
||||
ArrayExpr getDependencyArray() {
|
||||
this.getADefinition().getArgument(1).getALocalSource().asExpr() = result
|
||||
DataFlow::ArrayCreationNode getDependencyArray() {
|
||||
this.getADefinition().getArgument(1).getALocalSource() = result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,14 +160,10 @@ class ModuleApiCall extends DataFlow::CallNode {
|
||||
string getMethodName() { result = methodName }
|
||||
}
|
||||
|
||||
class ModuleApiCallDependencyInjection extends DependencyInjection {
|
||||
ModuleApiCall call;
|
||||
class ModuleApiCallDependencyInjection extends DependencyInjection instanceof ModuleApiCall {
|
||||
string methodName;
|
||||
|
||||
ModuleApiCallDependencyInjection() {
|
||||
this = call and
|
||||
methodName = call.getMethodName()
|
||||
}
|
||||
ModuleApiCallDependencyInjection() { methodName = super.getMethodName() }
|
||||
|
||||
/**
|
||||
* Gets the argument position for this method call that expects an injectable function.
|
||||
@@ -183,7 +179,7 @@ class ModuleApiCallDependencyInjection extends DependencyInjection {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInjectableFunction() {
|
||||
result = call.getArgument(this.injectableArgPos())
|
||||
result = super.getArgument(this.injectableArgPos())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,10 +262,10 @@ abstract class CustomDirective extends DirectiveInstance {
|
||||
DataFlow::SourceNode getMember(string name) { result.flowsTo(this.getMemberInit(name)) }
|
||||
|
||||
/** Gets the method `name` of this directive. */
|
||||
Function getMethod(string name) { DataFlow::valueNode(result) = this.getMember(name) }
|
||||
DataFlow::FunctionNode getMethod(string name) { result = this.getMember(name) }
|
||||
|
||||
/** Gets a link function of this directive. */
|
||||
abstract Function getALinkFunction();
|
||||
abstract DataFlow::FunctionNode getALinkFunction();
|
||||
|
||||
/** Holds if this directive's properties are bound to the controller. */
|
||||
abstract predicate bindsToController();
|
||||
@@ -284,7 +280,7 @@ abstract class CustomDirective extends DirectiveInstance {
|
||||
InjectableFunction getController() { result = this.getMember("controller") }
|
||||
|
||||
/** Gets the template URL of this directive, if any. */
|
||||
string getTemplateUrl() { this.getMember("templateUrl").asExpr().mayHaveStringValue(result) }
|
||||
string getTemplateUrl() { this.getMember("templateUrl").mayHaveStringValue(result) }
|
||||
|
||||
/**
|
||||
* Gets a template file for this directive, if any.
|
||||
@@ -302,9 +298,7 @@ abstract class CustomDirective extends DirectiveInstance {
|
||||
else result = DirectiveInstance.super.getAScope()
|
||||
}
|
||||
|
||||
private string getRestrictionString() {
|
||||
this.getMember("restrict").asExpr().mayHaveStringValue(result)
|
||||
}
|
||||
private string getRestrictionString() { this.getMember("restrict").mayHaveStringValue(result) }
|
||||
|
||||
private predicate hasTargetType(DirectiveTargetType type) {
|
||||
not exists(this.getRestrictionString()) or
|
||||
@@ -332,10 +326,10 @@ class GeneralDirective extends CustomDirective, MkCustomDirective {
|
||||
|
||||
/** Gets a node that contributes to the return value of the factory function. */
|
||||
override DataFlow::SourceNode getAnInstantiation() {
|
||||
exists(Function factory, InjectableFunction f |
|
||||
exists(DataFlow::FunctionNode factory, InjectableFunction f |
|
||||
f = definition.getAFactoryFunction() and
|
||||
factory = f.asFunction() and
|
||||
result.flowsToExpr(factory.getAReturnedExpr())
|
||||
result.flowsTo(factory.getAReturn())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -344,7 +338,7 @@ class GeneralDirective extends CustomDirective, MkCustomDirective {
|
||||
}
|
||||
|
||||
/** Gets the compile function of this directive, if any. */
|
||||
Function getCompileFunction() { result = this.getMethod("compile") }
|
||||
DataFlow::FunctionNode getCompileFunction() { result = this.getMethod("compile") }
|
||||
|
||||
/**
|
||||
* Gets a pre/post link function of this directive defined on its definition object.
|
||||
@@ -365,9 +359,8 @@ class GeneralDirective extends CustomDirective, MkCustomDirective {
|
||||
result = this.getMember("link").getAPropertySource(kind)
|
||||
or
|
||||
// { compile: function() { ... return link; } }
|
||||
exists(Expr compileReturn, DataFlow::SourceNode compileReturnSrc |
|
||||
compileReturn = this.getCompileFunction().getAReturnedExpr() and
|
||||
compileReturnSrc.flowsToExpr(compileReturn)
|
||||
exists(DataFlow::SourceNode compileReturnSrc |
|
||||
compileReturnSrc.flowsTo(this.getCompileFunction().getAReturn())
|
||||
|
|
||||
// link = function postLink() { ... }
|
||||
kind = "post" and
|
||||
@@ -380,18 +373,20 @@ class GeneralDirective extends CustomDirective, MkCustomDirective {
|
||||
}
|
||||
|
||||
/** Gets the pre-link function of this directive. */
|
||||
Function getPreLinkFunction() { result = this.getLinkFunction("pre").getAstNode() }
|
||||
DataFlow::FunctionNode getPreLinkFunction() { result = this.getLinkFunction("pre") }
|
||||
|
||||
/** Gets the post-link function of this directive. */
|
||||
Function getPostLinkFunction() { result = this.getLinkFunction("post").getAstNode() }
|
||||
DataFlow::FunctionNode getPostLinkFunction() { result = this.getLinkFunction("post") }
|
||||
|
||||
override Function getALinkFunction() { result = this.getLinkFunction(_).getAstNode() }
|
||||
override DataFlow::FunctionNode getALinkFunction() { result = this.getLinkFunction(_) }
|
||||
|
||||
override predicate bindsToController() {
|
||||
this.getMemberInit("bindToController").asExpr().mayHaveBooleanValue(true)
|
||||
this.getMemberInit("bindToController").mayHaveBooleanValue(true)
|
||||
}
|
||||
|
||||
override predicate hasIsolateScope() { this.getMember("scope").asExpr() instanceof ObjectExpr }
|
||||
override predicate hasIsolateScope() {
|
||||
this.getMember("scope") instanceof DataFlow::ObjectLiteralNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,7 +407,7 @@ class ComponentDirective extends CustomDirective, MkCustomComponent {
|
||||
comp.getConfig().hasPropertyWrite(name, result)
|
||||
}
|
||||
|
||||
override Function getALinkFunction() { none() }
|
||||
override DataFlow::FunctionNode getALinkFunction() { none() }
|
||||
|
||||
override predicate bindsToController() { none() }
|
||||
|
||||
@@ -561,13 +556,12 @@ class DirectiveTargetName extends string {
|
||||
*
|
||||
* See https://docs.angularjs.org/api/ng/service/$location for details.
|
||||
*/
|
||||
private class LocationFlowSource extends RemoteFlowSource {
|
||||
private class LocationFlowSource extends RemoteFlowSource instanceof DataFlow::MethodCallNode {
|
||||
LocationFlowSource() {
|
||||
exists(ServiceReference service, MethodCallExpr mce, string m, int n |
|
||||
exists(ServiceReference service, string m, int n |
|
||||
service.getName() = "$location" and
|
||||
this.asExpr() = mce and
|
||||
mce = service.getAMethodCall(m) and
|
||||
n = mce.getNumArgument()
|
||||
this = service.getAMethodCall(m) and
|
||||
n = super.getNumArgument()
|
||||
|
|
||||
m = "search" and n < 2
|
||||
or
|
||||
@@ -605,7 +599,7 @@ private class JQLiteObject extends JQuery::ObjectSource::Range {
|
||||
JQLiteObject() {
|
||||
this = angular().getAMemberCall("element")
|
||||
or
|
||||
exists(Parameter param | this = DataFlow::parameterNode(param) |
|
||||
exists(DataFlow::ParameterNode param | this = param |
|
||||
// element parameters to user-functions invoked by AngularJS
|
||||
param = any(LinkFunction link).getElementParameter()
|
||||
or
|
||||
@@ -613,13 +607,13 @@ private class JQLiteObject extends JQuery::ObjectSource::Range {
|
||||
param = d.getCompileFunction().getParameter(0)
|
||||
or
|
||||
exists(DataFlow::FunctionNode f, int i |
|
||||
f.flowsToExpr(d.getCompileFunction().getAReturnedExpr()) and i = 1
|
||||
f.flowsTo(d.getCompileFunction().getAReturn()) and i = 1
|
||||
or
|
||||
f = d.getMember("template") and i = 0
|
||||
or
|
||||
f = d.getMember("templateUrl") and i = 0
|
||||
|
|
||||
param = f.getAstNode().(Function).getParameter(i)
|
||||
param = f.getParameter(i)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -631,99 +625,111 @@ private class JQLiteObject extends JQuery::ObjectSource::Range {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `AngularJSCallNode` instead.
|
||||
* A call to an AngularJS function.
|
||||
*
|
||||
* Used for exposing behavior that is similar to the behavior of other libraries.
|
||||
*/
|
||||
abstract class AngularJSCall extends CallExpr {
|
||||
deprecated class AngularJSCall extends CallExpr {
|
||||
AngularJSCallNode node;
|
||||
|
||||
AngularJSCall() { this.flow() = node }
|
||||
|
||||
/** Holds if `e` is an argument that this call interprets as HTML. */
|
||||
deprecated predicate interpretsArgumentAsHtml(Expr e) { node.interpretsArgumentAsHtml(e.flow()) }
|
||||
|
||||
/** Holds if `e` is an argument that this call stores globally, e.g. in a cookie. */
|
||||
deprecated predicate storesArgumentGlobally(Expr e) { node.storesArgumentGlobally(e.flow()) }
|
||||
|
||||
/** Holds if `e` is an argument that this call interprets as code. */
|
||||
deprecated predicate interpretsArgumentAsCode(Expr e) { node.interpretsArgumentAsCode(e.flow()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to an AngularJS function.
|
||||
*
|
||||
* Used for exposing behavior that is similar to the behavior of other libraries.
|
||||
*/
|
||||
abstract class AngularJSCallNode extends DataFlow::CallNode {
|
||||
/**
|
||||
* Holds if `e` is an argument that this call interprets as HTML.
|
||||
*/
|
||||
abstract predicate interpretsArgumentAsHtml(Expr e);
|
||||
abstract predicate interpretsArgumentAsHtml(DataFlow::Node e);
|
||||
|
||||
/**
|
||||
* Holds if `e` is an argument that this call stores globally, e.g. in a cookie.
|
||||
*/
|
||||
abstract predicate storesArgumentGlobally(Expr e);
|
||||
abstract predicate storesArgumentGlobally(DataFlow::Node e);
|
||||
|
||||
/**
|
||||
* Holds if `e` is an argument that this call interprets as code.
|
||||
*/
|
||||
abstract predicate interpretsArgumentAsCode(Expr e);
|
||||
abstract predicate interpretsArgumentAsCode(DataFlow::Node e);
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on the AngularJS object itself.
|
||||
*/
|
||||
private class AngularMethodCall extends AngularJSCall {
|
||||
MethodCallExpr mce;
|
||||
private class AngularMethodCall extends AngularJSCallNode {
|
||||
AngularMethodCall() { this = angular().getAMemberCall(_) }
|
||||
|
||||
AngularMethodCall() {
|
||||
mce = angular().getAMemberCall(_).asExpr() and
|
||||
mce = this
|
||||
override predicate interpretsArgumentAsHtml(DataFlow::Node e) {
|
||||
super.getCalleeName() = "element" and
|
||||
e = super.getArgument(0)
|
||||
}
|
||||
|
||||
override predicate interpretsArgumentAsHtml(Expr e) {
|
||||
mce.getMethodName() = "element" and
|
||||
e = mce.getArgument(0)
|
||||
}
|
||||
override predicate storesArgumentGlobally(DataFlow::Node e) { none() }
|
||||
|
||||
override predicate storesArgumentGlobally(Expr e) { none() }
|
||||
|
||||
override predicate interpretsArgumentAsCode(Expr e) { none() }
|
||||
override predicate interpretsArgumentAsCode(DataFlow::Node e) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a builtin service or one of its methods.
|
||||
*/
|
||||
private class BuiltinServiceCall extends AngularJSCall {
|
||||
CallExpr call;
|
||||
|
||||
private class BuiltinServiceCall extends AngularJSCallNode {
|
||||
BuiltinServiceCall() {
|
||||
exists(BuiltinServiceReference service |
|
||||
service.getAMethodCall(_) = this or
|
||||
service.getACall() = this
|
||||
|
|
||||
call = this
|
||||
)
|
||||
}
|
||||
|
||||
override predicate interpretsArgumentAsHtml(Expr e) {
|
||||
override predicate interpretsArgumentAsHtml(DataFlow::Node e) {
|
||||
exists(ServiceReference service, string methodName |
|
||||
service.getName() = "$sce" and
|
||||
call = service.getAMethodCall(methodName)
|
||||
this = service.getAMethodCall(methodName)
|
||||
|
|
||||
// specialized call
|
||||
(methodName = "trustAsHtml" or methodName = "trustAsCss") and
|
||||
e = call.getArgument(0)
|
||||
e = this.getArgument(0)
|
||||
or
|
||||
// generic call with enum argument
|
||||
methodName = "trustAs" and
|
||||
exists(DataFlow::PropRead prn |
|
||||
prn.asExpr() = call.getArgument(0) and
|
||||
prn = this.getArgument(0) and
|
||||
(prn = service.getAPropertyAccess("HTML") or prn = service.getAPropertyAccess("CSS")) and
|
||||
e = call.getArgument(1)
|
||||
e = this.getArgument(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate storesArgumentGlobally(Expr e) {
|
||||
override predicate storesArgumentGlobally(DataFlow::Node e) {
|
||||
exists(ServiceReference service, string serviceName, string methodName |
|
||||
service.getName() = serviceName and
|
||||
call = service.getAMethodCall(methodName)
|
||||
this = service.getAMethodCall(methodName)
|
||||
|
|
||||
// AngularJS caches (only available during runtime, so similar to sessionStorage)
|
||||
(serviceName = "$cacheFactory" or serviceName = "$templateCache") and
|
||||
methodName = "put" and
|
||||
e = call.getArgument(1)
|
||||
e = this.getArgument(1)
|
||||
or
|
||||
serviceName = "$cookies" and
|
||||
(methodName = "put" or methodName = "putObject") and
|
||||
e = call.getArgument(1)
|
||||
e = this.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate interpretsArgumentAsCode(Expr e) {
|
||||
override predicate interpretsArgumentAsCode(DataFlow::Node e) {
|
||||
exists(ScopeServiceReference scope, string methodName |
|
||||
methodName =
|
||||
[
|
||||
@@ -731,22 +737,21 @@ private class BuiltinServiceCall extends AngularJSCall {
|
||||
"$watchGroup"
|
||||
]
|
||||
|
|
||||
call = scope.getAMethodCall(methodName) and
|
||||
e = call.getArgument(0)
|
||||
this = scope.getAMethodCall(methodName) and
|
||||
e = this.getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(ServiceReference service | service.getName() = ["$compile", "$parse", "$interpolate"] |
|
||||
call = service.getACall() and
|
||||
e = call.getArgument(0)
|
||||
this = service.getACall() and
|
||||
e = this.getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(ServiceReference service, CallExpr filterInvocation |
|
||||
exists(ServiceReference service |
|
||||
// `$filter('orderBy')(collection, expression)`
|
||||
service.getName() = "$filter" and
|
||||
call = service.getACall() and
|
||||
call.getArgument(0).mayHaveStringValue("orderBy") and
|
||||
filterInvocation.getCallee() = call and
|
||||
e = filterInvocation.getArgument(1)
|
||||
this = service.getACall() and
|
||||
this.getArgument(0).mayHaveStringValue("orderBy") and
|
||||
e = this.getACall().getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -754,33 +759,33 @@ private class BuiltinServiceCall extends AngularJSCall {
|
||||
/**
|
||||
* A link-function used in a custom AngularJS directive.
|
||||
*/
|
||||
class LinkFunction extends Function {
|
||||
class LinkFunction extends DataFlow::FunctionNode {
|
||||
LinkFunction() { this = any(GeneralDirective d).getALinkFunction() }
|
||||
|
||||
/**
|
||||
* Gets the scope parameter of this function.
|
||||
*/
|
||||
Parameter getScopeParameter() { result = this.getParameter(0) }
|
||||
DataFlow::ParameterNode getScopeParameter() { result = this.getParameter(0) }
|
||||
|
||||
/**
|
||||
* Gets the element parameter of this function (contains a jqLite-wrapped DOM element).
|
||||
*/
|
||||
Parameter getElementParameter() { result = this.getParameter(1) }
|
||||
DataFlow::ParameterNode getElementParameter() { result = this.getParameter(1) }
|
||||
|
||||
/**
|
||||
* Gets the attributes parameter of this function.
|
||||
*/
|
||||
Parameter getAttributesParameter() { result = this.getParameter(2) }
|
||||
DataFlow::ParameterNode getAttributesParameter() { result = this.getParameter(2) }
|
||||
|
||||
/**
|
||||
* Gets the controller parameter of this function.
|
||||
*/
|
||||
Parameter getControllerParameter() { result = this.getParameter(3) }
|
||||
DataFlow::ParameterNode getControllerParameter() { result = this.getParameter(3) }
|
||||
|
||||
/**
|
||||
* Gets the transclude-function parameter of this function.
|
||||
*/
|
||||
Parameter getTranscludeFnParameter() { result = this.getParameter(4) }
|
||||
DataFlow::ParameterNode getTranscludeFnParameter() { result = this.getParameter(4) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -807,29 +812,29 @@ class AngularScope extends TAngularScope {
|
||||
/**
|
||||
* Gets an access to this scope object.
|
||||
*/
|
||||
Expr getAnAccess() {
|
||||
DataFlow::Node getAnAccess() {
|
||||
exists(CustomDirective d | this = d.getAScope() |
|
||||
exists(Parameter p |
|
||||
exists(DataFlow::ParameterNode p |
|
||||
p = d.getController().getDependencyParameter("$scope") or
|
||||
p = d.getALinkFunction().getParameter(0)
|
||||
|
|
||||
result.mayReferToParameter(p)
|
||||
p.flowsTo(result)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ThisNode dis |
|
||||
dis.flowsToExpr(result) and
|
||||
dis.getBinder().getAstNode() = d.getController().asFunction() and
|
||||
dis.flowsTo(result) and
|
||||
dis.getBinder() = d.getController().asFunction() and
|
||||
d.bindsToController()
|
||||
)
|
||||
or
|
||||
d.hasIsolateScope() and result = d.getMember("scope").asExpr()
|
||||
d.hasIsolateScope() and result = d.getMember("scope")
|
||||
)
|
||||
or
|
||||
exists(DirectiveController c, DOM::ElementDefinition elem, Parameter p |
|
||||
exists(DirectiveController c, DOM::ElementDefinition elem, DataFlow::ParameterNode p |
|
||||
c.boundTo(elem) and
|
||||
this.mayApplyTo(elem) and
|
||||
p = c.getFactoryFunction().getDependencyParameter("$scope") and
|
||||
result.mayReferToParameter(p)
|
||||
p.flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -925,9 +930,7 @@ class RouteSetup extends DataFlow::CallNode, DependencyInjection {
|
||||
|
|
||||
result = controllerProperty
|
||||
or
|
||||
exists(ControllerDefinition def |
|
||||
controllerProperty.asExpr().mayHaveStringValue(def.getName())
|
||||
|
|
||||
exists(ControllerDefinition def | controllerProperty.mayHaveStringValue(def.getName()) |
|
||||
result = def.getAService()
|
||||
)
|
||||
)
|
||||
@@ -1007,7 +1010,7 @@ private class RouteInstantiatedController extends Controller {
|
||||
|
||||
override predicate boundTo(DOM::ElementDefinition elem) {
|
||||
exists(string url, HTML::HtmlFile template |
|
||||
setup.getRouteParam("templateUrl").asExpr().mayHaveStringValue(url) and
|
||||
setup.getRouteParam("templateUrl").mayHaveStringValue(url) and
|
||||
template.getAbsolutePath().regexpMatch(".*\\Q" + url + "\\E") and
|
||||
elem.getFile() = template
|
||||
)
|
||||
@@ -1015,22 +1018,19 @@ private class RouteInstantiatedController extends Controller {
|
||||
|
||||
override predicate boundToAs(DOM::ElementDefinition elem, string name) {
|
||||
this.boundTo(elem) and
|
||||
setup.getRouteParam("controllerAs").asExpr().mayHaveStringValue(name)
|
||||
setup.getRouteParam("controllerAs").mayHaveStringValue(name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataflow for the arguments of AngularJS dependency-injected functions.
|
||||
*/
|
||||
private class DependencyInjectedArgumentInitializer extends DataFlow::AnalyzedNode {
|
||||
private class DependencyInjectedArgumentInitializer extends DataFlow::AnalyzedNode instanceof DataFlow::ParameterNode {
|
||||
DataFlow::AnalyzedNode service;
|
||||
|
||||
DependencyInjectedArgumentInitializer() {
|
||||
exists(
|
||||
AngularJS::InjectableFunction f, Parameter param, AngularJS::CustomServiceDefinition def
|
||||
|
|
||||
this = DataFlow::parameterNode(param) and
|
||||
def.getServiceReference() = f.getAResolvedDependency(param) and
|
||||
exists(AngularJS::InjectableFunction f, AngularJS::CustomServiceDefinition def |
|
||||
def.getServiceReference() = f.getAResolvedDependency(this) and
|
||||
service = def.getAService()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ import javascript
|
||||
abstract class NgSourceProvider extends Locatable {
|
||||
/**
|
||||
* Holds if this element provides the source as `src` for an AngularJS expression at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* The location spans column `startColumn` of line `startLine` to
|
||||
* column `endColumn` of line `endLine` in file `filepath`.
|
||||
*/
|
||||
abstract predicate providesSourceAt(
|
||||
string src, string path, int startLine, int startColumn, int endLine, int endColumn
|
||||
string src, string filepath, int startLine, int startColumn, int endLine, int endColumn
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -92,10 +92,10 @@ abstract private class HtmlAttributeAsNgSourceProvider extends NgSourceProvider,
|
||||
endColumn = startColumn + src.length() - 1
|
||||
}
|
||||
|
||||
/** The source code of the expression. */
|
||||
/** Gets the source code of the expression. */
|
||||
abstract string getSource();
|
||||
|
||||
/** The offset into the attribute where the expression starts. */
|
||||
/** Gets the offset into the attribute where the expression starts. */
|
||||
abstract int getOffset();
|
||||
|
||||
override DOM::ElementDefinition getEnclosingElement() { result = this.getElement() }
|
||||
|
||||
@@ -41,33 +41,35 @@ abstract class DependencyInjection extends DataFlow::ValueNode {
|
||||
*/
|
||||
abstract class InjectableFunction extends DataFlow::ValueNode {
|
||||
/** Gets the parameter corresponding to dependency `name`. */
|
||||
abstract Parameter getDependencyParameter(string name);
|
||||
abstract DataFlow::ParameterNode getDependencyParameter(string name);
|
||||
|
||||
/**
|
||||
* Gets the `i`th dependency declaration, which is also named `name`.
|
||||
*/
|
||||
abstract AstNode getDependencyDeclaration(int i, string name);
|
||||
abstract DataFlow::Node getDependencyDeclaration(int i, string name);
|
||||
|
||||
/**
|
||||
* Gets an ASTNode for the `name` dependency declaration.
|
||||
* Gets a node for the `name` dependency declaration.
|
||||
*/
|
||||
AstNode getADependencyDeclaration(string name) { result = getDependencyDeclaration(_, name) }
|
||||
DataFlow::Node getADependencyDeclaration(string name) {
|
||||
result = getDependencyDeclaration(_, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ASTNode for the `i`th dependency declaration.
|
||||
* Gets the dataflow node for the `i`th dependency declaration.
|
||||
*/
|
||||
AstNode getDependencyDeclaration(int i) { result = getDependencyDeclaration(i, _) }
|
||||
DataFlow::Node getDependencyDeclaration(int i) { result = getDependencyDeclaration(i, _) }
|
||||
|
||||
/** Gets the function underlying this injectable function. */
|
||||
abstract Function asFunction();
|
||||
abstract DataFlow::FunctionNode asFunction();
|
||||
|
||||
/** Gets a location where this function is explicitly dependency injected. */
|
||||
abstract AstNode getAnExplicitDependencyInjection();
|
||||
/** Gets a node where this function is explicitly dependency injected. */
|
||||
abstract DataFlow::Node getAnExplicitDependencyInjection();
|
||||
|
||||
/**
|
||||
* Gets a service corresponding to the dependency-injected `parameter`.
|
||||
*/
|
||||
ServiceReference getAResolvedDependency(Parameter parameter) {
|
||||
ServiceReference getAResolvedDependency(DataFlow::ParameterNode parameter) {
|
||||
exists(string name, InjectableFunctionServiceRequest request |
|
||||
this = request.getAnInjectedFunction() and
|
||||
parameter = getDependencyParameter(name) and
|
||||
@@ -79,7 +81,7 @@ abstract class InjectableFunction extends DataFlow::ValueNode {
|
||||
* Gets a Custom service corresponding to the dependency-injected `parameter`.
|
||||
* (this is a convenience variant of `getAResolvedDependency`)
|
||||
*/
|
||||
DataFlow::Node getCustomServiceDependency(Parameter parameter) {
|
||||
DataFlow::Node getCustomServiceDependency(DataFlow::ParameterNode parameter) {
|
||||
exists(CustomServiceDefinition custom |
|
||||
custom.getServiceReference() = getAResolvedDependency(parameter) and
|
||||
result = custom.getAService()
|
||||
@@ -91,100 +93,88 @@ abstract class InjectableFunction extends DataFlow::ValueNode {
|
||||
* An injectable function that does not explicitly list its dependencies,
|
||||
* instead relying on implicit matching by parameter names.
|
||||
*/
|
||||
private class FunctionWithImplicitDependencyAnnotation extends InjectableFunction {
|
||||
override Function astNode;
|
||||
|
||||
private class FunctionWithImplicitDependencyAnnotation extends InjectableFunction instanceof DataFlow::FunctionNode {
|
||||
FunctionWithImplicitDependencyAnnotation() {
|
||||
this.(DataFlow::FunctionNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) and
|
||||
not exists(getAPropertyDependencyInjection(astNode))
|
||||
not exists(getAPropertyDependencyInjection(this))
|
||||
}
|
||||
|
||||
override Parameter getDependencyParameter(string name) {
|
||||
result = astNode.getParameterByName(name)
|
||||
override DataFlow::ParameterNode getDependencyParameter(string name) {
|
||||
result = super.getParameterByName(name)
|
||||
}
|
||||
|
||||
override Parameter getDependencyDeclaration(int i, string name) {
|
||||
override DataFlow::ParameterNode getDependencyDeclaration(int i, string name) {
|
||||
result.getName() = name and
|
||||
result = astNode.getParameter(i)
|
||||
result = super.getParameter(i)
|
||||
}
|
||||
|
||||
override Function asFunction() { result = astNode }
|
||||
override DataFlow::FunctionNode asFunction() { result = this }
|
||||
|
||||
override AstNode getAnExplicitDependencyInjection() { none() }
|
||||
override DataFlow::Node getAnExplicitDependencyInjection() { none() }
|
||||
}
|
||||
|
||||
private DataFlow::PropWrite getAPropertyDependencyInjection(Function function) {
|
||||
exists(DataFlow::FunctionNode ltf |
|
||||
ltf.getAstNode() = function and
|
||||
result = ltf.getAPropertyWrite("$inject")
|
||||
)
|
||||
private DataFlow::PropWrite getAPropertyDependencyInjection(DataFlow::FunctionNode function) {
|
||||
result = function.getAPropertyWrite("$inject")
|
||||
}
|
||||
|
||||
/**
|
||||
* An injectable function with an `$inject` property that lists its
|
||||
* dependencies.
|
||||
*/
|
||||
private class FunctionWithInjectProperty extends InjectableFunction {
|
||||
override Function astNode;
|
||||
private class FunctionWithInjectProperty extends InjectableFunction instanceof DataFlow::FunctionNode {
|
||||
DataFlow::ArrayCreationNode dependencies;
|
||||
|
||||
FunctionWithInjectProperty() {
|
||||
(
|
||||
this.(DataFlow::FunctionNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) or
|
||||
exists(FunctionWithExplicitDependencyAnnotation f | f.asFunction() = astNode)
|
||||
exists(FunctionWithExplicitDependencyAnnotation f | f.asFunction() = this)
|
||||
) and
|
||||
exists(DataFlow::PropWrite pwn |
|
||||
pwn = getAPropertyDependencyInjection(astNode) and
|
||||
pwn = getAPropertyDependencyInjection(this) and
|
||||
pwn.getRhs().getALocalSource() = dependencies
|
||||
)
|
||||
}
|
||||
|
||||
override Parameter getDependencyParameter(string name) {
|
||||
exists(int i | exists(getDependencyDeclaration(i, name)) | result = astNode.getParameter(i))
|
||||
override DataFlow::ParameterNode getDependencyParameter(string name) {
|
||||
exists(int i | exists(getDependencyDeclaration(i, name)) | result = super.getParameter(i))
|
||||
}
|
||||
|
||||
override AstNode getDependencyDeclaration(int i, string name) {
|
||||
exists(DataFlow::ValueNode decl |
|
||||
decl = dependencies.getElement(i) and
|
||||
decl.mayHaveStringValue(name) and
|
||||
result = decl.getAstNode()
|
||||
)
|
||||
override DataFlow::Node getDependencyDeclaration(int i, string name) {
|
||||
result = dependencies.getElement(i) and
|
||||
result.mayHaveStringValue(name)
|
||||
}
|
||||
|
||||
override Function asFunction() { result = astNode }
|
||||
override DataFlow::FunctionNode asFunction() { result = this }
|
||||
|
||||
override AstNode getAnExplicitDependencyInjection() {
|
||||
result = getAPropertyDependencyInjection(astNode).getAstNode()
|
||||
override DataFlow::Node getAnExplicitDependencyInjection() {
|
||||
result = getAPropertyDependencyInjection(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An injectable function embedded in an array of dependencies.
|
||||
*/
|
||||
private class FunctionWithExplicitDependencyAnnotation extends InjectableFunction {
|
||||
private class FunctionWithExplicitDependencyAnnotation extends InjectableFunction instanceof DataFlow::ArrayCreationNode {
|
||||
DataFlow::FunctionNode function;
|
||||
override ArrayExpr astNode;
|
||||
|
||||
FunctionWithExplicitDependencyAnnotation() {
|
||||
this.(DataFlow::SourceNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) and
|
||||
function.flowsToExpr(astNode.getElement(astNode.getSize() - 1))
|
||||
function.flowsTo(super.getElement(super.getSize() - 1))
|
||||
}
|
||||
|
||||
override Parameter getDependencyParameter(string name) {
|
||||
exists(int i | astNode.getElement(i).mayHaveStringValue(name) |
|
||||
result = asFunction().getParameter(i)
|
||||
)
|
||||
override DataFlow::ParameterNode getDependencyParameter(string name) {
|
||||
exists(int i | super.getElement(i).mayHaveStringValue(name) | result = function.getParameter(i))
|
||||
}
|
||||
|
||||
override AstNode getDependencyDeclaration(int i, string name) {
|
||||
result = astNode.getElement(i) and
|
||||
result.(Expr).mayHaveStringValue(name)
|
||||
override DataFlow::Node getDependencyDeclaration(int i, string name) {
|
||||
result = super.getElement(i) and
|
||||
result.mayHaveStringValue(name)
|
||||
}
|
||||
|
||||
override Function asFunction() { result = function.getAstNode() }
|
||||
override DataFlow::FunctionNode asFunction() { result = function }
|
||||
|
||||
override AstNode getAnExplicitDependencyInjection() {
|
||||
result = astNode or
|
||||
override DataFlow::Node getAnExplicitDependencyInjection() {
|
||||
result = this or
|
||||
result = function.(InjectableFunction).getAnExplicitDependencyInjection()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ private newtype TServiceReference =
|
||||
*/
|
||||
abstract class ServiceReference extends TServiceReference {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = getName() }
|
||||
string toString() { result = this.getName() }
|
||||
|
||||
/**
|
||||
* Gets the name of this reference.
|
||||
@@ -38,26 +38,26 @@ abstract class ServiceReference extends TServiceReference {
|
||||
* Gets a data flow node that may refer to this service.
|
||||
*/
|
||||
DataFlow::SourceNode getAReference() {
|
||||
result = DataFlow::parameterNode(any(ServiceRequest request).getDependencyParameter(this))
|
||||
result = any(ServiceRequestNode request).getDependencyParameter(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an access to the referenced service.
|
||||
*/
|
||||
Expr getAnAccess() {
|
||||
result.mayReferToParameter(any(ServiceRequest request).getDependencyParameter(this))
|
||||
DataFlow::Node getAnAccess() {
|
||||
any(ServiceRequestNode request).getDependencyParameter(this).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call that invokes the referenced service.
|
||||
*/
|
||||
CallExpr getACall() { result.getCallee() = getAnAccess() }
|
||||
DataFlow::CallNode getACall() { result.getCalleeNode() = this.getAnAccess() }
|
||||
|
||||
/**
|
||||
* Gets a method call that invokes method `methodName` on the referenced service.
|
||||
*/
|
||||
MethodCallExpr getAMethodCall(string methodName) {
|
||||
result.getReceiver() = getAnAccess() and
|
||||
DataFlow::MethodCallNode getAMethodCall(string methodName) {
|
||||
result.getReceiver() = this.getAnAccess() and
|
||||
result.getMethodName() = methodName
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ abstract class ServiceReference extends TServiceReference {
|
||||
* Gets an access to property `propertyName` on the referenced service.
|
||||
*/
|
||||
DataFlow::PropRef getAPropertyAccess(string propertyName) {
|
||||
result.getBase().asExpr() = getAnAccess() and
|
||||
result.getBase() = this.getAnAccess() and
|
||||
result.getPropertyName() = propertyName
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class BuiltinServiceReference extends ServiceReference, MkBuiltinServiceReferenc
|
||||
DataFlow::ParameterNode builtinServiceRef(string serviceName) {
|
||||
exists(InjectableFunction f, BuiltinServiceReference service |
|
||||
service.getName() = serviceName and
|
||||
result = DataFlow::parameterNode(f.getDependencyParameter(serviceName))
|
||||
result = f.getDependencyParameter(serviceName)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,17 +244,17 @@ abstract class RecipeDefinition extends DataFlow::CallNode, CustomServiceDefinit
|
||||
this = moduleRef(_).getAMethodCall(methodName) or
|
||||
this = builtinServiceRef("$provide").getAMethodCall(methodName)
|
||||
) and
|
||||
getArgument(0).asExpr().mayHaveStringValue(name)
|
||||
this.getArgument(0).mayHaveStringValue(name)
|
||||
}
|
||||
|
||||
override string getName() { result = name }
|
||||
|
||||
override DataFlow::SourceNode getAFactoryFunction() { result.flowsTo(getArgument(1)) }
|
||||
override DataFlow::SourceNode getAFactoryFunction() { result.flowsTo(this.getArgument(1)) }
|
||||
|
||||
override DataFlow::Node getAnInjectableFunction() {
|
||||
methodName != "value" and
|
||||
methodName != "constant" and
|
||||
result = getAFactoryFunction()
|
||||
result = this.getAFactoryFunction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ abstract class RecipeDefinition extends DataFlow::CallNode, CustomServiceDefinit
|
||||
*/
|
||||
abstract private class CustomSpecialServiceDefinition extends CustomServiceDefinition,
|
||||
DependencyInjection {
|
||||
override DataFlow::Node getAnInjectableFunction() { result = getAFactoryFunction() }
|
||||
override DataFlow::Node getAnInjectableFunction() { result = this.getAFactoryFunction() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,11 +278,11 @@ abstract private class CustomSpecialServiceDefinition extends CustomServiceDefin
|
||||
bindingset[moduleMethodName]
|
||||
private predicate isCustomServiceDefinitionOnModule(
|
||||
DataFlow::CallNode mce, string moduleMethodName, string serviceName,
|
||||
DataFlow::Node factoryArgument
|
||||
DataFlow::Node factoryFunction
|
||||
) {
|
||||
mce = moduleRef(_).getAMethodCall(moduleMethodName) and
|
||||
mce.getArgument(0).asExpr().mayHaveStringValue(serviceName) and
|
||||
factoryArgument = mce.getArgument(1)
|
||||
mce.getArgument(0).mayHaveStringValue(serviceName) and
|
||||
factoryFunction = mce.getArgument(1)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
@@ -296,7 +296,7 @@ private predicate isCustomServiceDefinitionOnProvider(
|
||||
factoryArgument = mce.getOptionArgument(0, serviceName)
|
||||
or
|
||||
mce.getNumArgument() = 2 and
|
||||
mce.getArgument(0).asExpr().mayHaveStringValue(serviceName) and
|
||||
mce.getArgument(0).mayHaveStringValue(serviceName) and
|
||||
factoryArgument = mce.getArgument(1)
|
||||
)
|
||||
}
|
||||
@@ -338,7 +338,7 @@ class FilterDefinition extends CustomSpecialServiceDefinition {
|
||||
override DataFlow::SourceNode getAService() {
|
||||
exists(InjectableFunction f |
|
||||
f = factoryFunction.getALocalSource() and
|
||||
result.flowsToExpr(f.asFunction().getAReturnedExpr())
|
||||
result.flowsTo(f.asFunction().getAReturn())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ class AnimationDefinition extends CustomSpecialServiceDefinition {
|
||||
override DataFlow::SourceNode getAService() {
|
||||
exists(InjectableFunction f |
|
||||
f = factoryFunction.getALocalSource() and
|
||||
result.flowsToExpr(f.asFunction().getAReturnedExpr())
|
||||
result.flowsTo(f.asFunction().getAReturn())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -446,22 +446,37 @@ BuiltinServiceReference getBuiltinServiceOfKind(string kind) {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `ServiceRequestNode` instead.
|
||||
* A request for one or more AngularJS services.
|
||||
*/
|
||||
abstract class ServiceRequest extends Expr {
|
||||
deprecated class ServiceRequest extends Expr {
|
||||
ServiceRequestNode node;
|
||||
|
||||
ServiceRequest() { this.flow() = node }
|
||||
|
||||
/** Gets the parameter of this request into which `service` is injected. */
|
||||
deprecated Parameter getDependencyParameter(ServiceReference service) {
|
||||
result.flow() = node.getDependencyParameter(service)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request for one or more AngularJS services.
|
||||
*/
|
||||
abstract class ServiceRequestNode extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the parameter of this request into which `service` is injected.
|
||||
*/
|
||||
abstract Parameter getDependencyParameter(ServiceReference service);
|
||||
abstract DataFlow::ParameterNode getDependencyParameter(ServiceReference service);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request for a scope service in the form of the link-function of a directive.
|
||||
*/
|
||||
private class LinkFunctionWithScopeInjection extends ServiceRequest {
|
||||
private class LinkFunctionWithScopeInjection extends ServiceRequestNode {
|
||||
LinkFunctionWithScopeInjection() { this instanceof LinkFunction }
|
||||
|
||||
override Parameter getDependencyParameter(ServiceReference service) {
|
||||
override DataFlow::ParameterNode getDependencyParameter(ServiceReference service) {
|
||||
service instanceof ScopeServiceReference and
|
||||
result = this.(LinkFunction).getScopeParameter()
|
||||
}
|
||||
@@ -470,10 +485,10 @@ private class LinkFunctionWithScopeInjection extends ServiceRequest {
|
||||
/**
|
||||
* A request for a service, in the form of a dependency-injected function.
|
||||
*/
|
||||
class InjectableFunctionServiceRequest extends ServiceRequest {
|
||||
class InjectableFunctionServiceRequest extends ServiceRequestNode {
|
||||
InjectableFunction injectedFunction;
|
||||
|
||||
InjectableFunctionServiceRequest() { injectedFunction.getAstNode() = this }
|
||||
InjectableFunctionServiceRequest() { injectedFunction = this }
|
||||
|
||||
/**
|
||||
* Gets the function of this request.
|
||||
@@ -483,7 +498,9 @@ class InjectableFunctionServiceRequest extends ServiceRequest {
|
||||
/**
|
||||
* Gets a name of a requested service.
|
||||
*/
|
||||
string getAServiceName() { exists(getAnInjectedFunction().getADependencyDeclaration(result)) }
|
||||
string getAServiceName() {
|
||||
exists(this.getAnInjectedFunction().getADependencyDeclaration(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a service with the specified name, relative to this request.
|
||||
@@ -494,16 +511,16 @@ class InjectableFunctionServiceRequest extends ServiceRequest {
|
||||
result.isInjectable()
|
||||
}
|
||||
|
||||
override Parameter getDependencyParameter(ServiceReference service) {
|
||||
override DataFlow::ParameterNode getDependencyParameter(ServiceReference service) {
|
||||
service = injectedFunction.getAResolvedDependency(result)
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getFactoryFunctionResult(RecipeDefinition def) {
|
||||
exists(Function factoryFunction, InjectableFunction f |
|
||||
exists(DataFlow::FunctionNode factoryFunction, InjectableFunction f |
|
||||
f = def.getAFactoryFunction() and
|
||||
factoryFunction = f.asFunction() and
|
||||
result.flowsToExpr(factoryFunction.getAReturnedExpr())
|
||||
result.flowsTo(factoryFunction.getAReturn())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -561,8 +578,8 @@ class ServiceRecipeDefinition extends RecipeDefinition {
|
||||
*/
|
||||
|
||||
exists(InjectableFunction f |
|
||||
f = getAFactoryFunction() and
|
||||
result.getAstNode() = f.asFunction()
|
||||
f = this.getAFactoryFunction() and
|
||||
result = f.asFunction()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -574,7 +591,7 @@ class ServiceRecipeDefinition extends RecipeDefinition {
|
||||
class ValueRecipeDefinition extends RecipeDefinition {
|
||||
ValueRecipeDefinition() { methodName = "value" }
|
||||
|
||||
override DataFlow::SourceNode getAService() { result = getAFactoryFunction() }
|
||||
override DataFlow::SourceNode getAService() { result = this.getAFactoryFunction() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -584,7 +601,7 @@ class ValueRecipeDefinition extends RecipeDefinition {
|
||||
class ConstantRecipeDefinition extends RecipeDefinition {
|
||||
ConstantRecipeDefinition() { methodName = "constant" }
|
||||
|
||||
override DataFlow::SourceNode getAService() { result = getAFactoryFunction() }
|
||||
override DataFlow::SourceNode getAService() { result = this.getAFactoryFunction() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -607,8 +624,8 @@ class ProviderRecipeDefinition extends RecipeDefinition {
|
||||
*/
|
||||
|
||||
exists(DataFlow::ThisNode thiz, InjectableFunction f |
|
||||
f = getAFactoryFunction() and
|
||||
thiz.getBinder().getFunction() = f.asFunction() and
|
||||
f = this.getAFactoryFunction() and
|
||||
thiz.getBinder() = f.asFunction() and
|
||||
result = thiz.getAPropertySource("$get")
|
||||
)
|
||||
}
|
||||
@@ -632,7 +649,9 @@ class ConfigMethodDefinition extends ModuleApiCall {
|
||||
/**
|
||||
* Gets a provided configuration method.
|
||||
*/
|
||||
InjectableFunction getConfigMethod() { result.(DataFlow::SourceNode).flowsTo(getArgument(0)) }
|
||||
InjectableFunction getConfigMethod() {
|
||||
result.(DataFlow::SourceNode).flowsTo(this.getArgument(0))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -645,12 +664,12 @@ class RunMethodDefinition extends ModuleApiCall {
|
||||
/**
|
||||
* Gets a provided run method.
|
||||
*/
|
||||
InjectableFunction getRunMethod() { result.(DataFlow::SourceNode).flowsTo(getArgument(0)) }
|
||||
InjectableFunction getRunMethod() { result.(DataFlow::SourceNode).flowsTo(this.getArgument(0)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `$scope` or `$rootScope` service.
|
||||
*/
|
||||
class ScopeServiceReference extends BuiltinServiceReference {
|
||||
ScopeServiceReference() { getName() = "$scope" or getName() = "$rootScope" }
|
||||
ScopeServiceReference() { this.getName() = "$scope" or this.getName() = "$rootScope" }
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ module Azure {
|
||||
/**
|
||||
* An expression that is used for authentication at Azure`.
|
||||
*/
|
||||
class Credentials extends CredentialsExpr {
|
||||
class Credentials extends CredentialsNode {
|
||||
string kind;
|
||||
|
||||
Credentials() {
|
||||
exists(CallExpr mce, string methodName |
|
||||
(methodName = "loginWithUsernamePassword" or methodName = "loginWithServicePrincipalSecret") and
|
||||
mce = DataFlow::moduleMember("ms-rest-azure", methodName).getACall().asExpr()
|
||||
exists(DataFlow::CallNode mce |
|
||||
mce =
|
||||
DataFlow::moduleMember("ms-rest-azure",
|
||||
["loginWithUsernamePassword", "loginWithServicePrincipalSecret"]).getACall()
|
||||
|
|
||||
this = mce.getArgument(0) and kind = "user name"
|
||||
or
|
||||
|
||||
@@ -198,7 +198,7 @@ module Babel {
|
||||
.getMember(["transform", "transformSync", "transformAsync"])
|
||||
.getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = [call, call.getParameter(2).getParameter(0).getAnImmediateUse()]
|
||||
succ = [call, call.getParameter(2).getParameter(0).asSource()]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +102,9 @@ private predicate isBrowserifyDependencyMap(ObjectExpr deps) {
|
||||
* Holds if `m` is a function that looks like a bundled module created
|
||||
* by Webpack.
|
||||
*
|
||||
* Parameters must be named either `module` or `exports`,
|
||||
* or their name must contain the substring `webpack_require`
|
||||
* or `webpack_module_template_argument`.
|
||||
* Parameters must be named either "module" or "exports",
|
||||
* or their name must contain the substring "webpack_require"
|
||||
* or "webpack_module_template_argument".
|
||||
*/
|
||||
private predicate isWebpackModule(FunctionExpr m) {
|
||||
forex(Parameter parm | parm = m.getAParameter() |
|
||||
|
||||
@@ -14,7 +14,7 @@ module Cheerio {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cheerio` function, possibly with a loaded DOM. */
|
||||
DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAUse() }
|
||||
DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* A creation of `cheerio` object, a collection of virtual DOM elements
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user