// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.
//
// classfile.cc -- classfile parsing and stripping.
//

// TODO(adonovan) don't pass pointers by reference; this is not
// compatible with Google C++ style.

// See README.txt for details.
//
// For definition of JVM class file format, see:
// Java SE 8 Edition:
// http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4

#define __STDC_FORMAT_MACROS 1
#define __STDC_LIMIT_MACROS 1
#include <inttypes.h>  // for PRIx32
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <set>
#include <sstream>
#include <string>
#include <vector>

#include "third_party/ijar/common.h"

namespace
{
// Converts a value to string.
// Workaround for mingw where std::to_string is not implemented.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52015.
template <typename T>
std::string ToString(const T &value)
{
    std::ostringstream oss;
    oss << value;
    return oss.str();
}
}  // namespace

namespace devtools_ijar
{

// See Table 4.3 in JVM Spec.
enum CONSTANT
{
    CONSTANT_Class              = 7,
    CONSTANT_FieldRef           = 9,
    CONSTANT_Methodref          = 10,
    CONSTANT_Interfacemethodref = 11,
    CONSTANT_String             = 8,
    CONSTANT_Integer            = 3,
    CONSTANT_Float              = 4,
    CONSTANT_Long               = 5,
    CONSTANT_Double             = 6,
    CONSTANT_NameAndType        = 12,
    CONSTANT_Utf8               = 1,
    CONSTANT_MethodHandle       = 15,
    CONSTANT_MethodType         = 16,
    CONSTANT_InvokeDynamic      = 18
};

// See Tables 4.1, 4.4, 4.5 in JVM Spec.
enum ACCESS
{
    ACC_PUBLIC       = 0x0001,
    ACC_PRIVATE      = 0x0002,
    ACC_PROTECTED    = 0x0004,
    ACC_STATIC       = 0x0008,
    ACC_FINAL        = 0x0010,
    ACC_SYNCHRONIZED = 0x0020,
    ACC_BRIDGE       = 0x0040,
    ACC_VOLATILE     = 0x0040,
    ACC_TRANSIENT    = 0x0080,
    ACC_INTERFACE    = 0x0200,
    ACC_ABSTRACT     = 0x0400,
    ACC_SYNTHETIC    = 0x1000
};

// See Table 4.7.20-A in Java 8 JVM Spec.
enum TARGET_TYPE
{
    // Targets for type parameter declarations (ElementType.TYPE_PARAMETER):
    CLASS_TYPE_PARAMETER  = 0x00,
    METHOD_TYPE_PARAMETER = 0x01,

    // Targets for type uses that may be externally visible in classes and members
    // (ElementType.TYPE_USE):
    CLASS_EXTENDS               = 0x10,
    CLASS_TYPE_PARAMETER_BOUND  = 0x11,
    METHOD_TYPE_PARAMETER_BOUND = 0x12,
    FIELD                       = 0x13,
    METHOD_RETURN               = 0x14,
    METHOD_RECEIVER             = 0x15,
    METHOD_FORMAL_PARAMETER     = 0x16,
    THROWS                      = 0x17,

    // TARGET_TYPE >= 0x40 is reserved for type uses that occur only within code
    // blocks. Ijar doesn't need to know about these.
};

struct Constant;

// TODO(adonovan) these globals are unfortunate
static std::vector<Constant *> const_pool_in;   // input constant pool
static std::vector<Constant *> const_pool_out;  // output constant_pool
static std::set<std::string> used_class_names;
static Constant *class_name;

// Returns the Constant object, given an index into the input constant pool.
// Note: constant(0) == NULL; this invariant is exploited by the
// InnerClassesAttribute, inter alia.
inline Constant *constant(int idx)
{
    if (idx < 0 || (unsigned)idx >= const_pool_in.size())
    {
        fprintf(stderr, "Illegal constant pool index: %d\n", idx);
        abort();
    }
    return const_pool_in[idx];
}

/**********************************************************************
 *                                                                    *
 *                             Constants                              *
 *                                                                    *
 **********************************************************************/

// See sec.4.4 of JVM spec.
struct Constant
{

    Constant(u1 tag) : slot_(0), tag_(tag) {}

    virtual ~Constant() {}

    // For UTF-8 string constants, returns the encoded string.
    // Otherwise, returns an undefined string value suitable for debugging.
    virtual std::string Display() = 0;

    virtual void Write(u1 *&p) = 0;

    // Called by slot() when a constant has been identified as required
    // in the output classfile's constant pool.  This is a hook allowing
    // constants to register their dependency on other constants, by
    // calling slot() on them in turn.
    virtual void Keep() {}

    bool Kept() { return slot_ != 0; }

    // Returns the index of this constant in the output class's constant
    // pool, assigning a slot if not already done.
    u2 slot()
    {
        if (slot_ == 0)
        {
            Keep();
            slot_ = const_pool_out.size();  // BugBot's "narrowing" warning
                                            // is bogus.  The number of
                                            // output constants can't exceed
                                            // the number of input constants.
            if (slot_ == 0)
            {
                fprintf(stderr, "Constant::slot() called before output phase.\n");
                abort();
            }
            const_pool_out.push_back(this);
            if (tag_ == CONSTANT_Long || tag_ == CONSTANT_Double)
            {
                const_pool_out.push_back(NULL);
            }
        }
        return slot_;
    }

    u2 slot_;  // zero => "this constant is unreachable garbage"
    u1 tag_;
};

// Extracts class names from a signature and puts them into the global
// variable used_class_names.
//
// desc: the descriptor class names should be extracted from.
// p: the position where the extraction should tart.
void ExtractClassNames(const std::string &desc, size_t *p);

// See sec.4.4.1 of JVM spec.
struct Constant_Class : Constant
{
    Constant_Class(u2 name_index) : Constant(CONSTANT_Class), name_index_(name_index) {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, constant(name_index_)->slot());
    }

    std::string Display() { return constant(name_index_)->Display(); }

    void Keep() { constant(name_index_)->slot(); }

    u2 name_index_;
};

// See sec.4.4.2 of JVM spec.
struct Constant_FMIref : Constant
{
    Constant_FMIref(u1 tag, u2 class_index, u2 name_type_index)
        : Constant(tag), class_index_(class_index), name_type_index_(name_type_index)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, constant(class_index_)->slot());
        put_u2be(p, constant(name_type_index_)->slot());
    }

    std::string Display()
    {
        return constant(class_index_)->Display() + "::" + constant(name_type_index_)->Display();
    }

    void Keep()
    {
        constant(class_index_)->slot();
        constant(name_type_index_)->slot();
    }

    u2 class_index_;
    u2 name_type_index_;
};

// See sec.4.4.3 of JVM spec.
struct Constant_String : Constant
{
    Constant_String(u2 string_index) : Constant(CONSTANT_String), string_index_(string_index) {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, constant(string_index_)->slot());
    }

    std::string Display() { return "\"" + constant(string_index_)->Display() + "\""; }

    void Keep() { constant(string_index_)->slot(); }

    u2 string_index_;
};

