IDEA-282934 [java-decompiler] - supported the case when the temp pattern variable is in the outer scope

GitOrigin-RevId: a00f7d0a99913f01260689b7c970e34df8477cfb
This commit is contained in:
Ilyas Selimov
2022-01-20 03:53:15 +00:00
committed by intellij-monorepo-bot
parent 436eee6cf4
commit 2c16bcb81e
19 changed files with 415 additions and 222 deletions
@@ -1,11 +1,15 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.java.decompiler.modules.decompiler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
import org.jetbrains.java.decompiler.modules.decompiler.stats.*;
import org.jetbrains.java.decompiler.struct.StructClass;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PatternHelper {
@@ -17,12 +21,16 @@ public class PatternHelper {
* @param statement root statement to start traversal
* @param structClass owner class of <code>statement</code>
*/
public static void replaceAssignmentsWithPatternVariables(RootStatement statement, StructClass structClass) {
if (!structClass.isVersion16()) return;
replaceAssignmentsWithPatternVariables(statement);
public static void replaceAssignmentsWithPatternVariables(@NotNull RootStatement statement, @NotNull StructClass structClass) {
if (!structClass.hasPatternsInInstanceofSupport()) return;
// todo VarExprent overrides equals, but not hashCode, duplicates are possible
Map<VarExprent, Statement> tempVarAssignments = new HashMap<>();
replaceAssignmentsWithPatternVariables(statement, tempVarAssignments);
SwitchHelper.removeTempVariableDeclarations(tempVarAssignments);
}
private static void replaceAssignmentsWithPatternVariables(Statement statement) {
private static void replaceAssignmentsWithPatternVariables(@NotNull Statement statement,
@NotNull Map<VarExprent, Statement> tempVarAssignments) {
if (statement instanceof IfStatement) {
IfStatement ifStatement = (IfStatement)statement;
FunctionExprent instanceOfExprent = findInstanceofExprent(ifStatement);
@@ -34,29 +42,34 @@ public class PatternHelper {
ConstExprent checkType = (ConstExprent)operands.get(1);
PatternVariableCandidate patternVarCandidate = findPatternVarCandidate(ifStatement.getIfstat(), operand, checkType);
if (patternVarCandidate == null) {
if (patternVarCandidate == null && ifStatement.getElsestat() != null) {
patternVarCandidate = findPatternVarCandidate(ifStatement.getElsestat(), operand, checkType);
}
if (patternVarCandidate == null) return;
instanceOfExprent.getLstOperands().remove(1);
instanceOfExprent.getLstOperands().add(patternVarCandidate.varExprent);
operands.remove(1);
if (!patternVarCandidate.varExprent.isDefinition()) {
patternVarCandidate.varExprent.setDefinition(true);
tempVarAssignments.put(patternVarCandidate.varExprent, ifStatement);
}
operands.add(patternVarCandidate.varExprent);
patternVarCandidate.ifElseStat.getExprents().remove(patternVarCandidate.assignmentExprent);
return;
}
for (Statement child : statement.getStats()) {
replaceAssignmentsWithPatternVariables(child);
replaceAssignmentsWithPatternVariables(child, tempVarAssignments);
}
}
private static FunctionExprent findInstanceofExprent(IfStatement ifStat) {
private static FunctionExprent findInstanceofExprent(@NotNull IfStatement ifStat) {
return ifStat.getHeadexprent().getAllExprents(true).stream()
.filter(expr -> expr.type == Exprent.EXPRENT_FUNCTION).map(expr -> (FunctionExprent)expr)
.filter(expr -> expr.getFuncType() == FunctionExprent.FUNCTION_INSTANCEOF)
.findFirst().orElse(null);
}
private static PatternVariableCandidate findPatternVarCandidate(Statement ifElseStat, VarExprent operand, ConstExprent checkType) {
private static PatternVariableCandidate findPatternVarCandidate(@NotNull Statement ifElseStat,
@NotNull VarExprent operand,
@NotNull ConstExprent checkType) {
if (ifElseStat instanceof BasicBlockStatement) {
List<Exprent> ifElseExprents = ifElseStat.getExprents();
if (ifElseExprents.isEmpty() || ifElseExprents.get(0).type != Exprent.EXPRENT_ASSIGNMENT) return null;
@@ -64,11 +77,24 @@ public class PatternHelper {
AssignmentExprent assignmentExprent = (AssignmentExprent)ifElseExprents.get(0);
if (assignmentExprent.getLeft().type != Exprent.EXPRENT_VAR) return null;
VarExprent varExprent = (VarExprent)assignmentExprent.getLeft();
if (!varExprent.isDefinition()) return null;
if (assignmentExprent.getRight().type != Exprent.EXPRENT_FUNCTION) return null;
FunctionExprent castExprent = (FunctionExprent)assignmentExprent.getRight();
if (castExprent.getFuncType() != FunctionExprent.FUNCTION_CAST) return null;
if (!varExprent.isDefinition()) {
Exprent leftAssignmentPart = assignmentExprent.getLeft();
Exprent rightAssignmentPart = assignmentExprent.getRight();
if (leftAssignmentPart.type != Exprent.EXPRENT_VAR || rightAssignmentPart.type != Exprent.EXPRENT_FUNCTION ||
((FunctionExprent)rightAssignmentPart).getFuncType() != FunctionExprent.FUNCTION_CAST) {
return null;
}
varExprent = ((VarExprent)leftAssignmentPart);
List<Exprent> castOperands = ((FunctionExprent)rightAssignmentPart).getLstOperands();
if (castOperands.size() != 2 || castOperands.get(1).type != Exprent.EXPRENT_CONST) return null;
VarType castType = ((ConstExprent)castOperands.get(1)).getConstType();
varExprent.setVarType(castType);
}
List<Exprent> castExprents = castExprent.getAllExprents();
if (castExprents.size() == 2 && operand.equals(castExprents.get(0)) && checkType.equals(castExprents.get(1))) {
return new PatternVariableCandidate(ifElseStat, assignmentExprent, varExprent);
@@ -82,11 +108,13 @@ public class PatternHelper {
}
private static class PatternVariableCandidate {
private final Statement ifElseStat;
private final AssignmentExprent assignmentExprent;
private final VarExprent varExprent;
private final @NotNull Statement ifElseStat;
private final @NotNull AssignmentExprent assignmentExprent;
private final @NotNull VarExprent varExprent;
private PatternVariableCandidate(Statement ifElseStat, AssignmentExprent assignmentExprent, VarExprent varExprent) {
private PatternVariableCandidate(@NotNull Statement ifElseStat,
@NotNull AssignmentExprent assignmentExprent,
@NotNull VarExprent varExprent) {
this.ifElseStat = ifElseStat;
this.assignmentExprent = assignmentExprent;
this.varExprent = varExprent;
@@ -138,32 +138,39 @@ public final class SwitchHelper {
(index instanceof InvocationExprent && ((InvocationExprent)index).getName().equals("ordinal")));
}
private static void removeTempVariableDeclarations(@NotNull Map<VarExprent, Statement> tempVarAssignments) {
static void removeTempVariableDeclarations(@NotNull Map<VarExprent, Statement> tempVarAssignments) {
if (tempVarAssignments.isEmpty()) return;
for (Statement statement : new HashSet<>(tempVarAssignments.values())) {
Statement parent = statement;
while (parent != null) {
boolean removed = false;
Statement firstStatement = parent.getFirst();
if (firstStatement.type == Statement.TYPE_BASIC_BLOCK) {
for (int i = 0; i < firstStatement.getExprents().size(); i++) {
Exprent exprent = firstStatement.getExprents().get(i);
Exprent assignmentExprent = null;
if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) {
assignmentExprent = exprent;
exprent = ((AssignmentExprent)exprent).getLeft();
}
if (exprent.type != Exprent.EXPRENT_VAR) continue;
VarExprent varExprent = (VarExprent)exprent;
if (varExprent.isDefinition() && tempVarAssignments.keySet().stream()
.anyMatch(expr -> expr.getIndex() == varExprent.getIndex() && expr.getVersion() == varExprent.getVersion())) {
firstStatement.getExprents().remove(assignmentExprent == null ? varExprent : assignmentExprent);
removed = true;
break;
}
}
if (removed) break;
List<Exprent> varExprents;
if (parent.getFirst().type == Statement.TYPE_BASIC_BLOCK) {
varExprents = parent.getFirst().getExprents();
}
else if (parent.type == Statement.TYPE_TRY_CATCH) {
varExprents = parent.getVarDefinitions();
}
else {
varExprents = Collections.emptyList();
}
for (int i = 0; i < varExprents.size(); i++) {
Exprent exprent = varExprents.get(i);
Exprent assignmentExprent = null;
if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) {
assignmentExprent = exprent;
exprent = ((AssignmentExprent)exprent).getLeft();
}
if (exprent.type != Exprent.EXPRENT_VAR) continue;
VarExprent varExprent = (VarExprent)exprent;
if (varExprent.isDefinition() && tempVarAssignments.keySet().stream()
.anyMatch(expr -> expr.getIndex() == varExprent.getIndex() && expr.getVersion() == varExprent.getVersion())) {
varExprents.remove(assignmentExprent == null ? varExprent : assignmentExprent);
removed = true;
break;
}
}
if (removed) break;
parent = parent.getParent();
}
}
@@ -176,7 +183,7 @@ public final class SwitchHelper {
@NotNull
Set<Object> findRealCaseValuesHashCodes(@NotNull SwitchStatement switchStatement) {
//noinspection SSBasedInspection,ConstantConditions
// noinspection SSBasedInspection
return switchStatement.getCaseValues().stream()
// We take only buckets that don't contain null value.
// null value represents default branch and no temp variable is assigned there.
@@ -204,16 +204,32 @@ public class StructClass extends StructMember {
return majorVersion >= CodeConstants.BYTECODE_JAVA_8;
}
public boolean hasSealedClassesSupport() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_17 || (majorVersion >= CodeConstants.BYTECODE_JAVA_15 && isPreviewVersion());
public boolean isVersion14() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_14;
}
public boolean isVersion15() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_15;
}
public boolean isVersion16() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_16;
}
public boolean isVersion17() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_17;
}
public boolean isPreviewVersion() {
return minorVersion == 0xFFFF;
}
public boolean isVersion16() {
return majorVersion >= CodeConstants.BYTECODE_JAVA_16;
public boolean hasSealedClassesSupport() {
return isVersion17() || (isVersion15() && isPreviewVersion());
}
public boolean hasPatternsInInstanceofSupport() {
return isVersion16() || (isVersion14() && isPreviewVersion());
}
@Override
@@ -212,24 +212,13 @@ public class SingleClassesTest {
@Test public void testInheritanceChainCycle() { doTest("pkg/TestInheritanceChainCycle"); }
@Test public void testDynamicConstantPoolEntry() { doTest("java11/TestDynamicConstantPoolEntry"); }
@Test
public void testInstanceof() {
doTest("patterns/TestInstanceof");
}
@Test
public void testInvertedInstanceof() {
doTest("patterns/TestInvertedInstanceof");
@Test public void testInstanceofWithPattern() {
doTest("patterns/TestInstanceofWithPattern");
}
@Test
public void testInstanceofBinaryExpr() {
doTest("patterns/TestInstanceofBinaryExpr");
}
@Test
public void testInstanceofVarNotSupported() {
doTest("patterns/TestInstanceofVarNotSupported");
@Test public void testInstanceofVarNotSupported() {
// bytecode version of this test data doesn't support patterns in instanceof, so no modifications regarding that are applied
doTest("patterns/TestInstanceofPatternNotSupported");
}
@Test(expected = ClassFormatException.class)
@@ -1,31 +0,0 @@
package patterns;
public class TestInstanceof {
void typePattern(Object str) {
if (str instanceof String s) {// 6
System.out.println(s);// 7
} else {
System.out.println("no");// 9
}
}// 11
}
class 'patterns/TestInstanceof' {
method 'typePattern (Ljava/lang/Object;)V' {
3 4
6 4
e 5
12 5
18 7
1b 7
1d 7
20 10
}
}
Lines mapping:
6 <-> 5
7 <-> 6
9 <-> 8
11 <-> 11
@@ -1,39 +0,0 @@
package patterns;
public class TestInstanceofBinaryExpr {
void typePattern(Object str) {
if (str instanceof String s) {// 6
if (s.length() > 1 || s.startsWith("a")) {
System.out.println(s);// 7
return;// 11
}
}
System.out.println("no");// 9
}
}
class 'patterns/TestInstanceofBinaryExpr' {
method 'typePattern (Ljava/lang/Object;)V' {
3 4
6 4
f 5
12 5
13 5
17 5
19 5
1c 5
1f 6
23 6
29 11
2c 11
2e 11
31 7
}
}
Lines mapping:
6 <-> 5
7 <-> 7
9 <-> 12
11 <-> 8
@@ -1,6 +1,6 @@
package patterns;
package decompiler;
public class TestInstanceofVarNotSupported {
public class TestInstanceofPatternNotSupported {
void typePattern(Object str) {
if (!(str instanceof String)) {// 5
System.out.println("no");// 6
@@ -16,7 +16,7 @@ public class TestInstanceofVarNotSupported {
}// 7 15
}
class 'patterns/TestInstanceofVarNotSupported' {
class 'decompiler/TestInstanceofPatternNotSupported' {
method 'typePattern (Ljava/lang/Object;)V' {
1 4
4 4
@@ -0,0 +1,235 @@
package decompiler;
import java.util.Collection;
import java.util.List;
public class TestInstanceofWithPattern {
void typePattern1(Object str) {
if (str instanceof String s) {// 9
System.out.println(s);// 10
} else {
System.out.println("no");// 12
}
}// 14
void typePattern2(Object str) {
if (!(str instanceof String s)) {// 17
System.out.println("no");// 18
} else {
if (s.length() > 3) {// 22
System.out.println(s);// 23
} else if (s.startsWith("a")) {// 24
System.out.println("" + s);// 25
}
}
}// 19 27
void typePatternInBinaryExpr(Object str) {
if (str instanceof String s) {// 30
if (s.length() > 1 || s.startsWith("a")) {
System.out.println(s);// 31
return;// 35
}
}
System.out.println("no");// 33
}
String returnInstanceof(Object obj) {
if (obj instanceof String s) {// 38
if (s.length() > 50) {
return "\"" + s.substring(0, 50) + "...\"";// 39
}
}
if (obj instanceof String s) {// 41
return "\"" + s + "\"";// 42
} else if (obj instanceof Collection<?> c) {// 44
return "Collection (size = " + c.size() + ")";// 45
} else {
return obj.toString();// 47
}
}
String complex(Object obj1, Object obj2) {
while(true) {
try {
if (obj1 instanceof String s) {// 53
while(!s.startsWith("a")) {// 55
}
return s;// 56
}
if (obj2 instanceof Collection<?> c) {// 59
return c.toString();// 60
}
} catch (Exception var7) {// 62
if (obj2 instanceof String s) {// 63
while(!s.startsWith("b")) {// 65
}
return s + "b";// 66
}
if (obj2 instanceof List<?> l) {// 69
String var10000 = this.getStr();// 70
return var10000 + l.size();
}
}
}
}
String getStr() {
return null;// 77
}
}
class 'decompiler/TestInstanceofWithPattern' {
method 'typePattern1 (Ljava/lang/Object;)V' {
3 7
6 7
e 8
12 8
18 10
1b 10
1d 10
20 13
}
method 'typePattern2 (Ljava/lang/Object;)V' {
1 16
4 16
7 17
a 17
c 17
f 26
16 19
19 19
1a 19
1d 20
21 20
28 21
2a 21
2d 21
30 22
34 22
39 22
3c 26
}
method 'typePatternInBinaryExpr (Ljava/lang/Object;)V' {
3 29
6 29
f 30
12 30
13 30
17 30
19 30
1c 30
1f 31
23 31
29 36
2c 36
2e 36
31 32
}
method 'returnInstanceof (Ljava/lang/Object;)Ljava/lang/String;' {
3 40
6 40
f 41
12 41
14 41
18 42
19 42
1b 42
1e 42
23 42
27 46
2a 46
33 47
38 47
3c 48
3f 48
48 49
4d 49
52 49
54 51
57 51
}
method 'complex (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;' {
5 58
8 58
14 59
16 59
19 59
1e 62
24 65
27 65
31 66
34 66
38 68
3e 69
41 69
4d 70
4f 70
52 70
57 73
5c 73
62 76
65 76
70 77
75 78
7a 78
7f 78
}
method 'getStr ()Ljava/lang/String;' {
0 85
1 85
}
}
Lines mapping:
9 <-> 8
10 <-> 9
12 <-> 11
14 <-> 14
17 <-> 17
18 <-> 18
19 <-> 27
22 <-> 20
23 <-> 21
24 <-> 22
25 <-> 23
27 <-> 27
30 <-> 30
31 <-> 32
33 <-> 37
35 <-> 33
38 <-> 41
39 <-> 43
41 <-> 47
42 <-> 48
44 <-> 49
45 <-> 50
47 <-> 52
53 <-> 59
55 <-> 60
56 <-> 63
59 <-> 66
60 <-> 67
62 <-> 69
63 <-> 70
65 <-> 71
66 <-> 74
69 <-> 77
70 <-> 78
77 <-> 86
Not mapped:
21
72
@@ -1,51 +0,0 @@
package patterns;
public class TestInvertedInstanceof {
void typePattern(Object str) {
if (!(str instanceof String s)) {// 5
System.out.println("no");// 6
} else {
if (s.length() > 3) {// 10
System.out.println(s);// 11
} else if (s.startsWith("a")) {// 12
System.out.println("" + s);// 13
}
}
}// 7 15
}
class 'patterns/TestInvertedInstanceof' {
method 'typePattern (Ljava/lang/Object;)V' {
1 4
4 4
7 5
a 5
c 5
f 14
16 7
19 7
1a 7
1d 8
21 8
28 9
2a 9
2d 9
30 10
34 10
39 10
3c 14
}
}
Lines mapping:
5 <-> 5
6 <-> 6
7 <-> 15
10 <-> 8
11 <-> 9
12 <-> 10
13 <-> 11
15 <-> 15
Not mapped:
9
@@ -1,12 +0,0 @@
package patterns;
public class TestInstanceof {
void typePattern(Object str) {
if (str instanceof String s) {
System.out.println(s);
} else {
System.out.println("no");
}
}
}
@@ -1,12 +0,0 @@
package patterns;
public class TestInstanceofBinaryExpr {
void typePattern(Object str) {
if (str instanceof String s && (s.length() > 1 || s.startsWith("a"))) {
System.out.println(s);
} else {
System.out.println("no");
}
}
}
@@ -1,6 +1,6 @@
package decompiler;
public class TestInstanceofVarNotSupported {
public class TestInstanceofPatternNotSupported {
void typePattern(Object str) {
if (!(str instanceof String)) {
System.out.println("no");
@@ -0,0 +1,79 @@
package patterns;
import java.util.Collection;
import java.util.List;
public class TestInstanceofWithPattern {
void typePattern1(Object str) {
if (str instanceof String s) {
System.out.println(s);
} else {
System.out.println("no");
}
}
void typePattern2(Object str) {
if (!(str instanceof String)) {
System.out.println("no");
return;
}
String s = (String) str;
if (s.length() > 3) {
System.out.println(s);
} else if (s.startsWith("a")) {
System.out.println(s + "");
}
}
void typePatternInBinaryExpr(Object str) {
if (str instanceof String s && (s.length() > 1 || s.startsWith("a"))) {
System.out.println(s);
} else {
System.out.println("no");
}
}
String returnInstanceof(Object obj) {
if (obj instanceof String s && s.length() > 50) {
return '"' + s.substring(0, 50) + "...\"";
}
if (obj instanceof String s) {
return '"' + s + '"';
}
if (obj instanceof Collection<?> c) {
return "Collection (size = " + c.size() + ")";
}
return obj.toString();
}
String complex(Object obj1, Object obj2) {
while(true) {
try {
if (obj1 instanceof String s) {
while (true) {
if (s.startsWith("a")) {
return s;
}
}
} else if (obj2 instanceof Collection<?> c) {
return c.toString();
}
} catch (Exception e) {
if (obj2 instanceof String s) {
while (true) {
if (s.startsWith("b")) {
return s + "b";
}
}
} else if (obj2 instanceof List<?> l) {
return getStr() + l.size();
}
}
}
}
String getStr() {
return null;
}
}
@@ -1,16 +0,0 @@
package patterns;
public class TestInvertedInstanceof {
void typePattern(Object str) {
if (!(str instanceof String)) {
System.out.println("no");
return;
}
String s = (String) str;
if (s.length() > 3) {
System.out.println(s);
} else if (s.startsWith("a")) {
System.out.println(s + "");
}
}
}