Packaging: Rafactor Javascript core libraries

Extract the external facing `qll` files into the codeql/javascript-all
query pack.
This commit is contained in:
Andrew Eisenberg
2021-08-25 12:02:31 -07:00
parent 48344d9ffc
commit 45d1fa7f01
410 changed files with 41 additions and 10 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,877 @@
/**
* Provides classes for dealing with AngularJS expressions (e.g. `<div id="{{myId}}"/>`).
*
* INTERNAL: Do not import this module directly, import `AngularJS` instead.
*
* NOTE: The API of this library is not stable yet and may change in
* the future.
*/
import javascript
/**
* Extensible class for AngularJS expression source providers (e.g. `id="{{myId}}"` of `<div id="{{myId}}" class="left"/>`).
*/
abstract class NgSourceProvider extends Locatable {
/**
* Holds if this element provides the source as `src` for an AngularJS expression at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
*/
abstract predicate providesSourceAt(
string src, string path, int startLine, int startColumn, int endLine, int endColumn
);
/**
* Gets the enclosing element of the provided source.
*/
abstract DOM::ElementDefinition getEnclosingElement();
}
/**
* The source of an AngularJS expression.
*/
private newtype TNgSource = MkNgSource(NgSourceProvider p)
/**
* The source of an AngularJS expression.
*/
class NgSource extends MkNgSource {
NgSourceProvider provider;
NgSource() { this = MkNgSource(provider) }
/**
* Gets the raw text of this source code.
*/
string getText() { provider.providesSourceAt(result, _, _, _, _, _) }
/**
* Gets the provider for this source code.
*/
NgSourceProvider getProvider() { result = provider }
/** Gets a textual representation of this element. */
string toString() { result = getText() }
}
/**
* Get the regexp that matches the shortest strings wrapped in '{{' and '}}'.
*/
private string getInterpolatedExpressionPattern() { result = "(?<=\\{\\{).*?(?=\\}\\})" }
/**
* AngularJS expression source from HTML text (e.g. `myExpr` of `<div>Some text {{myExpr}} some text</div>`).
*/
private class HtmlTextNodeAsNgSourceProvider extends NgSourceProvider, HTML::TextNode {
string source;
int offset;
HtmlTextNodeAsNgSourceProvider() {
source = getText().regexpFind(getInterpolatedExpressionPattern(), _, offset)
}
override predicate providesSourceAt(
string src, string path, int startLine, int startColumn, int endLine, int endColumn
) {
src = source and
getLocation().hasLocationInfo(path, startLine, startColumn, endLine, endColumn) // this is the entire surrounding text element, we could be more precise by counting lines
}
override DOM::ElementDefinition getEnclosingElement() { result = getParent() }
}
/**
* AngularJS expression source from HTML attributes (e.g. `myId` of `<div id="{{myId}}"/>`).
*/
abstract private class HtmlAttributeAsNgSourceProvider extends NgSourceProvider, HTML::Attribute {
override predicate providesSourceAt(
string src, string path, int startLine, int startColumn, int endLine, int endColumn
) {
src = getSource() and
getLocation().hasLocationInfo(path, startLine, startColumn - getOffset(), endLine, _) and
endColumn = startColumn + src.length() - 1
}
/** The source code of the expression. */
abstract string getSource();
/** The offset into the attribute where the expression starts. */
abstract int getOffset();
override DOM::ElementDefinition getEnclosingElement() { result = getElement() }
}
/**
* AngularJS expression sources interpolated with `{{}}` in attributes.
*/
private class HtmlAttributeAsInterpolatedNgSourceProvider extends HtmlAttributeAsNgSourceProvider {
string source;
int offset;
HtmlAttributeAsInterpolatedNgSourceProvider() {
source = getValue().regexpFind(getInterpolatedExpressionPattern(), _, offset) and
not this instanceof HtmlAttributeAsPlainNgSourceProvider
}
override string getSource() { result = source }
override int getOffset() { result = offset }
}
/**
* AngularJS expression sources in attributes.
*/
private class HtmlAttributeAsPlainNgSourceProvider extends HtmlAttributeAsNgSourceProvider {
HtmlAttributeAsPlainNgSourceProvider() {
exists(AngularJS::DirectiveInstance d |
d.getATarget() = this and
not (
// builtins that uses interpolation
d.getName() = "ngHref" or
d.getName() = "ngSrc" or
d.getName() = "ngSrcset" or
// string of the form: `<Ident>( as <Ident>)?`
d.getName() = "ngController"
)
)
}
override string getSource() { result = getValue() }
override int getOffset() { result = 0 }
}
/**
* AngularJS expression sources interpolated with `{{}}` in the `.template` field of an AngularJS directive.
*/
private class TemplateFieldNgSourceProvider extends NgSourceProvider {
AngularJS::GeneralDirective directive;
string source;
int offset;
TemplateFieldNgSourceProvider() {
this = directive.getMember("template").asExpr() and
source =
this.(ConstantString)
.getStringValue()
.regexpFind(getInterpolatedExpressionPattern(), _, offset)
}
override predicate providesSourceAt(
string src, string path, int startLine, int startColumn, int endLine, int endColumn
) {
src = source and
getLocation().hasLocationInfo(path, startLine, startColumn - offset, endLine, _) and
endColumn = startColumn + src.length() - 1
}
override DOM::ElementDefinition getEnclosingElement() { result = directive.getAMatchingElement() }
}
/**
* A token from a tokenized AngularJS expression source.
*/
abstract class NgToken extends TNgToken {
// (only exposed for testability)
/** Holds if this token starts at `start` of NgSource `src`. */
abstract predicate at(NgSource src, int start);
/** Gets a textual representation of this element. */
string toString() {
exists(string content |
is(content) and
result = "(" + this.ppKind() + ": " + content + ")"
)
}
/**
* Pretty prints the kind of this token.
*/
abstract string ppKind();
/**
* Holds if this token has source text content.
*/
abstract predicate is(string content);
/**
* Gets the predecessor of this token.
*/
NgToken pre() {
exists(NgSource src |
this.at(src, _) and
result.at(src, _)
) and
result.getIndex() + 1 = this.getIndex()
}
/**
* Gets the index of this token in the list of tokens of the enclosing `NgSource`.
*/
private int getIndex() {
exists(NgSource src, int start | this.at(src, start) |
start =
rank[result + 1](NgToken someToken, int someStart | someToken.at(src, someStart) | someStart)
)
}
/**
* Gets the successor of this token.
*/
NgToken succ() { result.pre() = this }
}
/**
* Provides classes for lexing AngularJS expression source code.
*
* See https://github.com/angular/angular.js/blob/master/src/ng/parse.js -> Lexer.prototype
*/
private module Lexer {
/**
* Base class for lexer tokens.
*
* Lexing is done using `regexpFind` on a disjunction of the result of the `getPattern` methods of all token types.
* This means that tokens defined by `getPattern` should *not* overlap, otherwise `regexpFind` might start skipping tokens.
*/
abstract private class NgTokenType extends string {
bindingset[this]
NgTokenType() { any() }
abstract string getPattern();
}
private class NgStringTokenType extends NgTokenType {
NgStringTokenType() { this = "NgStringTokenType" }
override string getPattern() { result = "'(?:\\\\'|[^'])*'|\"(?:\\\\\"|[^\"])*\"" }
}
private class NgWhitespaceTokenType extends NgTokenType {
NgWhitespaceTokenType() { this = "NgWhitespaceTokenType" }
override string getPattern() { result = "\\s+" }
}
private class NgIdentTokenType extends NgTokenType {
NgIdentTokenType() { this = "NgIdentTokenType" }
override string getPattern() { result = "[A-Za-z$_][\\w$]*" }
}
private class NgNumberTokenType extends NgTokenType {
NgNumberTokenType() { this = "NgNumberTokenType" }
override string getPattern() { result = "\\d+(\\.\\d*)?" }
}
private class NgOtherTokenType extends NgTokenType {
NgOtherTokenType() { this = "NgOtherTokenType" }
override string getPattern() { result = "[(){}\\[\\].,;:?]" }
}
private class NgOpTokenType extends NgTokenType {
NgOpTokenType() { this = "NgOpTokenType" }
override string getPattern() {
result =
concat(string op |
op = "===" or
op = "!==" or
op = "==" or
op = "!=" or
op = "<=" or
op = ">=" or
op = "&&" or
op = "||" or
op = "*" or
op = "!" or
op = "=" or
op = "<" or
op = ">" or
op = "+" or
op = "-" or
op = "/" or
op = "%" or
op = "|"
|
"\\Q" + op + "\\E", "|" order by op.length() desc
)
}
}
newtype TNgToken =
MkNgToken(NgSource src, int start, NgTokenType tp, string text) {
exists(string allTokenTypePattern |
allTokenTypePattern = concat(NgTokenType t, string p | p = t.getPattern() | p, "|") and
text = src.getText().regexpFind(allTokenTypePattern, _, start) and
text.regexpMatch(tp.getPattern())
)
}
abstract private class NgInternalToken extends TNgToken, NgToken {
override predicate at(NgSource src, int start) { this = MkNgToken(src, start, _, _) }
override string ppKind() {
exists(string s, string type |
this = MkNgToken(_, _, type, _) and
type = "Ng" + s + "TokenType" and
result = s.toUpperCase()
)
}
override predicate is(string content) { this = MkNgToken(_, _, _, content) }
}
/**
* A string token.
*/
class NgStringToken extends NgInternalToken {
NgStringToken() { this = MkNgToken(_, _, any(NgStringTokenType t), _) }
}
/**
* A number token.
*/
class NgNumToken extends NgInternalToken {
NgNumToken() { this = MkNgToken(_, _, any(NgNumberTokenType t), _) }
}
/**
* An identifier token.
*/
class NgIdentToken extends NgInternalToken {
NgIdentToken() { this = MkNgToken(_, _, any(NgIdentTokenType t), _) }
}
/**
* An "other" token.
*/
class NgOtherToken extends NgInternalToken {
NgOtherToken() { this = MkNgToken(_, _, any(NgOtherTokenType t), _) }
}
/**
* An operator token.
*/
class NgOpToken extends NgInternalToken {
NgOpToken() { this = MkNgToken(_, _, any(NgOpTokenType t), _) }
}
}
private import Lexer
/**
* An NgAst node from a parsed AngularJS expression source.
*/
abstract class NgAstNode extends TNode {
/**
* Holds if the node spans the tokens from `start` to `end` (inclusive).
*/
abstract predicate at(NgToken start, NgToken end);
/**
* Gets the `n`th child node.
*/
abstract NgAstNode getChild(int n);
/**
* Pretty prints the child nodes.
*/
language[monotonicAggregates]
string ppChildren() {
result =
concat(NgAstNode child, int idx |
child = getChild(idx) and
not child instanceof Empty
|
child.pp(), " " order by idx
)
}
/** Gets a textual representation of this element. */
string toString() { result = pp() }
/**
* Pretty-prints this node.
*/
abstract string pp();
}
/**
* The root node of an abstract syntax tree.
*/
class NgAst extends TNgAst, NgAstNode {
override predicate at(NgToken start, NgToken end) { this = TNgAst(start, end, _, _) }
override string pp() {
exists(string oneTime |
(if isOneTime() then oneTime = " <oneTime>" else oneTime = "") and
result = "(NgAst:" + oneTime + " " + ppChildren() + ")"
)
}
override NgAstNode getChild(int n) { n = 0 and this = TNgAst(_, _, _, result) }
/**
* Holds if this is a "one time binding" (i.e. ::-prefixed, e.g. `{{::myId}}`).
*/
predicate isOneTime() { this = TNgAst(_, _, true, _) }
}
/**
* An expression-statement node.
*/
class NgExprStmt extends TNgExprStmt, NgAstNode {
override predicate at(NgToken start, NgToken end) { this = TNgExprStmt(start, end, _) }
override string pp() { result = "(NgExprStmt: " + ppChildren() + ")" }
override NgAstNode getChild(int n) { n = 0 and this = TNgExprStmt(_, _, result) }
}
/**
* A "filter-chain" node (see https://github.com/angular/angular.js/blob/master/src/ng/parse.js -> filterChain).
*
* Example: `expr | filter1 | filter2 | ...`, the filters are optional (i.e. `Empty`).
*/
class NgFilterChain extends TNgFilterChain, NgAstNode {
override predicate at(NgToken start, NgToken end) { this = TNgFilterChain(start, end, _, _) }
override string pp() { result = "(NgFilterChain: " + ppChildren() + ")" }
override NgAstNode getChild(int n) {
n = 0 and result = getExpr()
or
n = 1 and result = getFilter()
}
/**
* Gets the leading expression of this filter chain.
*/
NgExpr getExpr() { this = TNgFilterChain(_, _, result, _) }
/**
* Gets the filter of this filter chain.
*/
NgFilter getFilter() { this = TNgFilterChain(_, _, _, result) }
}
/**
* Super class for `NgFilter` and `Empty`.
*/
abstract class NgMaybeFilter extends NgAstNode { }
/**
* A filter node.
*
* Example: `filter1 ... | filter2`
*/
class NgFilter extends TNgFilter, NgMaybeFilter {
override predicate at(NgToken start, NgToken end) { this = TNgFilter(start, end, _, _) }
override string pp() { result = "(NgFilter: " + ppChildren() + ")" }
override NgAstNode getChild(int n) {
n = 0 and result = getHeadFilter()
or
n = 1 and result = getTailFilter()
}
/**
* Gets the successor filter of this filter, if any.
*/
NgSingleFilter getHeadFilter() { this = TNgFilter(_, _, result, _) }
/**
* Gets the tail filter of this filter, if any.
*/
NgFilter getTailFilter() { this = TNgFilter(_, _, _, result) }
}
/**
* A single filter node.
*
* Example: `filter1:arg1:arg2`
*/
class NgSingleFilter extends TNgSingleFilter, NgAstNode {
override predicate at(NgToken start, NgToken end) { this = TNgSingleFilter(start, end, _, _) }
override string pp() {
exists(string sep |
(
if forall(NgAstNode child | child = getChild(_) | child instanceof Empty)
then sep = ""
else sep = " "
) and
result = "(NgSingleFilter: " + getName() + sep + ppChildren() + ")"
)
}
override NgAstNode getChild(int n) { n = 0 and this = TNgSingleFilter(_, _, _, result) }
/**
* Gets the name of the referenced filter.
*/
string getName() { this = TNgSingleFilter(_, _, result, _) }
/**
* Gets the `i`th argument expression of this filter call.
*/
NgExpr getArgument(int i) { result = getChild(0).(NgFilterArgument).getElement(i) }
}
/**
* An expression node.
*/
abstract class NgExpr extends NgAstNode { }
/**
* A variable reference expression node.
*/
class NgVarExpr extends TNgVarExpr, NgExpr {
NgIdentToken identifier;
NgVarExpr() { this = TNgVarExpr(identifier) }
override predicate at(NgToken start, NgToken end) { start = end and start = identifier }
override string pp() { result = "(NgVarExpr: " + getName() + ")" }
override NgAstNode getChild(int n) { none() }
/**
* Gets the name of the referenced variable.
*/
string getName() { identifier.is(result) }
}
/**
* A dot expression node for looking up a fixed property with a fixed name.
*/
class NgDotExpr extends TNgDotExpr, NgExpr {
override predicate at(NgToken start, NgToken end) { this = TNgDotExpr(start, end, _, _) }
override string pp() { result = "(NgDotExpr: " + getBase().pp() + "." + getName() + ")" }
override NgAstNode getChild(int n) { n = 0 and result = getBase() }
/**
* Gets the node for the base expression of this expression.
*/
NgAstNode getBase() { this = TNgDotExpr(_, _, result, _) }
/**
* Gets the name of the property that is looked up.
*/
string getName() { this = TNgDotExpr(_, _, _, result) }
}
/**
* A call expression node.
*/
class NgCallExpr extends TNgCallExpr, NgExpr {
override predicate at(NgToken start, NgToken end) { this = TNgCallExpr(start, end, _, _) }
override string pp() { result = "(NgCallExpr: " + ppChildren() + ")" }
override NgAstNode getChild(int n) {
n = 0 and this = TNgCallExpr(_, _, result, _)
or
n = 1 and this = TNgCallExpr(_, _, _, result)
}
/**
* Gets the callee expression of this call.
*/
NgExpr getCallee() { result = getChild(0) }
/**
* Gets the `i`th argument expression of this call.
*/
NgExpr getArgument(int i) { result = getChild(1).(NgConsCallArgument).getElement(i) }
}
/**
* A string expression node.
*/
class NgString extends TNgString, NgExpr {
NgStringToken stringToken;
NgString() { this = TNgString(stringToken) }
override predicate at(NgToken start, NgToken end) { start = end and start = stringToken }
override string pp() { result = getRawValue() }
override NgAstNode getChild(int n) { none() }
/**
* Gets the raw string value of this expression, including surrounding quotes.
*/
string getRawValue() { stringToken.is(result) }
/**
* Gets the string value of this expression, excluding surrounding quotes.
*/
string getStringValue() { result = getRawValue().substring(1, getRawValue().length() - 1) }
}
/**
* A number expression node.
*/
class NgNumber extends TNgNumber, NgExpr {
NgNumToken numberToken;
NgNumber() { this = TNgNumber(numberToken) }
override predicate at(NgToken start, NgToken end) { start = end and start = numberToken }
override string pp() { result = getValue() }
override NgAstNode getChild(int n) { none() }
/**
* Gets the number value of this expression.
*/
string getValue() { numberToken.is(result) }
}
/**
* Super class for `NgFilterArgument` and `Empty`.
*/
abstract class NgMaybeFilterArgument extends NgAstNode { }
/**
* A non-empty cons-list of arguments for a filter.
*/
class NgFilterArgument extends TNgFilterArgument, NgMaybeFilterArgument {
override predicate at(NgToken start, NgToken end) { this = TNgFilterArgument(start, end, _, _) }
override string pp() { result = "(NgFilterArgument: " + ppChildren() + ")" }
override NgAstNode getChild(int n) {
n = 0 and this = TNgFilterArgument(_, _, result, _)
or
n = 1 and this = TNgFilterArgument(_, _, _, result)
}
/**
* Gets the `i`th element of this entire cons-list.
*/
NgExpr getElement(int i) {
if i = 0
then result = getChild(0)
else result = getChild(1).(NgFilterArgument).getElement(i - 1)
}
}
/**
* Super class for `NgConsCallArgument` and `Empty`.
*/
abstract class NgCallArguments extends NgAstNode { }
/**
* A non-empty cons-list of arguments for a call.
*/
class NgConsCallArgument extends TNgConsCallArgument, NgCallArguments {
override predicate at(NgToken start, NgToken end) { this = TNgConsCallArgument(start, end, _, _) }
override string pp() { result = "(NgConsCallArgument: " + ppChildren() + ")" }
override NgAstNode getChild(int n) {
n = 0 and this = TNgConsCallArgument(_, _, result, _)
or
n = 1 and this = TNgConsCallArgument(_, _, _, result)
}
/**
* Gets the `i`th element of this entire cons-list.
*/
NgExpr getElement(int i) {
if i = 0
then result = getChild(0)
else result = getChild(1).(NgConsCallArgument).getElement(i - 1)
}
}
/**
* The empty alternative of maybes.
*/
class Empty extends TNgEmpty, NgMaybeFilter, NgMaybeFilterArgument, NgCallArguments {
override predicate at(NgToken start, NgToken end) { start = end and this = TNgEmpty() }
override string pp() { result = "<Empty>" }
override NgAstNode getChild(int n) { none() }
}
private module Parser {
/**
* Parses a `NgFilter` or `Empty`, whichever is possible.
*/
private NgMaybeFilter maybeFilter(NgToken start, NgToken end) {
if start.succ().(NgOpToken).is("|")
then result.(NgFilter).at(start.succ().succ(), end)
else result.(Empty).at(start, end)
}
/**
* Parses a `NgFilterArgument` or `Empty`, whichever is possible.
*/
private NgMaybeFilterArgument maybeFilterArgument(NgToken start, NgToken end) {
if start.succ().(NgOtherToken).is(":")
then result.(NgFilterArgument).at(start.succ().succ(), end)
else result.(Empty).at(start, end)
}
/**
* Parses a the first `NgConsCallArgument` of a call or `Empty`, whichever is possible.
*/
private NgCallArguments maybeFirstCallArgument(NgToken start, NgToken end) {
if start.succ().(NgOtherToken).is(")")
then result.(Empty).at(start, end)
else result.(NgConsCallArgument).at(start.succ(), end)
}
/**
* Parses a the second or later `NgConsCallArgument` of a call or `Empty`, whichever is possible.
*/
private NgCallArguments maybeCallArgument(NgToken start, NgToken end) {
if start.succ().(NgOtherToken).is(",")
then result.(NgConsCallArgument).at(start.succ().succ(), end)
else result.(Empty).at(start, end)
}
/**
* NgAst node types. Associated with a range of tokens.
*/
cached
newtype TNode =
TNgAst(NgToken start, NgToken end, boolean oneTime, NgExprStmt stmt) {
not exists(start.pre()) and
not exists(end.succ()) and
exists(NgToken stmtStart |
if start.(NgOtherToken).is(":") and start.succ().(NgOtherToken).is(":")
then (
stmtStart = start.succ().succ() and oneTime = true
) else (
stmtStart = start and oneTime = false
)
|
stmt.at(stmtStart, end)
)
} or
TNgExprStmt(NgToken start, NgToken end, NgFilterChain c) { c.at(start, end) } or
TNgFilterChain(NgToken start, NgToken end, NgExpr e, NgMaybeFilter f) {
exists(NgToken mid |
e.at(start, mid) and
f = maybeFilter(mid, end)
)
} or
TNgSingleFilter(NgToken start, NgToken end, string name, NgMaybeFilterArgument a) {
start.(NgIdentToken).is(name) and
a = maybeFilterArgument(start, end)
} or
TNgFilter(NgToken start, NgToken end, NgSingleFilter head, NgMaybeFilter tail) {
exists(NgToken endArgs |
head.at(start, endArgs) and
tail = maybeFilter(endArgs, end)
)
} or
TNgFilterArgument(NgToken start, NgToken end, NgExpr base, NgMaybeFilterArgument a) {
exists(NgToken mid |
base.at(start, mid) and
a = maybeFilterArgument(mid, end)
)
} or
TNgDotExpr(NgToken start, NgIdentToken end, NgExpr base, string name) {
base.at(start, end.pre().pre()) and
end.pre().(NgOtherToken).is(".") and
end.is(name)
} or
TNgCallExpr(NgToken start, NgOtherToken end, NgExpr base, NgCallArguments a) {
exists(NgToken endBase |
base.at(start, endBase) and
endBase.succ().(NgOtherToken).is("(") and
a = maybeFirstCallArgument(endBase.succ(), end.pre()) and
end.is(")")
)
} or
TNgConsCallArgument(NgToken start, NgToken end, NgExpr e, NgCallArguments a) {
exists(NgToken mid |
e.at(start, mid) and
a = maybeCallArgument(mid, end)
)
} or
TNgVarExpr(NgIdentToken t) or
TNgString(NgStringToken t) or
TNgNumber(NgNumToken t) or
TNgEmpty()
}
private import Parser
/**
* A node in an AngularJS expression that can have dataflow.
*
* Will eventually be a subtype of `DataFlow::Node`.
*/
class NgDataFlowNode extends TNode {
NgAstNode astNode;
NgDataFlowNode() { this = astNode }
/** Gets the AST node this node corresponds to. */
NgAstNode getAstNode() { result = astNode }
/** Gets a textual representation of this element. */
string toString() { result = astNode.toString() }
/**
* Gets a scope object for this node.
*/
AngularJS::AngularScope getAScope() {
exists(NgToken token, NgSource source |
astNode.at(token, _) and
token.at(source, _)
|
result.mayApplyTo(source.getProvider().getEnclosingElement())
)
}
}
/** Holds if everything in the given file should be considered part of an AngularJS app. */
private predicate fileIsImplicitlyAngularJS(HTML::HtmlFile file) {
// The file contains ng-* attributes.
exists(HTML::Attribute attrib |
attrib.getName().regexpMatch("ng-.*") and
attrib.getFile() = file
) and
// But does not contain the ng-app root element, implying that file is
// included from elsewhere.
not exists(HTML::Attribute attrib |
attrib.getName() = "ng-app" and
attrib.getFile() = file
)
}
/** Holds if `element` is under a `ng-non-bindable` directive, disabling interpretation by AngularJS. */
private predicate isNonBindable(HTML::Element element) {
exists(element.getParent*().getAttributeByName("ng-non-bindable"))
}
/**
* Holds if the contents and attribute values of the given element are interpreted by AngularJS,
* that is, any placeholder expressions therein, such as `{{x}}`, are evaluated and inserted in the output.
*/
predicate isInterpretedByAngularJS(HTML::Element element) {
(
fileIsImplicitlyAngularJS(element.getFile())
or
exists(element.getParent*().getAttributeByName("ng-app"))
) and
not isNonBindable(element) and
not element.getName() = "script" // script tags are never interpreted
}