// See sec.4.4.4 of JVM spec.
struct Constant_IntegerOrFloat : Constant
{
    Constant_IntegerOrFloat(u1 tag, u4 bytes) : Constant(tag), bytes_(bytes) {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u4be(p, bytes_);
    }

    std::string Display() { return "int/float"; }

    u4 bytes_;
};

// See sec.4.4.5 of JVM spec.
struct Constant_LongOrDouble : Constant_IntegerOrFloat
{
    Constant_LongOrDouble(u1 tag, u4 high_bytes, u4 low_bytes)
        : Constant_IntegerOrFloat(tag, high_bytes), low_bytes_(low_bytes)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u4be(p, bytes_);
        put_u4be(p, low_bytes_);
    }

    std::string Display() { return "long/double"; }

    u4 low_bytes_;
};

// See sec.4.4.6 of JVM spec.
struct Constant_NameAndType : Constant
{
    Constant_NameAndType(u2 name_index, u2 descr_index)
        : Constant(CONSTANT_NameAndType), name_index_(name_index), descr_index_(descr_index)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, constant(name_index_)->slot());
        put_u2be(p, constant(descr_index_)->slot());
    }

    std::string Display()
    {
        return constant(name_index_)->Display() + "::" + constant(descr_index_)->Display();
    }

    void Keep()
    {
        constant(name_index_)->slot();
        constant(descr_index_)->slot();
    }

    u2 name_index_;
    u2 descr_index_;
};

// See sec.4.4.7 of JVM spec.
struct Constant_Utf8 : Constant
{
    Constant_Utf8(u4 length, const u1 *utf8) : Constant(CONSTANT_Utf8), length_(length), utf8_(utf8)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, length_);
        put_n(p, utf8_, length_);
    }

    std::string Display() { return std::string((const char *)utf8_, length_); }

    u4 length_;
    const u1 *utf8_;
};

// See sec.4.4.8 of JVM spec.
struct Constant_MethodHandle : Constant
{
    Constant_MethodHandle(u1 reference_kind, u2 reference_index)
        : Constant(CONSTANT_MethodHandle),
          reference_kind_(reference_kind),
          reference_index_(reference_index)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u1(p, reference_kind_);
        put_u2be(p, reference_index_);
    }

    std::string Display()
    {
        return "Constant_MethodHandle::" + ToString(reference_kind_) +
               "::" + constant(reference_index_)->Display();
    }

    u1 reference_kind_;
    u2 reference_index_;
};

// See sec.4.4.9 of JVM spec.
struct Constant_MethodType : Constant
{
    Constant_MethodType(u2 descriptor_index)
        : Constant(CONSTANT_MethodType), descriptor_index_(descriptor_index)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, descriptor_index_);
    }

    std::string Display()
    {
        return "Constant_MethodType::" + constant(descriptor_index_)->Display();
    }

    u2 descriptor_index_;
};

// See sec.4.4.10 of JVM spec.
struct Constant_InvokeDynamic : Constant
{
    Constant_InvokeDynamic(u2 bootstrap_method_attr_index, u2 name_and_type_index)
        : Constant(CONSTANT_InvokeDynamic),
          bootstrap_method_attr_index_(bootstrap_method_attr_index),
          name_and_type_index_(name_and_type_index)
    {}

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, bootstrap_method_attr_index_);
        put_u2be(p, name_and_type_index_);
    }

    std::string Display()
    {
        return "Constant_InvokeDynamic::" + ToString(bootstrap_method_attr_index_) +
               "::" + constant(name_and_type_index_)->Display();
    }

    u2 bootstrap_method_attr_index_;
    u2 name_and_type_index_;
};

/**********************************************************************
 *                                                                    *
 *                             Attributes                             *
 *                                                                    *
 **********************************************************************/

// See sec.4.7 of JVM spec.
struct Attribute
{

    virtual ~Attribute() {}
    virtual void Write(u1 *&p) = 0;
    virtual void ExtractClassNames() {}

    void WriteProlog(u1 *&p, u2 length)
    {
        put_u2be(p, attribute_name_->slot());
        put_u4be(p, length);
    }

    Constant *attribute_name_;
};

struct KeepForCompileAttribute : Attribute
{
    void Write(u1 *&p) { WriteProlog(p, 0); }
};

// See sec.4.7.5 of JVM spec.
struct ExceptionsAttribute : Attribute
{

    static ExceptionsAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        ExceptionsAttribute *attr = new ExceptionsAttribute;
        attr->attribute_name_     = attribute_name;
        u2 number_of_exceptions   = get_u2be(p);
        for (int ii = 0; ii < number_of_exceptions; ++ii)
        {
            attr->exceptions_.push_back(constant(get_u2be(p)));
        }
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, exceptions_.size() * 2 + 2);
        put_u2be(p, exceptions_.size());
        for (size_t ii = 0; ii < exceptions_.size(); ++ii)
        {
            put_u2be(p, exceptions_[ii]->slot());
        }
    }

    std::vector<Constant *> exceptions_;
};

// See sec.4.7.6 of JVM spec.
struct InnerClassesAttribute : Attribute
{

    struct Entry
    {
        Constant *inner_class_info;
        Constant *outer_class_info;
        Constant *inner_name;
        u2 inner_class_access_flags;
    };

    virtual ~InnerClassesAttribute()
    {
        for (size_t i = 0; i < entries_.size(); i++)
        {
            delete entries_[i];
        }
    }

    static InnerClassesAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        InnerClassesAttribute *attr = new InnerClassesAttribute;
        attr->attribute_name_       = attribute_name;

        u2 number_of_classes = get_u2be(p);
        for (int ii = 0; ii < number_of_classes; ++ii)
        {
            Entry *entry                    = new Entry;
            entry->inner_class_info         = constant(get_u2be(p));
            entry->outer_class_info         = constant(get_u2be(p));
            entry->inner_name               = constant(get_u2be(p));
            entry->inner_class_access_flags = get_u2be(p);

            attr->entries_.push_back(entry);
        }
        return attr;
    }

    void Write(u1 *&p)
    {
        std::set<int> kept_entries;
        // We keep an entry if the constant referring to the inner class is already
        // kept. Then we mark its outer class and its class name as kept, too, then
        // iterate until a fixed point is reached.
        int entry_count;
        int iteration = 0;

        do
        {
            entry_count = kept_entries.size();
            for (int i_entry = 0; i_entry < static_cast<int>(entries_.size()); ++i_entry)
            {
                Entry *entry = entries_[i_entry];
                if (entry->inner_class_info->Kept() ||
                    used_class_names.find(entry->inner_class_info->Display()) !=
                        used_class_names.end() ||
                    entry->outer_class_info == class_name)
                {
                    if (entry->inner_name == NULL)
                    {
                        // JVMS 4.7.6: inner_name_index is zero iff the class is anonymous
                        continue;
                    }

                    kept_entries.insert(i_entry);

                    // JVMS 4.7.6: outer_class_info_index is zero for top-level classes
                    if (entry->outer_class_info != NULL)
                    {
                        entry->outer_class_info->slot();
                    }

                    entry->inner_name->slot();
                }
            }
            iteration += 1;
        } while (entry_count != static_cast<int>(kept_entries.size()));

        if (kept_entries.empty())
        {
            return;
        }

        WriteProlog(p, 2 + kept_entries.size() * 8);
        put_u2be(p, kept_entries.size());

        for (std::set<int>::iterator it = kept_entries.begin(); it != kept_entries.end(); ++it)
        {
            Entry *entry = entries_[*it];
            put_u2be(p, entry->inner_class_info == NULL ? 0 : entry->inner_class_info->slot());
            put_u2be(p, entry->outer_class_info == NULL ? 0 : entry->outer_class_info->slot());
            put_u2be(p, entry->inner_name == NULL ? 0 : entry->inner_name->slot());
            put_u2be(p, entry->inner_class_access_flags);
        }
    }

    std::vector<Entry *> entries_;
};

