js: make names of variables in minified mode never interfering with JS keywords

This commit is contained in:
Alexey Andreev 2023-02-17 19:36:21 +01:00
parent 1989fb70a4
commit 900f95c1ed
3 changed files with 123 additions and 44 deletions

View File

@ -21,11 +21,9 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import org.teavm.ast.ArrayFromDataExpr; import org.teavm.ast.ArrayFromDataExpr;
import org.teavm.ast.AssignmentStatement; import org.teavm.ast.AssignmentStatement;
import org.teavm.ast.BinaryExpr; import org.teavm.ast.BinaryExpr;
@ -66,7 +64,6 @@ import org.teavm.ast.TryCatchStatement;
import org.teavm.ast.UnaryExpr; import org.teavm.ast.UnaryExpr;
import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.UnwrapArrayExpr;
import org.teavm.ast.VariableExpr; import org.teavm.ast.VariableExpr;
import org.teavm.ast.VariableNode;
import org.teavm.ast.WhileStatement; import org.teavm.ast.WhileStatement;
import org.teavm.backend.javascript.codegen.NamingStrategy; import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
@ -98,14 +95,12 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
private DeferredCallSite prevCallSite; private DeferredCallSite prevCallSite;
private boolean end; private boolean end;
private final Map<String, String> blockIdMap = new HashMap<>(); private final Map<String, String> blockIdMap = new HashMap<>();
private final List<String> cachedVariableNames = new ArrayList<>();
private final Set<String> usedVariableNames = new HashSet<>();
private MethodNode currentMethod;
private int currentPart; private int currentPart;
private List<String> blockIds = new ArrayList<>(); private List<String> blockIds = new ArrayList<>();
private IntIndexedContainer blockIndexMap = new IntArrayList(); private IntIndexedContainer blockIndexMap = new IntArrayList();
private boolean longLibraryUsed; private boolean longLibraryUsed;
private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID); private static final MethodDescriptor CLINIT_METHOD = new MethodDescriptor("<clinit>", ValueType.VOID);
private VariableNameGenerator variableNameGenerator;
public StatementRenderer(RenderingContext context, SourceWriter writer) { public StatementRenderer(RenderingContext context, SourceWriter writer) {
this.context = context; this.context = context;
@ -114,11 +109,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
this.minifying = context.isMinifying(); this.minifying = context.isMinifying();
this.naming = context.getNaming(); this.naming = context.getNaming();
this.debugEmitter = context.getDebugEmitter(); this.debugEmitter = context.getDebugEmitter();
if (!minifying) { variableNameGenerator = new VariableNameGenerator(minifying);
usedVariableNames.add("$tmp");
usedVariableNames.add("$ptr");
usedVariableNames.add("$thread");
}
} }
public boolean isLongLibraryUsed() { public boolean isLongLibraryUsed() {
@ -134,7 +125,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
} }
public void setCurrentMethod(MethodNode currentMethod) { public void setCurrentMethod(MethodNode currentMethod) {
this.currentMethod = currentMethod; variableNameGenerator.setCurrentMethod(currentMethod);
} }
public void setCurrentPart(int currentPart) { public void setCurrentPart(int currentPart) {
@ -492,38 +483,7 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
} }
public String variableName(int index) { public String variableName(int index) {
while (index >= cachedVariableNames.size()) { return variableNameGenerator.variableName(index);
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);
}
} }
private void visitBinary(BinaryExpr expr, String op, boolean guarded) { private void visitBinary(BinaryExpr expr, String op, boolean guarded) {

View File

@ -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<String> cachedVariableNames = new ArrayList<>();
private int cachedVariableNameLastIndex;
private MethodNode currentMethod;
private Set<String> 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;
}
}
}

View File

@ -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<String>();
for (var i = 0; i < 10000; ++i) {
var name = generator.variableName(i);
assertTrue(usedNames.add(name));
assertFalse(RenderingUtil.KEYWORDS.contains(name));
}
}
}