view src/main/gov/nasa/jpf/vm/ClassInfo.java @ 23:db918c531e6d

streamlined class init, which was a mixed case of registerClass()/initializeClass() and pushRequiredClinits(). Now it is a single initializeClass(ti) method which combines the previous initializeClass(), pushRequiredClinits() and pushClinit() methods. The reason for combining these is the forthcoming replacement of separately locked clinits from different DirectCallStackFrames with a single synthetic frame that calls clinits from nested synchronized blocks. This is required to model hotspot, which does cause deadlocks with concurrent init of classes that cause subclass init during their clinit executions.
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Wed, 15 Apr 2015 22:40:21 -0700
parents e15b03204dc7
children 6774e2e08d37
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;
        }
        
        processJPFAnnotations(fi);
      }

      iFields = instanceFields;
      sFields = staticFields;

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

  protected void setMethod (MethodInfo mi){
    mi.linkToClass(this);
    methods.put( mi.getUniqueName(), mi);
    processJPFAnnotations(mi);
  }
  
  public void setMethods (MethodInfo[] newMethods) {
    if (newMethods != null && newMethods.length > 0) {
      methods = new LinkedHashMap<String, MethodInfo>();

      for (int i = 0; i < newMethods.length; i++) {
        setMethod( newMethods[i]);
      }
    }
  }
 
  protected void processJPFAttrAnnotation(InfoObject infoObj){
    AnnotationInfo ai = infoObj.getAnnotation("gov.nasa.jpf.annotation.JPFAttribute");
    if (ai != null){
      String[] attrTypes = ai.getValueAsStringArray();
      if (attrTypes != null){
        ClassLoader loader = config.getClassLoader();

        for (String clsName : attrTypes){
          try {
            Class<?> attrCls = loader.loadClass(clsName);
            Object attr = attrCls.newInstance(); // needs to have a default ctor
            infoObj.addAttr(attr);
            
          } catch (ClassNotFoundException cnfx){
            logger.warning("attribute class not found: " + clsName);
            
          } catch (IllegalAccessException iax){
            logger.warning("attribute class has no public default ctor: " + clsName);            
            
          } catch (InstantiationException ix){
            logger.warning("attribute class has no default ctor: " + clsName);            
          }
        }
      }
    }    
  }

  protected void processNoJPFExecutionAnnotation(InfoObject infoObj) {
    AnnotationInfo ai = infoObj.getAnnotation("gov.nasa.jpf.annotation.NoJPFExecution");
    if (ai != null) {
      infoObj.addAttr(NoJPFExec.SINGLETON);
    }
  }

  protected void processJPFAnnotations(InfoObject infoObj) {
    processJPFAttrAnnotation(infoObj);
    processNoJPFExecutionAnnotation(infoObj);
  }

    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();
    processJPFAnnotations(this);
    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.
   */
  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);

    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());
  }
  
  /**
   * initialize this class and its superclasses (but not interfaces)
   * this will cause execution of clinits of not-yet-initialized classes in this hierarchy
   *
   * 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
   *
   * @return true - if initialization pushed DirectCallStackFrames and caller has to re-execute
   */
  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()) {
      StaticElementInfo sei = ci.getStaticElementInfo();
      if (sei == null){
        sei = ci.registerClass(ti);
      }

      int status = sei.getStatus();
      if (status != INITIALIZED){
        // 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).

        if (status != 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 = ci.getMethod("<clinit>()V", false);
          if (mi != null) {
            DirectCallStackFrame frame = ci.createDirectCallStackFrame(ti, mi, 0);
            ti.pushFrame( frame);
            pushedFrames++;

          } else {
            // it has no clinit, we can set it initialized
            ci.setInitialized();
          }
        } else {
          // ignore if it's already being initialized  by our own thread (recursive request)
        }
      } else {
        break; // if this class is initialized, so are its superclasses
      }
    }

    return (pushedFrames > 0);
  }
    
  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()
  }

  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;
  }
}