Merge branch 'main' into javascript/ssrf

This commit is contained in:
valeria-meli
2021-09-17 17:08:52 -03:00
3659 changed files with 160183 additions and 46496 deletions

View File

@@ -1,12 +0,0 @@
/**
* Contains customizations to the standard library.
*
* This module is imported by `javascript.qll`, so any customizations defined here automatically
* apply to all queries.
*
* Typical examples of customizations include adding new subclasses of abstract classes such as
* `FileSystemAccess`, or the `Source` and `Sink` classes associated with the security queries
* to model frameworks that are not covered by the standard library.
*/
import javascript

View File

@@ -167,7 +167,7 @@ predicate whitelisted(UnusedLocal v) {
vd.isAmbient()
)
or
exists(DirectEval eval |
exists(Expr eval | eval instanceof DirectEval or eval instanceof GeneratedCodeExpr |
// eval nearby
eval.getEnclosingFunction() = v.getADeclaration().getEnclosingFunction() and
// ... but not on the RHS

View File

@@ -43,5 +43,8 @@ where
// exclude DOM properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude self-assignments that have been inserted to satisfy the TypeScript JS-checker
not e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag().getTitle() = "type"
not e.getAssignment().getParent().(ExprStmt).getDocumentation().getATag().getTitle() = "type" and
// exclude self-assignments in speculatively parsed template files
// named arguments may be incorrectly parsed as assignments
not e.getTopLevel() instanceof Templating::TemplateTopLevel
select e.getParent(), "This expression assigns " + dsc + " to itself."

View File

@@ -9,7 +9,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedData::ExternalAPIUsedWithUntrustedData
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedDataQuery
from ExternalAPIUsedWithUntrustedData externalAPI
select externalAPI, count(externalAPI.getUntrustedDataNode()) as numberOfUses,

View File

@@ -10,7 +10,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedData::ExternalAPIUsedWithUntrustedData
import semmle.javascript.security.dataflow.ExternalAPIUsedWithUntrustedDataQuery
import DataFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -16,7 +16,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.TaintedPath::TaintedPath
import semmle.javascript.security.dataflow.TaintedPathQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ZipSlip::ZipSlip
import semmle.javascript.security.dataflow.ZipSlipQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.TemplateObjectInjection::TemplateObjectInjection
import semmle.javascript.security.dataflow.TemplateObjectInjectionQuery
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.CommandInjection::CommandInjection
import semmle.javascript.security.dataflow.CommandInjectionQuery
import DataFlow::PathGraph
from

View File

@@ -16,7 +16,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.IndirectCommandInjection::IndirectCommandInjection
import semmle.javascript.security.dataflow.IndirectCommandInjectionQuery
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight
where

View File

@@ -15,7 +15,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironment::ShellCommandInjectionFromEnvironment
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeShellCommandConstruction::UnsafeShellCommandConstruction
import semmle.javascript.security.dataflow.UnsafeShellCommandConstructionQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss
import semmle.javascript.security.dataflow.ExceptionXssQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ReflectedXss::ReflectedXss
import semmle.javascript.security.dataflow.ReflectedXssQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.StoredXss::StoredXss
import semmle.javascript.security.dataflow.StoredXssQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.UnsafeHtmlConstruction::UnsafeHtmlConstruction
import semmle.javascript.security.dataflow.UnsafeHtmlConstructionQuery
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
where cfg.hasFlowPath(source, sink) and sink.getNode() = sinkNode

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeJQueryPlugin::UnsafeJQueryPlugin
import semmle.javascript.security.dataflow.UnsafeJQueryPluginQuery
import DataFlow::PathGraph
from

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss
import semmle.javascript.security.dataflow.DomBasedXssQuery
import DataFlow::PathGraph
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.XssThroughDom::XssThroughDom
import semmle.javascript.security.dataflow.XssThroughDomQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,8 +12,8 @@
*/
import javascript
import semmle.javascript.security.dataflow.SqlInjection
import semmle.javascript.security.dataflow.NosqlInjection
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
import DataFlow::PathGraph
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.CodeInjection::CodeInjection
import semmle.javascript.security.dataflow.CodeInjectionQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ImproperCodeSanitization::ImproperCodeSanitization
import semmle.javascript.security.dataflow.ImproperCodeSanitizationQuery
import DataFlow::PathGraph
private import semmle.javascript.heuristics.HeuristicSinks
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccess::UnsafeDynamicMethodAccess
import semmle.javascript.security.dataflow.UnsafeDynamicMethodAccessQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -16,7 +16,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.IncompleteHtmlAttributeSanitization::IncompleteHtmlAttributeSanitization
import semmle.javascript.security.dataflow.IncompleteHtmlAttributeSanitizationQuery
import semmle.javascript.security.IncompleteBlacklistSanitizer
/**

View File

@@ -56,31 +56,69 @@ DangerousPrefix getADangerousMatchedPrefix(EmptyReplaceRegExpTerm t) {
not exists(EmptyReplaceRegExpTerm pred | pred = t.getPredecessor+() and not pred.isNullable())
}
private import semmle.javascript.security.performance.ReDoSUtil as ReDoSUtil
/**
* Gets a char from a dangerous prefix that is matched by `t`.
*/
pragma[noinline]
DangerousPrefixSubstring getADangerousMatchedChar(EmptyReplaceRegExpTerm t) {
t.isNullable() and result = ""
or
t.getAMatchedString() = result
or
// A substring matched by some character class. This is only used to match the "word" part of a HTML tag (e.g. "iframe" in "<iframe").
exists(ReDoSUtil::CharacterClass cc |
cc = ReDoSUtil::getCanonicalCharClass(t) and
cc.matches(result) and
result.regexpMatch("\\w") and
// excluding character classes that match ">" (e.g. /<[^<]*>/), as these might consume nested HTML tags, and thus prevent the dangerous pattern this query is looking for.
not cc.matches(">")
)
or
t instanceof RegExpDot and
result.length() = 1
or
(
t instanceof RegExpOpt or
t instanceof RegExpStar or
t instanceof RegExpPlus or
t instanceof RegExpGroup or
t instanceof RegExpAlt
) and
result = getADangerousMatchedChar(t.getAChild())
}
/**
* Gets a substring of a dangerous prefix that is in the language starting at `t` (ignoring lookarounds).
*
* Note that the language of `t` is slightly restricted as not all RegExpTerm types are supported.
*/
DangerousPrefixSubstring getADangerousMatchedPrefixSubstring(EmptyReplaceRegExpTerm t) {
exists(string left |
t.isNullable() and left = ""
or
t.getAMatchedString() = left
or
(
t instanceof RegExpOpt or
t instanceof RegExpStar or
t instanceof RegExpPlus or
t instanceof RegExpGroup or
t instanceof RegExpAlt
) and
left = getADangerousMatchedPrefixSubstring(t.getAChild())
|
result = left + getADangerousMatchedPrefixSubstring(t.getSuccessor()) or
result = left
result = getADangerousMatchedChar(t) + getADangerousMatchedPrefixSubstring(t.getSuccessor())
or
result = getADangerousMatchedChar(t)
or
// loop around for repetitions (only considering alphanumeric characters in the repetition)
exists(RepetitionMatcher repetition | t = repetition |
result = getADangerousMatchedPrefixSubstring(repetition) + repetition.getAChar()
)
}
class RepetitionMatcher extends EmptyReplaceRegExpTerm {
string char;
pragma[noinline]
RepetitionMatcher() {
(this instanceof RegExpPlus or this instanceof RegExpStar) and
char = getADangerousMatchedChar(this.getAChild()) and
char.regexpMatch("\\w")
}
pragma[noinline]
string getAChar() { result = char }
}
/**
* Holds if `t` may match the dangerous `prefix` and some suffix, indicating intent to prevent a vulnerablity of kind `kind`.
*/
@@ -151,7 +189,7 @@ where
// skip leading optional elements
not dangerous.isNullable() and
// only warn about the longest match (presumably the most descriptive)
prefix = max(string m | matchesDangerousPrefix(dangerous, m, kind) | m order by m.length()) and
prefix = max(string m | matchesDangerousPrefix(dangerous, m, kind) | m order by m.length(), m) and
// only warn once per kind
not exists(EmptyReplaceRegExpTerm other |
other = dangerous.getAChild+() or other = dangerous.getPredecessor+()

View File

@@ -13,7 +13,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.LogInjection::LogInjection
import semmle.javascript.security.dataflow.LogInjectionQuery
from LogInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.TaintedFormatString::TaintedFormatString
import semmle.javascript.security.dataflow.TaintedFormatStringQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.FileAccessToHttp::FileAccessToHttp
import semmle.javascript.security.dataflow.FileAccessToHttpQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -8,6 +8,7 @@
* @id js/exposure-of-private-files
* @tags security
* external/cwe/cwe-200
* external/cwe/cwe-548
* @precision high
*/

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.PostMessageStar::PostMessageStar
import semmle.javascript.security.dataflow.PostMessageStarQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.StackTraceExposure::StackTraceExposure
import semmle.javascript.security.dataflow.StackTraceExposureQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -7,7 +7,7 @@
* @precision very-high
* @id js/disabling-certificate-validation
* @tags security
* external/cwe-295
* external/cwe/cwe-295
*/
import javascript

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.BuildArtifactLeak::BuildArtifactLeak
import semmle.javascript.security.dataflow.BuildArtifactLeakQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.CleartextLogging::CleartextLogging
import semmle.javascript.security.dataflow.CleartextLoggingQuery
import DataFlow::PathGraph
/**

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.CleartextStorage::CleartextStorage
import semmle.javascript.security.dataflow.CleartextStorageQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -10,6 +10,7 @@
* external/cwe/cwe-256
* external/cwe/cwe-260
* external/cwe/cwe-313
* external/cwe/cwe-522
*/
import javascript

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.BrokenCryptoAlgorithm::BrokenCryptoAlgorithm
import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmQuery
import semmle.javascript.security.SensitiveActions
import DataFlow::PathGraph

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.InsecureRandomness::InsecureRandomness
import semmle.javascript.security.dataflow.InsecureRandomnessQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentials::CorsMisconfigurationForCredentials
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.DeepObjectResourceExhaustion::DeepObjectResourceExhaustion
import semmle.javascript.security.dataflow.DeepObjectResourceExhaustionQuery
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node link,

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.RemotePropertyInjection::RemotePropertyInjection
import semmle.javascript.security.dataflow.RemotePropertyInjectionQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeDeserialization::UnsafeDeserialization
import semmle.javascript.security.dataflow.UnsafeDeserializationQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCode::HardcodedDataInterpretedAsCode
import semmle.javascript.security.dataflow.HardcodedDataInterpretedAsCodeQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ClientSideUrlRedirect::ClientSideUrlRedirect
import semmle.javascript.security.dataflow.ClientSideUrlRedirectQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ServerSideUrlRedirect::ServerSideUrlRedirect
import semmle.javascript.security.dataflow.ServerSideUrlRedirectQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.Xxe::Xxe
import semmle.javascript.security.dataflow.XxeQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.HostHeaderPoisoningInEmailGeneration::HostHeaderPoisoningInEmailGeneration
import semmle.javascript.security.dataflow.HostHeaderPoisoningInEmailGenerationQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.XpathInjection::XpathInjection
import semmle.javascript.security.dataflow.XpathInjectionQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.RegExpInjection::RegExpInjection
import semmle.javascript.security.dataflow.RegExpInjectionQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCall::UnvalidatedDynamicMethodCall
import semmle.javascript.security.dataflow.UnvalidatedDynamicMethodCallQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -13,7 +13,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.XmlBomb::XmlBomb
import semmle.javascript.security.dataflow.XmlBombQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -14,7 +14,7 @@
*/
import javascript
private import semmle.javascript.security.dataflow.HardcodedCredentials::HardcodedCredentials
import semmle.javascript.security.dataflow.HardcodedCredentialsQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, string value

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.ConditionalBypass::ConditionalBypass
import semmle.javascript.security.dataflow.ConditionalBypassQuery
import DataFlow::PathGraph
/**

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.DifferentKindsComparisonBypass::DifferentKindsComparisonBypass
import semmle.javascript.security.dataflow.DifferentKindsComparisonBypassQuery
from DifferentKindsComparison cmp, DataFlow::Node lSource, DataFlow::Node rSource
where

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.InsecureDownload::InsecureDownload
import semmle.javascript.security.dataflow.InsecureDownloadQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.LoopBoundInjection::LoopBoundInjection
import semmle.javascript.security.dataflow.LoopBoundInjectionQuery
import DataFlow::PathGraph
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTampering::TypeConfusionThroughParameterTampering
import semmle.javascript.security.dataflow.TypeConfusionThroughParameterTamperingQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -12,7 +12,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.HttpToFileAccess::HttpToFileAccess
import semmle.javascript.security.dataflow.HttpToFileAccessQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -17,7 +17,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.PrototypePollutingAssignment::PrototypePollutingAssignment
import semmle.javascript.security.dataflow.PrototypePollutingAssignmentQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -17,7 +17,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.PrototypePollution::PrototypePollution
import semmle.javascript.security.dataflow.PrototypePollutionQuery
import DataFlow::PathGraph
import semmle.javascript.dependencies.Dependencies

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.InsufficientPasswordHash::InsufficientPasswordHash
import semmle.javascript.security.dataflow.InsufficientPasswordHashQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink

View File

@@ -11,7 +11,7 @@
*/
import javascript
import semmle.javascript.security.dataflow.RequestForgery::RequestForgery
import semmle.javascript.security.dataflow.RequestForgeryQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node request

View File

@@ -11,7 +11,7 @@
import javascript
from Vue::Instance instance, DataFlow::Node def, DataFlow::FunctionNode arrow, ThisExpr dis
from Vue::Component instance, DataFlow::Node def, DataFlow::FunctionNode arrow, ThisExpr dis
where
instance.getABoundFunction() = def and
arrow.flowsTo(def) and

View File

@@ -1,4 +1,4 @@
- description: Standard Code Scanning queries for JavaScript
- qlpack: codeql-javascript
- queries: .
- apply: code-scanning-selectors.yml
from: codeql-suite-helpers
from: codeql/suite-helpers

View File

@@ -1,7 +1,7 @@
- description: Standard LGTM queries for JavaScript, including ones not displayed by default
- qlpack: codeql-javascript
- queries: .
- apply: lgtm-selectors.yml
from: codeql-suite-helpers
from: codeql/suite-helpers
# These are only for IDE use.
- exclude:
tags contain:

View File

@@ -1,4 +1,4 @@
- description: Standard LGTM queries for JavaScript
- apply: codeql-suites/javascript-lgtm-full.qls
- apply: lgtm-displayed-only.yml
from: codeql-suite-helpers
from: codeql/suite-helpers

View File

@@ -1,4 +1,4 @@
- description: Security-and-quality queries for JavaScript
- qlpack: codeql-javascript
- queries: .
- apply: security-and-quality-selectors.yml
from: codeql-suite-helpers
from: codeql/suite-helpers

View File

@@ -1,4 +1,4 @@
- description: Security-extended queries for JavaScript
- qlpack: codeql-javascript
- queries: .
- apply: security-extended-selectors.yml
from: codeql-suite-helpers
from: codeql/suite-helpers

View File

@@ -1,7 +0,0 @@
/**
* DEPRECATED: Use `javascript.qll` instead.
*
* Provides classes for working with JavaScript programs, as well as JSON, YAML and HTML.
*/
import javascript

View File

@@ -65,6 +65,12 @@ private predicate isExternalUserControlledCommit(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*head_ref\\b")
}
bindingset[context]
private predicate isExternalUserControlledDiscussion(string context) {
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*title\\b") or
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*body\\b")
}
from Actions::Run run, string context, Actions::On on
where
run.getAReferencedExpression() = context and
@@ -87,6 +93,9 @@ where
or
exists(on.getNode("pull_request_target")) and
isExternalUserControlledCommit(context)
or
(exists(on.getNode("discussion")) or exists(on.getNode("discussion_comment"))) and
isExternalUserControlledDiscussion(context)
)
select run,
"Potential injection from the " + context +

View File

@@ -1,136 +0,0 @@
/**
* Provides classes for working with JavaScript programs, as well as JSON, YAML and HTML.
*/
import Customizations
import semmle.javascript.Aliases
import semmle.javascript.AMD
import semmle.javascript.ApiGraphs
import semmle.javascript.Arrays
import semmle.javascript.AST
import semmle.javascript.BasicBlocks
import semmle.javascript.Base64
import semmle.javascript.CFG
import semmle.javascript.Classes
import semmle.javascript.Closure
import semmle.javascript.Collections
import semmle.javascript.Comments
import semmle.javascript.Concepts
import semmle.javascript.Constants
import semmle.javascript.DefUse
import semmle.javascript.DOM
import semmle.javascript.E4X
import semmle.javascript.EmailClients
import semmle.javascript.Errors
import semmle.javascript.ES2015Modules
import semmle.javascript.Expr
import semmle.javascript.Extend
import semmle.javascript.Externs
import semmle.javascript.Files
import semmle.javascript.Functions
import semmle.javascript.Generators
import semmle.javascript.GlobalAccessPaths
import semmle.javascript.HTML
import semmle.javascript.HtmlSanitizers
import semmle.javascript.InclusionTests
import semmle.javascript.JSDoc
import semmle.javascript.JSON
import semmle.javascript.JsonParsers
import semmle.javascript.JsonSchema
import semmle.javascript.JsonStringifiers
import semmle.javascript.JSX
import semmle.javascript.Lines
import semmle.javascript.Locations
import semmle.javascript.MembershipCandidates
import semmle.javascript.Modules
import semmle.javascript.NodeJS
import semmle.javascript.NPM
import semmle.javascript.Paths
import semmle.javascript.Promises
import semmle.javascript.CanonicalNames
import semmle.javascript.RangeAnalysis
import semmle.javascript.Regexp
import semmle.javascript.SSA
import semmle.javascript.StandardLibrary
import semmle.javascript.Stmt
import semmle.javascript.StringConcatenation
import semmle.javascript.StringOps
import semmle.javascript.Templates
import semmle.javascript.Tokens
import semmle.javascript.TypeAnnotations
import semmle.javascript.TypeScript
import semmle.javascript.Util
import semmle.javascript.Variables
import semmle.javascript.XML
import semmle.javascript.YAML
import semmle.javascript.dataflow.DataFlow
import semmle.javascript.dataflow.TaintTracking
import semmle.javascript.dataflow.TypeInference
import semmle.javascript.frameworks.Angular2
import semmle.javascript.frameworks.AngularJS
import semmle.javascript.frameworks.Anser
import semmle.javascript.frameworks.AsyncPackage
import semmle.javascript.frameworks.AWS
import semmle.javascript.frameworks.Azure
import semmle.javascript.frameworks.Babel
import semmle.javascript.frameworks.Cheerio
import semmle.javascript.frameworks.ComposedFunctions
import semmle.javascript.frameworks.Classnames
import semmle.javascript.frameworks.ClassValidator
import semmle.javascript.frameworks.ClientRequests
import semmle.javascript.frameworks.ClosureLibrary
import semmle.javascript.frameworks.CookieLibraries
import semmle.javascript.frameworks.Credentials
import semmle.javascript.frameworks.CryptoLibraries
import semmle.javascript.frameworks.D3
import semmle.javascript.frameworks.DateFunctions
import semmle.javascript.frameworks.DigitalOcean
import semmle.javascript.frameworks.Electron
import semmle.javascript.frameworks.EventEmitter
import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.FormParsers
import semmle.javascript.frameworks.GraphQL
import semmle.javascript.frameworks.jQuery
import semmle.javascript.frameworks.JWT
import semmle.javascript.frameworks.Handlebars
import semmle.javascript.frameworks.History
import semmle.javascript.frameworks.Immutable
import semmle.javascript.frameworks.Knex
import semmle.javascript.frameworks.LazyCache
import semmle.javascript.frameworks.LodashUnderscore
import semmle.javascript.frameworks.Logging
import semmle.javascript.frameworks.HttpFrameworks
import semmle.javascript.frameworks.HttpProxy
import semmle.javascript.frameworks.Markdown
import semmle.javascript.frameworks.MooTools
import semmle.javascript.frameworks.Nest
import semmle.javascript.frameworks.Next
import semmle.javascript.frameworks.NoSQL
import semmle.javascript.frameworks.PkgCloud
import semmle.javascript.frameworks.Prettier
import semmle.javascript.frameworks.PropertyProjection
import semmle.javascript.frameworks.Puppeteer
import semmle.javascript.frameworks.React
import semmle.javascript.frameworks.ReactNative
import semmle.javascript.frameworks.Redux
import semmle.javascript.frameworks.Request
import semmle.javascript.frameworks.RxJS
import semmle.javascript.frameworks.ServerLess
import semmle.javascript.frameworks.ShellJS
import semmle.javascript.frameworks.SystemCommandExecutors
import semmle.javascript.frameworks.SQL
import semmle.javascript.frameworks.SocketIO
import semmle.javascript.frameworks.StringFormatters
import semmle.javascript.frameworks.TorrentLibraries
import semmle.javascript.frameworks.Typeahead
import semmle.javascript.frameworks.UriLibraries
import semmle.javascript.frameworks.Vue
import semmle.javascript.frameworks.Vuex
import semmle.javascript.frameworks.WebSocket
import semmle.javascript.frameworks.XmlParsers
import semmle.javascript.frameworks.xUnit
import semmle.javascript.linters.ESLint
import semmle.javascript.linters.JSLint
import semmle.javascript.linters.Linting
import semmle.javascript.security.dataflow.RemoteFlowSources

View File

@@ -0,0 +1,4 @@
---
dependencies: {}
compiled: false
lockVersion: 1.0.0

View File

@@ -1,5 +1,7 @@
name: codeql-javascript
version: 0.0.0
dbscheme: semmlecode.javascript.dbscheme
name: codeql/javascript-queries
version: 0.0.3
suites: codeql-suites
extractor: javascript
dependencies:
codeql/javascript-all: "*"
codeql/suite-helpers: "*"

View File

@@ -1,3 +0,0 @@
/** Provides classes for working with files and folders. */
import semmle.javascript.Files

View File

@@ -1,321 +0,0 @@
/**
* Provides classes for working with
* [Asynchronous Module Definitions](https://github.com/amdjs/amdjs-api/wiki/AMD).
*/
import javascript
private import semmle.javascript.internal.CachedStages
/**
* An AMD `define` call.
*
* Example:
*
* ```
* define(['fs', 'express'], function(fs, express) {
* ...
* });
* ```
*
* The first argument is an (optional) array of dependencies,
* the second a factory method or object.
*
* We also recognize the three-argument form `define('m', ['fs', 'express'], ...)`
* where the first argument is the module name, the second argument an
* array of dependencies, and the third argument a factory method or object.
*/
class AmdModuleDefinition extends CallExpr {
AmdModuleDefinition() {
getParent() instanceof ExprStmt and
getCallee().(GlobalVarAccess).getName() = "define" and
exists(int n | n = getNumArgument() |
n = 1
or
n = 2 and getArgument(0) instanceof ArrayExpr
or
n = 3 and getArgument(0) instanceof ConstantString and getArgument(1) instanceof ArrayExpr
)
}
/** Gets the array of module dependencies, if any. */
ArrayExpr getDependencies() {
result = getArgument(0) or
result = getArgument(1)
}
/** Gets the `i`th dependency of this module definition. */
PathExpr getDependency(int i) { result = getDependencies().getElement(i) }
/** Gets a dependency of this module definition. */
PathExpr getADependency() {
result = getDependency(_) or
result = getARequireCall().getAnArgument()
}
/**
* Gets a data flow node containing the factory value of this module definition.
*/
pragma[nomagic]
DataFlow::SourceNode getFactoryNode() {
result = getFactoryNodeInternal() and
result instanceof DataFlow::ValueNode
}
private DataFlow::Node getFactoryNodeInternal() {
// To avoid recursion, this should not depend on `SourceNode`.
result = DataFlow::valueNode(getLastArgument()) or
result = getFactoryNodeInternal().getAPredecessor()
}
/** Gets the expression defining this module. */
Expr getModuleExpr() {
exists(DataFlow::Node f | f = getFactoryNode() |
if f instanceof DataFlow::FunctionNode
then
exists(ReturnStmt ret | ret.getContainer() = f.(DataFlow::FunctionNode).getAstNode() |
result = ret.getExpr()
)
else result = f.asExpr()
)
}
/** Gets a source node whose value becomes the definition of this module. */
DataFlow::SourceNode getAModuleSource() { result.flowsToExpr(getModuleExpr()) }
/**
* Holds if `p` is the parameter corresponding to dependency `dep`.
*/
predicate dependencyParameter(PathExpr dep, Parameter p) {
exists(int i |
dep = getDependency(i) and
p = getFactoryParameter(i)
)
}
/**
* Gets the parameter corresponding to dependency `name`.
*
* For instance, in the module definition
*
* ```
* define(['dep1', 'dep2'], function(pdep1, pdep2) { ... })
* ```
*
* parameters `pdep1` and `pdep2` correspond to dependencies
* `dep1` and `dep2`.
*/
Parameter getDependencyParameter(string name) {
exists(PathExpr dep |
dependencyParameter(dep, result) and
dep.getValue() = name
)
}
/**
* Gets the `i`th parameter of the factory function of this module.
*/
private Parameter getFactoryParameter(int i) {
getFactoryNodeInternal().asExpr().(Function).getParameter(i) = result
}
/**
* Gets the parameter corresponding to the pseudo-dependency `require`.
*/
Parameter getRequireParameter() {
result = getDependencyParameter("require")
or
// if no dependencies are listed, the first parameter is assumed to be `require`
not exists(getDependencies()) and result = getFactoryParameter(0)
}
pragma[noinline]
private Variable getRequireVariable() { result = getRequireParameter().getVariable() }
/**
* Gets the parameter corresponding to the pseudo-dependency `exports`.
*/
Parameter getExportsParameter() {
result = getDependencyParameter("exports")
or
// if no dependencies are listed, the second parameter is assumed to be `exports`
not exists(getDependencies()) and result = getFactoryParameter(1)
}
/**
* Gets the parameter corresponding to the pseudo-dependency `module`.
*/
Parameter getModuleParameter() {
result = getDependencyParameter("module")
or
// if no dependencies are listed, the third parameter is assumed to be `module`
not exists(getDependencies()) and result = getFactoryParameter(2)
}
/**
* Gets an abstract value representing one or more values that may flow
* into this module's `module.exports` property.
*/
DefiniteAbstractValue getAModuleExportsValue() {
result = [getAnImplicitExportsValue(), getAnExplicitExportsValue()]
}
pragma[noinline, nomagic]
private AbstractValue getAnImplicitExportsValue() {
// implicit exports: anything that is returned from the factory function
result = getModuleExpr().analyze().getAValue()
}
pragma[noinline]
private AbstractValue getAnExplicitExportsValue() {
// explicit exports: anything assigned to `module.exports`
exists(AbstractProperty moduleExports, AmdModule m |
this = m.getDefine() and
moduleExports.getBase().(AbstractModuleObject).getModule() = m and
moduleExports.getPropertyName() = "exports"
|
result = moduleExports.getAValue()
)
}
/**
* Gets a call to `require` inside this module.
*/
CallExpr getARequireCall() {
result.getCallee().getUnderlyingValue() = getRequireVariable().getAnAccess()
}
}
/**
* DEPRECATED: Use `AmdModuleDefinition` instead.
*/
deprecated class AMDModuleDefinition = AmdModuleDefinition;
/** An AMD dependency, considered as a path expression. */
private class AmdDependencyPath extends PathExprCandidate {
AmdDependencyPath() {
exists(AmdModuleDefinition amd |
this = amd.getDependencies().getAnElement() or
this = amd.getARequireCall().getAnArgument()
)
}
}
/** A constant path element appearing in an AMD dependency expression. */
private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
ConstantAmdDependencyPathElement() { this = any(AmdDependencyPath amd).getAPart() }
override string getValue() { result = getStringValue() }
}
/**
* 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
}
/**
* An AMD dependency, viewed as an import.
*/
private class AmdDependencyImport extends Import {
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
override PathExpr getImportedPath() { result = this }
/**
* Gets a file that looks like it might be the target of this import.
*
* Specifically, we look for files whose absolute path ends with the imported path, possibly
* adding well-known JavaScript file extensions like `.js`.
*/
private File guessTarget() {
exists(PathString imported, string abspath, string dirname, string basename |
targetCandidate(result, abspath, imported, dirname, basename)
|
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
or
exists(Folder dir |
// `dir` ends with the dirname of the imported path
dir.getAbsolutePath().regexpMatch(".*/\\Q" + dirname + "\\E") or
dirname = ""
|
result = dir.getJavaScriptFile(basename)
)
)
}
/**
* Holds if `f` is a file whose stem (that is, basename without extension) matches the imported path.
*
* Additionally, `abspath` is bound to the absolute path of `f`, `imported` to the imported path, and
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
*/
private predicate targetCandidate(
File f, string abspath, PathString imported, string dirname, string basename
) {
imported = getImportedPath().getValue() and
f.getStem() = imported.getStem() and
f.getAbsolutePath() = abspath and
dirname = imported.getDirName() and
basename = imported.getBaseName()
}
/**
* Gets the module whose absolute path matches this import, if there is only a single such module.
*/
private Module resolveByAbsolutePath() {
result.getFile() = unique(File file | file = guessTarget())
}
override Module getImportedModule() {
result = super.getImportedModule()
or
not exists(super.getImportedModule()) and
result = resolveByAbsolutePath()
}
override DataFlow::Node getImportedModuleNode() {
exists(Parameter param |
any(AmdModuleDefinition def).dependencyParameter(this, param) and
result = DataFlow::parameterNode(param)
)
}
}
/**
* An AMD-style module.
*
* Example:
*
* ```
* define(['fs', 'express'], function(fs, express) {
* ...
* });
* ```
*/
class AmdModule extends Module {
cached
AmdModule() {
Stages::DataFlowStage::ref() and
exists(unique(AmdModuleDefinition def | amdModuleTopLevel(def, this)))
}
/** Gets the definition of this module. */
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
override DataFlow::Node getAnExportedValue(string name) {
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
pwn.getBase().analyze().getAValue() = getDefine().getAModuleExportsValue() and
name = pwn.getPropertyName()
)
}
}
/**
* DEPRECATED: Use `AmdModule` instead.
*/
deprecated class AMDModule = AmdModule;

View File

@@ -1,478 +0,0 @@
/**
* Provides classes for working with the AST-based representation of JavaScript programs.
*/
import javascript
private import internal.StmtContainers
private import semmle.javascript.internal.CachedStages
/**
* A program element corresponding to JavaScript code, such as an expression
* or a statement.
*
* This class provides generic traversal methods applicable to all AST nodes,
* such as obtaining the children of an AST node.
*
* Examples:
*
* ```
* function abs(x) {
* return x < 0 ? -x : x;
* }
* abs(-42);
* ```
*/
class ASTNode extends @ast_node, NodeInStmtContainer {
override Location getLocation() { hasLocation(this, result) }
override File getFile() {
result = getLocation().getFile() // Specialized for performance reasons
}
/** Gets the first token belonging to this element. */
Token getFirstToken() {
exists(Location l1, Location l2 |
l1 = this.getLocation() and
l2 = result.getLocation() and
l1.getFile() = l2.getFile() and
l1.getStartLine() = l2.getStartLine() and
l1.getStartColumn() = l2.getStartColumn()
)
}
/** Gets the last token belonging to this element. */
Token getLastToken() {
exists(Location l1, Location l2 |
l1 = this.getLocation() and
l2 = result.getLocation() and
l1.getFile() = l2.getFile() and
l1.getEndLine() = l2.getEndLine() and
l1.getEndColumn() = l2.getEndColumn()
) and
// exclude empty EOF token
not result instanceof EOFToken
}
/** Gets a token belonging to this element. */
Token getAToken() {
exists(string path, int sl, int sc, int el, int ec, int tksl, int tksc, int tkel, int tkec |
this.getLocation().hasLocationInfo(path, sl, sc, el, ec) and
result.getLocation().hasLocationInfo(path, tksl, tksc, tkel, tkec)
|
(
sl < tksl
or
sl = tksl and sc <= tksc
) and
(
tkel < el
or
tkel = el and tkec <= ec
)
) and
// exclude empty EOF token
not result instanceof EOFToken
}
/** Gets the toplevel syntactic unit to which this element belongs. */
cached
TopLevel getTopLevel() { Stages::Ast::ref() and result = getParent().getTopLevel() }
/**
* Gets the `i`th child node of this node.
*
* _Note_: The indices of child nodes are considered an implementation detail and may
* change between versions of the extractor.
*/
ASTNode getChild(int i) {
result = getChildExpr(i) or
result = getChildStmt(i) or
properties(result, this, i, _, _) or
result = getChildTypeExpr(i)
}
/** Gets the `i`th child statement of this node. */
Stmt getChildStmt(int i) { stmts(result, _, this, i, _) }
/** Gets the `i`th child expression of this node. */
Expr getChildExpr(int i) { exprs(result, _, this, i, _) }
/** Gets the `i`th child type expression of this node. */
TypeExpr getChildTypeExpr(int i) { typeexprs(result, _, this, i, _) }
/** Gets a child node of this node. */
ASTNode getAChild() { result = getChild(_) }
/** Gets a child expression of this node. */
Expr getAChildExpr() { result = getChildExpr(_) }
/** Gets a child statement of this node. */
Stmt getAChildStmt() { result = getChildStmt(_) }
/** Gets the number of child nodes of this node. */
int getNumChild() { result = count(getAChild()) }
/** Gets the number of child expressions of this node. */
int getNumChildExpr() { result = count(getAChildExpr()) }
/** Gets the number of child statements of this node. */
int getNumChildStmt() { result = count(getAChildStmt()) }
/** Gets the parent node of this node, if any. */
cached
ASTNode getParent() { Stages::Ast::ref() and this = result.getAChild() }
/** Gets the first control flow node belonging to this syntactic entity. */
ControlFlowNode getFirstControlFlowNode() { result = this }
/** Holds if this syntactic entity belongs to an externs file. */
predicate inExternsFile() { getTopLevel().isExterns() }
/**
* Holds if this is an ambient node that is not a `TypeExpr` and is not inside a `.d.ts` file
*
* Since the overwhelming majority of ambient nodes are `TypeExpr` or inside `.d.ts` files,
* we avoid caching them.
*/
cached
private predicate isAmbientInternal() {
Stages::Ast::ref() and
getParent().isAmbientInternal()
or
not isAmbientTopLevel(getTopLevel()) and
(
this instanceof ExternalModuleDeclaration
or
this instanceof GlobalAugmentationDeclaration
or
this instanceof ExportAsNamespaceDeclaration
or
this instanceof TypeAliasDeclaration
or
this instanceof InterfaceDeclaration
or
has_declare_keyword(this)
or
has_type_keyword(this)
or
// An export such as `export declare function f()` should be seen as ambient.
has_declare_keyword(this.(ExportNamedDeclaration).getOperand())
or
exists(Function f |
this = f and
not f.hasBody()
)
)
}
/**
* Holds if this is part of an ambient declaration or type annotation in a TypeScript file.
*
* A declaration is ambient if it occurs under a `declare` modifier or is
* an interface declaration, type alias, type annotation, or type-only import/export declaration.
*
* The TypeScript compiler emits no code for ambient declarations, but they
* can affect name resolution and type checking at compile-time.
*/
pragma[inline]
predicate isAmbient() {
isAmbientInternal()
or
isAmbientTopLevel(getTopLevel())
or
this instanceof TypeExpr
}
}
/**
* Holds if the given file is a `.d.ts` file.
*/
cached
private predicate isAmbientTopLevel(TopLevel tl) {
Stages::Ast::ref() and tl.getFile().getBaseName().matches("%.d.ts")
}
/**
* A toplevel syntactic unit; that is, a stand-alone script, an inline script
* embedded in an HTML `<script>` tag, a code snippet assigned to an HTML event
* handler attribute, or a `javascript:` URL.
*
* Example:
*
* ```
* <script>
* window.done = true;
* </script>
* ```
*/
class TopLevel extends @toplevel, StmtContainer {
/** Holds if this toplevel is minified. */
predicate isMinified() {
// file name contains 'min' (not as part of a longer word)
getFile().getBaseName().regexpMatch(".*[^-._]*[-._]min([-._].*)?\\.\\w+")
or
exists(int numstmt | numstmt = strictcount(Stmt s | s.getTopLevel() = this) |
// there are more than two statements per line on average
numstmt.(float) / getNumberOfLines() > 2 and
// and there are at least ten statements overall
numstmt >= 10
)
or
// many variables, and they all have short names
count(VarDecl d | d.getTopLevel() = this) > 100 and
forall(VarDecl d | d.getTopLevel() = this | d.getName().length() <= 2)
}
/** Holds if this toplevel is an externs definitions file. */
predicate isExterns() {
// either it was explicitly extracted as an externs file...
is_externs(this)
or
// ...or it has a comment with an `@externs` tag in it
exists(JSDocTag externs |
externs.getTitle() = "externs" and
externs.getTopLevel() = this
)
}
/** Gets the toplevel to which this element belongs, that is, itself. */
override TopLevel getTopLevel() { result = this }
/** Gets the number of lines in this toplevel. */
int getNumberOfLines() { numlines(this, result, _, _) }
/** Gets the number of lines containing code in this toplevel. */
int getNumberOfLinesOfCode() { numlines(this, _, result, _) }
/** Gets the number of lines containing comments in this toplevel. */
int getNumberOfLinesOfComments() { numlines(this, _, _, result) }
override predicate isStrict() { getAStmt() instanceof StrictModeDecl }
override ControlFlowNode getFirstControlFlowNode() { result = getEntry() }
override string toString() { result = "<toplevel>" }
}
/**
* A stand-alone file or script originating from an HTML `<script>` element.
*
* Example:
*
* ```
* <script>
* window.done = true;
* </script>
* ```
*/
class Script extends TopLevel {
Script() { this instanceof @script or this instanceof @inline_script }
}
/**
* A stand-alone file or an external script originating from an HTML `<script>` element.
*
* Example:
*
* ```
* #! /usr/bin/node
* console.log("Hello, world!");
* ```
*/
class ExternalScript extends @script, Script { }
/**
* A script embedded inline in an HTML `<script>` element.
*
* Example:
*
* ```
* <script>
* window.done = true;
* </script>
* ```
*/
class InlineScript extends @inline_script, Script { }
/**
* A code snippet originating from an HTML attribute value.
*
* Examples:
*
* ```
* <div onclick="alert('hi')">Click me</div>
* <a href="javascript:alert('hi')">Click me</a>
* ```
*/
class CodeInAttribute extends TopLevel {
CodeInAttribute() {
this instanceof @event_handler or
this instanceof @javascript_url or
this instanceof @angular_template_toplevel
}
}
/**
* A code snippet originating from an event handler attribute.
*
* Example:
*
* ```
* <div onclick="alert('hi')">Click me</div>
* ```
*/
class EventHandlerCode extends @event_handler, CodeInAttribute { }
/**
* A code snippet originating from a URL with the `javascript:` URL scheme.
*
* Example:
*
* ```
* <a href="javascript:alert('hi')">Click me</a>
* ```
*/
class JavaScriptURL extends @javascript_url, CodeInAttribute { }
/**
* A toplevel syntactic entity containing Closure-style externs definitions.
*
* Example:
*
* <pre>
* /** @externs *&#47;
* /** @typedef {String} *&#47;
* var MyString;
* </pre>
*/
class Externs extends TopLevel {
Externs() { isExterns() }
}
/**
* A program element that is either an expression or a statement.
*
* Examples:
*
* ```
* var i = 0;
* i = 9
* ```
*/
class ExprOrStmt extends @expr_or_stmt, ControlFlowNode, ASTNode { }
/**
* A program element that contains statements, but isn't itself
* a statement, in other words a toplevel or a function.
*
* Example:
*
* ```
* function f() {
* g();
* }
* ```
*/
class StmtContainer extends @stmt_container, ASTNode {
/** Gets the innermost enclosing container in which this container is nested. */
cached
StmtContainer getEnclosingContainer() { none() }
/**
* Gets the innermost enclosing function or top-level,
* possibly this container itself if it is a function or top-level.
*
* To get a strictly enclosing function or top-level, use
* `getEnclosingContainer().getFunctionBoundary()`.
*
* TypeScript namespace declarations are containers that are not considered
* function boundaries. In plain JavaScript, all containers are function boundaries.
*/
StmtContainer getFunctionBoundary() {
if this instanceof Function or this instanceof TopLevel
then result = this
else result = getEnclosingContainer().getFunctionBoundary()
}
/** Gets a statement that belongs to this container. */
Stmt getAStmt() { result.getContainer() = this }
/**
* Gets the body of this container.
*
* For scripts or modules, this is the container itself; for functions,
* it is the function body.
*/
ASTNode getBody() { result = this }
/**
* Gets the (unique) entry node of the control flow graph for this toplevel or function.
*
* For most purposes, the start node should be used instead of the entry node;
* see predicate `getStart()`.
*/
ControlFlowEntryNode getEntry() { result.getContainer() = this }
/** Gets the (unique) exit node of the control flow graph for this toplevel or function. */
ControlFlowExitNode getExit() { result.getContainer() = this }
/**
* Gets the (unique) CFG node at which execution of this toplevel or function begins.
*
* Unlike the entry node, which is a synthetic construct, the start node corresponds to
* an actual program element, such as the first statement of a toplevel or the first
* parameter of a function.
*
* Empty toplevels do not have a start node.
*/
ConcreteControlFlowNode getStart() { successor(getEntry(), result) }
/**
* Gets the entry basic block of this function, that is, the basic block
* containing the entry node of its CFG.
*/
EntryBasicBlock getEntryBB() { result = getEntry() }
/**
* Gets the start basic block of this function, that is, the basic block
* containing the start node of its CFG.
*/
BasicBlock getStartBB() { result.getANode() = getStart() }
/** Gets the scope induced by this toplevel or function, if any. */
Scope getScope() { scopenodes(this, result) }
/**
* Holds if the code in this container is executed in ECMAScript strict mode.
*
* See Annex C of the ECMAScript language specification.
*/
predicate isStrict() { getEnclosingContainer().isStrict() }
}
/**
* Provides a class `ValueNode` encompassing all program elements that evaluate to
* a value at runtime.
*/
module AST {
/**
* A program element that evaluates to a value or destructures a value at runtime.
* This includes expressions and destructuring patterns, but also function and
* class declaration statements, as well as TypeScript namespace and enum declarations.
*
* Examples:
*
* ```
* 0 // expression
* (function id(x) { return x; }) // parenthesized function expression
* function id(x) { return x; } // function declaration
* ```
*/
class ValueNode extends ASTNode, @dataflownode {
/** Gets type inference results for this element. */
DataFlow::AnalyzedNode analyze() { result = DataFlow::valueNode(this).analyze() }
/** Gets the data flow node associated with this program element. */
DataFlow::ValueNode flow() { result = DataFlow::valueNode(this) }
}
}

View File

@@ -1,368 +0,0 @@
/**
* Provides aliases for commonly used classes that have different names
* in the QL libraries for other languages.
*/
import javascript
class AndBitwiseExpr = BitAndExpr;
class AndLogicalExpr = LogAndExpr;
class ArrayAccess = IndexExpr;
class AssignOp = CompoundAssignExpr;
/**
* DEPRECATED: The name `BlockStmt` is now preferred in all languages.
*/
deprecated class Block = BlockStmt;
class BoolLiteral = BooleanLiteral;
class CaseStmt = Case;
class ComparisonOperation = Comparison;
class DoStmt = DoWhileStmt;
class EqualityOperation = EqualityTest;
class FieldAccess = DotExpr;
class InstanceOfExpr = InstanceofExpr;
class LabelStmt = LabeledStmt;
class LogicalAndExpr = LogAndExpr;
class LogicalNotExpr = LogNotExpr;
class LogicalOrExpr = LogOrExpr;
class Loop = LoopStmt;
class MultilineComment = BlockComment;
class OrBitwiseExpr = BitOrExpr;
class OrLogicalExpr = LogOrExpr;
class ParenthesisExpr = ParExpr;
class ParenthesizedExpr = ParExpr;
class RelationalOperation = RelationalComparison;
class RemExpr = ModExpr;
class SingleLineComment = LineComment;
class SuperAccess = SuperExpr;
class SwitchCase = Case;
class ThisAccess = ThisExpr;
class VariableAccess = VarAccess;
class XorBitwiseExpr = XOrExpr;
// Aliases for deprecated predicates from the dbscheme
/**
* Alias for the predicate `is_externs` defined in the .dbscheme.
* Use `TopLevel#isExterns()` instead.
*/
deprecated predicate isExterns(TopLevel toplevel) { is_externs(toplevel) }
/**
* Alias for the predicate `is_module` defined in the .dbscheme.
* Use the `Module` class in `Module.qll` instead.
*/
deprecated predicate isModule(TopLevel toplevel) { is_module(toplevel) }
/**
* Alias for the predicate `is_nodejs` defined in the .dbscheme.
* Use `NodeModule` from `NodeJS.qll` instead.
*/
deprecated predicate isNodejs(TopLevel toplevel) { is_nodejs(toplevel) }
/**
* Alias for the predicate `is_es2015_module` defined in the .dbscheme.
* Use `ES2015Module` from `ES2015Modules.qll` instead.
*/
deprecated predicate isES2015Module(TopLevel toplevel) { is_es2015_module(toplevel) }
/**
* Alias for the predicate `is_closure_module` defined in the .dbscheme.
*/
deprecated predicate isClosureModule(TopLevel toplevel) { is_closure_module(toplevel) }
/**
* Alias for the predicate `stmt_containers` defined in the .dbscheme.
* Use `ASTNode#getContainer()` instead.
*/
deprecated predicate stmtContainers(Stmt stmt, StmtContainer container) {
stmt_containers(stmt, container)
}
/**
* Alias for the predicate `jump_targets` defined in the .dbscheme.
* Use `JumpStmt#getTarget()` instead.
*/
deprecated predicate jumpTargets(Stmt jump, Stmt target) { jump_targets(jump, target) }
/**
* Alias for the predicate `is_instantiated` defined in the .dbscheme.
* Use `NamespaceDeclaration#isInstantiated() instead.`
*/
deprecated predicate isInstantiated(NamespaceDeclaration decl) { is_instantiated(decl) }
/**
* Alias for the predicate `has_declare_keyword` defined in the .dbscheme.
*/
deprecated predicate hasDeclareKeyword(ASTNode stmt) { has_declare_keyword(stmt) }
/**
* Alias for the predicate `is_for_await_of` defined in the .dbscheme.
* Use `ForOfStmt#isAwait()` instead.
*/
deprecated predicate isForAwaitOf(ForOfStmt forof) { is_for_await_of(forof) }
/**
* Alias for the predicate `enclosing_stmt` defined in the .dbscheme.
* Use `ExprOrType#getEnclosingStmt` instead.
*/
deprecated predicate enclosingStmt(ExprOrType expr, Stmt stmt) { enclosing_stmt(expr, stmt) }
/**
* Alias for the predicate `expr_containers` defined in the .dbscheme.
* Use `ASTNode#getContainer()` instead.
*/
deprecated predicate exprContainers(ExprOrType expr, StmtContainer container) {
expr_containers(expr, container)
}
/**
* Alias for the predicate `array_size` defined in the .dbscheme.
* Use `ArrayExpr#getSize()` instead.
*/
deprecated predicate arraySize(Expr ae, int sz) { array_size(ae, sz) }
/**
* Alias for the predicate `is_delegating` defined in the .dbscheme.
* Use `YieldExpr#isDelegating()` instead.
*/
deprecated predicate isDelegating(YieldExpr yield) { is_delegating(yield) }
/**
* Alias for the predicate `is_arguments_object` defined in the .dbscheme.
* Use the `ArgumentsVariable` class instead.
*/
deprecated predicate isArgumentsObject(Variable id) { is_arguments_object(id) }
/**
* Alias for the predicate `is_computed` defined in the .dbscheme.
* Use the `isComputed()` method on the `MemberDeclaration`/`Property`/`PropertyPattern` class instead.
*/
deprecated predicate isComputed(Property prop) { is_computed(prop) }
/**
* Alias for the predicate `is_method` defined in the .dbscheme.
* Use the `isMethod()` method on the `MemberDeclaration`/`Property` class instead.
*/
deprecated predicate isMethod(Property prop) { is_method(prop) }
/**
* Alias for the predicate `is_static` defined in the .dbscheme.
* Use `MemberDeclaration#isStatic()` instead.
*/
deprecated predicate isStatic(Property prop) { is_static(prop) }
/**
* Alias for the predicate `is_abstract_member` defined in the .dbscheme.
* Use `MemberDeclaration#isAbstract()` instead.
*/
deprecated predicate isAbstractMember(Property prop) { is_abstract_member(prop) }
/**
* Alias for the predicate `is_const_enum` defined in the .dbscheme.
* Use `EnumDeclaration#isConst()` instead.
*/
deprecated predicate isConstEnum(EnumDeclaration id) { is_const_enum(id) }
/**
* Alias for the predicate `is_abstract_class` defined in the .dbscheme.
* Use `ClassDefinition#isAbstract()` instead.
*/
deprecated predicate isAbstractClass(ClassDeclStmt id) { is_abstract_class(id) }
/**
* Alias for the predicate `has_public_keyword` defined in the .dbscheme.
* Use `MemberDeclaration#hasPublicKeyword() instead.
*/
deprecated predicate hasPublicKeyword(Property prop) { has_public_keyword(prop) }
/**
* Alias for the predicate `has_private_keyword` defined in the .dbscheme.
* Use `MemberDeclaration#isPrivate() instead.
*/
deprecated predicate hasPrivateKeyword(Property prop) { has_private_keyword(prop) }
/**
* Alias for the predicate `has_protected_keyword` defined in the .dbscheme.
* Use `MemberDeclaration#isProtected() instead.
*/
deprecated predicate hasProtectedKeyword(Property prop) { has_protected_keyword(prop) }
/**
* Alias for the predicate `has_readonly_keyword` defined in the .dbscheme.
* Use `FieldDeclaration#isReadonly()` instead.
*/
deprecated predicate hasReadonlyKeyword(Property prop) { has_readonly_keyword(prop) }
/**
* Alias for the predicate `has_type_keyword` defined in the .dbscheme.
* Use the `isTypeOnly` method on the `ImportDeclaration`/`ExportDeclaration` classes instead.
*/
deprecated predicate hasTypeKeyword(ASTNode id) { has_type_keyword(id) }
/**
* Alias for the predicate `is_optional_member` defined in the .dbscheme.
* Use `FieldDeclaration#isOptional()` instead.
*/
deprecated predicate isOptionalMember(Property id) { is_optional_member(id) }
/**
* Alias for the predicate `has_definite_assignment_assertion` defined in the .dbscheme.
* Use the `hasDefiniteAssignmentAssertion` method on the `FieldDeclaration`/`VariableDeclarator` classes instead.
*/
deprecated predicate hasDefiniteAssignmentAssertion(ASTNode id) {
has_definite_assignment_assertion(id)
}
/**
* Alias for the predicate `is_optional_parameter_declaration` defined in the .dbscheme.
* Use `Parameter#isDeclaredOptional()` instead.
*/
deprecated predicate isOptionalParameterDeclaration(Parameter parameter) {
is_optional_parameter_declaration(parameter)
}
/**
* Alias for the predicate `has_asserts_keyword` defined in the .dbscheme.
* Use `PredicateTypeExpr#hasAssertsKeyword() instead.
*/
deprecated predicate hasAssertsKeyword(PredicateTypeExpr node) { has_asserts_keyword(node) }
/**
* Alias for the predicate `js_parse_errors` defined in the .dbscheme.
* Use the `JSParseError` class instead.
*/
deprecated predicate jsParseErrors(JSParseError id, TopLevel toplevel, string message, string line) {
js_parse_errors(id, toplevel, message, line)
}
/**
* Alias for the predicate `regexp_parse_errors` defined in the .dbscheme.
* Use the `RegExpParseError` class instead.
*/
deprecated predicate regexpParseErrors(RegExpParseError id, RegExpTerm regexp, string message) {
regexp_parse_errors(id, regexp, message)
}
/**
* Alias for the predicate `is_greedy` defined in the .dbscheme.
* Use `RegExpQuantifier#isGreedy` instead.
*/
deprecated predicate isGreedy(RegExpQuantifier id) { is_greedy(id) }
/**
* Alias for the predicate `range_quantifier_lower_bound` defined in the .dbscheme.
* Use `RegExpRange#getLowerBound()` instead.
*/
deprecated predicate rangeQuantifierLowerBound(RegExpRange id, int lo) {
range_quantifier_lower_bound(id, lo)
}
/**
* Alias for the predicate `range_quantifier_upper_bound` defined in the .dbscheme.
* Use `RegExpRange#getUpperBound() instead.
*/
deprecated predicate rangeQuantifierUpperBound(RegExpRange id, int hi) {
range_quantifier_upper_bound(id, hi)
}
/**
* Alias for the predicate `is_capture` defined in the .dbscheme.
* Use `RegExpGroup#isCapture()` instead.
*/
deprecated predicate isCapture(RegExpGroup id, int number) { is_capture(id, number) }
/**
* Alias for the predicate `is_named_capture` defined in the .dbscheme.
* Use `RegExpGroup#isNamed()` instead.
*/
deprecated predicate isNamedCapture(RegExpGroup id, string name) { is_named_capture(id, name) }
/**
* Alias for the predicate `is_inverted` defined in the .dbscheme.
* Use `RegExpCharacterClass#isInverted()` instead.
*/
deprecated predicate isInverted(RegExpCharacterClass id) { is_inverted(id) }
/**
* Alias for the predicate `regexp_const_value` defined in the .dbscheme.
* Use `RegExpConstant#getValue()` instead.
*/
deprecated predicate regexpConstValue(RegExpConstant id, string value) {
regexp_const_value(id, value)
}
/**
* Alias for the predicate `char_class_escape` defined in the .dbscheme.
* Use `RegExpCharacterClassEscape#getValue()` instead.
*/
deprecated predicate charClassEscape(RegExpCharacterClassEscape id, string value) {
char_class_escape(id, value)
}
/**
* Alias for the predicate `named_backref` defined in the .dbscheme.
* Use `RegExpBackRef#getName()` instead.
*/
deprecated predicate namedBackref(RegExpBackRef id, string name) { named_backref(id, name) }
/**
* Alias for the predicate `unicode_property_escapename` defined in the .dbscheme.
* Use `RegExpUnicodePropertyEscape#getName()` instead.
*/
deprecated predicate unicodePropertyEscapeName(RegExpUnicodePropertyEscape id, string name) {
unicode_property_escapename(id, name)
}
/**
* Alias for the predicate `unicode_property_escapevalue` defined in the .dbscheme.
* Use `RegExpUnicodePropertyEscape#getValue()` instead.
*/
deprecated predicate unicodePropertyEscapeValue(RegExpUnicodePropertyEscape id, string value) {
unicode_property_escapevalue(id, value)
}
/**
* Alias for the predicate `is_generator` defined in the .dbscheme.
* Use `Function#isGenerator()` instead.
*/
deprecated predicate isGenerator(Function fun) { is_generator(fun) }
/**
* Alias for the predicate `has_rest_parameter` defined in the .dbscheme.
* Use `Function#hasRestParameter()` instead.
*/
deprecated predicate hasRestParameter(Function fun) { has_rest_parameter(fun) }
/**
* Alias for the predicate `is_async` defined in the .dbscheme.
* Use `Function#isAsync()` instead.
*/
deprecated predicate isAsync(Function fun) { is_async(fun) }

File diff suppressed because it is too large Load Diff

View File

@@ -1,409 +0,0 @@
import javascript
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.dataflow.internal.PreCallGraphStep
/**
* Classes and predicates for modelling TaintTracking steps for arrays.
*/
module ArrayTaintTracking {
/**
* A taint propagating data flow edge caused by the builtin array functions.
*/
private class ArrayFunctionTaintStep extends TaintTracking::SharedTaintStep {
override predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) {
arrayFunctionTaintStep(pred, succ, _)
}
}
/**
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
*/
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
// `elt` and `ary`; similar for `forEach`
exists(Function f |
call.getArgument(0).getAFunctionValue(0).getFunction() = f and
call.(DataFlow::MethodCallNode).getMethodName() = ["map", "forEach"] and
pred = call.getReceiver() and
succ = DataFlow::parameterNode(f.getParameter([0, 2]))
)
or
// `array.map` with tainted return value in callback
exists(DataFlow::FunctionNode f |
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
pred = f.getAReturn() and
succ = call
)
or
// `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()
)
or
// `array.reduce` with tainted value in callback
call.(DataFlow::MethodCallNode).getMethodName() = "reduce" and
pred = call.getArgument(0).(DataFlow::FunctionNode).getAReturn() and // Require the argument to be a closure to avoid spurious call/return flow
succ = call
or
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
pred = call.getAnArgument() and
succ.(DataFlow::SourceNode).getAMethodCall(["push", "unshift"]) = call
or
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
pred = call.getASpreadArgument() and
// Make sure we handle reflective calls
succ = call.getReceiver().getALocalSource() and
call.getCalleeName() = ["push", "unshift"]
or
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
pred = call.getArgument(2) and
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
succ = call
or
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
call = arrayFromCall() and
pred = call.getAnArgument() and
succ = call
or
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
succ = call
or
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
succ = call and
pred = call.getAnArgument()
or
// find
// `e = arr.find(callback)`
call = arrayFindCall(pred) and
succ = call
}
}
/**
* Classes and predicates for modelling data-flow for arrays.
*/
private module ArrayDataFlow {
private import DataFlow::PseudoProperties
/**
* A step modelling the creation of an Array using the `Array.from(x)` method.
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
*/
private class ArrayFrom extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
exists(DataFlow::CallNode call |
call = arrayFromCall() and
pred = call.getArgument(0) and
succ = call and
fromProp = arrayLikeElement() and
toProp = arrayElement()
)
}
}
/**
* A step modelling an array copy where the spread operator is used.
* The result is essentially array concatenation.
*
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
*/
private class ArrayCopySpread extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
fromProp = arrayLikeElement() and
toProp = arrayElement() and
(
exists(DataFlow::MethodCallNode mcn |
mcn.getMethodName() = ["push", "unshift"] and
pred = mcn.getASpreadArgument() and
succ = mcn.getReceiver().getALocalSource()
)
or
pred = succ.(DataFlow::ArrayCreationNode).getASpreadArgument()
)
}
}
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["push", "unshift"] and
element = call.getAnArgument() and
obj.getAMethodCall() = call
)
}
}
/**
* A node that reads or writes an element from an array inside a for-loop.
*/
private class ArrayIndexingAccess extends DataFlow::Node {
DataFlow::PropRef read;
ArrayIndexingAccess() {
read = this and
TTNumber() =
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
exists(VarAccess i, ExprOrVarDecl init |
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
|
i.getVariable().getADefinition() = init or
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
)
}
}
/**
* A step for reading/writing an element from an array inside a for-loop.
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
*/
private class ArrayIndexingStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(ArrayIndexingAccess access |
prop = arrayElement() and
obj = access.(DataFlow::PropRead).getBase() and
element = access
)
}
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(ArrayIndexingAccess access |
prop = arrayElement() and
element = access.(DataFlow::PropWrite).getRhs() and
access = obj.getAPropertyWrite()
)
}
}
/**
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
* 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
prop = arrayElement() and
obj = call.getReceiver() and
element = call
)
}
}
/**
* A step for iterating an array using `map` or `forEach`.
*
* Array elements can be loaded from the array `arr` to `e` in e.g: `arr.forEach(e => ...)`.
*
* And array elements can be stored into a resulting array using `map(...)`.
* E.g. in `arr.map(e => foo)`, the resulting array (`arr.map(e => foo)`) will contain the element `foo`.
*
* And the second parameter in the callback is the array ifself, so there is a `loadStoreStep` from the array to that second parameter.
*/
private class ArrayIteration extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["map", "forEach"] and
prop = arrayElement() and
obj = call.getReceiver() and
element = call.getCallback(0).getParameter(0)
)
}
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "map" and
prop = arrayElement() and
element = call.getCallback(0).getAReturn() and
obj = call
)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["map", "forEach"] and
prop = arrayElement() and
pred = call.getReceiver() and
succ = call.getCallback(0).getParameter(2)
)
}
}
/**
* A step for creating an array and storing the elements in the array.
*/
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
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()
)
}
}
/**
* A step modelling that `splice` can insert elements into an array.
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
*/
private class ArraySpliceStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "splice" and
prop = arrayElement() and
element = call.getArgument(2) and
call = obj.getAMethodCall()
)
}
}
/**
* A step for modelling `concat`.
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
*/
private class ArrayConcatStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "concat" and
prop = arrayElement() and
(pred = call.getReceiver() or pred = call.getAnArgument()) and
succ = call
)
}
}
/**
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["slice", "splice", "filter"] and
prop = arrayElement() and
pred = call.getReceiver() and
succ = call
)
}
}
/**
* A step modelling that elements from an array `arr` are received by calling `find`.
*/
private class ArrayFindStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::CallNode call |
call = arrayFindCall(pred) and
succ = call and
prop = arrayElement()
)
}
}
}
private import ArrayLibraries
/**
* Classes and predicates modelling various libraries that work on arrays or array-like structures.
*/
private module ArrayLibraries {
private import DataFlow::PseudoProperties
/**
* Gets a call to `Array.from` or a polyfill implementing the same functionality.
*/
DataFlow::CallNode arrayFromCall() {
result = DataFlow::globalVarRef("Array").getAMemberCall("from")
or
result = DataFlow::moduleImport("array-from").getACall()
}
/**
* Gets a call to `Array.prototype.find` or a polyfill implementing the same functionality.
*/
DataFlow::CallNode arrayFindCall(DataFlow::Node array) {
result.(DataFlow::MethodCallNode).getMethodName() = "find" and
array = result.getReceiver()
or
result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall() and
array = result.getArgument(0)
}
/**
* A taint step through the `arrify` library, or other libraries that (maybe) convert values into arrays.
*/
private class ArrayifyStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::CallNode call | call = API::moduleImport(["arrify", "array-ify"]).getACall() |
pred = call.getArgument(0) and succ = call
)
}
}
/**
* A call to a library that copies the elements of an array into another array.
* E.g. `array-union` that creates a union of multiple arrays, or `array-uniq` that creates an array with unique elements.
*/
DataFlow::CallNode arrayCopyCall(DataFlow::Node array) {
result = API::moduleImport(["array-union", "array-uniq", "uniq"]).getACall() and
array = result.getAnArgument()
}
/**
* A taint step for a library that copies the elements of an array into another array.
*/
private class ArrayCopyTaint extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
call = arrayCopyCall(pred) and
succ = call
)
}
}
/**
* A loadStoreStep for a library that copies the elements of an array into another array.
*/
private class ArrayCopyLoadStore extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::CallNode call |
call = arrayCopyCall(pred) and
succ = call and
prop = arrayElement()
)
}
}
/**
* A taint step through a call to `Array.prototype.flat` or a polyfill implementing array flattening.
*/
private class ArrayFlatStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | succ = call |
call.(DataFlow::MethodCallNode).getMethodName() = "flat" and
pred = call.getReceiver()
or
call =
API::moduleImport(["array-flatten", "arr-flatten", "flatten", "array.prototype.flat"])
.getACall() and
pred = call.getAnArgument()
)
}
}
}

