view src/main/gov/nasa/jpf/vm/ClassInfo.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children b822e7665585
line wrap: on
line source

/*
 * Copyright (C) 2014, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All rights reserved.
 *
 * The Java Pathfinder core (jpf-core) platform is licensed under the
 * Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 *        http://www.apache.org/licenses/LICENSE-2.0. 
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package gov.nasa.jpf.vm;

import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.JPFConfigException;
import gov.nasa.jpf.JPFListener;
import gov.nasa.jpf.util.ImmutableList;
import gov.nasa.jpf.util.JPFLogger;
import gov.nasa.jpf.util.LocationSpec;
import gov.nasa.jpf.util.MethodSpec;
import gov.nasa.jpf.util.Misc;
import gov.nasa.jpf.util.OATHash;
import gov.nasa.jpf.util.Source;

import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;


/**
 * Describes the VM's view of a java class.  Contains descriptions of the
 * static and dynamic fields, declaredMethods, and information relevant to the
 * class.
 * 
 * Note that ClassInfos / classes have three different construction/initialization steps:
 * (1) construction : recursively via ClassLoaderInfo.getResolvedClassInfo -> ClassFileContainer.createClassInfo
 *     -> ClassInfo ctor -> resolveClass
 *     this only creates the ClassInfo object, but it is not visible/usable from SUT code yet and hence not
 *     observable from classLoaded() listeners
 * (2) registration : create StaticElementInfo and add it to the respective ClassLoaderInfo statics, then create
 *     the java.lang.Class companion object in the SUT
 *     this makes the ClassInfo usable from SUT code
 * (3) initialization : execute clinit (if the class has one)
 * 
 * Note that id/uniqueId are NOT set before registration takes place, and registration is not automatically performed since
 * listeners/peers might create ClassInfos internally (e.g. for inspection), which should not be visible from the SUT or observable
 * by other listeners.
 * 
 * Automatic registration from the ClassInfo ctors would require to pass a ThreadInfo context throughout the whole ClassLoaderInfo/
 * ClassFileContainer/ClassInfo chain and could lead to false positives for sharedness based POR, which would record this
 * thread as referencing even if this is a listener/peer internal request
 */
public class ClassInfo extends InfoObject implements Iterable<MethodInfo>, GenericSignatureHolder {

  //--- ClassInfo states, in chronological order
  // note the somewhat strange, decreasing values - >= 0 (=thread-id) means 
  // we are in clinit
  // ideally, we would have a separate RESOLVED state, but (a) this is somewhat
  // orthogonal to REGISTERED, and - more importantly - (b) we need the
  // superClass instance when initializing our Fields (instance field offsets).
  // Doing the field initialization during resolveReferencedClass() seems awkward and
  // error prone (there is not much you can do with an unresolved class then)
  
  // not registered or clinit'ed (but cached in loadedClasses)
  public static final int UNINITIALIZED = -1;
  // 'REGISTERED' simply means 'sei' is set (we have a StaticElementInfo)
  // 'INITIALIZING' is any number >=0, which is the thread objRef that executes the clinit
  public static final int INITIALIZED = -2;

  protected static final String ID_FIELD = "nativeId"; 

  protected static JPFLogger logger = JPF.getLogger("class");

  protected static int nClassInfos; // for statistics
  
  protected static Config config;

  /**
   * ClassLoader that loaded this class.
   */
  protected static final ClassLoader thisClassLoader = ClassInfo.class.getClassLoader();  
  
  /**
   * our abstract factory to createAndInitialize object and class fields
   */
  protected static FieldsFactory fieldsFactory;

  
  protected static final FieldInfo[] EMPTY_FIELDINFO_ARRAY = new FieldInfo[0];
  protected static final String[] EMPTY_STRING_ARRAY = new String[0];
  protected static final String UNINITIALIZED_STRING = "UNINITIALIZED"; 
  protected static final Map<String,MethodInfo> NO_METHODS = Collections.emptyMap();
  protected static final Set<ClassInfo> NO_INTERFACES = new HashSet<ClassInfo>();
  
  /**
   * support to auto-load listeners from annotations
   */
  protected static HashSet<String> autoloadAnnotations;
  protected static HashSet<String> autoloaded;

  /**
   * Name of the class. e.g. "java.lang.String"
   * NOTE - this is the expanded name for builtin types, e.g. "int", but NOT
   * for arrays, which are for some reason in Ldot notation, e.g. "[Ljava.lang.String;" or "[I"
   */
  protected String name;
  
  /** type erased signature of the class. e.g. "Ljava/lang/String;" */
  protected String signature;

  /** Generic type signatures of the class as per para. 4.4.4 of the revised VM spec */
  protected String genericSignature;

  /** The classloader that defined (directly loaded) this class */
  protected ClassLoaderInfo classLoader;
  
  // various class attributes
  protected boolean      isClass = true;
  protected boolean      isWeakReference = false;
  protected boolean      isObjectClassInfo = false;
  protected boolean      isStringClassInfo = false;
  protected boolean      isThreadClassInfo = false;
  protected boolean      isRefClassInfo = false;
  protected boolean      isArray = false;
  protected boolean      isEnum = false;
  protected boolean      isReferenceArray = false;
  protected boolean      isAbstract = false;
  protected boolean      isBuiltin = false;

  // that's ultimately where we keep the attributes
  // <2do> this is currently quite redundant, but these are used in reflection
  protected int modifiers;

  protected MethodInfo   finalizer = null;

  /** type based object attributes (for GC, partial order reduction and
   * property checks)
   */
  protected int elementInfoAttrs = 0;

  /**
   * all our declared declaredMethods (we don't flatten, this is not
   * a high-performance VM)
   */
  protected Map<String, MethodInfo> methods;

  /**
   * our instance fields.
   * Note these are NOT flattened, idx.e. only contain the declared ones
   */
  protected FieldInfo[] iFields;

  /** the storage size of instances of this class (stored as an int[]) */
  protected int instanceDataSize;

  /** where in the instance data array (int[]) do our declared fields start */
  protected int instanceDataOffset;

  /** total number of instance fields (flattened, not only declared ones) */
  protected int nInstanceFields;

  /**
   * our static fields. Again, not flattened
   */
  protected FieldInfo[] sFields;

  /** the storage size of static fields of this class (stored as an int[]) */
  protected int staticDataSize;

  /**
   * we only set the superClassName upon creation, it is instantiated into
   * a ClassInfo by resolveReferencedClass(), which is required to be called before
   * we can createAndInitialize objects of this type
   */
  protected ClassInfo  superClass;
  protected String superClassName;

  protected String enclosingClassName;
  protected String enclosingMethodName;

  protected String[] innerClassNames = EMPTY_STRING_ARRAY;
  protected BootstrapMethodInfo[] bootstrapMethods;
    
  /** direct ifcs implemented by this class */
  protected String[] interfaceNames;

  protected Set<ClassInfo> interfaces = new HashSet<ClassInfo>();
  
  /** cache of all interfaceNames (parent interfaceNames and interface parents) - lazy eval */
  protected Set<ClassInfo> allInterfaces;
  
  /** Name of the package. */
  protected String packageName;

  /** this is only set if the classfile has a SourceFile class attribute */
  protected String sourceFileName;

  /** 
   * Uniform resource locater for the class file. NOTE: since for builtin classes
   * there is no class file assigned is set to the typeName 
   */ 
  protected String classFileUrl;

  /** from where the corresponding classfile was loaded (if this is not a builtin) */
  protected gov.nasa.jpf.vm.ClassFileContainer container;

  
  /**
   *  a search global numeric id that is only unique within this ClassLoader namespace. Ids are
   *  computed by the ClassLoaderInfo/Statics implementation during ClassInfo registration
   */
  protected int  id = -1;

  /**
   * A search global unique id associate with this class, which is comprised of the classLoader id
   * and the (loader-specific) ClassInfo id. This is just a quick way to do search global checks for equality
   * 
   * NOTE - since this is based on the classloader-specific id, it can't be used before the ClassInfo is registered
   */
  protected long uniqueId = -1;

  /**
   * this is the object we use to enter declaredMethods in the underlying VM
   * (it replaces Reflection)
   */
  protected NativePeer nativePeer;

  /** Source file associated with the class.*/
  protected Source source;

  protected boolean enableAssertions;

  /** actions to be taken when an object of this type is gc'ed */
  protected ImmutableList<ReleaseAction> releaseActions; 
          
  
  static boolean init (Config config) {

    ClassInfo.config = config;
    
    setSourceRoots(config);
    //buildBCELModelClassPath(config);

    fieldsFactory = config.getEssentialInstance("vm.fields_factory.class",
                                                FieldsFactory.class);

    autoloadAnnotations = config.getNonEmptyStringSet("listener.autoload");
    if (autoloadAnnotations != null) {
      autoloaded = new HashSet<String>();

      if (logger.isLoggable(Level.INFO)) {
        for (String s : autoloadAnnotations){
          logger.info("watching for autoload annotation @" + s);
        }
      }
    }

    return true;
  }

