Merge pull request #1613 from asger-semmle/canonical-name-defs

Approved by xiemaisi
This commit is contained in:
semmle-qlci
2019-07-24 18:51:08 +01:00
committed by GitHub
44 changed files with 663 additions and 5 deletions

View File

@@ -68,7 +68,7 @@ predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
* For example, in the statement `var a = require("./a")`, the path expression
* `"./a"` imports a module `a` in the same folder.
*/
predicate importLookup(PathExpr path, Module target, string kind) {
predicate importLookup(ASTNode path, Module target, string kind) {
kind = "I" and
(
exists(Import i |
@@ -147,7 +147,15 @@ predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
)
}
from ASTNode ref, ASTNode decl, string kind
/**
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
*/
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
decl = ref.getClass().getAstNode() and
kind = "T"
}
from Locatable ref, ASTNode decl, string kind
where
variableDefLookup(ref, decl, kind)
or
@@ -161,4 +169,6 @@ where
typeLookup(ref, decl, kind)
or
typedInvokeLookup(ref, decl, kind)
or
jsdocTypeLookup(ref, decl, kind)
select ref, decl, kind

View File

@@ -25,6 +25,7 @@ import semmle.javascript.Extend
import semmle.javascript.Externs
import semmle.javascript.Files
import semmle.javascript.Functions
import semmle.javascript.GlobalAccessPaths
import semmle.javascript.HTML
import semmle.javascript.HtmlSanitizers
import semmle.javascript.JSDoc

View File

@@ -317,6 +317,7 @@ class ExprOrStmt extends @exprorstmt, ControlFlowNode, ASTNode { }
*/
class StmtContainer extends @stmt_container, ASTNode {
/** Gets the innermost enclosing container in which this container is nested. */
cached
StmtContainer getEnclosingContainer() { none() }
/**

View File

@@ -0,0 +1,180 @@
/**
* Provides predicates for associating qualified names with data flow nodes.
*/
import javascript
module GlobalAccessPath {
/**
* A local variable with exactly one definition, not counting implicit initialization.
*/
private class EffectivelyConstantVariable extends LocalVariable {
EffectivelyConstantVariable() {
strictcount(SsaExplicitDefinition ssa | ssa.getSourceVariable() = this) = 1
}
/** Gets the SSA definition of this variable. */
SsaExplicitDefinition getSsaDefinition() {
result.getSourceVariable() = this
}
/** Gets the data flow node representing the value of this variable, if one exists. */
DataFlow::Node getValue() {
result = getSsaDefinition().getRhsNode()
}
}
/**
* Gets the global access path referred to by `node`.
*
* This holds for direct references as well as for aliases
* established through local data flow.
*
* Examples:
* ```
* function f() {
* let v = foo.bar; // reference to 'foo.bar'
* v.baz; // reference to 'foo.bar.baz'
* }
*
* (function(ns) {
* ns.x; // reference to 'NS.x'
* })(NS = NS || {});
* ```
*/
string fromReference(DataFlow::Node node) {
result = fromReference(node.getImmediatePredecessor())
or
exists(EffectivelyConstantVariable var |
var.isCaptured() and
node.asExpr() = var.getAnAccess() and
result = fromReference(var.getValue())
)
or
node.accessesGlobal(result) and
result != "undefined"
or
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = fromReference(prop.getBase()) + "." + prop.getPropertyName()
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc |
result = acc.getClosureNamespace()
)
or
exists(DataFlow::PropertyProjection proj | node = proj |
proj.isSingletonProjection() and
result = fromReference(proj.getObject()) + "." + proj.getASelector()
)
or
// Treat 'e || {}' as having the same name as 'e'
exists(LogOrExpr e | node.asExpr() = e |
e.getRightOperand().(ObjectExpr).getNumProperty() = 0 and
result = fromReference(e.getLeftOperand().flow())
)
or
// Treat 'e && e.f' as having the same name as 'e.f'
exists(LogAndExpr e, Expr lhs, PropAccess rhs | node.asExpr() = e |
lhs = e.getLeftOperand() and
rhs = e.getRightOperand() and
(
exists(Variable v |
lhs = v.getAnAccess() and
rhs.getBase() = v.getAnAccess()
)
or
exists(string name |
lhs.(PropAccess).getQualifiedName() = name and
rhs.getBase().(PropAccess).getQualifiedName() = name
)
) and
result = fromReference(rhs.flow())
)
}
/**
* Holds if `rhs` is the right-hand side of a self-assignment.
*
* This usually happens in defensive initialization, for example:
* ```
* foo = foo || {};
* ```
*/
private predicate isSelfAssignment(DataFlow::Node rhs) {
fromRhs(rhs) = fromReference(rhs)
}
/**
* Holds if there is an assignment to `accessPath` in `file`, not counting
* self-assignments.
*/
private predicate isAssignedInFile(string accessPath, File file) {
exists(DataFlow::Node rhs |
fromRhs(rhs) = accessPath and
not isSelfAssignment(rhs) and
// Note: Avoid unneeded materialization of DataFlow::Node.getFile()
rhs.getAstNode().getFile() = file
)
}
/**
* Holds if `accessPath` is only assigned to from one file, not counting
* self-assignments.
*/
predicate isAssignedInUniqueFile(string accessPath) {
strictcount(File f | isAssignedInFile(accessPath, f)) = 1
}
/**
* Gets the global access path `node` is being assigned to, if any.
*
* Only holds for the immediate right-hand side of an assignment or property, not
* for nodes that transitively flow there.
*
* For example, the class nodes below all map to `foo.bar`:
* ```
* foo.bar = class {};
*
* foo = { bar: class {} };
*
* (function(f) {
* f.bar = class {}
* })(foo = foo || {});
* ```
*/
string fromRhs(DataFlow::Node node) {
exists(DataFlow::SourceNode base, string baseName, string name |
node = base.getAPropertyWrite(name).getRhs() and
result = baseName + "." + name
|
baseName = fromReference(base)
or
baseName = fromRhs(base)
)
or
exists(AssignExpr assign |
node = assign.getRhs().flow() and
result = assign.getLhs().(GlobalVarAccess).getName()
)
or
exists(FunctionDeclStmt fun |
node = DataFlow::valueNode(fun) and
result = fun.getId().(GlobalVarDecl).getName()
)
or
exists(ClassDeclStmt cls |
node = DataFlow::valueNode(cls) and
result = cls.getIdentifier().(GlobalVarDecl).getName()
)
}
/**
* Gets the global access path referenced by or assigned to `node`.
*/
string getAccessPath(DataFlow::Node node) {
result = fromReference(node)
or
not exists(fromReference(node)) and
result = fromRhs(node)
}
}

View File

@@ -132,7 +132,13 @@ class JSDocTypeExpr extends @jsdoc_type_expr, JSDocTypeExprParent, TypeAnnotatio
}
override Stmt getEnclosingStmt() {
result.getDocumentation() = getJSDocComment()
exists(Documentable astNode | astNode.getDocumentation() = getJSDocComment() |
result = astNode
or
result = astNode.(ExprOrType).getEnclosingStmt()
or
result = astNode.(Property).getObjectExpr().getEnclosingStmt()
)
}
override StmtContainer getContainer() { result = getEnclosingStmt().getContainer() }
@@ -205,9 +211,60 @@ class JSDocNamedTypeExpr extends @jsdoc_named_type_expr, JSDocTypeExpr {
override predicate isRawFunction() { getName() = "Function" }
/**
* Holds if this name consists of the unqualified name `prefix`
* followed by a (possibly empty) `suffix`.
*
* For example:
* - `foo.bar.Baz` has prefix `foo` and suffix `.bar.Baz`.
* - `Baz` has prefix `Baz` and an empty suffix.
*/
predicate hasNameParts(string prefix, string suffix) {
exists(string regex, string name | regex = "([^.]+)(.*)" |
name = getName() and
prefix = name.regexpCapture(regex, 1) and
suffix = name.regexpCapture(regex, 2)
)
}
pragma[noinline]
pragma[nomagic]
private predicate hasNamePartsAndEnv(string prefix, string suffix, JSDoc::Environment env) {
// Force join ordering
hasNameParts(prefix, suffix) and
env.isContainerInScope(getContainer())
}
/**
* Gets the qualified name of this name by resolving its prefix, if any.
*/
private string resolvedName() {
exists(string prefix, string suffix, JSDoc::Environment env |
hasNamePartsAndEnv(prefix, suffix, env) and
result = env.resolveAlias(prefix) + suffix
)
}
override predicate hasQualifiedName(string globalName) {
globalName = resolvedName()
or
not exists(resolvedName()) and
globalName = getName()
}
override DataFlow::ClassNode getClass() {
exists(string name |
hasQualifiedName(name) and
result.hasQualifiedName(name)
)
or
// Handle case where a local variable has a reference to the class,
// but the class doesn't have a globally qualified name.
exists(string alias, JSDoc::Environment env |
hasNamePartsAndEnv(alias, "", env) and
result.getAClassReference().flowsTo(env.getNodeFromAlias(alias))
)
}
}
/**
@@ -234,6 +291,10 @@ class JSDocAppliedTypeExpr extends @jsdoc_applied_type_expr, JSDocTypeExpr {
override predicate hasQualifiedName(string globalName) {
getHead().hasQualifiedName(globalName)
}
override DataFlow::ClassNode getClass() {
result = getHead().getClass()
}
}
/**
@@ -247,6 +308,10 @@ class JSDocNullableTypeExpr extends @jsdoc_nullable_type_expr, JSDocTypeExpr {
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
override JSDocTypeExpr getAnUnderlyingType() { result = getTypeExpr().getAnUnderlyingType() }
override DataFlow::ClassNode getClass() {
result = getTypeExpr().getClass()
}
}
/**
@@ -260,6 +325,10 @@ class JSDocNonNullableTypeExpr extends @jsdoc_non_nullable_type_expr, JSDocTypeE
predicate isPrefix() { jsdoc_prefix_qualifier(this) }
override JSDocTypeExpr getAnUnderlyingType() { result = getTypeExpr().getAnUnderlyingType() }
override DataFlow::ClassNode getClass() {
result = getTypeExpr().getClass()
}
}
/**
@@ -330,6 +399,10 @@ class JSDocOptionalParameterTypeExpr extends @jsdoc_optional_type_expr, JSDocTyp
JSDocTypeExpr getUnderlyingType() { result = getChild(0) }
override JSDocTypeExpr getAnUnderlyingType() { result = getUnderlyingType().getAnUnderlyingType() }
override DataFlow::ClassNode getClass() {
result = getUnderlyingType().getClass()
}
}
/**
@@ -353,3 +426,62 @@ class JSDocError extends @jsdoc_error {
/** Gets a textual representation of this element. */
string toString() { jsdoc_errors(this, _, _, result) }
}
module JSDoc {
/**
* A statement container which may declare JSDoc name aliases.
*/
class Environment extends StmtContainer {
/**
* Gets the fully qualified name aliased by the given unqualified name
* within this container.
*/
string resolveAlias(string alias) {
result = GlobalAccessPath::getAccessPath(getNodeFromAlias(alias))
}
/**
* Gets a data flow node from which the type name `alias` should
* be resolved.
*/
DataFlow::Node getNodeFromAlias(string alias) {
exists(VarDef def, VarRef ref |
def.getContainer() = this and
ref = def.getTarget().(BindingPattern).getABindingVarRef() and
ref.getName() = alias and
isTypenamePrefix(ref.getName()) // restrict size of predicate
|
exists(PropertyPattern p |
ref = p.getValuePattern() and
result.getAstNode() = p
)
or
result = DataFlow::valueNode(def.(Stmt))
or
ref = def.getTarget() and
result = def.getSource().flow()
or
result = DataFlow::parameterNode(def)
)
}
/**
* Holds if the aliases declared in this environment should be in scope
* within the given container.
*
* Specifically, this holds if this environment declares at least one
* alias and is an ancestor of `container`.
*/
final predicate isContainerInScope(StmtContainer container) {
exists(resolveAlias(_)) and // restrict size of predicate
container = this
or
isContainerInScope(container.getEnclosingContainer())
}
}
pragma[noinline]
private predicate isTypenamePrefix(string name) {
any(JSDocNamedTypeExpr expr).hasNameParts(name, _)
}
}

