From 900f95c1ed5f87c55088908658273da32d9aa6f3 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 17 Feb 2023 19:36:21 +0100 Subject: [PATCH] js: make names of variables in minified mode never interfering with JS keywords --- .../rendering/StatementRenderer.java | 48 +---------- .../rendering/VariableNameGenerator.java | 85 +++++++++++++++++++ .../rendering/VariableRenderingTest.java | 34 ++++++++ 3 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/VariableNameGenerator.java create mode 100644 core/src/test/java/org/teavm/backend/javascript/rendering/VariableRenderingTest.java diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java index 681e28ac9..ec5c7775b 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/StatementRenderer.java @@ -21,11 +21,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import org.teavm.ast.ArrayFromDataExpr; import org.teavm.ast.AssignmentStatement; import org.teavm.ast.BinaryExpr; @@ -66,7 +64,6 @@ import org.teavm.ast.TryCatchStatement; import org.teavm.ast.UnaryExpr; import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.VariableExpr; -import org.teavm.ast.VariableNode; import org.teavm.ast.WhileStatement; import org.teavm.backend.javascript.codegen.NamingStrategy; import org.teavm.backend.javascript.codegen.SourceWriter; @@ -98,14 +95,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { private DeferredCallSite prevCallSite; private boolean end; private final Map blockIdMap = new HashMap<>(); - private final List cachedVariableNames = new ArrayList<>(); - private final Set usedVariableNames = new HashSet<>(); - private MethodNode currentMethod; private int currentPart; private List blockIds = new ArrayList<>(); private IntIndexedContainer blockIndexMap = new IntArrayList(); private boolean longLibraryUsed; private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("", ValueType.VOID); + private VariableNameGenerator variableNameGenerator; public StatementRenderer(RenderingContext context, SourceWriter writer) { this.context = context; @@ -114,11 +109,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { this.minifying = context.isMinifying(); this.naming = context.getNaming(); this.debugEmitter = context.getDebugEmitter(); - if (!minifying) { - usedVariableNames.add("$tmp"); - usedVariableNames.add("$ptr"); - usedVariableNames.add("$thread"); - } + variableNameGenerator = new VariableNameGenerator(minifying); } public boolean isLongLibraryUsed() { @@ -134,7 +125,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { } public void setCurrentMethod(MethodNode currentMethod) { - this.currentMethod = currentMethod; + variableNameGenerator.setCurrentMethod(currentMethod); } public void setCurrentPart(int currentPart) { @@ -492,38 +483,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { } public String variableName(int index) { - while (index >= cachedVariableNames.size()) { - cachedVariableNames.add(null); - } - String name = cachedVariableNames.get(index); - if (name == null) { - name = generateVariableName(index); - cachedVariableNames.set(index, name); - } - return name; - } - - private String generateVariableName(int index) { - if (!minifying) { - VariableNode variable = currentMethod != null && index < currentMethod.getVariables().size() - ? currentMethod.getVariables().get(index) - : null; - if (variable != null && variable.getName() != null) { - String result = "$" + RenderingUtil.escapeName(variable.getName()); - if (RenderingUtil.KEYWORDS.contains(result) || !usedVariableNames.add(result)) { - String base = result; - int suffix = 0; - do { - result = base + "_" + suffix++; - } while (!usedVariableNames.add(result)); - } - return result; - } else { - return "var$" + index; - } - } else { - return RenderingUtil.indexToId(index); - } + return variableNameGenerator.variableName(index); } private void visitBinary(BinaryExpr expr, String op, boolean guarded) { diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/VariableNameGenerator.java b/core/src/main/java/org/teavm/backend/javascript/rendering/VariableNameGenerator.java new file mode 100644 index 000000000..fbd93f489 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/VariableNameGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.rendering; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.teavm.ast.MethodNode; + +public class VariableNameGenerator { + private boolean minifying; + private final List cachedVariableNames = new ArrayList<>(); + private int cachedVariableNameLastIndex; + private MethodNode currentMethod; + private Set usedVariableNames = new HashSet<>(); + + public VariableNameGenerator(boolean minifying) { + this.minifying = minifying; + if (!minifying) { + usedVariableNames.add("$tmp"); + usedVariableNames.add("$ptr"); + usedVariableNames.add("$thread"); + } + } + + public void setCurrentMethod(MethodNode currentMethod) { + this.currentMethod = currentMethod; + } + + public String variableName(int index) { + if (!minifying) { + while (index >= cachedVariableNames.size()) { + cachedVariableNames.add(null); + } + String name = cachedVariableNames.get(index); + if (name == null) { + name = generateVariableName(index); + cachedVariableNames.set(index, name); + } + return name; + } else { + while (index >= cachedVariableNames.size()) { + String name; + do { + name = RenderingUtil.indexToId(cachedVariableNameLastIndex++); + } while (RenderingUtil.KEYWORDS.contains(name)); + cachedVariableNames.add(name); + } + return cachedVariableNames.get(index); + } + } + + private String generateVariableName(int index) { + var variable = currentMethod != null && index < currentMethod.getVariables().size() + ? currentMethod.getVariables().get(index) + : null; + if (variable != null && variable.getName() != null) { + String result = "$" + RenderingUtil.escapeName(variable.getName()); + if (RenderingUtil.KEYWORDS.contains(result) || !usedVariableNames.add(result)) { + String base = result; + int suffix = 0; + do { + result = base + "_" + suffix++; + } while (!usedVariableNames.add(result)); + } + return result; + } else { + return "var$" + index; + } + } +} diff --git a/core/src/test/java/org/teavm/backend/javascript/rendering/VariableRenderingTest.java b/core/src/test/java/org/teavm/backend/javascript/rendering/VariableRenderingTest.java new file mode 100644 index 000000000..30f607b39 --- /dev/null +++ b/core/src/test/java/org/teavm/backend/javascript/rendering/VariableRenderingTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.javascript.rendering; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.HashSet; +import org.junit.Test; + +public class VariableRenderingTest { + @Test + public void shortenedNames() { + var generator = new VariableNameGenerator(true); + var usedNames = new HashSet(); + for (var i = 0; i < 10000; ++i) { + var name = generator.variableName(i); + assertTrue(usedNames.add(name)); + assertFalse(RenderingUtil.KEYWORDS.contains(name)); + } + } +}