24
24
25
25
package org .jenkinsci .plugins .scriptsecurity .sandbox .whitelists ;
26
26
27
+ import com .github .benmanes .caffeine .cache .Caffeine ;
28
+ import com .github .benmanes .caffeine .cache .LoadingCache ;
29
+ import edu .umd .cs .findbugs .annotations .CheckForNull ;
30
+ import edu .umd .cs .findbugs .annotations .NonNull ;
31
+ import java .lang .reflect .AccessibleObject ;
27
32
import java .lang .reflect .Array ;
28
33
import java .lang .reflect .Constructor ;
29
34
import java .lang .reflect .Field ;
30
35
import java .lang .reflect .Method ;
31
36
import java .lang .reflect .Modifier ;
32
37
import java .util .Arrays ;
33
38
import java .util .List ;
34
- import java .util .concurrent .ConcurrentHashMap ;
35
-
39
+ import java .util .function .BiPredicate ;
40
+ import java .util .function .Predicate ;
41
+ import java .util .function .Supplier ;
36
42
import org .jenkinsci .plugins .scriptsecurity .sandbox .Whitelist ;
37
43
38
- import edu .umd .cs .findbugs .annotations .CheckForNull ;
39
- import edu .umd .cs .findbugs .annotations .NonNull ;
40
-
41
44
/**
42
45
* A whitelist based on listing signatures and searching them. Lists of signatures should not change
43
46
* from invocation to invocation.
@@ -57,127 +60,55 @@ public abstract class EnumeratingWhitelist extends Whitelist {
57
60
58
61
protected abstract List <FieldSignature > staticFieldSignatures ();
59
62
60
- ConcurrentHashMap <String , Boolean > permittedCache = new ConcurrentHashMap <>(); // Not private to facilitate testing
61
-
62
- @ SafeVarargs
63
- private final void cacheSignatureList (List <Signature > ...sigs ) {
64
- for (List <Signature > list : sigs ) {
65
- for (Signature s : list ) {
66
- if (!s .isWildcard ()) { // Cache entries for wildcard signatures will never be accessed and just waste space
67
- permittedCache .put (s .toString (), Boolean .TRUE );
63
+ private final Predicate <Method > methodCache = cache (this ::methodSignatures , MethodSignature ::matches );
64
+ private final Predicate <Constructor <?>> constructorCache = cache (this ::newSignatures , NewSignature ::matches );
65
+ private final Predicate <Method > staticMethodCache = cache (this ::staticMethodSignatures , MethodSignature ::matches );
66
+ private final Predicate <Field > fieldCache = cache (this ::fieldSignatures , FieldSignature ::matches );
67
+ private final Predicate <Field > staticFieldCache = cache (this ::staticFieldSignatures , FieldSignature ::matches );
68
+
69
+ private static <M extends AccessibleObject , S extends Signature > Predicate <M > cache (Supplier <List <S >> signatures , BiPredicate <S , M > matches ) {
70
+ // Unfortunately xxxSignatures() will return empty if called from superclass constructor,
71
+ // since subclass constructors initialize lists, thus the need for Supplier.
72
+ // Would be cleaner for EnumeratingWhitelist to take all signatures in its constructor,
73
+ // and for StaticWhitelist to just be a utility with static constructor methods rather than a subclass.
74
+ LoadingCache <M , Boolean > cache = Caffeine .newBuilder ().weakKeys ().build ((M m ) -> {
75
+ for (S s : signatures .get ()) {
76
+ if (matches .test (s , m )) {
77
+ return true ;
68
78
}
69
79
}
70
- }
71
- }
72
-
73
- /** Prepopulates the "permitted" cache, resetting if populated already. Should be called when method signatures change or after initialization. */
74
- final void precache () {
75
- if (!permittedCache .isEmpty ()) {
76
- this .permittedCache .clear (); // No sense calling clearCache
77
- }
78
- cacheSignatureList ((List )methodSignatures (), (List )(newSignatures ()),
79
- (List )(staticMethodSignatures ()), (List )(fieldSignatures ()),
80
- (List )(staticFieldSignatures ()));
81
- }
82
-
83
- /** Frees up nearly all memory used for the cache. MUST BE CALLED if you change the result of the xxSignatures() methods. */
84
- final void clearCache () {
85
- this .permittedCache .clear ();
86
- this .permittedCache = new ConcurrentHashMap <>();
80
+ return false ;
81
+ });
82
+ return m -> {
83
+ if (signatures .get ().isEmpty ()) {
84
+ return false ; // shortcut
85
+ }
86
+ return cache .get (m );
87
+ };
87
88
}
88
89
89
90
@ Override public final boolean permitsMethod (@ NonNull Method method , @ NonNull Object receiver , @ NonNull Object [] args ) {
90
- String key = canonicalMethodSig (method );
91
- Boolean b = permittedCache .get (key );
92
- if (b != null ) {
93
- return b ;
94
- }
95
-
96
- boolean output = false ;
97
- for (MethodSignature s : methodSignatures ()) {
98
- if (s .matches (method )) {
99
- output = true ;
100
- break ;
101
- }
102
- }
103
- permittedCache .put (key , output );
104
- return output ;
91
+ return methodCache .test (method );
105
92
}
106
93
107
94
@ Override public final boolean permitsConstructor (@ NonNull Constructor <?> constructor , @ NonNull Object [] args ) {
108
- String key = canonicalConstructorSig (constructor );
109
- Boolean b = permittedCache .get (key );
110
- if (b != null ) {
111
- return b ;
112
- }
113
-
114
- boolean output = false ;
115
- for (NewSignature s : newSignatures ()) {
116
- if (s .matches (constructor )) {
117
- output = true ;
118
- break ;
119
- }
120
- }
121
- permittedCache .put (key , output );
122
- return output ;
95
+ return constructorCache .test (constructor );
123
96
}
124
97
125
98
@ Override public final boolean permitsStaticMethod (@ NonNull Method method , @ NonNull Object [] args ) {
126
- String key = canonicalStaticMethodSig (method );
127
- Boolean b = permittedCache .get (key );
128
- if (b != null ) {
129
- return b ;
130
- }
131
-
132
- boolean output = false ;
133
- for (MethodSignature s : staticMethodSignatures ()) {
134
- if (s .matches (method )) {
135
- output = true ;
136
- break ;
137
- }
138
- }
139
- permittedCache .put (key , output );
140
- return output ;
99
+ return staticMethodCache .test (method );
141
100
}
142
101
143
102
@ Override public final boolean permitsFieldGet (@ NonNull Field field , @ NonNull Object receiver ) {
144
- String key = canonicalFieldSig (field );
145
- Boolean b = permittedCache .get (key );
146
- if (b != null ) {
147
- return b ;
148
- }
149
-
150
- boolean output = false ;
151
- for (FieldSignature s : fieldSignatures ()) {
152
- if (s .matches (field )) {
153
- output = true ;
154
- break ;
155
- }
156
- }
157
- permittedCache .put (key , output );
158
- return output ;
103
+ return fieldCache .test (field );
159
104
}
160
105
161
106
@ Override public final boolean permitsFieldSet (@ NonNull Field field , @ NonNull Object receiver , Object value ) {
162
107
return permitsFieldGet (field , receiver );
163
108
}
164
109
165
110
@ Override public final boolean permitsStaticFieldGet (@ NonNull Field field ) {
166
- String key = canonicalStaticFieldSig (field );
167
- Boolean b = permittedCache .get (key );
168
- if (b != null ) {
169
- return b ;
170
- }
171
-
172
- boolean output = false ;
173
- for (FieldSignature s : staticFieldSignatures ()) {
174
- if (s .matches (field )) {
175
- output = true ;
176
- break ;
177
- }
178
- }
179
- permittedCache .put (key , output );
180
- return output ;
111
+ return staticFieldCache .test (field );
181
112
}
182
113
183
114
@ Override public final boolean permitsStaticFieldSet (@ NonNull Field field , Object value ) {
@@ -276,6 +207,8 @@ static String[] argumentTypes(Class<?>[] argumentTypes) {
276
207
return s ;
277
208
}
278
209
210
+ // TODO move all these to StaticWhitelist
211
+
279
212
/** Canonical name for a field access. */
280
213
static String canonicalFieldString (@ NonNull Field field ) {
281
214
return getName (field .getDeclaringClass ()) + ' ' + field .getName ();
@@ -385,7 +318,7 @@ public NewSignature(String type, String[] argumentTypes) {
385
318
public NewSignature (Class <?> type , Class <?>... argumentTypes ) {
386
319
this (getName (type ), argumentTypes (argumentTypes ));
387
320
}
388
- boolean matches (Constructor c ) {
321
+ boolean matches (Constructor <?> c ) {
389
322
return getName (c .getDeclaringClass ()).equals (type ) && Arrays .equals (argumentTypes (c .getParameterTypes ()), argumentTypes );
390
323
}
391
324
@ Override String signaturePart () {
0 commit comments