28
28
import groovy .lang .Binding ;
29
29
import groovy .lang .GroovyClassLoader ;
30
30
import groovy .lang .GroovyShell ;
31
+ import groovy .lang .Script ;
31
32
import hudson .Extension ;
32
33
import hudson .PluginManager ;
33
34
import hudson .model .AbstractDescribableImpl ;
36
37
import hudson .util .FormValidation ;
37
38
38
39
import java .beans .Introspector ;
40
+ import java .io .IOException ;
41
+ import java .io .Closeable ;
39
42
import java .lang .ref .Reference ;
40
43
import java .lang .ref .WeakReference ;
41
44
import java .lang .reflect .Field ;
68
71
import org .jenkinsci .plugins .scriptsecurity .scripts .UnapprovedClasspathException ;
69
72
import org .jenkinsci .plugins .scriptsecurity .scripts .UnapprovedUsageException ;
70
73
import org .jenkinsci .plugins .scriptsecurity .scripts .languages .GroovyLanguage ;
74
+ import org .kohsuke .groovy .sandbox .GroovyInterceptor ;
71
75
import org .kohsuke .stapler .DataBoundConstructor ;
72
76
import org .kohsuke .stapler .QueryParameter ;
73
77
import org .kohsuke .stapler .Stapler ;
@@ -277,6 +281,17 @@ private static void cleanUpObjectStreamClassCaches(@Nonnull Class<?> clazz) thro
277
281
}
278
282
}
279
283
284
+
285
+ /**
286
+ * Prepares the Groovy script to be executed.
287
+ * @param loader see parameter explanation under {@link #evaluate(ClassLoader, Binding)}
288
+ * @param binding see parameter explanation under {@link #evaluate(ClassLoader, Binding)}
289
+ * @return the prepared script
290
+ */
291
+ public PreparedScript prepare (ClassLoader loader , Binding binding ) throws IllegalAccessException , IOException {
292
+ return new PreparedScript (loader , binding );
293
+ }
294
+
280
295
/**
281
296
* Runs the Groovy script, using the sandbox if so configured.
282
297
* @param loader a class loader for constructing the shell, such as {@link PluginManager#uberClassLoader} (will be augmented by {@link #getClasspath} if nonempty)
@@ -287,63 +302,96 @@ private static void cleanUpObjectStreamClassCaches(@Nonnull Class<?> clazz) thro
287
302
* @throws UnapprovedUsageException in case of a non-sandbox issue
288
303
* @throws UnapprovedClasspathException in case some unapproved classpath entries were requested
289
304
*/
290
- @ SuppressFBWarnings (value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" , justification = "Managed by GroovyShell." )
291
305
public Object evaluate (ClassLoader loader , Binding binding ) throws Exception {
292
- if (!calledConfiguring ) {
293
- throw new IllegalStateException ("you need to call configuring or a related method before using GroovyScript" );
294
- }
295
- URLClassLoader urlcl = null ;
296
- ClassLoader memoryProtectedLoader = null ;
297
- List <ClasspathEntry > cp = getClasspath ();
298
- if (!cp .isEmpty ()) {
299
- List <URL > urlList = new ArrayList <URL >(cp .size ());
300
-
301
- for (ClasspathEntry entry : cp ) {
302
- ScriptApproval .get ().using (entry );
303
- urlList .add (entry .getURL ());
304
- }
305
-
306
- loader = urlcl = new URLClassLoader (urlList .toArray (new URL [urlList .size ()]), loader );
306
+ try (PreparedScript scriptInstance = prepare (loader , binding )) {
307
+ return scriptInstance .run ();
307
308
}
308
- boolean canDoCleanup = false ;
309
+ }
309
310
310
- try {
311
- loader = GroovySandbox .createSecureClassLoader (loader );
311
+ /**
312
+ * Allows access to execute a script multiple times without preparation and clean-up overhead.
313
+ * While the intricate logic to do this continues to be hidden from users.
314
+ */
315
+ public final class PreparedScript implements Closeable {
316
+
317
+ private final GroovyShell sh ;
318
+ private final Script preparedScript ;
319
+ private ClassLoader memoryProtectedLoader = null ;
320
+ private GroovyInterceptor scriptSandbox = null ;
321
+ private URLClassLoader urlcl = null ;
322
+ private boolean canDoCleanup = false ;
323
+
324
+ /**
325
+ * @see @see SecureGroovyScript#evaluate()
326
+ */
327
+ @ SuppressFBWarnings (value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" , justification = "Managed by GroovyShell." )
328
+ private PreparedScript (ClassLoader loader , Binding binding ) throws IllegalAccessException , IOException {
329
+ List <ClasspathEntry > cp = getClasspath ();
330
+ if (!cp .isEmpty ()) {
331
+ List <URL > urlList = new ArrayList <URL >(cp .size ());
332
+
333
+ for (ClasspathEntry entry : cp ) {
334
+ ScriptApproval .get ().using (entry );
335
+ urlList .add (entry .getURL ());
336
+ }
312
337
313
- Field loaderF = null ;
314
- try {
315
- loaderF = GroovyShell .class .getDeclaredField ("loader" );
316
- loaderF .setAccessible (true );
317
- canDoCleanup = true ;
318
- } catch (NoSuchFieldException nsme ) {
319
- LOGGER .log (Level .FINE , "GroovyShell fields have changed, field loader no longer exists -- memory leak fixes won't work" );
338
+ loader = urlcl = new URLClassLoader (urlList .toArray (new URL [urlList .size ()]), loader );
320
339
}
321
340
322
- GroovyShell sh ;
323
- if (sandbox ) {
324
- CompilerConfiguration cc = GroovySandbox .createSecureCompilerConfiguration ();
325
- sh = new GroovyShell (loader , binding , cc );
341
+ try {
342
+ loader = GroovySandbox .createSecureClassLoader (loader );
343
+ Field loaderF = null ;
344
+ try {
345
+ loaderF = GroovyShell .class .getDeclaredField ("loader" );
346
+ loaderF .setAccessible (true );
347
+ canDoCleanup = true ;
348
+ } catch (NoSuchFieldException nsme ) {
349
+ LOGGER .log (Level .FINE , "GroovyShell fields have changed, field loader no longer exists -- memory leak fixes won't work" );
350
+ }
326
351
327
- if (canDoCleanup ) {
328
- memoryProtectedLoader = new CleanGroovyClassLoader (loader , cc );
329
- loaderF .set (sh , memoryProtectedLoader );
352
+ if (sandbox ) {
353
+ CompilerConfiguration cc = GroovySandbox .createSecureCompilerConfiguration ();
354
+ sh = new GroovyShell (loader , binding , cc );
355
+
356
+ if (canDoCleanup ) {
357
+ memoryProtectedLoader = new CleanGroovyClassLoader (loader , cc );
358
+ loaderF .set (sh , memoryProtectedLoader );
359
+ }
360
+
361
+ preparedScript = sh .parse (script );
362
+ scriptSandbox = GroovySandbox .createSandbox (preparedScript , Whitelist .all ());
363
+ } else {
364
+ sh = new GroovyShell (loader , binding );
365
+ if (canDoCleanup ) {
366
+ memoryProtectedLoader = new CleanGroovyClassLoader (loader );
367
+ loaderF .set (sh , memoryProtectedLoader );
368
+ }
369
+
370
+ preparedScript = sh .parse (ScriptApproval .get ().using (script , GroovyLanguage .get ()));
330
371
}
372
+ } catch (Exception e ) {
373
+ close ();
374
+ throw e ;
375
+ }
376
+ }
331
377
378
+ public Object run () throws Exception {
379
+ if (sandbox ) {
380
+ scriptSandbox .register ();
332
381
try {
333
- return GroovySandbox .run (sh . parse ( script ), Whitelist . all () );
382
+ return preparedScript .run ();
334
383
} catch (RejectedAccessException x ) {
335
384
throw ScriptApproval .get ().accessRejected (x , ApprovalContext .create ());
385
+ } finally {
386
+ scriptSandbox .unregister ();
336
387
}
337
388
} else {
338
- sh = new GroovyShell (loader , binding );
339
- if (canDoCleanup ) {
340
- memoryProtectedLoader = new CleanGroovyClassLoader (loader );
341
- loaderF .set (sh , memoryProtectedLoader );
342
- }
343
- return sh .evaluate (ScriptApproval .get ().using (script , GroovyLanguage .get ()));
389
+ return preparedScript .run ();
344
390
}
391
+ }
345
392
346
- } finally {
393
+ @ Override
394
+ public void close () throws IOException {
347
395
try {
348
396
if (canDoCleanup ) {
349
397
cleanUpLoader (memoryProtectedLoader , new HashSet <ClassLoader >(), new HashSet <Class <?>>());
0 commit comments