Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1ab36dc

Browse files
committed
JS: Flow through *ngFor loops
1 parent 29dd847 commit 1ab36dc

5 files changed

Lines changed: 87 additions & 6 deletions

File tree

javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.nio.file.Path;
5+
import java.util.regex.Matcher;
56
import java.util.regex.Pattern;
67

78
import com.semmle.extractor.html.HtmlPopulator;
@@ -106,14 +107,22 @@ public void handleElement(Element elt) {
106107
false /* isTypeScript */);
107108
} else if (isAngularTemplateAttributeName(attr.getName())) {
108109
// For an attribute *ngFor="let var of EXPR", start parsing at EXPR
109-
int offset = attr.getName().equals("*ngFor") ? source.indexOf(" of ") + " of ".length() : 0;
110+
int offset = 0;
111+
if (attr.getName().equals("*ngFor")) {
112+
Matcher m = ANGULAR_FOR_LOOP_DECL.matcher(source);
113+
if (m.matches()) {
114+
String expr = m.group(2);
115+
offset = m.end(2) - expr.length();
116+
source = expr;
117+
}
118+
}
110119
snippetLoC =
111120
extractSnippet(
112121
TopLevelKind.eventHandler,
113122
config.withSourceType(SourceType.ANGULAR_TEMPLATE),
114123
scopeManager,
115124
textualExtractor,
116-
source.substring(offset),
125+
source,
117126
valueStart.getRow(),
118127
valueStart.getColumn() + offset,
119128
false /* isTypeScript */);
@@ -147,6 +156,8 @@ private boolean isAngularTemplateAttributeName(String name) {
147156
name.startsWith("*ng");
148157
}
149158

159+
private static final Pattern ANGULAR_FOR_LOOP_DECL = Pattern.compile("^ *let +(\\w+) +of(?: +|(?!\\w))(.*)");
160+
150161
/** List of HTML attributes whose value is interpreted as JavaScript. */
151162
private static final Pattern JS_ATTRIBUTE =
152163
Pattern.compile(

javascript/ql/src/semmle/javascript/frameworks/Angular2.qll

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ module Angular2 {
274274
result.getName() = name
275275
}
276276

277+
private DataFlow::Node getAttributeValueAsNode(HTML::Attribute attrib) {
278+
result = attrib.getCodeInAttribute().getChildStmt(0).(ExprStmt).getExpr().flow()
279+
}
280+
277281
/**
278282
* The class for an Angular component.
279283
*/
@@ -333,7 +337,7 @@ module Angular2 {
333337

334338
/** Gets an argument that flows into the `name` field of this component. */
335339
DataFlow::Node getATemplateArgument(string name) {
336-
result = getATemplateInstantiation().getAttributeByName("[" + name + "]").getCodeInAttribute().getChildStmt(0).(ExprStmt).getExpr().flow()
340+
result = getAttributeValueAsNode(getATemplateInstantiation().getAttributeByName("[" + name + "]"))
337341
}
338342

339343
/** Gets the `templateUrl` property of the `@Component` decorator. */
@@ -415,4 +419,57 @@ module Angular2 {
415419
)
416420
}
417421
}
422+
423+
/**
424+
* An attribute of form `*ngFor="let var of EXPR"`.
425+
*
426+
* The `EXPR` has been extracted as the sole `CodeInAttribute` top-level for this
427+
* attribute. There is no AST node for the implied for-of loop.
428+
*/
429+
private class ForLoopAttribute extends HTML::Attribute {
430+
ForLoopAttribute() {
431+
getName() = "*ngFor"
432+
}
433+
434+
/** Gets a data-flow node holding the value being iterated over. */
435+
DataFlow::Node getIterationDomain() {
436+
result = getAttributeValueAsNode(this)
437+
}
438+
439+
/** Gets the name of the variable holding the element of the current iteration. */
440+
string getIteratorName() {
441+
result = getValue().regexpCapture("^ *let (\\w+) .*", 1)
442+
}
443+
444+
/** Gets an HTML element in which the iterator variable is in scope. */
445+
HTML::Element getAnElementInScope() {
446+
result.getParent*() = getElement()
447+
}
448+
449+
/** Gets a reference to the iterator variable. */
450+
DataFlow::Node getAnIteratorAccess() {
451+
exists(HTML::Attribute attrib |
452+
attrib = getAnElementInScope().getAnAttribute() and
453+
isAngularExpressionAttribute(attrib) and
454+
result = getAGlobalVarAccessInAttribute(attrib.getCodeInAttribute(), getIteratorName()).flow()
455+
)
456+
}
457+
}
458+
459+
/**
460+
* A taint step `array -> elem` in `*ngFor="let elem of array"`, or more precisely,
461+
* a step from `array` to each access to `elem`.
462+
*/
463+
private class ForLoopStep extends TaintTracking::AdditionalTaintStep {
464+
ForLoopAttribute attrib;
465+
466+
ForLoopStep() {
467+
this = attrib.getIterationDomain()
468+
}
469+
470+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
471+
pred = this and
472+
succ = attrib.getAnIteratorAccess()
473+
}
474+
}
418475
}

javascript/ql/test/library-tests/frameworks/Angular2/foo.component.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@
55
[prop4]="foo | testPipe:'safe'"
66
[prop5]="42 | testPipe:foo"
77
></other-component>
8+
9+
<div *ngFor="let element of taintedArray">
10+
<other-component [prop1]="element"></other-component>
11+
</div>
12+
13+
<div *ngFor="let element of safeArray">
14+
<other-component [prop2]="element"></other-component>
15+
</div>

javascript/ql/test/library-tests/frameworks/Angular2/foo.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import { Component } from "@angular/core";
66
})
77
export class Foo {
88
foo: string;
9+
taintedArray: string[];
10+
safeArray: string[];
911

1012
constructor() {
1113
this.foo = source();
14+
this.taintedArray = [...source()];
15+
this.safeArray = ['a', 'b'];
1216
}
1317
}

javascript/ql/test/library-tests/frameworks/Angular2/test.expected

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pipeClassRef
2222
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | foo.component.html:5:20:5:27 | testPipe |
2323
| TestPipe.ts:4:8:9:1 | class T ... ;\\n }\\n} | foo.component.html:6:19:6:26 | testPipe |
2424
taintFlow
25-
| foo.component.ts:11:20:11:27 | source() | other.component.ts:18:48:18:57 | this.prop1 |
26-
| foo.component.ts:11:20:11:27 | source() | other.component.ts:21:48:21:57 | this.prop4 |
27-
| foo.component.ts:11:20:11:27 | source() | other.component.ts:22:48:22:57 | this.prop5 |
25+
| foo.component.ts:13:20:13:27 | source() | other.component.ts:18:48:18:57 | this.prop1 |
26+
| foo.component.ts:13:20:13:27 | source() | other.component.ts:21:48:21:57 | this.prop4 |
27+
| foo.component.ts:13:20:13:27 | source() | other.component.ts:22:48:22:57 | this.prop5 |
28+
| foo.component.ts:14:33:14:40 | source() | other.component.ts:18:48:18:57 | this.prop1 |

0 commit comments

Comments
 (0)