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 de0c6e1a Add JavaClassTest
de0c6e1a is described below
commit de0c6e1a0a22da030a31c6e475fd411d3cb71050
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Jan 7 07:19:52 2026 -0500
Add JavaClassTest
---
.../java/org/apache/bcel/classfile/JavaClass.java | 61 +--
.../org/apache/bcel/classfile/JavaClassTest.java | 464 +++++++++++++++++++++
2 files changed, 500 insertions(+), 25 deletions(-)
diff --git a/src/main/java/org/apache/bcel/classfile/JavaClass.java
b/src/main/java/org/apache/bcel/classfile/JavaClass.java
index 829e6765..648ef21b 100644
--- a/src/main/java/org/apache/bcel/classfile/JavaClass.java
+++ b/src/main/java/org/apache/bcel/classfile/JavaClass.java
@@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -49,6 +50,8 @@ import org.apache.commons.lang3.ArrayUtils;
*/
public class JavaClass extends AccessFlags implements Cloneable, Node,
Comparable<JavaClass> {
+ private static final String CLASS_NAME_OBJECT = "java.lang.Object";
+
/**
* The standard class file extension.
*
@@ -231,7 +234,7 @@ public class JavaClass extends AccessFlags implements
Cloneable, Node, Comparabl
superclassName =
constantPool.getConstantString(superclassNameIndex, Const.CONSTANT_Class);
superclassName = Utility.compactClassName(superclassName, false);
} else {
- superclassName = "java.lang.Object";
+ superclassName = CLASS_NAME_OBJECT;
}
interfaceNames = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
@@ -417,35 +420,43 @@ public class JavaClass extends AccessFlags implements
Cloneable, Node, Comparabl
* @since 6.8.0
*/
public Field findField(final String fieldName, final Type fieldType)
throws ClassNotFoundException {
- for (final Field field : fields) {
- if (field.getName().equals(fieldName)) {
- final Type fType = Type.getType(field.getSignature());
- /*
- * TODO: Check if assignment compatibility is sufficient. What
does Sun do?
- */
- if (fType.equals(fieldType)) {
- return field;
- }
- }
- }
+ return findFieldVisit(fieldName, fieldType, new HashSet<>());
+ }
- final JavaClass superclass = getSuperClass();
- if (superclass != null &&
!"java.lang.Object".equals(superclass.getClassName())) {
- final Field f = superclass.findField(fieldName, fieldType);
- if (f != null && (f.isPublic() || f.isProtected() ||
!f.isPrivate() && packageName.equals(superclass.getPackageName()))) {
- return f;
- }
+ private Field findFieldVisit(final String fieldName, final Type fieldType,
final Set<JavaClass> visiting) throws ClassNotFoundException {
+ if (!visiting.add(this)) {
+ throw new ClassCircularityError(getClassName());
}
- final JavaClass[] implementedInterfaces = getInterfaces();
- if (implementedInterfaces != null) {
- for (final JavaClass implementedInterface : implementedInterfaces)
{
- final Field f = implementedInterface.findField(fieldName,
fieldType);
- if (f != null) {
+ try {
+ for (final Field field : fields) {
+ if (field.getName().equals(fieldName)) {
+ final Type fType = Type.getType(field.getSignature());
+ // TODO: Check if assignment compatibility is sufficient.
What does Sun do?
+ if (fType.equals(fieldType)) {
+ return field;
+ }
+ }
+ }
+ final JavaClass superclass = getSuperClass();
+ if (superclass != null &&
!CLASS_NAME_OBJECT.equals(superclass.getClassName())) {
+ final Field f = superclass.findFieldVisit(fieldName,
fieldType, visiting);
+ if (f != null && (f.isPublic() || f.isProtected() ||
!f.isPrivate() && packageName.equals(superclass.getPackageName()))) {
return f;
}
}
+ final JavaClass[] implementedInterfaces = getInterfaces();
+ if (implementedInterfaces != null) {
+ for (final JavaClass implementedInterface :
implementedInterfaces) {
+ final Field f =
implementedInterface.findFieldVisit(fieldName, fieldType, visiting);
+ if (f != null) {
+ return f;
+ }
+ }
+ }
+ return null;
+ } finally {
+ visiting.remove(this);
}
- return null;
}
/**
@@ -669,7 +680,7 @@ public class JavaClass extends AccessFlags implements
Cloneable, Node, Comparabl
* @throws ClassNotFoundException if the superclass can't be found
*/
public JavaClass getSuperClass() throws ClassNotFoundException {
- if ("java.lang.Object".equals(getClassName())) {
+ if (CLASS_NAME_OBJECT.equals(getClassName())) {
return null;
}
return repository.loadClass(getSuperclassName());
diff --git a/src/test/java/org/apache/bcel/classfile/JavaClassTest.java
b/src/test/java/org/apache/bcel/classfile/JavaClassTest.java
new file mode 100644
index 00000000..c7dd07e1
--- /dev/null
+++ b/src/test/java/org/apache/bcel/classfile/JavaClassTest.java
@@ -0,0 +1,464 @@
+/*
+ * 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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.sql.Date;
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.AbstractCollection;
+import java.util.AbstractList;
+import java.util.AbstractMap;
+import java.util.AbstractQueue;
+import java.util.AbstractSequentialList;
+import java.util.AbstractSet;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.ConcurrentModificationException;
+import java.util.Currency;
+import java.util.Deque;
+import java.util.Dictionary;
+import java.util.DoubleSummaryStatistics;
+import java.util.DuplicateFormatFlagsException;
+import java.util.EmptyStackException;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.EventListenerProxy;
+import java.util.EventObject;
+import java.util.FormatFlagsConversionMismatchException;
+import java.util.Formattable;
+import java.util.FormattableFlags;
+import java.util.Formatter;
+import java.util.FormatterClosedException;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.IllegalFormatCodePointException;
+import java.util.IllegalFormatConversionException;
+import java.util.IllegalFormatException;
+import java.util.IllegalFormatFlagsException;
+import java.util.IllegalFormatPrecisionException;
+import java.util.IllegalFormatWidthException;
+import java.util.IllformedLocaleException;
+import java.util.InputMismatchException;
+import java.util.IntSummaryStatistics;
+import java.util.InvalidPropertiesFormatException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.ListResourceBundle;
+import java.util.Locale;
+import java.util.LongSummaryStatistics;
+import java.util.Map;
+import java.util.MissingFormatArgumentException;
+import java.util.MissingFormatWidthException;
+import java.util.MissingResourceException;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.PrimitiveIterator;
+import java.util.PriorityQueue;
+import java.util.Properties;
+import java.util.PropertyPermission;
+import java.util.PropertyResourceBundle;
+import java.util.Queue;
+import java.util.Random;
+import java.util.RandomAccess;
+import java.util.ResourceBundle;
+import java.util.Scanner;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.SimpleTimeZone;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.SplittableRandom;
+import java.util.Stack;
+import java.util.StringJoiner;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TooManyListenersException;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.UnknownFormatConversionException;
+import java.util.UnknownFormatFlagsException;
+import java.util.Vector;
+import java.util.WeakHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.BinaryOperator;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.DoubleConsumer;
+import java.util.function.DoubleFunction;
+import java.util.function.DoublePredicate;
+import java.util.function.DoubleSupplier;
+import java.util.function.DoubleToIntFunction;
+import java.util.function.DoubleToLongFunction;
+import java.util.function.DoubleUnaryOperator;
+import java.util.function.Function;
+import java.util.function.IntBinaryOperator;
+import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
+import java.util.function.IntPredicate;
+import java.util.function.IntSupplier;
+import java.util.function.IntToDoubleFunction;
+import java.util.function.IntToLongFunction;
+import java.util.function.IntUnaryOperator;
+import java.util.function.LongBinaryOperator;
+import java.util.function.LongConsumer;
+import java.util.function.LongFunction;
+import java.util.function.LongPredicate;
+import java.util.function.LongSupplier;
+import java.util.function.LongToDoubleFunction;
+import java.util.function.LongToIntFunction;
+import java.util.function.LongUnaryOperator;
+import java.util.function.ObjDoubleConsumer;
+import java.util.function.ObjIntConsumer;
+import java.util.function.ObjLongConsumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.ToDoubleBiFunction;
+import java.util.function.ToDoubleFunction;
+import java.util.function.ToIntBiFunction;
+import java.util.function.ToIntFunction;
+import java.util.function.ToLongBiFunction;
+import java.util.function.ToLongFunction;
+import java.util.function.UnaryOperator;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.Repository;
+import org.apache.bcel.generic.ClassGen;
+import org.apache.bcel.generic.InstructionFactory;
+import org.apache.bcel.generic.InstructionList;
+import org.apache.bcel.generic.MethodGen;
+import org.apache.bcel.generic.Type;
+import org.apache.bcel.util.ClassPath;
+import org.apache.bcel.util.SyntheticRepository;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Tests {@link JavaClass}.
+ */
+class JavaClassTest {
+
+ private static final String CLASS_NAME = "TargetClass";
+
+ private static final Class<?>[] CLASSES_JAVA_LANG = {
AbstractMethodError.class, Appendable.class, ArithmeticException.class,
+ ArrayIndexOutOfBoundsException.class, ArrayStoreException.class,
AssertionError.class, AutoCloseable.class, Boolean.class,
+ BootstrapMethodError.class, Byte.class, Character.class,
Character.Subset.class, Character.UnicodeBlock.class,
Character.UnicodeScript.class,
+ CharSequence.class, Class.class, ClassCastException.class,
ClassCircularityError.class, ClassFormatError.class, ClassLoader.class,
+ ClassNotFoundException.class, ClassValue.class, Cloneable.class,
CloneNotSupportedException.class, Comparable.class,
+ /* Compiler.class, */ Deprecated.class, Double.class, Enum.class,
EnumConstantNotPresentException.class, Error.class, Exception.class,
+ ExceptionInInitializerError.class, Float.class,
FunctionalInterface.class, IllegalAccessError.class,
IllegalAccessException.class,
+ IllegalArgumentException.class,
IllegalMonitorStateException.class, IllegalStateException.class,
IllegalThreadStateException.class,
+ IncompatibleClassChangeError.class,
IndexOutOfBoundsException.class, InheritableThreadLocal.class,
InstantiationError.class,
+ InstantiationException.class, Integer.class, InternalError.class,
InterruptedException.class, Iterable.class, LinkageError.class, Long.class,
+ Math.class, NegativeArraySizeException.class,
NoClassDefFoundError.class, NoSuchFieldError.class, NoSuchFieldException.class,
+ NoSuchMethodError.class, NoSuchMethodException.class,
NullPointerException.class, Number.class, NumberFormatException.class,
Object.class,
+ OutOfMemoryError.class, Override.class, Package.class,
Process.class, ProcessBuilder.class, ProcessBuilder.Redirect.class,
+ ProcessBuilder.Redirect.Type.class, Readable.class,
ReflectiveOperationException.class, Runnable.class, Runtime.class,
RuntimeException.class,
+ RuntimePermission.class, SafeVarargs.class,
SecurityException.class, SecurityManager.class, Short.class,
StackOverflowError.class,
+ StackTraceElement.class, StrictMath.class, String.class,
StringBuffer.class, StringBuilder.class, StringIndexOutOfBoundsException.class,
+ SuppressWarnings.class, System.class, Thread.class,
Thread.State.class, Thread.UncaughtExceptionHandler.class, ThreadDeath.class,
ThreadGroup.class,
+ ThreadLocal.class, Throwable.class, TypeNotPresentException.class,
UnknownError.class, UnsatisfiedLinkError.class,
+ UnsupportedClassVersionError.class,
UnsupportedOperationException.class, VerifyError.class,
VirtualMachineError.class, Void.class };
+// Doesn't compile due to cyclic inheritance
+// private interface InterfaceA extends InterfaceB {
+// }
+//
+// private interface InterfaceB extends InterfaceA {
+// }
+
+ private static final Class<?>[] CLASSES_JAVA_TIME = { Clock.class,
DateTimeException.class, DayOfWeek.class, Duration.class, Instant.class,
LocalDate.class, LocalDateTime.class,
+ LocalTime.class, Month.class, MonthDay.class,
OffsetDateTime.class, OffsetTime.class, Period.class, Year.class,
YearMonth.class,
+ ZonedDateTime.class, ZoneId.class, ZoneOffset.class };
+
+ private static final Class<?>[] CLASSES_JAVA_UTIL = {
AbstractCollection.class, AbstractList.class, AbstractMap.class,
AbstractMap.SimpleEntry.class,
+ AbstractMap.SimpleImmutableEntry.class, AbstractQueue.class,
AbstractSequentialList.class, AbstractSet.class, ArrayDeque.class,
ArrayList.class,
+ Arrays.class, Base64.class, Base64.Decoder.class,
Base64.Encoder.class, BitSet.class, Calendar.class, Calendar.Builder.class,
Collection.class,
+ Collections.class, Comparator.class,
ConcurrentModificationException.class, Currency.class, Date.class, Deque.class,
Dictionary.class,
+ DoubleSummaryStatistics.class,
DuplicateFormatFlagsException.class, EmptyStackException.class, EnumMap.class,
Enumeration.class, EnumSet.class,
+ EventListener.class, EventListenerProxy.class, EventObject.class,
Formattable.class, FormattableFlags.class,
+ FormatFlagsConversionMismatchException.class, Formatter.class,
Formatter.BigDecimalLayoutForm.class, FormatterClosedException.class,
+ GregorianCalendar.class, HashMap.class, HashSet.class,
Hashtable.class, IdentityHashMap.class, IllegalFormatCodePointException.class,
+ IllegalFormatConversionException.class,
IllegalFormatException.class, IllegalFormatFlagsException.class,
IllegalFormatPrecisionException.class,
+ IllegalFormatWidthException.class, IllformedLocaleException.class,
InputMismatchException.class, IntSummaryStatistics.class,
+ InvalidPropertiesFormatException.class, Iterator.class,
LinkedHashMap.class, LinkedHashSet.class, LinkedList.class, List.class,
ListIterator.class,
+ ListResourceBundle.class, Locale.class, Locale.Builder.class,
Locale.Category.class, Locale.FilteringMode.class, Locale.LanguageRange.class,
+ LongSummaryStatistics.class, Map.class, Map.Entry.class,
MissingFormatArgumentException.class, MissingFormatWidthException.class,
+ MissingResourceException.class, NavigableMap.class,
NavigableSet.class, NoSuchElementException.class, Objects.class,
Observable.class,
+ Observer.class, Optional.class, OptionalDouble.class,
OptionalInt.class, OptionalLong.class, PrimitiveIterator.class,
+ PrimitiveIterator.OfDouble.class, PrimitiveIterator.OfInt.class,
PrimitiveIterator.OfLong.class, PriorityQueue.class, Properties.class,
+ PropertyPermission.class, PropertyResourceBundle.class,
Queue.class, Random.class, RandomAccess.class, ResourceBundle.class,
+ ResourceBundle.Control.class, Scanner.class,
ServiceConfigurationError.class, ServiceLoader.class, Set.class,
SimpleTimeZone.class, SortedMap.class,
+ SortedSet.class, Spliterator.class, Spliterator.OfDouble.class,
Spliterator.OfInt.class, Spliterator.OfLong.class,
Spliterator.OfPrimitive.class,
+ Spliterators.class, Spliterators.AbstractDoubleSpliterator.class,
Spliterators.AbstractIntSpliterator.class,
+ Spliterators.AbstractLongSpliterator.class,
Spliterators.AbstractSpliterator.class, SplittableRandom.class, Stack.class,
StringJoiner.class,
+ StringTokenizer.class, Timer.class, TimerTask.class,
TimeZone.class, TooManyListenersException.class, TreeMap.class, TreeSet.class,
+ UnknownFormatConversionException.class,
UnknownFormatFlagsException.class, UUID.class, Vector.class, WeakHashMap.class
};
+
+ private static final Class<?>[] CLASSES_JAVA_UTIL_STREAM = {
BiConsumer.class, BiFunction.class, BinaryOperator.class, BiPredicate.class,
BooleanSupplier.class, Consumer.class,
+ DoubleBinaryOperator.class, DoubleConsumer.class,
DoubleFunction.class, DoublePredicate.class, DoubleSupplier.class,
DoubleToIntFunction.class,
+ DoubleToLongFunction.class, DoubleUnaryOperator.class,
Function.class, IntBinaryOperator.class, IntConsumer.class, IntFunction.class,
+ IntPredicate.class, IntSupplier.class, IntToDoubleFunction.class,
IntToLongFunction.class, IntUnaryOperator.class, LongBinaryOperator.class,
+ LongConsumer.class, LongFunction.class, LongPredicate.class,
LongSupplier.class, LongToDoubleFunction.class, LongToIntFunction.class,
+ LongUnaryOperator.class, ObjDoubleConsumer.class,
ObjIntConsumer.class, ObjLongConsumer.class, Predicate.class, Supplier.class,
+ ToDoubleBiFunction.class, ToDoubleFunction.class,
ToIntBiFunction.class, ToIntFunction.class, ToLongBiFunction.class,
ToLongFunction.class,
+ UnaryOperator.class };
+
+ @TempDir
+ static Path tempDir;
+
+ @BeforeAll
+ static void beforeAll() throws Exception {
+ // Create InterfaceA that extends InterfaceB (will create cycle)
+ createInterfaceA();
+ // Create InterfaceB that extends InterfaceA (completes the cycle)
+ createInterfaceB();
+ // Create a class that implements InterfaceA
+ createTargetClass();
+ // Cycle: InterfaceA -> InterfaceB -> InterfaceA -> ...
+ }
+
+ private static byte[] createClass(final String name, final String
extendsClass) throws IOException {
+ final ClassGen cg = new ClassGen(name, extendsClass, name + ".java",
Const.ACC_PUBLIC, new String[] {});
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ cg.getJavaClass().dump(baos);
+ return baos.toByteArray();
+ }
+
+ private static byte[] createClass(final String name, final String
extendsClass, final String implementsInterface) throws IOException {
+ final ClassGen cg = new ClassGen(name, extendsClass, name + ".java",
Const.ACC_PUBLIC, new String[] { implementsInterface });
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ cg.getJavaClass().dump(baos);
+ return baos.toByteArray();
+ }
+
+ private static byte[] createInterface(final String name, final String
extendsInterface) throws IOException {
+ final ClassGen cg = new ClassGen(name, "java.lang.Object", name +
".java", Const.ACC_PUBLIC | Const.ACC_INTERFACE | Const.ACC_ABSTRACT,
+ new String[] { extendsInterface });
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ cg.getJavaClass().dump(baos);
+ return baos.toByteArray();
+ }
+
+ private static void createInterfaceA() throws Exception {
+ // Create InterfaceA that extends InterfaceB (creating part of the
cycle)
+ final ClassGen cg = new ClassGen("InterfaceA", // class name
+ "java.lang.Object", // super class
+ "InterfaceA.java", // source file
+ Const.ACC_PUBLIC | Const.ACC_INTERFACE | Const.ACC_ABSTRACT,
// access flags
+ new String[] { "InterfaceB" } // interfaces (extends
InterfaceB)
+ );
+ // Create the class file
+ cg.getJavaClass().dump(tempDir.resolve("InterfaceA.class").toString());
+ }
+
+ private static void createInterfaceB() throws Exception {
+ // Create InterfaceB that extends InterfaceA (completing the cycle)
+ final ClassGen cg = new ClassGen("InterfaceB", // class name
+ "java.lang.Object", // super class
+ "InterfaceB.java", // source file
+ Const.ACC_PUBLIC | Const.ACC_INTERFACE | Const.ACC_ABSTRACT,
// access flags
+ new String[] { "InterfaceA" } // interfaces (extends
InterfaceA)
+ );
+ // Create the class file
+ cg.getJavaClass().dump(tempDir.resolve("InterfaceB.class").toString());
+ }
+
+ private static void createTargetClass() throws Exception {
+ // Create a class that implements InterfaceA
+ final ClassGen cg = new ClassGen(CLASS_NAME, // class name
+ "java.lang.Object", // super class
+ "VulnerableClass.java", // source file
+ Const.ACC_PUBLIC, // access flags
+ new String[] { "InterfaceA" } // interfaces
+ );
+ // Add default constructor
+ final InstructionList il = new InstructionList();
+ final MethodGen constructor = new MethodGen(Const.ACC_PUBLIC,
Type.VOID, Type.NO_ARGS, new String[] {}, "<init>", CLASS_NAME, il,
cg.getConstantPool());
+ final InstructionFactory factory = new InstructionFactory(cg);
+ il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
+ il.append(factory.createInvoke("java.lang.Object", "<init>",
Type.VOID, Type.NO_ARGS, Const.INVOKESPECIAL));
+ il.append(InstructionFactory.createReturn(Type.VOID));
+ constructor.setMaxStack();
+ constructor.setMaxLocals();
+ cg.addMethod(constructor.getMethod());
+ il.dispose();
+ // Create the class file
+ cg.getJavaClass().dump(tempDir.resolve(CLASS_NAME +
".class").toString());
+ }
+
+ static Class<?>[] getClassesJavaLang() {
+ return CLASSES_JAVA_LANG;
+ }
+
+ static Class<?>[] getClassesJavaTime() {
+ return CLASSES_JAVA_TIME;
+ }
+
+ static Class<?>[] getClassesJavaUtil() {
+ return CLASSES_JAVA_UTIL;
+ }
+
+ static Class<?>[] getClassesJavaUtilStream() {
+ return CLASSES_JAVA_UTIL_STREAM;
+ }
+
+ private Field findFieldDoesNotExist(final Class<?> clazz) throws
ClassNotFoundException {
+ return
Repository.lookupClass(clazz.getName()).findField("nonExistentField", Type.INT);
+ }
+
+ @Test
+ void testFindFieldCustomClass() throws Exception {
+ final byte[] classABytes = createClass("CyclicClassA", "CyclicClassB");
+ final byte[] classBBytes = createInterface("CyclicClassB",
"CyclicClassA");
+ final byte[] testClassBytes = createClass("CyclicTestClass",
"CyclicClassA");
+ final JavaClass interfaceA = new ClassParser(new
ByteArrayInputStream(classABytes), "CyclicClassA.class").parse();
+ final JavaClass interfaceB = new ClassParser(new
ByteArrayInputStream(classBBytes), "CyclicClassB.class").parse();
+ final JavaClass testClass = new ClassParser(new
ByteArrayInputStream(testClassBytes), "CyclicTestClass.class").parse();
+ final SyntheticRepository repo = SyntheticRepository.getInstance();
+ try {
+ repo.storeClass(interfaceA);
+ repo.storeClass(interfaceB);
+ repo.storeClass(testClass);
+ Repository.setRepository(repo);
+ assertThrows(ClassCircularityError.class, () ->
testClass.findField("nonExistentField", Type.INT));
+ } finally {
+ repo.removeClass(interfaceA);
+ repo.removeClass(interfaceB);
+ repo.removeClass(testClass);
+ }
+ }
+
+ @Test
+ void testFindFieldCustomInterface1() throws ClassNotFoundException {
+ // Set up repository to load classes from the malicious_classes
directory
+ final String classPath = tempDir.toString() +
System.getProperty("path.separator") + System.getProperty("java.class.path");
+ Repository.setRepository(SyntheticRepository.getInstance(new
ClassPath(classPath)));
+ assertThrows(ClassCircularityError.class, () ->
Repository.lookupClass(CLASS_NAME).findField("nonExistentField", Type.INT));
+ }
+
+ @Test
+ void testFindFieldCustomInterface2() throws Exception {
+ final byte[] interfaceABytes = createInterface("CyclicInterfaceA",
"CyclicInterfaceB");
+ final byte[] interfaceBBytes = createInterface("CyclicInterfaceB",
"CyclicInterfaceA");
+ final byte[] testClassBytes = createClass("CyclicTestClass",
"java.lang.Object", "CyclicInterfaceA");
+ final JavaClass interfaceA = new ClassParser(new
ByteArrayInputStream(interfaceABytes), "CyclicInterfaceA.class").parse();
+ final JavaClass interfaceB = new ClassParser(new
ByteArrayInputStream(interfaceBBytes), "CyclicInterfaceB.class").parse();
+ final JavaClass testClass = new ClassParser(new
ByteArrayInputStream(testClassBytes), "CyclicTestClass.class").parse();
+ final SyntheticRepository repo = SyntheticRepository.getInstance();
+ try {
+ repo.storeClass(interfaceA);
+ repo.storeClass(interfaceB);
+ repo.storeClass(testClass);
+ Repository.setRepository(repo);
+ assertThrows(ClassCircularityError.class, () ->
testClass.findField("nonExistentField", Type.INT));
+ } finally {
+ repo.removeClass(interfaceA);
+ repo.removeClass(interfaceB);
+ repo.removeClass(testClass);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("getClassesJavaLang")
+ @MethodSource("getClassesJavaTime")
+ @MethodSource("getClassesJavaUtil")
+ @MethodSource("getClassesJavaUtilStream")
+ void testFindFieldJavaLang(final Class<?> clazz) throws
ClassNotFoundException {
+ assertNull(findFieldDoesNotExist(clazz));
+ }
+
+ @ParameterizedTest
+ @MethodSource("getClassesJavaLang")
+ @MethodSource("getClassesJavaTime")
+ @MethodSource("getClassesJavaUtil")
+ @MethodSource("getClassesJavaUtilStream")
+ void testGetAllInterfaces(final Class<?> clazz) throws
ClassNotFoundException {
+
assertNotNull(Repository.lookupClass(clazz.getName()).getAllInterfaces());
+ }
+
+ @ParameterizedTest
+ @MethodSource("getClassesJavaLang")
+ @MethodSource("getClassesJavaTime")
+ @MethodSource("getClassesJavaUtil")
+ @MethodSource("getClassesJavaUtilStream")
+ void testGetSuperClasses(final Class<?> clazz) throws
ClassNotFoundException {
+
assertNotNull(Repository.lookupClass(clazz.getName()).getSuperClasses());
+ }
+
+}