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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.code.CodeConstants;
|
||||||
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
|
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.MethodDescriptor;
|
||||||
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class ConcatenationHelper {
|
public class ConcatenationHelper {
|
||||||
|
|
||||||
@@ -52,6 +56,12 @@ public class ConcatenationHelper {
|
|||||||
exprTmp = iex.getInstance();
|
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) {
|
if (exprTmp == null) {
|
||||||
@@ -125,20 +135,69 @@ public class ConcatenationHelper {
|
|||||||
lstOperands.set(i, rep);
|
lstOperands.set(i, rep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return createConcatExprent(lstOperands, expr.bytecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Exprent createConcatExprent(List<Exprent> lstOperands, Set<Integer> bytecode) {
|
||||||
// build exprent to return
|
// build exprent to return
|
||||||
Exprent func = lstOperands.get(0);
|
Exprent func = lstOperands.get(0);
|
||||||
|
|
||||||
for (int i = 1; i < lstOperands.size(); i++) {
|
for (int i = 1; i < lstOperands.size(); i++) {
|
||||||
List<Exprent> lstTmp = new ArrayList<Exprent>();
|
func = new FunctionExprent(FunctionExprent.FUNCTION_STR_CONCAT, Arrays.asList(func, lstOperands.get(i)), bytecode);
|
||||||
lstTmp.add(func);
|
|
||||||
lstTmp.add(lstOperands.get(i));
|
|
||||||
func = new FunctionExprent(FunctionExprent.FUNCTION_STR_CONCAT, lstTmp, expr.bytecode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return func;
|
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) {
|
private static boolean isAppendConcat(InvocationExprent expr, VarType cltype) {
|
||||||
|
|
||||||
if ("append".equals(expr.getName())) {
|
if ("append".equals(expr.getName())) {
|
||||||
|
|||||||
@@ -567,21 +567,14 @@ public class ExprProcessor implements CodeConstants {
|
|||||||
case opc_invokeinterface:
|
case opc_invokeinterface:
|
||||||
case opc_invokedynamic:
|
case opc_invokedynamic:
|
||||||
if (instr.opcode != opc_invokedynamic || instr.bytecode_version >= CodeConstants.BYTECODE_JAVA_7) {
|
if (instr.opcode != opc_invokedynamic || instr.bytecode_version >= CodeConstants.BYTECODE_JAVA_7) {
|
||||||
|
|
||||||
LinkConstant invoke_constant = pool.getLinkConstant(instr.getOperand(0));
|
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) {
|
if (instr.opcode == opc_invokedynamic && bootstrap != null) {
|
||||||
List<PooledConstant> bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (exprinv.getDescriptor().ret.type == CodeConstants.TYPE_VOID) {
|
||||||
exprlist.add(exprinv);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.code.CodeConstants;
|
||||||
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
|
import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
|
||||||
import org.jetbrains.java.decompiler.main.DecompilerContext;
|
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.StructClass;
|
||||||
import org.jetbrains.java.decompiler.struct.StructMethod;
|
import org.jetbrains.java.decompiler.struct.StructMethod;
|
||||||
import org.jetbrains.java.decompiler.struct.consts.LinkConstant;
|
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.MethodDescriptor;
|
||||||
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
||||||
import org.jetbrains.java.decompiler.struct.match.MatchEngine;
|
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.ListStack;
|
||||||
import org.jetbrains.java.decompiler.util.TextUtil;
|
import org.jetbrains.java.decompiler.util.TextUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
public class InvocationExprent extends Exprent {
|
public class InvocationExprent extends Exprent {
|
||||||
|
|
||||||
public static final int INVOKE_SPECIAL = 1;
|
public static final int INVOKE_SPECIAL = 1;
|
||||||
@@ -69,16 +66,22 @@ public class InvocationExprent extends Exprent {
|
|||||||
private String invokeDynamicClassSuffix;
|
private String invokeDynamicClassSuffix;
|
||||||
private int invocationTyp = INVOKE_VIRTUAL;
|
private int invocationTyp = INVOKE_VIRTUAL;
|
||||||
private List<Exprent> lstParameters = new ArrayList<Exprent>();
|
private List<Exprent> lstParameters = new ArrayList<Exprent>();
|
||||||
|
private List<PooledConstant> bootstrapArguments;
|
||||||
|
|
||||||
public InvocationExprent() {
|
public InvocationExprent() {
|
||||||
super(EXPRENT_INVOCATION);
|
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();
|
this();
|
||||||
|
|
||||||
name = cn.elementname;
|
name = cn.elementname;
|
||||||
classname = cn.classname;
|
classname = cn.classname;
|
||||||
|
this.bootstrapArguments = bootstrapArguments;
|
||||||
|
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
case CodeConstants.opc_invokestatic:
|
case CodeConstants.opc_invokestatic:
|
||||||
@@ -115,6 +118,15 @@ public class InvocationExprent extends Exprent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opcode == CodeConstants.opc_invokedynamic) {
|
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) {
|
if (dynamicInvocationType == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic) {
|
||||||
isStatic = true;
|
isStatic = true;
|
||||||
}
|
}
|
||||||
@@ -154,6 +166,7 @@ public class InvocationExprent extends Exprent {
|
|||||||
ExprProcessor.copyEntries(lstParameters);
|
ExprProcessor.copyEntries(lstParameters);
|
||||||
|
|
||||||
addBytecodeOffsets(expr.bytecode);
|
addBytecodeOffsets(expr.bytecode);
|
||||||
|
bootstrapArguments = expr.getBootstrapArguments();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -492,6 +505,10 @@ public class InvocationExprent extends Exprent {
|
|||||||
return invokeDynamicClassSuffix;
|
return invokeDynamicClassSuffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PooledConstant> getBootstrapArguments() {
|
||||||
|
return bootstrapArguments;
|
||||||
|
}
|
||||||
|
|
||||||
// *****************************************************************************
|
// *****************************************************************************
|
||||||
// IMatchable implementation
|
// 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 testInnerSignature() { doTest("pkg/TestInnerSignature"); }
|
||||||
@Test public void testParameterizedTypes() { doTest("pkg/TestParameterizedTypes"); }
|
@Test public void testParameterizedTypes() { doTest("pkg/TestParameterizedTypes"); }
|
||||||
@Test public void testShadowing() { doTest("pkg/TestShadowing", "pkg/Shadow", "ext/Shadow"); }
|
@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) {
|
protected void doTest(String testFile, String... companionFiles) {
|
||||||
ConsoleDecompiler decompiler = fixture.getDecompiler();
|
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