View File

@@ -521,6 +521,14 @@ class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
) {
getDef().getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/**
* Gets the data flow node representing the incoming value assigned at this definition,
* if any.
*/
DataFlow::Node getRhsNode() {
result = DataFlow::defSourceNode(getDef(), getSourceVariable())
}
}
/**

View File

@@ -105,4 +105,11 @@ class TypeAnnotation extends @type_annotation, Locatable {
* Note that this has no result for JSDoc type annotations.
*/
Type getType() { none() }
/**
* Gets the class referenced by this type annotation, if any.
*
* This unfolds nullability modifiers and generic type applications.
*/
DataFlow::ClassNode getClass() { none() }
}

View File

@@ -550,6 +550,10 @@ class TypeExpr extends ExprOrType, @typeexpr, TypeAnnotation {
override StmtContainer getContainer() { result = ExprOrType.super.getContainer() }
override TopLevel getTopLevel() { result = ExprOrType.super.getTopLevel() }
override DataFlow::ClassNode getClass() {
result.getAstNode() = getType().(ClassType).getClass()
}
}
/**

View File

@@ -161,6 +161,66 @@ module DataFlow {
/** Gets a textual representation of this element. */
string toString() { none() }
/**
* Gets the immediate predecessor of this node, if any.
*
* A node with an immediate predecessor can usually only have the value that flows
* into its from its immediate predecessor, currently with two exceptions:
*
* - An immediately-invoked function expression with a single return expression `e`
* has `e` as its immediate predecessor, even if the function can fall over the
* end and return `undefined`.
*
* - A destructuring property pattern or element pattern with a default value has
* both the `PropRead` and its default value as immediate predecessors.
*/
cached
DataFlow::Node getImmediatePredecessor() {
// Use of variable -> definition of variable
exists(SsaVariable var |
this = DataFlow::valueNode(var.getAUse()) and
result.(DataFlow::SsaDefinitionNode).getSsaVariable() = var
)
or
// Refinement of variable -> original definition of variable
exists(SsaRefinementNode refinement |
this.(DataFlow::SsaDefinitionNode).getSsaVariable() = refinement.getVariable() and
result.(DataFlow::SsaDefinitionNode).getSsaVariable() = refinement.getAnInput()
)
or
// Definition of variable -> RHS of definition
exists(SsaExplicitDefinition def |
this = TSsaDefNode(def) and
result = def.getRhsNode()
)
or
// IIFE call -> return value of IIFE
// Note: not sound in case function falls over end and returns 'undefined'
exists(Function fun |
localCall(this.asExpr(), fun) and
result = fun.getAReturnedExpr().flow() and
strictcount(fun.getAReturnedExpr()) = 1
)
or
// IIFE parameter -> IIFE call
exists(Parameter param |
this = DataFlow::parameterNode(param) and
localArgumentPassing(result.asExpr(), param)
)
or
// `{ x } -> e` in `let { x } = e`
exists(DestructuringPattern pattern |
this = TDestructuringPatternNode(pattern)
|
exists(VarDef def |
pattern = def.getTarget() and
result = DataFlow::valueNode(def.getDestructuringSource())
)
or
result = patternPropRead(pattern)
)
}
}
/**
@@ -1089,6 +1149,27 @@ module DataFlow {
nd = TExceptionalFunctionReturnNode(function)
}
/**
* INTERNAL. DO NOT USE.
*
* Gets the `PropRead` node corresponding to the value stored in the given
* binding pattern due to destructuring.
*
* For example, in `let { p: value } = f()`, the `value` pattern maps to a `PropRead`
* extracting the `p` property.
*/
private DataFlow::PropRead patternPropRead(BindingPattern value) {
exists(PropertyPattern prop |
value = prop.getValuePattern() and
result = TPropNode(prop)
)
or
exists(ArrayPattern array |
value = array.getAnElement() and
result = TElementPatternNode(array, value)
)
}
/**
* A classification of flows that are not modeled, or only modeled incompletely, by
* `DataFlowNode`:
@@ -1243,10 +1324,12 @@ module DataFlow {
}
/**
* INTERNAL. DO NOT USE.
*
* Gets the data flow node representing the source of the definition of `v` at `def`,
* if any.
*/
private Node defSourceNode(VarDef def, SsaSourceVariable v) {
Node defSourceNode(VarDef def, SsaSourceVariable v) {
exists(BindingPattern lhs, VarRef r |
lhs = def.getTarget() and r = lhs.getABindingVarRef() and r.getVariable() = v
|

View File

@@ -724,6 +724,18 @@ class ClassNode extends DataFlow::SourceNode {
DataFlow::SourceNode getAnInstanceReference() {
result = getAnInstanceReference(DataFlow::TypeTracker::end())
}
/**
* Holds if this class is exposed in the global scope through the given qualified name.
*/
pragma[noinline]
predicate hasQualifiedName(string name) {
exists(DataFlow::Node rhs |
getAClassReference().flowsTo(rhs) and
name = GlobalAccessPath::fromRhs(rhs) and
GlobalAccessPath::isAssignedInUniqueFile(name)
)
}
}
module ClassNode {

View File

@@ -0,0 +1,72 @@
test_fromReference
| other_ns.js:2:11:2:12 | ns | NS |
| other_ns.js:3:3:3:4 | ns | NS |
| other_ns.js:3:3:3:8 | ns.foo | NS.foo |
| other_ns.js:3:3:3:12 | ns.foo.bar | NS.foo.bar |
| other_ns.js:4:4:4:5 | NS | NS |
| other_ns.js:4:4:4:16 | NS = NS \|\| {} | NS |
| other_ns.js:4:9:4:10 | NS | NS |
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
| other_ns.js:6:1:6:8 | Conflict | Conflict |
| test.js:2:7:2:17 | v | foo.bar |
| test.js:2:11:2:13 | foo | foo |
| test.js:2:11:2:17 | foo.bar | foo.bar |
| test.js:3:3:3:3 | v | foo.bar |
| test.js:3:3:3:7 | v.baz | foo.bar.baz |
| test.js:4:7:4:24 | { baz, a, b: {c} } | foo.bar |
| test.js:4:7:4:28 | c | foo.bar.b.c |
| test.js:4:9:4:11 | baz | foo.bar.baz |
| test.js:4:14:4:14 | a | foo.bar.a |
| test.js:4:17:4:22 | b: {c} | foo.bar.b |
| test.js:4:20:4:22 | {c} | foo.bar.b |
| test.js:4:21:4:21 | c | foo.bar.b.c |
| test.js:4:28:4:28 | v | foo.bar |
| test.js:5:11:5:11 | c | foo.bar.b.c |
| test.js:5:11:5:13 | c.d | foo.bar.b.c.d |
| test.js:7:7:7:16 | w | window |
| test.js:7:11:7:16 | window | window |
| test.js:8:13:8:18 | window | window |
| test.js:8:13:8:20 | window.x | x |
| test.js:8:13:8:22 | window.x.y | x.y |
| test.js:9:13:9:18 | global | global |
| test.js:9:13:9:20 | global.x | x |
| test.js:9:13:9:22 | global.x.y | x.y |
| test.js:10:13:10:13 | w | window |
| test.js:10:13:10:15 | w.x | x |
| test.js:10:13:10:17 | w.x.y | x.y |
| test.js:12:7:12:25 | notUnique | foo.bar |
| test.js:12:19:12:21 | foo | foo |
| test.js:12:19:12:25 | foo.bar | foo.bar |
| test.js:13:7:13:15 | something | something |
| test.js:14:5:14:23 | notUnique | bar.baz |
| test.js:14:17:14:19 | bar | bar |
| test.js:14:17:14:23 | bar.baz | bar.baz |
| test.js:22:11:22:12 | ns | NS |
| test.js:23:3:23:4 | ns | NS |
| test.js:23:3:23:8 | ns.foo | NS.foo |
| test.js:23:3:23:12 | ns.foo.bar | NS.foo.bar |
| test.js:24:4:24:5 | NS | NS |
| test.js:24:4:24:16 | NS = NS \|\| {} | NS |
| test.js:24:9:24:10 | NS | NS |
| test.js:24:9:24:16 | NS \|\| {} | NS |
| test.js:26:1:26:8 | Conflict | Conflict |
| test.js:33:7:33:18 | { bar = {} } | foo |
| test.js:33:7:33:24 | bar | foo.bar |
| test.js:33:9:33:16 | bar = {} | foo.bar |
| test.js:33:22:33:24 | foo | foo |
| test.js:34:11:34:13 | bar | foo.bar |
| test.js:34:11:34:17 | bar.baz | foo.bar.baz |
test_fromRhs
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
| other_ns.js:6:12:6:13 | {} | Conflict |
| test.js:1:1:20:1 | functio ... ss {}\\n} | f |
| test.js:24:9:24:16 | NS \|\| {} | NS |
| test.js:26:12:26:13 | {} | Conflict |
| test.js:28:1:28:20 | class GlobalClass {} | GlobalClass |
| test.js:30:1:30:28 | functio ... on() {} | globalFunction |
| test.js:32:1:35:1 | functio ... .baz'\\n} | destruct |
test_assignedUnique
| GlobalClass |
| destruct |
| f |
| globalFunction |

View File

@@ -0,0 +1,13 @@
import javascript
query string test_fromReference(DataFlow::Node node) {
result = GlobalAccessPath::fromReference(node)
}
query string test_fromRhs(DataFlow::Node node) {
result = GlobalAccessPath::fromRhs(node)
}
query string test_assignedUnique() {
GlobalAccessPath::isAssignedInUniqueFile(result)
}

View File

@@ -0,0 +1,6 @@
// use NS from another file, that doesn't truly assign to it
(function(ns) {
ns.foo.bar; // 'NS.foo.bar'
})(NS = NS || {});
Conflict = {}; // assigned in multiple files

View File

@@ -0,0 +1,35 @@
function f() {
let v = foo.bar; // 'foo.bar'
v.baz; // 'foo.bar.baz'
let { baz, a, b: {c} } = v;
let d = c.d; // 'foo.bar.b.c.d'
let w = window;
let xy1 = window.x.y; // 'x.y'
let xy2 = global.x.y; // 'x.y'
let xy3 = w.x.y; // 'x.y'
let notUnique = foo.bar;
if (something()) {
notUnique = bar.baz;
}
notUnique.x; // No global access path
function localFunction() {}
class LocalClass {}
}
(function(ns) {
ns.foo.bar; // 'NS.foo.bar'
})(NS = NS || {});
Conflict = {}; // assigned in multiple files
class GlobalClass {}
function globalFunction() {}
function destruct() {
let { bar = {} } = foo;
let v = bar.baz; // 'foo.bar.baz'
}

View File

@@ -0,0 +1,15 @@
function test() {
let x = ns.very.long.namespace;
/**
* @param {x.Foo} foo
*/
function f(foo) {}
}
(function(iife) {
/**
* @param {iife.Foo} foo
*/
function f(foo) {}
})(IIFE);

View File

@@ -0,0 +1,13 @@
goog.module('test');
let net = goog.require('goog.net');
let { SomeType } = net;
/**
* @param {goog.net.SomeType} xio
* @param {net.SomeType} xio2
* @param {SomeType} xio3
*/
function test(xio, xio2, xio3) {
}

View File

@@ -0,0 +1,3 @@
ns = ns || {};
ns.very.long.namespace.Foo = class {};

View File

@@ -0,0 +1,5 @@
| bar.js:5:14:5:18 | x.Foo | ns.very.long.namespace.Foo |
| bar.js:12:14:12:21 | iife.Foo | IIFE.Foo |
| closure.js:8:12:8:28 | goog.net.SomeType | goog.net.SomeType |
| closure.js:9:12:9:23 | net.SomeType | goog.net.SomeType |
| closure.js:10:12:10:19 | SomeType | goog.net.SomeType |

View File

@@ -0,0 +1,5 @@
import javascript
query string test_hasQualifiedName(JSDocNamedTypeExpr expr) {
expr.hasQualifiedName(result)
}

View File

@@ -4,7 +4,7 @@ import Parameter_getDocumentation
import JSDocRestParameterTypeExpr
import getDocumentation
import JSDocRecordTypeExpr
import JSDoc
import JSDoc_test
import JSDocTag
import ObjectExpr_getDocumentation
import JSDocFunctionTypeExpr

View File

@@ -0,0 +1,14 @@
| tst.js:2:7:2:13 | a = g() | a | tst.js:2:11:2:13 | g() |
| tst.js:4:7:4:24 | { propB: b } = g() | b | tst.js:4:9:4:16 | propB: b |
| tst.js:6:7:6:34 | { propC ... } = g() | c | tst.js:6:9:6:16 | propC: c |
| tst.js:6:7:6:34 | { propC ... } = g() | d | tst.js:6:19:6:26 | propD: d |
| tst.js:8:7:8:41 | { array ... } = g() | elm1 | tst.js:8:22:8:25 | elm1 |
| tst.js:8:7:8:41 | { array ... } = g() | elm2 | tst.js:8:28:8:31 | elm2 |
| tst.js:17:3:17:22 | ({ propB: b }) = g() | b | tst.js:17:6:17:13 | propB: b |
| tst.js:19:3:19:32 | ({ prop ... ) = g() | c | tst.js:19:6:19:13 | propC: c |
| tst.js:19:3:19:32 | ({ prop ... ) = g() | d | tst.js:19:16:19:23 | propD: d |
| tst.js:21:3:21:22 | [ elm1, elm2 ] = g() | elm1 | tst.js:21:5:21:8 | elm1 |
| tst.js:21:3:21:22 | [ elm1, elm2 ] = g() | elm2 | tst.js:21:11:21:14 | elm2 |
| tst.js:31:12:31:23 | [elm1, elm2] | elm1 | tst.js:31:13:31:16 | elm1 |
| tst.js:31:12:31:23 | [elm1, elm2] | elm2 | tst.js:31:19:31:22 | elm2 |
| tst.js:31:26:31:40 | { prop: value } | value | tst.js:31:28:31:38 | prop: value |

View File

@@ -0,0 +1,4 @@
import javascript
from SsaExplicitDefinition def
select def, def.getSourceVariable(), def.getRhsNode()

View File

@@ -0,0 +1,35 @@
function f() {
let a = g();
let { propB: b } = g();
let { propC: c, propD: d } = g();
let { arrayProp: [ elm1, elm2 ] } = g();
a; // Ensure variables are live.
b;
c;
d;
elm1;
elm2;
({ propB: b }) = g();
({ propC: c, propD: d }) = g();
[ elm1, elm2 ] = g();
a;
b;
c;
d;
elm1;
elm2;
}
function h([elm1, elm2], { prop: value }) {
elm1;
elm2;
value;
}