// See sec.4.7.7 of JVM spec.
// We preserve EnclosingMethod attributes to be able to identify local and
// anonymous classes. These classes will be stripped of most content, as they
// represent implementation details that shoudn't leak into the ijars. Omitting
// EnclosingMethod attributes can lead to type-checking failures in the presence
// of generics (see b/9070939).
struct EnclosingMethodAttribute : Attribute
{

    static EnclosingMethodAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        EnclosingMethodAttribute *attr = new EnclosingMethodAttribute;
        attr->attribute_name_          = attribute_name;
        attr->class_                   = constant(get_u2be(p));
        attr->method_                  = constant(get_u2be(p));
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, 4);
        put_u2be(p, class_->slot());
        put_u2be(p, method_ == NULL ? 0 : method_->slot());
    }

    Constant *class_;
    Constant *method_;
};

// See sec.4.7.16.1 of JVM spec.
// Used by AnnotationDefault and other attributes.
struct ElementValue
{
    virtual ~ElementValue() {}
    virtual void Write(u1 *&p) = 0;
    virtual void ExtractClassNames() {}
    static ElementValue *Read(const u1 *&p);
    u1 tag_;
    u4 length_;
};

struct BaseTypeElementValue : ElementValue
{
    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, const_value_->slot());
    }
    static BaseTypeElementValue *Read(const u1 *&p)
    {
        BaseTypeElementValue *value = new BaseTypeElementValue;
        value->const_value_         = constant(get_u2be(p));
        return value;
    }
    Constant *const_value_;
};

struct EnumTypeElementValue : ElementValue
{
    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, type_name_->slot());
        put_u2be(p, const_name_->slot());
    }
    static EnumTypeElementValue *Read(const u1 *&p)
    {
        EnumTypeElementValue *value = new EnumTypeElementValue;
        value->type_name_           = constant(get_u2be(p));
        value->const_name_          = constant(get_u2be(p));
        return value;
    }
    Constant *type_name_;
    Constant *const_name_;
};

struct ClassTypeElementValue : ElementValue
{
    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, class_info_->slot());
    }

    virtual void ExtractClassNames()
    {
        size_t idx = 0;
        devtools_ijar::ExtractClassNames(class_info_->Display(), &idx);
    }

    static ClassTypeElementValue *Read(const u1 *&p)
    {
        ClassTypeElementValue *value = new ClassTypeElementValue;
        value->class_info_           = constant(get_u2be(p));
        return value;
    }
    Constant *class_info_;
};

struct ArrayTypeElementValue : ElementValue
{
    virtual ~ArrayTypeElementValue()
    {
        for (const auto *value : values_)
        {
            delete value;
        }
    }

    virtual void ExtractClassNames()
    {
        for (auto *value : values_)
        {
            value->ExtractClassNames();
        }
    }

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        put_u2be(p, values_.size());
        for (auto *value : values_)
        {
            value->Write(p);
        }
    }
    static ArrayTypeElementValue *Read(const u1 *&p)
    {
        ArrayTypeElementValue *value = new ArrayTypeElementValue;
        u2 num_values                = get_u2be(p);
        for (int ii = 0; ii < num_values; ++ii)
        {
            value->values_.push_back(ElementValue::Read(p));
        }
        return value;
    }
    std::vector<ElementValue *> values_;
};

// See sec.4.7.16 of JVM spec.
struct Annotation
{
    virtual ~Annotation()
    {
        for (size_t i = 0; i < element_value_pairs_.size(); i++)
        {
            delete element_value_pairs_[i]->element_value_;
            delete element_value_pairs_[i];
        }
    }

    void ExtractClassNames()
    {
        for (size_t i = 0; i < element_value_pairs_.size(); i++)
        {
            element_value_pairs_[i]->element_value_->ExtractClassNames();
        }
    }

    void Write(u1 *&p)
    {
        put_u2be(p, type_->slot());
        put_u2be(p, element_value_pairs_.size());
        for (size_t ii = 0; ii < element_value_pairs_.size(); ++ii)
        {
            put_u2be(p, element_value_pairs_[ii]->element_name_->slot());
            element_value_pairs_[ii]->element_value_->Write(p);
        }
    }
    static Annotation *Read(const u1 *&p)
    {
        Annotation *value          = new Annotation;
        value->type_               = constant(get_u2be(p));
        u2 num_element_value_pairs = get_u2be(p);
        for (int ii = 0; ii < num_element_value_pairs; ++ii)
        {
            ElementValuePair *pair = new ElementValuePair;
            pair->element_name_    = constant(get_u2be(p));
            pair->element_value_   = ElementValue::Read(p);
            value->element_value_pairs_.push_back(pair);
        }
        return value;
    }
    Constant *type_;
    struct ElementValuePair
    {
        Constant *element_name_;
        ElementValue *element_value_;
    };
    std::vector<ElementValuePair *> element_value_pairs_;
};

// See sec 4.7.20 of Java 8 JVM Spec
//
// Each entry in the annotations table represents a single run-time visible
// annotation on a type used in a declaration or expression. The type_annotation
// structure has the following format:
//
// type_annotation {
//   u1 target_type;
//   union {
//     type_parameter_target;
//     supertype_target;
//     type_parameter_bound_target;
//     empty_target;
//     method_formal_parameter_target;
//     throws_target;
//     localvar_target;
//     catch_target;
//     offset_target;
//     type_argument_target;
//   } target_info;
//   type_path target_path;
//   u2        type_index;
//   u2        num_element_value_pairs;
//   {
//     u2            element_name_index;
//     element_value value;
//   }
//   element_value_pairs[num_element_value_pairs];
// }
//
struct TypeAnnotation
{
    virtual ~TypeAnnotation()
    {
        delete target_info_;
        delete type_path_;
        delete annotation_;
    }

    void ExtractClassNames() { annotation_->ExtractClassNames(); }

    void Write(u1 *&p)
    {
        put_u1(p, target_type_);
        target_info_->Write(p);
        type_path_->Write(p);
        annotation_->Write(p);
    }

    static TypeAnnotation *Read(const u1 *&p)
    {
        TypeAnnotation *value = new TypeAnnotation;
        value->target_type_   = get_u1(p);
        value->target_info_   = ReadTargetInfo(p, value->target_type_);
        value->type_path_     = TypePath::Read(p);
        value->annotation_    = Annotation::Read(p);
        return value;
    }

