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

Reply via email to