View File

@@ -1,200 +0,0 @@
/**
* Provides classes and predicates for working with base64 encoders and decoders.
*/
import javascript
module Base64 {
/** A call to a base64 encoder. */
class Encode extends DataFlow::Node {
Encode::Range encode;
Encode() { this = encode }
/** Gets the input passed to the encoder. */
DataFlow::Node getInput() { result = encode.getInput() }
/** Gets the base64-encoded output of the encoder. */
DataFlow::Node getOutput() { result = encode.getOutput() }
}
module Encode {
/**
* A data flow node that should be considered a call to a base64 encoder.
*
* New base64 encoding functions can be supported by extending this class.
*/
abstract class Range extends DataFlow::Node {
/** Gets the input passed to the encoder. */
abstract DataFlow::Node getInput();
/** Gets the base64-encoded output of the encoder. */
abstract DataFlow::Node getOutput();
}
}
/** A call to a base64 decoder. */
class Decode extends DataFlow::Node {
Decode::Range encode;
Decode() { this = encode }
/** Gets the base64-encoded input passed to the decoder. */
DataFlow::Node getInput() { result = encode.getInput() }
/** Gets the output of the decoder. */
DataFlow::Node getOutput() { result = encode.getOutput() }
}
module Decode {
/**
* A data flow node that should be considered a call to a base64 decoder.
*
* New base64 decoding functions can be supported by extending this class.
*/
abstract class Range extends DataFlow::Node {
/** Gets the base64-encoded input passed to the decoder. */
abstract DataFlow::Node getInput();
/** Gets the output of the decoder. */
abstract DataFlow::Node getOutput();
}
}
/**
* A base64 decoding step, viewed as a taint-propagating data flow edge.
*
* Note that we currently do not model base64 encoding as a taint-propagating data flow edge
* to avoid false positives.
*/
private class Base64DecodingStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(Decode dec |
pred = dec.getInput() and
succ = dec.getOutput()
)
}
}
}
/** A call to `btoa`. */
private class Btoa extends Base64::Encode::Range, DataFlow::CallNode {
Btoa() { this = DataFlow::globalVarRef("btoa").getACall() }
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/** A call to `atob`. */
private class Atob extends Base64::Decode::Range, DataFlow::CallNode {
Atob() { this = DataFlow::globalVarRef("atob").getACall() }
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to `Buffer.prototype.toString` with encoding `base64`, approximated by
* looking for calls to `toString` where the first argument is the string `"base64"`.
*/
private class Buffer_toString extends Base64::Encode::Range, DataFlow::MethodCallNode {
Buffer_toString() {
getMethodName() = "toString" and
getArgument(0).mayHaveStringValue("base64")
}
override DataFlow::Node getInput() { result = getReceiver() }
override DataFlow::Node getOutput() { result = this }
}
/** A call to `Buffer.from` with encoding `base64`. */
private class Buffer_from extends Base64::Decode::Range, DataFlow::CallNode {
Buffer_from() {
this = DataFlow::globalVarRef("Buffer").getAMemberCall("from") and
getArgument(1).mayHaveStringValue("base64")
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to a base64 encoding function from one of the npm packages
* `base-64`, `js-base64`, `Base64`, or `base64-js`.
*/
private class NpmBase64Encode extends Base64::Encode::Range, DataFlow::CallNode {
NpmBase64Encode() {
exists(DataFlow::SourceNode enc |
enc = DataFlow::moduleImport("b64u") or
enc = DataFlow::moduleImport("b64url") or
enc = DataFlow::moduleImport("btoa") or
enc = DataFlow::moduleMember("Base64", "btoa") or
enc = DataFlow::moduleMember("abab", "btoa") or
enc = DataFlow::moduleMember("b2a", "btoa") or
enc = DataFlow::moduleMember("b64-lite", "btoa") or
enc = DataFlow::moduleMember("b64-lite", "toBase64") or
enc = DataFlow::moduleMember("b64u", "encode") or
enc = DataFlow::moduleMember("b64u", "toBase64") or
enc = DataFlow::moduleMember("b64u-lite", "toBase64Url") or
enc = DataFlow::moduleMember("b64u-lite", "toBinaryString") or
enc = DataFlow::moduleMember("b64url", "encode") or
enc = DataFlow::moduleMember("b64url", "toBase64") or
enc = DataFlow::moduleMember("base-64", "encode") or
enc = DataFlow::moduleMember("base64-js", "toByteArray") or
enc = DataFlow::moduleMember("base64-url", "encode") or
enc = DataFlow::moduleMember("base64url", "encode") or
enc = DataFlow::moduleMember("base64url", "toBase64") or
enc = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("encode") or
enc = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("encodeURI") or
enc = DataFlow::moduleMember("urlsafe-base64", "encode") or
enc = DataFlow::moduleMember("react-native-base64", ["encode", "encodeFromByteArray"])
|
this = enc.getACall()
)
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A call to a base64 decoding function from one of the npm packages
* `base-64`, `js-base64`, `Base64`, or `base64-js`.
*/
private class NpmBase64Decode extends Base64::Decode::Range, DataFlow::CallNode {
NpmBase64Decode() {
exists(DataFlow::SourceNode dec |
dec = DataFlow::moduleImport("atob") or
dec = DataFlow::moduleMember("Base64", "atob") or
dec = DataFlow::moduleMember("abab", "atob") or
dec = DataFlow::moduleMember("b2a", "atob") or
dec = DataFlow::moduleMember("b64-lite", "atob") or
dec = DataFlow::moduleMember("b64-lite", "fromBase64") or
dec = DataFlow::moduleMember("b64u", "decode") or
dec = DataFlow::moduleMember("b64u", "fromBase64") or
dec = DataFlow::moduleMember("b64u-lite", "fromBase64Url") or
dec = DataFlow::moduleMember("b64u-lite", "fromBinaryString") or
dec = DataFlow::moduleMember("b64url", "decode") or
dec = DataFlow::moduleMember("b64url", "fromBase64") or
dec = DataFlow::moduleMember("base-64", "decode") or
dec = DataFlow::moduleMember("base64-js", "fromByteArray") or
dec = DataFlow::moduleMember("base64-url", "decode") or
dec = DataFlow::moduleMember("base64url", "decode") or
dec = DataFlow::moduleMember("base64url", "fromBase64") or
dec = DataFlow::moduleMember("js-base64", "Base64").getAPropertyRead("decode") or
dec = DataFlow::moduleMember("urlsafe-base64", "decode") or
dec = DataFlow::moduleMember("react-native-base64", "decode")
|
this = dec.getACall()
)
}
override DataFlow::Node getInput() { result = getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}

View File

@@ -1,358 +0,0 @@
/**
* Provides classes for working with basic blocks, and predicates for computing
* liveness information for local variables.
*/
import javascript
private import internal.StmtContainers
private import semmle.javascript.internal.CachedStages
/**
* Holds if `nd` starts a new basic block.
*/
private predicate startsBB(ControlFlowNode nd) {
not exists(nd.getAPredecessor()) and exists(nd.getASuccessor())
or
nd.isJoin()
or
nd.getAPredecessor().isBranch()
}
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `bb`.
*/
private predicate succBB(BasicBlock bb, BasicBlock succ) { succ = bb.getLastNode().getASuccessor() }
/**
* Holds if the first node of basic block `bb` is a control flow
* successor of the last node of basic block `pre`.
*/
private predicate predBB(BasicBlock bb, BasicBlock pre) { succBB(pre, bb) }
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof ControlFlowEntryNode }
/** Holds if `bb` is an exit basic block. */
private predicate exitBB(BasicBlock bb) { bb.getLastNode() instanceof ControlFlowExitNode }
cached
private module Internal {
/**
* Holds if `succ` is a control flow successor of `nd` within the same basic block.
*/
private predicate intraBBSucc(ControlFlowNode nd, ControlFlowNode succ) {
succ = nd.getASuccessor() and
not succ instanceof BasicBlock
}
/**
* Holds if `nd` is the `i`th node in basic block `bb`.
*
* In other words, `i` is the shortest distance from a node `bb`
* that starts a basic block to `nd` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(BasicBlock bb, ControlFlowNode nd, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bb, nd, i)
cached
int bbLength(BasicBlock bb) { result = strictcount(ControlFlowNode nd | bbIndex(bb, nd, _)) }
cached
predicate useAt(BasicBlock bb, int i, Variable v, VarUse u) {
Stages::BasicBlocks::ref() and
v = u.getVariable() and
bbIndex(bb, u, i)
}
cached
predicate defAt(BasicBlock bb, int i, Variable v, VarDef d) {
exists(VarRef lhs |
lhs = d.getTarget().(BindingPattern).getABindingVarRef() and
v = lhs.getVariable()
|
lhs = d.getTarget() and
bbIndex(bb, d, i)
or
exists(PropertyPattern pp |
lhs = pp.getValuePattern() and
bbIndex(bb, pp, i)
)
or
exists(ObjectPattern op |
lhs = op.getRest() and
bbIndex(bb, lhs, i)
)
or
exists(ArrayPattern ap |
lhs = ap.getAnElement() and
bbIndex(bb, lhs, i)
)
)
}
cached
predicate reachableBB(BasicBlock bb) {
entryBB(bb)
or
exists(BasicBlock predBB | succBB(predBB, bb) | reachableBB(predBB))
}
}
private import Internal
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
private predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
private predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(exitBB/1, predBB/2)(_, dom, bb)
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*
* At the database level, a basic block is represented by its first control flow node.
*/
class BasicBlock extends @cfg_node, NodeInStmtContainer {
cached
BasicBlock() { Stages::BasicBlocks::ref() and startsBB(this) }
/** Gets a basic block succeeding this one. */
BasicBlock getASuccessor() { succBB(this, result) }
/** Gets a basic block preceding this one. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets a node in this block. */
ControlFlowNode getANode() { result = getNode(_) }
/** Gets the node at the given position in this block. */
ControlFlowNode getNode(int pos) { bbIndex(this, result, pos) }
/** Gets the first node in this block. */
ControlFlowNode getFirstNode() { result = this }
/** Gets the last node in this block. */
ControlFlowNode getLastNode() { result = getNode(length() - 1) }
/** Gets the length of this block. */
int length() { result = bbLength(this) }
/** 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`. */
predicate defAt(int i, Variable v, VarDef d) { defAt(this, i, v, d) }
/**
* Holds if `v` is live at entry to this basic block and `u` is a use of `v`
* witnessing the liveness.
*
* In other words, `u` is a use of `v` that is reachable from the
* entry node of this basic block without going through a redefinition
* of `v`. The use `u` may either be in this basic block, or in another
* basic block reachable from this one.
*/
predicate isLiveAtEntry(Variable v, VarUse u) {
// restrict `u` to be reachable from this basic block
u = getASuccessor*().getANode() and
(
// shortcut: if `v` is never defined, then it must be live
isDefinedInSameContainer(v)
implies
// otherwise, do full liveness computation
isLiveAtEntryImpl(v, u)
)
}
/**
* Holds if `v` is live at entry to this basic block and `u` is a use of `v`
* witnessing the liveness, where `v` is defined at least once in the enclosing
* function or script.
*/
private predicate isLiveAtEntryImpl(Variable v, VarUse u) {
isLocallyLiveAtEntry(v, u)
or
isDefinedInSameContainer(v) and
not this.defAt(_, v, _) and
getASuccessor().isLiveAtEntryImpl(v, u)
}
/**
* Holds if `v` is defined at least once in the function or script to which
* this basic block belongs.
*/
private predicate isDefinedInSameContainer(Variable v) {
exists(VarDef def | def.getAVariable() = v and def.getContainer() = getContainer())
}
/**
* Holds if `v` is a variable that is live at entry to this basic block.
*
* Note that this is equivalent to `bb.isLiveAtEntry(v, _)`, but may
* be more efficient on large databases.
*/
predicate isLiveAtEntry(Variable v) {
isLocallyLiveAtEntry(v, _)
or
not this.defAt(_, v, _) and getASuccessor().isLiveAtEntry(v)
}
/**
* Holds if local variable `v` is live at entry to this basic block and
* `u` is a use of `v` witnessing the liveness.
*/
predicate localIsLiveAtEntry(LocalVariable v, VarUse u) {
isLocallyLiveAtEntry(v, u)
or
not this.defAt(_, v, _) and getASuccessor().localIsLiveAtEntry(v, u)
}
/**
* Holds if local variable `v` is live at entry to this basic block.
*/
predicate localIsLiveAtEntry(LocalVariable v) {
isLocallyLiveAtEntry(v, _)
or
not this.defAt(_, v, _) and getASuccessor().localIsLiveAtEntry(v)
}
/**
* Holds if `d` is a definition of `v` that is reachable from the beginning of
* this basic block without going through a redefinition of `v`.
*/
predicate localMayBeOverwritten(LocalVariable v, VarDef d) {
isLocallyOverwritten(v, d)
or
not defAt(_, v, _) and getASuccessor().localMayBeOverwritten(v, d)
}
/**
* Gets the next index after `i` in this basic block at which `v` is
* defined or used, provided that `d` is a definition of `v` at index `i`.
* If there are no further uses or definitions of `v` after `i`, the
* result is the length of this basic block.
*/
private int nextDefOrUseAfter(PurelyLocalVariable v, int i, VarDef d) {
defAt(i, v, d) and
result =
min(int j |
(defAt(j, v, _) or useAt(j, v, _) or j = length()) and
j > i
)
}
/**
* Holds if `d` defines variable `v` at the `i`th node of this basic block, and
* the definition is live, that is, the variable may be read after this
* definition and before a re-definition.
*/
predicate localLiveDefAt(PurelyLocalVariable v, int i, VarDef d) {
exists(int j | j = nextDefOrUseAfter(v, i, d) |
useAt(j, v, _)
or
j = length() and getASuccessor().localIsLiveAtEntry(v)
)
}
/**
* Holds if `u` is a use of `v` in this basic block, and there are
* no definitions of `v` before it.
*/
private predicate isLocallyLiveAtEntry(Variable v, VarUse u) {
exists(int n | useAt(n, v, u) | not exists(int m | m < n | defAt(m, v, _)))
}
/**
* Holds if `d` is a definition of `v` in this basic block, and there are
* no other definitions of `v` before it.
*/
private predicate isLocallyOverwritten(Variable v, VarDef d) {
exists(int n | defAt(n, v, d) | not exists(int m | m < n | defAt(m, v, _)))
}
/**
* Gets the basic block that immediately dominates this basic block.
*/
ReachableBasicBlock getImmediateDominator() { bbIDominates(result, this) }
}
/**
* An unreachable basic block, that is, a basic block
* whose first node is unreachable.
*/
class UnreachableBlock extends BasicBlock {
UnreachableBlock() { getFirstNode().isUnreachable() }
}
/**
* An entry basic block, that is, a basic block
* whose first node is the entry node of a statement container.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { entryBB(this) }
}
/**
* A basic block that is reachable from an entry basic block.
*/
class ReachableBasicBlock extends BasicBlock {
ReachableBasicBlock() { reachableBB(this) }
/**
* Holds if this basic block strictly dominates `bb`.
*/
pragma[inline]
predicate strictlyDominates(ReachableBasicBlock bb) { bbIDominates+(this, bb) }
/**
* Holds if this basic block dominates `bb`.
*
* This predicate is reflexive: each reachable basic block dominates itself.
*/
pragma[inline]
predicate dominates(ReachableBasicBlock bb) { bbIDominates*(this, bb) }
/**
* Holds if this basic block strictly post-dominates `bb`.
*/
pragma[inline]
predicate strictlyPostDominates(ReachableBasicBlock bb) { bbIPostDominates+(this, bb) }
/**
* Holds if this basic block post-dominates `bb`.
*
* This predicate is reflexive: each reachable basic block post-dominates itself.
*/
pragma[inline]
predicate postDominates(ReachableBasicBlock bb) { bbIPostDominates*(this, bb) }
}
/**
* A reachable basic block with more than one predecessor.
*/
class ReachableJoinBlock extends ReachableBasicBlock {
ReachableJoinBlock() { getFirstNode().isJoin() }
/**
* Holds if this basic block belongs to the dominance frontier of `b`, that is
* `b` dominates a predecessor of this block, but not this block itself.
*
* Algorithm from Cooper et al., "A Simple, Fast Dominance Algorithm" (Figure 5),
* who in turn attribute it to Ferrante et al., "The program dependence graph and
* its use in optimization".
*/
predicate inDominanceFrontierOf(ReachableBasicBlock b) {
b = getAPredecessor() and not b = getImmediateDominator()
or
exists(ReachableBasicBlock prev | inDominanceFrontierOf(prev) |
b = prev.getImmediateDominator() and
not b = getImmediateDominator()
)
}
}

View File

@@ -1,419 +0,0 @@
/**
* Provides classes for working with a CFG-based program representation.
*
* ## Overview
*
* Each `StmtContainer` (that is, function or toplevel) has an intra-procedural
* CFG associated with it, which is composed of `ControlFlowNode`s under a successor
* relation exposed by predicates `ControlFlowNode.getASuccessor()` and
* `ControlFlowNode.getAPredecessor()`.
*
* Each CFG has designated entry and exit nodes with types
* `ControlFlowEntryNode` and `ControlFlowExitNode`, respectively, which are the only two
* subtypes of `SyntheticControlFlowNode`. All `ControlFlowNode`s that are _not_
* `SyntheticControlFlowNode`s belong to class `ConcreteControlFlowNode`.
*
* The predicate `ASTNode.getFirstControlFlowNode()` relates AST nodes
* to the first (concrete) CFG node in the sub-graph of the CFG
* corresponding to the node.
*
* Most statement containers also have a _start node_, obtained by
* `StmtContainer.getStart()`, which is the unique CFG node at which execution
* of the toplevel or function begins. Unlike the entry node, which is a synthetic
* construct, the start node corresponds to an AST node: for instance, for
* toplevels, it is the first CFG node of the first statement, and for functions
* with parameters it is the CFG node corresponding to the first parameter.
*
* Empty toplevels do not have a start node, since all their CFG nodes are
* synthetic.
*
* ## CFG Nodes
*
* Non-synthetic CFG nodes exist for six kinds of AST nodes, representing various
* aspects of the program's runtime semantics:
*
* - `Expr`: the CFG node represents the evaluation of the expression,
* including any side effects this may have;
* - `Stmt`: the CFG node represents the execution of the statement;
* - `Property`: the CFG node represents the assignment of the property;
* - `PropertyPattern`: the CFG node represents the matching of the property;
* - `MemberDefinition`: the CFG node represents the definition of the member
* method or field;
* - `MemberSignature`: the CFG node represents the point where the signature
* is declared, although this has no effect at runtime.
*
* ## CFG Structure
*
* ### Expressions
*
* For most expressions, the successor relation visits sub-expressions first,
* and then the expression itself, representing the order of evaluation at
* runtime. For example, the CFG for the expression `23 + 19` is
*
* <pre>
* &hellip; &rarr; [23] &rarr; [19] &rarr; [23 + 19] &rarr; &hellip;
* </pre>
*
* In particular, this means that `23` is the first CFG node of the expression
* `23 + 19`.
*
* Similarly, for assignments the left hand side is visited first, then
* the right hand side, then the assignment itself:
*
* <pre>
* &hellip; &rarr; [x] &rarr; [y] &rarr; [x = y] &rarr; &hellip;
* </pre>
*
* For properties, the name expression is visited first, then the value,
* then the default value, if any. The same principle applies for getter
* and setter properties: in this case, the "value" is simply the accessor
* function, and there is no default value.
*
* There are only a few exceptions, generally for cases where the value of
* the whole expression is the value of one of its sub-expressions. That
* sub-expression then comes last in the CFG:
*
* - Parenthesized expression:
* <pre>
* &hellip; &rarr; [(x)] &rarr; [x] &rarr; &hellip;
* </pre>
* - Conditional expressions:
* <pre>
* &hellip; &rarr; [x ? y : z] &rarr; [x] &#x252c;&rarr; [y] &rarr; &hellip; <br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#x2514;&rarr; [z] &rarr; &hellip;
* </pre>
* - Short-circuiting operator `&&` (same for `||`):
* <pre>
* &hellip; &rarr; [x && y] &rarr; [x] &rarr; &hellip; <br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &darr; <br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [y] &rarr; &hellip;
* </pre>
* - Sequence/comma expressions:
* <pre>
* &hellip; &rarr; [x, y] &rarr; [x] &rarr; [y] &rarr; &hellip;
* </pre>
*
* Finally, array expressions and object expressions also precede their
* sub-expressions in the CFG to model the fact that the new array/object
* is created before its elements/properties are evaluated:
*
* <pre>
* &hellip; &rarr; [{ x: 42 }] &rarr; [x] &rarr; [42] &rarr; [x : 42] &rarr; &hellip;
* </pre>
*
* ### Statements
*
* For most statements, the successor relation visits the statement first and then
* its sub-expressions and sub-statements.
*
* For example, the CFG of a block statement first visits the individual statements,
* then the block statement itself.
*
* Similarly, the CFG for an `if` statement first visits the statement itself, then
* the condition. The condition, in turn, has the "then" branch as one of its successors
* and the "else" branch (if it exists) or the next statement after the "if" (if it does not)
* as the other:
*
* <pre>
* &hellip; &rarr; [if (x) s1 else s2] &rarr; [x] &#x252c;&rarr; [s1] &rarr; &hellip;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#x2514;&rarr; [s2] &rarr; &hellip;
* </pre>
*
* For loops, the CFG reflects the order in which the loop test and the body are
* executed.
*
* For instance, the CFG of a `while` loop starts with the statement itself, followed by
* the condition. The condition has two successors: the body, and the statement following
* the loop. The body, in turn, has the condition as its successor. This reflects the fact
* that `while` loops first test their condition before executing their body:
*
* <pre>
* &hellip; &rarr; [while (x) s] &rarr; [x] &rarr; &hellip;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#x21c5;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [s]
* </pre>
*
* On the other hand, `do`-`while` loops first execute their body before testing their condition:
*
* <pre>
* &hellip; &rarr; [do s while (x)] &rarr; [s] &#x21c4; [x] &rarr; &hellip;
* </pre>
*
* The CFG of a for loop starts with the loop itself, followed by the initializer expression
* (if any), then the test expression (if any). The test expression has two successors: the
* body, and the statement following the loop. The body, in turn, has the update expression
* (if any) as its successor, and the update expression has the test expression as its only
* successor:
*
* <pre>
* &hellip; &rarr; [for(i;t;u) s] &rarr; [i] &rarr; [t] &rarr; &hellip;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#x2199;&nbsp;&#x2196
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[s] &rarr; [u]
* </pre>
*
* The CFG of a for-in loop `for(x in y) s` starts with the loop itself, followed by the
* iteration domain `y`. That node has two successors: the iterator `x`, and the statement
* following the loop (modeling early exit in case `y` is empty). After the iterator `x`
* comes the loop body `s`, which again has two successors: the iterator `x` (modeling the
* case where there are more elements to iterate over), and the statement following the loop
* (modeling the case where there are no more elements to iterate):
*
* <pre>
* &hellip; &rarr; [for(x in y) s] &rarr; [y] &rarr; &nbsp;&hellip;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&darr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&uarr;
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[x] &#x21c4; [s]
* </pre>
*
* For-of loops are the same.
*
* Finally, `return` and `throw` statements are different from all other statement types in
* that for them the statement itself comes _after_ the operand, reflecting the fact that
* the operand is evaluated before the return or throw is initiated:
*
* <pre>
* &hellip; &rarr; [x] &rarr; [return x;] &rarr; &hellip;
* </pre>
*
* ### Unstructured control flow
*
* Unstructured control flow is modeled in the obvious way: `break` and `continue` statements
* have as their successor the next statement that is executed after the jump; `throw`
* statements have the nearest enclosing `catch` clause as their successor, or the exit node
* of the enclosing container if there is no enclosing `catch`; `return` statements have the
* exit node of the enclosing container as their successor.
*
* In all cases, the control flow may be intercepted by an intervening `finally` block. For
* instance, consider the following code snippet:
*
* <pre>
* try {
* &nbsp;&nbsp;if (x)
* &nbsp;&nbsp;&nbsp;&nbsp;return;
* &nbsp;&nbsp;s
* } finally {
* &nbsp;&nbsp;t
* }
* u
* </pre>
*
* Here, the successor of `return` is not the exit node of the enclosing container, but instead
* the `finally` block. The last statement of the `finally` block (here, `t`) has two successors:
* `u` to model the case where `finally` was entered from `s`, and the exit node of the enclosing
* container to model the case where the `return` is resumed after the `finally` block.
*
* Note that `finally` blocks can lead to imprecise control flow modeling since the `finally`
* block resumes the action of _all_ statements it intercepts: in the above example, the CFG
* not only models the executions `return` &rarr; `finally` &rarr; `t` &rarr; `exit` and
* `s` &rarr; `finally` &rarr; `t` &rarr; `u`, but also allows the path `return` &rarr;
* `finally` &rarr; `t` &rarr; `u`, which does not correspond to any actual execution.
*
* The CFG also models the fact that certain kinds of expressions (calls, `new` expressions,
* property accesses and `await` expressions) can throw exceptions, but _only_ if there is
* an enclosing `try`-`catch` statement.
*
* ### Function preambles
*
* The CFG of a function starts with its entry node, followed by a _preamble_, which is a part of
* the CFG that models parameter passing and function hoisting. The preamble is followed by the
* function body, which in turn is followed by the exit node.
*
* For function expressions, the preamble starts with the function name, if any, to reflect the
* fact that the function object is bound to that name inside the scope of the function. Next,
* for both function expressions and function declarations, the parameters are executed in sequence
* to represent parameter passing. If a parameter has a default value, that value is visited before
* the parameter itself. Finally, the CFG nodes corresponding to the names of all hoisted functions
* inside the outer function body are visited in lexical order. This reflects the fact that hoisted
* functions are initialized before the body starts executing, but _after_ parameters have been
* initialized.
*
* For instance, consider the following function declaration:
*
* <pre>
* function outer(x, y = 42) {
* &nbsp;&nbsp;s
* &nbsp;&nbsp;function inner() {}
* &nbsp;&nbsp;t
* }
* </pre>
*
* Its CFG is
*
* <pre>
* [entry] &rarr; [x] &rarr; [42] &rarr; [y] &rarr; [inner] &rarr; [s] &rarr; [function inner() {}] &rarr; [t] &rarr; [exit]
* </pre>
*
* Note that the function declaration `[function inner() {}]` as a whole is part of the CFG of the
* body of `outer`, while its function identifier `inner` is part of the preamble.
*
* ### Toplevel preambles
*
* Similar to functions, toplevels (that is, modules, scripts or event handlers) also have a
* preamble. For ECMAScript 2015 modules, all import specifiers are traversed first, in lexical
* order, reflecting the fact that imports are resolved before execution of the module itself
* begins; next, for all toplevels, the names of hoisted functions are traversed in lexical order
* (as for functions). Afterwards, the CFG continues with the body of the toplevel, and ends
* with the exit node.
*
* As an example, consider the following module:
*
* ```
* s
* import x as y from 'foo';
* function f() {}
* t
* ```
*
* Its CFG is
*
* <pre>
* [entry] &rarr; [x as y] &rarr; [f] &rarr; [s] &rarr; [import x as y from 'foo';] &rarr; [function f() {}] &rarr; [t] &rarr; [exit]
* </pre>
*
* Note that the `import` statement as a whole is part of the CFG of the body, while its single
* import specifier `x as y` forms part of the preamble.
*/
import javascript
private import internal.StmtContainers
/**
* A node in the control flow graph, which is an expression, a statement,
* or a synthetic node.
*/
class ControlFlowNode extends @cfg_node, Locatable, NodeInStmtContainer {
/** Gets a node succeeding this node in the CFG. */
ControlFlowNode getASuccessor() { successor(this, result) }
/** Gets a node preceding this node in the CFG. */
ControlFlowNode getAPredecessor() { this = result.getASuccessor() }
/** Holds if this is a node with more than one successor. */
predicate isBranch() { strictcount(getASuccessor()) > 1 }
/** Holds if this is a node with more than one predecessor. */
predicate isJoin() { strictcount(getAPredecessor()) > 1 }
/**
* Holds if this is a start node, that is, the CFG node where execution of a
* toplevel or function begins.
*/
predicate isStart() { this = any(StmtContainer sc).getStart() }
/**
* Holds if this is a final node of `container`, that is, a CFG node where execution
* of that toplevel or function terminates.
*/
predicate isAFinalNodeOfContainer(StmtContainer container) {
getASuccessor().(SyntheticControlFlowNode).isAFinalNodeOfContainer(container)
}
/**
* Holds if this is a final node, that is, a CFG node where execution of a
* toplevel or function terminates.
*/
final predicate isAFinalNode() { isAFinalNodeOfContainer(_) }
/**
* Holds if this node is unreachable, that is, it has no predecessors in the CFG.
* Entry nodes are always considered reachable.
*
* Note that in a block of unreachable code, only the first node is unreachable
* in this sense. For instance, in
*
* ```
* function foo() { return; s1; s2; }
* ```
*
* `s1` is unreachable, but `s2` is not.
*/
predicate isUnreachable() {
forall(ControlFlowNode pred | pred = getAPredecessor() |
pred.(SyntheticControlFlowNode).isUnreachable()
)
// note the override in ControlFlowEntryNode below
}
/** Gets the basic block this node belongs to. */
BasicBlock getBasicBlock() { this = result.getANode() }
/**
* For internal use.
*
* Gets a string representation of this control-flow node that can help
* distinguish it from other nodes with the same `toString` value.
*/
string describeControlFlowNode() {
if this = any(MethodDeclaration mem).getBody()
then result = "function in " + any(MethodDeclaration mem | mem.getBody() = this)
else
if this instanceof @decorator_list
then result = "parameter decorators of " + this.(ASTNode).getParent().(Function).describe()
else result = toString()
}
}
/**
* A synthetic CFG node that does not correspond to a statement or expression;
* examples include guard nodes and entry/exit nodes.
*/
class SyntheticControlFlowNode extends @synthetic_cfg_node, ControlFlowNode {
override Location getLocation() { hasLocation(this, result) }
}
/** A synthetic CFG node marking the entry point of a function or toplevel script. */
class ControlFlowEntryNode extends SyntheticControlFlowNode, @entry_node {
override predicate isUnreachable() { none() }
override string toString() { result = "entry node of " + getContainer().toString() }
}
/** A synthetic CFG node marking the exit of a function or toplevel script. */
class ControlFlowExitNode extends SyntheticControlFlowNode, @exit_node {
override predicate isAFinalNodeOfContainer(StmtContainer container) {
exit_cfg_node(this, container)
}
override string toString() { result = "exit node of " + getContainer().toString() }
}
/**
* A synthetic CFG node recording that some condition is known to hold
* at this point in the program.
*/
class GuardControlFlowNode extends SyntheticControlFlowNode, @guard_node {
/** Gets the expression that this guard concerns. */
Expr getTest() { guard_node(this, _, result) }
/**
* Holds if this guard dominates basic block `bb`, that is, the guard
* is known to hold at `bb`.
*/
predicate dominates(ReachableBasicBlock bb) {
this = bb.getANode()
or
exists(ReachableBasicBlock prev | prev.strictlyDominates(bb) | this = prev.getANode())
}
}
/**
* A guard node recording that some condition is known to be truthy or
* falsy at this point in the program.
*/
class ConditionGuardNode extends GuardControlFlowNode, @condition_guard {
/** Gets the value recorded for the condition. */
boolean getOutcome() {
guard_node(this, 0, _) and result = false
or
guard_node(this, 1, _) and result = true
}
override string toString() { result = "guard: " + getTest() + " is " + getOutcome() }
}
/**
* A CFG node corresponding to a program element, that is, a CFG node that is
* not a `SyntheticControlFlowNode`.
*/
class ConcreteControlFlowNode extends ControlFlowNode {
ConcreteControlFlowNode() { not this instanceof SyntheticControlFlowNode }
}

View File

@@ -1,338 +0,0 @@
/**
* Provides classes for working with name resolution of namespaces and types.
*/
import javascript
/**
* A fully qualified name relative to a specific root,
* usually referring to a TypeScript namespace or type.
*
* Canonical names are organized in a prefix tree, that is, the "parent"
* of a canonical name is the name corresponding to its prefix.
*
* It is possible for two different canonical names to have the same
* qualified name, namely if they are rooted in different scopes. The `hasQualifiedName`
* predicates deal specifically with canonical names that are rooted in
* the global scope or in the scope of a named module.
*
* This class is only populated when full TypeScript extraction is enabled.
*/
class CanonicalName extends @symbol {
/**
* Gets the parent of this canonical name, that is, the prefix of its qualified name.
*/
CanonicalName getParent() { symbol_parent(this, result) }
/**
* Gets a child of this canonical name, that is, an extension of its qualified name.
*/
CanonicalName getAChild() { result.getParent() = this }
/**
* Gets the child of this canonical name that has the given `name`, if any.
*/
CanonicalName getChild(string name) {
result = getAChild() and
result.getName() = name
}
/**
* Gets the name without prefix.
*/
string getName() { symbols(this, _, result) }
/**
* Gets the name of the external module represented by this canonical name, if any.
*/
string getExternalModuleName() {
symbol_module(this, result)
or
exists(PackageJSON pkg |
getModule() = pkg.getMainModule() and
result = pkg.getPackageName()
)
}
/**
* Gets the name of the global variable represented by this canonical name, if any.
*/
string getGlobalName() { symbol_global(this, result) }
/**
* Gets the module represented by this canonical name, if such a module exists
* and was extracted.
*/
Module getModule() { ast_node_symbol(result, this) }
/**
* Holds if this canonical name has a child, i.e. an extension of its qualified name.
*/
predicate hasChild() { exists(getAChild()) }
/**
* True if this has no parent.
*/
predicate isRoot() { not exists(getParent()) }
/**
* Holds if this is the export namespace of a module.
*/
predicate isModuleRoot() {
exists(getModule()) or
exists(getExternalModuleName())
}
/**
* Holds if this is the export namespace of the given module.
*/
predicate isModuleRoot(StmtContainer mod) { ast_node_symbol(mod, this) }
/**
* Holds if this canonical name is exported by its parent.
*
* Some entities, such as type parameters, have canonical names that are not
* available through qualified name access.
*/
predicate isExportedMember() { this instanceof @member_symbol }
/** Holds if this has the given qualified name, rooted in the global scope. */
predicate hasQualifiedName(string globalName) {
globalName = getGlobalName()
or
exists(string prefix |
getParent().hasQualifiedName(prefix) and
globalName = prefix + "." + getName()
)
}
/** Holds if this has the given qualified name, rooted in the given external module. */
predicate hasQualifiedName(string moduleName, string exportedName) {
moduleName = getParent().getExternalModuleName() and
exportedName = getName()
or
exists(string prefix |
getParent().hasQualifiedName(moduleName, prefix) and
exportedName = prefix + "." + getName()
)
}
/**
* Gets the qualified name without the root.
*/
string getRelativeName() {
if getParent().isModuleRoot()
then result = getName()
else
if exists(getGlobalName())
then result = min(getGlobalName())
else
if exists(getParent())
then result = getParent().getRelativeName() + "." + getName()
else (
not isModuleRoot() and result = getName()
)
}
/**
* Gets the outermost scope from which this type can be accessed by a qualified name (without using an `import`).
*
* This is typically the top-level of a module, but for locally declared types (e.g. types declared inside a function),
* this is the container where the type is declared.
*/
Scope getRootScope() {
exists(CanonicalName root | root = getRootName() |
if exists(root.getModule())
then result = root.getModule().getScope()
else
if exists(root.getGlobalName())
then result instanceof GlobalScope
else result = getADefinition().getContainer().getScope()
)
}
private CanonicalName getRootName() {
if exists(getGlobalName()) or isModuleRoot() or not exists(getParent())
then result = this
else result = getParent().getRootName()
}
/**
* Gets a definition of the entity with this canonical name.
*/
ASTNode getADefinition() { none() }
/**
* Gets a use that refers to the entity with this canonical name.
*/
ExprOrType getAnAccess() { none() }
/**
* Gets a string describing the root scope of this canonical name.
*/
string describeRoot() {
exists(CanonicalName root | root = getRootName() |
if exists(root.getExternalModuleName())
then result = "module '" + min(root.getExternalModuleName()) + "'"
else
if exists(root.getModule().getFile().getRelativePath())
then result = root.getModule().getFile().getRelativePath()
else
if exists(root.getGlobalName())
then result = "global scope"
else
if exists(root.getADefinition())
then
exists(StmtContainer container | container = root.getADefinition().getContainer() |
result = container.(TopLevel).getFile().getRelativePath()
or
not container instanceof TopLevel and
result = container.getLocation().toString()
)
else result = "unknown scope"
)
}
/**
* Gets the fully qualified name, followed by the name of its enclosing module or file.
*
* For example, the class `Server` from the `http` module has `toString` value: `Server in module 'http'`.
*
* In the file `foo.ts` below, the class `C` has `toString` value `C in foo.ts:2` because it is
* defined in a lexical scope starting on line 2:
* ```
* namespace X {
* function doWork() {
* class C {}
* }
* }
* ```
*/
string toString() {
if isModuleRoot()
then result = describeRoot()
else result = getRelativeName() + " in " + describeRoot()
}
}
/**
* The canonical name for a type.
*/
class TypeName extends CanonicalName {
TypeName() {
exists(TypeReference ref | type_symbol(ref, this)) or
exists(TypeDefinition def | ast_node_symbol(def, this)) or
base_type_names(_, this) or
base_type_names(this, _)
}
/**
* Gets a definition of the type with this canonical name, if any.
*/
override TypeDefinition getADefinition() { ast_node_symbol(result, this) }
/**
* Gets a type annotation that refers to this type name.
*/
override TypeAccess getAnAccess() { result.getTypeName() = this }
/**
* Gets a type that refers to this canonical name.
*/
TypeReference getATypeReference() { result.getTypeName() = this }
/**
* Gets a type named in the `extends` or `implements` clause of this type.
*/
TypeName getABaseTypeName() { base_type_names(this, result) }
/**
* Gets the "self type" that refers to this canonical name.
*
* For a generic class or interface, the type arguments on the self type
* all refer to the corresponding type parameters declared on that class or interface.
*
* For example, the "self type" of `Array` is `Array<T>`, where `T` refers to the
* type parameter declared on the `Array` type.
*/
TypeReference getType() { self_types(this, result) }
}
/**
* The canonical name for a namespace.
*/
class Namespace extends CanonicalName {
Namespace() {
getAChild().isExportedMember() or
exists(NamespaceDefinition def | ast_node_symbol(def, this)) or
exists(NamespaceAccess ref | ast_node_symbol(ref, this))
}
/**
* Gets a definition of the namespace with this canonical name, if any.
*/
override NamespaceDefinition getADefinition() { ast_node_symbol(result, this) }
/**
* Gets a part of a type annotation that refers to this namespace.
*/
override NamespaceAccess getAnAccess() { result.getNamespace() = this }
/** Gets a namespace nested in this one. */
Namespace getNamespaceMember(string name) {
result.getParent() = this and
result.getName() = name and
result.isExportedMember()
}
/** Gets the type of the given name in this namespace, if any. */
TypeName getTypeMember(string name) {
result.getParent() = this and
result.getName() = name and
result.isExportedMember()
}
/**
* Gets a namespace declaration or top-level whose exports contribute directly to this namespace.
*
* This includes containers that don't actually contain any `export` statements, but whose
* exports would contribute to this namespace, if there were any.
*
* Does not include containers whose exports only contribute indirectly, through re-exports.
* That is, there is at most one namespace associated with a given container.
*
* Namespaces defined by enum declarations have no exporting containers.
*/
StmtContainer getAnExportingContainer() { ast_node_symbol(result, this) }
}
/**
* The canonical name for a function.
*/
class CanonicalFunctionName extends CanonicalName {
CanonicalFunctionName() {
exists(Function fun | ast_node_symbol(fun, this)) or
exists(InvokeExpr invoke | ast_node_symbol(invoke, this))
}
/**
* Gets a function with this canonical name.
*/
override Function getADefinition() { ast_node_symbol(result, this) }
/**
* Gets an expression (such as a callee expression in a function call or `new` expression)
* that refers to a function with this canonical name.
*/
override Expr getAnAccess() {
exists(InvokeExpr invk | ast_node_symbol(invk, this) | result = invk.getCallee())
or
ast_node_symbol(result, this) and
not result instanceof InvokeExpr
}
/**
* Gets the implementation of this function, if it exists.
*/
Function getImplementation() { result = getADefinition() and result.hasBody() }
}

View File

@@ -1,104 +0,0 @@
/**
* Provides classes for reasoning about character escapes in literals.
*/
import javascript
module CharacterEscapes {
/**
* Provides sets of characters (as strings) with specific string/regexp characteristics.
*/
private module Sets {
string sharedEscapeChars() { result = "fnrtvux0\\" }
string regexpAssertionChars() { result = "bB" }
string regexpCharClassChars() { result = "cdDpPsSwW" }
string regexpBackreferenceChars() { result = "123456789k" }
string regexpMetaChars() { result = "^$*+?.()|{}[]-" }
}
/**
* Gets the `i`th character of `raw`, which is preceded by an uneven number of backslashes.
*/
bindingset[raw]
string getAnEscapedCharacter(string raw, int i) {
result = raw.regexpFind("(?<=(^|[^\\\\])\\\\(\\\\{2}){0,10}).", _, i)
}
/**
* Holds if `n` is delimited by `delim` and contains `rawStringNode` with the raw string value `raw`.
*/
private predicate hasRawStringAndQuote(
DataFlow::ValueNode n, string delim, ASTNode rawStringNode, string raw
) {
rawStringNode = n.asExpr() and
raw = rawStringNode.(StringLiteral).getRawValue() and
delim = raw.charAt(0)
or
rawStringNode = n.asExpr().(TemplateLiteral).getAnElement() and
raw = rawStringNode.(TemplateElement).getRawValue() and
delim = "`"
or
rawStringNode = n.asExpr() and
raw = rawStringNode.(RegExpLiteral).getRawValue() and
delim = "/"
}
/**
* Gets a character in `n` that is preceded by a single useless backslash.
*
* The character is the `i`th character of `rawStringNode`'s raw string value.
*/
string getAnIdentityEscapedCharacter(DataFlow::Node n, ASTNode rawStringNode, int i) {
exists(string delim, string raw, string additionalEscapeChars |
hasRawStringAndQuote(n, delim, rawStringNode, raw) and
if rawStringNode instanceof RegExpLiteral
then
additionalEscapeChars =
Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() +
Sets::regexpBackreferenceChars()
else additionalEscapeChars = "b"
|
result = getAnEscapedCharacter(raw, i) and
not result = (Sets::sharedEscapeChars() + delim + additionalEscapeChars).charAt(_)
)
}
/**
* Holds if `src` likely contains a regular expression mistake, to be reported by `js/useless-regexp-character-escape`.
*/
predicate hasALikelyRegExpPatternMistake(RegExpPatternSource src) {
exists(getALikelyRegExpPatternMistake(src, _, _, _))
}
/**
* Gets a character in `n` 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`.
*/
string getALikelyRegExpPatternMistake(
RegExpPatternSource src, string mistake, ASTNode rawStringNode, int i
) {
result = getAnIdentityEscapedCharacter(src, rawStringNode, i) and
(
result = Sets::regexpAssertionChars().charAt(_) and mistake = "is not an assertion"
or
result = Sets::regexpCharClassChars().charAt(_) and mistake = "is not a character class"
or
result = Sets::regexpBackreferenceChars().charAt(_) and mistake = "is not a backreference"
or
// conservative formulation: we do not know in general if the sequence is enclosed in a character class `[...]`
result = Sets::regexpMetaChars().charAt(_) and
mistake = "may still represent a meta-character"
) and
// avoid the benign case where preceding escaped backslashes turns into backslashes when the regexp is constructed
not exists(string raw |
not rawStringNode instanceof RegExpLiteral and
hasRawStringAndQuote(_, _, rawStringNode, raw) and
result = raw.regexpFind("(?<=(^|[^\\\\])((\\\\{3})|(\\\\{7}))).", _, i)
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,283 +0,0 @@
/**
* Provides classes for working with the Closure-Library module system.
*/
import javascript
module Closure {
/**
* A reference to a Closure namespace.
*/
class ClosureNamespaceRef extends DataFlow::Node {
ClosureNamespaceRef::Range range;
ClosureNamespaceRef() { this = range }
/**
* Gets the namespace being referenced.
*/
string getClosureNamespace() { result = range.getClosureNamespace() }
}
module ClosureNamespaceRef {
/**
* A reference to a Closure namespace.
*
* Can be subclassed to classify additional nodes as namespace references.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the namespace being referenced.
*/
abstract string getClosureNamespace();
}
}
/**
* A data flow node that returns the value of a closure namespace.
*/
class ClosureNamespaceAccess extends ClosureNamespaceRef {
override ClosureNamespaceAccess::Range range;
}
module ClosureNamespaceAccess {
/**
* A data flow node that returns the value of a closure namespace.
*
* Can be subclassed to classify additional nodes as namespace accesses.
*/
abstract class Range extends ClosureNamespaceRef::Range { }
}
/**
* A call to a method on the `goog.` namespace, as a closure reference.
*/
abstract private class DefaultNamespaceRef extends DataFlow::MethodCallNode,
ClosureNamespaceRef::Range {
DefaultNamespaceRef() { this = DataFlow::globalVarRef("goog").getAMethodCall() }
override string getClosureNamespace() { result = getArgument(0).getStringValue() }
}
/**
* Holds if `node` is the data flow node corresponding to the expression in
* a top-level expression statement.
*/
private predicate isTopLevelExpr(DataFlow::Node node) {
any(TopLevel tl).getAChildStmt().(ExprStmt).getExpr().flow() = node
}
/**
* A top-level call to `goog.provide`.
*/
private class DefaultClosureProvideCall extends DefaultNamespaceRef {
DefaultClosureProvideCall() {
getMethodName() = "provide" and
isTopLevelExpr(this)
}
}
/**
* A top-level call to `goog.provide`.
*/
class ClosureProvideCall extends ClosureNamespaceRef, DataFlow::MethodCallNode {
override DefaultClosureProvideCall range;
}
/**
* A call to `goog.require`.
*/
private class DefaultClosureRequireCall extends DefaultNamespaceRef, ClosureNamespaceAccess::Range {
DefaultClosureRequireCall() { getMethodName() = "require" }
}
/**
* A call to `goog.require`.
*/
class ClosureRequireCall extends ClosureNamespaceAccess, DataFlow::MethodCallNode {
override DefaultClosureRequireCall range;
}
/**
* A top-level call to `goog.module` or `goog.declareModuleId`.
*/
private class DefaultClosureModuleDeclaration extends DefaultNamespaceRef {
DefaultClosureModuleDeclaration() {
(getMethodName() = "module" or getMethodName() = "declareModuleId") and
isTopLevelExpr(this)
}
}
/**
* A top-level call to `goog.module` or `goog.declareModuleId`.
*/
class ClosureModuleDeclaration extends ClosureNamespaceRef, DataFlow::MethodCallNode {
override DefaultClosureModuleDeclaration range;
}
private GlobalVariable googVariable() { variables(result, "goog", any(GlobalScope sc)) }
pragma[nomagic]
private MethodCallExpr googModuleDeclExpr() {
result.getReceiver() = googVariable().getAnAccess() and
result.getMethodName() = ["module", "declareModuleId"]
}
pragma[nomagic]
private MethodCallExpr googModuleDeclExprInContainer(StmtContainer container) {
result = googModuleDeclExpr() and
container = result.getContainer()
}
pragma[noinline]
private ClosureRequireCall getARequireInTopLevel(ClosureModule m) { result.getTopLevel() = m }
/**
* A module using the Closure module system, declared using `goog.module()` or `goog.declareModuleId()`.
*/
class ClosureModule extends Module {
ClosureModule() { exists(googModuleDeclExprInContainer(this)) }
/**
* Gets the call to `goog.module` or `goog.declareModuleId` in this module.
*/
ClosureModuleDeclaration getModuleDeclaration() { result.getTopLevel() = this }
/**
* Gets the namespace of this module.
*/
string getClosureNamespace() { result = getModuleDeclaration().getClosureNamespace() }
override Module getAnImportedModule() {
result.(ClosureModule).getClosureNamespace() =
getARequireInTopLevel(this).getClosureNamespace()
}
/**
* Gets the top-level `exports` variable in this module, if this module is defined by
* a `good.module` call.
*
* This variable denotes the object exported from this module.
*
* Has no result for ES6 modules using `goog.declareModuleId`.
*/
Variable getExportsVariable() {
getModuleDeclaration().getMethodName() = "module" and
result = getScope().getVariable("exports")
}
override DataFlow::Node getAnExportedValue(string name) {
exists(DataFlow::PropWrite write, Expr base |
result = write.getRhs() and
write.writes(base.flow(), name, _) and
(
base = getExportsVariable().getAReference()
or
base = getExportsVariable().getAnAssignedExpr()
)
)
}
}
/**
* A global Closure script, that is, a toplevel that is executed in the global scope and
* contains a toplevel call to `goog.provide` or `goog.require`.
*/
class ClosureScript extends TopLevel {
ClosureScript() {
not this instanceof ClosureModule and
(
any(ClosureProvideCall provide).getTopLevel() = this
or
any(ClosureRequireCall require).getTopLevel() = this
)
}
/** Gets the identifier of a namespace required by this module. */
string getARequiredNamespace() {
exists(ClosureRequireCall require |
require.getTopLevel() = this and
result = require.getClosureNamespace()
)
}
/** Gets the identifer of a namespace provided by this module. */
string getAProvidedNamespace() {
exists(ClosureProvideCall require |
require.getTopLevel() = this and
result = require.getClosureNamespace()
)
}
}
/**
* Holds if `name` is a closure namespace, including proper namespace prefixes.
*/
pragma[noinline]
predicate isClosureNamespace(string name) {
exists(string namespace | namespace = any(ClosureNamespaceRef ref).getClosureNamespace() |
name = namespace.substring(0, namespace.indexOf("."))
or
name = namespace
)
or
name = "goog" // The closure libraries themselves use the "goog" namespace
}
/**
* Holds if a prefix of `name` is a closure namespace.
*/
bindingset[name]
private predicate hasClosureNamespacePrefix(string name) {
isClosureNamespace(name.substring(0, name.indexOf(".")))
or
isClosureNamespace(name)
}
/**
* Gets the closure namespace path addressed by the given data flow node, if any.
*/
string getClosureNamespaceFromSourceNode(DataFlow::SourceNode node) {
node = AccessPath::getAReferenceOrAssignmentTo(result) and
hasClosureNamespacePrefix(result)
}
/**
* Gets the closure namespace path written to by the given property write, if any.
*/
string getWrittenClosureNamespace(DataFlow::PropWrite node) {
node.getRhs() = AccessPath::getAnAssignmentTo(result) and
hasClosureNamespacePrefix(result)
}
/**
* Gets a data flow node that refers to the given value exported from a Closure module.
*/
DataFlow::SourceNode moduleImport(string moduleName) {
getClosureNamespaceFromSourceNode(result) = moduleName
}
/**
* A call to `goog.bind`, as a partial function invocation.
*/
private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode {
BindCall() { this = moduleImport("goog.bind").getACall() }
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
index >= 0 and
callback = getArgument(0) and
argument = getArgument(index + 2)
}
override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
boundArgs = getNumArgument() - 2 and
callback = getArgument(0) and
result = this
}
override DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
callback = getArgument(0) and
result = getArgument(1)
}
}
}

View File

@@ -1,266 +0,0 @@
/**
* Provides predicates and classes for working with the standard library collection implementations.
* Currently [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and
* [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) are implemented.
*/
import javascript
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.
*/
private module CollectionDataFlow {
/**
* A step for `Set.add()` method, which adds an element to a Set.
*/
private class SetAdd extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("add") and
element = call.getArgument(0) and
prop = setElement()
)
}
}
/**
* A step for the `Set` constructor, which copies any elements from the first argument into the resulting set.
*/
private class SetConstructor extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
exists(DataFlow::NewNode invoke |
invoke = DataFlow::globalVarRef("Set").getAnInstantiation() and
pred = invoke.getArgument(0) and
succ = invoke and
fromProp = arrayLikeElement() and
toProp = setElement()
)
}
}
/**
* A step for modelling `for of` iteration on arrays, maps, sets, and iterators.
*
* For sets and iterators the l-value are the elements of the set/iterator.
* For maps the l-value is a tuple containing a key and a value.
*/
private class ForOfStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
e = DataFlow::lvalueNode(forOf.getLValue()) and
prop = arrayLikeElement()
)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
exists(ForOfStmt forOf |
pred = forOf.getIterationDomain().flow() and
succ = DataFlow::lvalueNode(forOf.getLValue()) and
fromProp = mapValueAll() and
toProp = "1"
)
}
}
/**
* A step for a call to `forEach` on a Set or Map.
*/
private class SetMapForEach extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "forEach" and
obj = call.getReceiver() and
element = call.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueAll()]
)
}
}
/**
* A call to the `get` method on a Map.
* If the key of the call to `get` has a known string value, then only the value corresponding to that key will be retrieved. (The known string value is encoded as part of the pseudo-property)
*/
private class MapGet extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "get" and
obj = call.getReceiver() and
element = call and
// reading the join of known and unknown values
(prop = mapValue(call.getArgument(0)) or prop = mapValueUnknownKey())
)
}
}
/**
* A call to the `set` method on a Map.
*
* If the key of the call to `set` has a known string value,
* then the value will be stored into a pseudo-property corresponding to the known string value.
* Otherwise the value will be stored into a pseudo-property corresponding to values with unknown keys.
* The value will additionally be stored into a pseudo-property corresponding to all values.
*/
class MapSet extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("set") and
element = call.getArgument(1) and
prop = [mapValue(call.getArgument(0)), mapValueAll()]
)
}
}
/**
* A step for a call to `values` on a Map or a Set.
*/
private class MapAndSetValues extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "values" and
pred = call.getReceiver() and
succ = call and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
)
}
}
/**
* A step for a call to `keys` on a Set.
*/
private class SetKeys extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "keys" and
pred = call.getReceiver() and
succ = call and
fromProp = setElement() and
toProp = iteratorElement()
)
}
}
}