View File

@@ -0,0 +1,190 @@
/**
* Provides classes for working with the AngularJS `$injector` methods.
*
* INTERNAL: Do not import this module directly, import `AngularJS` instead.
*
* NOTE: The API of this library is not stable yet and may change in
* the future.
*/
import javascript
private import AngularJS
private import ServiceDefinitions
/**
* Holds if `nd` is an `angular.injector()` value
*/
private DataFlow::CallNode angularInjector() { result = angular().getAMemberCall("injector") }
/**
* A call to `$angular.injector().invoke(...)`
*/
class InjectorInvokeCall extends DataFlow::CallNode, DependencyInjection {
InjectorInvokeCall() { this = angularInjector().getAMemberCall("invoke") }
override DataFlow::Node getAnInjectableFunction() { result = getArgument(0) }
}
/**
* Base class for expressions that dependency-inject some of their input with AngularJS dependency injection services.
*/
abstract class DependencyInjection extends DataFlow::ValueNode {
/**
* Gets a node that will be dependency-injected.
*/
abstract DataFlow::Node getAnInjectableFunction();
}
/**
* An injectable function, that is, a function that could have its dependency
* parameters automatically provided by the AngularJS `$inject` service.
*/
abstract class InjectableFunction extends DataFlow::ValueNode {
/** Gets the parameter corresponding to dependency `name`. */
abstract Parameter getDependencyParameter(string name);
/**
* Gets the `i`th dependency declaration, which is also named `name`.
*/
abstract ASTNode getDependencyDeclaration(int i, string name);
/**
* Gets an ASTNode for the `name` dependency declaration.
*/
ASTNode getADependencyDeclaration(string name) { result = getDependencyDeclaration(_, name) }
/**
* Gets the ASTNode for the `i`th dependency declaration.
*/
ASTNode getDependencyDeclaration(int i) { result = getDependencyDeclaration(i, _) }
/** Gets the function underlying this injectable function. */
abstract Function asFunction();
/** Gets a location where this function is explicitly dependency injected. */
abstract ASTNode getAnExplicitDependencyInjection();
/**
* Gets a service corresponding to the dependency-injected `parameter`.
*/
ServiceReference getAResolvedDependency(Parameter parameter) {
exists(string name, InjectableFunctionServiceRequest request |
this = request.getAnInjectedFunction() and
parameter = getDependencyParameter(name) and
result = request.getAServiceDefinition(name)
)
}
/**
* Gets a Custom service corresponding to the dependency-injected `parameter`.
* (this is a convenience variant of `getAResolvedDependency`)
*/
DataFlow::Node getCustomServiceDependency(Parameter parameter) {
exists(CustomServiceDefinition custom |
custom.getServiceReference() = getAResolvedDependency(parameter) and
result = custom.getAService()
)
}
}
/**
* An injectable function that does not explicitly list its dependencies,
* instead relying on implicit matching by parameter names.
*/
private class FunctionWithImplicitDependencyAnnotation extends InjectableFunction {
override Function astNode;
FunctionWithImplicitDependencyAnnotation() {
this.(DataFlow::FunctionNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) and
not exists(getAPropertyDependencyInjection(astNode))
}
override Parameter getDependencyParameter(string name) {
result = astNode.getParameterByName(name)
}
override Parameter getDependencyDeclaration(int i, string name) {
result.getName() = name and
result = astNode.getParameter(i)
}
override Function asFunction() { result = astNode }
override ASTNode getAnExplicitDependencyInjection() { none() }
}
private DataFlow::PropWrite getAPropertyDependencyInjection(Function function) {
exists(DataFlow::FunctionNode ltf |
ltf.getAstNode() = function and
result = ltf.getAPropertyWrite("$inject")
)
}
/**
* An injectable function with an `$inject` property that lists its
* dependencies.
*/
private class FunctionWithInjectProperty extends InjectableFunction {
override Function astNode;
DataFlow::ArrayCreationNode dependencies;
FunctionWithInjectProperty() {
(
this.(DataFlow::FunctionNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) or
exists(FunctionWithExplicitDependencyAnnotation f | f.asFunction() = astNode)
) and
exists(DataFlow::PropWrite pwn |
pwn = getAPropertyDependencyInjection(astNode) and
pwn.getRhs().getALocalSource() = dependencies
)
}
override Parameter getDependencyParameter(string name) {
exists(int i | exists(getDependencyDeclaration(i, name)) | result = astNode.getParameter(i))
}
override ASTNode getDependencyDeclaration(int i, string name) {
exists(DataFlow::ValueNode decl |
decl = dependencies.getElement(i) and
decl.mayHaveStringValue(name) and
result = decl.getAstNode()
)
}
override Function asFunction() { result = astNode }
override ASTNode getAnExplicitDependencyInjection() {
result = getAPropertyDependencyInjection(astNode).getAstNode()
}
}
/**
* An injectable function embedded in an array of dependencies.
*/
private class FunctionWithExplicitDependencyAnnotation extends InjectableFunction {
DataFlow::FunctionNode function;
override ArrayExpr astNode;
FunctionWithExplicitDependencyAnnotation() {
this.(DataFlow::SourceNode).flowsTo(any(DependencyInjection d).getAnInjectableFunction()) and
function.flowsToExpr(astNode.getElement(astNode.getSize() - 1))
}
override Parameter getDependencyParameter(string name) {
exists(int i | astNode.getElement(i).mayHaveStringValue(name) |
result = asFunction().getParameter(i)
)
}
override ASTNode getDependencyDeclaration(int i, string name) {
result = astNode.getElement(i) and
result.(Expr).mayHaveStringValue(name)
}
override Function asFunction() { result = function.getAstNode() }
override ASTNode getAnExplicitDependencyInjection() {
result = astNode or
result = function.(InjectableFunction).getAnExplicitDependencyInjection()
}
}

