Merge pull request #4958 from asgerf/js/angular2

Approved by erik-krogh
This commit is contained in:
CodeQL CI
2021-01-26 02:53:33 -08:00
committed by GitHub
69 changed files with 37278 additions and 1267 deletions

View File

@@ -16,6 +16,16 @@ import javascript
import semmle.javascript.frameworks.Templating
import semmle.javascript.RestrictedLocations
/**
* Holds if the `rel` attribute may be injected by an Angular2 directive.
*/
predicate maybeInjectedByAngular() {
DataFlow::moduleMember("@angular/core", "HostBinding")
.getACall()
.getArgument(0)
.mayHaveStringValue("attr.rel")
}
/**
* Holds if the href attribute contains a host that we cannot determine statically.
*/
@@ -44,6 +54,8 @@ where
e.getName() = "a" and
// and the host in the href is not hard-coded
hasDynamicHrefHostAttributeValue(e) and
// disable for Angular applications that dynamically inject the 'rel' attribute
not maybeInjectedByAngular() and
e.getAttributeByName("target").getStringValue() = "_blank" and
// there is no `rel` attribute specifying link type `noopener`/`noreferrer`;
// `rel` attributes with non-constant value are handled conservatively

View File

@@ -98,6 +98,7 @@ import semmle.javascript.frameworks.PropertyProjection
import semmle.javascript.frameworks.React
import semmle.javascript.frameworks.ReactNative
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

View File

