// Copyright 2022 Code Intelligence GmbH // // Licensed 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 // // http://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 com.code_intelligence.jazzer.autofuzz; import io.github.classgraph.ClassInfo; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Comparator; import java.util.stream.Stream; class AccessibleObjectLookup { private static final Comparator> STABLE_CLASS_COMPARATOR = Comparator.comparing(Class::getName); private static final Comparator STABLE_EXECUTABLE_COMPARATOR = Comparator.comparing(Executable::getName).thenComparing(executable -> { if (executable instanceof Method) { return org.objectweb.asm.Type.getMethodDescriptor((Method) executable); } else { return org.objectweb.asm.Type.getConstructorDescriptor((Constructor) executable); } }); private final Class referenceClass; public AccessibleObjectLookup(Class referenceClass) { this.referenceClass = referenceClass; } Class[] getAccessibleClasses(Class type) { return Stream.concat(Arrays.stream(type.getDeclaredClasses()), Arrays.stream(type.getClasses())) .distinct() .filter(this::isAccessible) .sorted(STABLE_CLASS_COMPARATOR) .toArray(Class[] ::new); } Constructor[] getAccessibleConstructors(Class type) { // Neither of getDeclaredConstructors and getConstructors is a superset of the other: While // getDeclaredConstructors returns constructors with all visibility modifiers, it does not // return the implicit default constructor. return Stream .concat( Arrays.stream(type.getDeclaredConstructors()), Arrays.stream(type.getConstructors())) .distinct() .filter(this::isAccessible) .sorted(STABLE_EXECUTABLE_COMPARATOR) .filter(constructor -> { try { constructor.setAccessible(true); return true; } catch (Exception e) { // Can't make the constructor accessible, e.g. because it is in a standard library // module. We can't do anything about this, so we skip the constructor. return false; } }) .toArray(Constructor[] ::new); } Method[] getAccessibleMethods(Class type) { return Stream.concat(Arrays.stream(type.getDeclaredMethods()), Arrays.stream(type.getMethods())) .distinct() .filter(this::isAccessible) .sorted(STABLE_EXECUTABLE_COMPARATOR) .filter(method -> { try { method.setAccessible(true); return true; } catch (Exception e) { // Can't make the method accessible, e.g. because it is in a standard library module. We // can't do anything about this, so we skip the method. return false; } }) .toArray(Method[] ::new); } boolean isAccessible(Class clazz, int modifiers) { if (Modifier.isPublic(modifiers)) { return true; } if (referenceClass == null) { return false; } if (Modifier.isPrivate(modifiers)) { return clazz.equals(referenceClass); } if (Modifier.isProtected(modifiers)) { return clazz.isAssignableFrom(referenceClass); } // No visibility modifiers implies default visibility, which means visible in the same package. return clazz.getPackage().equals(referenceClass.getPackage()); } boolean isAccessible(ClassInfo clazz, int modifiers) { if (Modifier.isPublic(modifiers)) { return true; } if (referenceClass == null) { return false; } if (Modifier.isPrivate(modifiers)) { return clazz.getName().equals(referenceClass.getName()); } if (Modifier.isProtected(modifiers)) { return isAssignableFrom(clazz, referenceClass); } // No visibility modifiers implies default visibility, which means visible in the same package. return clazz.getPackageName().equals(referenceClass.getPackage().getName()); } boolean isAssignableFrom(ClassInfo clazz, Class potentialSubclass) { if (potentialSubclass.getName().equals(clazz.getName())) { return true; } if (potentialSubclass.equals(Object.class)) { return clazz.getName().equals(Object.class.getName()); } if (potentialSubclass.getSuperclass() == null) { return false; } return isAssignableFrom(clazz, potentialSubclass.getSuperclass()); } private boolean isAccessible(Executable executable) { return isAccessible(executable.getDeclaringClass(), executable.getModifiers()); } private boolean isAccessible(Class clazz) { return isAccessible(clazz, clazz.getModifiers()); } }