    struct TargetInfo
    {
        virtual ~TargetInfo() {}
        virtual void Write(u1 *&p) = 0;
    };

    struct TypeParameterTargetInfo : TargetInfo
    {
        void Write(u1 *&p) { put_u1(p, type_parameter_index_); }
        static TypeParameterTargetInfo *Read(const u1 *&p)
        {
            TypeParameterTargetInfo *value = new TypeParameterTargetInfo;
            value->type_parameter_index_   = get_u1(p);
            return value;
        }
        u1 type_parameter_index_;
    };

    struct ClassExtendsInfo : TargetInfo
    {
        void Write(u1 *&p) { put_u2be(p, supertype_index_); }
        static ClassExtendsInfo *Read(const u1 *&p)
        {
            ClassExtendsInfo *value = new ClassExtendsInfo;
            value->supertype_index_ = get_u2be(p);
            return value;
        }
        u2 supertype_index_;
    };

    struct TypeParameterBoundInfo : TargetInfo
    {
        void Write(u1 *&p)
        {
            put_u1(p, type_parameter_index_);
            put_u1(p, bound_index_);
        }
        static TypeParameterBoundInfo *Read(const u1 *&p)
        {
            TypeParameterBoundInfo *value = new TypeParameterBoundInfo;
            value->type_parameter_index_  = get_u1(p);
            value->bound_index_           = get_u1(p);
            return value;
        }
        u1 type_parameter_index_;
        u1 bound_index_;
    };

    struct EmptyInfo : TargetInfo
    {
        void Write(u1 *& /*p*/) {}
        static EmptyInfo *Read(const u1 *& /*p*/) { return new EmptyInfo; }
    };

    struct MethodFormalParameterInfo : TargetInfo
    {
        void Write(u1 *&p) { put_u1(p, method_formal_parameter_index_); }
        static MethodFormalParameterInfo *Read(const u1 *&p)
        {
            MethodFormalParameterInfo *value      = new MethodFormalParameterInfo;
            value->method_formal_parameter_index_ = get_u1(p);
            return value;
        }
        u1 method_formal_parameter_index_;
    };

    struct ThrowsTypeInfo : TargetInfo
    {
        void Write(u1 *&p) { put_u2be(p, throws_type_index_); }
        static ThrowsTypeInfo *Read(const u1 *&p)
        {
            ThrowsTypeInfo *value     = new ThrowsTypeInfo;
            value->throws_type_index_ = get_u2be(p);
            return value;
        }
        u2 throws_type_index_;
    };

    static TargetInfo *ReadTargetInfo(const u1 *&p, u1 target_type)
    {
        switch (target_type)
        {
            case CLASS_TYPE_PARAMETER:
            case METHOD_TYPE_PARAMETER:
                return TypeParameterTargetInfo::Read(p);
            case CLASS_EXTENDS:
                return ClassExtendsInfo::Read(p);
            case CLASS_TYPE_PARAMETER_BOUND:
            case METHOD_TYPE_PARAMETER_BOUND:
                return TypeParameterBoundInfo::Read(p);
            case FIELD:
            case METHOD_RETURN:
            case METHOD_RECEIVER:
                return new EmptyInfo;
            case METHOD_FORMAL_PARAMETER:
                return MethodFormalParameterInfo::Read(p);
            case THROWS:
                return ThrowsTypeInfo::Read(p);
            default:
                fprintf(stderr, "Illegal type annotation target type: %d\n", target_type);
                abort();
        }
    }

    struct TypePath
    {
        void Write(u1 *&p)
        {
            put_u1(p, path_.size());
            for (TypePathEntry entry : path_)
            {
                put_u1(p, entry.type_path_kind_);
                put_u1(p, entry.type_argument_index_);
            }
        }
        static TypePath *Read(const u1 *&p)
        {
            TypePath *value = new TypePath;
            u1 path_length  = get_u1(p);
            for (int ii = 0; ii < path_length; ++ii)
            {
                TypePathEntry entry;
                entry.type_path_kind_      = get_u1(p);
                entry.type_argument_index_ = get_u1(p);
                value->path_.push_back(entry);
            }
            return value;
        }

        struct TypePathEntry
        {
            u1 type_path_kind_;
            u1 type_argument_index_;
        };
        std::vector<TypePathEntry> path_;
    };

    u1 target_type_;
    TargetInfo *target_info_;
    TypePath *type_path_;
    Annotation *annotation_;
};

struct AnnotationTypeElementValue : ElementValue
{
    virtual ~AnnotationTypeElementValue() { delete annotation_; }

    void Write(u1 *&p)
    {
        put_u1(p, tag_);
        annotation_->Write(p);
    }
    static AnnotationTypeElementValue *Read(const u1 *&p)
    {
        AnnotationTypeElementValue *value = new AnnotationTypeElementValue;
        value->annotation_                = Annotation::Read(p);
        return value;
    }

    Annotation *annotation_;
};

ElementValue *ElementValue::Read(const u1 *&p)
{
    const u1 *start = p;
    ElementValue *result;
    u1 tag = get_u1(p);
    if (tag != 0 && strchr("BCDFIJSZs", (char)tag) != NULL)
    {
        result = BaseTypeElementValue::Read(p);
    }
    else if ((char)tag == 'e')
    {
        result = EnumTypeElementValue::Read(p);
    }
    else if ((char)tag == 'c')
    {
        result = ClassTypeElementValue::Read(p);
    }
    else if ((char)tag == '[')
    {
        result = ArrayTypeElementValue::Read(p);
    }
    else if ((char)tag == '@')
    {
        result = AnnotationTypeElementValue::Read(p);
    }
    else
    {
        fprintf(stderr, "Illegal element_value::tag: %d\n", tag);
        abort();
    }
    result->tag_    = tag;
    result->length_ = p - start;
    return result;
}

// See sec.4.7.20 of JVM spec.
// We preserve AnnotationDefault attributes because they are required
// in order to make use of an annotation in new code.
struct AnnotationDefaultAttribute : Attribute
{
    virtual ~AnnotationDefaultAttribute() { delete default_value_; }

    static AnnotationDefaultAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        AnnotationDefaultAttribute *attr = new AnnotationDefaultAttribute;
        attr->attribute_name_            = attribute_name;
        attr->default_value_             = ElementValue::Read(p);
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, default_value_->length_);
        default_value_->Write(p);
    }

    virtual void ExtractClassNames() { default_value_->ExtractClassNames(); }

    ElementValue *default_value_;
};

// See sec.4.7.2 of JVM spec.
// We preserve ConstantValue attributes because they are required for
// compile-time constant propagation.
struct ConstantValueAttribute : Attribute
{

    static ConstantValueAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        ConstantValueAttribute *attr = new ConstantValueAttribute;
        attr->attribute_name_        = attribute_name;
        attr->constantvalue_         = constant(get_u2be(p));
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, 2);
        put_u2be(p, constantvalue_->slot());
    }

    Constant *constantvalue_;
};

// See sec.4.7.9 of JVM spec.
// We preserve Signature attributes because they are required by the
// compiler for type-checking of generics.
struct SignatureAttribute : Attribute
{