View File

@@ -1,137 +0,0 @@
/** Provides classes for working with JavaScript comments. */
import javascript
/**
* A JavaScript source-code comment.
*
* Examples:
*
* <pre>
* // a line comment
* /* a block
* comment *&#47
* &lt;!-- an HTML line comment
* </pre>
*/
class Comment extends @comment, Locatable {
override Location getLocation() { hasLocation(this, result) }
/** Gets the toplevel element this comment belongs to. */
TopLevel getTopLevel() { comments(this, _, result, _, _) }
/** Gets the text of this comment, not including delimiters. */
string getText() { comments(this, _, _, result, _) }
/** Gets the `i`th line of comment text. */
string getLine(int i) { result = getText().splitAt("\n", i) }
/** Gets the next token after this comment. */
Token getNextToken() { next_token(this, result) }
override int getNumLines() { result = count(getLine(_)) }
override string toString() { comments(this, _, _, _, result) }
/** Holds if this comment spans lines `start` to `end` (inclusive) in file `f`. */
predicate onLines(File f, int start, int end) {
exists(Location loc | loc = getLocation() |
f = loc.getFile() and
start = loc.getStartLine() and
end = loc.getEndLine()
)
}
}
/**
* A line comment, that is, either an HTML comment or a `//` comment.
*
* Examples:
*
* <pre>
* // a line comment
* &lt;!-- an HTML line comment
* </pre>
*/
class LineComment extends @line_comment, Comment { }
/**
* An HTML comment start/end token interpreted as a line comment.
*
* Example:
*
* ```
* &lt;!-- an HTML line comment
* --> also an HTML line comment
* ```
*/
class HtmlLineComment extends @html_comment, LineComment { }
/**
* An HTML comment start token interpreted as a line comment.
*
* Example:
*
* ```
* &lt;!-- an HTML line comment
* ```
*/
class HtmlCommentStart extends @html_comment_start, HtmlLineComment { }
/**
* An HTML comment end token interpreted as a line comment.
*
* Example:
*
* ```
* --> also an HTML line comment
* ```
*/
class HtmlCommentEnd extends @htmlcommentend, HtmlLineComment { }
/**
* A `//` comment.
*
* Example:
*
* ```
* // a line comment
* ```
*/
class SlashSlashComment extends @slashslash_comment, LineComment { }
/**
* A block comment (which may be a JSDoc comment).
*
* Examples:
*
* <pre>
* /* a block comment
* (but not a JSDoc comment) *&#47;
* /** a JSDoc comment *&#47;
* </pre>
*/
class BlockComment extends @block_comment, Comment { }
/**
* A C-style block comment which is not a JSDoc comment.
*
* Example:
*
* <pre>
* /* a block comment
* (but not a JSDoc comment) *&#47;
* </pre>
*/
class SlashStarComment extends @slashstar_comment, BlockComment { }
/**
* A JSDoc comment.
*
* Example:
*
* <pre>
* /** a JSDoc comment *&#47;
* </pre>
*/
class DocComment extends @doc_comment, BlockComment { }

