From 2ba4ce097757e9500ea71296f070f674355034dd Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 29 May 2019 22:49:13 +0300 Subject: [PATCH] Fix inability to synchronize using array object See #393 --- .../javascript/rendering/AstVisitor.java | 433 ++++++++++++++++++ .../rendering/RuntimeAstTransformer.java | 59 +++ .../javascript/rendering/RuntimeRenderer.java | 1 + .../org/teavm/backend/javascript/array.js | 3 +- tests/src/test/java/org/teavm/vm/VMTest.java | 9 +- 5 files changed, 503 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java b/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java new file mode 100644 index 000000000..b34bfcac1 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/AstVisitor.java @@ -0,0 +1,433 @@ +/* + * 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.backend.javascript.rendering; + +import org.mozilla.javascript.Node; +import org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.ArrayComprehension; +import org.mozilla.javascript.ast.ArrayComprehensionLoop; +import org.mozilla.javascript.ast.ArrayLiteral; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.AstRoot; +import org.mozilla.javascript.ast.Block; +import org.mozilla.javascript.ast.BreakStatement; +import org.mozilla.javascript.ast.CatchClause; +import org.mozilla.javascript.ast.ConditionalExpression; +import org.mozilla.javascript.ast.ContinueStatement; +import org.mozilla.javascript.ast.DoLoop; +import org.mozilla.javascript.ast.ElementGet; +import org.mozilla.javascript.ast.EmptyStatement; +import org.mozilla.javascript.ast.ExpressionStatement; +import org.mozilla.javascript.ast.ForInLoop; +import org.mozilla.javascript.ast.ForLoop; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.FunctionNode; +import org.mozilla.javascript.ast.GeneratorExpression; +import org.mozilla.javascript.ast.GeneratorExpressionLoop; +import org.mozilla.javascript.ast.IfStatement; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.LabeledStatement; +import org.mozilla.javascript.ast.LetNode; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.NewExpression; +import org.mozilla.javascript.ast.NumberLiteral; +import org.mozilla.javascript.ast.ObjectLiteral; +import org.mozilla.javascript.ast.ObjectProperty; +import org.mozilla.javascript.ast.ParenthesizedExpression; +import org.mozilla.javascript.ast.PropertyGet; +import org.mozilla.javascript.ast.RegExpLiteral; +import org.mozilla.javascript.ast.ReturnStatement; +import org.mozilla.javascript.ast.Scope; +import org.mozilla.javascript.ast.StringLiteral; +import org.mozilla.javascript.ast.SwitchCase; +import org.mozilla.javascript.ast.SwitchStatement; +import org.mozilla.javascript.ast.ThrowStatement; +import org.mozilla.javascript.ast.TryStatement; +import org.mozilla.javascript.ast.UnaryExpression; +import org.mozilla.javascript.ast.VariableDeclaration; +import org.mozilla.javascript.ast.VariableInitializer; +import org.mozilla.javascript.ast.WhileLoop; + +public class AstVisitor { + public void accept(AstNode node) { + switch (node.getType()) { + case Token.SCRIPT: + visitRoot((AstRoot) node); + break; + case Token.CALL: + case Token.NEW: + visitFunctionCall((FunctionCall) node); + break; + case Token.FUNCTION: + visitFunction((FunctionNode) node); + break; + case Token.ARRAYCOMP: + visitArrayComprehension((ArrayComprehension) node); + break; + case Token.GETPROP: + visitPropertyGet((PropertyGet) node); + break; + case Token.GENEXPR: + visitGenerator((GeneratorExpression) node); + break; + case Token.NUMBER: + visitNumber((NumberLiteral) node); + break; + case Token.STRING: + visitString((StringLiteral) node); + break; + case Token.TRUE: + visitTrue(node); + break; + case Token.FALSE: + visitFalse(node); + break; + case Token.THIS: + visitThis(node); + break; + case Token.NULL: + visitNull(node); + break; + case Token.NAME: + visitName((Name) node); + break; + case Token.REGEXP: + visitRegexp((RegExpLiteral) node); + break; + case Token.OBJECTLIT: + visitObjectLiteral((ObjectLiteral) node); + break; + case Token.ARRAYLIT: + visitArrayLiteral((ArrayLiteral) node); + break; + case Token.BLOCK: + if (node instanceof Block) { + visitBlock((Block) node); + } else if (node instanceof Scope) { + visitScope((Scope) node); + } + break; + case Token.HOOK: + visitConditionalExpr((ConditionalExpression) node); + break; + case Token.GETELEM: + visitElementGet((ElementGet) node); + break; + case Token.LETEXPR: + visitLet((LetNode) node); + break; + case Token.LP: + visitParenthesized((ParenthesizedExpression) node); + break; + case Token.EMPTY: + visitEmpty((EmptyStatement) node); + break; + case Token.EXPR_VOID: + case Token.EXPR_RESULT: + if (node instanceof ExpressionStatement) { + visitExpressionStatement((ExpressionStatement) node); + } else if (node instanceof LabeledStatement) { + visitLabeledStatement((LabeledStatement) node); + } + break; + case Token.BREAK: + visitBreak((BreakStatement) node); + break; + case Token.CONTINUE: + visitContinue((ContinueStatement) node); + break; + case Token.RETURN: + visitReturn((ReturnStatement) node); + break; + case Token.DO: + visitDo((DoLoop) node); + break; + case Token.FOR: + if (node instanceof ForInLoop) { + visitForIn((ForInLoop) node); + } else if (node instanceof ForLoop) { + visitFor((ForLoop) node); + } + break; + case Token.IF: + visitIf((IfStatement) node); + break; + case Token.SWITCH: + visitSwitch((SwitchStatement) node); + break; + case Token.THROW: + visitThrow((ThrowStatement) node); + break; + case Token.TRY: + visitTry((TryStatement) node); + break; + case Token.CONST: + case Token.VAR: + case Token.LET: + visitVariableDeclaration((VariableDeclaration) node); + break; + case Token.WHILE: + visitWhile((WhileLoop) node); + break; + default: + if (node instanceof InfixExpression) { + visitInfix((InfixExpression) node); + } else if (node instanceof UnaryExpression) { + visitUnary((UnaryExpression) node); + } + break; + } + } + + protected void visitRoot(AstRoot node) { + for (Node child : node) { + accept((AstNode) child); + } + } + + protected void visitBlock(Block node) { + for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { + accept((AstNode) child); + } + } + + protected void visitScope(Scope node) { + for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { + accept((AstNode) child); + } + } + + protected void visitLabeledStatement(LabeledStatement node) { + accept(node.getStatement()); + } + + protected void visitBreak(BreakStatement node) { + } + + protected void visitContinue(ContinueStatement node) { + } + + protected void visitReturn(ReturnStatement node) { + if (node.getReturnValue() != null) { + accept(node.getReturnValue()); + } + } + + protected void visitThrow(ThrowStatement node) { + accept(node.getExpression()); + } + + protected void visitDo(DoLoop node) { + accept(node.getBody()); + accept(node.getCondition()); + } + + protected void visitForIn(ForInLoop node) { + accept(node.getIterator()); + accept(node.getIteratedObject()); + accept(node.getBody()); + } + + protected void visitFor(ForLoop node) { + accept(node.getInitializer()); + accept(node.getCondition()); + accept(node.getIncrement()); + accept(node.getBody()); + } + + protected void visitWhile(WhileLoop node) { + accept(node.getCondition()); + accept(node.getBody()); + } + + protected void visitIf(IfStatement node) { + accept(node.getCondition()); + accept(node.getThenPart()); + if (node.getElsePart() != null) { + accept(node.getElsePart()); + } + } + + protected void visitSwitch(SwitchStatement node) { + accept(node.getExpression()); + for (SwitchCase sc : node.getCases()) { + if (sc.getExpression() != null) { + accept(sc.getExpression()); + } + if (sc.getStatements() != null) { + for (AstNode stmt : sc.getStatements()) { + accept(stmt); + } + } + } + } + + protected void visitTry(TryStatement node) { + accept(node.getTryBlock()); + for (CatchClause cc : node.getCatchClauses()) { + if (cc.getCatchCondition() != null) { + accept(cc.getCatchCondition()); + } + accept(cc.getBody()); + } + if (node.getFinallyBlock() != null) { + accept(node.getFinallyBlock()); + } + } + + protected void visitVariableDeclaration(VariableDeclaration node) { + for (int i = 1; i < node.getVariables().size(); ++i) { + visitVariableInitializer(node.getVariables().get(i)); + } + } + + protected void visitVariableInitializer(VariableInitializer node) { + accept(node.getTarget()); + if (node.getInitializer() != null) { + accept(node.getInitializer()); + } + } + + protected void visitExpressionStatement(ExpressionStatement node) { + accept(node.getExpression()); + } + + protected void visitElementGet(ElementGet node) { + accept(node.getTarget()); + accept(node.getElement()); + } + + protected void visitPropertyGet(PropertyGet node) { + accept(node.getLeft()); + accept(node.getRight()); + } + + protected void visitFunctionCall(FunctionCall node) { + accept(node.getTarget()); + for (AstNode arg : node.getArguments()) { + accept(arg); + } + if (node instanceof NewExpression) { + NewExpression newExpr = (NewExpression) node; + if (newExpr.getInitializer() != null) { + accept(newExpr.getInitializer()); + } + } + } + + protected void visitConditionalExpr(ConditionalExpression node) { + accept(node.getTestExpression()); + accept(node.getTrueExpression()); + accept(node.getFalseExpression()); + } + + protected void visitArrayComprehension(ArrayComprehension node) { + for (ArrayComprehensionLoop loop : node.getLoops()) { + accept(loop.getIterator()); + accept(loop.getIteratedObject()); + } + if (node.getFilter() != null) { + accept(node.getFilter()); + } + accept(node.getResult()); + } + + protected void visitGenerator(GeneratorExpression node) { + for (GeneratorExpressionLoop loop : node.getLoops()) { + accept(loop.getIterator()); + accept(loop.getIteratedObject()); + } + if (node.getFilter() != null) { + accept(node.getFilter()); + } + accept(node.getResult()); + } + + protected void visitNumber(NumberLiteral node) { + } + + protected void visitString(StringLiteral node) { + } + + protected void visitThis(AstNode node) { + } + + protected void visitTrue(AstNode node) { + } + + protected void visitFalse(AstNode node) { + } + + protected void visitNull(AstNode node) { + } + + protected void visitEmpty(EmptyStatement node) { + } + + protected void visitName(Name node) { + } + + protected void visitRegexp(RegExpLiteral node) { + } + + protected void visitArrayLiteral(ArrayLiteral node) { + for (AstNode element : node.getElements()) { + accept(element); + } + } + + protected void visitObjectLiteral(ObjectLiteral node) { + if (node.getElements() != null) { + for (ObjectProperty property : node.getElements()) { + visitObjectProperty(property); + } + } + } + + protected void visitObjectProperty(ObjectProperty node) { + accept(node.getLeft()); + accept(node.getRight()); + } + + protected void visitFunction(FunctionNode node) { + if (node.getFunctionName() != null) { + accept(node.getFunctionName()); + } + for (AstNode param : node.getParams()) { + accept(param); + } + + accept(node.getBody()); + } + + protected void visitLet(LetNode node) { + accept(node.getVariables()); + accept(node.getBody()); + } + + protected void visitParenthesized(ParenthesizedExpression node) { + accept(node.getExpression()); + } + + protected void visitUnary(UnaryExpression node) { + accept(node.getOperand()); + } + + protected void visitInfix(InfixExpression node) { + accept(node.getLeft()); + accept(node.getRight()); + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java new file mode 100644 index 000000000..4df67d9c3 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RuntimeAstTransformer.java @@ -0,0 +1,59 @@ +/* + * 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.backend.javascript.rendering; + +import org.mozilla.javascript.Token; +import org.mozilla.javascript.ast.AstNode; +import org.mozilla.javascript.ast.ExpressionStatement; +import org.mozilla.javascript.ast.FunctionCall; +import org.mozilla.javascript.ast.InfixExpression; +import org.mozilla.javascript.ast.KeywordLiteral; +import org.mozilla.javascript.ast.Name; +import org.mozilla.javascript.ast.PropertyGet; +import org.teavm.backend.javascript.codegen.NamingStrategy; +import org.teavm.model.FieldReference; + +public class RuntimeAstTransformer extends AstVisitor { + private static final FieldReference MONITOR_FIELD = new FieldReference( + "java.lang.Object", "monitor"); + private NamingStrategy names; + + public RuntimeAstTransformer(NamingStrategy names) { + this.names = names; + } + + @Override + protected void visitExpressionStatement(ExpressionStatement node) { + AstNode expression = node.getExpression(); + if (expression.getType() == Token.CALL) { + FunctionCall call = (FunctionCall) expression; + if (call.getTarget().getType() == Token.NAME) { + String id = ((Name) call.getTarget()).getIdentifier(); + if (id.equals("$rt_initMonitorField")) { + AstNode arg = call.getArguments().get(0); + accept(arg); + String fieldId = names.getNameFor(MONITOR_FIELD); + PropertyGet propertyGet = new PropertyGet(arg, new Name(0, fieldId)); + KeywordLiteral nullExpr = new KeywordLiteral(0, 0, Token.NULL); + InfixExpression assign = new InfixExpression(Token.ASSIGN, propertyGet, nullExpr, 0); + node.setExpression(assign); + return; + } + } + } + super.visitExpressionStatement(node); + } +} 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 31c5e4854..698ab36f2 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 @@ -76,6 +76,7 @@ public class RuntimeRenderer { public void renderHandWrittenRuntime(String name) throws IOException { AstRoot ast = parseRuntime(name); ast.visit(new StringConstantElimination()); + new RuntimeAstTransformer(writer.getNaming()).accept(ast); AstWriter astWriter = new AstWriter(writer); astWriter.hoist(ast); astWriter.print(ast); diff --git a/core/src/main/resources/org/teavm/backend/javascript/array.js b/core/src/main/resources/org/teavm/backend/javascript/array.js index 6a4408610..0750f401e 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/array.js +++ b/core/src/main/resources/org/teavm/backend/javascript/array.js @@ -15,9 +15,10 @@ */ function $rt_array(cls, data) { + $rt_initMonitorField(this); + this.$id$ = 0; this.type = cls; this.data = data; - this.$id$ = 0; this.constructor = $rt_arraycls(cls); } $rt_array.prototype = Object.create($rt_objcls().prototype); diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index e1677500b..309b52290 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -473,7 +473,6 @@ public class VMTest { assertEquals("foo", b[0]); } - @Test public void stringConstantsInBaseClass() { new DerivedClassWithConstantFields(); @@ -506,4 +505,12 @@ public class VMTest { } assertTrue("Exception was not caught", exceptionCaught); } + + @Test + public void arrayMonitor() throws InterruptedException { + int[] array = { 1, 2, 3 }; + synchronized (array) { + array.wait(1); + } + } } \ No newline at end of file