Skip to content

Commit da82459

Browse files
lauraharkercopybara-github
authored andcommitted
Refactor AbstractPeepholeOptimization::addFeatureToEnclosingScript to not use n.getSourceFile()
This fixes a @closureUnaware crash (as seen in TranspileAndOptimizeClosureUnawareCodeTest). AbstractPeepholeOptimization::addFeatureToEnclosingScript assumed that "node.getSourceFile()" matched the source file of the containing SCRIPT node. This breaks down if optimizations move code between scripts. It now also crashed for @closureUnaware code, because we create synthetic file names for each @closureUnaware block within a file, which confuses compiler.getScriptNode. PiperOrigin-RevId: 858630083
1 parent 5ed36d1 commit da82459

File tree

5 files changed

+86
-37
lines changed

5 files changed

+86
-37
lines changed

src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
package com.google.javascript.jscomp;
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.common.base.Preconditions.checkState;
2021

21-
import com.google.common.collect.LinkedHashMultimap;
22+
import com.google.common.collect.ImmutableSet;
2223
import com.google.javascript.jscomp.base.Tri;
2324
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
2425
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
2526
import com.google.javascript.rhino.Node;
26-
import com.google.javascript.rhino.StaticSourceFile;
2727
import java.math.BigInteger;
28+
import java.util.LinkedHashSet;
2829

2930
/**
3031
* An abstract class whose implementations run peephole optimizations:
@@ -38,9 +39,14 @@ abstract class AbstractPeepholeOptimization {
3839
/** Intentionally not exposed to subclasses */
3940
private AstAnalyzer astAnalyzer;
4041

41-
/** Features added to scripts during the optimization. */
42-
private final LinkedHashMultimap<StaticSourceFile, Feature> newFeatures =
43-
LinkedHashMultimap.create();
42+
/**
43+
* New parser features added in some {@link #optimizeSubtree} call.
44+
*
45+
* <p>{@link PeepholeOptimizationsPass} uses this to call {@link NodeUtil#addFeaturesToScript} in
46+
* closer to O(1) time, as this class doesn't know what script node it's currently in, and would
47+
* need to walk the AST.
48+
*/
49+
private final LinkedHashSet<Feature> newFeatures = new LinkedHashSet<>();
4450

4551
/**
4652
* Given a node to optimize and a traversal, optimize the node. Subclasses
@@ -100,11 +106,11 @@ void beginTraversal(AbstractCompiler compiler) {
100106
* called again.
101107
*/
102108
void endTraversal() {
103-
for (StaticSourceFile file : newFeatures.keySet()) {
104-
Node script = compiler.getScriptNode(file.getName());
105-
NodeUtil.addFeaturesToScript(
106-
script, FeatureSet.BARE_MINIMUM.with(newFeatures.get(file)), this.compiler);
107-
}
109+
checkState(
110+
this.newFeatures.isEmpty(),
111+
"Expected getNewFeatures() to be empty in endTraversal() but found %s for %s",
112+
this.newFeatures,
113+
this.getClass().getName());
108114
this.compiler = null;
109115
astAnalyzer = null;
110116
}
@@ -244,7 +250,24 @@ protected final void markNewScopesChanged(Node n) {
244250
NodeUtil.markNewScopesChanged(n, compiler);
245251
}
246252

247-
protected final void addFeatureToEnclosingScript(Node n, Feature feature) {
248-
newFeatures.put(n.getStaticSourceFile(), feature);
253+
/** Calls {@link NodeUtil#addFeatureToScript} with the script currently being visited. */
254+
protected final void addFeatureToEnclosingScript(Feature feature) {
255+
// NOTE: we could implement this as
256+
// protected final void addFeatureToEnclosingScript(Node node, Feature feature) {
257+
// NodeUtil.addFeatureToScript(NodeUtil.getEnclosingScript(node), ...
258+
// This is expected to be behaviorally equivalent.
259+
// However, walking the AST to get the enclosing script is O(depth of the AST) per call. With
260+
// the current implementation, we let PeepholeOptimizationsPass to find the script in what's
261+
// usually O(1) time, or worst case it only calls getEnclosingScript() once per NodeTraversal
262+
// and then caches the result.
263+
newFeatures.add(feature);
264+
}
265+
266+
final ImmutableSet<Feature> getNewFeatures() {
267+
return ImmutableSet.copyOf(newFeatures);
268+
}
269+
270+
final void clearNewFeatures() {
271+
newFeatures.clear();
249272
}
250273
}

src/com/google/javascript/jscomp/PeepholeOptimizationsPass.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
package com.google.javascript.jscomp;
1818

19+
1920
import com.google.common.annotations.VisibleForTesting;
20-
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
21+
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
2122
import com.google.javascript.rhino.Node;
2223
import java.util.Arrays;
2324
import java.util.List;
@@ -83,7 +84,7 @@ public void process(Node externs, Node root) {
8384
endTraversal();
8485
}
8586