View File

@@ -1,107 +0,0 @@
/**
* Provides abstract classes representing generic concepts such as file system
* access or system command execution, for which individual framework libraries
* provide concrete subclasses.
*/
import javascript
/**
* A data flow node that executes an operating system command,
* for instance by spawning a new process.
*/
abstract class SystemCommandExecution extends DataFlow::Node {
/** Gets an argument to this execution that specifies the command. */
abstract DataFlow::Node getACommandArgument();
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { none() }
/**
* Gets an argument to this command execution that specifies the argument list
* to the command.
*/
DataFlow::Node getArgumentList() { none() }
/** Holds if the command execution happens synchronously. */
abstract predicate isSync();
/**
* Gets the data-flow node (if it exists) for an options argument.
*/
abstract DataFlow::Node getOptionsArg();
}
/**
* A data flow node that performs a file system access (read, write, copy, permissions, stats, etc).
*/
abstract class FileSystemAccess extends DataFlow::Node {
/** Gets an argument to this file system access that is interpreted as a path. */
abstract DataFlow::Node getAPathArgument();
/**
* Gets an argument to this file system access that is interpreted as a root folder
* in which the path arguments are constrained.
*
* In other words, if a root argument is provided, the underlying file access does its own
* sanitization to prevent the path arguments from traversing outside the root folder.
*/
DataFlow::Node getRootPathArgument() { none() }
/**
* Holds if this file system access will reject paths containing upward navigation
* segments (`../`).
*
* `argument` should refer to the relevant path argument or root path argument.
*/
predicate isUpwardNavigationRejected(DataFlow::Node argument) { none() }
}
/**
* A data flow node that reads data from the file system.
*/
abstract class FileSystemReadAccess extends FileSystemAccess {
/** Gets a node that represents data from the file system. */
abstract DataFlow::Node getADataNode();
}
/**
* A data flow node that writes data to the file system.
*/
abstract class FileSystemWriteAccess extends FileSystemAccess {
/** Gets a node that represents data to be written to the file system. */
abstract DataFlow::Node getADataNode();
}
/**
* A data flow node that contains a file name or an array of file names from the local file system.
*/
abstract class FileNameSource extends DataFlow::Node { }
/**
* A data flow node that performs a database access.
*/
abstract class DatabaseAccess extends DataFlow::Node {
/** Gets an argument to this database access that is interpreted as a query. */
abstract DataFlow::Node getAQueryArgument();
}
/**
* A data flow node that reads persistent data.
*/
abstract class PersistentReadAccess extends DataFlow::Node {
/**
* Gets a corresponding persistent write, if any.
*/
abstract PersistentWriteAccess getAWrite();
}
/**
* A data flow node that writes persistent data.
*/
abstract class PersistentWriteAccess extends DataFlow::Node {
/**
* Gets the data flow node corresponding to the written value.
*/
abstract DataFlow::Node getValue();
}