    static SignatureAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        SignatureAttribute *attr = new SignatureAttribute;
        attr->attribute_name_    = attribute_name;
        attr->signature_         = constant(get_u2be(p));
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, 2);
        put_u2be(p, signature_->slot());
    }

    virtual void ExtractClassNames()
    {
        size_t signature_idx = 0;
        devtools_ijar::ExtractClassNames(signature_->Display(), &signature_idx);
    }

    Constant *signature_;
};

// See sec.4.7.15 of JVM spec.
// We preserve Deprecated attributes because they are required by the
// compiler to generate warning messages.
struct DeprecatedAttribute : Attribute
{
    static DeprecatedAttribute *Read(const u1 *& /*p*/, Constant *attribute_name)
    {
        DeprecatedAttribute *attr = new DeprecatedAttribute;
        attr->attribute_name_     = attribute_name;
        return attr;
    }

    void Write(u1 *&p) { WriteProlog(p, 0); }
};

// See sec.4.7.16-17 of JVM spec v3.  Includes RuntimeVisible and
// RuntimeInvisible.
//
// We preserve all annotations.
struct AnnotationsAttribute : Attribute
{
    virtual ~AnnotationsAttribute()
    {
        for (size_t i = 0; i < annotations_.size(); i++)
        {
            delete annotations_[i];
        }
    }

    static AnnotationsAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        AnnotationsAttribute *attr = new AnnotationsAttribute;
        attr->attribute_name_      = attribute_name;
        u2 num_annotations         = get_u2be(p);
        for (int ii = 0; ii < num_annotations; ++ii)
        {
            Annotation *annotation = Annotation::Read(p);
            attr->annotations_.push_back(annotation);
        }
        return attr;
    }

    virtual void ExtractClassNames()
    {
        for (auto *annotation : annotations_)
        {
            annotation->ExtractClassNames();
        }
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, -1);
        u1 *payload_start = p - 4;
        put_u2be(p, annotations_.size());
        for (auto *annotation : annotations_)
        {
            annotation->Write(p);
        }
        put_u4be(payload_start, p - 4 - payload_start);  // backpatch length
    }

    std::vector<Annotation *> annotations_;
};

// See sec.4.7.18-19 of JVM spec.  Includes RuntimeVisible and
// RuntimeInvisible.
//
// We preserve all annotations.
struct ParameterAnnotationsAttribute : Attribute
{

    static ParameterAnnotationsAttribute *Read(const u1 *&p, Constant *attribute_name)
    {
        ParameterAnnotationsAttribute *attr = new ParameterAnnotationsAttribute;
        attr->attribute_name_               = attribute_name;
        u1 num_parameters                   = get_u1(p);
        for (int ii = 0; ii < num_parameters; ++ii)
        {
            std::vector<Annotation *> annotations;
            u2 num_annotations = get_u2be(p);
            for (int ii = 0; ii < num_annotations; ++ii)
            {
                Annotation *annotation = Annotation::Read(p);
                annotations.push_back(annotation);
            }
            attr->parameter_annotations_.push_back(annotations);
        }
        return attr;
    }

    virtual void ExtractClassNames()
    {
        for (size_t i = 0; i < parameter_annotations_.size(); i++)
        {
            const std::vector<Annotation *> &annotations = parameter_annotations_[i];
            for (size_t j = 0; j < annotations.size(); j++)
            {
                annotations[j]->ExtractClassNames();
            }
        }
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, -1);
        u1 *payload_start = p - 4;
        put_u1(p, parameter_annotations_.size());
        for (size_t ii = 0; ii < parameter_annotations_.size(); ++ii)
        {
            std::vector<Annotation *> &annotations = parameter_annotations_[ii];
            put_u2be(p, annotations.size());
            for (size_t jj = 0; jj < annotations.size(); ++jj)
            {
                annotations[jj]->Write(p);
            }
        }
        put_u4be(payload_start, p - 4 - payload_start);  // backpatch length
    }

    std::vector<std::vector<Annotation *>> parameter_annotations_;
};

// See sec.4.7.20 of Java 8 JVM spec. Includes RuntimeVisibleTypeAnnotations
// and RuntimeInvisibleTypeAnnotations.
struct TypeAnnotationsAttribute : Attribute
{
    static TypeAnnotationsAttribute *Read(const u1 *&p,
                                          Constant *attribute_name,
                                          u4 /*attribute_length*/)
    {
        auto attr             = new TypeAnnotationsAttribute;
        attr->attribute_name_ = attribute_name;
        u2 num_annotations    = get_u2be(p);
        for (int ii = 0; ii < num_annotations; ++ii)
        {
            TypeAnnotation *annotation = TypeAnnotation::Read(p);
            attr->type_annotations_.push_back(annotation);
        }
        return attr;
    }

    virtual void ExtractClassNames()
    {
        for (auto *type_annotation : type_annotations_)
        {
            type_annotation->ExtractClassNames();
        }
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, -1);
        u1 *payload_start = p - 4;
        put_u2be(p, type_annotations_.size());
        for (TypeAnnotation *annotation : type_annotations_)
        {
            annotation->Write(p);
        }
        put_u4be(payload_start, p - 4 - payload_start);  // backpatch length
    }

    std::vector<TypeAnnotation *> type_annotations_;
};

// See JVMS §4.7.24
struct MethodParametersAttribute : Attribute
{
    static MethodParametersAttribute *Read(const u1 *&p,
                                           Constant *attribute_name,
                                           u4 /*attribute_length*/)
    {
        auto attr             = new MethodParametersAttribute;
        attr->attribute_name_ = attribute_name;
        u1 parameters_count   = get_u1(p);
        for (int ii = 0; ii < parameters_count; ++ii)
        {
            MethodParameter *parameter = new MethodParameter;
            parameter->name_           = constant(get_u2be(p));
            parameter->access_flags_   = get_u2be(p);
            attr->parameters_.push_back(parameter);
        }
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, -1);
        u1 *payload_start = p - 4;
        put_u1(p, parameters_.size());
        for (MethodParameter *parameter : parameters_)
        {
            put_u2be(p, parameter->name_->slot());
            put_u2be(p, parameter->access_flags_);
        }
        put_u4be(payload_start, p - 4 - payload_start);  // backpatch length
    }

    struct MethodParameter
    {
        Constant *name_;
        u2 access_flags_;
    };

    std::vector<MethodParameter *> parameters_;
};

// See JVMS §4.7.28
struct NestHostAttribute : Attribute
{
    static NestHostAttribute *Read(const u1 *&p, Constant *attribute_name, u4 /*attribute_length*/)
    {
        auto attr               = new NestHostAttribute;
        attr->attribute_name_   = attribute_name;
        attr->host_class_index_ = constant(get_u2be(p));
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, 2);
        put_u2be(p, host_class_index_->slot());
    }

    Constant *host_class_index_;
};

