IDEA-151950 Decompiler doesn't work for classes from JDK 9 - support java 9 string...
authorEgor.Ushakov <egor.ushakov@jetbrains.com>
Tue, 1 Mar 2016 15:04:29 +0000 (18:04 +0300)
committerEgor.Ushakov <egor.ushakov@jetbrains.com>
Tue, 1 Mar 2016 15:07:55 +0000 (18:07 +0300)
plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java
plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java
plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
plugins/java-decompiler/engine/test/org/jetbrains/java/decompiler/SingleClassesTest.java
plugins/java-decompiler/engine/testData/classes/java9/TestJava9StringConcat.class [new file with mode: 0644]
plugins/java-decompiler/engine/testData/classes/pkg/TestStringConcat.class [new file with mode: 0644]
plugins/java-decompiler/engine/testData/results/TestJava9StringConcat.dec [new file with mode: 0644]
plugins/java-decompiler/engine/testData/results/TestStringConcat.dec [new file with mode: 0644]
plugins/java-decompiler/engine/testData/src/java9/TestJava9StringConcat.java [new file with mode: 0644]
plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.class [new file with mode: 0644]
plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.java [new file with mode: 0644]

index 74ef0a2975f17a2462ae406f8f0106792b64818f..7d7b33d3864ec9a19bc5b828fae50f69a15a1f3c 100644 (file)
@@ -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<Exprent> 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<Exprent> lstOperands, Set<Integer> bytecode) {
     // build exprent to return
     Exprent func = lstOperands.get(0);
 
     for (int i = 1; i < lstOperands.size(); i++) {
-      List<Exprent> lstTmp = new ArrayList<Exprent>();
-      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<Exprent> extractParameters(List<PooledConstant> bootstrapArguments, InvocationExprent expr) {
+    List<Exprent> parameters = expr.getLstParameters();
+    if (bootstrapArguments != null) {
+      PooledConstant constant = bootstrapArguments.get(0);
+      if (constant.type == CodeConstants.CONSTANT_String) {
+        String recipe = ((PrimitiveConstant)constant).getString();
+
+        List<Exprent> 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())) {
index cbc66c78c63af685e8694049ce8068d04883f90c..008d0bb794aba401a80b3ec7af8ad772ee41bee6 100644 (file)
@@ -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<PooledConstant> bootstrap_arguments = null;
             if (instr.opcode == opc_invokedynamic && bootstrap != null) {
-              List<PooledConstant> 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);
             }
index 294a115207d3c76ef0ae29e63556ec5de750889d..3ceb99cafc60f6936a263673b77a1b46ed2db696 100644 (file)
@@ -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.
  */
 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<Exprent> lstParameters = new ArrayList<Exprent>();
+  private List<PooledConstant> bootstrapArguments;
 
   public InvocationExprent() {
     super(EXPRENT_INVOCATION);
   }
 
-  public InvocationExprent(int opcode, LinkConstant cn, ListStack<Exprent> stack, int dynamicInvocationType, Set<Integer> bytecodeOffsets) {
+  public InvocationExprent(int opcode,
+                           LinkConstant cn,
+                           List<PooledConstant> bootstrapArguments,
+                           ListStack<Exprent> stack,
+                           Set<Integer> 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<PooledConstant> getBootstrapArguments() {
+    return bootstrapArguments;
+  }
+
   // *****************************************************************************
   // IMatchable implementation
   // *****************************************************************************
index 1ab4ea2f32449cf4e28232ea22120d570e3e5d46..b8869b0d64d3cc37a1cfcd393293eafcf3327e54 100644 (file)
@@ -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/plugins/java-decompiler/engine/testData/classes/java9/TestJava9StringConcat.class b/plugins/java-decompiler/engine/testData/classes/java9/TestJava9StringConcat.class
new file mode 100644 (file)
index 0000000..721a0ed
Binary files /dev/null and b/plugins/java-decompiler/engine/testData/classes/java9/TestJava9StringConcat.class differ
diff --git a/plugins/java-decompiler/engine/testData/classes/pkg/TestStringConcat.class b/plugins/java-decompiler/engine/testData/classes/pkg/TestStringConcat.class
new file mode 100644 (file)
index 0000000..0292073
Binary files /dev/null and b/plugins/java-decompiler/engine/testData/classes/pkg/TestStringConcat.class differ
diff --git a/plugins/java-decompiler/engine/testData/results/TestJava9StringConcat.dec b/plugins/java-decompiler/engine/testData/results/TestJava9StringConcat.dec
new file mode 100644 (file)
index 0000000..389bb85
--- /dev/null
@@ -0,0 +1,27 @@
+package java9;
+
+public class TestJava9StringConcat {
+   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 'java9/TestJava9StringConcat' {
+   method 'test1 (Ljava/lang/String;I)Ljava/lang/String;' {
+      2      4
+      7      4
+   }
+
+   method 'test2 (Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;' {
+      3      8
+      8      8
+   }
+}
+
+Lines mapping:
+20 <-> 5
+24 <-> 9
diff --git a/plugins/java-decompiler/engine/testData/results/TestStringConcat.dec b/plugins/java-decompiler/engine/testData/results/TestStringConcat.dec
new file mode 100644 (file)
index 0000000..b3fc995
--- /dev/null
@@ -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/plugins/java-decompiler/engine/testData/src/java9/TestJava9StringConcat.java b/plugins/java-decompiler/engine/testData/src/java9/TestJava9StringConcat.java
new file mode 100644 (file)
index 0000000..9e19596
--- /dev/null
@@ -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/plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.class b/plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.class
new file mode 100644 (file)
index 0000000..0292073
Binary files /dev/null and b/plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.class differ
diff --git a/plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.java b/plugins/java-decompiler/engine/testData/src/pkg/TestStringConcat.java
new file mode 100644 (file)
index 0000000..1a77677
--- /dev/null
@@ -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 pkg;
+
+public class TestStringConcat {
+  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