View File

@@ -1,128 +0,0 @@
/**
* Provides classes for working with expressions that evaluate to constant values.
*/
import javascript
/**
* An expression that evaluates to a constant primitive value.
*/
abstract class ConstantExpr extends Expr { }
/**
* Provides classes for expressions that evaluate to constant values according to a bottom-up syntactic analysis.
*/
module SyntacticConstants {
/**
* An expression that evaluates to a constant value according to a bottom-up syntactic analysis.
*/
abstract class SyntacticConstant extends ConstantExpr { }
/**
* A literal primitive expression.
*
* Note that `undefined`, `NaN` and `Infinity` are global variables, and are not covered by this class.
*/
class PrimitiveLiteralConstant extends SyntacticConstant {
PrimitiveLiteralConstant() {
this instanceof NumberLiteral
or
this instanceof StringLiteral
or
this instanceof BooleanLiteral
or
exists(TemplateLiteral lit | lit = this |
lit.getNumChildExpr() = 0
or
lit.getNumChildExpr() = 1 and
lit.getElement(0) instanceof TemplateElement
)
}
}
/**
* A literal null expression.
*/
class NullConstant extends SyntacticConstant, NullLiteral { }
/**
* A unary operation on a syntactic constant.
*/
class UnaryConstant extends SyntacticConstant, UnaryExpr {
UnaryConstant() { getOperand() instanceof SyntacticConstant }
}
/**
* A binary operation on syntactic constants.
*/
class BinaryConstant extends SyntacticConstant, BinaryExpr {
BinaryConstant() {
getLeftOperand() instanceof SyntacticConstant and
getRightOperand() instanceof SyntacticConstant
}
}
/**
* A conditional expression on syntactic constants.
*/
class ConditionalConstant extends SyntacticConstant, ConditionalExpr {
ConditionalConstant() {
getCondition() instanceof SyntacticConstant and
getConsequent() instanceof SyntacticConstant and
getAlternate() instanceof SyntacticConstant
}
}
/**
* A use of the global variable `undefined` or `void e`.
*/
class UndefinedConstant extends SyntacticConstant {
UndefinedConstant() {
this.(GlobalVarAccess).getName() = "undefined" or
this instanceof VoidExpr
}
}
/**
* A use of the global variable `NaN`.
*/
class NaNConstant extends SyntacticConstant {
NaNConstant() { this.(GlobalVarAccess).getName() = "NaN" }
}
/**
* A use of the global variable `Infinity`.
*/
class InfinityConstant extends SyntacticConstant {
InfinityConstant() { this.(GlobalVarAccess).getName() = "Infinity" }
}
/**
* An expression that wraps the syntactic constant it evaluates to.
*/
class WrappedConstant extends SyntacticConstant {
WrappedConstant() { getUnderlyingValue() instanceof SyntacticConstant }
}
/**
* Holds if `c` evaluates to `undefined`.
*/
predicate isUndefined(SyntacticConstant c) { c.getUnderlyingValue() instanceof UndefinedConstant }
/**
* Holds if `c` evaluates to `null`.
*/
predicate isNull(SyntacticConstant c) { c.getUnderlyingValue() instanceof NullConstant }
/**
* Holds if `c` evaluates to `null` or `undefined`.
*/
predicate isNullOrUndefined(SyntacticConstant c) { isUndefined(c) or isNull(c) }
}
/**
* An expression that evaluates to a constant string.
*/
class ConstantString extends ConstantExpr {
ConstantString() { exists(getStringValue()) }
}

