IDEA-151950 Decompiler doesn't work for classes from JDK 9 - support java 9 string concatenation

master
Egor.Ushakov 8 years ago
parent f128515325
commit 4724fd78a6
  1. 69
      src/org/jetbrains/java/decompiler/modules/decompiler/ConcatenationHelper.java
  2. 13
      src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java
  3. 35
      src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java
  4. 4
      test/org/jetbrains/java/decompiler/SingleClassesTest.java
  5. BIN
      testData/classes/java9/TestJava9StringConcat.class
  6. BIN
      testData/classes/pkg/TestStringConcat.class
  7. 27
      testData/results/TestJava9StringConcat.dec
  8. 31
      testData/results/TestStringConcat.dec
  9. 26
      testData/src/java9/TestJava9StringConcat.java
  10. BIN
      testData/src/pkg/TestStringConcat.class
  11. 26
      testData/src/pkg/TestStringConcat.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<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())) {

@ -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);
}

@ -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<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
// *****************************************************************************

@ -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();

@ -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

@ -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

@ -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 + ")";
}
}

@ -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 + ")";
}
}
Loading…
Cancel
Save