mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Packaging: Rafactor Javascript core libraries
Extract the external facing `qll` files into the codeql/javascript-all query pack.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
Reference in New Issue
Block a user