View File

@@ -1,524 +0,0 @@
/**
* Provides classes for working with DOM elements.
*/
import javascript
import semmle.javascript.frameworks.Templating
private import semmle.javascript.dataflow.InferredTypes
module DOM {
/**
* A definition of a DOM element, for instance by an HTML element in an HTML file
* or a JSX element in a JavaScript file.
*/
abstract class ElementDefinition extends Locatable {
/**
* Gets the name of the DOM element; for example, a `<p>` element has
* name `p`.
*/
abstract string getName();
/**
* Gets the `i`th attribute of this DOM element, if it can be determined.
*
* For example, the 0th (and only) attribute of `<a href="https://semmle.com">Semmle</a>`
* is `href="https://semmle.com"`.
*/
AttributeDefinition getAttribute(int i) { none() }
/**
* Gets an attribute of this DOM element with name `name`.
*
* For example, the DOM element `<a href="https://semmle.com">Semmle</a>`
* has a single attribute `href="https://semmle.com"` with the name `href`.
*/
AttributeDefinition getAttributeByName(string name) {
result.getElement() = this and
result.getName() = name
}
/**
* Gets an attribute of this DOM element.
*/
AttributeDefinition getAnAttribute() { result.getElement() = this }
/**
* Gets the parent element of this element.
*/
abstract ElementDefinition getParent();
/**
* Gets the root element (i.e. an element without a parent) in which this element is contained.
*/
ElementDefinition getRoot() {
if not exists(getParent()) then result = this else result = getParent().getRoot()
}
/**
* Gets the document element to which this element belongs, if it can be determined.
*/
DocumentElementDefinition getDocument() { result = getRoot() }
}
/**
* An HTML element, viewed as an `ElementDefinition`.
*/
private class HtmlElementDefinition extends ElementDefinition, @xmlelement {
HtmlElementDefinition() { this instanceof HTML::Element }
override string getName() { result = this.(HTML::Element).getName() }
override AttributeDefinition getAttribute(int i) {
result = this.(HTML::Element).getAttribute(i)
}
override ElementDefinition getParent() { result = this.(HTML::Element).getParent() }
}
/**
* A JSX element, viewed as an `ElementDefinition`.
*/
private class JsxElementDefinition extends ElementDefinition, @jsx_element {
JsxElementDefinition() { this instanceof JSXElement }
override string getName() { result = this.(JSXElement).getName() }
override AttributeDefinition getAttribute(int i) { result = this.(JSXElement).getAttribute(i) }
override ElementDefinition getParent() { result = this.(JSXElement).getJsxParent() }
}
/**
* A DOM attribute as defined, for instance, by an HTML attribute in an HTML file
* or a JSX attribute in a JavaScript file.
*/
abstract class AttributeDefinition extends Locatable {
/**
* Gets the name of this attribute, if any.
*
* JSX spread attributes do not have a name.
*/
abstract string getName();
/**
* Gets the data flow node whose value is the value of this attribute,
* if any.
*
* This is undefined for HTML elements, where the attribute value is not
* computed but specified directly.
*/
DataFlow::Node getValueNode() { none() }
/**
* Gets the value of this attribute, if it can be determined.
*/
string getStringValue() { result = getValueNode().getStringValue() }
/**
* Gets the DOM element this attribute belongs to.
*/
ElementDefinition getElement() { this = result.getAttributeByName(_) }
/**
* Holds if the value of this attribute might be a template value
* such as `{{window.location.url}}`.
*/
predicate mayHaveTemplateValue() {
getStringValue().regexpMatch(Templating::getDelimiterMatchingRegexp())
}
}
/**
* An HTML attribute, viewed as an `AttributeDefinition`.
*/
private class HtmlAttributeDefinition extends AttributeDefinition, @xmlattribute {
HtmlAttributeDefinition() { this instanceof HTML::Attribute }
override string getName() { result = this.(HTML::Attribute).getName() }
override string getStringValue() { result = this.(HTML::Attribute).getValue() }
override ElementDefinition getElement() { result = this.(HTML::Attribute).getElement() }
}
/**
* A JSX attribute, viewed as an `AttributeDefinition`.
*/
private class JsxAttributeDefinition extends AttributeDefinition, @jsx_attribute {
JSXAttribute attr;
JsxAttributeDefinition() { this = attr }
override string getName() { result = attr.getName() }
override DataFlow::Node getValueNode() { result = DataFlow::valueNode(attr.getValue()) }
override ElementDefinition getElement() { result = attr.getElement() }
}
/**
* An HTML `<document>` element.
*/
class DocumentElementDefinition extends ElementDefinition {
DocumentElementDefinition() { this.getName() = "html" }
override string getName() { none() }
override AttributeDefinition getAttribute(int i) { none() }
override AttributeDefinition getAttributeByName(string name) { none() }
override ElementDefinition getParent() { none() }
}
/**
* Holds if the value of attribute `attr` is interpreted as a URL.
*/
predicate isUrlValuedAttribute(AttributeDefinition attr) {
exists(string eltName, string attrName |
eltName = attr.getElement().getName() and
attrName = attr.getName()
|
(
eltName = "script" or
eltName = "iframe" or
eltName = "embed" or
eltName = "video" or
eltName = "audio" or
eltName = "source" or
eltName = "track"
) and
attrName = "src"
or
(
eltName = "link" or
eltName = "a" or
eltName = "base" or
eltName = "area"
) and
attrName = "href"
or
eltName = "form" and
attrName = "action"
or
(eltName = "input" or eltName = "button") and
attrName = "formaction"
)
}
/**
* A data flow node or other program element that may refer to
* a DOM element.
*/
abstract class Element extends Locatable {
ElementDefinition defn;
/** Gets the definition of this element. */
ElementDefinition getDefinition() { result = defn }
/** Gets the tag name of this DOM element. */
string getName() { result = defn.getName() }
/** Gets the `i`th attribute of this DOM element, if it can be determined. */
AttributeDefinition getAttribute(int i) { result = defn.getAttribute(i) }
/** Gets an attribute of this DOM element with the given `name`. */
AttributeDefinition getAttributeByName(string name) { result = defn.getAttributeByName(name) }
}
/**
* The default implementation of `Element`, including both
* element definitions and data flow nodes that may refer to them.
*/
private class DefaultElement extends Element {
DefaultElement() {
defn = this
or
exists(Element that |
this.(Expr).flow().getALocalSource().asExpr() = that and
defn = that.getDefinition()
)
}
}
/**
* Holds if `attr` is an invalid id attribute because of `reason`.
*/
predicate isInvalidHtmlIdAttributeValue(DOM::AttributeDefinition attr, string reason) {
attr.getName() = "id" and
exists(string v | v = attr.getStringValue() |
v = "" and
reason = "must contain at least one character"
or
v.regexpMatch(".*\\s.*") and
reason = "must not contain any space characters"
)
}
/** Gets a call that queries the DOM for a collection of DOM nodes. */
private DataFlow::SourceNode domElementCollection() {
exists(string collectionName |
collectionName = "getElementsByClassName" or
collectionName = "getElementsByName" or
collectionName = "getElementsByTagName" or
collectionName = "getElementsByTagNameNS" or
collectionName = "querySelectorAll"
|
(
result = documentRef().getAMethodCall(collectionName) or
result = DataFlow::globalVarRef(collectionName).getACall()
)
)
}
/** Gets a call that creates a DOM node or queries the DOM for a DOM node. */
private DataFlow::SourceNode domElementCreationOrQuery() {
exists(string methodName |
methodName = "createElement" or
methodName = "createElementNS" or
methodName = "createRange" or
methodName = "getElementById" or
methodName = "querySelector"
|
result = documentRef().getAMethodCall(methodName) or
result = DataFlow::globalVarRef(methodName).getACall()
)
}
module DomValueSource {
/**
* A data flow node that should be considered a source of DOM values.
*/
abstract class Range extends DataFlow::Node { }
private string getADomPropertyName() {
exists(ExternalInstanceMemberDecl decl |
result = decl.getName() and
isDomRootType(decl.getDeclaringType().getASupertype*())
)
}
/**
* A data flow node that might refer to some form.
* Either by a read like `document.forms[0]`, or a property read from `document` with some constant property-name.
* E.g. if `<form name="foobar">..</form>` exists, then `document.foobar` refers to that form.
*/
private DataFlow::SourceNode forms() {
result = documentRef().getAPropertyRead("forms").getAPropertyRead()
or
exists(DataFlow::PropRead read |
read = documentRef().getAPropertyRead() and
result = read
|
read.mayHavePropertyName(_) and
not read.mayHavePropertyName(getADomPropertyName())
)
}
private InferredType getArgumentTypeFromJQueryMethodGet(JQuery::MethodCall call) {
call.getMethodName() = "get" and
result = call.getArgument(0).analyze().getAType()
}
private class DefaultRange extends Range {
DefaultRange() {
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
or
exists(DataFlow::PropRead read |
this = read and
read = domValueRef().getAPropertyRead()
|
not read.mayHavePropertyName(_)
or
read.mayHavePropertyName(getADomPropertyName())
or
read.mayHavePropertyName(any(string s | exists(s.toInt())))
)
or
this = domElementCreationOrQuery()
or
this = domElementCollection()
or
this = forms()
or
// reading property `foo` - where a child has `name="foo"` - resolves to that child.
// We only look for such properties on forms/document, to avoid potential false positives.
exists(DataFlow::SourceNode form | form = [forms(), documentRef()] |
this = form.getAPropertyRead(any(string s | not s = getADomPropertyName()))
)
or
exists(JQuery::MethodCall call | this = call and call.getMethodName() = "get" |
call.getNumArgument() = 1 and
unique(InferredType t | t = getArgumentTypeFromJQueryMethodGet(call)) = TTNumber()
)
or
// A `this` node from a callback given to a `$().each(callback)` call.
// purposely not using JQuery::MethodCall to avoid `jquery.each()`.
exists(DataFlow::CallNode eachCall | eachCall = JQuery::objectRef().getAMethodCall("each") |
this = DataFlow::thisNode(eachCall.getCallback(0).getFunction()) or
this = eachCall.getABoundCallbackParameter(0, 1)
)
or
// A receiver node of an event handler on a DOM node
exists(DataFlow::SourceNode domNode, DataFlow::FunctionNode eventHandler |
// NOTE: we do not use `getABoundFunctionValue()`, since bound functions tend to have
// a different receiver anyway
eventHandler = domNode.getAPropertySource(any(string n | n.matches("on%")))
or
eventHandler =
domNode.getAMethodCall("addEventListener").getArgument(1).getAFunctionValue()
|
domNode = domValueRef() and
this = eventHandler.getReceiver()
)
or
this = DataFlow::thisNode(any(EventHandlerCode evt))
}
}
}
/**
* Gets a reference to a DOM event.
*/
private DataFlow::SourceNode domEventSource() {
// e.g. <form onSubmit={e => e.target}/>
exists(JSXAttribute attr | attr.getName().matches("on%") |
result = attr.getValue().flow().getABoundFunctionValue(0).getParameter(0)
)
or
// node.addEventListener("submit", e => e.target)
result = domValueRef().getAMethodCall("addEventListener").getABoundCallbackParameter(1, 0)
or
// node.onSubmit = (e => e.target);
exists(DataFlow::PropWrite write | write = domValueRef().getAPropertyWrite() |
write.getPropertyName().matches("on%") and
result = write.getRhs().getAFunctionValue().getParameter(0)
)
}
/** Gets a data flow node that refers directly to a value from the DOM. */
DataFlow::SourceNode domValueSource() { result instanceof DomValueSource::Range }
/** Gets a data flow node that may refer to a value from the DOM. */
private DataFlow::SourceNode domValueRef(DataFlow::TypeTracker t) {
t.start() and
result = domValueSource()
or
t.start() and
result = domValueRef().getAMethodCall(["item", "namedItem"])
or
t.startInProp("target") and
result = domEventSource()
or
exists(DataFlow::TypeTracker t2 | result = domValueRef(t2).track(t2, t))
}
/** Gets a data flow node that may refer to a value from the DOM. */
DataFlow::SourceNode domValueRef() {
result = domValueRef(DataFlow::TypeTracker::end())
or
result.hasUnderlyingType("Element")
}
module LocationSource {
/**
* A data flow node that should be considered a source of the DOM `location` object.
*
* Can be subclassed to add additional such nodes.
*/
abstract class Range extends DataFlow::Node { }
private class DefaultRange extends Range {
DefaultRange() {
exists(string propName | this = documentRef().getAPropertyRead(propName) |
propName = "documentURI" or
propName = "documentURIObject" or
propName = "location" or
propName = "referrer" or
propName = "URL"
)
or
this = DOM::domValueRef().getAPropertyRead("baseUri")
or
this = DataFlow::globalVarRef("location")
or
this = any(DataFlow::Node n | n.hasUnderlyingType("Location")).getALocalSource() and
not this = nonFirstLocationType(DataFlow::TypeTracker::end()) // only start from the source, and not the locations we can type-track to.
}
}
}
/**
* Get a reference to a node of type `Location` that has gone through at least 1 type-tracking step.
*/
private DataFlow::SourceNode nonFirstLocationType(DataFlow::TypeTracker t) {
// One step inlined in the beginning.
exists(DataFlow::TypeTracker t2 |
result =
any(DataFlow::Node n | n.hasUnderlyingType("Location")).getALocalSource().track(t2, t)
)
or
exists(DataFlow::TypeTracker t2 | result = nonFirstLocationType(t2).track(t2, t))
}
/** Gets a data flow node that directly refers to a DOM `location` object. */
DataFlow::SourceNode locationSource() { result instanceof LocationSource::Range }
/** Gets a reference to a DOM `location` object. */
private DataFlow::SourceNode locationRef(DataFlow::TypeTracker t) {
t.start() and
result = locationSource()
or
t.startInProp("location") and
result = [DataFlow::globalObjectRef(), documentSource()]
or
exists(DataFlow::TypeTracker t2 | result = locationRef(t2).track(t2, t))
}
/** Gets a reference to a DOM `location` object. */
DataFlow::SourceNode locationRef() { result = locationRef(DataFlow::TypeTracker::end()) }
module DocumentSource {
/**
* A data flow node that should be considered a source of the `document` object.
*
* Can be subclassed to add additional such nodes.
*/
abstract class Range extends DataFlow::Node { }
private class DefaultRange extends Range {
DefaultRange() { this = DataFlow::globalVarRef("document") }
}
}
/**
* Gets a direct reference to the `document` object.
*/
DataFlow::SourceNode documentSource() { result instanceof DocumentSource::Range }
/**
* Gets a reference to the `document` object.
*/
private DataFlow::SourceNode documentRef(DataFlow::TypeTracker t) {
t.start() and
result instanceof DocumentSource::Range
or
exists(DataFlow::TypeTracker t2 | result = documentRef(t2).track(t2, t))
}
/**
* Gets a reference to the 'document' object.
*/
DataFlow::SourceNode documentRef() {
result = documentRef(DataFlow::TypeTracker::end())
or
result.hasUnderlyingType("Document")
}
/**
* Holds if a value assigned to property `name` of a DOM node can be interpreted as JavaScript via the `javascript:` protocol.
*/
string getAPropertyNameInterpretedAsJavaScriptUrl() {
result = ["action", "formaction", "href", "src", "data"]
}
}

View File

@@ -1,309 +0,0 @@
/** Provides classes and predicates for working with variable definitions and uses. */
import javascript
/**
* Holds if `def` is a CFG node that assigns the value of `rhs` to `lhs`.
*
* This predicate covers four kinds of definitions:
*
* <table border="1">
* <tr><th>Example<th><code>def</code><th><code>lhs</code><th><code>rhs</code></tr>
* <tr><td><code>x = y</code><td><code>x = y</code><td><code>x</code><td><code>y</code></tr>
* <tr><td><code>var a = b</code><td><code>var a = b</code><td><code>a</code><td><code>b</code></tr>
* <tr><td><code>function f { ... }</code><td><code>f</code><td><code>f</code><td><code>function f { ... }</code></tr>
* <tr><td><code>function f ( x = y ){ ... }</code><td><code>x</code><td><code>x</code><td><code>y</code></tr>
* <tr><td><code>class C { ... }</code><td><code>C</code><td><code>C</code><td><code>class C { ... }</code></tr>
* <tr><td><code>namespace N { ... }</code><td><code>N</code><td><code>N</code><td><code>namespace N { ... }</code></tr>
* <tr><td><code>enum E { ... }</code><td><code>E</code><td><code>E</code><td><code>enum E { ... }</code></tr>
* <tr><td><code>import x = y</code><td><code>x</code><td><code>x</code><td><code>y</code></tr>
* <tr><td><code>enum { x = y }</code><td><code>x</code><td><code>x</code><td><code>y</code></tr>
* </table>
*
* Note that `def` and `lhs` are not in general the same: the latter
* represents the point where `lhs` is evaluated to an assignable reference,
* the former the point where the value of `rhs` is actually assigned
* to that reference.
*/
private predicate defn(ControlFlowNode def, Expr lhs, AST::ValueNode rhs) {
exists(AssignExpr assgn | def = assgn | lhs = assgn.getTarget() and rhs = assgn.getRhs())
or
exists(VariableDeclarator vd | def = vd | lhs = vd.getBindingPattern() and rhs = vd.getInit())
or
exists(Function f | def = f.getIdentifier() | lhs = def and rhs = f)
or
exists(ClassDefinition c | lhs = c.getIdentifier() | def = c and rhs = c and not c.isAmbient())
or
exists(NamespaceDeclaration n | def = n | lhs = n.getIdentifier() and rhs = n)
or
exists(EnumDeclaration ed | def = ed.getIdentifier() | lhs = def and rhs = ed)
or
exists(ImportEqualsDeclaration i | def = i |
lhs = i.getIdentifier() and rhs = i.getImportedEntity()
)
or
exists(ImportSpecifier i | def = i | lhs = i.getLocal() and rhs = i)
or
exists(EnumMember member | def = member.getIdentifier() |
lhs = def and rhs = member.getInitializer()
)
}
/**
* Holds if `def` is a CFG node that assigns to `lhs`.
*
* This predicate extends the three-argument version of `defn` to also cover definitions
* where there is no explicit right hand side:
*
* <table border="1">
* <tr><th>Example<th><code>def</code><th><code>lhs</code></tr>
* <tr><td><code>x += y</code><td><code>x += y</code><td><code>x</code></tr>
* <tr><td><code>++z.q</code><td><code>++z.q</code><td><code>z.q</code></tr>
* <tr><td><code>import { a as b } from 'm'</code><td><code>a as b</code><td><code>b</code></tr>
* <tr><td><code>for (var p in o) ...</code><td><code>var p</code><td><code>p</code></tr>
* <tr><td><code>enum { x }</code><td><code>x</code><td><code>x</code></tr>
* </table>
*
* Additionally, parameters are also considered definitions, which are their own `lhs`.
*/
private predicate defn(ControlFlowNode def, Expr lhs) {
defn(def, lhs, _)
or
lhs = def.(CompoundAssignExpr).getTarget()
or
lhs = def.(UpdateExpr).getOperand().getUnderlyingReference()
or
exists(EnhancedForLoop efl | def = efl.getIteratorExpr() |
lhs = def.(Expr).stripParens() or
lhs = def.(VariableDeclarator).getBindingPattern()
)
or
lhs = def and
(
def instanceof Parameter or
def = any(ComprehensionBlock cb).getIterator()
)
or
exists(EnumMember member | def = member.getIdentifier() |
lhs = def and not exists(member.getInitializer())
)
}
/**
* Holds if `l` is one of the lvalues in the assignment `def`, or
* a destructuring pattern that contains some of the lvalues.
*
* For example, if `def` is `[{ x: y }] = e`, then `l` can be any
* of `y`, `{ x: y }` and `[{ x: y }]`.
*/
private predicate lvalAux(Expr l, ControlFlowNode def) {
defn(def, l)
or
exists(ArrayPattern ap | lvalAux(ap, def) | l = ap.getAnElement().stripParens())
or
exists(ObjectPattern op | lvalAux(op, def) |
l = op.getAPropertyPattern().getValuePattern().stripParens() or
l = op.getRest().stripParens()
)
}
/**
* An expression that can be evaluated to a reference, that is,
* a variable reference or a property access.
*/
class RefExpr extends Expr {
RefExpr() {
this instanceof VarRef or
this instanceof PropAccess
}
}
/**
* A variable reference or property access that is written to.
*
* For instance, in the assignment `x.p = x.q`, `x.p` is written to
* and `x.q` is not; in the expression `++i`, `i` is written to
* (and also read from).
*/
class LValue extends RefExpr {
LValue() { lvalAux(this, _) }
/** Gets the definition in which this lvalue occurs. */
ControlFlowNode getDefNode() { lvalAux(this, result) }
/** Gets the source of the assignment. */
AST::ValueNode getRhs() { defn(_, this, result) }
}
/**
* A variable reference or property access that is read from.
*
* For instance, in the assignment `x.p = x.q`, `x.q` is read from
* and `x.p` is not; in the expression `++i`, `i` is read from
* (and also written to).
*/
class RValue extends RefExpr {
RValue() {
not this instanceof LValue and not this instanceof VarDecl
or
// in `x++` and `x += 1`, `x` is both RValue and LValue
this = any(CompoundAssignExpr a).getTarget()
or
this = any(UpdateExpr u).getOperand().getUnderlyingReference()
or
this = any(NamespaceDeclaration decl).getIdentifier()
}
}
/**
* A ControlFlowNode that defines (that is, initializes or updates) variables or properties.
*
* The following program elements are definitions:
*
* - assignment expressions (`x = 42`)
* - update expressions (`++x`)
* - variable declarators with an initializer (`var x = 42`)
* - for-in and for-of statements (`for (x in o) { ... }`)
* - parameters of functions or catch clauses (`function (x) { ... }`)
* - named functions (`function x() { ... }`)
* - named classes (`class x { ... }`)
* - import specifiers (`import { x } from 'm'`)
*
* Note that due to destructuring, a single `VarDef` may define multiple
* variables and/or properties; for example, `{ x, y: z.p } = e` defines variable
* `x` as well as property `p` of `z`.
*/
class VarDef extends ControlFlowNode {
VarDef() { defn(this, _) }
/**
* Gets the target of this definition, which is either a simple variable
* reference, a destructuring pattern, or a property access.
*/
Expr getTarget() { defn(this, result) }
/** Gets a variable defined by this node, if any. */
Variable getAVariable() { result = getTarget().(BindingPattern).getAVariable() }
/**
* Gets the source of this definition, that is, the data flow node representing
* the value that this definition assigns to its target.
*
* This predicate is not defined for `VarDef`s where the source is implicit,
* such as `for-in` loops, parameters or destructuring assignments.
*/
AST::ValueNode getSource() {
exists(Expr target | not target instanceof DestructuringPattern and defn(this, target, result))
}
/**
* Gets the source that this definition destructs, that is, the
* right hand side of a destructuring assignment.
*/
AST::ValueNode getDestructuringSource() {
exists(Expr target | target instanceof DestructuringPattern and defn(this, target, result))
}
/**
* Holds if this definition of `v` is overwritten by another definition, that is,
* another definition of `v` is reachable from it in the CFG.
*/
predicate isOverwritten(Variable v) {
exists(BasicBlock bb, int i | bb.defAt(i, v, this) |
exists(int j | bb.defAt(j, v, _) and j > i)
or
bb.getASuccessor+().defAt(_, v, _)
)
}
}
/**
* A ControlFlowNode that uses (that is, reads from) a single variable.
*
* Some variable definitions are also uses, notably the operands of update expressions.
*/
class VarUse extends ControlFlowNode, @varref {
VarUse() { this instanceof RValue }
/** Gets the variable this use refers to. */
Variable getVariable() { result = this.(VarRef).getVariable() }
/**
* Gets a definition that may reach this use.
*
* For global variables, each definition is considered to reach each use.
*/
VarDef getADef() {
result = getSsaVariable().getDefinition().getAContributingVarDef() or
result.getAVariable() = getVariable().(GlobalVariable)
}
/**
* Gets the unique SSA variable this use refers to.
*
* This predicate is only defined for variables that can be SSA-converted.
*/
SsaVariable getSsaVariable() { result.getAUse() = this }
}
/**
* Holds if the definition of `v` in `def` reaches `use` along some control flow path
* without crossing another definition of `v`.
*/
predicate definitionReaches(Variable v, VarDef def, VarUse use) {
v = use.getVariable() and
exists(BasicBlock bb, int i, int next | next = nextDefAfter(bb, v, i, def) |
exists(int j | j in [i + 1 .. next - 1] | bb.useAt(j, v, use))
or
exists(BasicBlock succ | succ = bb.getASuccessor() |
succ.isLiveAtEntry(v, use) and
next = bb.length()
)
)
}
/**
* Holds if the definition of local variable `v` in `def` reaches `use` along some control flow path
* without crossing another definition of `v`.
*/
predicate localDefinitionReaches(LocalVariable v, VarDef def, VarUse use) {
exists(SsaExplicitDefinition ssa |
ssa.defines(def, v) and
ssa = getAPseudoDefinitionInput*(use.getSsaVariable().getDefinition())
)
}
/** Holds if `nd` is a pseudo-definition and the result is one of its inputs. */
private SsaDefinition getAPseudoDefinitionInput(SsaDefinition nd) {
result = nd.(SsaPseudoDefinition).getAnInput()
}
/**
* Holds if `d` is a definition of `v` at index `i` in `bb`, and the result is the next index
* in `bb` after `i` at which the same variable is defined, or `bb.length()` if there is none.
*/
private int nextDefAfter(BasicBlock bb, Variable v, int i, VarDef d) {
bb.defAt(i, v, d) and
result =
min(int jj |
(bb.defAt(jj, v, _) or jj = bb.length()) and
jj > i
)
}
/**
* Holds if the `later` definition of `v` could overwrite its `earlier` definition.
*
* This is the case if there is a path from `earlier` to `later` that does not cross
* another definition of `v`.
*/
predicate localDefinitionOverwrites(LocalVariable v, VarDef earlier, VarDef later) {
exists(BasicBlock bb, int i, int next | next = nextDefAfter(bb, v, i, earlier) |
bb.defAt(next, v, later)
or
exists(BasicBlock succ | succ = bb.getASuccessor() |
succ.localMayBeOverwritten(v, later) and
next = bb.length()
)
)
}

