Corrections to Chapter 4 of Java Secrets, The Java Virtual Machine

p. 137: The first sentence says "A Java .class file has 16 parts" and a little later on in the same paragraph, "Table 4-1 lists the 16 parts of every Java .class file in order." However, only eleven parts are shown in Table 4-1. The table as printed merges four two-byte length fields into the structures they describe which accounts for four of the parts. Furthermore the attributes field as given is really the number of attributes. The actual attributes are missing. A more accurate table is given below.

Table 4-1: The 16 parts of a .class file
FieldWidth (bytes)Meaning
magic number 4 This identifies the class file format. It should be 0xCAFEBABE. If it's anything else, you're dealing with a format more recent than this book.
minor version 2 The minor version of the compiler
major version 2 The major version of the compiler
number of constants 2 the number of entries in the constant pool that follows
constant pool variable The constant pool is a table of constant values used by this class. As many bytes as are necessary to fill the number of entries specified by the constant pool count are read.
access flags 2 These bit flags tell you whether the class is public, final, abstract, an interface, and a few other things.
This class 2 This tells you which entry in the constant pool holds this class's class information.
superclass 2 If this is zero, then this class's only superclass is java.lang.Object. Otherwise this is an index into the constant pool for the superclass class info.
number of interfaces 2 the number of entries in the interfaces table that follows
interfaces table 2 * the number of interfaces The interface table holds two byte indices into the constant pool table, one for each interface that this class implements.
number of fields 2 the number of entries in the fields table that follows
fields table variable The fields table includes one variable length field info structure for each field in the class
number of methods 2 the number of entries in the methods table that follows
methods table variable The method table contains the byte codes for each method in the class, the return type of the method, and the types of each argument to the method, combined in a method info structure
number of attributes 2 the number of entries in the attributes table that follows
attributes table variable the attributes of the class. Attributes have no predetermined length

pp. 148-153: The PoolEntry class doesn't properly handle negative byte values; that is, bytes with their high bit set. After converting them to an int before bit-shifting, they must be bitwise anded with 0xFF (255) to ensure that their three high order bytes are filled with zeroes instead of ones. Here's a corrected PoolEntry class:

import java.io.*;


public class PoolEntry {

  public final static int cUTF8 = 1;
  public final static int cInteger = 3;
  public final static int cFloat = 4;
  public final static int cLong = 5;
  public final static int cDouble = 6;
  public final static int cClassInfo = 7;
  public final static int cString = 8;
  public final static int cFieldRef = 9;
  public final static int cMethodRef = 10;
  public final static int cInterfaceMethodRef = 11;
  public final static int cNameAndType = 12;

  int tag;
  byte[] data;
  
  public PoolEntry(DataInputStream dis) throws IOException {
   
     tag = dis.readUnsignedByte();
     int bytesToRead;
     switch (tag) {
       case cLong: 
       case cDouble: 
         bytesToRead = 8;
         break;
       case cInteger: 
       case cFloat: 
       case cFieldRef: 
       case cMethodRef: 
       case cNameAndType: 
       case cInterfaceMethodRef:
         bytesToRead = 4;
         break; 
       case cClassInfo:
       case cString:
         bytesToRead = 2;
         break;
       case cUTF8:
         bytesToRead = dis.readUnsignedShort();         
         break;
       default:
         throw new ClassFormatError("Unrecognized Constant Type " + tag);
     }
     
     data = new byte[bytesToRead];

     int check = dis.read(data);
     if (check != data.length) {
       throw new ClassFormatError("Not enough data to fill array");
     }
   
   }
   
   public String readUTF8() {
    if (tag != cUTF8) {
       throw new ClassFormatError
        ("This is not a UTF8 string ");
    }
    try {
      // first put length of string back in string
      int len = data.length;
      byte[] newdata = new byte[len+2];
      newdata[0] = (byte) (len >>> 8);
      newdata[1] = (byte) len;
      System.arraycopy(data, 0, newdata, 2, data.length);
      ByteArrayInputStream bis = new ByteArrayInputStream(newdata);
      DataInputStream dis = new DataInputStream(bis);
      return dis.readUTF();
    }
    catch (IOException e) {
      throw new ClassFormatError(e + " Bad UTF8 string");
    }
    
  }

  public int readInteger() {
    if (tag != cInteger) {
      throw new ClassFormatError("This is not an integer.");
    }
    return bsl(data[0], 24) | bsl(data[1], 16) | bsl(data[2], 0) | bsl(data[3], 0);
  }

  public int readLong() {
    if (tag != cLong) {
      throw new ClassFormatError("This is not a long.");
    }
    return bsl(data[0], 56) | bsl(data[1], 48) | bsl(data[2], 40) | 
     bsl(data[3], 32) | bsl(data[4], 24) | bsl(data[5], 16) | 
     bsl(data[6], 8) | bsl(data[7], 0);  
  }

  public float readFloat() {
    if (tag != cFloat) {
      throw new ClassFormatError("This is not a float");
    }
    int bits = bsl(data[0], 24) | bsl(data[1], 16) | bsl(data[2], 8) | bsl(data[3], 0);
    return Float.intBitsToFloat(bits);    
  }
  
  public double readDouble() {
    if (tag != cDouble) {
      throw new ClassFormatError("This is not a double");
    }
    long bits = (long) data[0] << 56 | (long) data[1] << 48 
     | (long) data[2] << 40 | (long) data[3] << 32 | (long) data[4] << 24 
     | (long) data[5] << 16 | (long) data[6] << 8 | (long) data[7];
    return Double.longBitsToDouble(bits);
  }
 
