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