diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 575c3be1b..c2336042c 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -73,6 +73,18 @@ public class JCLPlugin implements TeaVMPlugin { ValueType.arrayOf(ValueType.object("java.lang.Object")), ValueType.object("java.lang.invoke.CallSite")), lms); + StringConcatFactorySubstritutor stringConcatSubstitutor = new StringConcatFactorySubstritutor(); + host.add(new MethodReference("java.lang.invoke.StringConcatFactory", "makeConcat", + ValueType.object("java.lang.invoke.MethodHandles$Lookup"), ValueType.object("java.lang.String"), + ValueType.object("java.lang.invoke.MethodType"), ValueType.object("java.lang.invoke.CallSite")), + stringConcatSubstitutor); + host.add(new MethodReference("java.lang.invoke.StringConcatFactory", "makeConcatWithConstants", + ValueType.object("java.lang.invoke.MethodHandles$Lookup"), ValueType.object("java.lang.String"), + ValueType.object("java.lang.invoke.MethodType"), ValueType.object("java.lang.String"), + ValueType.arrayOf(ValueType.object("java.lang.Object")), + ValueType.object("java.lang.invoke.CallSite")), + stringConcatSubstitutor); + if (!isBootstrap()) { host.add(new ScalaHacks()); } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/StringConcatFactorySubstritutor.java b/classlib/src/main/java/org/teavm/classlib/impl/StringConcatFactorySubstritutor.java new file mode 100644 index 000000000..6bdf37bb9 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/StringConcatFactorySubstritutor.java @@ -0,0 +1,125 @@ +/* + * Copyright 2018 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.classlib.impl; + +import org.teavm.dependency.BootstrapMethodSubstitutor; +import org.teavm.dependency.DynamicCallSite; +import org.teavm.model.MethodReference; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.ValueType; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.model.emit.ValueEmitter; + +public class StringConcatFactorySubstritutor implements BootstrapMethodSubstitutor { + private static final String STRING_BUILDER = "java.lang.StringBuilder"; + private static final char VALUE_ARGUMENT = '\1'; + private static final char CONST_ARGUMENT = '\2'; + + @Override + public ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter pe) { + ValueEmitter sb = pe.construct(STRING_BUILDER); + + if (callSite.getBootstrapMethod().getName().equals("makeConcatWithConstants")) { + appendArgumentWithRecipe(sb, callSite); + } else { + appendSimpleArguments(sb, callSite); + } + + return sb.invokeSpecial("toString", ValueType.object("java.lang.String")); + } + + private ValueEmitter appendSimpleArguments(ValueEmitter sb, DynamicCallSite callSite) { + int parameterCount = callSite.getCalledMethod().parameterCount(); + for (int i = 0; i < parameterCount; ++i) { + sb = appendArgument(sb, callSite.getCalledMethod().parameterType(i), callSite.getArguments().get(i)); + } + return sb; + } + + private ValueEmitter appendArgumentWithRecipe(ValueEmitter sb, DynamicCallSite callSite) { + String recipe = callSite.getBootstrapArguments().get(0).getString(); + int charCount = recipe.length(); + int constantIndex = 0; + int valueIndex = 0; + int paramIndex = 0; + StringBuilder acc = new StringBuilder(); + for (int i = 0; i < charCount; ++i) { + char c = recipe.charAt(i); + switch (c) { + case VALUE_ARGUMENT: { + sb = flushAcc(sb, acc); + ValueType type = callSite.getCalledMethod().parameterType(paramIndex++); + sb = appendArgument(sb, type, callSite.getArguments().get(valueIndex++)); + break; + } + case CONST_ARGUMENT: { + sb = flushAcc(sb, acc); + ValueType type = callSite.getCalledMethod().parameterType(paramIndex++); + RuntimeConstant poolConstant = callSite.getBootstrapArguments().get(1 + constantIndex++); + sb = appendArgument(sb, type, constant(sb.getProgramEmitter(), poolConstant)); + break; + } + default: + acc.append(c); + break; + } + } + + sb = flushAcc(sb, acc); + return sb; + } + + private ValueEmitter flushAcc(ValueEmitter sb, StringBuilder acc) { + if (acc.length() == 0) { + return sb; + } else if (acc.length() == 1) { + sb = appendArgument(sb, ValueType.CHARACTER, sb.getProgramEmitter().constant(acc.charAt(0)) + .cast(ValueType.CHARACTER)); + } else { + sb = appendArgument(sb, ValueType.object("java.lang.Object"), + sb.getProgramEmitter().constant(acc.toString())); + } + acc.setLength(0); + return sb; + } + + private ValueEmitter appendArgument(ValueEmitter sb, ValueType type, ValueEmitter argument) { + if (!(type instanceof ValueType.Primitive)) { + type = ValueType.object("java.lang.Object"); + } + MethodReference method = new MethodReference(STRING_BUILDER, "append", type, ValueType.object(STRING_BUILDER)); + return sb.invokeSpecial(method, argument); + } + + private ValueEmitter constant(ProgramEmitter pe, RuntimeConstant value) { + switch (value.getKind()) { + case RuntimeConstant.STRING: + return pe.constant(value.getString()); + case RuntimeConstant.TYPE: + return pe.constant(value.getValueType()); + case RuntimeConstant.INT: + return pe.constant(value.getInt()); + case RuntimeConstant.LONG: + return pe.constant(value.getLong()); + case RuntimeConstant.FLOAT: + return pe.constant(value.getFloat()); + case RuntimeConstant.DOUBLE: + return pe.constant(value.getDouble()); + default: + throw new IllegalArgumentException("Unsupported constant type: " + value.getKind()); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonVisitor.java b/classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonVisitor.java index f28381360..d12c5117e 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonVisitor.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonVisitor.java @@ -28,7 +28,7 @@ class JCLComparisonVisitor extends ClassVisitor { private JCLClass jclClass; public JCLComparisonVisitor(ClassReaderSource classSource, Map packageMap) { - super(Opcodes.ASM5); + super(Opcodes.ASM7); this.classSource = classSource; this.packageMap = packageMap; } diff --git a/core/src/main/java/org/teavm/parsing/Parser.java b/core/src/main/java/org/teavm/parsing/Parser.java index be89d9779..9ec7d1052 100644 --- a/core/src/main/java/org/teavm/parsing/Parser.java +++ b/core/src/main/java/org/teavm/parsing/Parser.java @@ -67,7 +67,7 @@ public class Parser { } public MethodHolder parseMethod(MethodNode node, String fileName) { - MethodNode nodeWithoutJsr = new MethodNode(Opcodes.ASM5, node.access, node.name, node.desc, node.signature, + MethodNode nodeWithoutJsr = new MethodNode(Opcodes.ASM7, node.access, node.name, node.desc, node.signature, node.exceptions.toArray(new String[0])); JSRInlinerAdapter adapter = new JSRInlinerAdapter(nodeWithoutJsr, node.access, node.name, node.desc, node.signature, node.exceptions.toArray(new String[0])); diff --git a/core/src/main/java/org/teavm/parsing/ProgramParser.java b/core/src/main/java/org/teavm/parsing/ProgramParser.java index 2057871ec..1aac6bf6f 100644 --- a/core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -435,7 +435,7 @@ public class ProgramParser { } // TODO: invokedynamic support (a great task, involving not only parser, but every layer of TeaVM) - private MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM5) { + private MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM7) { @Override public void visitVarInsn(int opcode, int local) { switch (opcode) { diff --git a/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java b/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java index 360e746d2..27d794cc1 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java +++ b/core/src/main/java/org/teavm/vm/TeaVMPluginReader.java @@ -206,7 +206,7 @@ final class TeaVMPluginReader { PluginDescriptor descriptor; public PluginDescriptorFiller(PluginDescriptor descriptor) { - super(Opcodes.ASM5); + super(Opcodes.ASM7); this.descriptor = descriptor; } @@ -223,12 +223,12 @@ final class TeaVMPluginReader { } private AnnotationVisitor readClassArray(Consumer resultConsumer) { - return new AnnotationVisitor(Opcodes.ASM5) { + return new AnnotationVisitor(Opcodes.ASM7) { @Override public AnnotationVisitor visitArray(String name) { List values = new ArrayList<>(); if (name.equals("value")) { - return new AnnotationVisitor(Opcodes.ASM5) { + return new AnnotationVisitor(Opcodes.ASM7) { @Override public void visit(String name, Object value) { values.add(((Type) value).getClassName()); diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java index 02f630d69..562c2656a 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingClassLoader.java @@ -129,7 +129,7 @@ public class MetaprogrammingClassLoader extends ClassLoader { boolean compileTime; CompileTimeClassVisitor() { - super(Opcodes.ASM5, null); + super(Opcodes.ASM7, null); } @Override diff --git a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java index 3ea257e9f..8dd83ed00 100644 --- a/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java +++ b/metaprogramming/impl/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingInstrumentation.java @@ -46,7 +46,7 @@ public class MetaprogrammingInstrumentation { class ClassTransformer extends ClassVisitor { ClassTransformer(ClassVisitor cv) { - super(Opcodes.ASM5, cv); + super(Opcodes.ASM7, cv); } @Override @@ -60,7 +60,7 @@ public class MetaprogrammingInstrumentation { private boolean instrumented; MethodTransformer(MethodVisitor mv) { - super(Opcodes.ASM5, mv); + super(Opcodes.ASM7, mv); } @Override diff --git a/pom.xml b/pom.xml index 14684f3e8..91679395c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,8 @@ 2.47.2 2.6.2 2017.3.5 - 6.1.1 + 7.0 + 1.8 1.7.7 false @@ -216,16 +217,16 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.0 - 1.8 - 1.8 + ${java.version} + ${java.version} org.apache.maven.plugins maven-javadoc-plugin - 3.0.0 + 3.0.1 build-javadoc @@ -289,7 +290,7 @@ org.apache.felix maven-bundle-plugin - 2.5.3 + 4.1.0 org.apache.maven.plugins diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index 061781cfa..a75a1932b 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -162,6 +162,16 @@ public class VMTest { assertEquals("SECOND ", ClassWithStaticField.foo(false)); } + @Test + public void stringConcat() { + assertEquals("(23)", surroundWithParentheses(23)); + assertEquals("(42)", surroundWithParentheses(42)); + } + + private String surroundWithParentheses(int value) { + return "(" + value + ")"; + } + @Test public void variableReadInCatchBlock() { int n = foo(); diff --git a/tools/maven/plugin/pom.xml b/tools/maven/plugin/pom.xml index f84d47dbc..4f9926dcd 100644 --- a/tools/maven/plugin/pom.xml +++ b/tools/maven/plugin/pom.xml @@ -85,7 +85,7 @@ org.apache.maven.plugins maven-plugin-plugin - 3.5.1 + 3.6.0 true teavm