@@ -300,7 +300,11 @@ class InlineScript extends @inline_script, Script { }
* ```
*/
class CodeInAttribute extends TopLevel {
CodeInAttribute() { this instanceof @event_handler or this instanceof @javascript_url }
CodeInAttribute() {
this instanceof @event_handler or
this instanceof @javascript_url or
this instanceof @angular_template_toplevel
}
}
/**

View File

@@ -10,6 +10,27 @@ module HTML {
HtmlFile() { getFileType().isHtml() }
}
/**
* A file that may contain HTML elements.
*
* This is either an `.html` file or a source code file containing
* embedded HTML snippets.
*/
private class FileContainingHtml extends File {
FileContainingHtml() {
getFileType().isHtml()
or
// The file contains an expression containing an HTML element
exists(Expr e |
e.getFile() = this and
xml_element_parent_expression(_, e, _)
)
}
}
/** Gets `i`th root node of the HTML fragment embedded in the given expression, if any. */
Element getHtmlElementFromExpr(Expr e, int i) { xml_element_parent_expression(result, e, i) }
/**
* An HTML element.
*
@@ -20,7 +41,7 @@ module HTML {
* ```
*/
class Element extends Locatable, @xmlelement {
Element() { exists(HtmlFile f | xmlElements(this, _, _, _, f)) }
Element() { exists(FileContainingHtml f | xmlElements(this, _, _, _, f)) }
override Location getLocation() { xmllocations(this, result) }
@@ -97,10 +118,15 @@ module HTML {
* ```
*/
class Attribute extends Locatable, @xmlattribute {
Attribute() { exists(HtmlFile f | xmlAttrs(this, _, _, _, _, f)) }
Attribute() { exists(FileContainingHtml f | xmlAttrs(this, _, _, _, _, f)) }
override Location getLocation() { xmllocations(this, result) }
/**
* Gets the inline script of this attribute, if any.
*/
CodeInAttribute getCodeInAttribute() { toplevel_parent_xml_node(result, this) }
/**
* Gets the element to which this attribute belongs.
*/
@@ -127,32 +153,6 @@ module HTML {
override string toString() { result = getName() + "=" + getValue() }
/**
* Gets the inline script of this attribute, if any.
*/
CodeInAttribute getCodeInAttribute() {
exists(
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
int el2, int ec2
|
l1 = getLocation() and
l2 = result.getLocation() and
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
|
(
sl1 = sl2 and sc1 < sc2
or
sl1 < sl2
) and
(
el1 = el2 and ec1 > ec2
or
el1 > el2
)
)
}
override string getAPrimaryQlClass() { result = "HTML::Attribute" }
}
@@ -226,26 +226,7 @@ module HTML {
* Gets the inline script of this script element, if any.
*/
private InlineScript getInlineScript() {
exists(
string f, Location l1, int sl1, int sc1, int el1, int ec1, Location l2, int sl2, int sc2,
int el2, int ec2
|
l1 = getLocation() and
l2 = result.getLocation() and
l1.hasLocationInfo(f, sl1, sc1, el1, ec1) and
l2.hasLocationInfo(f, sl2, sc2, el2, ec2)
|
(
sl1 = sl2 and sc1 < sc2
or
sl1 < sl2
) and
(
el1 = el2 and ec1 > ec2
or
el1 > el2
)
) and
toplevel_parent_xml_node(result, this) and
// the src attribute has precedence
not exists(getSourcePath())
}
@@ -295,7 +276,7 @@ module HTML {
* Note that instances of this class are only available if extraction is done with `--html all` or `--experimental`.
*/
class TextNode extends Locatable, @xmlcharacters {
TextNode() { exists(HtmlFile f | xmlChars(this, _, _, _, _, f)) }
TextNode() { exists(FileContainingHtml f | xmlChars(this, _, _, _, _, f)) }
override string toString() { result = getText() }
@@ -334,7 +315,7 @@ module HTML {
* ```
*/
class CommentNode extends Locatable, @xmlcomment {
CommentNode() { exists(HtmlFile f | xmlComments(this, _, _, f)) }
CommentNode() { exists(FileContainingHtml f | xmlComments(this, _, _, f)) }
/** Gets the element in which this comment occurs. */
Element getParent() { xmlComments(this, _, result, _) }

View File

@@ -236,6 +236,7 @@ abstract class Import extends ASTNode {
* behavior of Node.js imports, which prefer core modules such as `fs` over any
* source module of the same name.
*/
cached
Module getImportedModule() {
if exists(resolveExternsImport())
then result = resolveExternsImport()

View File

@@ -1308,6 +1308,20 @@ module DataFlow {
nd = TFunctionReturnNode(function)
}
/**
* INTERNAL: Do not use outside standard library.
*
* Gets a data flow node unique to the given field declaration.
*
* Note that this node defaults to being disconnected from the data flow
* graph, as the individual property reads and writes affecting the field are
* analyzed independently of the field declaration.
*
* Certain framework models may need this node to model the behavior of
* class and field decorators.
*/
DataFlow::Node fieldDeclarationNode(FieldDeclaration field) { result = TPropNode(field) }
/**
* Gets the data flow node corresponding the given l-value expression, if
* such a node exists.
@@ -1592,7 +1606,8 @@ module DataFlow {
e instanceof E4X::XMLAttributeSelector or
e instanceof E4X::XMLDotDotExpression or
e instanceof E4X::XMLFilterExpression or
e instanceof E4X::XMLQualifiedIdentifier
e instanceof E4X::XMLQualifiedIdentifier or
e instanceof Angular2::PipeRefExpr
)
or
exists(Expr e | e = nd.asExpr() |

View File

@@ -1007,6 +1007,11 @@ class ClassNode extends DataFlow::SourceNode {
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
result = impl.getFieldTypeAnnotation(fieldName)
}
/**
* Gets a decorator applied to this class.
*/
DataFlow::Node getADecorator() { result = impl.getADecorator() }
}
module ClassNode {
@@ -1064,6 +1069,9 @@ module ClassNode {
* Gets the type annotation for the field `fieldName`, if any.
*/
TypeAnnotation getFieldTypeAnnotation(string fieldName) { none() }
/** Gets a decorator applied to this class. */
DataFlow::Node getADecorator() { none() }
}
/**
@@ -1131,6 +1139,10 @@ module ClassNode {
result = field.getTypeAnnotation()
)
}
override DataFlow::Node getADecorator() {
result = astNode.getADecorator().getExpression().flow()
}
}
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {

View File

@@ -308,7 +308,9 @@ module SourceNode {
astNode instanceof DynamicImportExpr or
astNode instanceof ImportSpecifier or
astNode instanceof ImportMetaExpr or
astNode instanceof TaggedTemplateExpr
astNode instanceof TaggedTemplateExpr or
astNode instanceof Angular2::PipeRefExpr or
astNode instanceof Angular2::TemplateVarRefExpr
)
or
DataFlow::parameterNode(this, _)

View File

@@ -307,6 +307,11 @@ class AnalyzedNegativeConditionGuard extends AnalyzedRefinement {
}
}
/** Holds if `v` is a variable in an Angular template. */
private predicate isAngularTemplateVariable(LocalVariable v) {
v = any(Angular2::TemplateTopLevel tl).getScope().getAVariable()
}
/**
* Gets the abstract value representing the initial value of variable `v`.
*
@@ -325,7 +330,10 @@ private AbstractValue getImplicitInitValue(LocalVariable v) {
then
// model hoisting
result = TAbstractFunction(getAFunDecl(v))
else result = TAbstractUndefined()
else
if isAngularTemplateVariable(v)
then result = TIndefiniteAbstractValue("heap")
else result = TAbstractUndefined()
}
/**

View File

@@ -7,6 +7,7 @@ private import semmle.javascript.security.dataflow.Xss
private import semmle.javascript.security.dataflow.CodeInjectionCustomizations
private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomizations
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.internal.PreCallGraphStep
/**
* Provides classes for working with Angular (also known as Angular 2.x) applications.
@@ -196,6 +197,10 @@ module Angular2 {
or
result = getOptionArgument(argumentOffset + 1, "body")
}
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
result = this and responseType = "rxjs.observable" and promise = false
}
}
private string getInternalName(string name) {
@@ -218,4 +223,371 @@ module Angular2 {
private class DomAdapterLocation extends DOM::LocationSource::Range {
DomAdapterLocation() { this = domAdapter().getAMethodCall("getLocation") }
}
/**
* A reference to a pipe function, occurring in an Angular pipe expression
* that has been desugared to a function call.
*
* For example, the expression `x | f: y` is desugared to `f(x, y)` where
* `f` is a `PipeRefExpr`.
*/
class PipeRefExpr extends Expr, @angular_pipe_ref {
/** Gets the identifier node naming the pipe. */
Identifier getIdentifier() { result = getChildExpr(0) }
/** Gets the name of the pipe being referenced. */
string getName() { result = getIdentifier().getName() }
override string getAPrimaryQlClass() { result = "Angular2::PipeRefExpr" }
}
/**
* A reference to a variable in a template expression, corresponding
* to a property on the component class.
*/
class TemplateVarRefExpr extends Expr {
TemplateVarRefExpr() { this = any(TemplateTopLevel tl).getScope().getAVariable().getAnAccess() }
}
/** The top-level containing an Angular expression. */
class TemplateTopLevel extends TopLevel, @angular_template_toplevel {
/** Gets the expression in this top-level. */
Expr getExpression() { result = getChildStmt(0).(ExprStmt).getExpr() }
/** Gets the data flow node representing the initialization of the given variable in this scope. */
DataFlow::Node getVariableInit(string name) {
result = DataFlow::ssaDefinitionNode(SSA::implicitInit(getScope().getVariable(name)))
}
/** Gets a data flow node corresponding to a use of the given template variable within this top-level. */
DataFlow::SourceNode getAVariableUse(string name) {
result = getScope().getVariable(name).getAnAccess().flow()
}
}
/** The RHS of a `templateUrl` property, seen as a path expression. */
private class TemplateUrlPath extends PathExpr {
TemplateUrlPath() {
exists(Property prop |
prop.getName() = "templateUrl" and
this = prop.getInit()
)
}
override string getValue() { result = this.(Expr).getStringValue() }
}
/**
* Holds if the value of `attrib` is interpreted as an Angular expression.
*/
predicate isAngularExpressionAttribute(HTML::Attribute attrib) {
attrib.getName().matches("(%)") or
attrib.getName().matches("[%]") or
attrib.getName().matches("*ng%")
}
private DataFlow::Node getAttributeValueAsNode(HTML::Attribute attrib) {
result = attrib.getCodeInAttribute().(TemplateTopLevel).getExpression().flow()
}
/**
* The class for an Angular component.
*/
class ComponentClass extends DataFlow::ClassNode {
DataFlow::CallNode decorator;
ComponentClass() {
decorator = getADecorator() and
decorator = DataFlow::moduleMember("@angular/core", "Component").getACall()
}
/**
* Gets a data flow node representing the value of the declared
* instance field of the given name.
*/
DataFlow::Node getFieldNode(string name) {
exists(FieldDeclaration f |
f.getName() = name and
f.getDeclaringClass().flow() = this and
result = DataFlow::fieldDeclarationNode(f)
)
}
/**
* Gets a data flow node representing data flowing into a field of
* this component.
*/
DataFlow::Node getFieldInputNode(string name) {
result = getFieldNode(name)
or
result = getInstanceMember(name, DataFlow::MemberKind::setter()).getParameter(0)
}
/**
* Gets a data flow node representing data flowing out of a field
* of this component.
*/
DataFlow::Node getFieldOutputNode(string name) {
result = getFieldNode(name)
or
result = getInstanceMember(name, DataFlow::MemberKind::getter()).getReturnNode()
or
result = getInstanceMethod(name)
}
/**
* Gets the `selector` property of the `@Component` decorator.
*/
string getSelector() { decorator.getOptionArgument(0, "selector").mayHaveStringValue(result) }
/** Gets an HTML element that instantiates this component. */
HTML::Element getATemplateInstantiation() { result.getName() = getSelector() }
/**
* Gets an argument that flows into the `name` field of this component.
*
* For example, if the selector for this component is `"my-class"`, then this
* predicate can match an attribute like: `<my-class [foo]="1+2"/>`.
* The result of this predicate would be the `1+2` expression, and `name` would be `"foo"`.
*/
DataFlow::Node getATemplateArgument(string name) {
result =
getAttributeValueAsNode(getATemplateInstantiation().getAttributeByName("[" + name + "]"))
}
/**
* Gets the file referred to by `templateUrl`.
*
* Has no result if the template is given inline via a `template` property.
*/
pragma[noinline]
File getTemplateFile() {
result = decorator.getOptionArgument(0, "templateUrl").asExpr().(PathExpr).resolve()
}
/** Gets an element in the HTML template of this component. */
HTML::Element getATemplateElement() {
result.getFile() = getTemplateFile()
or
result.getParent*() =
HTML::getHtmlElementFromExpr(decorator.getOptionArgument(0, "template").asExpr(), _)
}
/**
* Gets an access to the given template variable within the template body of this component.
*/
DataFlow::SourceNode getATemplateVarAccess(string name) {
result =
getATemplateElement()
.getAnAttribute()
.getCodeInAttribute()
.(TemplateTopLevel)
.getAVariableUse(name)
}
}
/** A class with the `@Pipe` decorator. */
class PipeClass extends DataFlow::ClassNode {
DataFlow::CallNode decorator;
PipeClass() {
decorator = DataFlow::moduleMember("@angular/core", "Pipe").getACall() and
decorator = getADecorator()
}
/** Gets the value of the `name` option passed to the `@Pipe` decorator. */
string getPipeName() { decorator.getOptionArgument(0, "name").mayHaveStringValue(result) }
/** Gets a reference to this pipe. */
DataFlow::Node getAPipeRef() { result.asExpr().(PipeRefExpr).getName() = getPipeName() }
}
private class ComponentSteps extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(ComponentClass cls, string name |
// From <my-class [foo]="bar"/> to `foo` field in class
pred = cls.getATemplateArgument(name) and
succ = cls.getFieldInputNode(name)
or
// From `foo` field in class to <other-component [baz]="foo"/>
pred = cls.getFieldOutputNode(name) and
succ = cls.getATemplateVarAccess(name)
or
// From property write to the field input node
pred = cls.getAReceiverNode().getAPropertyWrite(name).getRhs() and
succ = cls.getFieldInputNode(name)
or
// From the field node to property read.
// We use `getFieldNode` instead of `getFieldOutputNode` as the other two cases
// from `getFieldOutputNode` are already handled by the general data flow library.
pred = cls.getFieldNode(name) and
succ = cls.getAReceiverNode().getAPropertyRead(name)
)
}
}
private class PipeSteps extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(PipeClass cls |
pred = cls.getInstanceMethod("transform") and
succ = cls.getAPipeRef()
)
}
}
/**
* An attribute of form `*ngFor="let var of EXPR"`.
*
* The `EXPR` has been extracted as the sole `CodeInAttribute` top-level for this
* attribute. There is no AST node for the implied for-of loop.
*/
private class ForLoopAttribute extends HTML::Attribute {
ForLoopAttribute() { getName() = "*ngFor" }
/** Gets a data-flow node holding the value being iterated over. */
DataFlow::Node getIterationDomain() { result = getAttributeValueAsNode(this) }
/** Gets the name of the variable holding the element of the current iteration. */
string getIteratorName() { result = getValue().regexpCapture(" *let +(\\w+).*", 1) }
/** Gets an HTML element in which the iterator variable is in scope. */
HTML::Element getAnElementInScope() { result.getParent*() = getElement() }
/** Gets a reference to the iterator variable. */
DataFlow::Node getAnIteratorAccess() {
result =
getAnElementInScope()
.getAnAttribute()
.getCodeInAttribute()
.(TemplateTopLevel)
.getAVariableUse(getIteratorName())
}
}
/**
* A taint step `array -> elem` in `*ngFor="let elem of array"`, or more precisely,
* a step from `array` to each access to `elem`.
*/
private class ForLoopStep extends TaintTracking::AdditionalTaintStep {
ForLoopAttribute attrib;
ForLoopStep() { this = attrib.getIterationDomain() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
succ = attrib.getAnIteratorAccess()
}
}
private class AnyCastStep extends PreCallGraphStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call |
call = any(TemplateTopLevel tl).getAVariableUse("$any").getACall() and
pred = call.getArgument(0) and
succ = call
)
}
}
/**
* Gets an invocation of the pipe of the given name.
*
* For example, the call generated from `items | async` would be found by `getAPipeCall("async")`.
*/
DataFlow::CallNode getAPipeCall(string name) {
result.getCalleeNode().asExpr().(PipeRefExpr).getName() = name
}
private class BuiltinPipeStep extends TaintTracking::AdditionalTaintStep, DataFlow::CallNode {
string name;
BuiltinPipeStep() { this = getAPipeCall(name) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
exists(int i | pred = getArgument(i) |
i = 0 and
name =
[
"async", "i18nPlural", "json", "keyvalue", "lowercase", "uppercase", "titlecase",
"slice"
]
or
i = 1 and name = "date" // date format string
)
or
name = "translate" and
succ = this and
pred = [getArgument(1), getOptionArgument(1, _)]
}
}
/**
* A `<mat-table>` element.
*/
class MatTableElement extends HTML::Element {
MatTableElement() { getName() = "mat-table" }
/** Gets the data flow node corresponding to the `[dataSource]` attribute. */
DataFlow::Node getDataSourceNode() {
result = getAttributeValueAsNode(getAttributeByName("[dataSource]"))
}
/**
* Gets an element of form `<mat-cell *matCellDef="let rowBinding">` in this table.
*/
HTML::Element getATableCell(string rowBinding) {
result.getName() = "mat-cell" and
result.getParent+() = this and
rowBinding =
result.getAttributeByName("*matCellDef").getValue().regexpCapture(" *let +(\\w+).*", 1)
}
/** Gets a data flow node that refers to one of the rows from the data source. */
DataFlow::Node getARowRef() {
exists(string rowBinding |
result =
getATableCell(rowBinding)
.getChild*()
.getAnAttribute()
.getCodeInAttribute()
.(TemplateTopLevel)
.getAVariableUse(rowBinding)
)
}
}
/**
* A taint step from `x -> y` in code of form:
* ```
* <mat-table [dataSource]="x">
* <mat-cell *matCellDef="let y">
* <foo [prop]="y"/>
* </mat-cell>
* </mat-table>
* ```
*/
private class MatTableTaintStep extends TaintTracking::AdditionalTaintStep {
MatTableElement table;
MatTableTaintStep() { this = table.getDataSourceNode() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
succ = table.getARowRef()
}
}
/** A taint step into the data array of a `MatTableDataSource` instance. */
private class MatTableDataSourceStep extends TaintTracking::AdditionalTaintStep, DataFlow::NewNode {
MatTableDataSourceStep() {
this =
DataFlow::moduleMember("@angular/material/table", "MatTableDataSource").getAnInstantiation()
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = [getArgument(0), getAPropertyWrite("data").getRhs()] and
succ = this
}
}
}

