From 4724fd78a6e296c27aae91182afeebc84c0c2161 Mon Sep 17 00:00:00 2001 From: "Egor.Ushakov" Date: Tue, 1 Mar 2016 18:04:29 +0300 Subject: [PATCH] IDEA-151950 Decompiler doesn't work for classes from JDK 9 - support java 9 string concatenation --- .../decompiler/ConcatenationHelper.java | 69 ++++++++++++++++-- .../modules/decompiler/ExprProcessor.java | 13 +--- .../decompiler/exps/InvocationExprent.java | 35 ++++++--- .../java/decompiler/SingleClassesTest.java | 4 +- .../classes/java9/TestJava9StringConcat.class | Bin 0 -> 871 bytes testData/classes/pkg/TestStringConcat.class | Bin 0 -> 740 bytes testData/results/TestJava9StringConcat.dec | 27 +++++++ testData/results/TestStringConcat.dec | 31 ++++++++ testData/src/java9/TestJava9StringConcat.java | 26 +++++++ testData/src/pkg/TestStringConcat.class | Bin 0 -> 740 bytes testData/src/pkg/TestStringConcat.java | 26 +++++++ 11 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 testData/classes/java9/TestJava9StringConcat.class create mode 100644 testData/classes/pkg/TestStringConcat.class create mode 100644 testData/results/TestJava9StringConcat.dec create mode 100644 testData/results/TestStringConcat.dec create mode 100644 testData/src/java9/TestJava9StringConcat.java create mode 100644 testData/src/pkg/TestStringConcat.class create mode 100644 testData/src/pkg/TestStringConcat.java diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java b/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java index 74ef0a2..7d7b33d 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,15 @@ package org.jetbrains.java.decompiler.modules.decompiler; import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.struct.consts.PooledConstant; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Set; public class ConcatenationHelper { @@ -52,6 +56,12 @@ public class ConcatenationHelper { exprTmp = iex.getInstance(); } } + else if ("makeConcatWithConstants".equals(iex.getName())) { // java 9 style + List parameters = extractParameters(iex.getBootstrapArguments(), iex); + if (parameters.size() >= 2) { + return createConcatExprent(parameters, expr.bytecode); + } + } } if (exprTmp == null) { @@ -125,20 +135,69 @@ public class ConcatenationHelper { lstOperands.set(i, rep); } } + return createConcatExprent(lstOperands, expr.bytecode); + } + private static Exprent createConcatExprent(List lstOperands, Set bytecode) { // build exprent to return Exprent func = lstOperands.get(0); for (int i = 1; i < lstOperands.size(); i++) { - List lstTmp = new ArrayList(); - lstTmp.add(func); - lstTmp.add(lstOperands.get(i)); - func = new FunctionExprent(FunctionExprent.FUNCTION_STR_CONCAT, lstTmp, expr.bytecode); + func = new FunctionExprent(FunctionExprent.FUNCTION_STR_CONCAT, Arrays.asList(func, lstOperands.get(i)), bytecode); } return func; } + // See StringConcatFactory in jdk sources + private static final char TAG_ARG = '\u0001'; + private static final char TAG_CONST = '\u0002'; + + private static List extractParameters(List bootstrapArguments, InvocationExprent expr) { + List parameters = expr.getLstParameters(); + if (bootstrapArguments != null) { + PooledConstant constant = bootstrapArguments.get(0); + if (constant.type == CodeConstants.CONSTANT_String) { + String recipe = ((PrimitiveConstant)constant).getString(); + + List res = new ArrayList<>(); + StringBuilder acc = new StringBuilder(); + int parameterId = 0; + for (int i = 0; i < recipe.length(); i++) { + char c = recipe.charAt(i); + + if (c == TAG_CONST || c == TAG_ARG) { + // Detected a special tag, flush all accumulated characters + // as a constant first: + if (acc.length() > 0) { + res.add(new ConstExprent(VarType.VARTYPE_STRING, acc.toString(), expr.bytecode)); + acc.setLength(0); + } + if (c == TAG_CONST) { + // skip for now + } + if (c == TAG_ARG) { + res.add(parameters.get(parameterId++)); + } + } + else { + // Not a special characters, this is a constant embedded into + // the recipe itself. + acc.append(c); + } + } + + // Flush the remaining characters as constant: + if (acc.length() > 0) { + res.add(new ConstExprent(VarType.VARTYPE_STRING, acc.toString(), expr.bytecode)); + } + + return res; + } + } + return new ArrayList<>(parameters); + } + private static boolean isAppendConcat(InvocationExprent expr, VarType cltype) { if ("append".equals(expr.getName())) { diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java index cbc66c7..008d0bb 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java @@ -567,21 +567,14 @@ public class ExprProcessor implements CodeConstants { case opc_invokeinterface: case opc_invokedynamic: if (instr.opcode != opc_invokedynamic || instr.bytecode_version >= CodeConstants.BYTECODE_JAVA_7) { - LinkConstant invoke_constant = pool.getLinkConstant(instr.getOperand(0)); - int dynamic_invokation_type = -1; + List bootstrap_arguments = null; if (instr.opcode == opc_invokedynamic && bootstrap != null) { - List bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1); - if (bootstrap_arguments.size() > 1) { // INVOKEDYNAMIC is used not only for lambdas - PooledConstant link = bootstrap_arguments.get(1); - if (link instanceof LinkConstant) { - dynamic_invokation_type = ((LinkConstant)link).index1; - } - } + bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1); } - InvocationExprent exprinv = new InvocationExprent(instr.opcode, invoke_constant, stack, dynamic_invokation_type, bytecode_offsets); + InvocationExprent exprinv = new InvocationExprent(instr.opcode, invoke_constant, bootstrap_arguments, stack, bytecode_offsets); if (exprinv.getDescriptor().ret.type == CodeConstants.TYPE_VOID) { exprlist.add(exprinv); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java index 294a115..3ceb99c 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,6 @@ */ package org.jetbrains.java.decompiler.modules.decompiler.exps; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collections; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; import org.jetbrains.java.decompiler.main.DecompilerContext; @@ -36,6 +29,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.consts.LinkConstant; +import org.jetbrains.java.decompiler.struct.consts.PooledConstant; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.struct.match.MatchEngine; @@ -45,6 +39,9 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.ListStack; import org.jetbrains.java.decompiler.util.TextUtil; +import java.util.*; +import java.util.Map.Entry; + public class InvocationExprent extends Exprent { public static final int INVOKE_SPECIAL = 1; @@ -69,16 +66,22 @@ public class InvocationExprent extends Exprent { private String invokeDynamicClassSuffix; private int invocationTyp = INVOKE_VIRTUAL; private List lstParameters = new ArrayList(); + private List bootstrapArguments; public InvocationExprent() { super(EXPRENT_INVOCATION); } - public InvocationExprent(int opcode, LinkConstant cn, ListStack stack, int dynamicInvocationType, Set bytecodeOffsets) { + public InvocationExprent(int opcode, + LinkConstant cn, + List bootstrapArguments, + ListStack stack, + Set bytecodeOffsets) { this(); name = cn.elementname; classname = cn.classname; + this.bootstrapArguments = bootstrapArguments; switch (opcode) { case CodeConstants.opc_invokestatic: @@ -115,6 +118,15 @@ public class InvocationExprent extends Exprent { } if (opcode == CodeConstants.opc_invokedynamic) { + int dynamicInvocationType = -1; + if (bootstrapArguments != null) { + if (bootstrapArguments.size() > 1) { // INVOKEDYNAMIC is used not only for lambdas + PooledConstant link = bootstrapArguments.get(1); + if (link instanceof LinkConstant) { + dynamicInvocationType = ((LinkConstant)link).index1; + } + } + } if (dynamicInvocationType == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic) { isStatic = true; } @@ -154,6 +166,7 @@ public class InvocationExprent extends Exprent { ExprProcessor.copyEntries(lstParameters); addBytecodeOffsets(expr.bytecode); + bootstrapArguments = expr.getBootstrapArguments(); } @Override @@ -492,6 +505,10 @@ public class InvocationExprent extends Exprent { return invokeDynamicClassSuffix; } + public List getBootstrapArguments() { + return bootstrapArguments; + } + // ***************************************************************************** // IMatchable implementation // ***************************************************************************** diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index 1ab4ea2..b8869b0 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,6 +77,8 @@ public class SingleClassesTest { @Test public void testInnerSignature() { doTest("pkg/TestInnerSignature"); } @Test public void testParameterizedTypes() { doTest("pkg/TestParameterizedTypes"); } @Test public void testShadowing() { doTest("pkg/TestShadowing", "pkg/Shadow", "ext/Shadow"); } + @Test public void testStringConcat() { doTest("pkg/TestStringConcat"); } + @Test public void testJava9StringConcat() { doTest("java9/TestJava9StringConcat"); } protected void doTest(String testFile, String... companionFiles) { ConsoleDecompiler decompiler = fixture.getDecompiler(); diff --git a/testData/classes/java9/TestJava9StringConcat.class b/testData/classes/java9/TestJava9StringConcat.class new file mode 100644 index 0000000000000000000000000000000000000000..721a0ed9499e46d3ea2ea1853198faba1186a168 GIT binary patch literal 871 zcmaJfgc5Pg#-aq6Z`TMGS3OG{IlP?-qCB?{s|q>7>dheUATv`JR28+SLdHuo{bq5=S;%i^| z<_%%4-uO(&cl1aSDqUa6kJHP64EtgbaHe2nWL^-C>fH-*C7OXyXU(1oeRbA8ZQRc# zjF*H~79&OYG`NsM)6UdzdwLoU<%w@w*y?i&@40v@uG`USD9m%)P8mfwgypwdo5+M> z@PnAgGO<|TL=24%uN@rn8oFAKrxQZ?R4EyD0ue=$ zRllW&muSRydneQ=kWnorwo?O5wvx*HL+oEqWCr|e3ZJo(M4}^tpywOecJP#Nc<+8$ zy%Bc*kHbFl{5tJ7$8VEY$+pC&9rnb&@a!k(2M0W6w#A#Iea0>p*jnfwa!dwF$JH5! z_t@r!WN?i(Gq7N{XN?UB 5 +24 <-> 9 diff --git a/testData/results/TestStringConcat.dec b/testData/results/TestStringConcat.dec new file mode 100644 index 0000000..b3fc995 --- /dev/null +++ b/testData/results/TestStringConcat.dec @@ -0,0 +1,31 @@ +package pkg; + +public class TestStringConcat { + public String test1(String var1, int var2) { + return var1 + var2;// 20 + } + + public String test2(String var1, int var2, Object var3) { + return "(" + var1 + "-" + var2 + "---" + var3 + ")";// 24 + } +} + +class 'pkg/TestStringConcat' { + method 'test1 (Ljava/lang/String;I)Ljava/lang/String;' { + f 4 + 12 4 + } + + method 'test2 (Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;' { + 7 8 + 10 8 + 19 8 + 22 8 + 27 8 + 2a 8 + } +} + +Lines mapping: +20 <-> 5 +24 <-> 9 diff --git a/testData/src/java9/TestJava9StringConcat.java b/testData/src/java9/TestJava9StringConcat.java new file mode 100644 index 0000000..9e19596 --- /dev/null +++ b/testData/src/java9/TestJava9StringConcat.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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 java9; + +public class TestJava9StringConcat { + public String test1(String prefix, int a) { + return prefix + a; + } + + public String test2(String var, int b, Object c) { + return "(" + var + "-" + b + "---" + c + ")"; + } +} \ No newline at end of file diff --git a/testData/src/pkg/TestStringConcat.class b/testData/src/pkg/TestStringConcat.class new file mode 100644 index 0000000000000000000000000000000000000000..029207376cef59555729fc47605dda16cd3b41b5 GIT binary patch literal 740 zcmZ`%O;5r=5PeI5){24!1Qk^LqChIqcrh3cl9-qfJwQx67HCKjTT+X^OD`qS#2?^~ zGR{g33bo0;-I?us^JcOipRex#PEeMygsg})86jjjtaI4lkdu&?uqk1SN81t#B6dXV zGDOcz%XH5f1hsU_5U$%@gCX89E#rFh&^DZw-X2gU;u??c5kpaJ^z|pbI?%0Nwdp#h z)vH~V=5s#dnBg>t@r!WN?i(Gq7N{XN?UB