  public static boolean isObjectClassInfo (ClassInfo ci){
    return ci.isObjectClassInfo();
  }

  public static boolean isStringClassInfo (ClassInfo ci){
    return ci.isStringClassInfo();
  }

  
   //--- initialization interface towards parsers (which might reside in other packages)
    
  protected void setClass(String clsName, String superClsName, int flags, int cpCount) throws ClassParseException {
    String parsedName = Types.getClassNameFromTypeName(clsName);

    if (name != null && !name.equals(parsedName)){
      throw new ClassParseException("wrong class name (expected: " + name + ", found: " + parsedName + ')');
    }
    name = parsedName;
    
    // the enclosingClassName is set on demand since it requires loading enclosing class candidates
    // to verify their innerClass attributes

    int i = name.lastIndexOf('.');
    packageName = (i > 0) ? name.substring(0, i) : "";

    modifiers = flags;
    
    // annotations are interfaces too (not exposed by Modifier)
    isClass = ((flags & Modifier.INTERFACE) == 0);

    superClassName = superClsName;
  }

  public void setInnerClassNames(String[] clsNames) {
    innerClassNames = clsNames;
  }

  public void setEnclosingClass (String clsName) {
    enclosingClassName = clsName;
  }
  
  public void setEnclosingMethod (String mthName){
    enclosingMethodName = mthName;    
  }

  public void setInterfaceNames(String[] ifcNames) {
    interfaceNames = ifcNames;
  }
  
  public void setSourceFile (String fileName){
    // prepend if we already know the package
    if (packageName.length() > 0) {
      // Source will take care of proper separator chars later
      sourceFileName = packageName.replace('.', '/') + '/' + fileName;
    } else {
      sourceFileName = fileName;
    }
  }