View File

@@ -0,0 +1,77 @@
/**
* Provides taint steps modeling flow through `rxjs` Observable objects.
*/
private import javascript
/**
* A step `x -> y` in `x.subscribe(y => ...)`, modeling flow out of an rxjs Observable.
*/
private class RxJsSubscribeStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
RxJsSubscribeStep() { getMethodName() = "subscribe" }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getReceiver() and
succ = getCallback(0).getParameter(0)
}
}
/**
* Gets a data flow node that can take the value of any input sent to `pipe`.
*
* For example, in `map(x => ...)`, `x` refers to any value sent to the pipe
* created by the `map` call.
*/
private DataFlow::Node pipeInput(DataFlow::CallNode pipe) {
pipe = DataFlow::moduleMember("rxjs/operators", ["map", "filter"]).getACall() and
result = pipe.getCallback(0).getParameter(0)
}
/**
* Gets a data flow node whose value becomes the output of the given `pipe`.
*
* For example, in `map(x => x + 1)`, the `x + 1` node becomes the output of
* the pipe.
*/
private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) {
pipe = DataFlow::moduleMember("rxjs/operators", "map").getACall() and
result = pipe.getCallback(0).getReturnNode()
or
pipe = DataFlow::moduleMember("rxjs/operators", "filter").getACall() and
result = pipe.getCallback(0).getParameter(0)
}
/**
* Holds if `pipe` acts as the identity function for success values.
*
* We currently lack a data-flow node to represent its input/ouput so it must
* be special-cased.
*/
private predicate isIdentityPipe(DataFlow::CallNode pipe) {
pipe = DataFlow::moduleMember("rxjs/operators", "catchError").getACall()
}
/**
* A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
*/
private class RxJsPipeMapStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
RxJsPipeMapStep() { getMethodName() = "pipe" }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getReceiver() and
succ = pipeInput(getArgument(0).getALocalSource())
or
exists(int i |
pred = pipeOutput(getArgument(i).getALocalSource()) and
succ = pipeInput(getArgument(i + 1).getALocalSource())
)
or
pred = pipeOutput(getLastArgument().getALocalSource()) and
succ = this
or
// Handle a common case where the last step is `catchError`.
isIdentityPipe(getLastArgument().getALocalSource()) and
pred = pipeOutput(getArgument(getNumArgument() - 2)) and
succ = this
}
}

