4545import java .lang .reflect .Method ;
4646import java .lang .reflect .Modifier ;
4747import java .net .URI ;
48- import java .net .URISyntaxException ;
4948import java .net .URL ;
50- import java .nio .file .Paths ;
51- import java .security .CodeSource ;
5249import java .util .ArrayList ;
5350import java .util .Collection ;
5451import java .util .Collections ;
5552import java .util .List ;
5653import java .util .Set ;
5754import java .util .TreeSet ;
58- import java .util .logging .Level ;
5955import java .util .logging .Logger ;
6056import edu .umd .cs .findbugs .annotations .CheckForNull ;
6157import edu .umd .cs .findbugs .annotations .NonNull ;
58+ import edu .umd .cs .findbugs .annotations .Nullable ;
6259import groovy .lang .MissingPropertyException ;
60+ import java .util .regex .Matcher ;
61+ import java .util .regex .Pattern ;
6362import javax .inject .Inject ;
6463import jenkins .model .Jenkins ;
6564import jenkins .scm .impl .SingleSCMSource ;
@@ -251,20 +250,20 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
251250 private final @ NonNull String prefix ;
252251 /** {@link Class#getName} minus package prefix */
253252 private final @ CheckForNull String clazz ;
254- /** {@code jar:file: /…/libs/$hash.jar!/} */
255- private final @ NonNull String srcUrl ;
253+ /** {@code /…/libs/$hash.jar}, or null if resuming a pre-dir2Jar build */
254+ private final @ Nullable String jar ;
256255
257256 LoadedClasses (String library , String libraryDirectoryName , boolean trusted , Boolean changelog , Run <?,?> run ) {
258- this (library , trusted , changelog , "" , null , /* cf. LibraryAdder.retrieve */ "jar:" + new File (run .getRootDir (), "libs/" + libraryDirectoryName + ".jar" ).toURI () + "!/" );
257+ this (library , trusted , changelog , "" , null , /* cf. LibraryAdder.retrieve */ new File (run .getRootDir (), "libs/" + libraryDirectoryName + ".jar" ).getAbsolutePath () );
259258 }
260259
261- LoadedClasses (String library , boolean trusted , Boolean changelog , String prefix , String clazz , String srcUrl ) {
260+ LoadedClasses (String library , boolean trusted , Boolean changelog , String prefix , String clazz , String jar ) {
262261 this .library = library ;
263262 this .trusted = trusted ;
264263 this .changelog = changelog ;
265264 this .prefix = prefix ;
266265 this .clazz = clazz ;
267- this .srcUrl = srcUrl ;
266+ this .jar = jar ;
268267 }
269268
270269 @ Override public Object getProperty (String property ) {
@@ -288,12 +287,12 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
288287 String fullClazz = clazz != null ? clazz + '$' + property : property ;
289288 loadClass (prefix + fullClazz );
290289 // OK, class really exists, stash it and await methods
291- return new LoadedClasses (library , trusted , changelog , prefix , fullClazz , srcUrl );
290+ return new LoadedClasses (library , trusted , changelog , prefix , fullClazz , jar );
292291 } else if (clazz != null ) {
293292 throw new MissingPropertyException (property , loadClass (prefix + clazz ));
294293 } else {
295294 // Still selecting package components.
296- return new LoadedClasses (library , trusted , changelog , prefix + property + '.' , null , srcUrl );
295+ return new LoadedClasses (library , trusted , changelog , prefix + property + '.' , null , jar );
297296 }
298297 }
299298
@@ -339,6 +338,8 @@ private static boolean isSandboxed() {
339338
340339 // TODO putProperty for static field set
341340
341+ private static final Pattern JAR_URL = Pattern .compile ("jar:(file:/.+[.]jar)!/.+" );
342+
342343 private Class <?> loadClass (String name ) {
343344 CpsFlowExecution exec = CpsThread .current ().getExecution ();
344345 GroovyClassLoader loader = (trusted ? exec .getTrustedShell () : exec .getShell ()).getClassLoader ();
@@ -351,14 +352,22 @@ private Class<?> loadClass(String name) {
351352 if (definingLoader != loader ) {
352353 throw new IllegalAccessException ("cannot access " + c + " via library handle: " + definingLoader + " is not " + loader );
353354 }
354- // Note that this goes through GroovyCodeSource.<init>(File, String), which unlike (say) URLClassLoader set the “location” to the actual file, *not* the root.
355- CodeSource codeSource = c .getProtectionDomain ().getCodeSource ();
356- if (codeSource == null ) {
357- throw new IllegalAccessException (name + " had no defined code source" );
358- }
359- String loc = codeSource .getLocation ().toString ();
360- if (!loc .startsWith (srcUrl )) {
361- throw new IllegalAccessException (name + " was defined in " + loc + " which was not inside " + srcUrl );
355+ if (jar != null ) {
356+ URL res = loader .getResource (name .replaceFirst ("[$][^.]+$" , "" ).replace ('.' , '/' ) + ".groovy" );
357+ if (res == null ) {
358+ throw new IllegalAccessException ("Unknown where " + name + " (" + c .getProtectionDomain ().getCodeSource ().getLocation () + ") was loaded from" );
359+ }
360+ Matcher m = JAR_URL .matcher (res .toString ());
361+ if (!m .matches ()) {
362+ throw new IllegalAccessException ("Unexpected URL " + res );
363+ }
364+ File actual = new File (URI .create (m .group (1 )));
365+ if (!actual .equals (new File (jar ))) {
366+ throw new IllegalAccessException (name + " was defined in " + actual + " rather than the expected " + jar );
367+ }
368+ LOGGER .fine (() -> "loaded " + name + " from " + res + " ~ " + actual + " as expected" );
369+ } else {
370+ LOGGER .fine (() -> "loaded " + name + " but resuming from an old build which did not properly record JAR location" );
362371 }
363372 if (!Modifier .isPublic (c .getModifiers ())) { // unlikely since Groovy makes classes implicitly public
364373 throw new IllegalAccessException (c + " is not public" );
0 commit comments