diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TBitSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TBitSet.java index 97e189df5..35ab92f0a 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TBitSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TBitSet.java @@ -136,7 +136,9 @@ public class TBitSet extends TObject implements TCloneable, TSerializable { for (int i = fromDataIndex + 1; i < toDataIndex; ++i) { data[i] ^= 0xFFFFFFFF; } - data[toDataIndex] ^= trailingOneBits(toIndex); + if ((toIndex & 31) != 0) { + data[toDataIndex] ^= trailingOneBits(toIndex); + } } if (toIndex == length) { recalculateLength(); @@ -177,7 +179,9 @@ public class TBitSet extends TObject implements TCloneable, TSerializable { for (int i = fromDataIndex + 1; i < toDataIndex; ++i) { data[i] = 0xFFFFFFFF; } - data[toDataIndex] |= trailingOneBits(toIndex); + if ((toIndex & 31) != 0) { + data[toDataIndex] |= trailingOneBits(toIndex); + } } } @@ -226,7 +230,9 @@ public class TBitSet extends TObject implements TCloneable, TSerializable { for (int i = fromDataIndex + 1; i < toDataIndex; ++i) { data[i] = 0; } - data[toDataIndex] &= trailingZeroBits(toIndex); + if ((toIndex & 31) != 0) { + data[toDataIndex] &= trailingZeroBits(toIndex); + } } recalculateLength(); } @@ -422,7 +428,7 @@ public class TBitSet extends TObject implements TCloneable, TSerializable { public void or(TBitSet set) { length = TMath.max(length, set.length); ensureCapacity((length + 31) / 32); - int sz = TMath.min(data.length, set.length); + int sz = TMath.min(data.length, set.data.length); for (int i = 0; i < sz; ++i) { data[i] |= set.data[i]; } @@ -431,7 +437,7 @@ public class TBitSet extends TObject implements TCloneable, TSerializable { public void xor(TBitSet set) { length = TMath.max(length, set.length); ensureCapacity((length + 31) / 32); - int sz = TMath.min(data.length, set.length); + int sz = TMath.min(data.length, set.data.length); for (int i = 0; i < sz; ++i) { data[i] ^= set.data[i]; } diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index 3d4bd744f..9dd0ae501 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -95,6 +95,7 @@ import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.RaiseInstruction; import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.lowlevel.BoundCheckInsertion; import org.teavm.model.util.AsyncMethodFinder; import org.teavm.model.util.ProgramUtils; import org.teavm.vm.BuildTarget; @@ -126,6 +127,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { private List customVirtualMethods = new ArrayList<>(); private int topLevelNameLimit = 10000; private AstDependencyExtractor dependencyExtractor = new AstDependencyExtractor(); + private boolean strict; @Override public List getTransformers() { @@ -207,6 +209,10 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { this.topLevelNameLimit = topLevelNameLimit; } + public void setStrict(boolean strict) { + this.strict = strict; + } + @Override public boolean requiresRegisterAllocation() { return true; @@ -274,6 +280,14 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { exceptionCons.getVariable(1).propagate(stringType); exceptionCons.use(); + if (strict) { + exceptionCons = dependencyAnalyzer.linkMethod(new MethodReference( + ArrayIndexOutOfBoundsException.class, "", void.class)); + exceptionCons.getVariable(0).propagate(dependencyAnalyzer.getType( + ArrayIndexOutOfBoundsException.class.getName())); + exceptionCons.use(); + } + if (stackTraceIncluded) { includeStackTraceMethods(dependencyAnalyzer); } @@ -324,6 +338,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { @Override public void beforeOptimizations(Program program, MethodReader method) { + if (strict) { + new BoundCheckInsertion().transformProgram(program, method.getReference()); + } } @Override diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java index ef6ae3dea..f27c50dbe 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/NameFrequencyEstimator.java @@ -20,6 +20,7 @@ import org.teavm.ast.AssignmentStatement; import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.AsyncMethodPart; import org.teavm.ast.BinaryExpr; +import org.teavm.ast.BoundCheckExpr; import org.teavm.ast.ConstantExpr; import org.teavm.ast.InitClassStatement; import org.teavm.ast.InstanceOfExpr; @@ -303,4 +304,16 @@ class NameFrequencyEstimator extends RecursiveVisitor implements MethodNodeVisit consumer.consumeFunction("$rt_isInstance"); } } + + @Override + public void visit(BoundCheckExpr expr) { + super.visit(expr); + if (expr.getArray() != null && expr.getIndex() != null) { + consumer.consumeFunction("$rt_checkBounds"); + } else if (expr.getArray() != null) { + consumer.consumeFunction("$rt_checkUpperBound"); + } else if (expr.isLower()) { + consumer.consumeFunction("$rt_checkLowerBound"); + } + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 88f90b7c2..a54cc08cf 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -250,7 +250,8 @@ public class Renderer implements RenderingManager { private void renderRuntimeAliases() throws IOException { String[] names = { "$rt_throw", "$rt_compare", "$rt_nullCheck", "$rt_cls", "$rt_createArray", "$rt_isInstance", "$rt_nativeThread", "$rt_suspending", "$rt_resuming", "$rt_invalidPointer", - "$rt_s", "$rt_eraseClinit", "$rt_imul", "$rt_wrapException" }; + "$rt_s", "$rt_eraseClinit", "$rt_imul", "$rt_wrapException", "$rt_checkBounds", + "$rt_checkUpperBound", "$rt_checkLowerBound" }; boolean first = true; for (String name : names) { if (!first) { @@ -413,8 +414,7 @@ public class Renderer implements RenderingManager { ScopedName name = naming.getNameForClassInit(cls.getName()); renderFunctionDeclaration(name); - writer.append("()").ws() - .append("{").softNewLine().indent(); + writer.append("()").ws().append("{").softNewLine().indent(); if (isAsync) { writer.append("var ").append(context.pointerName()).ws().append("=").ws() diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java index d9cc67595..e2613aee8 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeRenderer.java @@ -26,6 +26,7 @@ import org.mozilla.javascript.ast.AstRoot; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReader; @@ -46,6 +47,8 @@ public class RuntimeRenderer { "", String.class, String.class, String.class, int.class, void.class); private static final MethodReference SET_STACK_TRACE_METHOD = new MethodReference(Throwable.class, "setStackTrace", StackTraceElement[].class, void.class); + private static final MethodReference AIOOBE_INIT_METHOD = new MethodReference(ArrayIndexOutOfBoundsException.class, + "", void.class); private final ClassReaderSource classSource; private final SourceWriter writer; @@ -69,6 +72,7 @@ public class RuntimeRenderer { renderRuntimeCreateException(); renderCreateStackTraceElement(); renderSetStackTrace(); + renderThrowAIOOBE(); } catch (IOException e) { throw new RenderingException("IO error", e); } @@ -249,4 +253,18 @@ public class RuntimeRenderer { } writer.outdent().append("}").newLine(); } + + private void renderThrowAIOOBE() throws IOException { + writer.append("function $rt_throwAIOOBE()").ws().append("{").indent().softNewLine(); + + ClassReader cls = classSource.get(AIOOBE_INIT_METHOD.getClassName()); + if (cls != null) { + MethodReader method = cls.getMethod(AIOOBE_INIT_METHOD.getDescriptor()); + if (method != null && !method.hasModifier(ElementModifier.ABSTRACT)) { + writer.append("$rt_throw(").appendInit(AIOOBE_INIT_METHOD).append("());").softNewLine(); + } + } + + writer.outdent().append("}").newLine(); + } } 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 4e139fbd8..369a78ba5 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 @@ -1564,7 +1564,36 @@ public class StatementRenderer implements ExprVisitor, StatementVisitor { @Override public void visit(BoundCheckExpr expr) { - expr.getIndex().acceptVisitor(this); + try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } + + if (expr.getArray() != null && expr.isLower()) { + writer.appendFunction("$rt_checkBounds").append("("); + } else if (expr.getArray() != null) { + writer.appendFunction("$rt_checkUpperBound").append("("); + } else if (expr.isLower()) { + writer.appendFunction("$rt_checkLowerBound").append("("); + } + + expr.getIndex().acceptVisitor(this); + + if (expr.getArray() != null) { + writer.append(",").ws(); + expr.getArray().acceptVisitor(this); + } + + if (expr.getArray() != null || expr.isLower()) { + writer.append(")"); + } + + if (expr.getLocation() != null) { + popLocation(); + } + } catch (IOException e) { + throw new RenderingException("IO error occurred", e); + } } private class InjectorContextImpl implements InjectorContext { diff --git a/core/src/main/java/org/teavm/model/lowlevel/BoundCheckInsertion.java b/core/src/main/java/org/teavm/model/lowlevel/BoundCheckInsertion.java new file mode 100644 index 000000000..63ecfe56f --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/BoundCheckInsertion.java @@ -0,0 +1,442 @@ +/* + * Copyright 2019 konsoletyper. + * + * 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.model.lowlevel; + +import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.IntHashSet; +import com.carrotsearch.hppc.IntSet; +import com.carrotsearch.hppc.cursors.IntCursor; +import java.util.Arrays; +import org.teavm.model.BasicBlock; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.Variable; +import org.teavm.model.instructions.AbstractInstructionVisitor; +import org.teavm.model.instructions.ArrayLengthInstruction; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.BinaryBranchingInstruction; +import org.teavm.model.instructions.BinaryInstruction; +import org.teavm.model.instructions.BinaryOperation; +import org.teavm.model.instructions.BoundCheckInstruction; +import org.teavm.model.instructions.BranchingInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.GetElementInstruction; +import org.teavm.model.instructions.IntegerConstantInstruction; +import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.instructions.NumericOperandType; +import org.teavm.model.instructions.PutElementInstruction; +import org.teavm.model.instructions.UnwrapArrayInstruction; +import org.teavm.model.util.DominatorWalker; +import org.teavm.model.util.DominatorWalkerCallback; +import org.teavm.model.util.PhiUpdater; + +public class BoundCheckInsertion { + public void transformProgram(Program program, MethodReference methodReference) { + if (program.basicBlockCount() == 0) { + return; + } + + InsertionVisitor visitor = new InsertionVisitor(program.variableCount()); + new DominatorWalker(program).walk(visitor); + if (visitor.changed) { + new PhiUpdater().updatePhis(program, methodReference.parameterCount() + 1); + } + } + + static class InsertionVisitor extends AbstractInstructionVisitor + implements DominatorWalkerCallback { + BlockBounds bounds; + boolean changed; + private boolean[] isConstant; + private boolean[] isConstantSizedArray; + private int[] constantValue; + private IntSet[] upperArrayLengths; + private boolean[] nonNegative; + private int[] map; + private int[] arrayLengthVars; + private int[] arrayLengthReverseVars; + private int[] comparisonLeft; + private int[] comparisonRight; + private int conditionBlock; + private int comparisonValue; + private int comparisonVariable; + private ComparisonMode comparisonMode; + + InsertionVisitor(int variableCount) { + isConstant = new boolean[variableCount]; + isConstantSizedArray = new boolean[variableCount]; + constantValue = new int[variableCount]; + upperArrayLengths = new IntSet[variableCount]; + nonNegative = new boolean[variableCount]; + map = new int[variableCount]; + for (int i = 0; i < variableCount; ++i) { + map[i] = i; + } + arrayLengthVars = new int[variableCount]; + Arrays.fill(arrayLengthVars, -1); + arrayLengthReverseVars = new int[variableCount]; + comparisonLeft = new int[variableCount]; + Arrays.fill(comparisonLeft, -1); + comparisonRight = new int[variableCount]; + } + + @Override + public BlockBounds visit(BasicBlock block) { + bounds = new BlockBounds(); + + if (comparisonMode != null && conditionBlock == block.getIndex()) { + switch (comparisonMode) { + case LESS_THAN_ARRAY_LENGTH: + addArrayBound(comparisonVariable, comparisonValue); + break; + case NON_NEGATIVE: + markAsNonNegative(comparisonVariable); + break; + } + } + + for (Instruction instruction : block) { + instruction.acceptVisitor(this); + } + + bounds.comparisonMode = comparisonMode; + bounds.comparisonValue = comparisonValue; + bounds.comparisonVariable = comparisonVariable; + bounds.conditionBlock = conditionBlock; + + return bounds; + } + + @Override + public void endVisit(BasicBlock block, BlockBounds state) { + IntArrayList addedArrayBounds = state.addedArrayBounds; + int size = addedArrayBounds.size(); + for (int i = 0; i < size; i += 2) { + int index = addedArrayBounds.get(i); + int array = addedArrayBounds.get(i + 1); + upperArrayLengths[index].removeAll(array); + } + + for (IntCursor cursor : state.nonNegatives) { + nonNegative[cursor.value] = false; + } + + comparisonMode = state.comparisonMode; + comparisonValue = state.comparisonValue; + comparisonVariable = state.comparisonVariable; + conditionBlock = state.conditionBlock; + } + + @Override + public void visit(JumpInstruction insn) { + prepareJump(); + } + + @Override + public void visit(BranchingInstruction insn) { + prepareJump(); + int operand = index(insn.getOperand()); + int left = comparisonLeft[operand]; + int right = comparisonRight[operand]; + + if (left >= 0) { + switch (insn.getCondition()) { + case LESS: + if (arrayLengthVars[right] >= 0) { + comparisonMode = ComparisonMode.LESS_THAN_ARRAY_LENGTH; + comparisonValue = arrayLengthVars[right]; + comparisonVariable = left; + conditionBlock = insn.getConsequent().getIndex(); + } else if (isConstant[left] && constantValue[left] >= -1) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = right; + conditionBlock = insn.getConsequent().getIndex(); + } else if (isConstant[right] && constantValue[right] >= 0) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = left; + conditionBlock = insn.getAlternative().getIndex(); + } + break; + + case GREATER_OR_EQUAL: + if (arrayLengthVars[right] >= 0) { + comparisonMode = ComparisonMode.LESS_THAN_ARRAY_LENGTH; + comparisonValue = arrayLengthVars[right]; + comparisonVariable = left; + conditionBlock = insn.getAlternative().getIndex(); + } else if (isConstant[left] && constantValue[left] >= -1) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = right; + conditionBlock = insn.getAlternative().getIndex(); + } else if (isConstant[right] && constantValue[right] >= 0) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = left; + conditionBlock = insn.getConsequent().getIndex(); + } + break; + + case GREATER: + if (arrayLengthVars[left] >= 0) { + comparisonMode = ComparisonMode.LESS_THAN_ARRAY_LENGTH; + comparisonValue = arrayLengthVars[left]; + comparisonVariable = right; + conditionBlock = insn.getConsequent().getIndex(); + } else if (isConstant[left] && constantValue[left] >= 0) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = right; + conditionBlock = insn.getAlternative().getIndex(); + } else if (isConstant[right] && constantValue[right] >= -1) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = left; + conditionBlock = insn.getConsequent().getIndex(); + } + break; + + case LESS_OR_EQUAL: + if (arrayLengthVars[left] >= 0) { + comparisonMode = ComparisonMode.LESS_THAN_ARRAY_LENGTH; + comparisonValue = arrayLengthVars[left]; + comparisonVariable = right; + conditionBlock = insn.getAlternative().getIndex(); + } else if (isConstant[left] && constantValue[left] >= 0) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = right; + conditionBlock = insn.getConsequent().getIndex(); + } else if (isConstant[right] && constantValue[right] >= -1) { + comparisonMode = ComparisonMode.NON_NEGATIVE; + comparisonVariable = left; + conditionBlock = insn.getAlternative().getIndex(); + } + break; + + default: + break; + } + } + } + + @Override + public void visit(BinaryBranchingInstruction insn) { + prepareJump(); + } + + private void prepareJump() { + conditionBlock = -1; + comparisonMode = null; + comparisonValue = 0; + } + + @Override + public void visit(IntegerConstantInstruction insn) { + int receiver = index(insn.getReceiver()); + isConstant[receiver] = true; + constantValue[receiver] = insn.getConstant(); + } + + @Override + public void visit(BinaryInstruction insn) { + int first = index(insn.getFirstOperand()); + int second = index(insn.getSecondOperand()); + int receiver = index(insn.getReceiver()); + if (isConstant[first] && isConstant[second]) { + int a = constantValue[first]; + int b = constantValue[second]; + int r; + switch (insn.getOperation()) { + case ADD: + r = a + b; + break; + case SUBTRACT: + r = a - b; + break; + case COMPARE: + r = Integer.compare(a, b); + break; + case DIVIDE: + r = a / b; + break; + case MODULO: + r = a % b; + break; + case MULTIPLY: + r = a * b; + break; + case AND: + r = a & b; + break; + case OR: + r = a | b; + break; + case XOR: + r = a ^ b; + break; + case SHIFT_LEFT: + r = a << b; + break; + case SHIFT_RIGHT: + r = a >> b; + break; + case SHIFT_RIGHT_UNSIGNED: + r = a >>> b; + break; + default: + return; + } + + isConstant[receiver] = true; + constantValue[receiver] = r; + } else if (insn.getOperation() == BinaryOperation.COMPARE + && insn.getOperandType() == NumericOperandType.INT) { + comparisonLeft[receiver] = first; + comparisonRight[receiver] = second; + } + } + + @Override + public void visit(ConstructArrayInstruction insn) { + int size = index(insn.getSize()); + int receiver = index(insn.getReceiver()); + if (isConstant[size]) { + isConstantSizedArray[receiver] = true; + constantValue[receiver] = constantValue[size]; + } + arrayLengthVars[size] = receiver; + arrayLengthReverseVars[receiver] = size; + } + + @Override + public void visit(ArrayLengthInstruction insn) { + int array = index(insn.getArray()); + int receiver = index(insn.getReceiver()); + if (arrayLengthVars[receiver] >= 0) { + map[receiver] = arrayLengthReverseVars[receiver]; + } else { + if (isConstantSizedArray[array]) { + isConstant[receiver] = true; + constantValue[receiver] = constantValue[array]; + } + arrayLengthVars[receiver] = array; + arrayLengthReverseVars[array] = receiver; + } + } + + @Override + public void visit(AssignInstruction insn) { + assign(insn.getAssignee(), insn.getReceiver()); + } + + @Override + public void visit(UnwrapArrayInstruction insn) { + assign(insn.getArray(), insn.getReceiver()); + } + + private void assign(Variable from, Variable to) { + map[to.getIndex()] = map[from.getIndex()]; + } + + @Override + public void visit(GetElementInstruction insn) { + tryInsertBoundCheck(insn.getIndex(), insn.getArray(), insn); + } + + @Override + public void visit(PutElementInstruction insn) { + tryInsertBoundCheck(insn.getIndex(), insn.getArray(), insn); + } + + private void tryInsertBoundCheck(Variable indexVar, Variable arrayVar, Instruction instruction) { + boolean lower = true; + boolean upper = true; + int index = index(indexVar); + int array = index(arrayVar); + if (isConstant[index]) { + if (isConstantSizedArray[array]) { + upper = false; + } + } + if (upper) { + IntSet bounds = upperArrayLengths[index]; + if (bounds != null && bounds.contains(array)) { + upper = false; + } + } + + if (lower) { + if ((isConstant[index] && constantValue[index] >= 0) || nonNegative[index]) { + lower = false; + } + } + + if (upper) { + addArrayBound(index, array); + } + markAsNonNegative(index); + + if (lower || upper) { + BoundCheckInstruction boundCheck = new BoundCheckInstruction(); + if (lower) { + boundCheck.setLower(true); + } + if (upper) { + boundCheck.setArray(arrayVar); + } + boundCheck.setIndex(indexVar); + boundCheck.setReceiver(indexVar); + boundCheck.setLocation(instruction.getLocation()); + instruction.insertPrevious(boundCheck); + changed = true; + } + } + + private void addArrayBound(int index, int array) { + IntSet upperSet = upperArrayLengths[index]; + if (upperSet == null) { + upperSet = new IntHashSet(); + upperArrayLengths[index] = upperSet; + } + if (upperSet.add(array)) { + bounds.addedArrayBounds.add(index, array); + } + } + + private void markAsNonNegative(int index) { + if (!nonNegative[index]) { + nonNegative[index] = true; + bounds.nonNegatives.add(index); + } + } + + private int index(Variable var) { + return map[var.getIndex()]; + } + } + + static class BlockBounds { + IntArrayList addedArrayBounds = new IntArrayList(); + IntArrayList nonNegatives = new IntArrayList(); + + int conditionBlock; + int comparisonValue; + int comparisonVariable; + ComparisonMode comparisonMode; + } + + enum ComparisonMode { + LESS_THAN_ARRAY_LENGTH, + NON_NEGATIVE + } +} diff --git a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java index 159acc620..1edbada98 100644 --- a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java +++ b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java @@ -37,6 +37,7 @@ import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.BinaryBranchingInstruction; import org.teavm.model.instructions.BinaryInstruction; import org.teavm.model.instructions.BinaryOperation; +import org.teavm.model.instructions.BoundCheckInstruction; import org.teavm.model.instructions.BranchingInstruction; import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.CastIntegerInstruction; @@ -781,5 +782,15 @@ public class GlobalValueNumbering implements MethodOptimization { int val = replaceMap[insn.getObjectRef().getIndex()]; insn.setObjectRef(program.variableAt(val)); } + + @Override + public void visit(BoundCheckInstruction insn) { + int index = replaceMap[insn.getIndex().getIndex()]; + insn.setIndex(program.variableAt(index)); + if (insn.getArray() != null) { + int array = replaceMap[insn.getArray().getIndex()]; + insn.setArray(program.variableAt(array)); + } + } }; } diff --git a/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java b/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java index d49d792f6..b6dee5abd 100644 --- a/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java +++ b/core/src/main/java/org/teavm/model/optimization/UnusedVariableElimination.java @@ -119,11 +119,11 @@ public class UnusedVariableElimination implements MethodOptimization { return false; } - private static class InstructionOptimizer extends AbstractInstructionVisitor { + static class InstructionOptimizer extends AbstractInstructionVisitor { private boolean[] used; boolean eliminate; - public InstructionOptimizer(boolean[] used) { + InstructionOptimizer(boolean[] used) { this.used = used; } diff --git a/core/src/main/java/org/teavm/model/optimization/VariableEscapeAnalyzer.java b/core/src/main/java/org/teavm/model/optimization/VariableEscapeAnalyzer.java index 2a085925b..8da1fcf91 100644 --- a/core/src/main/java/org/teavm/model/optimization/VariableEscapeAnalyzer.java +++ b/core/src/main/java/org/teavm/model/optimization/VariableEscapeAnalyzer.java @@ -113,5 +113,12 @@ public final class VariableEscapeAnalyzer { public void visit(MonitorExitInstruction insn) { escaping[insn.getObjectRef().getIndex()] = true; } + + @Override + public void visit(BoundCheckInstruction insn) { + if (insn.getArray() != null) { + escaping[insn.getArray().getIndex()] = true; + } + } } } diff --git a/core/src/main/java/org/teavm/model/text/InstructionStringifier.java b/core/src/main/java/org/teavm/model/text/InstructionStringifier.java index 18b03691d..5001a670d 100644 --- a/core/src/main/java/org/teavm/model/text/InstructionStringifier.java +++ b/core/src/main/java/org/teavm/model/text/InstructionStringifier.java @@ -36,6 +36,9 @@ class InstructionStringifier implements InstructionReader { Set occupiedLabels = new HashSet<>(); for (int i = 0; i < program.variableCount(); ++i) { VariableReader var = program.variableAt(i); + if (var == null) { + continue; + } String suggestedName = var.getLabel() != null ? var.getLabel() : Integer.toString(i); if (!occupiedLabels.add(suggestedName)) { int suffix = 1; diff --git a/core/src/main/java/org/teavm/model/text/ListingParser.java b/core/src/main/java/org/teavm/model/text/ListingParser.java index 73c85d61b..943f67969 100644 --- a/core/src/main/java/org/teavm/model/text/ListingParser.java +++ b/core/src/main/java/org/teavm/model/text/ListingParser.java @@ -444,6 +444,7 @@ public class ListingParser { insn.setLower(true); } addInstruction(insn); + break; } default: unexpected(); diff --git a/core/src/main/java/org/teavm/model/util/PhiUpdater.java b/core/src/main/java/org/teavm/model/util/PhiUpdater.java index a9e0b1c01..d25a2ac4c 100644 --- a/core/src/main/java/org/teavm/model/util/PhiUpdater.java +++ b/core/src/main/java/org/teavm/model/util/PhiUpdater.java @@ -58,17 +58,14 @@ import org.teavm.model.instructions.ConstructArrayInstruction; import org.teavm.model.instructions.ConstructInstruction; import org.teavm.model.instructions.ConstructMultiArrayInstruction; import org.teavm.model.instructions.DoubleConstantInstruction; -import org.teavm.model.instructions.EmptyInstruction; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.FloatConstantInstruction; import org.teavm.model.instructions.GetElementInstruction; import org.teavm.model.instructions.GetFieldInstruction; -import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.InstructionVisitor; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.IsInstanceInstruction; -import org.teavm.model.instructions.JumpInstruction; import org.teavm.model.instructions.LongConstantInstruction; import org.teavm.model.instructions.MonitorEnterInstruction; import org.teavm.model.instructions.MonitorExitInstruction; diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 0a318d91c..9addff0b2 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -630,3 +630,21 @@ var $rt_udiv = function(a, b) { var $rt_umod = function(a, b) { return ((a >>> 0) % (b >>> 0)) >>> 0; }; +function $rt_checkBounds(index, array) { + if (index < 0 || index >= array.length) { + $rt_throwAIOOBE(); + } + return index; +} +function $rt_checkUpperBound(index, array) { + if (index >= array.length) { + $rt_throwAIOOBE(); + } + return index; +} +function $rt_checkLowerBound(index) { + if (index < 0) { + $rt_throwAIOOBE(); + } + return index; +} \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java index bb04a803f..aa7dd79c6 100644 --- a/tests/src/test/java/org/teavm/dependency/ClassValueTest.java +++ b/tests/src/test/java/org/teavm/dependency/ClassValueTest.java @@ -74,7 +74,9 @@ public class ClassValueTest { } private DependencyInfo runTest(String methodName) { - TeaVM vm = new TeaVMBuilder(new JavaScriptTarget()).build(); + JavaScriptTarget target = new JavaScriptTarget(); + target.setStrict(true); + TeaVM vm = new TeaVMBuilder(target).build(); vm.add(new DependencyTestPatcher(getClass().getName(), methodName)); vm.installPlugins(); vm.entryPoint(getClass().getName()); diff --git a/tests/src/test/java/org/teavm/incremental/IncrementalTest.java b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java index 66ae0ca7e..f22bc176f 100644 --- a/tests/src/test/java/org/teavm/incremental/IncrementalTest.java +++ b/tests/src/test/java/org/teavm/incremental/IncrementalTest.java @@ -207,6 +207,7 @@ public class IncrementalTest { vm.setProgramCache(programCache); target.setAstCache(astCache); target.setMinifying(false); + target.setStrict(true); vm.add(new EntryPointTransformer(entryPoint)); vm.entryPoint(EntryPoint.class.getName()); vm.installPlugins(); diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java index 74683fb24..d897668d8 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java @@ -770,7 +770,8 @@ public class CodeServlet extends HttpServlet { jsTarget.setMinifying(false); jsTarget.setAstCache(astCache); jsTarget.setDebugEmitter(debugInformationBuilder); - jsTarget.setTopLevelNameLimit(500); + jsTarget.setTopLevelNameLimit(2000); + jsTarget.setStrict(true); vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); vm.setCacheStatus(classSource); vm.addVirtualMethods(m -> true); diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 6bc315ea1..babb0dcdf 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -565,6 +565,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { DebugInformationBuilder debugEmitter = new DebugInformationBuilder(new ReferenceCache()); Supplier targetSupplier = () -> { JavaScriptTarget target = new JavaScriptTarget(); + target.setStrict(true); if (decodeStack) { target.setDebugEmitter(debugEmitter); target.setStackTraceIncluded(true);