View File

@@ -124,13 +124,24 @@ case @toplevel.kind of
0 = @script
| 1 = @inline_script
| 2 = @event_handler
| 3 = @javascript_url;
| 3 = @javascript_url
| 4 = @angular_template_toplevel;
is_module (int tl: @toplevel ref);
is_nodejs (int tl: @toplevel ref);
is_es2015_module (int tl: @toplevel ref);
is_closure_module (int tl: @toplevel ref);
@xml_node_with_code = @xmlelement | @xmlattribute;
toplevel_parent_xml_node(
unique int toplevel: @toplevel ref,
int xmlnode: @xml_node_with_code ref);
xml_element_parent_expression(
unique int xmlnode: @xmlelement ref,
int expression: @expr ref,
int index: int ref);
// statements
#keyset[parent, idx]
stmts (unique int id: @stmt,
@@ -354,6 +365,7 @@ case @expr.kind of
| 116 = @assignlogandexpr
| 117 = @assignlogorexpr
| 118 = @assignnullishcoalescingexpr
| 119 = @angular_pipe_ref
;
@varaccess = @proper_varaccess | @export_varaccess;

View File

@@ -50,6 +50,10 @@
<v>3</v>
</e>
<e>
<k>@angular_template_toplevel</k>
<v>100</v>
</e>
<e>
<k>@stmt</k>
<v>1096691</v>
</e>
@@ -434,6 +438,10 @@
<v>1</v>
</e>
<e>
<k>@angular_pipe_ref</k>
<v>100</v>
</e>
<e>
<k>@preinc_expr</k>
<v>1792</v>
</e>
@@ -9264,6 +9272,170 @@
<dependencies/>
</relation>
<relation>
<name>toplevel_parent_xml_node</name>
<cardinality>43</cardinality>
<columnsizes>
<e>
<k>toplevel</k>
<v>43</v>
</e>
<e>
<k>xmlnode</k>
<v>43</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>toplevel</src>
<trg>xmlnode</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>43</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>xmlnode</src>
<trg>toplevel</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>43</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
<relation>
<name>xml_element_parent_expression</name>
<cardinality>1</cardinality>
<columnsizes>
<e>
<k>xmlnode</k>
<v>1</v>
</e>
<e>
<k>expression</k>
<v>1</v>
</e>
<e>
<k>index</k>
<v>1</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>xmlnode</src>
<trg>expression</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>xmlnode</src>
<trg>index</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>expression</src>
<trg>xmlnode</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>expression</src>
<trg>index</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>index</src>
<trg>xmlnode</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>index</src>
<trg>expression</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
<relation>
<name>is_nodejs</name>
<cardinality>12</cardinality>
<columnsizes>