View File

@@ -0,0 +1,678 @@
/**
* Provides classes for working with the definitions of AngularJS services.
*
* Supports registration and lookup of AngularJS services:
*
* - dependency injection services, such as `factory` and `provider`
* - special AngularJS services, such as `filter` and `controller`
*
* INTERNAL: Do not import this module directly, import `AngularJS` instead.
*
* NOTE: The API of this library is not stable yet and may change in
* the future.
*/
import javascript
private import AngularJS
/**
* A reference to a service.
*/
private newtype TServiceReference =
MkBuiltinServiceReference(string name) { exists(getBuiltinKind(name)) } or
MkCustomServiceReference(CustomServiceDefinition service)
/**
* A reference to a service.
*/
abstract class ServiceReference extends TServiceReference {
/** Gets a textual representation of this element. */
string toString() { result = getName() }
/**
* Gets the name of this reference.
*/
abstract string getName();
/**
* Gets a data flow node that may refer to this service.
*/
DataFlow::SourceNode getAReference() {
result = DataFlow::parameterNode(any(ServiceRequest request).getDependencyParameter(this))
}
/**
* Gets an access to the referenced service.
*/
Expr getAnAccess() {
result.mayReferToParameter(any(ServiceRequest request).getDependencyParameter(this))
}
/**
* Gets a call that invokes the referenced service.
*/
CallExpr getACall() { result.getCallee() = getAnAccess() }
/**
* Gets a method call that invokes method `methodName` on the referenced service.
*/
MethodCallExpr getAMethodCall(string methodName) {
result.getReceiver() = getAnAccess() and
result.getMethodName() = methodName
}
/**
* Gets an access to property `propertyName` on the referenced service.
*/
DataFlow::PropRef getAPropertyAccess(string propertyName) {
result.getBase().asExpr() = getAnAccess() and
result.getPropertyName() = propertyName
}
/**
* Holds if the service is available for dependency injection.
*/
abstract predicate isInjectable();
}
/**
* A reference to a builtin service.
*/
class BuiltinServiceReference extends ServiceReference, MkBuiltinServiceReference {
override string getName() { this = MkBuiltinServiceReference(result) }
override predicate isInjectable() { any() }
}
/**
* Holds if `ref` is a reference to the builtin service with the name `serviceName`.
*
* Note that `BuiltinServiceReference.getAnAccess` should be used instead of this predicate when possible (they are semantically equivalent for builtin services).
* This predicate can avoid the non-monotonic recursion that `getAnAccess` can cause.
*/
DataFlow::ParameterNode builtinServiceRef(string serviceName) {
exists(InjectableFunction f, BuiltinServiceReference service |
service.getName() = serviceName and
result = DataFlow::parameterNode(f.getDependencyParameter(serviceName))
)
}
/**
* A reference to a custom service.
*/
class CustomServiceReference extends ServiceReference, MkCustomServiceReference {
CustomServiceDefinition def;
CustomServiceReference() { this = MkCustomServiceReference(def) }
override string getName() { result = def.getName() }
override predicate isInjectable() { def instanceof RecipeDefinition }
}
/**
* Gets the kind of the builtin "service" named `name`.
*
* The possible kinds are:
* - controller-only: services that are only available to controllers.
* - provider: a provider for a service.
* - service: a service.
* - type: a special builtin service that is usable everywhere.
*/
private string getBuiltinKind(string name) {
// according to https://docs.angularjs.org/api
result = "controller-only" and name = "$scope"
or
result = "service" and
(
// ng
name = "$anchorScroll" or
name = "$animate" or
name = "$animateCss" or
name = "$cacheFactory" or
name = "$controller" or
name = "$document" or
name = "$exceptionHandler" or
name = "$filter" or
name = "$http" or
name = "$httpBackend" or
name = "$httpParamSerializer" or
name = "$httpParamSerializerJQLike" or
name = "$interpolate" or
name = "$interval" or
name = "$jsonpCallbacks" or
name = "$locale" or
name = "$location" or
name = "$log" or
name = "$parse" or
name = "$q" or
name = "$rootElement" or
name = "$rootScope" or
name = "$sce" or
name = "$sceDelegate" or
name = "$templateCache" or
name = "$templateRequest" or
name = "$timeout" or
name = "$window" or
name = "$xhrFactory" or
// auto
name = "$injector" or
name = "$provide" or
// ngAnimate
name = "$animate" or
name = "$animateCss" or
// ngAria
name = "$aria" or
// ngComponentRouter
name = "$rootRouter" or
name = "$routerRootComponent" or
// ngCookies
name = "$cookieStore" or
name = "$cookies" or
//ngMock
name = "$animate" or
name = "$componentController" or
name = "$controller" or
name = "$exceptionHandler" or
name = "$httpBackend" or
name = "$interval" or
name = "$log" or
name = "$timeout" or
//ngMockE2E
name = "$httpBackend" or
// ngResource
name = "$resource" or
// ngRoute
name = "$route" or
name = "$routeParams" or
// ngSanitize
name = "$sanitize" or
// ngTouch
name = "$swipe"
)
or
result = "provider" and
(
// ng
name = "$anchorScrollProvider" or
name = "$animateProvider" or
name = "$compileProvider" or
name = "$controllerProvider" or
name = "$filterProvider" or
name = "$httpProvider" or
name = "$interpolateProvider" or
name = "$locationProvider" or
name = "$logProvider" or
name = "$parseProvider" or
name = "$provider" or
name = "$qProvider" or
name = "$rootScopeProvider" or
name = "$sceDelegateProvider" or
name = "$sceProvider" or
name = "$templateRequestProvider" or
// ngAria
name = "$ariaProvider" or
// ngCookies
name = "$cookiesProvider" or
// ngmock
name = "$exceptionHandlerProvider" or
// ngResource
name = "$resourceProvider" or
// ngRoute
name = "$routeProvider" or
// ngSanitize
name = "$sanitizeProvider"
)
or
result = "type" and
(
// ng
name = "$cacheFactory" or
name = "$compile" or
name = "$rootScope" or
// ngMock
name = "$rootScope"
)
}
/**
* A custom AngularJS service, defined through `$provide.service`,
* `module.controller` or a similar method.
*/
abstract class CustomServiceDefinition extends DataFlow::Node {
/** Gets a factory function used to create the defined service. */
abstract DataFlow::SourceNode getAFactoryFunction();
/** Gets a service defined by this definition. */
abstract DataFlow::SourceNode getAService();
/** Gets the name of the service defined by this definition. */
abstract string getName();
/** Gets the reference to the defined service. */
ServiceReference getServiceReference() { result = MkCustomServiceReference(this) }
}
/**
* A definition of a custom AngularJS dependency injection service using a "recipe".
*/
abstract class RecipeDefinition extends DataFlow::CallNode, CustomServiceDefinition,
DependencyInjection {
string methodName;
string name;
RecipeDefinition() {
(
this = moduleRef(_).getAMethodCall(methodName) or
this = builtinServiceRef("$provide").getAMethodCall(methodName)
) and
getArgument(0).asExpr().mayHaveStringValue(name)
}
override string getName() { result = name }
override DataFlow::SourceNode getAFactoryFunction() { result.flowsTo(getArgument(1)) }
override DataFlow::Node getAnInjectableFunction() {
methodName != "value" and
methodName != "constant" and
result = getAFactoryFunction()
}
}
/**
* A custom special AngularJS service, defined through
* `$controllerProvider.register`, `module.filter` or a similar method.
*
* Special services are those that have a meaning outside the AngularJS
* dependency injection system.
* This includes, filters (used in AngularJS expressions) and controllers
* (used through `ng-controller` directives).
*/
abstract private class CustomSpecialServiceDefinition extends CustomServiceDefinition,
DependencyInjection {
override DataFlow::Node getAnInjectableFunction() { result = getAFactoryFunction() }
}
/**
* Holds if `mce` defines a service of type `moduleMethodName` with name `serviceName` using the `factoryFunction` as the factory function.
*/
bindingset[moduleMethodName]
private predicate isCustomServiceDefinitionOnModule(
DataFlow::CallNode mce, string moduleMethodName, string serviceName,
DataFlow::Node factoryArgument
) {
mce = moduleRef(_).getAMethodCall(moduleMethodName) and
mce.getArgument(0).asExpr().mayHaveStringValue(serviceName) and
factoryArgument = mce.getArgument(1)
}
pragma[inline]
private predicate isCustomServiceDefinitionOnProvider(
DataFlow::CallNode mce, string providerName, string providerMethodName, string serviceName,
DataFlow::Node factoryArgument
) {
mce = builtinServiceRef(providerName).getAMethodCall(providerMethodName) and
(
mce.getNumArgument() = 1 and
factoryArgument = mce.getOptionArgument(0, serviceName)
or
mce.getNumArgument() = 2 and
mce.getArgument(0).asExpr().mayHaveStringValue(serviceName) and
factoryArgument = mce.getArgument(1)
)
}
/**
* A controller defined with `module.controller` or `$controllerProvider.register`.
*/
class ControllerDefinition extends CustomSpecialServiceDefinition {
string name;
DataFlow::Node factoryFunction;
ControllerDefinition() {
isCustomServiceDefinitionOnModule(this, "controller", name, factoryFunction) or
isCustomServiceDefinitionOnProvider(this, "$controllerProvider", "register", name,
factoryFunction)
}
override string getName() { result = name }
override DataFlow::SourceNode getAService() { result = factoryFunction.getALocalSource() }
override DataFlow::SourceNode getAFactoryFunction() { result = factoryFunction.getALocalSource() }
}
/**
* A filter defined with `module.filter` or `$filterProvider.register`.
*/
class FilterDefinition extends CustomSpecialServiceDefinition {
string name;
DataFlow::Node factoryFunction;
FilterDefinition() {
isCustomServiceDefinitionOnModule(this, "filter", name, factoryFunction) or
isCustomServiceDefinitionOnProvider(this, "$filterProvider", "register", name, factoryFunction)
}
override string getName() { result = name }
override DataFlow::SourceNode getAService() {
exists(InjectableFunction f |
f = factoryFunction.getALocalSource() and
result.flowsToExpr(f.asFunction().getAReturnedExpr())
)
}
override DataFlow::SourceNode getAFactoryFunction() { result = factoryFunction.getALocalSource() }
}
/**
* A directive defined with `module.directive` or `$compileProvider.directive`.
*/
class DirectiveDefinition extends CustomSpecialServiceDefinition {
string name;
DataFlow::Node factoryFunction;
DirectiveDefinition() {
isCustomServiceDefinitionOnModule(this, "directive", name, factoryFunction) or
isCustomServiceDefinitionOnProvider(this, "$compileProvider", "directive", name, factoryFunction)
}
override string getName() { result = name }
override DataFlow::SourceNode getAService() {
exists(CustomDirective d |
d.getDefinition() = this and
result = d.getAnInstantiation()
)
}
override DataFlow::SourceNode getAFactoryFunction() { result = factoryFunction.getALocalSource() }
}
private class CustomDirectiveControllerDependencyInjection extends DependencyInjection {
CustomDirectiveControllerDependencyInjection() {
this instanceof DirectiveDefinition or
this instanceof ComponentDefinition
}
override DataFlow::Node getAnInjectableFunction() {
exists(CustomDirective d |
d.getDefinition() = this and
// Note that `.getController` cannot be used here, since that involves a cast to InjectableFunction, and that cast only succeeds because of this method
result = d.getMember("controller")
)
}
}
/**
* A component defined with `module.component` or `$compileProvider.component`.
*/
class ComponentDefinition extends CustomSpecialServiceDefinition {
string name;
DataFlow::Node config;
ComponentDefinition() {
isCustomServiceDefinitionOnModule(this, "component", name, config) or
isCustomServiceDefinitionOnProvider(this, "$compileProvider", "component", name, config)
}
override string getName() { result = name }
override DataFlow::SourceNode getAService() {
exists(CustomDirective d |
d.getDefinition() = this and
result = d.getAnInstantiation()
)
}
override DataFlow::SourceNode getAFactoryFunction() { none() }
/** Gets the configuration object for the defined component. */
DataFlow::SourceNode getConfig() { result = config.getALocalSource() }
}
/**
* An animation defined with `module.animation` or `$animationProvider.register`.
*/
class AnimationDefinition extends CustomSpecialServiceDefinition {
string name;
DataFlow::Node factoryFunction;
AnimationDefinition() {
isCustomServiceDefinitionOnModule(this, "animation", name, factoryFunction) or
isCustomServiceDefinitionOnProvider(this, "$animateProvider", "register", name, factoryFunction)
}
override string getName() { result = name }
override DataFlow::SourceNode getAService() {
exists(InjectableFunction f |
f = factoryFunction.getALocalSource() and
result.flowsToExpr(f.asFunction().getAReturnedExpr())
)
}
override DataFlow::SourceNode getAFactoryFunction() { result = factoryFunction.getALocalSource() }
}
/**
* Gets a builtin service with a specific kind.
*/
BuiltinServiceReference getBuiltinServiceOfKind(string kind) {
exists(string name |
kind = getBuiltinKind(name) and
result = MkBuiltinServiceReference(name)
)
}
/**
* A request for one or more AngularJS services.
*/
abstract class ServiceRequest extends Expr {
/**
* Gets the parameter of this request into which `service` is injected.
*/
abstract Parameter getDependencyParameter(ServiceReference service);
}
/**
* The request for a scope service in the form of the link-function of a directive.
*/
private class LinkFunctionWithScopeInjection extends ServiceRequest {
LinkFunctionWithScopeInjection() { this instanceof LinkFunction }
override Parameter getDependencyParameter(ServiceReference service) {
service instanceof ScopeServiceReference and
result = this.(LinkFunction).getScopeParameter()
}
}
/**
* A request for a service, in the form of a dependency-injected function.
*/
class InjectableFunctionServiceRequest extends ServiceRequest {
InjectableFunction injectedFunction;
InjectableFunctionServiceRequest() { injectedFunction.getAstNode() = this }
/**
* Gets the function of this request.
*/
InjectableFunction getAnInjectedFunction() { result = injectedFunction }
/**
* Gets a name of a requested service.
*/
string getAServiceName() { exists(getAnInjectedFunction().getADependencyDeclaration(result)) }
/**
* Gets a service with the specified name, relative to this request.
* (implementation detail: all services are in the global namespace)
*/
ServiceReference getAServiceDefinition(string name) {
result.getName() = name and
result.isInjectable()
}
override Parameter getDependencyParameter(ServiceReference service) {
service = injectedFunction.getAResolvedDependency(result)
}
}
private DataFlow::SourceNode getFactoryFunctionResult(RecipeDefinition def) {
exists(Function factoryFunction, InjectableFunction f |
f = def.getAFactoryFunction() and
factoryFunction = f.asFunction() and
result.flowsToExpr(factoryFunction.getAReturnedExpr())
)
}
/**
* An AngularJS factory recipe definition, that is, a method call of the form
* `module.factory("name", f)`.
*/
class FactoryRecipeDefinition extends RecipeDefinition {
FactoryRecipeDefinition() { methodName = "factory" }
override DataFlow::SourceNode getAService() {
/*
* The Factory recipe constructs a new service using a function
* with zero or more arguments (these are dependencies on other
* services). The return value of this function is the service
* instance created by this recipe.
*/
result = getFactoryFunctionResult(this)
}
}
/**
* An AngularJS decorator recipe definition, that is, a method call of the form
* `module.decorator("name", f)`.
*/
class DecoratorRecipeDefinition extends RecipeDefinition {
DecoratorRecipeDefinition() { methodName = "decorator" }
override DataFlow::SourceNode getAService() {
/*
* The return value of the function provided to the decorator
* will take place of the service, directive, or filter being
* decorated.
*/
result = getFactoryFunctionResult(this)
}
}
/**
* An AngularJS service recipe definition, that is, a method call of the form
* `module.service("name", f)`.
*/
class ServiceRecipeDefinition extends RecipeDefinition {
ServiceRecipeDefinition() { methodName = "service" }
override DataFlow::SourceNode getAService() {
/*
* The service recipe produces a service just like the Value or
* Factory recipes, but it does so by invoking a constructor with
* the new operator. The constructor can take zero or more
* arguments, which represent dependencies needed by the instance
* of this type.
*/
exists(InjectableFunction f |
f = getAFactoryFunction() and
result.getAstNode() = f.asFunction()
)
}
}
/**
* An AngularJS value recipe definition, that is, a method call of the form
* `module.value("name", value)`.
*/
class ValueRecipeDefinition extends RecipeDefinition {
ValueRecipeDefinition() { methodName = "value" }
override DataFlow::SourceNode getAService() { result = getAFactoryFunction() }
}
/**
* An AngularJS constant recipe definition, that is, a method call of the form
* `module.constant("name", "constant value")`.
*/
class ConstantRecipeDefinition extends RecipeDefinition {
ConstantRecipeDefinition() { methodName = "constant" }
override DataFlow::SourceNode getAService() { result = getAFactoryFunction() }
}
/**
* An AngularJS provider recipe definition, that is, a method call of the form
* `module.provider("name", fun)`.
*/
class ProviderRecipeDefinition extends RecipeDefinition {
ProviderRecipeDefinition() { methodName = "provider" }
override string getName() { result = name or result = name + "Provider" }
override DataFlow::SourceNode getAService() {
/*
* The Provider recipe is syntactically defined as a custom type
* that implements a $get method. This method is a factory function
* just like the one we use in the Factory recipe. In fact, if you
* define a Factory recipe, an empty Provider type with the $get
* method set to your factory function is automatically created
* under the hood.
*/
exists(DataFlow::ThisNode thiz, InjectableFunction f |
f = getAFactoryFunction() and
thiz.getBinder().getFunction() = f.asFunction() and
result = thiz.getAPropertySource("$get")
)
}
}
private class ProviderRecipeServiceInjection extends DependencyInjection {
ProviderRecipeServiceInjection() { this instanceof ProviderRecipeDefinition }
override DataFlow::Node getAnInjectableFunction() {
result = this.(ProviderRecipeDefinition).getAService()
}
}
/**
* An AngularJS config method definition, that is, a method call of the form
* `module.config(fun)`.
*/
class ConfigMethodDefinition extends ModuleApiCall {
ConfigMethodDefinition() { methodName = "config" }
/**
* Gets a provided configuration method.
*/
InjectableFunction getConfigMethod() { result.(DataFlow::SourceNode).flowsTo(getArgument(0)) }
}
/**
* An AngularJS run method definition, that is, a method call of the form
* `module.run(fun)`.
*/
class RunMethodDefinition extends ModuleApiCall {
RunMethodDefinition() { methodName = "run" }
/**
* Gets a provided run method.
*/
InjectableFunction getRunMethod() { result.(DataFlow::SourceNode).flowsTo(getArgument(0)) }
}
/**
* The `$scope` or `$rootScope` service.
*/
class ScopeServiceReference extends BuiltinServiceReference {
ScopeServiceReference() { getName() = "$scope" or getName() = "$rootScope" }
}