1 // Copyright 2022 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.code_intelligence.jazzer.autofuzz; 16 17 import io.github.classgraph.ClassInfo; 18 import java.lang.reflect.Constructor; 19 import java.lang.reflect.Executable; 20 import java.lang.reflect.Method; 21 import java.lang.reflect.Modifier; 22 import java.util.Arrays; 23 import java.util.Comparator; 24 import java.util.stream.Stream; 25 26 class AccessibleObjectLookup { 27 private static final Comparator<Class<?>> STABLE_CLASS_COMPARATOR = 28 Comparator.comparing(Class::getName); 29 private static final Comparator<Executable> STABLE_EXECUTABLE_COMPARATOR = 30 Comparator.comparing(Executable::getName).thenComparing(executable -> { 31 if (executable instanceof Method) { 32 return org.objectweb.asm.Type.getMethodDescriptor((Method) executable); 33 } else { 34 return org.objectweb.asm.Type.getConstructorDescriptor((Constructor<?>) executable); 35 } 36 }); 37 38 private final Class<?> referenceClass; 39 AccessibleObjectLookup(Class<?> referenceClass)40 public AccessibleObjectLookup(Class<?> referenceClass) { 41 this.referenceClass = referenceClass; 42 } 43 getAccessibleClasses(Class<?> type)44 Class<?>[] getAccessibleClasses(Class<?> type) { 45 return Stream.concat(Arrays.stream(type.getDeclaredClasses()), Arrays.stream(type.getClasses())) 46 .distinct() 47 .filter(this::isAccessible) 48 .sorted(STABLE_CLASS_COMPARATOR) 49 .toArray(Class<?>[] ::new); 50 } 51 getAccessibleConstructors(Class<?> type)52 Constructor<?>[] getAccessibleConstructors(Class<?> type) { 53 // Neither of getDeclaredConstructors and getConstructors is a superset of the other: While 54 // getDeclaredConstructors returns constructors with all visibility modifiers, it does not 55 // return the implicit default constructor. 56 return Stream 57 .concat( 58 Arrays.stream(type.getDeclaredConstructors()), Arrays.stream(type.getConstructors())) 59 .distinct() 60 .filter(this::isAccessible) 61 .sorted(STABLE_EXECUTABLE_COMPARATOR) 62 .filter(constructor -> { 63 try { 64 constructor.setAccessible(true); 65 return true; 66 } catch (Exception e) { 67 // Can't make the constructor accessible, e.g. because it is in a standard library 68 // module. We can't do anything about this, so we skip the constructor. 69 return false; 70 } 71 }) 72 .toArray(Constructor<?>[] ::new); 73 } 74 75 Method[] getAccessibleMethods(Class<?> type) { 76 return Stream.concat(Arrays.stream(type.getDeclaredMethods()), Arrays.stream(type.getMethods())) 77 .distinct() 78 .filter(this::isAccessible) 79 .sorted(STABLE_EXECUTABLE_COMPARATOR) 80 .filter(method -> { 81 try { 82 method.setAccessible(true); 83 return true; 84 } catch (Exception e) { 85 // Can't make the method accessible, e.g. because it is in a standard library module. We 86 // can't do anything about this, so we skip the method. 87 return false; 88 } 89 }) 90 .toArray(Method[] ::new); 91 } 92 93 boolean isAccessible(Class<?> clazz, int modifiers) { 94 if (Modifier.isPublic(modifiers)) { 95 return true; 96 } 97 if (referenceClass == null) { 98 return false; 99 } 100 if (Modifier.isPrivate(modifiers)) { 101 return clazz.equals(referenceClass); 102 } 103 if (Modifier.isProtected(modifiers)) { 104 return clazz.isAssignableFrom(referenceClass); 105 } 106 // No visibility modifiers implies default visibility, which means visible in the same package. 107 return clazz.getPackage().equals(referenceClass.getPackage()); 108 } 109 110 boolean isAccessible(ClassInfo clazz, int modifiers) { 111 if (Modifier.isPublic(modifiers)) { 112 return true; 113 } 114 if (referenceClass == null) { 115 return false; 116 } 117 if (Modifier.isPrivate(modifiers)) { 118 return clazz.getName().equals(referenceClass.getName()); 119 } 120 if (Modifier.isProtected(modifiers)) { 121 return isAssignableFrom(clazz, referenceClass); 122 } 123 // No visibility modifiers implies default visibility, which means visible in the same package. 124 return clazz.getPackageName().equals(referenceClass.getPackage().getName()); 125 } 126 127 boolean isAssignableFrom(ClassInfo clazz, Class<?> potentialSubclass) { 128 if (potentialSubclass.getName().equals(clazz.getName())) { 129 return true; 130 } 131 if (potentialSubclass.equals(Object.class)) { 132 return clazz.getName().equals(Object.class.getName()); 133 } 134 if (potentialSubclass.getSuperclass() == null) { 135 return false; 136 } 137 return isAssignableFrom(clazz, potentialSubclass.getSuperclass()); 138 } 139 140 private boolean isAccessible(Executable executable) { 141 return isAccessible(executable.getDeclaringClass(), executable.getModifiers()); 142 } 143 144 private boolean isAccessible(Class<?> clazz) { 145 return isAccessible(clazz, clazz.getModifiers()); 146 } 147 } 148