mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Merge branch 'main' into javascript/ssrf
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CommandInjection::CommandInjection
|
||||
import semmle.javascript.security.dataflow.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.UnsafeJQueryPlugin::UnsafeJQueryPlugin
|
||||
import semmle.javascript.security.dataflow.UnsafeJQueryPluginQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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+()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @id js/exposure-of-private-files
|
||||
* @tags security
|
||||
* external/cwe/cwe-200
|
||||
* external/cwe/cwe-548
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @precision very-high
|
||||
* @id js/disabling-certificate-validation
|
||||
* @tags security
|
||||
* external/cwe-295
|
||||
* external/cwe/cwe-295
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CleartextLogging::CleartextLogging
|
||||
import semmle.javascript.security.dataflow.CleartextLoggingQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* external/cwe/cwe-256
|
||||
* external/cwe/cwe-260
|
||||
* external/cwe/cwe-313
|
||||
* external/cwe/cwe-522
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ConditionalBypass::ConditionalBypass
|
||||
import semmle.javascript.security.dataflow.ConditionalBypassQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 +
|
||||
|
||||
@@ -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
|
||||
4
javascript/ql/src/qlpack.lock.yml
Normal file
4
javascript/ql/src/qlpack.lock.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
dependencies: {}
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
@@ -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: "*"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/** Provides classes for working with files and folders. */
|
||||
|
||||
import semmle.javascript.Files
|
||||
@@ -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;
|
||||
@@ -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 */
|
||||
* /** @typedef {String} */
|
||||
* 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) }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
* … → [23] → [19] → [23 + 19] → …
|
||||
* </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>
|
||||
* … → [x] → [y] → [x = y] → …
|
||||
* </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>
|
||||
* … → [(x)] → [x] → …
|
||||
* </pre>
|
||||
* - Conditional expressions:
|
||||
* <pre>
|
||||
* … → [x ? y : z] → [x] ┬→ [y] → … <br>
|
||||
* └→ [z] → …
|
||||
* </pre>
|
||||
* - Short-circuiting operator `&&` (same for `||`):
|
||||
* <pre>
|
||||
* … → [x && y] → [x] → … <br>
|
||||
* ↓ <br>
|
||||
* [y] → …
|
||||
* </pre>
|
||||
* - Sequence/comma expressions:
|
||||
* <pre>
|
||||
* … → [x, y] → [x] → [y] → …
|
||||
* </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>
|
||||
* … → [{ x: 42 }] → [x] → [42] → [x : 42] → …
|
||||
* </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>
|
||||
* … → [if (x) s1 else s2] → [x] ┬→ [s1] → …
|
||||
* └→ [s2] → …
|
||||
* </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>
|
||||
* … → [while (x) s] → [x] → …
|
||||
* ⇅
|
||||
* [s]
|
||||
* </pre>
|
||||
*
|
||||
* On the other hand, `do`-`while` loops first execute their body before testing their condition:
|
||||
*
|
||||
* <pre>
|
||||
* … → [do s while (x)] → [s] ⇄ [x] → …
|
||||
* </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>
|
||||
* … → [for(i;t;u) s] → [i] → [t] → …
|
||||
* ↙ ↖
|
||||
* [s] → [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>
|
||||
* … → [for(x in y) s] → [y] → …
|
||||
* ↓ ↑
|
||||
* [x] ⇄ [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>
|
||||
* … → [x] → [return x;] → …
|
||||
* </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 {
|
||||
* if (x)
|
||||
* return;
|
||||
* s
|
||||
* } finally {
|
||||
* 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` → `finally` → `t` → `exit` and
|
||||
* `s` → `finally` → `t` → `u`, but also allows the path `return` →
|
||||
* `finally` → `t` → `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) {
|
||||
* s
|
||||
* function inner() {}
|
||||
* t
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Its CFG is
|
||||
*
|
||||
* <pre>
|
||||
* [entry] → [x] → [42] → [y] → [inner] → [s] → [function inner() {}] → [t] → [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] → [x as y] → [f] → [s] → [import x as y from 'foo';] → [function f() {}] → [t] → [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 }
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
* <!-- 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
|
||||
* <!-- an HTML line comment
|
||||
* </pre>
|
||||
*/
|
||||
class LineComment extends @line_comment, Comment { }
|
||||
|
||||
/**
|
||||
* An HTML comment start/end token interpreted as a line comment.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* <!-- 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:
|
||||
*
|
||||
* ```
|
||||
* <!-- 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) */
|
||||
* /** a JSDoc comment */
|
||||
* </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) */
|
||||
* </pre>
|
||||
*/
|
||||
class SlashStarComment extends @slashstar_comment, BlockComment { }
|
||||
|
||||
/**
|
||||
* A JSDoc comment.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <pre>
|
||||
* /** a JSDoc comment */
|
||||
* </pre>
|
||||
*/
|
||||
class DocComment extends @doc_comment, BlockComment { }
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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") }
|
||||
}
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user