  public void setFields(FieldInfo[] fields) {
    if (fields == null){
      iFields = EMPTY_FIELDINFO_ARRAY;
      sFields = EMPTY_FIELDINFO_ARRAY;
      
    } else { // there are fields, we have to tell them apart
      int nInstance = 0, nStatic = 0;
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].isStatic()) {
          nStatic++;
        } else {
          nInstance++;
        }
      }

      FieldInfo[] instanceFields = (nInstance > 0) ? new FieldInfo[nInstance] : EMPTY_FIELDINFO_ARRAY;
      FieldInfo[] staticFields = (nStatic > 0) ? new FieldInfo[nStatic] : EMPTY_FIELDINFO_ARRAY;

      int iInstance = 0;
      int iStatic = 0;
      for (int i = 0; i < fields.length; i++) {
        FieldInfo fi = fields[i];

        if (fi.isStatic()) {
          staticFields[iStatic++] = fi;
        } else {
          instanceFields[iInstance++] = fi;
        }
      }

      iFields = instanceFields;
      sFields = staticFields;

      // we can't link the fields yet because we need the superclasses to be resolved
    }
  }

  public void setMethods (MethodInfo[] methods) {
    if (methods != null && methods.length > 0) {
      HashMap<String, MethodInfo> map = new LinkedHashMap<String, MethodInfo>();

      for (int i = 0; i < methods.length; i++) {
        MethodInfo mi = methods[i];
        mi.linkToClass(this);
        map.put(mi.getUniqueName(), mi);
      }
      
      this.methods = map;
    }
  }

  public AnnotationInfo getResolvedAnnotationInfo (String typeName){
    return classLoader.getResolvedAnnotationInfo( typeName);
  }
  
  @Override
  public void setAnnotations(AnnotationInfo[] annotations) {
    this.annotations = annotations;
  }
  
  //--- end initialization interface
 
  //--- the overridden annotation accessors (we need these because of inherited superclass annotations)
  // note that we don't flatten annotations anymore, assuming the prevalent query will be getAnnotation(name)
  
  @Override
  public boolean hasAnnotations(){
    if (annotations.length > 0){
      return true;
    }
    
    for (ClassInfo ci = superClass; ci != null; ci = ci.superClass){
      AnnotationInfo[] a = ci.annotations;
      for (int j=0; j<a.length; j++){
        if (a[j].isInherited()){
          return true;
        }
      }
    }
    
    return false;
  }
  
  /**
   * return all annotations, which includes the ones inherited from our superclasses
   * NOTE - this is not very efficient
   */
  @Override
  public AnnotationInfo[] getAnnotations() {
    int nAnnotations = annotations.length;
    for (ClassInfo ci = superClass; ci != null; ci = ci.superClass){
      AnnotationInfo[] a = ci.annotations;
      for (int i=0; i<a.length; i++){
        if (a[i].isInherited()){
          nAnnotations++;
        }
      }
    }
    
    AnnotationInfo[] allAnnotations = new AnnotationInfo[nAnnotations];
    System.arraycopy(annotations, 0, allAnnotations, 0, annotations.length);
    int idx=annotations.length;
    for (ClassInfo ci = superClass; ci != null; ci = ci.superClass){
      AnnotationInfo[] a = ci.annotations;
      for (int i=0; i<a.length; i++){
        if (a[i].isInherited()){
          allAnnotations[idx++] = a[i];
        }
      }
    }
    
    return allAnnotations;
  }
    
  @Override
  public AnnotationInfo getAnnotation (String annotationName){
    AnnotationInfo[] a = annotations;
    for (int i=0; i<a.length; i++){
      if (a[i].getName().equals(annotationName)){
        return a[i];
      }
    }
    
    for (ClassInfo ci = superClass; ci != null; ci = ci.superClass){
      a = ci.annotations;
      for (int i=0; i<a.length; i++){
        AnnotationInfo ai = a[i];
        if (ai.getName().equals(annotationName) && ai.isInherited()){
          return ai;
        }
      }
    }
    
    return null;
  }
  
  protected ClassInfo (String name, ClassLoaderInfo cli, String classFileUrl){
    nClassInfos++;
    
    this.name = name;
    this.classLoader = cli;
    this.classFileUrl = classFileUrl;
    
    this.methods = NO_METHODS;  // yet

    // rest has to be initialized by concrete ctor, which should call resolveAndLink(parser)
  }
  
  /**
   * the initialization part that has to happen once we have super, fields, methods and annotations
   * NOTE - this has to be called by concrete ctors after parsing class files
   */
  protected void resolveAndLink () throws ClassParseException {
    
    //--- these might get streamlined
    isStringClassInfo = isStringClassInfo0();
    isThreadClassInfo = isThreadClassInfo0();
    isObjectClassInfo = isObjectClassInfo0();
    isRefClassInfo = isRefClassInfo0();
   // isWeakReference = isWeakReference0();
    isAbstract = (modifiers & Modifier.ABSTRACT) != 0;
   // isEnum = isEnum0();
    
    finalizer = getFinalizer0();

    resolveClass(); // takes care of super classes and interfaces

    // Used to enter native methods (in the host VM).
    // This needs to be initialized AFTER we get our  MethodInfos, since it does a reverse lookup to determine which
    // ones are handled by the peer (by means of setting MethodInfo attributes)
    nativePeer = loadNativePeer();
    checkUnresolvedNativeMethods();

    linkFields(); // computes field offsets
    
    setAssertionStatus();
    processJPFConfigAnnotation();
    loadAnnotationListeners();    
  }
  
  protected ClassInfo(){
    nClassInfos++;
    
    // for explicit subclass initialization
  }
  
  /**
   * ClassInfo ctor used for builtin types (arrays and primitive types)
   * idx.e. classes we don't have class files for
   */
  protected ClassInfo (String builtinClassName, ClassLoaderInfo classLoader) {
    nClassInfos++;

    this.classLoader = classLoader;

    isArray = (builtinClassName.charAt(0) == '[');
    isReferenceArray = isArray && (builtinClassName.endsWith(";") || builtinClassName.charAt(1) == '[');
    isBuiltin = true;

    name = builtinClassName;

    logger.log(Level.FINE, "generating builtin class: %1$s", name);

    packageName = ""; // builtin classes don't reside in java.lang !
    sourceFileName = null;
    source = null;
    genericSignature = "";

    // no fields
    iFields = EMPTY_FIELDINFO_ARRAY;
    sFields = EMPTY_FIELDINFO_ARRAY;

    if (isArray) {
      if(classLoader.isSystemClassLoader()) {
        superClass = ((SystemClassLoaderInfo)classLoader).getObjectClassInfo();
      } else {
        superClass = ClassLoaderInfo.getCurrentSystemClassLoader().getObjectClassInfo();
      }
      interfaceNames = loadArrayInterfaces();
      methods = loadArrayMethods();
    } else {
      superClass = null; // strange, but true, a 'no object' class
      interfaceNames = loadBuiltinInterfaces(name);
      methods = loadBuiltinMethods(name);
    }

    enableAssertions = true; // doesn't really matter - no code associated

    classFileUrl = name;
    
    // no fields or declaredMethods, so we don't have to link/resolve anything
  }
  
  public static int getNumberOfLoadedClasses(){
    return nClassInfos;
  }
  
  //--- the VM type specific methods
  // <2do> those should be abstract
  
  protected void setAnnotationValueGetterCode (MethodInfo pmi, FieldInfo fi){
    // to be overridden by VM specific class
  }
  
  protected void setDirectCallCode (MethodInfo miCallee, MethodInfo miStub){
    // to be overridden by VM specific class
  }
  
  protected void setLambdaDirectCallCode (MethodInfo miDirectCall, BootstrapMethodInfo bootstrapMethod){
    // to be overridden by VM specific class
  }
  
  protected void setNativeCallCode (NativeMethodInfo miNative){
    // to be overridden by VM specific class
  }
  
  protected void setRunStartCode (MethodInfo miStub, MethodInfo miRun){
    // to be overridden by VM specific class
  }
  
  /**
   * createAndInitialize a fully synthetic implementation of an Annotation proxy
   */
  protected ClassInfo (ClassInfo annotationCls, String name, ClassLoaderInfo classLoader, String url) {
    this.classLoader = classLoader;
    
    this.name = name;
    isClass = true;

    //superClass = objectClassInfo;
    superClass = ClassLoaderInfo.getSystemResolvedClassInfo("gov.nasa.jpf.AnnotationProxyBase");

    interfaceNames = new String[]{ annotationCls.name };    
    packageName = annotationCls.packageName;
    sourceFileName = annotationCls.sourceFileName;
    genericSignature = annotationCls.genericSignature;

    sFields = new FieldInfo[0]; // none
    staticDataSize = 0;

    methods = new HashMap<String, MethodInfo>();
    iFields = new FieldInfo[annotationCls.methods.size()];
    nInstanceFields = iFields.length;

    // all accessor declaredMethods of ours make it into iField/method combinations
    int idx = 0;
    int off = 0;  // no super class
    for (MethodInfo mi : annotationCls.getDeclaredMethodInfos()) {
      String mname = mi.getName();
      String mtype = mi.getReturnType();
      String genericSignature = mi.getGenericSignature();

      // create and initialize an instance field for it
      FieldInfo fi = FieldInfo.create(mname, mtype, 0);
      fi.linkToClass(this, idx, off);
      fi.setGenericSignature(genericSignature);
      iFields[idx++] = fi;
      off += fi.getStorageSize();

      MethodInfo pmi = new MethodInfo(this, mname, mi.getSignature(), Modifier.PUBLIC, 1, 2);
      pmi.setGenericSignature(genericSignature);
      
      setAnnotationValueGetterCode( pmi, fi);
      methods.put(pmi.getUniqueName(), pmi);
    }

    instanceDataSize = computeInstanceDataSize();
    instanceDataOffset = 0;

    classFileUrl = url;
    linkFields();
  }
  
  
  //used to create synthetic classes that implement functional interfaces
  protected ClassInfo createFuncObjClassInfo (BootstrapMethodInfo bootstrapMethod, String name, String samUniqueName, String[] fieldTypesName) {
   return null;
 }
 
 protected ClassInfo (ClassInfo funcInterface, BootstrapMethodInfo bootstrapMethod, String name, String[] fieldTypesName) {
   ClassInfo enclosingClass = bootstrapMethod.enclosingClass;
   this.classLoader = enclosingClass.classLoader;

   this.name = name;
   isClass = true;

   superClassName = "java.lang.Object";

   interfaceNames = new String[]{ funcInterface.name };    
   packageName = enclosingClass.getPackageName();

   // creating fields used to capture free variables
   int n = fieldTypesName.length;
   
   iFields = new FieldInfo[n];
   nInstanceFields = n;
   
   sFields = new FieldInfo[0];
   staticDataSize = 0;
   
   int idx = 0;
   int off = 0;  // no super class
   
   int i = 0;
   for(String type: fieldTypesName) {
     FieldInfo fi = FieldInfo.create("arg" + i++, type, 0);
     fi.linkToClass(this, idx, off);
     iFields[idx++] = fi;
     off += fi.getStorageSize();
   }
   
   linkFields();
 }
  
  // since id and hence uniqueId are not set before this class is registered, we can't use them
  
  @Override
  public int hashCode() {
    return OATHash.hash(name.hashCode(), classLoader.hashCode());
  }
  
  @Override
  public boolean equals (Object o) {
    if (o instanceof ClassInfo) {
      ClassInfo other = (ClassInfo)o;
      if (classLoader == other.classLoader) {
        // beware of ClassInfos that are not registered yet - in this case we have to equals names
        if (name.equals(other.name)) {
          return true;
        }
      }
    }
    
    return false;
  }

  protected String computeSourceFileName(){
    return name.replace('.', '/') + ".java";
  }

  protected void checkUnresolvedNativeMethods(){
    for (MethodInfo mi : methods.values()){
      if (mi.isUnresolvedNativeMethod()){
        NativeMethodInfo nmi = new NativeMethodInfo(mi, null, nativePeer);
        nmi.replace(mi);
      }
    }
  }

  protected void processJPFConfigAnnotation() {
    AnnotationInfo ai = getAnnotation("gov.nasa.jpf.annotation.JPFConfig");
    if (ai != null) {
      for (String s : ai.getValueAsStringArray()) {
        config.parse(s);
      }
    }
  }

  protected void loadAnnotationListeners () {
    if (autoloadAnnotations != null) {
      autoloadListeners(annotations); // class annotations

      for (int i=0; i<sFields.length; i++) {
        autoloadListeners(sFields[i].getAnnotations());
      }

      for (int i=0; i<iFields.length; i++) {
        autoloadListeners(iFields[i].getAnnotations());
      }

      // method annotations are checked during method loading
      // (to avoid extra iteration)
    }
  }

  void autoloadListeners(AnnotationInfo[] annos) {
    if ((annos != null) && (autoloadAnnotations != null)) {
      for (AnnotationInfo ai : annos) {
        String aName = ai.getName();
        if (autoloadAnnotations.contains(aName)) {
          if (!autoloaded.contains(aName)) {
            autoloaded.add(aName);
            String key = "listener." + aName;
            String defClsName = aName + "Checker";
            try {
              JPFListener listener = config.getInstance(key, JPFListener.class, defClsName);
              
              JPF jpf = VM.getVM().getJPF(); // <2do> that's a BAD access path
              jpf.addUniqueTypeListener(listener);

              if (logger.isLoggable(Level.INFO)){
                logger.info("autoload annotation listener: @", aName, " => ", listener.getClass().getName());
              }

            } catch (JPFConfigException cx) {
              logger.warning("no autoload listener class for annotation " + aName +
                             " : " + cx.getMessage());
              autoloadAnnotations.remove(aName);
            }
          }
        }
      }

      if (autoloadAnnotations.isEmpty()) {
        autoloadAnnotations = null;
      }
    }
  }

  protected NativePeer loadNativePeer(){
    return NativePeer.getNativePeer(this);
  }
  
  /**
   * Returns the class loader that 
   */
  public ClassLoaderInfo getClassLoaderInfo() {
    return classLoader;
  }

  /**
   * the container this is stored in
   */
  public Statics getStatics() {
    return classLoader.getStatics();
  }
  
  /**
   * required by InfoObject interface
   */
  public ClassInfo getClassInfo() {
    return this;
  }

  protected void setAssertionStatus() {
    if(isInitialized()) {
      return;
    } else {
      enableAssertions = classLoader.desiredAssertionStatus(name);
    }
  }

  boolean getAssertionStatus () {
    return enableAssertions;
  }

  public boolean desiredAssertionStatus() {
    return classLoader.desiredAssertionStatus(name);
  }

  @Override
  public String getGenericSignature() {
    return genericSignature;
  }

  @Override
  public void setGenericSignature(String sig){
    genericSignature = sig;
  }
  
  public boolean isArray () {
    return isArray;
  }

  public boolean isEnum () {
    return isEnum;
  }

  public boolean isAbstract() {
    return isAbstract;
  }

  public boolean isBuiltin(){
    return isBuiltin;
  }
  
  public boolean isInterface() {
    return ((modifiers & Modifier.INTERFACE) != 0);
  }

  public boolean isReferenceArray () {
    return isReferenceArray;
  }

  public boolean isObjectClassInfo() {
    return isObjectClassInfo;
  }

  public boolean isStringClassInfo() {
    return isStringClassInfo;
  }

  public boolean isThreadClassInfo() {
    return isThreadClassInfo;
  }

  protected void checkNoClinitInitialization(){
    if (!isInitialized()){
      ThreadInfo ti = ThreadInfo.getCurrentThread();
      registerClass(ti);
      setInitialized(); // we might want to check if there is a clinit
    }
  }
  
  protected ClassInfo createAnnotationProxy (String proxyName){
    // to be overridden by VM specific ClassInfos
    return null;
  }
  
  public ClassInfo getAnnotationProxy (){
    // <2do> test if this is a annotation ClassInfo
    
    checkNoClinitInitialization(); // annotation classes don't have clinits
    
    ClassInfo ciProxy = classLoader.getResolvedAnnotationProxy(this);
    ciProxy.checkNoClinitInitialization();
    
    return ciProxy;
  }
  
