content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * 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 com.taobao.android.dx.cf.direct;
  
  import com.taobao.android.dx.cf.attrib.AttSourceFile;
  import com.taobao.android.dx.cf.cst.ConstantPoolParser;
  import com.taobao.android.dx.cf.iface.Attribute;
  import com.taobao.android.dx.cf.iface.AttributeList;
  import com.taobao.android.dx.cf.iface.ClassFile;
  import com.taobao.android.dx.cf.iface.FieldList;
  import com.taobao.android.dx.cf.iface.MethodList;
  import com.taobao.android.dx.cf.iface.ParseException;
  import com.taobao.android.dx.cf.iface.ParseObserver;
  import com.taobao.android.dx.cf.iface.StdAttributeList;
  import com.taobao.android.dx.rop.code.AccessFlags;
  import com.taobao.android.dx.rop.cst.ConstantPool;
  import com.taobao.android.dx.rop.cst.CstString;
  import com.taobao.android.dx.rop.cst.CstType;
  import com.taobao.android.dx.rop.cst.StdConstantPool;
  import com.taobao.android.dx.rop.type.StdTypeList;
  import com.taobao.android.dx.rop.type.Type;
  import com.taobao.android.dx.rop.type.TypeList;
  import com.taobao.android.dx.util.ByteArray;
  import com.taobao.android.dx.util.Hex;
  
  /**
   * Class file with info taken from a {@code byte[]} or slice thereof.
   */
  public class DirectClassFile implements ClassFile {
      /** the expected value of the ClassFile.magic field */
      private static final int CLASS_FILE_MAGIC = 0xcafebabe;
  
      /**
       * minimum {@code .class} file major version
       *
       * See http://en.wikipedia.org/wiki/Java_class_file for an up-to-date
       * list of version numbers. Currently known (taken from that table) are:
       *
       *     J2SE 7.0 = 51 (0x33 hex),
       *     J2SE 6.0 = 50 (0x32 hex),
       *     J2SE 5.0 = 49 (0x31 hex),
       *     JDK 1.4 = 48 (0x30 hex),
       *     JDK 1.3 = 47 (0x2F hex),
       *     JDK 1.2 = 46 (0x2E hex),
       *     JDK 1.1 = 45 (0x2D hex).
       *
       * Valid ranges are typically of the form
       * "A.0 through B.C inclusive" where A <= B and C >= 0,
       * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
       */
      private static final int CLASS_FILE_MIN_MAJOR_VERSION = 45;
  
      /**
       * maximum {@code .class} file major version
       *
       * Note: if you change this, please change "java.class.version" in System.java.
       */
      private static final int CLASS_FILE_MAX_MAJOR_VERSION = 52;
  
      /** maximum {@code .class} file minor version */
      private static final int CLASS_FILE_MAX_MINOR_VERSION = 0;
  
      /**
       * {@code non-null;} the file path for the class, excluding any base directory
       * specification
       */
      private final String filePath;
  
      /** {@code non-null;} the bytes of the file */
      private final ByteArray bytes;
  
      /**
       * whether to be strict about parsing; if
       * {@code false}, this avoids doing checks that only exist
       * for purposes of verification (such as magic number matching and
       * path-package consistency checking)
       */
      private final boolean strictParse;
  
      /**
       * {@code null-ok;} the constant pool; only ever {@code null}
       * before the constant pool is successfully parsed
       */
      private StdConstantPool pool;
  
      /**
       * the class file field {@code access_flags}; will be {@code -1}
       * before the file is successfully parsed
       */
      private int accessFlags;
  
      /**
       * {@code null-ok;} the class file field {@code this_class},
       * interpreted as a type constant; only ever {@code null}
       * before the file is successfully parsed
       */
      private CstType thisClass;
  
      /**
       * {@code null-ok;} the class file field {@code super_class}, interpreted
       * as a type constant if non-zero
       */
      private CstType superClass;
  
      /**
       * {@code null-ok;} the class file field {@code interfaces}; only
       * ever {@code null} before the file is successfully
       * parsed
       */
      private TypeList interfaces;
  
      /**
       * {@code null-ok;} the class file field {@code fields}; only ever
       * {@code null} before the file is successfully parsed
       */
      private FieldList fields;
  
      /**
       * {@code null-ok;} the class file field {@code methods}; only ever
       * {@code null} before the file is successfully parsed
       */
      private MethodList methods;
  
      /**
       * {@code null-ok;} the class file field {@code attributes}; only
       * ever {@code null} before the file is successfully
       * parsed
       */
      private StdAttributeList attributes;
  
      /** {@code null-ok;} attribute factory, if any */
      private AttributeFactory attributeFactory;
  
      /** {@code null-ok;} parse observer, if any */
      private ParseObserver observer;
  
      /**
       * Returns the string form of an object or {@code "(none)"}
       * (rather than {@code "null"}) for {@code null}.
       *
       * @param obj {@code null-ok;} the object to stringify
       * @return {@code non-null;} the appropriate string form
       */
      public static String stringOrNone(Object obj) {
          if (obj == null) {
              return "(none)";
          }
  
          return obj.toString();
      }
  
      /**
       * Constructs an instance.
       *
       * @param bytes {@code non-null;} the bytes of the file
       * @param filePath {@code non-null;} the file path for the class,
       * excluding any base directory specification
       * @param strictParse whether to be strict about parsing; if
       * {@code false}, this avoids doing checks that only exist
       * for purposes of verification (such as magic number matching and
       * path-package consistency checking)
       */
      public DirectClassFile(ByteArray bytes, String filePath,
                             boolean strictParse) {
          if (bytes == null) {
              throw new NullPointerException("bytes == null");
          }
  
          if (filePath == null) {
              throw new NullPointerException("filePath == null");
          }
  
          this.filePath = filePath;
          this.bytes = bytes;
          this.strictParse = strictParse;
          this.accessFlags = -1;
      }
  
      /**
       * Constructs an instance.
       *
       * @param bytes {@code non-null;} the bytes of the file
       * @param filePath {@code non-null;} the file path for the class,
       * excluding any base directory specification
       * @param strictParse whether to be strict about parsing; if
       * {@code false}, this avoids doing checks that only exist
       * for purposes of verification (such as magic number matching and
       * path-package consistency checking)
       */
      public DirectClassFile(byte[] bytes, String filePath,
                             boolean strictParse) {
          this(new ByteArray(bytes), filePath, strictParse);
      }
  
      /**
       * Sets the parse observer for this instance.
       *
       * @param observer {@code null-ok;} the observer
       */
      public void setObserver(ParseObserver observer) {
          this.observer = observer;
      }
  
      /**
       * Sets the attribute factory to use.
       *
       * @param attributeFactory {@code non-null;} the attribute factory
       */
      public void setAttributeFactory(AttributeFactory attributeFactory) {
          if (attributeFactory == null) {
              throw new NullPointerException("attributeFactory == null");
          }
  
          this.attributeFactory = attributeFactory;
      }
  
      /**
       * Gets the path where this class file is located.
       *
       * @return {@code non-null;} the filePath
       */
      public String getFilePath() {
        return filePath;
      }
  
      /**
       * Gets the {@link ByteArray} that this instance's data comes from.
       *
       * @return {@code non-null;} the bytes
       */
      public ByteArray getBytes() {
          return bytes;
      }
  
      /** {@inheritDoc} */
      public int getMagic() {
          parseToInterfacesIfNecessary();
          return getMagic0();
      }
  
      /** {@inheritDoc} */
      public int getMinorVersion() {
          parseToInterfacesIfNecessary();
          return getMinorVersion0();
      }
  
      /** {@inheritDoc} */
      public int getMajorVersion() {
          parseToInterfacesIfNecessary();
          return getMajorVersion0();
      }
  
      /** {@inheritDoc} */
      public int getAccessFlags() {
          parseToInterfacesIfNecessary();
          return accessFlags;
      }
  
      /** {@inheritDoc} */
      public CstType getThisClass() {
          parseToInterfacesIfNecessary();
          return thisClass;
      }
  
      /** {@inheritDoc} */
      public CstType getSuperclass() {
          parseToInterfacesIfNecessary();
          return superClass;
      }
  
      /** {@inheritDoc} */
      public ConstantPool getConstantPool() {
          parseToInterfacesIfNecessary();
          return pool;
      }
  
      /** {@inheritDoc} */
      public TypeList getInterfaces() {
          parseToInterfacesIfNecessary();
          return interfaces;
      }
  
      /** {@inheritDoc} */
      public FieldList getFields() {
          parseToEndIfNecessary();
          return fields;
      }
  
      /** {@inheritDoc} */
      public MethodList getMethods() {
          parseToEndIfNecessary();
          return methods;
      }
  
      /** {@inheritDoc} */
      public AttributeList getAttributes() {
          parseToEndIfNecessary();
          return attributes;
      }
  
      /** {@inheritDoc} */
      public CstString getSourceFile() {
          AttributeList attribs = getAttributes();
          Attribute attSf = attribs.findFirst(AttSourceFile.ATTRIBUTE_NAME);
  
          if (attSf instanceof AttSourceFile) {
              return ((AttSourceFile) attSf).getSourceFile();
          }
  
          return null;
      }
  
      /**
       * Constructs and returns an instance of {@link TypeList} whose
       * data comes from the bytes of this instance, interpreted as a
       * list of constant pool indices for classes, which are in turn
       * translated to type constants. Instance construction will fail
       * if any of the (alleged) indices turn out not to refer to
       * constant pool entries of type {@code Class}.
       *
       * @param offset offset into {@link #bytes} for the start of the
       * data
       * @param size number of elements in the list (not number of bytes)
       * @return {@code non-null;} an appropriately-constructed class list
       */
      public TypeList makeTypeList(int offset, int size) {
          if (size == 0) {
              return StdTypeList.EMPTY;
          }
  
          if (pool == null) {
              throw new IllegalStateException("pool not yet initialized");
          }
  
          return new DcfTypeList(bytes, offset, size, pool, observer);
      }
  
      /**
       * Gets the class file field {@code magic}, but without doing any
       * checks or parsing first.
       *
       * @return the magic value
       */
      public int getMagic0() {
          return bytes.getInt(0);
      }
  
      /**
       * Gets the class file field {@code minor_version}, but
       * without doing any checks or parsing first.
       *
       * @return the minor version
       */
      public int getMinorVersion0() {
          return bytes.getUnsignedShort(4);
      }
  
      /**
       * Gets the class file field {@code major_version}, but
       * without doing any checks or parsing first.
       *
       * @return the major version
       */
      public int getMajorVersion0() {
          return bytes.getUnsignedShort(6);
      }
  
      /**
       * Runs {@link #parse} if it has not yet been run to cover up to
       * the interfaces list.
       */
      private void parseToInterfacesIfNecessary() {
          if (accessFlags == -1) {
              parse();
          }
      }
  
      /**
       * Runs {@link #parse} if it has not yet been run successfully.
       */
      private void parseToEndIfNecessary() {
          if (attributes == null) {
              parse();
          }
      }
  
      /**
       * Does the parsing, handing exceptions.
       */
      private void parse() {
          try {
              parse0();
          } catch (ParseException ex) {
              ex.addContext("...while parsing " + filePath);
              throw ex;
          } catch (RuntimeException ex) {
              ParseException pe = new ParseException(ex);
              pe.addContext("...while parsing " + filePath);
              throw pe;
          }
      }
  
      /**
       * Sees if the .class file header magic has the good value.
       *
       * @param magic the value of a classfile "magic" field
       * @return true if the magic is valid
       */
      private boolean isGoodMagic(int magic) {
          return magic == CLASS_FILE_MAGIC;
      }
  
      /**
       * Sees if the .class file header version are within
       * range.
       *
       * @param minorVersion the value of a classfile "minor_version" field
       * @param majorVersion the value of a classfile "major_version" field
       * @return true if the parameters are valid and within range
       */
      private boolean isGoodVersion(int minorVersion, int majorVersion) {
          /* Valid version ranges are typically of the form
           * "A.0 through B.C inclusive" where A <= B and C >= 0,
           * which is why we don't have a CLASS_FILE_MIN_MINOR_VERSION.
           */
          if (minorVersion >= 0) {
              /* Check against max first to handle the case where
               * MIN_MAJOR == MAX_MAJOR.
               */
              if (majorVersion == CLASS_FILE_MAX_MAJOR_VERSION) {
                  if (minorVersion <= CLASS_FILE_MAX_MINOR_VERSION) {
                      return true;
                  }
              } else if (majorVersion < CLASS_FILE_MAX_MAJOR_VERSION &&
                         majorVersion >= CLASS_FILE_MIN_MAJOR_VERSION) {
                  return true;
              }
          }
  
          return false;
      }
  
      /**
       * Does the actual parsing.
       */
      private void parse0() {
          if (bytes.size() < 10) {
              throw new ParseException("severely truncated class file");
          }
  
          if (observer != null) {
              observer.parsed(bytes, 0, 0, "begin classfile");
              observer.parsed(bytes, 0, 4, "magic: " + Hex.u4(getMagic0()));
              observer.parsed(bytes, 4, 2,
                              "minor_version: " + Hex.u2(getMinorVersion0()));
              observer.parsed(bytes, 6, 2,
                              "major_version: " + Hex.u2(getMajorVersion0()));
          }
  
          if (strictParse) {
              /* Make sure that this looks like a valid class file with a
               * version that we can handle.
               */
              if (!isGoodMagic(getMagic0())) {
                  throw new ParseException("bad class file magic (" + Hex.u4(getMagic0()) + ")");
              }
  
              if (!isGoodVersion(getMinorVersion0(), getMajorVersion0())) {
                  throw new ParseException("unsupported class file version " +
                                           getMajorVersion0() + "." +
                                           getMinorVersion0());
              }
          }
  
          ConstantPoolParser cpParser = new ConstantPoolParser(bytes);
          cpParser.setObserver(observer);
          pool = cpParser.getPool();
          pool.setImmutable();
  
          int at = cpParser.getEndOffset();
          int accessFlags = bytes.getUnsignedShort(at); // u2 access_flags;
          int cpi = bytes.getUnsignedShort(at + 2); // u2 this_class;
          thisClass = (CstType) pool.get(cpi);
          cpi = bytes.getUnsignedShort(at + 4); // u2 super_class;
          superClass = (CstType) pool.get0Ok(cpi);
          int count = bytes.getUnsignedShort(at + 6); // u2 interfaces_count
  
          if (observer != null) {
              observer.parsed(bytes, at, 2,
                              "access_flags: " +
                              AccessFlags.classString(accessFlags));
              observer.parsed(bytes, at + 2, 2, "this_class: " + thisClass);
              observer.parsed(bytes, at + 4, 2, "super_class: " +
                              stringOrNone(superClass));
              observer.parsed(bytes, at + 6, 2,
                              "interfaces_count: " + Hex.u2(count));
              if (count != 0) {
                  observer.parsed(bytes, at + 8, 0, "interfaces:");
              }
          }
  
          at += 8;
          interfaces = makeTypeList(at, count);
          at += count * 2;
  
          if (strictParse) {
              /*
               * Make sure that the file/jar path matches the declared
               * package/class name.
               */
              String thisClassName = thisClass.getClassType().getClassName();
              if (!(filePath.endsWith(".class") &&
                    filePath.startsWith(thisClassName) &&
                    (filePath.length() == (thisClassName.length() + 6)))) {
                  throw new ParseException("class name (" + thisClassName +
                                           ") does not match path (" +
                                           filePath + ")");
              }
          }
  
          /*
           * Only set the instance variable accessFlags here, since
           * that's what signals a successful parse of the first part of
           * the file (through the interfaces list).
           */
          this.accessFlags = accessFlags;
  
          FieldListParser flParser =
              new FieldListParser(this, thisClass, at, attributeFactory);
          flParser.setObserver(observer);
          fields = flParser.getList();
          at = flParser.getEndOffset();
  
          MethodListParser mlParser =
              new MethodListParser(this, thisClass, at, attributeFactory);
          mlParser.setObserver(observer);
          methods = mlParser.getList();
          at = mlParser.getEndOffset();
  
          AttributeListParser alParser =
              new AttributeListParser(this, AttributeFactory.CTX_CLASS, at,
                                      attributeFactory);
          alParser.setObserver(observer);
          attributes = alParser.getList();
          attributes.setImmutable();
          at = alParser.getEndOffset();
  
          if (at != bytes.size()) {
              throw new ParseException("extra bytes at end of class file, " +
                                       "at offset " + Hex.u4(at));
          }
  
          if (observer != null) {
              observer.parsed(bytes, at, 0, "end classfile");
          }
      }
  
      /**
       * Implementation of {@link TypeList} whose data comes directly
       * from the bytes of an instance of this (outer) class,
       * interpreted as a list of constant pool indices for classes
       * which are in turn returned as type constants. Instance
       * construction will fail if any of the (alleged) indices turn out
       * not to refer to constant pool entries of type
       * {@code Class}.
       */
      private static class DcfTypeList implements TypeList {
          /** {@code non-null;} array containing the data */
          private final ByteArray bytes;
  
          /** number of elements in the list (not number of bytes) */
          private final int size;
  
          /** {@code non-null;} the constant pool */
          private final StdConstantPool pool;
  
          /**
           * Constructs an instance.
           *
           * @param bytes {@code non-null;} original classfile's bytes
           * @param offset offset into {@link #bytes} for the start of the
           * data
           * @param size number of elements in the list (not number of bytes)
           * @param pool {@code non-null;} the constant pool to use
           * @param observer {@code null-ok;} parse observer to use, if any
           */
          public DcfTypeList(ByteArray bytes, int offset, int size,
                  StdConstantPool pool, ParseObserver observer) {
              if (size < 0) {
                  throw new IllegalArgumentException("size < 0");
              }
  
              bytes = bytes.slice(offset, offset + size * 2);
              this.bytes = bytes;
              this.size = size;
              this.pool = pool;
  
              for (int i = 0; i < size; i++) {
                  offset = i * 2;
                  int idx = bytes.getUnsignedShort(offset);
                  CstType type;
                  try {
                      type = (CstType) pool.get(idx);
                  } catch (ClassCastException ex) {
                      // Translate the exception.
                      throw new RuntimeException("bogus class cpi", ex);
                  }
                  if (observer != null) {
                      observer.parsed(bytes, offset, 2, "  " + type);
                  }
              }
          }
  
          /** {@inheritDoc} */
          public boolean isMutable() {
              return false;
          }
  
          /** {@inheritDoc} */
          public int size() {
              return size;
          }
  
          /** {@inheritDoc} */
          public int getWordCount() {
              // It is the same as size because all elements are classes.
              return size;
          }
  
          /** {@inheritDoc} */
          public Type getType(int n) {
              int idx = bytes.getUnsignedShort(n * 2);
              return ((CstType) pool.get(idx)).getClassType();
          }
  
          /** {@inheritDoc} */
          public TypeList withAddedType(Type type) {
              throw new UnsupportedOperationException("unsupported");
          }
      }
  }
  