Skip to content

Commit 13aa511

Browse files
committed
JS: Support TemplatePlaceholderTag.getEnclosingExpr
fixup! makeLocation
1 parent 0a14de1 commit 13aa511

9 files changed

Lines changed: 80 additions & 44 deletions

File tree

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import java.util.Set;
88
import java.util.Stack;
9+
import java.util.regex.Matcher;
910

1011
import com.semmle.js.ast.AClass;
1112
import com.semmle.js.ast.AFunction;
@@ -570,6 +571,16 @@ public Label visit(Literal nd, Context c) {
570571
regexpExtractor.extract(source.substring(1, source.lastIndexOf('/')), offsets, nd, false);
571572
} else if (nd.isStringLiteral() && !c.isInsideType() && nd.getRaw().length() < 1000) {
572573
regexpExtractor.extract(valueString, makeStringLiteralOffsets(nd.getRaw()), nd, true);
574+
575+
// Scan the string for template tags, if we're in a context where such tags are relevant.
576+
if (scopeManager.isInTemplateFile()) {
577+
Matcher m = TemplateEngines.TEMPLATE_TAGS.matcher(nd.getRaw());
578+
int offset = nd.getLoc().getStart().getOffset();
579+
while (m.find()) {
580+
Label locationLbl = TemplateEngines.makeLocation(lexicalExtractor.getTextualExtractor(), offset + m.start(), offset + m.end());
581+
trapwriter.addTuple("expr_contains_template_tag_location", key, locationLbl);
582+
}
583+
}
573584
}
574585
return key;
575586
}
@@ -2225,7 +2236,8 @@ public Label visit(AngularPipeRef nd, Context c) {
22252236
@Override
22262237
public Label visit(GeneratedCodeExpr nd, Context c) {
22272238
Label key = super.visit(nd, c);
2228-
trapwriter.addTuple("generated_code_expr_info", key, nd.getOpeningDelimiter(), nd.getClosingDelimiter(), nd.getBody());
2239+
Label templateLbl = TemplateEngines.makeLocation(lexicalExtractor.getTextualExtractor(), nd.getLoc().getStart().getOffset(), nd.getLoc().getEnd().getOffset());
2240+
trapwriter.addTuple("expr_contains_template_tag_location", key, templateLbl);
22292241
return key;
22302242
}
22312243
}

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

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public JavaScriptHTMLElementHandler(TextualExtractor textualExtractor) {
3939
this.textualExtractor = textualExtractor;
4040

4141
this.scopeManager =
42-
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion());
42+
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), true);
4343
}
4444

