JS: add support for CCE in strict mode

This commit is contained in:
Alexey Andreev 2021-03-17 21:11:18 +03:00
parent 02e8955abc
commit 4c0c7872a1
15 changed files with 276 additions and 117 deletions

View File

@ -26,6 +26,7 @@ import org.teavm.ast.AssignmentStatement;
import org.teavm.ast.BinaryOperation;
import org.teavm.ast.BoundCheckExpr;
import org.teavm.ast.BreakStatement;
import org.teavm.ast.CastExpr;
import org.teavm.ast.ContinueStatement;
import org.teavm.ast.Expr;
import org.teavm.ast.InitClassStatement;
@ -208,7 +209,11 @@ class StatementGenerator implements InstructionVisitor {
@Override
public void visit(CastInstruction insn) {
assign(Expr.var(insn.getValue().getIndex()), insn.getReceiver());
CastExpr expr = new CastExpr();
expr.setLocation(insn.getLocation());
expr.setValue(Expr.var(insn.getValue().getIndex()));
expr.setTarget(insn.getTargetType());
assign(expr, insn.getReceiver());
}
@Override

View File

@ -284,6 +284,11 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
NullPointerException.class, "<init>", void.class));
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(NullPointerException.class.getName()));
exceptionCons.use();
exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference(
ClassCastException.class, "<init>", void.class));
exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType(ClassCastException.class.getName()));
exceptionCons.use();
}
if (stackTraceIncluded) {
@ -370,7 +375,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
controller.getUnprocessedClassSource(), classes,
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming,
controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m),
controller.getClassInitializerInfo());
controller.getClassInitializerInfo(), strict);
renderingContext.setMinifying(obfuscated);
Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods,
controller.getDiagnostics(), renderingContext);

View File