// See JVMS §4.7.29
struct NestMembersAttribute : Attribute
{
    static NestMembersAttribute *Read(const u1 *&p,
                                      Constant *attribute_name,
                                      u4 /*attribute_length*/)
    {
        auto attr             = new NestMembersAttribute;
        attr->attribute_name_ = attribute_name;
        u2 number_of_classes  = get_u2be(p);
        for (int ii = 0; ii < number_of_classes; ++ii)
        {
            attr->classes_.push_back(constant(get_u2be(p)));
        }
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, classes_.size() * 2 + 2);
        put_u2be(p, classes_.size());
        for (size_t ii = 0; ii < classes_.size(); ++ii)
        {
            put_u2be(p, classes_[ii]->slot());
        }
    }

    std::vector<Constant *> classes_;
};

struct GeneralAttribute : Attribute
{
    static GeneralAttribute *Read(const u1 *&p, Constant *attribute_name, u4 attribute_length)
    {
        auto attr                = new GeneralAttribute;
        attr->attribute_name_    = attribute_name;
        attr->attribute_length_  = attribute_length;
        attr->attribute_content_ = p;
        p += attribute_length;
        return attr;
    }

    void Write(u1 *&p)
    {
        WriteProlog(p, attribute_length_);
        put_n(p, attribute_content_, attribute_length_);
    }

    u4 attribute_length_;
    const u1 *attribute_content_;
};

/**********************************************************************
 *                                                                    *
 *                             ClassFile                              *
 *                                                                    *
 **********************************************************************/

struct HasAttrs
{
    std::vector<Attribute *> attributes;

    void WriteAttrs(u1 *&p);
    void ReadAttrs(const u1 *&p);

    virtual ~HasAttrs()
    {
        for (const auto *attribute : attributes)
        {
            delete attribute;
        }
    }

    void ExtractClassNames()
    {
        for (auto *attribute : attributes)
        {
            attribute->ExtractClassNames();
        }
    }
};

// A field or method.
// See sec.4.5 and 4.6 of JVM spec.
struct Member : HasAttrs
{
    u2 access_flags;
    Constant *name;
    Constant *descriptor;

    static Member *Read(const u1 *&p)
    {
        Member *m       = new Member;
        m->access_flags = get_u2be(p);
        m->name         = constant(get_u2be(p));
        m->descriptor   = constant(get_u2be(p));
        m->ReadAttrs(p);
        return m;
    }

    void Write(u1 *&p)
    {
        put_u2be(p, access_flags);
        put_u2be(p, name->slot());
        put_u2be(p, descriptor->slot());
        WriteAttrs(p);
    }
};

// See sec.4.1 of JVM spec.
struct ClassFile : HasAttrs
{

    size_t length;

    // Header:
    u4 magic;
    u2 major;
    u2 minor;

    // Body:
    u2 access_flags;
    Constant *this_class;
    Constant *super_class;
    std::vector<Constant *> interfaces;
    std::vector<Member *> fields;
    std::vector<Member *> methods;

    virtual ~ClassFile()
    {
        for (size_t i = 0; i < fields.size(); i++)
        {
            delete fields[i];
        }

        for (size_t i = 0; i < methods.size(); i++)
        {
            delete methods[i];
        }

        // Constants do not need to be deleted; they are owned by the constant pool.
    }

    void WriteClass(u1 *&p);

    bool ReadConstantPool(const u1 *&p);

    bool IsExplicitlyKept();

    bool IsLocalOrAnonymous();

    void WriteHeader(u1 *&p)
    {
        put_u4be(p, magic);
        put_u2be(p, major);
        put_u2be(p, minor);

        put_u2be(p, const_pool_out.size());
        for (u2 ii = 1; ii < const_pool_out.size(); ++ii)
        {
            if (const_pool_out[ii] != NULL)
            {  // NB: NULLs appear after long/double.
                const_pool_out[ii]->Write(p);
            }
        }
    }

    void WriteBody(u1 *&p)
    {
        put_u2be(p, access_flags);
        put_u2be(p, this_class->slot());
        put_u2be(p, super_class == NULL ? 0 : super_class->slot());
        put_u2be(p, interfaces.size());
        for (size_t ii = 0; ii < interfaces.size(); ++ii)
        {
            put_u2be(p, interfaces[ii]->slot());
        }
        put_u2be(p, fields.size());
        for (size_t ii = 0; ii < fields.size(); ++ii)
        {
            fields[ii]->Write(p);
        }
        put_u2be(p, methods.size());
        for (size_t ii = 0; ii < methods.size(); ++ii)
        {
            methods[ii]->Write(p);
        }

        Attribute *inner_classes = NULL;

        // Make the inner classes attribute the last, so that it can know which
        // constants were needed
        for (size_t ii = 0; ii < attributes.size(); ii++)
        {
            if (attributes[ii]->attribute_name_->Display() == "InnerClasses")
            {
                inner_classes = attributes[ii];
                attributes.erase(attributes.begin() + ii);
                break;
            }
        }

        if (inner_classes != NULL)
        {
            attributes.push_back(inner_classes);
        }

        WriteAttrs(p);
    }
};