86-
private class PeepCallback extends AbstractPostOrderCallback {
87+
private class PeepCallback extends NodeTraversal.AbstractScopedCallback {
8788
@Override
8889
public void visit(NodeTraversal t, Node n, Node parent) {
8990
Node currentNode = n;
@@ -94,6 +95,29 @@ public void visit(NodeTraversal t, Node n, Node parent) {
9495
}
9596
}
9697
}
98+
99+
@Override
100+
public void enterScope(NodeTraversal t) {}
101+
102+
@Override
103+
public void exitScope(NodeTraversal t) {
104+
// Call updateFeatures() here, instead of immediately after
105+
// `optim.optimizeSubtree(currentNode)` in visit, to avoid repeating work for
106+
// every AST node x every peephole optimization. In practice, profiling shows a small
107+
// but measurable improvement for large projects. See comments on cl/856752797.
108+
updateFeatures(t);
109+
}
110+
111+
private void updateFeatures(NodeTraversal t) {
112+
for (AbstractPeepholeOptimization optim : peepholeOptimizations) {
113+
var newFeatures = optim.getNewFeatures();
114+
if (!newFeatures.isEmpty()) {
115+
NodeUtil.addFeaturesToScript(
116+
t.getCurrentScript(), FeatureSet.BARE_MINIMUM.with(newFeatures), compiler);
117+
}
118+
optim.clearNewFeatures();
119+
}
120+
}
97121
}
98122

99123
/** Make sure that all the optimizations have the current compiler so they can report errors. */

src/com/google/javascript/jscomp/PeepholeRemoveDeadCode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ private void redeclareIfBlockScopedVar(Node decl, Node parent) {
10071007
parent.addChildToBack(var);
10081008
}
10091009
}
1010-
this.addFeatureToEnclosingScript(parent, Feature.LET_DECLARATIONS);
1010+
this.addFeatureToEnclosingScript(Feature.LET_DECLARATIONS);
10111011
}
10121012

10131013
/**

test/com/google/javascript/jscomp/PeepholeOptimizationsPassTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -250,23 +250,23 @@ public void testAddFeatureToEnclosingScript() {
250250
@Override
251251
public Node optimizeSubtree(Node node) {
252252
if (node.isAdd()) {
253-
this.addFeatureToEnclosingScript(node, Feature.LET_DECLARATIONS);
254-
this.addFeatureToEnclosingScript(node, Feature.LET_DECLARATIONS);
255-
this.addFeatureToEnclosingScript(node, Feature.CLASSES);
253+
this.addFeatureToEnclosingScript(Feature.LET_DECLARATIONS);
254+
this.addFeatureToEnclosingScript(Feature.LET_DECLARATIONS);
255+
this.addFeatureToEnclosingScript(Feature.CLASSES);
256256
}
257257
return node;
258258
}
259259
},
260260
new AbstractPeepholeOptimization() {
261261
@Override
262262
public Node optimizeSubtree(Node node) {
263-
if (node.isAdd()) {
264-
this.addFeatureToEnclosingScript(node, Feature.CONST_DECLARATIONS);
263+
if (node.isSub()) {
264+
this.addFeatureToEnclosingScript(Feature.CONST_DECLARATIONS);
265265
}
266266
return node;
267267
}
268268
});
269-
testSame("(3 + 4);");
269+
testSame("(3 + 4); function sub() { return 3 - 4; }");
270270
Compiler compiler = getLastCompiler();
271271
Node script = checkNotNull(compiler.getScriptNode("testcode"));
272272
assertThat(script.getProp(Node.FEATURE_SET))

test/com/google/javascript/jscomp/TranspileAndOptimizeClosureUnawareTest.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,24 +125,26 @@ public void testFoldConstants_multipleClosureUnawareBlocksInFile() {
125125
public void testFoldConstants_regressionTest_crashInNodeUtilAddFeatureToScript() {
126126
setLanguageOut(CompilerOptions.LanguageMode.ECMASCRIPT_NEXT);
127127

128-
// TODO: b/421970620 - fix this crash.
129-
assertThrows(
130-
RuntimeException.class,
131-
() ->
132-
testSame(
133-
closureUnaware(
134-
"""
135-
return function f() {
136-
function referenceX() { return x; }
128+
test(
129+
closureUnaware(
130+
"""
131+
return function f() {
132+
function referenceX() { return x; }
137133
138-
return 1;
134+
return 1;
139135
140-
// Regression test for a crash in PeepholeRemoveDeadCode.
141-
let x = 0;
142-
referenceX();
143-
x++;
144-
}
145-
""")));
136+
// Regression test for a crash in PeepholeRemoveDeadCode.
137+
let x = 0;
138+
referenceX();
139+
x++;
140+
}
141+
"""),
142+
expectedClosureUnaware(
143+
"""
144+
return function() {
145+
return 1;
146+
}
147+
"""));
146148
}
147149

148150
@Test

0 commit comments

Comments
 (0)