@ -22,6 +22,7 @@ import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.BinaryExpr;
import org.teavm.ast.BoundCheckExpr;
import org.teavm.ast.CastExpr;
import org.teavm.ast.ConstantExpr;
import org.teavm.ast.InitClassStatement;
import org.teavm.ast.InstanceOfExpr;
@ -69,13 +70,16 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
private boolean async;
private final Set<MethodReference> injectedMethods;
private final Set<MethodReference> asyncFamilyMethods;
private final boolean strict;
NameFrequencyEstimator(NameFrequencyConsumer consumer, ClassReaderSource classSource,
Set<MethodReference> injectedMethods, Set<MethodReference> asyncFamilyMethods) {
Set<MethodReference> injectedMethods, Set<MethodReference> asyncFamilyMethods,
boolean strict) {
this.consumer = consumer;
this.classSource = classSource;
this.injectedMethods = injectedMethods;
this.asyncFamilyMethods = asyncFamilyMethods;
this.strict = strict;
}
public void estimate(PreparedClass cls) {
@ -470,17 +474,33 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit
public void visit(InstanceOfExpr expr) {
super.visit(expr);
visitType(expr.getType());
if (expr.getType() instanceof ValueType.Object) {
String clsName = ((ValueType.Object) expr.getType()).getClassName();
ClassReader cls = classSource.get(clsName);
if (cls == null || cls.hasModifier(ElementModifier.INTERFACE)) {
consumer.consumeFunction("$rt_isInstance");
}
} else {
if (!isClass(expr.getType())) {
consumer.consumeFunction("$rt_isInstance");
}
}
@Override
public void visit(CastExpr expr) {
super.visit(expr);
if (strict) {
visitType(expr.getTarget());
if (isClass(expr.getTarget())) {
consumer.consumeFunction("$rt_castToClass");
} else {
consumer.consumeFunction("$rt_castToInterface");
}
}
}
private boolean isClass(ValueType type) {
if (!(type instanceof ValueType.Object)) {
return false;
}
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
return cls != null && !cls.hasModifier(ElementModifier.INTERFACE);
}
@Override
public void visit(BoundCheckExpr expr) {
super.visit(expr);

View File

@ -261,6 +261,7 @@ public class Renderer implements RenderingManager {
"$rt_createLongArrayFromData", "$rt_createBooleanArray", "$rt_createByteArray",
"$rt_createShortArray", "$rt_createCharArray", "$rt_createIntArray", "$rt_createLongArray",
"$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare",
"$rt_castToClass", "$rt_castToInterface",
"Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO",
"Long_hi", "Long_lo");
}
@ -287,7 +288,7 @@ public class Renderer implements RenderingManager {
if (minifying) {
NamingOrderer orderer = new NamingOrderer();
NameFrequencyEstimator estimator = new NameFrequencyEstimator(orderer, classSource, asyncMethods,
asyncFamilyMethods);
asyncFamilyMethods, context.isStrict());
for (PreparedClass cls : classes) {
estimator.estimate(cls);
}

View File

@ -65,12 +65,14 @@ public class RenderingContext {
private boolean minifying;
private ClassInitializerInfo classInitializerInfo;
private TextLocation lastEmittedLocation = TextLocation.EMPTY;
private boolean strict;
public RenderingContext(DebugInformationEmitter debugEmitter,
ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
ClassLoader classLoader, ServiceRepository services, Properties properties,
NamingStrategy naming, DependencyInfo dependencyInfo,
Predicate<MethodReference> virtualPredicate, ClassInitializerInfo classInitializerInfo) {
Predicate<MethodReference> virtualPredicate, ClassInitializerInfo classInitializerInfo,
boolean strict) {
this.debugEmitter = debugEmitter;
this.initialClassSource = initialClassSource;
this.classSource = classSource;
@ -81,6 +83,7 @@ public class RenderingContext {
this.dependencyInfo = dependencyInfo;
this.virtualPredicate = virtualPredicate;
this.classInitializerInfo = classInitializerInfo;
this.strict = strict;
}
public ClassReaderSource getInitialClassSource() {
@ -399,6 +402,10 @@ public class RenderingContext {
return holder.injector;
}
public boolean isStrict() {
return strict;
}
@PlatformMarker
private static boolean isBootstrap() {
return false;

View File

@ -49,6 +49,8 @@ public class RuntimeRenderer {
"setStackTrace", StackTraceElement[].class, void.class);
private static final MethodReference AIOOBE_INIT_METHOD = new MethodReference(ArrayIndexOutOfBoundsException.class,
"<init>", void.class);
private static final MethodReference CCE_INIT_METHOD = new MethodReference(ClassCastException.class,
"<init>", void.class);
private final ClassReaderSource classSource;
private final SourceWriter writer;
@ -73,6 +75,7 @@ public class RuntimeRenderer {
renderCreateStackTraceElement();
renderSetStackTrace();
renderThrowAIOOBE();
renderThrowCCE();
} catch (IOException e) {
throw new RenderingException("IO error", e);
}
@ -267,4 +270,18 @@ public class RuntimeRenderer {
writer.outdent().append("}").newLine();
}
private void renderThrowCCE() throws IOException {
writer.append("function $rt_throwCCE()").ws().append("{").indent().softNewLine();
ClassReader cls = classSource.get(CCE_INIT_METHOD.getClassName());
if (cls != null) {
MethodReader method = cls.getMethod(CCE_INIT_METHOD.getDescriptor());
if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) {
writer.append("$rt_throw(").appendInit(CCE_INIT_METHOD).append("());").softNewLine();
}
}
writer.outdent().append("}").newLine();
}
}

View File

@ -915,7 +915,41 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
@Override
public void visit(CastExpr expr) {
expr.getValue().acceptVisitor(this);
if (context.isStrict()) {
try {
if (expr.getLocation() != null) {
pushLocation(expr.getLocation());
}
if (isClass(expr.getTarget(), context.getClassSource())) {
writer.appendFunction("$rt_castToClass");
} else {
writer.appendFunction("$rt_castToInterface");
}
writer.append("(");
precedence = Precedence.min();
expr.getValue().acceptVisitor(this);
writer.append(",").ws();
context.typeToClsString(writer, expr.getTarget());
writer.append(")");
if (expr.getLocation() != null) {
popLocation();
}
} catch (IOException e) {
throw new RenderingException("IO error occurred", e);
}
} else {
expr.getValue().acceptVisitor(this);
}
}
static boolean isClass(ValueType type, ClassReaderSource classSource) {
if (!(type instanceof ValueType.Object)) {
return false;
}
String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className);
return cls != null && !cls.hasModifier(ElementModifier.INTERFACE);
}
@Override
@ -1475,32 +1509,26 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor {
if (expr.getLocation() != null) {
pushLocation(expr.getLocation());
}
if (expr.getType() instanceof ValueType.Object) {
String clsName = ((ValueType.Object) expr.getType()).getClassName();
ClassReader cls = classSource.get(clsName);
if (cls != null && !cls.hasModifier(ElementModifier.INTERFACE)) {
boolean needsParentheses = Precedence.COMPARISON.ordinal() < precedence.ordinal();
if (needsParentheses) {
writer.append('(');
}
precedence = Precedence.CONDITIONAL.next();
expr.getExpr().acceptVisitor(this);
writer.append(" instanceof ").appendClass(clsName);
if (needsParentheses) {
writer.append(')');
}
if (expr.getLocation() != null) {
popLocation();
}
return;
if (isClass(expr.getType(), context.getClassSource())) {
boolean needsParentheses = Precedence.COMPARISON.ordinal() < precedence.ordinal();
if (needsParentheses) {
writer.append('(');
}
precedence = Precedence.CONDITIONAL.next();
expr.getExpr().acceptVisitor(this);
writer.append(" instanceof ");
context.typeToClsString(writer, expr.getType());
if (needsParentheses) {
writer.append(')');
}
} else {
writer.appendFunction("$rt_isInstance").append("(");
precedence = Precedence.min();
expr.getExpr().acceptVisitor(this);
writer.append(",").ws();
context.typeToClsString(writer, expr.getType());
writer.append(")");
}
writer.appendFunction("$rt_isInstance").append("(");
precedence = Precedence.min();
expr.getExpr().acceptVisitor(this);
writer.append(",").ws();
context.typeToClsString(writer, expr.getType());
writer.append(")");
if (expr.getLocation() != null) {
popLocation();
}

View File

@ -227,6 +227,14 @@ abstract class AbstractInstructionAnalyzer extends AbstractInstructionReader {
}
}
@Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
String className = extractClassName(targetType);
if (className != null) {
getAnalyzer().linkClass(className);
}
}
@Override
public void initClass(String className) {
getAnalyzer().linkClass(className).initClass(getCallLocation());

View File

@ -220,6 +220,7 @@ class DependencyGraphBuilder {
@Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
super.cast(receiver, value, targetType);
DependencyNode valueNode = nodes[value.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
ClassReaderSource classSource = dependencyAnalyzer.getClassSource();
@ -253,6 +254,7 @@ class DependencyGraphBuilder {
valueNode.connect(receiverNode);
}
}
@Override
public void exit(VariableReader valueToReturn) {
if (valueToReturn != null) {

View File

@ -30,6 +30,9 @@ import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
@ -41,6 +44,8 @@ public class Devirtualization {
private Set<? extends MethodReference> readonlyVirtualMethods = Collections.unmodifiableSet(virtualMethods);
private int virtualCallSites;
private int directCallSites;
private int remainingCasts;
private int eliminatedCasts;
public Devirtualization(DependencyInfo dependency, ClassHierarchy hierarchy) {
this.dependency = dependency;
@ -55,6 +60,14 @@ public class Devirtualization {
return directCallSites;
}
public int getRemainingCasts() {
return remainingCasts;
}
public int getEliminatedCasts() {
return eliminatedCasts;
}
public void apply(MethodHolder method) {
MethodDependencyInfo methodDep = dependency.getMethod(method.getReference());
if (methodDep == null) {
@ -69,50 +82,10 @@ public class Devirtualization {
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) {
if (!(insn instanceof InvokeInstruction)) {
continue;
}
InvokeInstruction invoke = (InvokeInstruction) insn;
if (invoke.getType() != InvocationType.VIRTUAL) {
continue;
}
ValueDependencyInfo var = methodDep.getVariable(invoke.getInstance().getIndex());
Set<MethodReference> implementations = getImplementations(var.getTypes(),
invoke.getMethod());
if (implementations.size() == 1) {
MethodReference resolvedImplementaiton = implementations.iterator().next();
if (shouldLog) {
System.out.print("DIRECT CALL " + invoke.getMethod() + " resolved to "
+ resolvedImplementaiton.getClassName());
if (insn.getLocation() != null) {
System.out.print(" at " + insn.getLocation().getFileName() + ":"
+ insn.getLocation().getLine());
}
System.out.println();
}
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(resolvedImplementaiton);
directCallSites++;
} else {
virtualMethods.addAll(implementations);
if (shouldLog) {
System.out.print("VIRTUAL CALL " + invoke.getMethod() + " resolved to [");
boolean first = true;
for (MethodReference impl : implementations) {
if (!first) {
System.out.print(", ");
}
first = false;
System.out.print(impl.getClassName());
}
System.out.print("]");
if (insn.getLocation() != null) {
System.out.print(" at " + insn.getLocation().getFileName() + ":"
+ insn.getLocation().getLine());
}
System.out.println();
}
virtualCallSites++;
if (insn instanceof InvokeInstruction) {
applyToInvoke(methodDep, (InvokeInstruction) insn);
} else if (insn instanceof CastInstruction) {
applyToCast(methodDep, (CastInstruction) insn);
}
}
}
@ -122,6 +95,104 @@ public class Devirtualization {
}
}
private void applyToInvoke(MethodDependencyInfo methodDep, InvokeInstruction invoke) {
if (invoke.getType() != InvocationType.VIRTUAL) {
return;
}
ValueDependencyInfo var = methodDep.getVariable(invoke.getInstance().getIndex());
Set<MethodReference> implementations = getImplementations(var.getTypes(),
invoke.getMethod());
if (implementations.size() == 1) {
MethodReference resolvedImplementaiton = implementations.iterator().next();
if (shouldLog) {
System.out.print("DIRECT CALL " + invoke.getMethod() + " resolved to "
+ resolvedImplementaiton.getClassName());
if (invoke.getLocation() != null) {
System.out.print(" at " + invoke.getLocation().getFileName() + ":"
+ invoke.getLocation().getLine());
}
System.out.println();
}
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(resolvedImplementaiton);
directCallSites++;
} else {
virtualMethods.addAll(implementations);
if (shouldLog) {
System.out.print("VIRTUAL CALL " + invoke.getMethod() + " resolved to [");
boolean first = true;
for (MethodReference impl : implementations) {
if (!first) {
System.out.print(", ");
}
first = false;
System.out.print(impl.getClassName());
}
System.out.print("]");
if (invoke.getLocation() != null) {
System.out.print(" at " + invoke.getLocation().getFileName() + ":"
+ invoke.getLocation().getLine());
}
System.out.println();
}
virtualCallSites++;
}
}
private void applyToCast(MethodDependencyInfo methodDep, CastInstruction cast) {
ValueDependencyInfo var = methodDep.getVariable(cast.getValue().getIndex());
boolean canFail = false;
String failType = null;
for (String type : var.getTypes()) {
if (castCanFail(type, cast.getTargetType())) {
failType = type;
canFail = true;
}
}
if (canFail) {
if (shouldLog) {
System.out.print("REMAINING CAST to " + cast.getTargetType() + " (example is " + failType + ")");
if (cast.getLocation() != null) {
System.out.print(" at " + cast.getLocation().getFileName() + ":"
+ cast.getLocation().getLine());
}
System.out.println();
}
remainingCasts++;
} else {
if (shouldLog) {
System.out.print("ELIMINATED CAST to " + cast.getTargetType());
if (cast.getLocation() != null) {
System.out.print(" at " + cast.getLocation().getFileName() + ":"
+ cast.getLocation().getLine());
}
System.out.println();
}
AssignInstruction assign = new AssignInstruction();
assign.setAssignee(cast.getValue());
assign.setReceiver(cast.getReceiver());
assign.setLocation(cast.getLocation());
cast.replace(assign);
eliminatedCasts++;
}
}
private boolean castCanFail(String type, ValueType target) {
if (type.startsWith("[")) {
ValueType valueType = ValueType.parse(type);
if (hierarchy.isSuperType(target, valueType, false)) {
return false;
}
} else if (target instanceof ValueType.Object) {
String targetClassName = ((ValueType.Object) target).getClassName();
if (hierarchy.isSuperType(targetClassName, type, false)) {
return false;
}
}
return true;
}
private Set<MethodReference> getImplementations(String[] classNames, MethodReference ref) {
return implementations(hierarchy, dependency, classNames, ref);
}

View File

@ -44,6 +44,18 @@ function $rt_isAssignable(from, to) {
}
return false;
}
function $rt_castToInterface(obj, cls) {
if (obj !== null && !$rt_isInstance(obj, cls)) {
$rt_throwCCE();
}
return obj;
}
function $rt_castToClass(obj, cls) {
if (obj !== null && !(obj instanceof cls)) {
$rt_throwCCE();
}
return obj;
}
Array.prototype.fill = Array.prototype.fill || function(value,start,end) {
var len = this.length;
if (!len) return this;

View File

@ -247,7 +247,12 @@ class JSClassProcessor {
}
ClassReader targetClass = classSource.get(targetClassName);
if (targetClass.getAnnotations().get(JSFunctor.class.getName()) == null) {
return false;
AssignInstruction assign = new AssignInstruction();
assign.setLocation(location.getSourceLocation());
assign.setAssignee(cast.getValue());
assign.setReceiver(cast.getReceiver());
replacement.add(assign);
return true;
}
Variable result = marshaller.unwrapFunctor(location, cast.getValue(), targetClass);

View File

@ -33,7 +33,6 @@ import org.teavm.model.ReferenceCache;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
@ -290,14 +289,7 @@ class JSValueMarshaller {
} else if (className.equals("java.lang.String")) {
return unwrap(var, "unwrapString", JSMethods.JS_OBJECT, stringType, location.getSourceLocation());
} else if (typeHelper.isJavaScriptClass(className)) {
Variable result = program.createVariable();
CastInstruction castInsn = new CastInstruction();
castInsn.setReceiver(result);
castInsn.setValue(var);
castInsn.setTargetType(type);
castInsn.setLocation(location.getSourceLocation());
replacement.add(castInsn);
return result;
return var;
}
} else if (type instanceof ValueType.Array) {
return unwrapArray(location, var, (ValueType.Array) type);
@ -314,14 +306,6 @@ class JSValueMarshaller {
itemType = ((ValueType.Array) itemType).getItemType();
}
CastInstruction castInsn = new CastInstruction();
castInsn.setValue(var);
castInsn.setTargetType(ValueType.parse(JSArrayReader.class));
var = program.createVariable();
castInsn.setReceiver(var);
castInsn.setLocation(location.getSourceLocation());
replacement.add(castInsn);
var = degree == 1
? unwrapSingleDimensionArray(location, var, itemType)
: unwrapMultiDimensionArray(location, var, itemType, degree);
@ -477,16 +461,6 @@ class JSValueMarshaller {
private Variable unwrap(Variable var, String methodName, ValueType argType, ValueType resultType,
TextLocation location) {
if (!argType.isObject(JSObject.class.getName())) {
Variable castValue = program.createVariable();
CastInstruction castInsn = new CastInstruction();
castInsn.setValue(var);
castInsn.setReceiver(castValue);
castInsn.setLocation(location);
castInsn.setTargetType(argType);
replacement.add(castInsn);
var = castValue;
}
Variable result = program.createVariable();
InvokeInstruction insn = new InvokeInstruction();
insn.setMethod(referenceCache.getCached(referenceCache.getCached(new MethodReference(

View File

@ -1084,7 +1084,7 @@ public class CompositeMethodGenerator {
private Variable unwrapArray(ValueType type, Variable array) {
CastInstruction cast = new CastInstruction();
cast.setTargetType(ValueType.arrayOf(type));
cast.setTargetType(type);
cast.setValue(array);
cast.setReceiver(program.createVariable());
add(cast);

View File

@ -35,6 +35,7 @@ class ResourceProgramTransformer {
Object.class, String[].class);
private static final MethodReference GET_PROPERTY = new MethodReference(ResourceAccessor.class, "getProperty",
Object.class, String.class, Object.class);
private static final ValueType RESOURCE = ValueType.parse(Resource.class);
private ClassHierarchy hierarchy;
private Program program;
@ -59,6 +60,15 @@ class ResourceProgramTransformer {
insn.insertNextAll(replacement);
insn.delete();
}
} else if (insn instanceof CastInstruction) {
CastInstruction cast = (CastInstruction) insn;
if (hierarchy.isSuperType(RESOURCE, cast.getTargetType(), false)) {
AssignInstruction assign = new AssignInstruction();
assign.setReceiver(cast.getReceiver());
assign.setAssignee(cast.getValue());
assign.setLocation(cast.getLocation());
insn.replace(assign);
}
}
}
}
@ -171,13 +181,7 @@ class ResourceProgramTransformer {
return instructions;
}
default: {
Variable resultVar = insn.getProgram().createVariable();
getProperty(insn, property, instructions, resultVar);
CastInstruction castInsn = new CastInstruction();
castInsn.setReceiver(insn.getReceiver());
castInsn.setTargetType(type);
castInsn.setValue(resultVar);
instructions.add(castInsn);
getProperty(insn, property, instructions, insn.getReceiver());
return instructions;
}
}