void HasAttrs::ReadAttrs(const u1 *&p)
{
    u2 attributes_count = get_u2be(p);
    for (int ii = 0; ii < attributes_count; ii++)
    {
        Constant *attribute_name = constant(get_u2be(p));
        u4 attribute_length      = get_u4be(p);

        std::string attr_name = attribute_name->Display();
        if (attr_name == "SourceFile" || attr_name == "StackMapTable" ||
            attr_name == "LineNumberTable" || attr_name == "LocalVariableTable" ||
            attr_name == "LocalVariableTypeTable" || attr_name == "Code" ||
            attr_name == "Synthetic" || attr_name == "BootstrapMethods" ||
            attr_name == "SourceDebugExtension")
        {
            p += attribute_length;  // drop these attributes
        }
        else if (attr_name == "Exceptions")
        {
            attributes.push_back(ExceptionsAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "Signature")
        {
            attributes.push_back(SignatureAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "Deprecated")
        {
            attributes.push_back(DeprecatedAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "EnclosingMethod")
        {
            attributes.push_back(EnclosingMethodAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "InnerClasses")
        {
            // TODO(bazel-team): omit private inner classes
            attributes.push_back(InnerClassesAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "AnnotationDefault")
        {
            attributes.push_back(AnnotationDefaultAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "ConstantValue")
        {
            attributes.push_back(ConstantValueAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "RuntimeVisibleAnnotations" ||
                 attr_name == "RuntimeInvisibleAnnotations")
        {
            attributes.push_back(AnnotationsAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "RuntimeVisibleParameterAnnotations" ||
                 attr_name == "RuntimeInvisibleParameterAnnotations")
        {
            attributes.push_back(ParameterAnnotationsAttribute::Read(p, attribute_name));
        }
        else if (attr_name == "Scala" || attr_name == "ScalaSig" || attr_name == "ScalaInlineInfo")
        {
            // These are opaque blobs, so can be handled with a general
            // attribute handler
            attributes.push_back(GeneralAttribute::Read(p, attribute_name, attribute_length));
        }
        else if (attr_name == "RuntimeVisibleTypeAnnotations" ||
                 attr_name == "RuntimeInvisibleTypeAnnotations")
        {
            attributes.push_back(
                TypeAnnotationsAttribute::Read(p, attribute_name, attribute_length));
        }
        else if (attr_name == "MethodParameters")
        {
            attributes.push_back(
                MethodParametersAttribute::Read(p, attribute_name, attribute_length));
        }
        else if (attr_name == "NestHost")
        {
            attributes.push_back(NestHostAttribute::Read(p, attribute_name, attribute_length));
        }
        else if (attr_name == "NestMembers")
        {
            attributes.push_back(NestMembersAttribute::Read(p, attribute_name, attribute_length));
        }
        else if (attr_name == "com.google.devtools.ijar.KeepForCompile")
        {
            auto attr             = new KeepForCompileAttribute;
            attr->attribute_name_ = attribute_name;
            attributes.push_back(attr);
        }
        else
        {
            // Skip over unknown attributes with a warning.  The JVM spec
            // says this is ok, so long as we handle the mandatory attributes.
            fprintf(stderr, "ijar: skipping unknown attribute: \"%s\".\n", attr_name.c_str());
            p += attribute_length;
        }
    }
}

void HasAttrs::WriteAttrs(u1 *&p)
{
    u1 *p_size = p;

    put_u2be(p, 0);
    int n_written_attrs = 0;
    for (size_t ii = 0; ii < attributes.size(); ii++)
    {
        u1 *before = p;
        attributes[ii]->Write(p);
        if (p != before)
        {
            n_written_attrs++;
        }
    }

    put_u2be(p_size, n_written_attrs);
}

// See sec.4.4 of JVM spec.
bool ClassFile::ReadConstantPool(const u1 *&p)
{

    const_pool_in.clear();
    const_pool_in.push_back(NULL);  // dummy first item

    u2 cp_count = get_u2be(p);
    for (int ii = 1; ii < cp_count; ++ii)
    {
        u1 tag = get_u1(p);

        if (devtools_ijar::verbose)
        {
            fprintf(stderr, "cp[%d/%d] = tag %d\n", ii, cp_count, tag);
        }

        switch (tag)
        {
            case CONSTANT_Class:
            {
                u2 name_index = get_u2be(p);
                const_pool_in.push_back(new Constant_Class(name_index));
                break;
            }
            case CONSTANT_FieldRef:
            case CONSTANT_Methodref:
            case CONSTANT_Interfacemethodref:
            {
                u2 class_index = get_u2be(p);
                u2 nti         = get_u2be(p);
                const_pool_in.push_back(new Constant_FMIref(tag, class_index, nti));
                break;
            }
            case CONSTANT_String:
            {
                u2 string_index = get_u2be(p);
                const_pool_in.push_back(new Constant_String(string_index));
                break;
            }
            case CONSTANT_NameAndType:
            {
                u2 name_index       = get_u2be(p);
                u2 descriptor_index = get_u2be(p);
                const_pool_in.push_back(new Constant_NameAndType(name_index, descriptor_index));
                break;
            }
            case CONSTANT_Utf8:
            {
                u2 length = get_u2be(p);
                if (devtools_ijar::verbose)
                {
                    fprintf(stderr, "Utf8: \"%s\" (%d)\n",
                            std::string((const char *)p, length).c_str(), length);
                }

                const_pool_in.push_back(new Constant_Utf8(length, p));
                p += length;
                break;
            }
            case CONSTANT_Integer:
            case CONSTANT_Float:
            {
                u4 bytes = get_u4be(p);
                const_pool_in.push_back(new Constant_IntegerOrFloat(tag, bytes));
                break;
            }
            case CONSTANT_Long:
            case CONSTANT_Double:
            {
                u4 high_bytes = get_u4be(p);
                u4 low_bytes  = get_u4be(p);
                const_pool_in.push_back(new Constant_LongOrDouble(tag, high_bytes, low_bytes));
                // Longs and doubles occupy two constant pool slots.
                // ("In retrospect, making 8-byte constants take two "constant
                // pool entries was a poor choice." --JVM Spec.)
                const_pool_in.push_back(NULL);
                ii++;
                break;
            }
            case CONSTANT_MethodHandle:
            {
                u1 reference_kind  = get_u1(p);
                u2 reference_index = get_u2be(p);
                const_pool_in.push_back(new Constant_MethodHandle(reference_kind, reference_index));
                break;
            }
            case CONSTANT_MethodType:
            {
                u2 descriptor_index = get_u2be(p);
                const_pool_in.push_back(new Constant_MethodType(descriptor_index));
                break;
            }
            case CONSTANT_InvokeDynamic:
            {
                u2 bootstrap_method_attr = get_u2be(p);
                u2 name_name_type_index  = get_u2be(p);
                const_pool_in.push_back(
                    new Constant_InvokeDynamic(bootstrap_method_attr, name_name_type_index));
                break;
            }
            default:
            {
                fprintf(stderr, "Unknown constant: %02x. Passing class through.\n", tag);
                return false;
            }
        }
    }

    return true;
}

bool ClassFile::IsLocalOrAnonymous()
{
    for (const Attribute *attribute : attributes)
    {
        if (attribute->attribute_name_->Display() == "EnclosingMethod")
        {
            // JVMS 4.7.6: a class must has EnclosingMethod attribute iff it
            // represents a local class or an anonymous class
            return true;
        }
    }
    return false;
}

static bool HasKeepForCompile(const std::vector<Attribute *> attributes)
{
    for (const Attribute *attribute : attributes)
    {
        if (attribute->attribute_name_->Display() == "com.google.devtools.ijar.KeepForCompile")
        {
            return true;
        }
    }
    return false;
}

bool ClassFile::IsExplicitlyKept()
{
    if (HasKeepForCompile(attributes))
    {
        return true;
    }
    for (const Member *method : methods)
    {
        if (HasKeepForCompile(method->attributes))
        {
            return true;
        }
    }
    return false;
}

static ClassFile *ReadClass(const void *classdata, size_t length)
{
    const u1 *p = (u1 *)classdata;

    ClassFile *clazz = new ClassFile;

    clazz->length = length;

    clazz->magic = get_u4be(p);
    if (clazz->magic != 0xCAFEBABE)
    {
        fprintf(stderr, "Bad magic %" PRIx32 "\n", clazz->magic);
        abort();
    }
    clazz->major = get_u2be(p);
    clazz->minor = get_u2be(p);

    if (!clazz->ReadConstantPool(p))
    {
        delete clazz;
        return NULL;
    }

    clazz->access_flags = get_u2be(p);
    clazz->this_class   = constant(get_u2be(p));
    class_name          = clazz->this_class;

    u2 super_class_id  = get_u2be(p);
    clazz->super_class = super_class_id == 0 ? NULL : constant(super_class_id);

    u2 interfaces_count = get_u2be(p);
    for (int ii = 0; ii < interfaces_count; ++ii)
    {
        clazz->interfaces.push_back(constant(get_u2be(p)));
    }

    u2 fields_count = get_u2be(p);
    for (int ii = 0; ii < fields_count; ++ii)
    {
        Member *field = Member::Read(p);

        if ((field->access_flags & ACC_PRIVATE) == ACC_PRIVATE)
        {
            // drop private fields
            continue;
        }
        clazz->fields.push_back(field);
    }

    u2 methods_count = get_u2be(p);
    for (int ii = 0; ii < methods_count; ++ii)
    {
        Member *method = Member::Read(p);

        if (HasKeepForCompile(method->attributes))
        {
            // Always keep methods marked as such
            clazz->methods.push_back(method);
            continue;
        }

        // drop class initializers
        if (method->name->Display() == "<clinit>")
            continue;

        if ((method->access_flags & ACC_PRIVATE) == ACC_PRIVATE)
        {
            // drop private methods
            continue;
        }
        if ((method->access_flags & (ACC_SYNTHETIC | ACC_BRIDGE | ACC_PUBLIC | ACC_PROTECTED)) ==
            ACC_SYNTHETIC)
        {
            // drop package-private non-bridge synthetic methods, e.g. synthetic
            // constructors used to instantiate private nested classes within their
            // declaring compilation unit
            continue;
        }
        clazz->methods.push_back(method);
    }

    clazz->ReadAttrs(p);

    return clazz;
}

// In theory, '/' is also reserved, but it's okay if we just parse package
// identifiers as part of the class name. Note that signatures are UTF-8, but
// this works just as well as in plain ASCII.
static const char *SIGNATURE_NON_IDENTIFIER_CHARS = ".;[<>:";

void Expect(const std::string &desc, size_t *p, char expected)
{
    if (desc[*p] != expected)
    {
        fprintf(stderr, "Expected '%c' in '%s' at %zd in signature\n", expected,
                desc.substr(*p).c_str(), *p);
        exit(1);
    }

    *p += 1;
}

// These functions form a crude recursive descent parser for descriptors and
// signatures in class files (see JVM spec 4.3).
//
// This parser is a bit more liberal than the spec, but this should be fine,
// because it accepts all valid class files and croaks only on invalid ones.
void ParseFromClassTypeSignature(const std::string &desc, size_t *p);
void ParseSimpleClassTypeSignature(const std::string &desc, size_t *p);
void ParseClassTypeSignatureSuffix(const std::string &desc, size_t *p);
void ParseIdentifier(const std::string &desc, size_t *p);
void ParseTypeArgumentsOpt(const std::string &desc, size_t *p);
void ParseMethodDescriptor(const std::string &desc, size_t *p);

void ParseClassTypeSignature(const std::string &desc, size_t *p)
{
    Expect(desc, p, 'L');
    ParseSimpleClassTypeSignature(desc, p);
    ParseClassTypeSignatureSuffix(desc, p);
    Expect(desc, p, ';');
}

void ParseSimpleClassTypeSignature(const std::string &desc, size_t *p)
{
    ParseIdentifier(desc, p);
    ParseTypeArgumentsOpt(desc, p);
}

void ParseClassTypeSignatureSuffix(const std::string &desc, size_t *p)
{
    while (desc[*p] == '.')
    {
        *p += 1;
        ParseSimpleClassTypeSignature(desc, p);
    }
}

void ParseIdentifier(const std::string &desc, size_t *p)
{
    size_t next    = desc.find_first_of(SIGNATURE_NON_IDENTIFIER_CHARS, *p);
    std::string id = desc.substr(*p, next - *p);
    used_class_names.insert(id);
    *p = next;
}

void ParseTypeArgumentsOpt(const std::string &desc, size_t *p)
{
    if (desc[*p] != '<')
    {
        return;
    }

    *p += 1;
    while (desc[*p] != '>')
    {
        switch (desc[*p])
        {
            case '*':
                *p += 1;
                break;

            case '+':
            case '-':
                *p += 1;
                ExtractClassNames(desc, p);
                break;

            default:
                ExtractClassNames(desc, p);
                break;
        }
    }

    *p += 1;
}

void ParseMethodDescriptor(const std::string &desc, size_t *p)
{
    Expect(desc, p, '(');
    while (desc[*p] != ')')
    {
        ExtractClassNames(desc, p);
    }

    Expect(desc, p, ')');
    ExtractClassNames(desc, p);
}

void ParseFormalTypeParameters(const std::string &desc, size_t *p)
{
    Expect(desc, p, '<');
    while (desc[*p] != '>')
    {
        ParseIdentifier(desc, p);
        Expect(desc, p, ':');
        if (desc[*p] != ':' && desc[*p] != '>')
        {
            ExtractClassNames(desc, p);
        }

        while (desc[*p] == ':')
        {
            Expect(desc, p, ':');
            ExtractClassNames(desc, p);
        }
    }

    Expect(desc, p, '>');
}

void ExtractClassNames(const std::string &desc, size_t *p)
{
    switch (desc[*p])
    {
        case '<':
            ParseFormalTypeParameters(desc, p);
            ExtractClassNames(desc, p);
            break;

        case 'L':
            ParseClassTypeSignature(desc, p);
            break;

        case '[':
            *p += 1;
            ExtractClassNames(desc, p);
            break;

        case 'T':
            *p += 1;
            ParseIdentifier(desc, p);
            Expect(desc, p, ';');
            break;

        case '(':
            ParseMethodDescriptor(desc, p);
            break;

        case 'B':
        case 'C':
        case 'D':
        case 'F':
        case 'I':
        case 'J':
        case 'S':
        case 'Z':
        case 'V':
            *p += 1;
            break;

        default:
            fprintf(stderr, "Invalid signature %s\n", desc.substr(*p).c_str());
    }
}

void ClassFile::WriteClass(u1 *&p)
{
    used_class_names.clear();
    std::vector<Member *> members;
    members.insert(members.end(), fields.begin(), fields.end());
    members.insert(members.end(), methods.begin(), methods.end());
    ExtractClassNames();
    for (auto *member : members)
    {
        size_t idx = 0;
        devtools_ijar::ExtractClassNames(member->descriptor->Display(), &idx);
        member->ExtractClassNames();
    }

    // We have to write the body out before the header in order to reference
    // the essential constants and populate the output constant pool:
    u1 *body = new u1[length];
    u1 *q    = body;
    WriteBody(q);  // advances q
    u4 body_length = q - body;

    WriteHeader(p);  // advances p
    put_n(p, body, body_length);
    delete[] body;
}

bool StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length)
{
    ClassFile *clazz = ReadClass(classdata_in, in_length);
    bool keep        = true;
    if (clazz == NULL || clazz->IsExplicitlyKept())
    {
        // Class is invalid or kept. Simply copy it to the output and call it a day.
        // TODO: If kept, only emit methods marked with KeepForCompile attribute,
        // as opposed to the entire type.
        put_n(classdata_out, classdata_in, in_length);
    }
    else if (clazz->IsLocalOrAnonymous())
    {
        keep = false;
    }
    else
    {
        // Constant pool item zero is a dummy entry.  Setting it marks the
        // beginning of the output phase; calls to Constant::slot() will
        // fail if called prior to this.
        const_pool_out.push_back(NULL);
        clazz->WriteClass(classdata_out);

        delete clazz;
    }

    // Now clean up all the mess we left behind.

    for (size_t i = 0; i < const_pool_in.size(); i++)
    {
        delete const_pool_in[i];
    }

    const_pool_in.clear();
    const_pool_out.clear();
    return keep;
}

}  // namespace devtools_ijar
