This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git
The following commit(s) were added to refs/heads/master by this push:
new dd744dd8 Add support for permitted subclasses (#493)
dd744dd8 is described below
commit dd744dd8e313318d0a97aaf02af8efb1104f5fcb
Author: nbauma109 <[email protected]>
AuthorDate: Sun Feb 1 21:54:37 2026 +0100
Add support for permitted subclasses (#493)
* Add support for permitted subclasses
* fix checkstyle issues
* fix checksyle issues
* Added javadoc descriptions
* added javadoc
* Javadoc
Updated documentation for ATTR_PERMITTED_SUBCLASSES constant.
---------
Co-authored-by: nbauma109 <[email protected]>
Co-authored-by: Gary Gregory <[email protected]>
---
src/main/java/org/apache/bcel/Const.java | 12 +-
.../java/org/apache/bcel/classfile/Attribute.java | 2 +
.../apache/bcel/classfile/DescendingVisitor.java | 8 +
.../org/apache/bcel/classfile/EmptyVisitor.java | 5 +
.../apache/bcel/classfile/PermittedSubclasses.java | 177 +++++++++++++++++++++
.../java/org/apache/bcel/classfile/Visitor.java | 10 ++
.../verifier/statics/StringRepresentation.java | 11 ++
.../bcel/classfile/PermittedSubclassesTest.java | 72 +++++++++
src/test/resources/sealed/SealedDemo.java | 43 +++++
.../resources/sealed/sealed-demo-jdk21.0.8.jar | Bin 0 -> 2788 bytes
10 files changed, 338 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/apache/bcel/Const.java
b/src/main/java/org/apache/bcel/Const.java
index 24445b2e..60336ef0 100644
--- a/src/main/java/org/apache/bcel/Const.java
+++ b/src/main/java/org/apache/bcel/Const.java
@@ -3168,13 +3168,21 @@ public final class Const {
/** Attribute constant for Record. */
public static final byte ATTR_RECORD = 27;
+ /**
+ * Attribute constant for PermittedSubclasses.
+ *
+ * @since 6.13.0
+ */
+ public static final byte ATTR_PERMITTED_SUBCLASSES = 28;
+
/** Count of known attributes. */
- public static final short KNOWN_ATTRIBUTES = 28; // count of attributes
+ public static final short KNOWN_ATTRIBUTES = 29; // count of attributes
private static final String[] ATTRIBUTE_NAMES = { "SourceFile",
"ConstantValue", "Code", "Exceptions", "LineNumberTable", "LocalVariableTable",
"InnerClasses", "Synthetic", "Deprecated", "PMGClass",
"Signature", "StackMap", "RuntimeVisibleAnnotations",
"RuntimeInvisibleAnnotations",
"RuntimeVisibleParameterAnnotations",
"RuntimeInvisibleParameterAnnotations", "AnnotationDefault",
"LocalVariableTypeTable", "EnclosingMethod",
- "StackMapTable", "BootstrapMethods", "MethodParameters", "Module",
"ModulePackages", "ModuleMainClass", "NestHost", "NestMembers", "Record" };
+ "StackMapTable", "BootstrapMethods", "MethodParameters", "Module",
"ModulePackages", "ModuleMainClass", "NestHost", "NestMembers", "Record",
+ "PermittedSubclasses" };
/**
* Constants used in the StackMap attribute.
diff --git a/src/main/java/org/apache/bcel/classfile/Attribute.java
b/src/main/java/org/apache/bcel/classfile/Attribute.java
index d78c5f56..90246fa4 100644
--- a/src/main/java/org/apache/bcel/classfile/Attribute.java
+++ b/src/main/java/org/apache/bcel/classfile/Attribute.java
@@ -199,6 +199,8 @@ public abstract class Attribute implements Cloneable, Node {
return new NestMembers(nameIndex, length, dataInput, constantPool);
case Const.ATTR_RECORD:
return new Record(nameIndex, length, dataInput, constantPool);
+ case Const.ATTR_PERMITTED_SUBCLASSES:
+ return new PermittedSubclasses(nameIndex, length, dataInput,
constantPool);
default:
// Never reached
throw new IllegalStateException("Unrecognized attribute type tag
parsed: " + tag);
diff --git a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
index 45180804..37f72df7 100644
--- a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
+++ b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
@@ -496,6 +496,14 @@ public class DescendingVisitor implements Visitor {
stack.pop();
}
+ /** @since 6.13.0 */
+ @Override
+ public void visitPermittedSubclasses(final PermittedSubclasses obj) {
+ stack.push(obj);
+ obj.accept(visitor);
+ stack.pop();
+ }
+
/**
* @since 6.0
*/
diff --git a/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java
b/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java
index 7a16e9ba..7a5165bd 100644
--- a/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java
+++ b/src/main/java/org/apache/bcel/classfile/EmptyVisitor.java
@@ -284,6 +284,11 @@ public class EmptyVisitor implements Visitor {
public void visitNestMembers(final NestMembers obj) {
}
+ /** @since 6.13.0 */
+ @Override
+ public void visitPermittedSubclasses(final PermittedSubclasses obj) {
+ }
+
/**
* @since 6.0
*/
diff --git a/src/main/java/org/apache/bcel/classfile/PermittedSubclasses.java
b/src/main/java/org/apache/bcel/classfile/PermittedSubclasses.java
new file mode 100644
index 00000000..b5e48f8c
--- /dev/null
+++ b/src/main/java/org/apache/bcel/classfile/PermittedSubclasses.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.util.Args;
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * This class is derived from <em>Attribute</em> and records the classes and
interfaces that are permitted to extend or
+ * implement the current class or interface. There may be at most one
PermittedSubclasses attribute in a ClassFile
+ * structure.
+ *
+ * @see Attribute
+ * @since 6.13.0
+ */
+public final class PermittedSubclasses extends Attribute {
+
+ private int[] classes;
+
+ /**
+ * Constructs object from input stream.
+ *
+ * @param nameIndex Index in constant pool.
+ * @param length Content length in bytes.
+ * @param dataInput Input stream.
+ * @param constantPool Array of constants.
+ * @throws IOException if an I/O error occurs.
+ */
+ PermittedSubclasses(final int nameIndex, final int length, final DataInput
dataInput, final ConstantPool constantPool) throws IOException {
+ this(nameIndex, length, (int[]) null, constantPool);
+ classes = ClassParser.readU2U2Table(dataInput);
+ }
+
+ /**
+ * Constructs object from table of class indices in constant pool.
+ *
+ * @param nameIndex Index in constant pool.
+ * @param length Content length in bytes.
+ * @param classes Table of indices in constant pool.
+ * @param constantPool Array of constants.
+ */
+ public PermittedSubclasses(final int nameIndex, final int length, final
int[] classes, final ConstantPool constantPool) {
+ super(Const.ATTR_PERMITTED_SUBCLASSES, nameIndex, length,
constantPool);
+ this.classes = ArrayUtils.nullToEmpty(classes);
+ Args.requireU2(this.classes.length, "classes.length");
+ }
+
+ /**
+ * Initialize from another object. Note that both objects use the same
references (shallow copy). Use copy() for a
+ * physical copy.
+ *
+ * @param c Source to copy.
+ */
+ public PermittedSubclasses(final PermittedSubclasses c) {
+ this(c.getNameIndex(), c.getLength(), c.getClasses(),
c.getConstantPool());
+ }
+
+ /**
+ * Called by objects that are traversing the nodes of the tree implicitly
defined by the contents of a Java class.
+ * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree
of objects.
+ *
+ * @param v Visitor object.
+ */
+ @Override
+ public void accept(final Visitor v) {
+ v.visitPermittedSubclasses(this);
+ }
+
+ /**
+ * Creates a deep clone of this object given constant pool.
+ *
+ * @return deep copy of this attribute.
+ */
+ @Override
+ public Attribute copy(final ConstantPool constantPool) {
+ final PermittedSubclasses c = (PermittedSubclasses) clone();
+ if (classes.length > 0) {
+ c.classes = classes.clone();
+ }
+ c.setConstantPool(constantPool);
+ return c;
+ }
+
+ /**
+ * Dumps PermittedSubclasses attribute to file stream in binary format.
+ *
+ * @param file Output file stream.
+ * @throws IOException if an I/O error occurs.
+ */
+ @Override
+ public void dump(final DataOutputStream file) throws IOException {
+ super.dump(file);
+ file.writeShort(classes.length);
+ for (final int index : classes) {
+ file.writeShort(index);
+ }
+ }
+
+ /**
+ * Gets the class indices in constant pool.
+ *
+ * @return array of indices into constant pool of class names.
+ */
+ public int[] getClasses() {
+ return classes;
+ }
+
+ /**
+ * Gets permitted class names.
+ *
+ * @return string array of class names.
+ */
+ public String[] getClassNames() {
+ final String[] names = new String[classes.length];
+ Arrays.setAll(names, i ->
Utility.pathToPackage(super.getConstantPool().getConstantString(classes[i],
Const.CONSTANT_Class)));
+ return names;
+ }
+
+ /**
+ * Gets the number of classes.
+ *
+ * @return Length of classes table.
+ */
+ public int getNumberClasses() {
+ return classes.length;
+ }
+
+ /**
+ * Sets class indices.
+ *
+ * @param classes the list of class indexes Also redefines
number_of_classes according to table length.
+ */
+ public void setClasses(final int[] classes) {
+ this.classes = ArrayUtils.nullToEmpty(classes);
+ }
+
+ /**
+ * String representation of PermittedSubclasses (for debugging purposes).
+ *
+ * @return String representation, that is, a list of permitted subclasses.
+ */
+ @Override
+ public String toString() {
+ final StringBuilder buf = new StringBuilder();
+ buf.append("PermittedSubclasses(");
+ buf.append(classes.length);
+ buf.append("):\n");
+ for (final int index : classes) {
+ final String className =
super.getConstantPool().getConstantString(index, Const.CONSTANT_Class);
+ buf.append(" ").append(Utility.compactClassName(className,
false)).append("\n");
+ }
+ return buf.substring(0, buf.length() - 1); // remove the last newline
+ }
+}
diff --git a/src/main/java/org/apache/bcel/classfile/Visitor.java
b/src/main/java/org/apache/bcel/classfile/Visitor.java
index 86fa8b57..5df8e243 100644
--- a/src/main/java/org/apache/bcel/classfile/Visitor.java
+++ b/src/main/java/org/apache/bcel/classfile/Visitor.java
@@ -411,6 +411,16 @@ public interface Visitor {
// empty
}
+ /**
+ * Visits a PermittedSubclasses attribute.
+ *
+ * @param obj the attribute.
+ * @since 6.13.0
+ */
+ default void visitPermittedSubclasses(final PermittedSubclasses obj) {
+ // empty
+ }
+
/**
* Visits a ParameterAnnotations attribute.
*
diff --git
a/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
b/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
index 88b0d53c..e9329f90 100644
--- a/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
+++ b/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
@@ -61,6 +61,7 @@ import org.apache.bcel.classfile.NestMembers;
import org.apache.bcel.classfile.Node;
import org.apache.bcel.classfile.ParameterAnnotationEntry;
import org.apache.bcel.classfile.ParameterAnnotations;
+import org.apache.bcel.classfile.PermittedSubclasses;
import org.apache.bcel.classfile.Record;
import org.apache.bcel.classfile.RecordComponentInfo;
import org.apache.bcel.classfile.Signature;
@@ -385,6 +386,16 @@ public class StringRepresentation extends
org.apache.bcel.classfile.EmptyVisitor
tostring = toString(obj);
}
+ /**
+ * Visits PermittedSubclasses attribute.
+ *
+ * @since 6.13.0
+ */
+ @Override
+ public void visitPermittedSubclasses(final PermittedSubclasses obj) {
+ tostring = toString(obj);
+ }
+
/**
* @since 6.0
*/
diff --git
a/src/test/java/org/apache/bcel/classfile/PermittedSubclassesTest.java
b/src/test/java/org/apache/bcel/classfile/PermittedSubclassesTest.java
new file mode 100644
index 00000000..503349ca
--- /dev/null
+++ b/src/test/java/org/apache/bcel/classfile/PermittedSubclassesTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * https://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 org.apache.bcel.classfile;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.bcel.AbstractTest;
+import org.apache.bcel.verifier.statics.StringRepresentation;
+import org.junit.jupiter.api.Test;
+
+class PermittedSubclassesTest extends AbstractTest {
+
+ private static final String SEALED_JAR_PATH =
"src/test/resources/sealed/sealed-demo-jdk21.0.8.jar";
+
+ private static final String SHAPE_CLASS_ENTRY =
"org/jd/core/v1/SealedDemo$Shape.class";
+
+ private JavaClass parseJarClass(final String entryName) throws IOException
{
+ try (JarFile jar = new JarFile(SEALED_JAR_PATH)) {
+ final JarEntry entry = jar.getJarEntry(entryName);
+ assertNotNull(entry, "Missing jar entry: " + entryName);
+ try (InputStream inputStream = jar.getInputStream(entry)) {
+ return new ClassParser(inputStream, entryName).parse();
+ }
+ }
+ }
+
+ @Test
+ void readsPermittedSubclassesAttribute() throws IOException {
+ final JavaClass clazz = parseJarClass(SHAPE_CLASS_ENTRY);
+ final Attribute[] attributes = findAttribute("PermittedSubclasses",
clazz);
+ assertEquals(1, attributes.length, "Expected one PermittedSubclasses
attribute");
+ final PermittedSubclasses permittedSubclasses = (PermittedSubclasses)
attributes[0];
+ final List<String> classNames =
Arrays.asList(permittedSubclasses.getClassNames());
+ assertEquals(2, classNames.size(), "Expected two permitted
subclasses");
+ assertTrue(classNames.contains("org.jd.core.v1.SealedDemo$Circle"),
"Missing permitted subclass Circle");
+ assertTrue(classNames.contains("org.jd.core.v1.SealedDemo$Rectangle"),
"Missing permitted subclass Rectangle");
+ }
+
+ @Test
+ void stringRepresentationHandlesPermittedSubclasses() throws IOException {
+ final JavaClass clazz = parseJarClass(SHAPE_CLASS_ENTRY);
+ final Attribute[] attributes = findAttribute("PermittedSubclasses",
clazz);
+ final PermittedSubclasses permittedSubclasses = (PermittedSubclasses)
attributes[0];
+ assertEquals(permittedSubclasses.toString(), new
StringRepresentation(permittedSubclasses).toString());
+ }
+}
diff --git a/src/test/resources/sealed/SealedDemo.java
b/src/test/resources/sealed/SealedDemo.java
new file mode 100644
index 00000000..cda1f71b
--- /dev/null
+++ b/src/test/resources/sealed/SealedDemo.java
@@ -0,0 +1,43 @@
+package org.jd.core.v1;
+
+public class SealedDemo {
+
+ sealed interface Shape permits Circle, Rectangle {
+ double area();
+ }
+
+ static final class Circle implements Shape {
+ private final double radius;
+
+ Circle(double radius) {
+ this.radius = radius;
+ }
+
+ @Override
+ public double area() {
+ return Math.PI * radius * radius;
+ }
+ }
+
+ static non-sealed class Rectangle implements Shape {
+ private final double width;
+ private final double height;
+
+ Rectangle(double width, double height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public double area() {
+ return width * height;
+ }
+ }
+
+ double sealedSwitch(Shape shape) {
+ return switch (shape) {
+ case Circle circle -> circle.area();
+ case Rectangle rectangle -> rectangle.area();
+ };
+ }
+}
diff --git a/src/test/resources/sealed/sealed-demo-jdk21.0.8.jar
b/src/test/resources/sealed/sealed-demo-jdk21.0.8.jar
new file mode 100644
index 00000000..46d49ef9
Binary files /dev/null and
b/src/test/resources/sealed/sealed-demo-jdk21.0.8.jar differ