4545
/*
@@ -382,21 +382,6 @@ private void emitTopLevelXmlNodeBinding(
382382
writer.addTuple("toplevel_parent_xml_node", topLevelLabel, htmlNodeLabel);
383383
}
384384

385-
private static final String MUSTACHE_TAG_DOUBLE = "\\{\\{(?!\\{)(.*?)\\}\\}"; // {{ x }}
386-
private static final String MUSTACHE_TAG_TRIPLE = "\\{\\{\\{(.*?)\\}\\}\\}"; // {{{ x }}}
387-
private static final String MUSTACHE_TAG_PERCENT = "\\{%(?!>)(.*?)%\\}"; // {% x %}
388-
private static final String EJS_TAG = "<%(?![%<>}])[-=]?(.*?)[_-]?%>"; // <% x %>
389-
390-
/** Pattern for a template tag whose contents should be parsed as an expression */
391-
private static final Pattern TEMPLATE_EXPR_OPENING_TAG =
392-
Pattern.compile("^(?:\\{\\{\\{?|<%[-=])"); // {{, {{{, <%=, <%-
393-
394-
private static final Pattern TEMPLATE_TAGS =
395-
Pattern.compile(
396-
StringUtil.glue(
397-
"|", MUSTACHE_TAG_DOUBLE, MUSTACHE_TAG_TRIPLE, MUSTACHE_TAG_PERCENT, EJS_TAG),
398-
Pattern.DOTALL);
399-
400385
private void extractTemplateTags(
401386
TextualExtractor textualExtractor,
402387
ScopeManager scopeManager,
@@ -407,9 +392,8 @@ private void extractTemplateTags(
407392
if (start >= end) return;
408393
if (isEmbedded) return; // Do not extract template tags for HTML snippets embedded in a JS file
409394

410-
LocationManager locationManager = textualExtractor.getLocationManager();
411395
TrapWriter trapwriter = textualExtractor.getTrapwriter();
412-
Matcher m = TEMPLATE_TAGS.matcher(textualExtractor.getSource()).region(start, end);
396+
Matcher m = TemplateEngines.TEMPLATE_TAGS.matcher(textualExtractor.getSource()).region(start, end);
413397
while (m.find()) {
414398
int startOffset = m.start();
415399
int endOffset = m.end();
@@ -424,15 +408,12 @@ private void extractTemplateTags(
424408
String rawText = m.group();
425409
trapwriter.addTuple("template_placeholder_tag_info", lbl, parentLabel.get(), rawText);
426410

427-
// Emit location
428-
Position startPos = textualExtractor.getSourceMap().getStart(startOffset);
429-
Position endPos = textualExtractor.getSourceMap().getEnd(endOffset - 1);
430-
int endColumn = endPos.getColumn() - 1; // Convert to inclusive end position (still 1-based)
431-
locationManager.emitFileLocation(
432-
lbl, startPos.getLine(), startPos.getColumn(), endPos.getLine(), endColumn);
411+
// Emit location entity
412+
Label locationLbl = TemplateEngines.makeLocation(textualExtractor, startOffset, endOffset);
413+
trapwriter.addTuple("hasLocation", lbl, locationLbl);
433414

434415
// Parse the contents as a template expression, if the delimiter expects an expression.
435-
Matcher delimMatcher = TEMPLATE_EXPR_OPENING_TAG.matcher(rawText);
416+
Matcher delimMatcher = TemplateEngines.TEMPLATE_EXPR_OPENING_TAG.matcher(rawText);
436417
if (delimMatcher.find()) {
437418
// The body of the template tag is stored in the first capture group of each pattern
438419
int bodyGroup = getNonNullCaptureGroup(m);

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,22 @@ public Label lookupNamespace(String name) {
103103
private final ECMAVersion ecmaVersion;
104104
private final Set<String> implicitGlobals = new LinkedHashSet<String>();
105105
private Scope implicitVariableScope;
106+
private final boolean isInTemplateScope;
106107

107-
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion) {
108+
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, boolean isInTemplateScope) {
108109
this.trapWriter = trapWriter;
109110
this.toplevelScope = enterScope(ScopeKind.GLOBAL, trapWriter.globalID("global_scope"), null);
110111
this.ecmaVersion = ecmaVersion;
111112
this.implicitVariableScope = toplevelScope;
113+
this.isInTemplateScope = isInTemplateScope;
114+
}
115+
116+
/**
117+
* Returns true the current scope is potentially in a template file, and may contain
118+
* relevant template tags.
119+
*/
120+
public boolean isInTemplateFile() {
121+
return isInTemplateScope;
112122
}
113123

114124
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public LoCInfo extract(TextualExtractor textualExtractor) {
7777
}
7878

7979
ScopeManager scopeManager =
80-
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion());
80+
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), false);
8181
Label toplevelLabel = null;
8282
LoCInfo loc;
8383
try {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.semmle.js.extractor;
2+
3+
import java.util.regex.Pattern;
4+
5+
import com.semmle.util.data.StringUtil;
6+
import com.semmle.util.locations.Position;
7+
import com.semmle.util.trap.TrapWriter.Label;
8+
9+
public class TemplateEngines {
10+
private static final String MUSTACHE_TAG_TRIPLE = "\\{\\{\\{(.*?)\\}\\}\\}"; // {{{ x }}}
11+
private static final String MUSTACHE_TAG_DOUBLE = "\\{\\{(?!\\{)(.*?)\\}\\}"; // {{ x }}}
12+
private static final String MUSTACHE_TAG_PERCENT = "\\{%(?!>)(.*?)%\\}"; // {% x %}
13+
private static final String EJS_TAG = "<%(?![%<>}])[-=]?(.*?)[_-]?%>"; // <% x %>
14+
15+
/** Pattern for a template tag whose contents should be parsed as an expression */
16+
public static final Pattern TEMPLATE_EXPR_OPENING_TAG =
17+
Pattern.compile("^(?:\\{\\{\\{?|<%[-=])"); // {{, {{{, <%=, <%-
18+
19+
/**
20+
* Pattern matching a template tag from a supported template engine.
21+
*/
22+
public static final Pattern TEMPLATE_TAGS =
23+
Pattern.compile(
24+
StringUtil.glue(
25+
"|", TemplateEngines.MUSTACHE_TAG_DOUBLE, MUSTACHE_TAG_TRIPLE, MUSTACHE_TAG_PERCENT, EJS_TAG),
26+
Pattern.DOTALL);
27+
28+
/**
29+
* Returns the location label for a template tag at the given offsets.
30+
*/
31+
public static Label makeLocation(TextualExtractor extractor, int startOffset, int endOffset) {
32+
Position startPos = extractor.getSourceMap().getStart(startOffset);
33+
Position endPos = extractor.getSourceMap().getEnd(endOffset - 1);
34+
int endColumn = endPos.getColumn() - 1; // Convert to inclusive end position (still 1-based)
35+
return extractor.getLocationManager().emitLocationsDefault(startPos.getLine(), startPos.getColumn(), endPos.getLine(), endColumn);
36+
}
37+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public LoCInfo extract(TextualExtractor textualExtractor) {
2323
File sourceFile = textualExtractor.getExtractedFile();
2424
Result res = state.getTypeScriptParser().parse(sourceFile, source, textualExtractor.getMetrics());
2525
ScopeManager scopeManager =
26-
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017);
26+
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, false);
2727
try {
2828
FileSnippet snippet = state.getSnippets().get(sourceFile.toPath());
2929
SourceType sourceType = snippet != null ? snippet.getSourceType() : jsExtractor.establishSourceType(source, false);

javascript/ql/src/semmle/javascript/Expr.qll

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2893,18 +2893,9 @@ class ImportMetaExpr extends @import_meta_expr, Expr {
28932893
* ```
28942894
*/
28952895
class GeneratedCodeExpr extends @generated_code_expr, Expr {
2896-
/** Gets the opening delimiter, such as `{{` or `{{{`. */
2897-
string getOpeningDelimiter() { generated_code_expr_info(this, result, _, _) }
2898-
2899-
/** Gets the closing delimiter, such as `}}` or `}}}`. */
2900-
string getClosingDelimiter() { generated_code_expr_info(this, _, result, _) }
2901-
2902-
/** Gets the text between the delimiters, including any surrounding whitespace, such as the `x` in `{{x}}`. */
2903-
string getBody() { generated_code_expr_info(this, _, _, result) }
2904-
29052896
/** Gets the placeholder tag that was parsed as an expression. */
29062897
Templating::TemplatePlaceholderTag getPlaceholderTag() {
2907-
result.getLocation() = this.getLocation()
2898+
this = result.getEnclosingExpr()
29082899
}
29092900

29102901
override string getAPrimaryQlClass() { result = "GeneratedCodeExpr" }

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ module Templating {
114114
* that is, without being enclosed in a string literal or similar.
115115
*/
116116
predicate isInPlainCodeContext() { this = any(GeneratedCodeExpr e).getPlaceholderTag() }
117+
118+
/**
119+
* Gets the innermost JavaScript expression containing this template tag, if any.
120+
*/
121+
Expr getEnclosingExpr() {
122+
expr_contains_template_tag_location(result, getLocation())
123+
}
117124
}
118125

119126
/**

javascript/ql/src/semmlecode.javascript.dbscheme

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,9 @@ case @expr.kind of
417417
@e4x_xml_attribute_selector = @e4x_xml_static_attribute_selector | @e4x_xml_dynamic_attribute_selector;
418418
@e4x_xml_qualident = @e4x_xml_static_qualident | @e4x_xml_dynamic_qualident;
419419

420-
generated_code_expr_info(
421-
unique int expr: @generated_code_expr ref,
422-
varchar(900) openingDelimiter: string ref,
423-
varchar(900) closingDelimiter: string ref,
424-
varchar(900) body: string ref
420+
expr_contains_template_tag_location(
421+
int expr: @expr ref,
422+
int location: @location ref
425423
);
426424

427425
@template_placeholder_tag_parent = @xmlelement | @xmlattribute | @file;

0 commit comments

Comments
 (0)