View File

@@ -1,399 +0,0 @@
/**
* Provides classes for working with defensive programming patterns.
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* A test in a defensive programming pattern.
*/
abstract class DefensiveExpressionTest extends DataFlow::ValueNode {
/** Gets the unique Boolean value that this test evaluates to, if any. */
abstract boolean getTheTestResult();
}
/**
* Provides classes for specific kinds of defensive programming patterns.
*/
module DefensiveExpressionTest {
/**
* A defensive truthiness check that may be worth keeping, even if it
* is strictly speaking useless.
*
* We currently recognize three patterns:
*
* - the first `x` in `x || (x = e)`
* - the second `x` in `x = (x || e)`
* - the second `x` in `var x = x || e`
*/
class DefensiveInit extends DefensiveExpressionTest {
DefensiveInit() {
exists(VarAccess va, LogOrExpr o, VarRef va2 |
va = astNode and
va = o.getLeftOperand().stripParens() and
va2.getVariable() = va.getVariable()
|
exists(AssignExpr assgn | va2 = assgn.getTarget() |
assgn = o.getRightOperand().stripParens() or
o = assgn.getRhs().stripParens()
)
or
exists(VariableDeclarator vd | va2 = vd.getBindingPattern() |
o = vd.getInit().stripParens()
)
)
}
override boolean getTheTestResult() { result = analyze().getTheBooleanValue() }
}
/**
* Gets the inner expression of `e`, with any surrounding parentheses and boolean nots removed.
* `polarity` is true iff the inner expression is nested in an even number of negations.
*/
private Expr stripNotsAndParens(Expr e, boolean polarity) {
exists(Expr inner | inner = e.stripParens() |
if inner instanceof LogNotExpr
then result = stripNotsAndParens(inner.(LogNotExpr).getOperand(), polarity.booleanNot())
else (
result = inner and polarity = true
)
)
}
/**
* An equality test for `null` and `undefined`.
*
* Examples: `e === undefined` or `typeof e !== undefined`.
*/
abstract private class UndefinedNullTest extends EqualityTest {
/** Gets the unique Boolean value that this test evaluates to, if any. */
abstract boolean getTheTestResult();
/**
* Gets the expression that is tested for being `null` or `undefined`.
*/
abstract Expr getOperand();
}
/**
* A dis- or conjunction that tests if an expression is `null` or `undefined` in either branch.
*
* Example: a branch in `x === null || x === undefined`.
*/
private class CompositeUndefinedNullTestPart extends DefensiveExpressionTest {
UndefinedNullTest test;
boolean polarity;
CompositeUndefinedNullTestPart() {
exists(
LogicalBinaryExpr composite, Variable v, Expr op, Expr opOther, UndefinedNullTest testOther
|
composite.hasOperands(op, opOther) and
this = op.flow() and
test = stripNotsAndParens(op, polarity) and
testOther = stripNotsAndParens(opOther, _) and
test.getOperand().(VarRef).getVariable() = v and
testOther.getOperand().(VarRef).getVariable() = v
)
}
override boolean getTheTestResult() {
polarity = true and result = test.getTheTestResult()
or
polarity = false and result = test.getTheTestResult().booleanNot()
}
}
/**
* A test for `undefined` or `null` in an if-statement.
*
* Example: `if (x === null) ...`.
*/
private class ConsistencyCheckingUndefinedNullGuard extends DefensiveExpressionTest {
UndefinedNullTest test;
boolean polarity;
ConsistencyCheckingUndefinedNullGuard() {
exists(IfStmt c |
this = c.getCondition().flow() and
test = stripNotsAndParens(c.getCondition(), polarity) and
test.getOperand() instanceof VarRef
)
}
override boolean getTheTestResult() {
polarity = true and result = test.getTheTestResult()
or
polarity = false and result = test.getTheTestResult().booleanNot()
}
}
/**
* Holds if `t` is `null` or `undefined`.
*/
private predicate isNullOrUndefined(InferredType t) {
t = TTNull() or
t = TTUndefined()
}
/**
* Holds if `t` is not `null` or `undefined`.
*/
private predicate isNotNullOrUndefined(InferredType t) { not isNullOrUndefined(t) }
/**
* A value comparison for `null` and `undefined`.
*
* Examples: `x === null` or `x != undefined`.
*/
private class NullUndefinedComparison extends UndefinedNullTest {
Expr operand;
InferredType op2type;
NullUndefinedComparison() {
exists(Expr op2 | hasOperands(operand, op2) |
op2type = TTNull() and SyntacticConstants::isNull(op2)
or
op2type = TTUndefined() and SyntacticConstants::isUndefined(op2)
)
}
override boolean getTheTestResult() {
result = getPolarity() and
(
if this instanceof StrictEqualityTest
then
// case: `operand === null` or `operand === undefined`
operand.analyze().getTheType() = op2type
else
// case: `operand == null` or `operand == undefined`
not isNotNullOrUndefined(operand.analyze().getAType())
)
or
result = getPolarity().booleanNot() and
(
if this instanceof StrictEqualityTest
then
// case: `operand !== null` or `operand !== undefined`
not operand.analyze().getAType() = op2type
else
// case: `operand != null` or `operand != undefined`
not isNullOrUndefined(operand.analyze().getAType())
)
}
override Expr getOperand() { result = operand }
}
/**
* Comparison against `undefined`, such as `x === undefined`.
*/
class UndefinedComparison extends NullUndefinedComparison {
UndefinedComparison() { op2type = TTUndefined() }
}
/**
* An expression that throws an exception if one of its subexpressions evaluates to `null` or `undefined`.
*
* Examples: `sub.p` or `sub()`.
*/
private class UndefinedNullCrashUse extends Expr {
Expr target;
UndefinedNullCrashUse() {
exists(Expr thrower | stripNotsAndParens(this, _) = thrower |
thrower.(InvokeExpr).getCallee().getUnderlyingValue() = target
or
thrower.(PropAccess).getBase().getUnderlyingValue() = target
or
thrower.(MethodCallExpr).getReceiver().getUnderlyingValue() = target
)
}
/**
* Gets the subexpression that will cause an exception to be thrown if it is `null` or `undefined`.
*/
Expr getVulnerableSubexpression() { result = target }
}
/**
* An expression that throws an exception if one of its subexpressions is not a `function`.
*
* Example: `sub()`.
*/
private class NonFunctionCallCrashUse extends Expr {
Expr target;
NonFunctionCallCrashUse() {
stripNotsAndParens(this, _).(InvokeExpr).getCallee().getUnderlyingValue() = target
}
/**
* Gets the subexpression that will cause an exception to be thrown if it is not a `function`.
*/
Expr getVulnerableSubexpression() { result = target }
}
/**
* Gets the first expression that is guarded by `guard`.
*/
private Expr getAGuardedExpr(Expr guard) {
exists(LogicalBinaryExpr op |
op.getLeftOperand() = guard and
op.getRightOperand() = result
)
or
exists(IfStmt c, ExprStmt guardedStmt |
c.getCondition() = guard and
result = guardedStmt.getExpr()
|
guardedStmt = c.getAControlledStmt() or
guardedStmt = c.getAControlledStmt().(BlockStmt).getStmt(0)
)
or
exists(ConditionalExpr c | c.getCondition() = guard | result = c.getABranch())
}
/**
* Holds if `t` is `string`, `number` or `boolean`.
*/
private predicate isStringOrNumOrBool(InferredType t) {
t = TTString() or
t = TTNumber() or
t = TTBoolean()
}
/**
* A defensive expression that tests for `undefined` and `null` using a truthiness test.
*
* Examples: The condition in `if(x) { x.p; }` or `!x || x.m()`.
*/
private class UndefinedNullTruthinessGuard extends DefensiveExpressionTest {
VarRef guardVar;
boolean polarity;
UndefinedNullTruthinessGuard() {
exists(VarRef useVar |
guardVar = stripNotsAndParens(this.asExpr(), polarity) and
guardVar.getVariable() = useVar.getVariable()
|
getAGuardedExpr(this.asExpr()).(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar and
// exclude types whose truthiness depend on the value
not isStringOrNumOrBool(guardVar.analyze().getAType())
)
}
override boolean getTheTestResult() {
exists(boolean testResult | testResult = guardVar.analyze().getTheBooleanValue() |
if polarity = true then result = testResult else result = testResult.booleanNot()
)
}
}
/**
* A defensive expression that tests for `undefined` and `null`.
*
* Example: the condition in `if(x !== null) { x.p; }`.
*/
private class UndefinedNullTypeGuard extends DefensiveExpressionTest {
UndefinedNullTest test;
boolean polarity;
UndefinedNullTypeGuard() {
exists(Expr guard, VarRef guardVar, VarRef useVar |
this = guard.flow() and
test = stripNotsAndParens(guard, polarity) and
test.getOperand() = guardVar and
guardVar.getVariable() = useVar.getVariable()
|
getAGuardedExpr(guard).(UndefinedNullCrashUse).getVulnerableSubexpression() = useVar
)
}
override boolean getTheTestResult() {
polarity = true and result = test.getTheTestResult()
or
polarity = false and result = test.getTheTestResult().booleanNot()
}
}
/**
* A test for the value of a `typeof` expression.
*
* Example: `typeof x === 'undefined'`.
*/
private class TypeofTest extends EqualityTest {
Expr operand;
TypeofTag tag;
TypeofTest() { TaintTracking::isTypeofGuard(this, operand, tag) }
boolean getTheTestResult() {
exists(boolean testResult |
testResult = true and operand.analyze().getTheType().getTypeofTag() = tag
or
testResult = false and not operand.analyze().getAType().getTypeofTag() = tag
|
if getPolarity() = true then result = testResult else result = testResult.booleanNot()
)
}
/**
* Gets the operand used in the `typeof` expression.
*/
Expr getOperand() { result = operand }
/**
* Gets the `typeof` tag that is tested.
*/
TypeofTag getTag() { result = tag }
}
/**
* A defensive expression that tests if an expression has type `function`.
*
* Example: the condition in `if(typeof x === 'function') x()`.
*/
private class FunctionTypeGuard extends DefensiveExpressionTest {
TypeofTest test;
boolean polarity;
FunctionTypeGuard() {
exists(Expr guard, VarRef guardVar, VarRef useVar |
this = guard.flow() and
test = stripNotsAndParens(guard, polarity) and
test.getOperand() = guardVar and
guardVar.getVariable() = useVar.getVariable()
|
getAGuardedExpr(guard).(NonFunctionCallCrashUse).getVulnerableSubexpression() = useVar
) and
test.getTag() = "function"
}
override boolean getTheTestResult() {
polarity = true and result = test.getTheTestResult()
or
polarity = false and result = test.getTheTestResult().booleanNot()
}
}
/**
* A test for `undefined` using a `typeof` expression.
*
* Example: `typeof x === "undefined"'.
*/
class TypeofUndefinedTest extends UndefinedNullTest {
TypeofTest test;
TypeofUndefinedTest() {
this = test and
test.getTag() = "undefined"
}
override boolean getTheTestResult() { result = test.getTheTestResult() }
override Expr getOperand() { result = test.getOperand() }
}
}

View File

@@ -1,222 +0,0 @@
/**
* Provides classes for working with dynamic property accesses.
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.dataflow.DataFlow::DataFlow
private import semmle.javascript.dataflow.internal.FlowSteps
/**
* Gets a node that refers to an element of `array`, likely obtained
* as a result of enumerating the elements of the array.
*/
SourceNode getAnEnumeratedArrayElement(SourceNode array) {
exists(MethodCallNode call, string name |
call = array.getAMethodCall(name) and
(name = "forEach" or name = "map") and
result = call.getCallback(0).getParameter(0)
)
or
exists(DataFlow::PropRead read |
read = array.getAPropertyRead() and
not exists(read.getPropertyName()) and
not read.getPropertyNameExpr().analyze().getAType() = TTString() and
result = read
)
}
/**
* A data flow node that refers to the name of a property obtained by enumerating
* the properties of some object.
*/
abstract class EnumeratedPropName extends DataFlow::Node {
/**
* Gets the data flow node holding the object whose properties are being enumerated.
*
* For example, gets `src` in `for (var key in src)`.
*/
abstract DataFlow::Node getSourceObject();
/**
* Gets a source node that refers to the object whose properties are being enumerated.
*/
DataFlow::SourceNode getASourceObjectRef() {
result = AccessPath::getAnAliasedSourceNode(getSourceObject())
}
/**
* Gets a property read that accesses the corresponding property value in the source object.
*
* For example, gets `src[key]` in `for (var key in src) { src[key]; }`.
*/
SourceNode getASourceProp() {
exists(Node base, Node key |
dynamicPropReadStep(base, key, result) and
getASourceObjectRef().flowsTo(base) and
key.getImmediatePredecessor*() = this
)
}
}
/**
* Property enumeration through `for-in` for `Object.keys` or similar.
*/
private class ForInEnumeratedPropName extends EnumeratedPropName {
DataFlow::Node object;
ForInEnumeratedPropName() {
exists(ForInStmt stmt |
this = DataFlow::lvalueNode(stmt.getLValue()) and
object = stmt.getIterationDomain().flow()
)
or
exists(CallNode call |
call = globalVarRef("Object").getAMemberCall("keys")
or
call = globalVarRef("Object").getAMemberCall("getOwnPropertyNames")
or
call = globalVarRef("Reflect").getAMemberCall("ownKeys")
|
object = call.getArgument(0) and
this = getAnEnumeratedArrayElement(call)
)
}
override Node getSourceObject() { result = object }
}
/**
* Property enumeration through `Object.entries`.
*/
private class EntriesEnumeratedPropName extends EnumeratedPropName {
CallNode entries;
SourceNode entry;
EntriesEnumeratedPropName() {
entries = globalVarRef("Object").getAMemberCall("entries") and
entry = getAnEnumeratedArrayElement(entries) and
this = entry.getAPropertyRead("0")
}
override DataFlow::Node getSourceObject() { result = entries.getArgument(0) }
override SourceNode getASourceProp() {
result = super.getASourceProp()
or
result = entry.getAPropertyRead("1")
}
}
/**
* Gets a function that enumerates object properties when invoked.
*
* Invocations takes the following form:
* ```js
* fn(obj, (value, key, o) => { ... })
* ```
*/
private SourceNode propertyEnumerator() {
result = moduleImport("for-own") or
result = moduleImport("for-in") or
result = moduleMember("ramda", "forEachObjIndexed") or
result = LodashUnderscore::member("forEach") or
result = LodashUnderscore::member("each")
}
/**
* Property enumeration through a library function taking a callback.
*/
private class LibraryCallbackEnumeratedPropName extends EnumeratedPropName {
CallNode call;
FunctionNode callback;
LibraryCallbackEnumeratedPropName() {
call = propertyEnumerator().getACall() and
callback = call.getCallback(1) and
this = callback.getParameter(1)
}
override Node getSourceObject() { result = call.getArgument(0) }
override SourceNode getASourceObjectRef() {
result = super.getASourceObjectRef()
or
result = callback.getParameter(2)
}
override SourceNode getASourceProp() {
result = super.getASourceProp()
or
result = callback.getParameter(0)
}
}
/**
* A dynamic property access that is not obviously an array access.
*/
class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode {
// Use IndexExpr instead of PropRead as we're not interested in implicit accesses like
// rest-patterns and for-of loops.
override IndexExpr astNode;
DynamicPropRead() {
not exists(astNode.getPropertyName()) and
// Exclude obvious array access
astNode.getPropertyNameExpr().analyze().getAType() = TTString()
}
/** Gets the base of the dynamic read. */
DataFlow::Node getBase() { result = astNode.getBase().flow() }
/** Gets the node holding the name of the property. */
DataFlow::Node getPropertyNameNode() { result = astNode.getIndex().flow() }
/**
* Holds if the value of this read was assigned to earlier in the same basic block.
*
* For example, this is true for `dst[x]` on line 2 below:
* ```js
* dst[x] = {};
* dst[x][y] = src[y];
* ```
*/
predicate hasDominatingAssignment() {
exists(DataFlow::PropWrite write, BasicBlock bb, int i, int j, SsaVariable ssaVar |
write = getBase().getALocalSource().getAPropertyWrite() and
bb.getNode(i) = write.getWriteNode() and
bb.getNode(j) = astNode and
i < j and
write.getPropertyNameExpr() = ssaVar.getAUse() and
astNode.getIndex() = ssaVar.getAUse()
)
}
}
/**
* Holds if `output` is the result of `base[key]`, either directly or through
* one or more function calls, ignoring reads that can't access the prototype chain.
*/
predicate dynamicPropReadStep(Node base, Node key, SourceNode output) {
exists(DynamicPropRead read |
not read.hasDominatingAssignment() and
base = read.getBase() and
key = read.getPropertyNameNode() and
output = read
)
or
// Summarize functions returning a dynamic property read of two parameters, such as `function getProp(obj, prop) { return obj[prop]; }`.
exists(
CallNode call, Function callee, ParameterNode baseParam, ParameterNode keyParam, Node innerBase,
Node innerKey, SourceNode innerOutput
|
dynamicPropReadStep(innerBase, innerKey, innerOutput) and
baseParam.flowsTo(innerBase) and
keyParam.flowsTo(innerKey) and
innerOutput.flowsTo(callee.getAReturnedExpr().flow()) and
call.getACallee() = callee and
argumentPassingStep(call, base, callee, baseParam) and
argumentPassingStep(call, key, callee, keyParam) and
output = call
)
}

View File

@@ -1,135 +0,0 @@
/**
* Provides classes for working with E4X.
*/
import javascript
module E4X {
/**
* An E4X wildcard pseudo-identifier.
*
* Example:
*
* ```
* *
* ```
*/
class XMLAnyName extends Expr, @e4x_xml_anyname { }
/**
* An E4X qualified identifier.
*
* Examples:
*
* ```
* soap::encodingStyle
* soap::["encodingStyle"]
* ```
*
* Note that qualified identifiers are not currently supported by the parser, so snapshots
* will not usually contain any.
*/
class XMLQualifiedIdentifier extends Expr, @e4x_xml_qualident {
/**
* Gets the left operand of this qualified identifier, which is either
* an identifier or a wildcard.
*/
Expr getLeft() { result = getChildExpr(0) }
/**
* Gets the right operand of this qualified identifer, which is either
* an identifier, or an arbitrary expression for computed qualified
* identifiers.
*/
Expr getRight() { result = getChildExpr(1) }
/**
* Holds if this is a qualified identifier with a computed name, as in
* `q::[expr]`.
*/
predicate isComputed() { this instanceof @e4x_xml_dynamic_qualident }
override ControlFlowNode getFirstControlFlowNode() {
result = getLeft().getFirstControlFlowNode()
}
}
/**
* An E4X attribute selector.
*
* Examples:
*
* ```
* @border
* @[p]
* ```
*/
class XMLAttributeSelector extends Expr, @e4x_xml_attribute_selector {
/**
* Gets the selected attribute, which is either a static name (that is, a
* wildcard identifier or a possibly qualified name), or an arbitrary
* expression for computed attribute selectors.
*/
Expr getAttribute() { result = getChildExpr(0) }
/**
* Holds if this is an attribute selector with a computed name, as in
* `@[expr]`.
*/
predicate isComputed() { this instanceof @e4x_xml_dynamic_attribute_selector }
override ControlFlowNode getFirstControlFlowNode() {
result = getAttribute().getFirstControlFlowNode()
}
}
/**
* An E4X filter expression.
*
* Example:
*
* ```
* employees.(@id == 0 || @id == 1)
* ```
*/
class XMLFilterExpression extends Expr, @e4x_xml_filter_expression {
/**
* Gets the left operand of this filter expression.
*/
Expr getLeft() { result = getChildExpr(0) }
/**
* Gets the right operand of this filter expression.
*/
Expr getRight() { result = getChildExpr(1) }
override ControlFlowNode getFirstControlFlowNode() {
result = getLeft().getFirstControlFlowNode()
}
}
/**
* An E4X "dot-dot" expression.
*
* Example:
*
* ```
* e..name
* ```
*/
class XMLDotDotExpression extends Expr, @e4x_xml_dotdotexpr {
/**
* Gets the base expression of this dot-dot expression.
*/
Expr getBase() { result = getChildExpr(0) }
/**
* Gets the index expression of this dot-dot expression.
*/
Expr getIndex() { result = getChildExpr(1) }
override ControlFlowNode getFirstControlFlowNode() {
result = getBase().getFirstControlFlowNode()
}
}
}

View File

@@ -1,729 +0,0 @@
/** Provides classes for working with ECMAScript 2015 modules. */
import javascript
private import semmle.javascript.internal.CachedStages
/**
* An ECMAScript 2015 module.
*
* Example:
*
* ```
* import console from 'console';
*
* console.log("Hello, world!");
* ```
*/
class ES2015Module extends Module {
ES2015Module() { is_es2015_module(this) }
override ModuleScope getScope() { result.getScopeElement() = this }
/** Gets the full path of the file containing this module. */
override string getPath() { result = getFile().getAbsolutePath() }
/** Gets the short name of this module without file extension. */
override string getName() { result = getFile().getStem() }
/** Gets an export declaration in this module. */
ExportDeclaration getAnExport() { result.getTopLevel() = this }
override DataFlow::Node getAnExportedValue(string name) {
exists(ExportDeclaration ed | ed = getAnExport() and result = ed.getSourceNode(name))
}
/** Holds if this module exports variable `v` under the name `name`. */
predicate exportsAs(LexicalName v, string name) { getAnExport().exportsAs(v, name) }
override predicate isStrict() {
// modules are implicitly strict
any()
}
}
/**
* Holds if `mod` contains one or more named export declarations other than `default`.
*/
private predicate hasNamedExports(ES2015Module mod) {
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() != "default"
or
exists(mod.getAnExport().(ExportNamedDeclaration).getAnExportedDecl())
or
// Bulk re-exports only export named bindings (not "default")
mod.getAnExport() instanceof BulkReExportDeclaration
}
/**
* Holds if this module contains a `default` export.
*/
private predicate hasDefaultExport(ES2015Module mod) {
// export default foo;
mod.getAnExport() instanceof ExportDefaultDeclaration
or
// export { foo as default };
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() = "default"
}
/**
* Holds if `mod` contains both named and `default` exports.
*
* This is used to determine whether a default-import of the module should be reinterpreted
* as a namespace-import, to accomodate the non-standard behavior implemented by some compilers.
*/
private predicate hasBothNamedAndDefaultExports(ES2015Module mod) {
hasNamedExports(mod) and
hasDefaultExport(mod)
}
/**
* An import declaration.
*
* Examples:
*
* ```
* import console, { log, error as fatal } from 'console';
* import * as console from 'console';
* ```
*/
class ImportDeclaration extends Stmt, Import, @import_declaration {
override ES2015Module getEnclosingModule() { result = getTopLevel() }
override PathExpr getImportedPath() { result = getChildExpr(-1) }
/** Gets the `i`th import specifier of this import declaration. */
ImportSpecifier getSpecifier(int i) { result = getChildExpr(i) }
/** Gets an import specifier of this import declaration. */
ImportSpecifier getASpecifier() { result = getSpecifier(_) }
override DataFlow::Node getImportedModuleNode() {
// `import * as http from 'http'` or `import http from `http`'
exists(ImportSpecifier is |
is = getASpecifier() and
result = DataFlow::valueNode(is)
|
is instanceof ImportNamespaceSpecifier and
count(getASpecifier()) = 1
or
// For compatibility with the non-standard implementation of default imports,
// treat default imports as namespace imports in cases where it can't cause ambiguity
// between named exports and the properties of a default-exported object.
not hasBothNamedAndDefaultExports(getImportedModule()) and
is.getImportedName() = "default"
)
or
// `import { createServer } from 'http'`
result = DataFlow::destructuredModuleImportNode(this)
}
/** Holds if this is declared with the `type` keyword, so it only imports types. */
predicate isTypeOnly() { has_type_keyword(this) }
override string getAPrimaryQlClass() { result = "ImportDeclaration" }
}
/** A literal path expression appearing in an `import` declaration. */
private class LiteralImportPath extends PathExpr, ConstantString {
LiteralImportPath() { exists(ImportDeclaration req | this = req.getChildExpr(-1)) }
override string getValue() { result = getStringValue() }
}
/**
* An import specifier in an import declaration.
*
* Examples:
*
* ```
* import
* console, // default import specifier
* {
* log, // named import specifier
* error as fatal // renaming import specifier
* } from 'console';
*
* import
* * as console // namespace import specifier
* from 'console';
* ```
*/
class ImportSpecifier extends Expr, @import_specifier {
/** Gets the imported symbol; undefined for default and namespace import specifiers. */
Identifier getImported() { result = getChildExpr(0) }
/**
* Gets the name of the imported symbol.
*
* For example, consider these four imports:
*
* ```javascript
* import { x } from 'a'
* import { y as z } from 'b'
* import f from 'c'
* import * as g from 'd'
* ```
*
* The names of the imported symbols for the first three of them are, respectively,
* `x`, `y` and `default`, while the last one does not import an individual symbol.
*/
string getImportedName() { result = getImported().getName() }
/** Gets the local variable into which this specifier imports. */
VarDecl getLocal() { result = getChildExpr(1) }
override string getAPrimaryQlClass() { result = "ImportSpecifier" }
}
/**
* A named import specifier.
*
* Examples:
*
* ```
* import
* {
* log, // named import specifier
* error as fatal // renaming import specifier
* } from 'console';
* ```
*/
class NamedImportSpecifier extends ImportSpecifier, @named_import_specifier { }
/**
* A default import specifier.
*
* Example:
*
* ```
* import
* console // default import specifier
* from 'console';
* ```
*/
class ImportDefaultSpecifier extends ImportSpecifier, @import_default_specifier {
override string getImportedName() { result = "default" }
}
/**
* A namespace import specifier.
*
* Example:
*
* ```
* import
* * as console // namespace import specifier
* from 'console';
* ```
*/
class ImportNamespaceSpecifier extends ImportSpecifier, @import_namespace_specifier { }
/**
* A bulk import that imports an entire module as a namespace.
*
* Example:
*
* ```
* import * as console from 'console';
* ```
*/
class BulkImportDeclaration extends ImportDeclaration {
BulkImportDeclaration() { getASpecifier() instanceof ImportNamespaceSpecifier }
/** Gets the local namespace variable under which the module is imported. */
VarDecl getLocal() { result = getASpecifier().getLocal() }
}
/**
* A selective import that imports zero or more declarations.
*
* Example:
*
* ```
* import console, { log } from 'console';
* ```
*/
class SelectiveImportDeclaration extends ImportDeclaration {
SelectiveImportDeclaration() { not this instanceof BulkImportDeclaration }
/** Holds if `local` is the local variable into which `imported` is imported. */
predicate importsAs(string imported, LexicalDecl local) {
exists(ImportSpecifier spec | spec = getASpecifier() |
imported = spec.getImported().getName() and
local = spec.getLocal()
)
or
imported = "default" and local = getASpecifier().(ImportDefaultSpecifier).getLocal()
}
}
/**
* An export declaration.
*
* Examples:
*
* ```
* export * from 'a'; // bulk re-export declaration
*
* export default function f() {}; // default export declaration
* export default 42; // default export declaration
*
* export { x, y as z }; // named export declaration
* export var x = 42; // named export declaration
* export { x } from 'a'; // named re-export declaration
* export x from 'a'; // default re-export declaration
* ```
*/
abstract class ExportDeclaration extends Stmt, @export_declaration {
/** Gets the module to which this export declaration belongs. */
ES2015Module getEnclosingModule() { this = result.getAnExport() }
/** Holds if this export declaration exports variable `v` under the name `name`. */
abstract predicate exportsAs(LexicalName v, string name);
/**
* Gets the data flow node corresponding to the value this declaration exports
* under the name `name`.
*
* For example, consider the following exports:
*
* ```javascript
* export var x = 23;
* export { y as z };
* export default function f() { ... };
* export { x } from 'a';
* ```
*
* The first one exports `23` under the name `x`, the second one exports
* `y` under the name `z`, while the third one exports `function f() { ... }`
* under the name `default`.
*
* The final export re-exports under the name `x` whatever module `a`
* exports under the same name. In particular, its source node belongs
* to module `a` or possibly to some other module from which `a` re-exports.
*/
abstract DataFlow::Node getSourceNode(string name);
/** Holds if is declared with the `type` keyword, so only types are exported. */
predicate isTypeOnly() { has_type_keyword(this) }
override string getAPrimaryQlClass() { result = "ExportDeclaration" }
}
/**
* A bulk re-export declaration of the form `export * from 'a'`, which re-exports
* all exports of another module.
*
* Examples:
*
* ```
* export * from 'a'; // bulk re-export declaration
* ```
*/
class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declaration {
/** Gets the name of the module from which this declaration re-exports. */
override ConstantString getImportedPath() { result = getChildExpr(0) }
override predicate exportsAs(LexicalName v, string name) {
getReExportedES2015Module().exportsAs(v, name) and
not isShadowedFromBulkExport(this, name)
}
override DataFlow::Node getSourceNode(string name) {
result = getReExportedES2015Module().getAnExport().getSourceNode(name)
}
}
/**
* Holds if the given bulk export 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.
* For instance, directly exporting an interface `X` will block a variable `X` from being re-exported:
* ```
* export interface X {}
* export * from 'lib' // will not re-export X
* ```
* At runtime, the interface `X` will have been removed, so `X` is actually re-exported anyway,
* but we ignore this subtlety.
*/
private predicate isShadowedFromBulkExport(BulkReExportDeclaration reExport, string name) {
exists(ExportNamedDeclaration other | other.getTopLevel() = reExport.getEnclosingModule() |
other.getAnExportedDecl().getName() = name
or
other.getASpecifier().getExportedName() = name
)
}
/**
* A default export declaration.
*
* Examples:
*
* ```
* export default function f() {};
* export default 42;
* ```
*/
class ExportDefaultDeclaration extends ExportDeclaration, @export_default_declaration {
/** Gets the operand statement or expression that is exported by this declaration. */
ExprOrStmt getOperand() { result = getChild(0) }
override predicate exportsAs(LexicalName v, string name) {
name = "default" and v = getADecl().getVariable()
}
/** Gets the declaration, if any, exported by this default export. */
VarDecl getADecl() {
exists(ExprOrStmt op | op = getOperand() |
result = op.(FunctionDeclStmt).getIdentifier() or
result = op.(ClassDeclStmt).getIdentifier()
)
}
override DataFlow::Node getSourceNode(string name) {
name = "default" and result = DataFlow::valueNode(getOperand())
}
}
/**
* A named export declaration.
* *
* Examples:
*
* ```
* export { x, y as z };
* export var x = 42;
* export { x } from 'a';
* ```
*/
class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaration {
/** Gets the operand statement or expression that is exported by this declaration. */
ExprOrStmt getOperand() { result = getChild(-1) }
/**
* Gets an identifier, if any, exported as part of a declaration by this named export.
*
* Does not include names of export specifiers.
* That is, it includes the `v` in `export var v` but not in `export {v}`.
*/
Identifier getAnExportedDecl() {
exists(ExprOrStmt op | op = getOperand() |
result = op.(DeclStmt).getADecl().getBindingPattern().getABindingVarRef() or
result = op.(FunctionDeclStmt).getIdentifier() or
result = op.(ClassDeclStmt).getIdentifier() or
result = op.(NamespaceDeclaration).getIdentifier() or
result = op.(EnumDeclaration).getIdentifier() or
result = op.(InterfaceDeclaration).getIdentifier() or
result = op.(TypeAliasDeclaration).getIdentifier() or
result = op.(ImportEqualsDeclaration).getIdentifier()
)
}
/** Gets the variable declaration, if any, exported by this named export. */
VarDecl getADecl() { result = getAnExportedDecl() }
override predicate exportsAs(LexicalName v, string name) {
exists(LexicalDecl vd | vd = getAnExportedDecl() |
name = vd.getName() and v = vd.getALexicalName()
)
or
exists(ExportSpecifier spec | spec = getASpecifier() and name = spec.getExportedName() |
v = spec.getLocal().(LexicalAccess).getALexicalName()
or
this.(ReExportDeclaration).getReExportedES2015Module().exportsAs(v, spec.getLocalName())
)
}
override DataFlow::Node getSourceNode(string name) {
exists(VarDef d | d.getTarget() = getADecl() |
name = d.getTarget().(VarDecl).getName() and
result = DataFlow::valueNode(d.getSource())
)
or
exists(ObjectPattern obj | obj = getOperand().(DeclStmt).getADecl().getBindingPattern() |
exists(DataFlow::PropRead read | read = result |
read.getBase() = obj.flow() and
name = read.getPropertyName()
)
)
or
exists(ExportSpecifier spec | spec = getASpecifier() and name = spec.getExportedName() |
not exists(getImportedPath()) and result = DataFlow::valueNode(spec.getLocal())
or
exists(ReExportDeclaration red | red = this |
result = red.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName())
)
)
}
/** Gets the module from which the exports are taken if this is a re-export. */
ConstantString getImportedPath() { result = getChildExpr(-2) }
/** Gets the `i`th export specifier of this declaration. */
ExportSpecifier getSpecifier(int i) { result = getChildExpr(i) }
/** Gets an export specifier of this declaration. */
ExportSpecifier getASpecifier() { result = getSpecifier(_) }
}
/**
* An export declaration with the `type` modifier.
*/
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration {
TypeOnlyExportDeclaration() { isTypeOnly() }
override predicate exportsAs(LexicalName v, string name) {
super.exportsAs(v, name) and
not v instanceof Variable
}
}
/**
* An export specifier in an export declaration.
*
* Examples:
*
* ```
* export
* * // namespace export specifier
* from 'a';
*
* export
* default // default export specifier
* var x = 42;
*
* export {
* x, // named export specifier
* y as z // named export specifier
* };
*
* export
* x // default re-export specifier
* from 'a';
* ```
*/
class ExportSpecifier extends Expr, @exportspecifier {
/** Gets the declaration to which this specifier belongs. */
ExportDeclaration getExportDeclaration() { result = getParent() }
/** Gets the local symbol that is being exported. */
Identifier getLocal() { result = getChildExpr(0) }
/** Gets the name under which the symbol is exported. */
Identifier getExported() { result = getChildExpr(1) }
/**
* Gets the local name of the exported symbol, that is, the name
* of the exported local variable, or the imported name in a
* re-export.
*
* For example, consider these six exports:
*
* ```javascript
* export { x }
* export { y as z }
* export function f() {}
* export default 42
* export * from 'd'
* export default from 'm'
* ```
*
* The local names for the first three of them are, respectively,
* `x`, `y` and `f`; the fourth one exports an un-named value, and
* hence has no local name; the fifth one does not export a unique
* name, and hence also does not have a local name.
*
* The sixth one (unlike the fourth one) _does_ have a local name
* (that is, `default`), since it is a re-export.
*/
string getLocalName() { result = getLocal().getName() }
/**
* Gets the name under which the symbol is exported.
*
* For example, consider these five exports:
*
* ```javascript
* export { x }
* export { y as z }
* export function f() {}
* export default 42
* export * from 'd'
* ```
*
* The exported names for the first four of them are, respectively,
* `x`, `z`, `f` and `default`, while the last one does not have
* an exported name since it does not export a unique symbol.
*/
string getExportedName() { result = getExported().getName() }
override string getAPrimaryQlClass() { result = "ExportSpecifier" }
}
/**
* A named export specifier.
*
* Examples:
*
* ```
* export {
* x, // named export specifier
* y as z // named export specifier
* };
* ```
*/
class NamedExportSpecifier extends ExportSpecifier, @named_export_specifier { }
/**
* A default export specifier.
*
* Examples:
*
* ```
* export
* default // default export specifier
* 42;
* export
* x // default re-export specifier
* from 'a';
* ```
*/
class ExportDefaultSpecifier extends ExportSpecifier, @export_default_specifier {
override string getExportedName() { result = "default" }
}
/**
* A default export specifier in a re-export declaration.
*
* Example:
*
* ```
* export
* x // default re-export specifier
* from 'a';
* ```
*/
class ReExportDefaultSpecifier extends ExportDefaultSpecifier {
ReExportDefaultSpecifier() { getExportDeclaration() instanceof ReExportDeclaration }
override string getLocalName() { result = "default" }
override string getExportedName() { result = getExported().getName() }
}
/**
* A namespace export specifier, that is `*` or `* as x` occuring in an export declaration.
*
* Examples:
*
* ```
* export
* * // namespace export specifier
* from 'a';
*
* export
* * as x // namespace export specifier
* from 'a';
* ```
*/
class ExportNamespaceSpecifier extends ExportSpecifier, @export_namespace_specifier { }
/**
* An export declaration that re-exports declarations from another module.
*
* Examples:
*
* ```
* export * from 'a'; // bulk re-export declaration
* export * as x from 'a'; // namespace re-export declaration
* export { x } from 'a'; // named re-export declaration
* export x from 'a'; // default re-export declaration
* ```
*/
abstract class ReExportDeclaration extends ExportDeclaration {
/** Gets the path of the module from which this declaration re-exports. */
abstract ConstantString getImportedPath();
/**
* DEPRECATED. Use `getReExportedES2015Module()` instead.
*
* Gets the module from which this declaration re-exports.
*/
deprecated ES2015Module getImportedModule() { result = getReExportedModule() }
/** Gets the module from which this declaration re-exports, if it is an ES2015 module. */
ES2015Module getReExportedES2015Module() { result = getReExportedModule() }
/** Gets the module from which this declaration re-exports. */
cached
Module getReExportedModule() {
Stages::Imports::ref() and
result.getFile() = getEnclosingModule().resolve(getImportedPath().(PathExpr))
or
result = resolveFromTypeRoot()
}
/**
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
*/
private Module resolveFromTypeRoot() {
result.getFile() =
min(TypeRootFolder typeRoot |
|
typeRoot.getModuleFile(getImportedPath().getStringValue())
order by
typeRoot.getSearchPriority(getFile().getParentContainer())
)
}
}
/** A literal path expression appearing in a re-export declaration. */
private class LiteralReExportPath extends PathExpr, ConstantString {
LiteralReExportPath() { exists(ReExportDeclaration bred | this = bred.getImportedPath()) }
override string getValue() { result = getStringValue() }
}
/**
* A named export declaration that re-exports symbols imported from another module.
*
* Example:
*
* ```
* export { x } from 'a';
* ```
*/
class SelectiveReExportDeclaration extends ReExportDeclaration, ExportNamedDeclaration {
SelectiveReExportDeclaration() { exists(ExportNamedDeclaration.super.getImportedPath()) }
/** Gets the path of the module from which this declaration re-exports. */
override ConstantString getImportedPath() {
result = ExportNamedDeclaration.super.getImportedPath()
}
}
/**
* An export declaration that exports zero or more declarations from the module it appears in.
*
* Examples:
*
* ```
* export default function f() {};
* export default 42;
* export { x, y as z };
* export var x = 42;
* ```
*/
class OriginalExportDeclaration extends ExportDeclaration {
OriginalExportDeclaration() { not this instanceof ReExportDeclaration }
override predicate exportsAs(LexicalName v, string name) {
this.(ExportDefaultDeclaration).exportsAs(v, name) or
this.(ExportNamedDeclaration).exportsAs(v, name)
}
override DataFlow::Node getSourceNode(string name) {
result = this.(ExportDefaultDeclaration).getSourceNode(name) or
result = this.(ExportNamedDeclaration).getSourceNode(name)
}
}

