001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.classloader;
014
015import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_DOUBLE;
016import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_FIELDREF;
017import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_FLOAT;
018import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_INT;
019import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_INTERFACE_METHODREF;
020import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_LONG;
021import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_MEMBERNAME_AND_DESCRIPTOR;
022import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_METHODREF;
023import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_STRING;
024import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_TYPEREF;
025import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_UNUSED;
026import static org.jikesrvm.classloader.ClassLoaderConstants.TAG_UTF;
027
028import java.io.ByteArrayInputStream;
029import java.io.DataInputStream;
030import java.io.IOException;
031import java.io.InputStream;
032
033import org.jikesrvm.Properties;
034import org.jikesrvm.VM;
035
036/**
037 * Manufacture type descriptions as needed by the running virtual machine.
038 */
039public class RVMClassLoader {
040
041  private static final boolean DBG_APP_CL = false;
042
043  private static ClassLoader appCL;
044
045  /**
046   * Set list of places to be searched for application classes and resources.
047   * Do NOT set the java.class.path property; it is probably too early in
048   * the VM's booting cycle to set properties.
049   *
050   * @param classpath path specification in standard "classpath" format
051   */
052  public static void stashApplicationRepositories(String classpath) {
053    if (DBG_APP_CL) {
054      VM.sysWriteln("RVMClassLoader.stashApplicationRepositories: " + "applicationRepositories = ", classpath);
055    }
056    /* If mis-initialized, trash it. */
057    if (appCL != null && !classpath.equals(applicationRepositories)) {
058      appCL = null;
059      if (DBG_APP_CL) {
060        VM.sysWriteln("RVMClassLoader.stashApplicationRepositories: Wiping out my remembered appCL.");
061      }
062    }
063    applicationRepositories = classpath;
064  }
065
066  /**
067   * Are Java 1.4 style assertions enabled?
068   */
069  private static boolean assertionsEnabled = false;
070  /**
071   * String describing packages and classes to enable assertions on (of the form ":<packagename>...|:<classname>")
072   */
073  private static String[] enabledAssertionStrings;
074  /**
075   * String describing packages and classes to disable assertions on (of the form ":<packagename>...|:<classname>")
076   */
077  private static String[] disabledAssertionStrings;
078
079  /**
080   * Remember the given enable assertions string
081   * @param arg String of the form ":<packagename>...|:<classname>"
082   */
083  public static void stashEnableAssertionArg(String arg) {
084    assertionsEnabled = true;
085    enabledAssertionStrings = arg.split(":");
086    if (enabledAssertionStrings != null) {
087      if ((enabledAssertionStrings.length == 0) ||
088          (enabledAssertionStrings.length == 1 && enabledAssertionStrings[0].equals(""))) {
089        // force enabled assertion strings to null when no arguments are passed with -ea
090        enabledAssertionStrings = null;
091      }
092    }
093  }
094
095  /**
096   * Remember the given disable assertions string
097   * @param arg String of the form ":<packagename>...|:<classname>"
098   */
099  public static void stashDisableAssertionArg(String arg) {
100    if (arg == null || arg.equals("")) {
101      assertionsEnabled = false;
102    } else {
103      disabledAssertionStrings = arg.split(":");
104    }
105  }
106
107  /**
108   * Calculate the desired assertion status for a freshly loaded class
109   * @param klass to check against command line argument
110   * @return whether assertions should be enabled on class
111   */
112  static boolean getDesiredAssertionStatus(RVMClass klass) {
113    if (!assertionsEnabled) {
114      // trivial - no assertions are enabled
115      return false;
116    } else {
117      if (enabledAssertionStrings == null && disabledAssertionStrings == null) {
118        // assertions enabled unconditionally
119        return true;
120      } else {
121        // search command line arguments to see if assertions are enabled
122        boolean result = false;
123        if (enabledAssertionStrings != null) {
124          for (String s : enabledAssertionStrings) {
125            if (s.equals(klass.getTypeRef().getName().classNameFromDescriptor()) ||
126                klass.getPackageName().startsWith(s)) {
127              result = true;
128              break;
129            }
130          }
131        }
132        if (disabledAssertionStrings != null) {
133          for (String s : disabledAssertionStrings) {
134            if (s.equals(klass.getTypeRef().getName().classNameFromDescriptor()) ||
135                klass.getPackageName().startsWith(s)) {
136              result = false;
137              break;
138            }
139          }
140        }
141        return result;
142      }
143    }
144  }
145
146  /**
147   * Set list of places to be searched for application classes and resources.<p>
148   *
149   * Our Jikes RVM classloader can not handle having the class path reset
150   * after it's been set up.   Unfortunately, it is stashed by classes in
151   * GNU Classpath.
152   *
153   * @param classpath path specification in standard "classpath" format
154   */
155  public static void setApplicationRepositories(String classpath) {
156    System.setProperty("java.class.path", classpath);
157    stashApplicationRepositories(classpath);
158    if (DBG_APP_CL) {
159      VM.sysWriteln("RVMClassLoader.setApplicationRepositories: applicationRepositories = ", applicationRepositories);
160    }
161  }
162
163  /**
164   * Get list of places currently being searched for application
165   * classes and resources.
166   * @return names of directories, .zip files, and .jar files
167   */
168  public static String getApplicationRepositories() {
169    return applicationRepositories;
170  }
171
172  /** Are we getting the application CL?  Access is synchronized via the
173   *  Class object.  Probably not necessary, but doesn't hurt, or shouldn't.
174   *  Used for sanity checks. */
175  private static int gettingAppCL = 0;
176
177  /** Is the application class loader ready for use?  Don't leak it out until
178   * it is! */
179  private static boolean appCLReady;
180
181  public static void declareApplicationClassLoaderIsReady() {
182    appCLReady = true;
183  }
184
185  public static ClassLoader getApplicationClassLoader() {
186    if (!VM.runningVM) {
187      return null;
188    }              /* trick the boot image writer with null,
189                                   which it will use when initializing
190                                   java.lang.ClassLoader$StaticData */
191
192    /* Lie, until we are really ready for someone to actually try
193     * to use this class loader to load classes and resources.
194     */
195    if (!appCLReady) {
196      return BootstrapClassLoader.getBootstrapClassLoader();
197    }
198
199    if (appCL != null) {
200      return appCL;
201    }
202
203    // Sanity Checks:
204    //    synchronized (this) {
205    if (gettingAppCL > 0 || DBG_APP_CL) {
206      VM.sysWriteln("JikesRVM: RVMClassLoader.getApplicationClassLoader(): ",
207                    gettingAppCL > 0 ? "Recursively " : "",
208                    "invoked with ",
209                    gettingAppCL,
210                    " previous instances pending");
211    }
212    if (gettingAppCL > 0) {
213      VM.sysFail(
214          "JikesRVM: While we are setting up the application class loader, some class required that selfsame application class loader.  This is a chicken-and-egg problem; see a Jikes RVM Guru.");
215    }
216    ++gettingAppCL;
217
218    String r = getApplicationRepositories();
219
220    if (Properties.verboseBoot >= 1 || DBG_APP_CL) {
221      VM.sysWriteln("RVMClassLoader.getApplicationClassLoader(): " +
222                    "Initializing Application ClassLoader, with" +
223                    " repositories: `", r, "'...");
224    }
225
226    appCL = new ApplicationClassLoader(r);
227
228    if (Properties.verboseBoot >= 1 || DBG_APP_CL) {
229      VM.sysWriteln("RVMClassLoader.getApplicationClassLoader(): ...initialized Application classloader, to ",
230                    appCL.toString());
231    }
232    --gettingAppCL;
233    //    }
234    return appCL;
235  }
236
237  //----------------//
238  // implementation //
239  //----------------//
240
241  //
242  private static String applicationRepositories;
243
244  // Names of special methods.
245  //
246  /** {@code <clinit>} */
247  public static final Atom StandardClassInitializerMethodName = Atom.findOrCreateAsciiAtom("<clinit>");
248  /** "()V" */
249  public static final Atom StandardClassInitializerMethodDescriptor = Atom.findOrCreateAsciiAtom("()V");
250
251  /** {@code <init>} */
252  public static final Atom StandardObjectInitializerMethodName = Atom.findOrCreateAsciiAtom("<init>");
253  /** {@code ()V} */
254  public static final Atom StandardObjectInitializerMethodDescriptor = Atom.findOrCreateAsciiAtom("()V");
255  /** {@code this} */
256  public static final Atom StandardObjectInitializerHelperMethodName = Atom.findOrCreateAsciiAtom("this");
257
258  /** {@code finalize} */
259  public static final Atom StandardObjectFinalizerMethodName = Atom.findOrCreateAsciiAtom("finalize");
260  /** {@code ()V} */
261  public static final Atom StandardObjectFinalizerMethodDescriptor = Atom.findOrCreateAsciiAtom("()V");
262
263  // Names of .class file attributes.
264  //
265  /** {@code Code} */
266  static final Atom codeAttributeName = Atom.findOrCreateAsciiAtom("Code");
267  /** {@code ConstantValue} */
268  static final Atom constantValueAttributeName = Atom.findOrCreateAsciiAtom("ConstantValue");
269  /** {@code LineNumberTable} */
270  static final Atom lineNumberTableAttributeName = Atom.findOrCreateAsciiAtom("LineNumberTable");
271  /** {@code Exceptions} */
272  static final Atom exceptionsAttributeName = Atom.findOrCreateAsciiAtom("Exceptions");
273  /** {@code SourceFile} */
274  static final Atom sourceFileAttributeName = Atom.findOrCreateAsciiAtom("SourceFile");
275  /** {@code LocalVariableTable} */
276  static final Atom localVariableTableAttributeName = Atom.findOrCreateAsciiAtom("LocalVariableTable");
277  /** {@code Deprecated}  */
278  static final Atom deprecatedAttributeName = Atom.findOrCreateAsciiAtom("Deprecated");
279  /** {@code InnerClasses} */
280  static final Atom innerClassesAttributeName = Atom.findOrCreateAsciiAtom("InnerClasses");
281  /** {@code Synthetic} */
282  static final Atom syntheticAttributeName = Atom.findOrCreateAsciiAtom("Synthetic");
283  /** {@code EnclosingMethod} */
284  static final Atom enclosingMethodAttributeName = Atom.findOrCreateAsciiAtom("EnclosingMethod");
285  /** {@code Signature} */
286  static final Atom signatureAttributeName = Atom.findOrCreateAsciiAtom("Signature");
287  /** {@code RuntimeVisibleAnnotations} */
288  static final Atom runtimeVisibleAnnotationsAttributeName =
289      Atom.findOrCreateAsciiAtom("RuntimeVisibleAnnotations");
290  /** {@code RuntimeInvisibleAnnotations} */
291  static final Atom runtimeInvisibleAnnotationsAttributeName =
292      Atom.findOrCreateAsciiAtom("RuntimeInvisibleAnnotations");
293  /** {@code RuntimeVisibleParameterAnnotations} */
294  static final Atom runtimeVisibleParameterAnnotationsAttributeName =
295      Atom.findOrCreateAsciiAtom("RuntimeVisibleParameterAnnotations");
296  /** {@code RuntimeInvisibleParameterAnnotations} */
297  static final Atom runtimeInvisibleParameterAnnotationsAttributeName =
298      Atom.findOrCreateAsciiAtom("RuntimeInvisibleParameterAnnotations");
299  /** {@code AnnotationDefault} */
300  static final Atom annotationDefaultAttributeName = Atom.findOrCreateAsciiAtom("AnnotationDefault");
301
302  /** Initialize at boot time.
303   */
304  public static void boot() {
305    appCL = null;
306  }
307
308  /**
309   * Initialize for boot image writing.
310   *
311   * @param bootstrapClasspath the bootstrap classpath to load the classes from
312   */
313  public static void init(String bootstrapClasspath) {
314    // specify where the VM's core classes and resources live
315    //
316    applicationRepositories = "."; // Carried over.
317    BootstrapClassLoader.boot(bootstrapClasspath);
318  }
319
320  public static RVMType defineClassInternal(String className, byte[] classRep, int offset, int length,
321                                            ClassLoader classloader) throws ClassFormatError {
322    return defineClassInternal(className, new ByteArrayInputStream(classRep, offset, length), classloader);
323  }
324
325  public static RVMType defineClassInternal(String className, InputStream is, ClassLoader classloader)
326      throws ClassFormatError {
327    TypeReference tRef;
328    if (className == null) {
329      // NUTS: Our caller hasn't bothered to tell us what this class is supposed
330      //       to be called, so we must read the input stream and discover it ourselves
331      //       before we actually can create the RVMClass instance.
332      try {
333        is.mark(is.available());
334        tRef = getClassTypeRef(new DataInputStream(is), classloader);
335        is.reset();
336      } catch (IOException e) {
337        ClassFormatError cfe = new ClassFormatError(e.getMessage());
338        cfe.initCause(e);
339        throw cfe;
340      }
341    } else {
342      Atom classDescriptor = Atom.findOrCreateAsciiAtom(className).descriptorFromClassName();
343      tRef = TypeReference.findOrCreate(classloader, classDescriptor);
344    }
345
346    try {
347      if (VM.VerifyAssertions) VM._assert(tRef.isClassType());
348      if (VM.TraceClassLoading && VM.runningVM) {
349        VM.sysWriteln("loading \"" + tRef.getName() + "\" with " + classloader);
350      }
351      RVMClass ans = ClassFileReader.readClass(tRef, new DataInputStream(is));
352      tRef.setType(ans);
353      return ans;
354    } catch (IOException e) {
355      ClassFormatError cfe = new ClassFormatError(e.getMessage());
356      cfe.initCause(e);
357      throw cfe;
358    }
359  }
360
361  // Shamelessly cloned & owned from ClassFileReader.readClass constructor....
362  private static TypeReference getClassTypeRef(DataInputStream input, ClassLoader cl)
363      throws IOException, ClassFormatError {
364    int magic = input.readInt();
365    if (magic != 0xCAFEBABE) {
366      throw new ClassFormatError("bad magic number " + Integer.toHexString(magic));
367    }
368
369    // Drop class file version number on floor. readClass will do the check later.
370    input.readUnsignedShort(); // minor ID
371    input.readUnsignedShort(); // major ID
372
373    //
374    // pass 1: read constant pool
375    //
376    int[] constantPool = new int[input.readUnsignedShort()];
377    byte[] tmpTags = new byte[constantPool.length];
378
379    // note: slot 0 is unused
380    for (int i = 1; i < constantPool.length; i++) {
381      tmpTags[i] = input.readByte();
382      switch (tmpTags[i]) {
383        case TAG_UTF: {
384          byte[] utf = new byte[input.readUnsignedShort()];
385          input.readFully(utf);
386          constantPool[i] = Atom.findOrCreateUtf8Atom(utf).getId();
387          break;
388        }
389
390        case TAG_UNUSED:
391          break;
392
393        case TAG_INT:
394        case TAG_FLOAT:
395        case TAG_FIELDREF:
396        case TAG_METHODREF:
397        case TAG_INTERFACE_METHODREF:
398        case TAG_MEMBERNAME_AND_DESCRIPTOR:
399          input.readInt(); // drop on floor
400          break;
401
402        case TAG_LONG:
403        case TAG_DOUBLE:
404          i++;
405          input.readLong(); // drop on floor
406          break;
407
408        case TAG_TYPEREF:
409          constantPool[i] = input.readUnsignedShort();
410          break;
411
412        case TAG_STRING:
413          input.readUnsignedShort(); // drop on floor
414          break;
415
416        default:
417          throw new ClassFormatError("bad constant pool entry: " + tmpTags[i]);
418      }
419    }
420
421    //
422    // pass 2: post-process type constant pool entries
423    // (we must do this in a second pass because of forward references)
424    //
425    for (int i = 1; i < constantPool.length; i++) {
426      switch (tmpTags[i]) {
427        case TAG_LONG:
428        case TAG_DOUBLE:
429          ++i;
430          break;
431
432        case TAG_TYPEREF: { // in: utf index
433          Atom typeName = Atom.getAtom(constantPool[constantPool[i]]);
434          constantPool[i] = TypeReference.findOrCreate(cl, typeName.descriptorFromClassName()).getId();
435          break;
436        } // out: type reference id
437      }
438    }
439
440    // drop modifiers on floor.
441    input.readUnsignedShort();
442
443    int myTypeIndex = input.readUnsignedShort();
444    return TypeReference.getTypeRef(constantPool[myTypeIndex]);
445  }
446
447
448  /**
449   * An unpleasant hack to deal with the problem of replaying work
450   * when using dynamic classloaders (whose identity will vary from
451   * run to run).  When we can't find the classloader that was specified,
452   * see if there are any other (non-matching) classloaders that
453   * have the relevant class loaded.
454   *
455   * @param clazz The class we're after
456   * @return A usable classloader or null
457   */
458  public static ClassLoader findWorkableClassloader(Atom clazz) {
459    for (ClassLoader clx: TypeReference.getCLDict()) {
460      TypeReference tRef = TypeReference.findOrCreate(clx, clazz);
461      RVMClass cls = (RVMClass) tRef.peekType();
462
463      if (cls != null)
464        return clx;
465      else {
466        try {
467          cls = tRef.resolve().asClass();
468          cls.resolve();
469          cls.instantiate();
470          cls.initialize();
471          return clx;
472        } catch (NoClassDefFoundError cnf) {
473          /* silently catch this exception and try another class loader */
474        }
475      }
476    }
477    return null;
478  }
479}