/**
  public static ClassInfo getAnnotationProxy (ClassInfo ciAnnotation){
    ThreadInfo ti = ThreadInfo.getCurrentThread();

    // make sure the annotationCls is initialized (no code there)
    if (!ciAnnotation.isInitialized()) {
      ciAnnotation.registerClass(ti);
      ciAnnotation.setInitialized(); // no clinit
    }

    String url = computeProxyUrl(ciAnnotation);
    ClassInfo ci = null; // getOriginalClassInfo(url);

    if (ci == null){
      String cname = ciAnnotation.getName() + "$Proxy";
      ci = new ClassInfo(ciAnnotation, cname, ciAnnotation.classLoader, url);
      ciAnnotation.classLoader.addResolvedClass(ci);
      if (!ci.isInitialized()){
        ci.registerClass(ti);
        ci.setInitialized();
      }
    }

    return ci;
  }
**/

  public boolean areAssertionsEnabled() {
    return enableAssertions;
  }

  public boolean hasInstanceFields () {
    return (instanceDataSize > 0);
  }

  public ElementInfo getClassObject(){
    StaticElementInfo sei = getStaticElementInfo();
    
    if (sei != null){
      int objref = sei.getClassObjectRef();
      return VM.getVM().getElementInfo(objref);
    }

    return null;
  }
  
  public ElementInfo getModifiableClassObject(){
    StaticElementInfo sei = getStaticElementInfo();
    
    if (sei != null){
      int objref = sei.getClassObjectRef();
      return VM.getVM().getModifiableElementInfo(objref);
    }

    return null;
  }
  

  public int getClassObjectRef () {
    StaticElementInfo sei = getStaticElementInfo();    
    return (sei != null) ? sei.getClassObjectRef() : MJIEnv.NULL;
  }

  public gov.nasa.jpf.vm.ClassFileContainer getContainer(){
    return container;
  }
  
  public String getClassFileUrl (){
    return classFileUrl;
  }

  //--- type based object release actions
  
  public boolean hasReleaseAction (ReleaseAction action){
    return (releaseActions != null) && releaseActions.contains(action);
  }
  
  /**
   * NOTE - this can only be set *before* subclasses are loaded (e.g. from classLoaded() notification) 
   */
  public void addReleaseAction (ReleaseAction action){
    // flattened in ctor to super releaseActions
    releaseActions = new ImmutableList<ReleaseAction>( action, releaseActions);
  }
  
  /**
   * recursively process release actions registered for this type or any of
   * its super types (only classes). The releaseAction list is flattened during
   * ClassInfo initialization, to reduce runtime overhead during GC sweep
   */
  public void processReleaseActions (ElementInfo ei){
    if (superClass != null){
      superClass.processReleaseActions(ei);
    }
    
    if (releaseActions != null) {
      for (ReleaseAction action : releaseActions) {
        action.release(ei);
      }
    }
  }
  
  public int getModifiers() {
    return modifiers;
  }

  /**
   * Note that 'uniqueName' is the name plus the argument type part of the
   * signature, idx.e. everything that's relevant for overloading
   * (besides saving some const space, we also ease reverse lookup
   * of natives that way).
   * Note also that we don't have to make any difference between
   * class and instance declaredMethods, because that just matters in the
   * INVOKExx instruction, when looking up the relevant ClassInfo to start
   * searching in (either by means of the object type, or by means of the
   * constpool classname entry).
   */
  public MethodInfo getMethod (String uniqueName, boolean isRecursiveLookup) {
    MethodInfo mi = methods.get(uniqueName);

    if ((mi == null) && isRecursiveLookup && (superClass != null)) {
      mi = superClass.getMethod(uniqueName, true);
    }

    return mi;
  }

  /**
   * if we don't know the return type
   * signature is in paren/dot notation
   */
  public MethodInfo getMethod (String name, String signature, boolean isRecursiveLookup) {
    MethodInfo mi = null;
    String matchName = name + signature;

    for (Map.Entry<String, MethodInfo>e : methods.entrySet()) {
      if (e.getKey().startsWith(matchName)){
        mi = e.getValue();
        break;
      }
    }

    if ((mi == null) && isRecursiveLookup && (superClass != null)) {
      mi = superClass.getMethod(name, signature, true);
    }

    return mi;
  }

  
  public MethodInfo getDefaultMethod (String uniqueName) {
    MethodInfo mi = null;
    
    for (ClassInfo ci = this; ci != null; ci = ci.superClass){
      for (ClassInfo ciIfc : ci.interfaces){
        MethodInfo miIfc = ciIfc.getMethod(uniqueName, true);
        if (miIfc != null && !miIfc.isAbstract()){
          if (mi != null){
            // this has to throw a IncompatibleClassChangeError in the client since Java prohibits ambiguous default methods
            String msg = "Conflicting default methods: " + mi.getFullName() + ", " + miIfc.getFullName();
            throw new ClassChangeException(msg);
          } else {
            mi = miIfc;
          }
        }
      }
    }
    
    return mi;
  }
  
  public MethodInfo getInterfaceAbstractMethod (String uniqueName) {
    MethodInfo mi = this.getMethod(uniqueName, true);
    
    if(mi != null) {
      return mi;
    }
    
    for (ClassInfo ci = this; ci != null && mi == null; ci = ci.superClass){
      for (ClassInfo ciIfc : ci.interfaces){
        mi = ciIfc.getMethod(uniqueName, true);
        if (mi != null && mi.isAbstract()){
          return mi;
        }
      }
    }
    
    return null;
  }

  /**
   * method lookup for use by reflection methods (java.lang.Class.getXMethod)
   * 
   * note this doesn't specify the return type, which means covariant return 
   * types are not allowed in reflection lookup.
   * 
   * note also this includes interface methods, but only after the inheritance
   * hierarchy has been searched
   */
  public MethodInfo getReflectionMethod (String fullName, boolean isRecursiveLookup) {
        
    // first look for methods within the class hierarchy
    for (ClassInfo ci = this; ci != null; ci = ci.superClass){
      for (Map.Entry<String, MethodInfo>e : ci.methods.entrySet()) {
        String name = e.getKey();
        if (name.startsWith(fullName)) {
          return e.getValue();
        }
      }
      if (!isRecursiveLookup){
        return null;
      }
    }

    // this is the recursive case - if none found, look for interface methods
    for (ClassInfo ci : getAllInterfaces() ){
      for (Map.Entry<String, MethodInfo>e : ci.methods.entrySet()) {
        String name = e.getKey();
        if (name.startsWith(fullName)) {
          return e.getValue();
        }
      }      
    }    

    return null;
  }
  
  /**
   * iterate over all declaredMethods of this class (and it's superclasses), until
   * the provided MethodLocator tells us it's done
   */
  public void matchMethods (MethodLocator loc) {
    for (MethodInfo mi : methods.values()) {
      if (loc.match(mi)) {
        return;
      }
    }
    if (superClass != null) {
      superClass.matchMethods(loc);
    }
  }

  /**
   * iterate over all declaredMethods declared in this class, until the provided
   * MethodLocator tells us it's done
   */
  public void matchDeclaredMethods (MethodLocator loc) {
    for (MethodInfo mi : methods.values()) {
      if (loc.match(mi)) {
        return;
      }
    }
  }

  @Override
  public Iterator<MethodInfo> iterator() {
    return new Iterator<MethodInfo>() {
      ClassInfo ci = ClassInfo.this;
      Iterator<MethodInfo> it = ci.methods.values().iterator();

      @Override
	public boolean hasNext() {
        if (it.hasNext()) {
          return true;
        } else {
          if (ci.superClass != null) {
            ci = ci.superClass;
            it = ci.methods.values().iterator();
            return it.hasNext();
          } else {
            return false;
          }
        }
      }

      @Override
	public MethodInfo next() {
        if (hasNext()) {
          return it.next();
        } else {
          throw new NoSuchElementException();
        }
      }

      @Override
	public void remove() {
        // not supported
        throw new UnsupportedOperationException("can't remove methods");
      }
    };
  }
  
  public Iterator<MethodInfo> declaredMethodIterator() {
    return methods.values().iterator();
  }

  /**
   * Search up the class hierarchy to find a static field
   * @param fName name of field
   * @return null if field name not found (not declared)
   */
  public FieldInfo getStaticField (String fName) {
    FieldInfo fi;
    ClassInfo c = this;

    while (c != null) {
      fi = c.getDeclaredStaticField(fName);
      if (fi != null) {
        return fi;
      }
      c = c.superClass;
    }

    //interfaceNames can have static fields too
    // <2do> why would that not be already resolved here ?
    for (ClassInfo ci : getAllInterfaces()) {
      fi = ci.getDeclaredStaticField(fName);
      if (fi != null) {
        return fi;
      }
    }

    return null;
  }

  public Object getStaticFieldValueObject (String id){
    ClassInfo c = this;
    Object v;

    while (c != null){
      ElementInfo sei = c.getStaticElementInfo();
      v = sei.getFieldValueObject(id);
      if (v != null){
        return v;
      }
      c = c.getSuperClass();
    }

    return null;
  }

  public FieldInfo[] getDeclaredStaticFields() {
    return sFields;
  }

  public FieldInfo[] getDeclaredInstanceFields() {
    return iFields;
  }

  /**
   * FieldInfo lookup in the static fields that are declared in this class
   * <2do> pcm - should employ a map at some point, but it's usually not that
   * important since we can cash the returned FieldInfo in the PUT/GET_STATIC insns
   */
  public FieldInfo getDeclaredStaticField (String fName) {
    for (int i=0; i<sFields.length; i++) {
      if (sFields[i].getName().equals(fName)) return sFields[i];
    }

    return null;
  }

  /**
   * base relative FieldInfo lookup - the workhorse
   * <2do> again, should eventually use Maps
   * @param fName the field name
   */
  public FieldInfo getInstanceField (String fName) {
    FieldInfo fi;
    ClassInfo c = this;

    while (c != null) {
      fi = c.getDeclaredInstanceField(fName);
      if (fi != null) return fi;
      c = c.superClass;
    }

    return null;
  }

  /**
   * FieldInfo lookup in the fields that are declared in this class
   */
  public FieldInfo getDeclaredInstanceField (String fName) {
    for (int i=0; i<iFields.length; i++) {
      if (iFields[i].getName().equals(fName)) return iFields[i];
    }

    return null;
  }
  
  public String getSignature() {
    if (signature == null) {
      signature = Types.getTypeSignature(name, false);
    }
    
    return signature;     
  }

  /**
   * Returns the name of the class.  e.g. "java.lang.String".  similar to
   * java.lang.Class.getName().
   */
  public String getName () {
    return name;
  }

  public String getSimpleName () {
    int i;
    String enclosingClassName = getEnclosingClassName();
    
    if(enclosingClassName!=null){
      i = enclosingClassName.length();      
    } else{
      i = name.lastIndexOf('.');
    }
    
    return name.substring(i+1);
  }

  public String getPackageName () {
    return packageName;
  }

  public int getId() {
    return id;
  }

  public long getUniqueId() {
    return uniqueId;
  }

  public int getFieldAttrs (int fieldIndex) {
    fieldIndex = 0; // Get rid of IDE warning
     
    return 0;
  }

  public void setElementInfoAttrs (int attrs){
    elementInfoAttrs = attrs;
  }

  public void addElementInfoAttr (int attr){
    elementInfoAttrs |= attr;
  }

  public int getElementInfoAttrs () {
    return elementInfoAttrs;
  }

  public Source getSource () {
    if (source == null) {
      source = loadSource();
    }

    return source;
  }

  public String getSourceFileName () {
    return sourceFileName;
  }

  /**
   * Returns the information about a static field.
   */
  public FieldInfo getStaticField (int index) {
    return sFields[index];
  }

  /**
   * Returns the name of a static field.
   */
  public String getStaticFieldName (int index) {
    return getStaticField(index).getName();
  }

  /**
   * Checks if a static method call is deterministic, but only for
   * abtraction based determinism, due to Bandera.choose() calls
   */
  public boolean isStaticMethodAbstractionDeterministic (ThreadInfo th,
                                                         MethodInfo mi) {
    //    Reflection r = reflection.instantiate();
    //    return r.isStaticMethodAbstractionDeterministic(th, mi);
    // <2do> - still has to be implemented
     
    th = null;  // Get rid of IDE warning
    mi = null;
     
    return true;
  }

  public String getSuperClassName() {
    return superClassName;
  }

  /**
   * Return the super class.
   */
  public ClassInfo getSuperClass () {
    return superClass;
  }

  /**
   * return the ClassInfo for the provided superclass name. If this is equals
   * to ourself, return this (a little bit strange if we hit it in the first place)
   */
  public ClassInfo getSuperClass (String clsName) {
    if (clsName.equals(name)) return this;

    if (superClass != null) {
      return superClass.getSuperClass(clsName);
    } else {
      return null;
    }
  }
  
  /**
   * beware - this loads (but not yet registers) the enclosing class
   */
  public String getEnclosingClassName(){
    return enclosingClassName;
  }
  
  /**
   * beware - this loads (but not yet registers) the enclosing class
   */
  public ClassInfo getEnclosingClassInfo() {
    String enclName = getEnclosingClassName();
    return (enclName == null ? null : classLoader.getResolvedClassInfo(enclName)); // ? is this supposed to use the same classloader
  }

  public String getEnclosingMethodName(){
    return enclosingMethodName;
  }

  /**
   * same restriction as getEnclosingClassInfo() - might not be registered/initialized
   */
  public MethodInfo getEnclosingMethodInfo(){
    MethodInfo miEncl = null;
    
    if (enclosingMethodName != null){
      ClassInfo ciIncl = getEnclosingClassInfo();
      miEncl = ciIncl.getMethod( enclosingMethodName, false);
    }
    
    return miEncl;
  }
  
  /**
   * Returns true if the class is a system class.
   */
  public boolean isSystemClass () {
    return name.startsWith("java.") || name.startsWith("javax.");
  }

  /**
   * <2do> that's stupid - we should use subclasses for builtin and box types
   */
  public boolean isBoxClass () {
    if (name.startsWith("java.lang.")) {
      String rawType = name.substring(10);
      if (rawType.startsWith("Boolean") ||
          rawType.startsWith("Byte") ||
          rawType.startsWith("Character") ||
          rawType.startsWith("Integer") ||
          rawType.startsWith("Float") ||
          rawType.startsWith("Long") ||
          rawType.startsWith("Double")) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns the type of a class.
   */
  public String getType () {
    if (!isArray) {
      return "L" + name.replace('.', '/') + ";";
    } else {
      return name;
    }
  }

  /**
   * is this a (subclass of) WeakReference? this must be efficient, since it's
   * called in the mark phase on all live objects
   */
  public boolean isWeakReference () {
    return isWeakReference;
  }

  /**
   * note this only returns true is this is really the java.lang.ref.Reference classInfo
   */
  public boolean isReferenceClassInfo () {
    return isRefClassInfo;
  }

  /**
   * whether this refers to a primitive type.
   */
  public boolean isPrimitive() {
    return superClass == null && !isObjectClassInfo();
  }


  boolean hasRefField (int ref, Fields fv) {
    ClassInfo c = this;

    do {
      FieldInfo[] fia = c.iFields;
      for (int i=0; i<fia.length; i++) {
        FieldInfo fi = c.iFields[i];
        if (fi.isReference() && (fv.getIntValue( fi.getStorageOffset()) == ref)) return true;
      }
      c = c.superClass;
    } while (c != null);

    return false;
  }

  boolean hasImmutableInstances () {
    return ((elementInfoAttrs & ElementInfo.ATTR_IMMUTABLE) != 0);
  }

  public boolean hasInstanceFieldInfoAttr (Class<?> type){
    for (int i=0; i<nInstanceFields; i++){
      if (getInstanceField(i).hasAttr(type)){
        return true;
      }
    }
    
    return false;
  }
  
  public NativePeer getNativePeer () {
    return nativePeer;
  }
  
  /**
   * Returns true if the given class is an instance of the class
   * or interface specified.
   */
  public boolean isInstanceOf (String cname) {
    if (isPrimitive()) {
      return Types.getJNITypeCode(name).equals(cname);

    } else {
      cname = Types.getClassNameFromTypeName(cname);
      ClassInfo ci = this.classLoader.getResolvedClassInfo(cname);
      return isInstanceOf(ci);
    }
  }

  /**
   * Returns true if the given class is an instance of the class
   * or interface specified.
   */
  public boolean isInstanceOf (ClassInfo ci) {
    if (isPrimitive()) { // no inheritance for builtin types
      return (this==ci);
    } else {
      for (ClassInfo c = this; c != null; c = c.superClass) {
        if (c==ci) {
          return true;
        }
      }

      return getAllInterfaces().contains(ci);
    }
  }

  public boolean isInnerClassOf (String enclosingName){
    // don't register or initialize yet
    ClassInfo ciEncl = classLoader.tryGetResolvedClassInfo( enclosingName);
    if (ciEncl != null){
      return ciEncl.hasInnerClass(name);
    } else {
      return false;
    }
  }
  
  public boolean hasInnerClass (String innerName){
    for (int i=0; i<innerClassNames.length; i++){
      if (innerClassNames[i].equals(innerName)){
        return true;
      }
    }
    
    return false;
  }


  public static String makeModelClassPath (Config config) {
    StringBuilder buf = new StringBuilder(256);
    String ps = File.pathSeparator;
    String v;

    for (File f : config.getPathArray("boot_classpath")){
      buf.append(f.getAbsolutePath());
      buf.append(ps);
    }

    for (File f : config.getPathArray("classpath")){
      buf.append(f.getAbsolutePath());
      buf.append(ps);
    }

    // finally, we load from the standard Java libraries
    v = System.getProperty("sun.boot.class.path");
    if (v != null) {
      buf.append(v);
    }
    
    return buf.toString();
  }
  
  protected static String[] loadArrayInterfaces () {
    return new String[] {"java.lang.Cloneable", "java.io.Serializable"};
  }

  protected static String[] loadBuiltinInterfaces (String type) {
    return EMPTY_STRING_ARRAY;
  }


  /**
   * Loads the ClassInfo for named class.
   * @param set a Set to which the interface names (String) are added
   * @param ifcs class to find interfaceNames for.
   */
  void loadInterfaceRec (Set<ClassInfo> set, String[] interfaces) throws ClassInfoException {
    if (interfaces != null) {
      for (String iname : interfaces) {

        ClassInfo ci = classLoader.getResolvedClassInfo(iname);

        if (set != null){
          set.add(ci);
        }

        loadInterfaceRec(set, ci.interfaceNames);
      }
    }
  }

  int computeInstanceDataOffset () {
    if (superClass == null) {
      return 0;
    } else {
      return superClass.getInstanceDataSize();
    }
  }

  int getInstanceDataOffset () {
    return instanceDataOffset;
  }

  ClassInfo getClassBase (String clsBase) {
    if ((clsBase == null) || (name.equals(clsBase))) return this;

    if (superClass != null) {
      return superClass.getClassBase(clsBase);
    }

    return null; // Eeek - somebody asked for a class that isn't in the base list
  }

  int computeInstanceDataSize () {
    int n = getDataSize( iFields);

    for (ClassInfo c=superClass; c!= null; c=c.superClass) {
      n += c.getDataSize(c.iFields);
    }

    return n;
  }

  public int getInstanceDataSize () {
    return instanceDataSize;
  }

  int getDataSize (FieldInfo[] fields) {
    int n=0;
    for (int i=0; i<fields.length; i++) {
      n += fields[i].getStorageSize();
    }

    return n;
  }

  public int getNumberOfDeclaredInstanceFields () {
    return iFields.length;
  }

  public FieldInfo getDeclaredInstanceField (int i) {
    return iFields[i];
  }

  public int getNumberOfInstanceFields () {
    return nInstanceFields;
  }

  public FieldInfo getInstanceField (int i) {
    int idx = i - (nInstanceFields - iFields.length);
    if (idx >= 0) {
      return ((idx < iFields.length) ? iFields[idx] : null);
    } else {
      return ((superClass != null) ? superClass.getInstanceField(i) : null);
    }
  }

  public FieldInfo[] getInstanceFields(){
    FieldInfo[] fields = new FieldInfo[nInstanceFields];
    
    for (int i=0; i<fields.length; i++){
      fields[i] = getInstanceField(i);
    }
    
    return fields;
  }

  public int getStaticDataSize () {
    return staticDataSize;
  }

  int computeStaticDataSize () {
    return getDataSize(sFields);
  }

  public int getNumberOfStaticFields () {
    return sFields.length;
  }

  protected Source loadSource () {
    return Source.getSource(sourceFileName);
  }

  public static boolean isBuiltinClass (String cname) {
    char c = cname.charAt(0);

    // array class
    if ((c == '[') || cname.endsWith("[]")) {
      return true;
    }

    // primitive type class
    if (Character.isLowerCase(c)) {
      if ("int".equals(cname) || "byte".equals(cname) ||
          "boolean".equals(cname) || "double".equals(cname) ||
          "long".equals(cname) || "char".equals(cname) ||
          "short".equals(cname) || "float".equals(cname) || "void".equals(cname)) {
        return true;
      }
    }

    return false;
  }

  /**
   * set the locations where we look up sources
   */
  static void setSourceRoots (Config config) {
    Source.init(config);
  }

  /**
   * get names of all interfaceNames (transitive, idx.e. incl. bases and super-interfaceNames)
   * @return a Set of String interface names
   */
  public Set<ClassInfo> getAllInterfaces () {
    if (allInterfaces == null) {
      HashSet<ClassInfo> set = new HashSet<ClassInfo>();

      for (ClassInfo ci=this; ci != null; ci=ci.superClass) {
        loadInterfaceRec(set, ci.interfaceNames);
      }

      allInterfaces = Collections.unmodifiableSet(set);
    }

    return allInterfaces;
  }

  /**
   * get names of directly implemented interfaceNames
   */
  public String[] getDirectInterfaceNames () {
    return interfaceNames;
  }

  public Set<ClassInfo> getInterfaceClassInfos() {
    return interfaces;
  }

  public Set<ClassInfo> getAllInterfaceClassInfos() {
    return getAllInterfaces();
  }

  
  /**
   * get names of direct inner classes
   */
  public String[] getInnerClasses(){
    return innerClassNames;
  }
  
  public ClassInfo[] getInnerClassInfos(){
    ClassInfo[] innerClassInfos = new ClassInfo[innerClassNames.length];
    
    for (int i=0; i< innerClassNames.length; i++){
      innerClassInfos[i] = classLoader.getResolvedClassInfo(innerClassNames[i]); // ? is this supposed to use the same classloader
    }
    
    return innerClassInfos;
  }
  
  public BootstrapMethodInfo getBootstrapMethodInfo(int index) {
    return bootstrapMethods[index];
  }

  public ClassInfo getComponentClassInfo () {
    if (isArray()) {
      String cn = name.substring(1);

      if (cn.charAt(0) != '[') {
        cn = Types.getTypeName(cn);
      }

      ClassInfo cci = classLoader.getResolvedClassInfo(cn);

      return cci;
    }

    return null;
  }

  /**
   * most definitely not a public method, but handy for the NativePeer
   */
  protected Map<String, MethodInfo> getDeclaredMethods () {
    return methods;
  }

  /**
   * be careful, this replaces or adds MethodInfos dynamically
   */
  public MethodInfo putDeclaredMethod (MethodInfo mi){
    return methods.put(mi.getUniqueName(), mi);
  }

  public MethodInfo[] getDeclaredMethodInfos() {
    MethodInfo[] a = new MethodInfo[methods.size()];
    methods.values().toArray(a);
    return a;
  }

  public Instruction[] getMatchingInstructions (LocationSpec lspec){
    Instruction[] insns = null;

    if (lspec.matchesFile(sourceFileName)){
      for (MethodInfo mi : methods.values()) {
        Instruction[] a = mi.getMatchingInstructions(lspec);
        if (a != null){
          if (insns != null) {
            // not very efficient but probably rare
            insns = Misc.appendArray(insns, a);
          } else {
            insns = a;
          }

          // little optimization
          if (!lspec.isLineInterval()) {
            break;
          }
        }
      }
    }

    return insns;
  }

  public List<MethodInfo> getMatchingMethodInfos (MethodSpec mspec){
    ArrayList<MethodInfo> list = null;
    if (mspec.matchesClass(name)) {
      for (MethodInfo mi : methods.values()) {
        if (mspec.matches(mi)) {
          if (list == null) {
            list = new ArrayList<MethodInfo>();
          }
          list.add(mi);
        }
      }
    }
    return list;
  }

  public MethodInfo getFinalizer () {
    return finalizer;
  }

  public MethodInfo getClinit() {
    // <2do> braindead - cache
    for (MethodInfo mi : methods.values()) {
      if ("<clinit>".equals(mi.getName())) {
        return mi;
      }
    }
    return null;
  }

  public boolean hasCtors() {
    // <2do> braindead - cache
    for (MethodInfo mi : methods.values()) {
      if ("<init>".equals(mi.getName())) {
        return true;
      }
    }
    return false;
  }

  public static ClassInfo getInitializedSystemClassInfo (String clsName, ThreadInfo ti){
    ClassLoaderInfo systemLoader = ClassLoaderInfo.getCurrentSystemClassLoader();
    ClassInfo ci = systemLoader.getResolvedClassInfo(clsName);

    ci.registerClass(ti); // this is safe to call on already loaded classes

    if (ci.initializeClass(ti)) {
      throw new ClinitRequired(ci);
    }

    return ci;
  }

  /**
   * this one is for clients that need to synchronously get an initialized classinfo.
   * NOTE: we don't handle clinits here. If there is one, this will throw
   * an exception. NO STATIC BLOCKS / FIELDS ALLOWED
   */
  public static ClassInfo getInitializedClassInfo (String clsName, ThreadInfo ti){
    ClassLoaderInfo cl = ClassLoaderInfo.getCurrentClassLoader();
    return cl.getInitializedClassInfo(clsName, ti);
  }

  public boolean isRegistered () {
    //return (id != -1);
    return getStaticElementInfo() != null;
  }
  
  /**
   * this registers a ClassInfo in the corresponding ClassLoader statics so that we can cross-link from
   * SUT code and access static fields.
   */
  public StaticElementInfo registerClass (ThreadInfo ti){
    StaticElementInfo sei = getStaticElementInfo();
    
    if (sei == null) {
      // do this recursively for superclasses and interfaceNames
      // respective classes might be defined by another classloader, so we have to call their ClassInfo.registerClass()
      
      if (superClass != null) {
        superClass.registerClass(ti);
      }

      for (ClassInfo ifc : interfaces) {
        ifc.registerClass(ti);
      }
      
      ClassInfo.logger.finer("registering class: ", name);
      
      ElementInfo ei = createClassObject( ti);
      sei = createAndLinkStaticElementInfo( ti, ei);
      
      // SUT class is fully resolved and registered (but not necessarily initialized), notify listeners
      ti.getVM().notifyClassLoaded(this);
    }
    
    return sei;
  }

  ElementInfo createClassObject (ThreadInfo ti){
    Heap heap = VM.getVM().getHeap(); // ti can be null (during main thread initialization)

    int anchor = name.hashCode(); // 2do - this should also take the ClassLoader ref into account

    SystemClassLoaderInfo systemClassLoader = ti.getSystemClassLoaderInfo();

    ClassInfo classClassInfo = systemClassLoader.getClassClassInfo();    
    ElementInfo ei = heap.newSystemObject(classClassInfo, ti, anchor);
    int clsObjRef = ei.getObjectRef();
    
    ElementInfo eiClsName = heap.newSystemString(name, ti, clsObjRef);
    ei.setReferenceField("name", eiClsName.getObjectRef());

    ei.setBooleanField("isPrimitive", isPrimitive());
    
    // setting the ID_FIELD is done in registerClass once we have a StaticElementInfo

    // link the SUT class object to the classloader 
    ei.setReferenceField("classLoader", classLoader.getClassLoaderObjectRef());
    
    return ei;
  }
  
  StaticElementInfo createAndLinkStaticElementInfo (ThreadInfo ti, ElementInfo eiClsObj) {
    Statics statics = classLoader.getStatics();
    StaticElementInfo sei = statics.newClass(this, ti, eiClsObj);
    
    id = sei.getObjectRef();  // kind of a misnomer, it's really an id    
    uniqueId = ((long)classLoader.getId() << 32) | id;
    
    eiClsObj.setIntField( ID_FIELD, id);      
    
    return sei;
  }

  
  // for startup classes, the order of initialization is reversed since we can't create
  // heap objects before we have a minimal set of registered classes
  
  void registerStartupClass(ThreadInfo ti, List<ClassInfo> list) {
    if (!isRegistered()) {
      // do this recursively for superclasses and interfaceNames
      // respective classes might be defined by another classloader, so we have
      // to call their ClassInfo.registerClass()

      if (superClass != null) {
        superClass.registerStartupClass(ti, list);
      }

      for (ClassInfo ifc : interfaces) {
        ifc.registerStartupClass(ti, list);
      }
    }

    if (!list.contains(this)) {
      list.add(this);
      ClassInfo.logger.finer("registering startup class: ", name);
      createStartupStaticElementInfo(ti);
    }
    
      // SUT class is fully resolved and registered (but not necessarily initialized), notify listeners
      ti.getVM().notifyClassLoaded(this);
  }
  
  StaticElementInfo createStartupStaticElementInfo (ThreadInfo ti) {
    Statics statics = classLoader.getStatics();
    StaticElementInfo sei = statics.newStartupClass(this, ti);
    
    id = sei.getObjectRef();  // kind of a misnomer, it's really an id    
    uniqueId = ((long)classLoader.getId() << 32) | id;
    
    return sei;
  }
  
  ElementInfo createAndLinkStartupClassObject (ThreadInfo ti) {
    StaticElementInfo sei = getStaticElementInfo();
    ElementInfo ei = createClassObject(ti);
    
    sei.setClassObjectRef(ei.getObjectRef());
    ei.setIntField( ID_FIELD, id);      
    
    return ei;
  }
  
  boolean checkIfValidClassClassInfo() {
    return getDeclaredInstanceField( ID_FIELD) != null;
  }
  
  public boolean isInitializing () {
    StaticElementInfo sei = getStaticElementInfo();
    return ((sei != null) && (sei.getStatus() >= 0));
  }

  /**
   * note - this works recursively upwards since there might
   * be a superclass with a clinit that is still executing
   */
  public boolean isInitialized () {
    for (ClassInfo ci = this; ci != null; ci = ci.superClass){
      StaticElementInfo sei = ci.getStaticElementInfo();
      if (sei == null || sei.getStatus() != INITIALIZED){
        return false;
      }
    }
    
    return true;
  }

  public boolean isResolved () {
    return (!isObjectClassInfo() && superClass != null);
  }

  public boolean needsInitialization () {
    StaticElementInfo sei = getStaticElementInfo();
    return ((sei == null) || (sei.getStatus() > INITIALIZED));
  }

  public void setInitializing(ThreadInfo ti) {
    StaticElementInfo sei = getModifiableStaticElementInfo();
    sei.setStatus(ti.getId());
  }
  
  /**
   * check if this class requires clinit execution. If so,
   * push the corresponding DirectCallStackFrames.
   * 
   * clients have to be aware of that frames might get pushed
   * and properly handle re-execution
   */
  public boolean pushRequiredClinits (ThreadInfo ti){
    StaticElementInfo sei = getStaticElementInfo();    
    if (sei == null) {
      sei = registerClass(ti);
    }

    return initializeClass(ti); // indicates if we pushed clinits
  }
    
  public void setInitialized() {
    StaticElementInfo sei = getModifiableStaticElementInfo();
    sei.setStatus(INITIALIZED);

    // we don't emit classLoaded() notifications for non-builtin classes
    // here anymore because it would be confusing to get instructionExecuted()
    // notifications from the <clinit> execution before the classLoaded()
  }

  /**
   * perform static initialization of class
   * this recursively initializes all super classes, but NOT the interfaces
   *
   * @param ti executing thread
   * @return  true if clinit stackframes were pushed, idx.e. context instruction
   * needs to be re-executed
   */
  public boolean initializeClass (ThreadInfo ti) {
    int pushedFrames = 0;

    // push clinits of class hierarchy (upwards, since call stack is LIFO)
    for (ClassInfo ci = this; ci != null; ci = ci.getSuperClass()) {
      if (ci.pushClinit(ti)) {
        
        // note - we don't treat registration/initialization of a class as
        // a sharedness-changing operation since it is done automatically by
        // the VM and the triggering action in the SUT (e.g. static field access or method call)
        // is the one that should update sharedness and/or break the transition accordingly
        
        // we can't do setInitializing() yet because there is no global lock that
        // covers the whole clinit chain, and we might have a context switch before executing
        // a already pushed subclass clinit - there can be races as to which thread
        // does the static init first. Note this case is checked in INVOKECLINIT
        // (which is one of the reasons why we have it).
        pushedFrames++;
      }
    }

    return (pushedFrames > 0);
  }

  /**
   * local class initialization
   * @return true if we pushed a &lt;clinit&gt; frame
   */
  protected boolean pushClinit (ThreadInfo ti) {
    StaticElementInfo sei = getStaticElementInfo();
    int stat = sei.getStatus();
    
    if (stat != INITIALIZED) {
      if (stat != ti.getId()) {
        // even if it is already initializing - if it does not happen in the current thread
        // we have to sync, which we do by calling clinit
        MethodInfo mi = getMethod("<clinit>()V", false);
        if (mi != null) {
          DirectCallStackFrame frame = createDirectCallStackFrame(ti, mi, 0);
          ti.pushFrame( frame);
          return true;

        } else {
          // it has no clinit, so it already is initialized
          setInitialized();
        }
      } else {
        // ignore if it's already being initialized  by our own thread (recursive request)
      }
    }

    return false;
  }

  public StaticElementInfo getStaticElementInfo() {
    if (id != -1) {
      return classLoader.getStatics().get( id);
    } else {
      return null;
    }
  }

  public StaticElementInfo getModifiableStaticElementInfo() {
    if (id != -1) {
      return classLoader.getStatics().getModifiable( id);
    } else {
      return null;      
    }
  }

  Fields createArrayFields (String type, int nElements, int typeSize, boolean isReferenceArray) {
    return fieldsFactory.createArrayFields( type, this,
                                            nElements, typeSize, isReferenceArray);
  }

  /**
   * Creates the fields for a class.  This gets called during registration of a ClassInfo
   */
  Fields createStaticFields () {
    return fieldsFactory.createStaticFields(this);
  }

  void initializeStaticData (ElementInfo ei, ThreadInfo ti) {
    for (int i=0; i<sFields.length; i++) {
      FieldInfo fi = sFields[i];
      fi.initialize(ei, ti);
    }
  }

  /**
   * Creates the fields for an object.
   */
  public Fields createInstanceFields () {
    return fieldsFactory.createInstanceFields(this);
  }

  void initializeInstanceData (ElementInfo ei, ThreadInfo ti) {
    // Note this is only used for field inits, and array elements are not fields!
    // Since Java has only limited element init requirements (either 0 or null),
    // we do this ad hoc in the ArrayFields ctor

    // the order of inits should not matter, since this is only
    // for constant inits. In case of a "class X { int a=42; int b=a; ..}"
    // we have a explicit "GETFIELD a, PUTFIELD b" in the ctor, but to play it
    // safely we init top down

    if (superClass != null) { // do superclasses first
      superClass.initializeInstanceData(ei, ti);
    }

    for (int i=0; i<iFields.length; i++) {
      FieldInfo fi = iFields[i];
      fi.initialize(ei, ti);
    }
  }

  Map<String, MethodInfo> loadArrayMethods () {
    return new HashMap<String, MethodInfo>(0);
  }

  Map<String, MethodInfo> loadBuiltinMethods (String type) {
    type = null;  // Get rid of IDE warning 
     
    return new HashMap<String, MethodInfo>(0);
  }

  protected ClassInfo loadSuperClass (String superName) throws ClassInfoException {
    if (isObjectClassInfo()) {
      return null;
    }

    logger.finer("resolving superclass: ", superName, " of ", name);

    // resolve the superclass
    ClassInfo sci = resolveReferencedClass(superName);

    return sci;
  }

  protected Set<ClassInfo> loadInterfaces (String[] ifcNames) throws ClassInfoException {
    if (ifcNames == null || ifcNames.length == 0){
      return NO_INTERFACES;
      
    } else {
      Set<ClassInfo> set = new HashSet<ClassInfo>();

      for (String ifcName : ifcNames) {
        ClassInfo.logger.finer("resolving interface: ", ifcName, " of ", name);
        ClassInfo ifc = resolveReferencedClass(ifcName);
        set.add(ifc);
      }

      return set;
    }
  }
  
  /**
   * loads superclass and direct interfaces, and computes information
   * that depends on them
   */
  protected void resolveClass() {
    if (!isObjectClassInfo){
      superClass = loadSuperClass(superClassName);
      releaseActions = superClass.releaseActions;
    }
    interfaces = loadInterfaces(interfaceNames);

    //computeInheritedAnnotations(superClass);

    isWeakReference = isWeakReference0();
    isEnum = isEnum0();
  }

  /**
   * get a ClassInfo for a referenced type that is resolved with the same classLoader, but make
   * sure we only do this once per path
   * 
   * This method is called by the following bytecode instructions:
   * anewarray, checkcast, getstatic, instanceof, invokespecial, 
   * invokestatic, ldc, ldc_w, multianewarray, new, and putstatic
   * 
   * It loads the class referenced by these instructions and adds it to the 
   * resolvedClasses map of the classLoader
   */
  public ClassInfo resolveReferencedClass(String cname) {
    if(name.equals(cname)) {
      return this;
    }

    // if the class has been already resolved just return it
    ClassInfo ci = classLoader.getAlreadyResolvedClassInfo(cname);
    if(ci != null) {
      return ci;
    }
 
    // The defining class loader of the class initiate the load of referenced classes
    ci = classLoader.loadClass(cname);
    classLoader.addResolvedClass(ci);

    return ci;
  }

  protected int linkFields (FieldInfo[] fields, int idx, int off){
    for (FieldInfo fi: fields) {      
      fi.linkToClass(this, idx, off);
      
      int storageSize = fi.getStorageSize();      
      off += storageSize;
      idx++;
    }
    
    return off;
  }
  
  protected void linkFields() {
    //--- instance fields
    if(superClass != null) {
      int superDataSize = superClass.instanceDataSize;
      instanceDataSize = linkFields( iFields,  superClass.nInstanceFields, superDataSize);
      nInstanceFields = superClass.nInstanceFields + iFields.length;
      instanceDataOffset = superClass.instanceDataSize;
      
    } else {
      instanceDataSize = linkFields( iFields, 0, 0);
      nInstanceFields = iFields.length;
      instanceDataOffset = 0;
    }
    
    //--- static fields
    staticDataSize = linkFields( sFields, 0, 0);
  }

  // this resolves all annotations in this class hierarchy, which sets inherited attributes
  protected void checkInheritedAnnotations (){
    
  }
  
  @Override
  public String toString() {
    return "ClassInfo[name=" + name + "]";
  }

  protected MethodInfo getFinalizer0 () {
    MethodInfo mi = getMethod("finalize()V", true);

    // we are only interested in non-empty method bodies, Object.finalize()
    // is a dummy
    if ((mi != null) && (!mi.getClassInfo().isObjectClassInfo())) {
      return mi;
    }

    return null;
  }

  protected boolean isObjectClassInfo0 () {
	if (name.equals("java.lang.Object")) {
	  return true;
	}
	return false;
  }

  protected boolean isStringClassInfo0 () {
    if(name.equals("java.lang.String")) {
      return true;
    }
    return false;
  }

  protected boolean isRefClassInfo0 () {
    if(name.equals("java.lang.ref.Reference")) {
      return true;
    }
    return false;
  }

  protected boolean isWeakReference0 () {
	if(name.equals("java.lang.ref.WeakReference")) {
      return true;
	}

    for (ClassInfo ci = this; !ci.isObjectClassInfo(); ci = ci.superClass) {
      if (ci.isWeakReference()) {
        return true;
      }
    }

    return false;
  }

  protected boolean isEnum0 () {
	if(name.equals("java.lang.Enum")) {
      return true;
	}

    for (ClassInfo ci = this; !ci.isObjectClassInfo(); ci = ci.superClass) {
      if (ci.isEnum()) {
        return true;
      }
    }

    return false;
  }

  protected boolean isThreadClassInfo0 () {
    if(name.equals("java.lang.Thread")) {
      return true;
    }
    return false;
  }


  /**
   * It creates an instance from a original ClassInfo instance. It doesn't copy sei & 
   * uniqueId.
   * 
   * It is used for the cases where cl tries to load a class that the original version 
   * of which has been loaded by some other classloader.
   */
  public ClassInfo cloneFor (ClassLoaderInfo cl) {
    ClassInfo ci;

    try {
      ci = (ClassInfo)clone();

      ci.classLoader = cl;
      ci.interfaces = new HashSet<ClassInfo>();
      ci.resolveClass();

      ci.id = -1;
      ci.uniqueId = -1;

      if (methods != Collections.EMPTY_MAP){
        ci.methods = (Map<String, MethodInfo>)((HashMap<String, MethodInfo>) methods).clone();
      }

      for(Map.Entry<String, MethodInfo> e: ci.methods.entrySet()) {
        MethodInfo mi = e.getValue();
        e.setValue(mi.getInstanceFor(ci));
      }

      ci.iFields = new FieldInfo[iFields.length];
      for(int i=0; i<iFields.length; i++) {
        ci.iFields[i] = iFields[i].getInstanceFor(ci);
      }

      ci.sFields = new FieldInfo[sFields.length];
      for(int i=0; i<sFields.length; i++) {
        ci.sFields[i] = sFields[i].getInstanceFor(ci);
      }

      if(nativePeer != null) {
        ci.nativePeer = NativePeer.getNativePeer(ci);
      }

      ci.setAssertionStatus();

    } catch (CloneNotSupportedException cnsx){
      cnsx.printStackTrace();
      return null;
    }

    VM.getVM().notifyClassLoaded(ci);
    return ci;
  }
  
  // <2do> should be abstract
  public StackFrame createStackFrame (ThreadInfo ti, MethodInfo callee){
    return null;
  }
  
  public DirectCallStackFrame createDirectCallStackFrame (ThreadInfo ti, MethodInfo callee, int nLocalSlots){
    return null;
  }
  
  public DirectCallStackFrame createRunStartStackFrame (ThreadInfo ti, MethodInfo miRun){
    return null;
  }
}