View File

@@ -1,59 +0,0 @@
import javascript
/**
* An operation that sends an email.
*/
abstract class EmailSender extends DataFlow::SourceNode {
/**
* Gets a data flow node holding the plaintext version of the email body.
*/
abstract DataFlow::Node getPlainTextBody();
/**
* Gets a data flow node holding the HTML body of the email.
*/
abstract DataFlow::Node getHtmlBody();
/**
* Gets a data flow node holding the address of the email recipient(s).
*/
abstract DataFlow::Node getTo();
/**
* Gets a data flow node holding the address of the email sender.
*/
abstract DataFlow::Node getFrom();
/**
* Gets a data flow node holding the email subject.
*/
abstract DataFlow::Node getSubject();
/**
* Gets a data flow node that refers to the HTML body or plaintext body of the email.
*/
DataFlow::Node getABody() {
result = getPlainTextBody() or
result = getHtmlBody()
}
}
/**
* An email-sending call based on the `nodemailer` package.
*/
private class NodemailerEmailSender extends EmailSender, DataFlow::MethodCallNode {
NodemailerEmailSender() {
this =
DataFlow::moduleMember("nodemailer", "createTransport").getACall().getAMethodCall("sendMail")
}
override DataFlow::Node getPlainTextBody() { result = getOptionArgument(0, "text") }
override DataFlow::Node getHtmlBody() { result = getOptionArgument(0, "html") }
override DataFlow::Node getTo() { result = getOptionArgument(0, "to") }
override DataFlow::Node getFrom() { result = getOptionArgument(0, "from") }
override DataFlow::Node getSubject() { result = getOptionArgument(0, "subject") }
}

View File

@@ -1,29 +0,0 @@
/** Provides classes for working with syntax errors. */
import javascript
/** An error encountered during extraction. */
abstract class Error extends Locatable {
override Location getLocation() { hasLocation(this, result) }
/** Gets the message associated with this error. */
abstract string getMessage();
override string toString() { result = getMessage() }
/** Holds if this error prevented the file from being extracted. */
predicate isFatal() { any() }
}
/** A JavaScript parse error encountered during extraction. */
class JSParseError extends @js_parse_error, Error {
/** Gets the toplevel element this error occurs in. */
TopLevel getTopLevel() { js_parse_errors(this, result, _, _) }
override string getMessage() { js_parse_errors(this, _, result, _) }
/** Gets the source text of the line this error occurs on. */
string getLine() { js_parse_errors(this, _, _, result) }
override predicate isFatal() { not getTopLevel() instanceof Angular2::TemplateTopLevel }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,215 +0,0 @@
/**
* Provides classes for reasoning about `extend`-like functions.
*/
import javascript
/**
* A call to an `extend`-like function, which copies properties from
* one or more objects into another object, and returns the result.
*/
abstract class ExtendCall extends DataFlow::CallNode {
/**
* Gets an object from which properties are taken.
*/
abstract DataFlow::Node getASourceOperand();
/**
* Gets the object into which properties are stored, if any.
*/
abstract DataFlow::Node getDestinationOperand();
/**
* Holds if the copying operation recursively copies nested objects
* into the destination.
*/
abstract predicate isDeep();
/**
* Gets an object from which properties are taken or stored.
*/
DataFlow::Node getAnOperand() {
result = getASourceOperand() or
result = getDestinationOperand()
}
}
/** A version of `JQuery::dollarSource()` with fewer dependencies. */
private DataFlow::SourceNode localDollar() {
result.accessesGlobal(["$", "jQuery"])
or
result = DataFlow::moduleImport("jquery")
}
/**
* An extend call of form `extend(true/false, dst, src1, src2, ...)`, where the true/false
* argument is possibly omitted.
*/
private class ExtendCallWithFlag extends ExtendCall {
ExtendCallWithFlag() {
exists(string name | this = DataFlow::moduleImport(name).getACall() |
name = "extend" or
name = "extend2" or
name = "just-extend" or
name = "node.extend"
)
or
this = localDollar().getAMemberCall("extend")
}
/**
* Holds if the first argument appears to be a boolean flag.
*/
predicate hasFlag() { getArgument(0).mayHaveBooleanValue(_) }
/**
* Gets the `n`th argument after the optional boolean flag.
*/
DataFlow::Node getTranslatedArgument(int n) {
if hasFlag() then result = getArgument(n + 1) else result = getArgument(n)
}
override DataFlow::Node getASourceOperand() {
exists(int n | n >= 1 | result = getTranslatedArgument(n))
}
override DataFlow::Node getDestinationOperand() { result = getTranslatedArgument(0) }
override predicate isDeep() { getArgument(0).mayHaveBooleanValue(true) }
}
/**
* A deep extend call of form `extend(dst, src1, src2, ...)`.
*/
private class ExtendCallDeep extends ExtendCall {
ExtendCallDeep() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::moduleMember("deep", "extend") or
callee = DataFlow::moduleImport("deep-assign") or
callee = DataFlow::moduleImport("deep-extend") or
callee = DataFlow::moduleImport("deep-merge").getACall() or
callee = DataFlow::moduleImport("deepmerge") or
callee = DataFlow::moduleImport("defaults-deep") or
callee = DataFlow::moduleMember("js-extend", "extend") or
callee = DataFlow::moduleMember("merge", "recursive") or
callee = DataFlow::moduleImport("merge-deep") or
callee = DataFlow::moduleImport("merge-options") or
callee = DataFlow::moduleImport("mixin-deep") or
callee = DataFlow::moduleMember("ramda", "mergeDeepLeft") or
callee = DataFlow::moduleMember("ramda", "mergeDeepRight") or
callee = DataFlow::moduleMember("smart-extend", "deep") or
callee = LodashUnderscore::member("merge") or
callee = LodashUnderscore::member("mergeWith") or
callee = LodashUnderscore::member("defaultsDeep") or
callee = AngularJS::angular().getAPropertyRead("merge")
)
}
override DataFlow::Node getASourceOperand() { exists(int n | n >= 1 | result = getArgument(n)) }
override DataFlow::Node getDestinationOperand() { result = getArgument(0) }
override predicate isDeep() { any() }
}
/**
* A shallow extend call of form `extend(dst, src1, src2, ...)`.
*/
private class ExtendCallShallow extends ExtendCall {
ExtendCallShallow() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::globalVarRef("Object").getAPropertyRead("assign") or
callee = DataFlow::moduleImport("defaults") or
callee = DataFlow::moduleImport("extend-shallow") or
callee = DataFlow::moduleImport("merge") or
callee = DataFlow::moduleImport("mixin-object") or
callee = DataFlow::moduleImport("object-assign") or
callee = DataFlow::moduleImport("object.assign") or
callee = DataFlow::moduleImport("object.defaults") or
callee = DataFlow::moduleImport("smart-extend") or
callee = DataFlow::moduleImport("util-extend") or
callee = DataFlow::moduleImport("utils-merge") or
callee = DataFlow::moduleImport("xtend/mutable") or
callee = LodashUnderscore::member("extend") or
callee = AngularJS::angular().getAPropertyRead("extend")
)
}
override DataFlow::Node getASourceOperand() { exists(int n | n >= 1 | result = getArgument(n)) }
override DataFlow::Node getDestinationOperand() { result = getArgument(0) }
override predicate isDeep() { none() }
}
/**
* A shallow extend call of form `extend(src1, src2, ...)`.
*/
private class FunctionalExtendCallShallow extends ExtendCall {
FunctionalExtendCallShallow() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::moduleImport("xtend") or
callee = DataFlow::moduleImport("xtend/immutable") or
callee = DataFlow::moduleMember("ramda", "merge")
)
}
override DataFlow::Node getASourceOperand() { result = getAnArgument() }
override DataFlow::Node getDestinationOperand() { none() }
override predicate isDeep() { none() }
}
/**
* A taint propagating data flow edge from the objects flowing into an extend call to its return value
* and to the source of the destination object.
*/
private class ExtendCallTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(ExtendCall extend |
pred = extend.getASourceOperand() and succ = extend.getDestinationOperand().getALocalSource()
or
pred = extend.getAnOperand() and succ = extend
)
}
}
private import semmle.javascript.dataflow.internal.PreCallGraphStep
/**
* A step through a cloning library, such as `clone` or `fclone`.
*/
private class CloneStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
// `camelcase-keys` isn't quite a cloning library. But it's pretty close.
call = DataFlow::moduleImport(["clone", "fclone", "sort-keys", "camelcase-keys"]).getACall()
or
call = DataFlow::moduleMember("json-cycle", ["decycle", "retrocycle"]).getACall()
|
pred = call.getArgument(0) and
succ = call
)
}
}
/**
* A deep extend call from the [webpack-merge](https://npmjs.org/package/webpack-merge) library.
*/
private class WebpackMergeDeep extends ExtendCall, DataFlow::CallNode {
WebpackMergeDeep() {
this = DataFlow::moduleMember("webpack-merge", "merge").getACall()
or
this =
DataFlow::moduleMember("webpack-merge", ["mergeWithCustomize", "mergeWithRules"])
.getACall()
.getACall()
}
override DataFlow::Node getASourceOperand() { result = getAnArgument() }
override DataFlow::Node getDestinationOperand() { none() }
override predicate isDeep() { any() }
}

Some files were not shown because too many files have changed in this diff Show More