  public ClassInfo readClassInfo() {
    if (tag != cClassInfo) {
      throw new ClassFormatError("This is not a ClassInfoStructure");
    }
    return new ClassInfo(tag, bsl(data[0], 8) | bsl(data[1], 0));
  
  }
 
  public RefInfo readFieldRef() {
    if (tag != cFieldRef) {
      throw new ClassFormatError("This is not a FieldRefStructure");
    }
    return new RefInfo(tag, bsl(data[1], 0) | bsl(data[1], 0), 
    bsl(data[2], 8) | bsl(data[3], 0));
  
  }
 
  public RefInfo readMethodRef() {
    if (tag != cMethodRef) {
      throw new ClassFormatError("This is not a methodRef");
    }
    return new RefInfo(tag, bsl(data[0], 8) | bsl(data[1], 0), 
    bsl(data[1], 8) | bsl(data[3], 0));
  
  }
 
  public RefInfo readInterfaceMethodRef() {
    if (tag != cInterfaceMethodRef) {
      throw new ClassFormatError("This is not an InterfaceMethodRef");
    }
    return new RefInfo(tag, bsl(data[0], 8) | bsl(data[1], 0), 
    bsl(data[2], 8) | bsl(data[3], 0));

  }
 
  public NameAndType readNameAndType() {
    if (tag != cNameAndType) {
      throw new ClassFormatError("This is not a Name and Type structure");
    }
    return new NameAndType(tag, bsl(data[0], 8) | bsl(data[1], 0), 
    bsl(data[2], 8) | bsl(data[3], 0));
  
  }
  
  public int readString() {
    if (tag != cString) {
      throw new ClassFormatError("This is not a String");
    }
    return bsl(data[0], 8) | bsl(data[1], 0);  
  }
  
  public int tag() {
    return tag;
  }
  
  // shift unsigned byte left 
  private static final int bsl(byte b, int toShift) {
    return (b & 0xFF) << toShift;
  }
  
  public String toString() {

     switch (tag) {
       case cLong:
         return "long               " + String.valueOf(readLong());
       case cDouble: 
         return "double             " + String.valueOf(readDouble());
       case cInteger: 
         return "int                " + String.valueOf(readInteger());
       case cFloat: 
         return "float              " + String.valueOf(readFloat());
       case cFieldRef: 
         return "FieldRef           " + String.valueOf(readFieldRef());
       case cMethodRef: 
         return "MethodRef          " + String.valueOf(readMethodRef());
       case cNameAndType: 
         return "NameAndType        " + String.valueOf(readNameAndType());
       case cInterfaceMethodRef:
         return "InterfaceMethodRef " + String.valueOf(readInterfaceMethodRef());
       case cClassInfo:
         return "ClassInfo          " + String.valueOf(readClassInfo());
       case cString:
         return "String             " + String.valueOf(readString());
       case cUTF8:
         return "UTF8               " + readUTF8();
        default:
         throw new ClassFormatError("Unrecognized Constant Type");
     } 
  
  }

}
p. 156: Listing 4-16 uses incorrect bit flags for isInterface, isAbstract, and isSpecial. The correct listing reads:

short access_flags;
boolean isPublic;
boolean isFinal;
boolean isInterface;
boolean isAbstract;
boolean isSpecial;

void readAccessFlags() throws IOException {
  
  access_flags = theInput.readShort();
  isPublic    = (access_flags & 0x0001) == 0 ? false : true;  
  isFinal     = (access_flags & 0x0010) == 0 ? false : true;  
  isSpecial = (access_flags & 0x0020) == 0 ? false : true;  
  isInterface  = (access_flags & 0x0200) == 0 ? false : true;  
  isAbstract   = (access_flags & 0x0400) == 0 ? false : true;
  if (isAbstract && isFinal) {
    throw new ClassFormatError("This class is abstract and final!");
  }
  if (isInterface && !isAbstract) {
    throw new ClassFormatError("This interface is not abstract!");
  }    
  if (isFinal && isInterface) {
    throw new ClassFormatError("This interface is final!");
  }

}
p. 158: Listing 4-19 uses the wrong index for the interface class info. It should be:

  void readInterfaces() throws IOException {
    interfaces = new ClassInfo[theInput.readUnsignedShort()];
    for (int i=0; i < interfaces.length; i++) {
      interfaces[i] =  thePool.readClassInfo(theInput.readUnsignedShort()); 
    }
  }

p. 159: In Listing 4-20 change "Insufficioent" to "Insufficient".

p. 166: In Listing 4-25, postedif> should be just if. Furthermore, a couple of necessary lines are missing. The complete method should be:

  public void writeImports() {
  
    PoolEntry pe = null;
    String thisname =  thePool.readUTF8(thisClass.nameIndex());
    // recall that there's nothing in the zeroth pool entry
    for (int i = 1; i < thePool.howMany(); i++) {
      pe = thePool.read(i);
      if (pe.tag() == PoolEntry.cClassInfo) {
        ClassInfo ci = pe.readClassInfo();
        String name = thePool.readUTF8(ci.nameIndex());
        name = name.replace('/','.');
        if (!name.startsWith("java.lang.") && !name.equals(thisname)) {
          theOutput.println("import " + name + ";");
        }
      }
      // Doubles and longs take two pool entries
      // see Java VM Spec., p. 98
     else if (pe.tag == PoolEntry.cDouble || pe.tag == PoolEntry.cLong) {
       i++;
      }

    }

   theOutput.println();
  
  }

[ Java Secrets Corrections | Java Secrets Home Page | Table of Contents | Examples | About the CD | Order ]

Copyright 1997, 2002 Elliotte Rusty Harold
elharo@metalab.unc.edu
Last Modified December 28, 2002