mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
[java-decompiler] IDEA-282931 Support sealed classes decompilation
Adds the sealed modifier to decompiled sealed classes and interfaces. Also adds non-sealed modifier when a class isn't final, isn't sealed but exstends or implements a sealed class or interface. GitOrigin-RevId: 4633fa9c153c8117f300fc1af96040bc3389dccf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2f46eb0f99
commit
b201e28172
+32
@@ -420,6 +420,7 @@ public class ClassWriter {
|
||||
}
|
||||
|
||||
List<StructRecordComponent> components = cl.getRecordComponents();
|
||||
List<String> permittedSubclassSignature = cl.getPermittedSubclasses();
|
||||
|
||||
if (components != null) {
|
||||
// records are implicitly final
|
||||
@@ -428,6 +429,13 @@ public class ClassWriter {
|
||||
|
||||
appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED);
|
||||
|
||||
if (permittedSubclassSignature != null) {
|
||||
buffer.append("sealed ");
|
||||
}
|
||||
else if (node.isNonSealed()) {
|
||||
buffer.append("non-sealed ");
|
||||
}
|
||||
|
||||
if (isEnum) {
|
||||
buffer.append("enum ");
|
||||
}
|
||||
@@ -498,9 +506,33 @@ public class ClassWriter {
|
||||
}
|
||||
}
|
||||
|
||||
if (permittedSubclassSignature != null) {
|
||||
List<ClassNode> permittedOuterSubClasses = getClassNodes(permittedSubclassSignature).stream()
|
||||
.filter(subClass -> !subClass.enclosingClasses.contains(node.classStruct.qualifiedName))
|
||||
.collect(Collectors.toList());
|
||||
if (!permittedOuterSubClasses.isEmpty()) { // only generate permits lists for nested classes
|
||||
buffer.append("permits ");
|
||||
for (int i = 0; i < permittedOuterSubClasses.size(); i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
ClassNode subClass = permittedOuterSubClasses.get(i);
|
||||
DecompilerContext.getImportCollector().getShortName(subClass.classStruct.qualifiedName); // add qualified name to potential import list
|
||||
buffer.append(subClass.simpleName);
|
||||
}
|
||||
buffer.append(' ');
|
||||
}
|
||||
}
|
||||
buffer.append('{').appendLineSeparator();
|
||||
}
|
||||
|
||||
private static List<ClassNode> getClassNodes(List<String> qualifiedClassNames) {
|
||||
return qualifiedClassNames.stream()
|
||||
.map(str -> DecompilerContext.getClassProcessor().getMapRootClasses().get(str))
|
||||
.collect(Collectors.toList()
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean isVarArgRecord(StructClass cl) {
|
||||
String canonicalConstructorDescriptor =
|
||||
cl.getRecordComponents().stream().map(c -> c.getDescriptor()).collect(Collectors.joining("", "(", ")V"));
|
||||
|
||||
+33
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// 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.main;
|
||||
|
||||
import org.jetbrains.java.decompiler.code.CodeConstants;
|
||||
@@ -22,6 +22,7 @@ import org.jetbrains.java.decompiler.struct.attr.StructEnclosingMethodAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
|
||||
import org.jetbrains.java.decompiler.struct.gen.VarType;
|
||||
import org.jetbrains.java.decompiler.util.InterpreterUtil;
|
||||
import org.jetbrains.java.decompiler.util.TextBuffer;
|
||||
@@ -29,6 +30,7 @@ import org.jetbrains.java.decompiler.util.TextBuffer;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ClassesProcessor implements CodeConstants {
|
||||
public static final int AVERAGE_CLASS_SIZE = 16 * 1024;
|
||||
@@ -122,6 +124,27 @@ public class ClassesProcessor implements CodeConstants {
|
||||
}
|
||||
}
|
||||
|
||||
// set non-sealed if class extends or implements a sealed class
|
||||
for (Entry<String, ClassNode> ent : mapRootClasses.entrySet()) {
|
||||
ClassNode clazz = ent.getValue();
|
||||
List<String> qualifiedSealedSuperNames = new ArrayList<>(Arrays.asList(clazz.classStruct.getInterfaceNames()));
|
||||
PrimitiveConstant superConst = clazz.classStruct.superClass;
|
||||
if (superConst != null) qualifiedSealedSuperNames.add(superConst.getString());
|
||||
List<ClassNode> potentialSealedSupers = qualifiedSealedSuperNames.stream()
|
||||
.map(mapRootClasses::get)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
for (ClassNode potentialSealedSuper : potentialSealedSupers) {
|
||||
if (potentialSealedSuper.classStruct.getPermittedSubclasses() != null &&
|
||||
potentialSealedSuper.classStruct.getPermittedSubclasses().contains(clazz.classStruct.qualifiedName) &&
|
||||
(clazz.access & CodeConstants.ACC_FINAL) == 0 &&
|
||||
clazz.classStruct.getPermittedSubclasses() == null
|
||||
) {
|
||||
clazz.setNonSealed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bDecompileInner) {
|
||||
// connect nested classes
|
||||
for (Entry<String, ClassNode> ent : mapRootClasses.entrySet()) {
|
||||
@@ -418,6 +441,7 @@ public class ClassesProcessor implements CodeConstants {
|
||||
|
||||
public int type;
|
||||
public int access;
|
||||
public boolean isNonSealed = false;
|
||||
public String simpleName;
|
||||
public final StructClass classStruct;
|
||||
private ClassWrapper wrapper;
|
||||
@@ -491,6 +515,14 @@ public class ClassesProcessor implements CodeConstants {
|
||||
return node.wrapper;
|
||||
}
|
||||
|
||||
public boolean isNonSealed() {
|
||||
return isNonSealed;
|
||||
}
|
||||
|
||||
public void setNonSealed(boolean nonSealed) {
|
||||
isNonSealed = nonSealed;
|
||||
}
|
||||
|
||||
public static class LambdaInformation {
|
||||
public String method_name;
|
||||
public String method_descriptor;
|
||||
|
||||
+7
@@ -3,6 +3,7 @@ package org.jetbrains.java.decompiler.struct;
|
||||
|
||||
import org.jetbrains.java.decompiler.code.CodeConstants;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructPermittedSubclassesAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.attr.StructRecordAttribute;
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
|
||||
@@ -160,6 +161,12 @@ public class StructClass extends StructMember {
|
||||
return recordAttr.getComponents();
|
||||
}
|
||||
|
||||
public List<String> getPermittedSubclasses() {
|
||||
StructPermittedSubclassesAttribute permittedSubClassAttr = getAttribute(StructGeneralAttribute.ATTRIBUTE_PERMITTED_SUBCLASSES);
|
||||
if (permittedSubClassAttr == null) return null;
|
||||
return permittedSubClassAttr.getClasses();
|
||||
}
|
||||
|
||||
public int[] getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// 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.struct.attr;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
@@ -36,6 +36,7 @@ public class StructGeneralAttribute {
|
||||
public static final Key<StructMethodParametersAttribute> ATTRIBUTE_METHOD_PARAMETERS = new Key<>("MethodParameters");
|
||||
public static final Key<StructModuleAttribute> ATTRIBUTE_MODULE = new Key<>("Module");
|
||||
public static final Key<StructRecordAttribute> ATTRIBUTE_RECORD = new Key<>("Record");
|
||||
public static final Key<StructPermittedSubclassesAttribute> ATTRIBUTE_PERMITTED_SUBCLASSES = new Key<>("PermittedSubclasses");
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class Key<T extends StructGeneralAttribute> {
|
||||
@@ -100,6 +101,8 @@ public class StructGeneralAttribute {
|
||||
}
|
||||
else if (ATTRIBUTE_RECORD.name.equals(name)) {
|
||||
return new StructRecordAttribute();
|
||||
} else if (ATTRIBUTE_PERMITTED_SUBCLASSES.name.equals(name)) {
|
||||
return new StructPermittedSubclassesAttribute();
|
||||
}
|
||||
else {
|
||||
return null; // unsupported attribute
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// 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.struct.attr;
|
||||
|
||||
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
|
||||
import org.jetbrains.java.decompiler.util.DataInputFullStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
PermittedSubclasses_attribute {
|
||||
u2 attribute_name_index;
|
||||
u4 attribute_length;
|
||||
u2 number_of_classes;
|
||||
u2 classes[number_of_classes];
|
||||
}
|
||||
*/
|
||||
public class StructPermittedSubclassesAttribute extends StructGeneralAttribute {
|
||||
List<String> classes;
|
||||
|
||||
@Override
|
||||
public void initContent(DataInputFullStream data, ConstantPool pool) throws IOException {
|
||||
int numberOfClasses = data.readUnsignedShort();
|
||||
String[] classes = new String[numberOfClasses];
|
||||
for (int i = 0; i < numberOfClasses; i++) {
|
||||
classes[i] = pool.getPrimitiveConstant(data.readUnsignedShort()).getString();
|
||||
}
|
||||
this.classes = Arrays.asList(classes);
|
||||
}
|
||||
|
||||
public List<String> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
+18
@@ -147,6 +147,24 @@ public class SingleClassesTest {
|
||||
@Test public void testRecordVararg() { doTest("records/TestRecordVararg"); }
|
||||
@Test public void testRecordGenericVararg() { doTest("records/TestRecordGenericVararg"); }
|
||||
@Test public void testRecordAnno() { doTest("records/TestRecordAnno"); }
|
||||
@Test public void testRootWithClassInner() { doTest("sealed/RootWithClassInner"); }
|
||||
@Test public void testRootWithInterfaceInner() { doTest("sealed/RootWithInterfaceInner"); }
|
||||
@Test public void testRootWithClassOuter() { doTest("sealed/RootWithClassOuter",
|
||||
"sealed/ClassExtends", "sealed/ClassNonSealed", "sealed/ClassNonSealedExtendsImplements");
|
||||
}
|
||||
@Test public void testRootWithInterfaceOuter() { doTest("sealed/RootWithInterfaceOuter",
|
||||
"sealed/ClassImplements", "sealed/InterfaceNonSealed", "sealed/ClassNonSealedExtendsImplements");
|
||||
}
|
||||
@Test public void testClassNonSealed() { doTest("sealed/ClassNonSealed",
|
||||
"sealed/RootWithClassOuter", "sealed/ClassExtends", "sealed/ClassNonSealedExtendsImplements");
|
||||
}
|
||||
@Test public void testClassNonSealedExtendsImplements() { doTest("sealed/ClassNonSealedExtendsImplements",
|
||||
"sealed/RootWithClassOuter", "sealed/ClassExtends", "sealed/ClassNonSealed");
|
||||
}
|
||||
@Test public void testInterfaceNonSealed() { doTest("sealed/InterfaceNonSealed",
|
||||
"sealed/RootWithInterfaceOuter", "sealed/ClassImplements", "sealed/ClassNonSealedExtendsImplements");
|
||||
}
|
||||
@Test public void testRootWithModule() { doTest("sealed/foo/RootWithModule", "sealed/bar/BarClassExtends");}
|
||||
@Test public void testInheritanceChainCycle() { doTest("pkg/TestInheritanceChainCycle"); }
|
||||
@Test public void testDynamicConstantPoolEntry() { doTest("java11/TestDynamicConstantPoolEntry"); }
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed class ClassNonSealed extends RootWithClassOuter {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed class ClassNonSealedExtendsImplements extends RootWithClassOuter implements RootWithInterfaceOuter {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed interface InterfaceNonSealed extends RootWithInterfaceOuter {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package sealed;
|
||||
|
||||
sealed class RootWithClassInner {
|
||||
static final class Inner extends RootWithClassInner {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package sealed;
|
||||
|
||||
abstract sealed class RootWithClassOuter permits ClassExtends, ClassNonSealed, ClassNonSealedExtendsImplements {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package sealed;
|
||||
|
||||
sealed interface RootWithInterfaceInner {
|
||||
public static final class Inner implements RootWithInterfaceInner {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package sealed;
|
||||
|
||||
sealed interface RootWithInterfaceOuter permits ClassImplements, InterfaceNonSealed, ClassNonSealedExtendsImplements {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package sealed.foo;
|
||||
|
||||
import sealed.bar.BarClassExtends;
|
||||
|
||||
public abstract sealed class RootWithModule permits BarClassExtends {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
final class ClassExtends extends RootWithClassOuter {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
final class FinalImplements implements RootWithInterfaceOuter {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed class ClassNonSealed extends RootWithClassOuter {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed class ClassNonSealedImplements extends RootWithClassOuter implements RootWithInterfaceOuter {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
non-sealed interface InterfaceNonSealed extends RootWithInterfaceOuter {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package sealed;
|
||||
|
||||
sealed interface RootWithClassInner {
|
||||
final class Inner implements RootWithClassInner {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
abstract sealed class RootWithClassOuter permits ClassExtends, ClassNonSealed, ClassNonSealedImplements {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package sealed;
|
||||
|
||||
sealed interface RootWithInterfaceInner {
|
||||
final class Inner implements RootWithInterfaceInner {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package sealed;
|
||||
|
||||
sealed interface RootWithInterfaceOuter permits FinalImplements, InterfaceNonSealed, ClassNonSealedImplements {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package sealed.bar;
|
||||
|
||||
import sealed.foo.RootWithModule;
|
||||
|
||||
final public class BarClassExtends extends RootWithModule {
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package sealed.foo;
|
||||
|
||||
import sealed.bar.BarClassExtends;
|
||||
|
||||
public abstract sealed class RootWithModule permits BarClassExtends {
|
||||
}
|
||||
Reference in New Issue
Block a user