IDEA-151950 Decompiler doesn't work for classes from JDK 9 - support java 9 string concatenation
This commit is contained in:
@@ -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();
|
||||
|
||||
BIN
testData/classes/java9/TestJava9StringConcat.class
Normal file
BIN
testData/classes/java9/TestJava9StringConcat.class
Normal file
Binary file not shown.
BIN
testData/classes/pkg/TestStringConcat.class
Normal file
BIN
testData/classes/pkg/TestStringConcat.class
Normal file
Binary file not shown.
27
testData/results/TestJava9StringConcat.dec
Normal file
27
testData/results/TestJava9StringConcat.dec
Normal file
@@ -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
|
||||
31
testData/results/TestStringConcat.dec
Normal file
31
testData/results/TestStringConcat.dec
Normal file
@@ -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
|
||||
26
testData/src/java9/TestJava9StringConcat.java
Normal file
26
testData/src/java9/TestJava9StringConcat.java
Normal file
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
BIN
testData/src/pkg/TestStringConcat.class
Normal file
BIN
testData/src/pkg/TestStringConcat.class
Normal file
Binary file not shown.
26
testData/src/pkg/TestStringConcat.java
Normal file
26
testData/src/pkg/TestStringConcat.java
Normal file
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user