// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#ifndef QV4COMPILEDDATA_P_H
#define QV4COMPILEDDATA_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <functional>

#include <QtCore/qcryptographichash.h>
#include <QtCore/qhash.h>
#include <QtCore/qhashfunctions.h>
#include <QtCore/qlocale.h>
#include <QtCore/qscopeguard.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qurl.h>
#include <QtCore/qvector.h>
#include <QtCore/qtyperevision.h>

#if QT_CONFIG(temporaryfile)
#include <QtCore/qsavefile.h>
#endif

#include <private/qendian_p.h>
#include <private/qqmlnullablevalue_p.h>
#include <private/qqmlpropertycachevector_p.h>
#include <private/qqmlrefcount_p.h>
#include <private/qqmltype_p.h>
#include <private/qv4compilationunitmapper_p.h>
#include <private/qv4staticvalue_p.h>

#include <functional>
#include <limits.h>

QT_BEGIN_NAMESPACE

// Bump this whenever the compiler data structures change in an incompatible way.
//
// IMPORTANT:
//
// Also change the comment behind the number to describe the latest change. This has the added
// benefit that if another patch changes the version too, it will result in a merge conflict, and
// not get removed silently.
// Also update the comparison functions in qqmlpreviewdiff.cpp when you change the data structures.
#define QV4_DATA_STRUCTURE_VERSION 0x4d // Added AOT lookup validation

class QIODevice;
class QQmlTypeNameCache;
class QQmlType;
class QQmlEngine;
class QQmlPropertyData;
class QQmlScriptData;

namespace QQmlPrivate {
struct AOTCompiledFunction;
}

namespace QmlIR {
struct Document;
}

namespace QV4 {
namespace Heap {
struct Module;
struct String;
struct InternalClass;
};

struct Function;
class EvalISelFactory;
class ResolvedTypeReference;

namespace CompiledData {

// index is per-object binding index
using BindingPropertyData = QList<const QQmlPropertyData *>;

// map from name index
struct ResolvedTypeReferenceMap: public QHash<int, ResolvedTypeReference*>
{
    bool addToHash(QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const;
};

struct String;
struct Function;
struct Lookup;
struct RegExp;
struct Unit;

template <typename ItemType, typename Container, const ItemType *(Container::*IndexedGetter)(int index) const>
struct TableIterator
{
    TableIterator(const Container *container, int index) : container(container), index(index) {}
    const Container *container;
    int index;

    const ItemType *operator->() const { return (container->*IndexedGetter)(index); }
    ItemType operator*() const {return *operator->();}

    TableIterator &operator++()
    {
        ++index;
        return *this;
    }

    TableIterator operator++(int)
    {
        TableIterator result(container, index);
        ++index;
        return result;
    }

    TableIterator operator+(int offset) const { return TableIterator(container, index + offset); }
    TableIterator operator-(int offset) const { return TableIterator(container, index - offset); }
    int operator-(TableIterator other) const { return index - other.index; }

    bool operator==(const TableIterator &rhs) const { return index == rhs.index; }
    bool operator!=(const TableIterator &rhs) const { return index != rhs.index; }
};

struct Location
{
    Location() : m_data(QSpecialIntegerBitfieldZero) {}
    Location(quint32 l, quint32 c) : Location()
    {
        m_data.set<LineField>(l);
        m_data.set<ColumnField>(c);
        Q_ASSERT(m_data.get<LineField>() == l);
        Q_ASSERT(m_data.get<ColumnField>() == c);
    }

    inline bool operator<(const Location &other) const {
        return m_data.get<LineField>() < other.m_data.get<LineField>()
                || (m_data.get<LineField>() == other.m_data.get<LineField>()
                    && m_data.get<ColumnField>() < other.m_data.get<ColumnField>());
    }

    friend size_t qHash(const Location &location, size_t seed = 0)
    {
        return QT_PREPEND_NAMESPACE(qHash)(location.m_data.data(), seed);
    }

    friend bool comparesEqual(const Location &a, const Location &b) noexcept
    {
        return a.m_data.data()== b.m_data.data();
    }
    Q_DECLARE_EQUALITY_COMPARABLE(Location)

    void set(quint32 line, quint32 column)
    {
        m_data.set<LineField>(line);
        m_data.set<ColumnField>(column);
    }

    quint32 line() const { return m_data.get<LineField>(); }
    quint32 column() const { return m_data.get<ColumnField>(); }

private:
    using LineField = quint32_le_bitfield_member<0, 20>;
    using ColumnField = quint32_le_bitfield_member<20, 12>;

    quint32_le_bitfield_union<LineField, ColumnField> m_data;
};
static_assert(sizeof(Location) == 4, "Location structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct RegExp
{
    enum Flag : quint8 {
        RegExp_NoFlags    = 0x0,
        RegExp_Global     = 0x01,
        RegExp_IgnoreCase = 0x02,
        RegExp_Multiline  = 0x04,
        RegExp_Sticky     = 0x08,
        RegExp_Unicode    = 0x10,
    };

    Q_DECLARE_FLAGS(Flags, Flag);

    RegExp() : m_data(QSpecialIntegerBitfieldZero) {}
    RegExp(quint32 flags, quint32 stringIndex) : RegExp()
    {
        m_data.set<FlagsField>(flags);
        m_data.set<StringIndexField>(stringIndex);
    }

    quint32 flags() const { return m_data.get<FlagsField>(); }
    quint32 stringIndex() const { return m_data.get<StringIndexField>(); }

private:
    using FlagsField = quint32_le_bitfield_member<0, 5>;
    using StringIndexField = quint32_le_bitfield_member<5, 27>;
    quint32_le_bitfield_union<FlagsField, StringIndexField> m_data;
};
static_assert(sizeof(RegExp) == 4, "RegExp structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Lookup
{
    enum Type : unsigned int {
        Type_Getter = 0,
        Type_Setter = 1,
        Type_GlobalGetter = 2,
        Type_QmlContextPropertyGetter = 3
    };

    enum Mode : unsigned int {
        Mode_ForStorage = 0,
        Mode_ForCall = 1
    };

    quint32 type() const { return m_data.get<TypeField>(); }
    quint32 nameIndex() const { return m_data.get<NameIndexField>(); }
    quint32 mode() const { return m_data.get<ModeField>(); }

    Lookup() : m_data(QSpecialIntegerBitfieldZero) {}
    Lookup(Type type, Mode mode, quint32 nameIndex) : Lookup()
    {
        m_data.set<TypeField>(type);
        m_data.set<ModeField>(mode);
        m_data.set<NameIndexField>(nameIndex);
    }

private:
    using TypeField = quint32_le_bitfield_member<0, 2>;
    using ModeField = quint32_le_bitfield_member<2, 1>;
    // 1 bit left
    using NameIndexField = quint32_le_bitfield_member<4, 28>;
    quint32_le_bitfield_union<TypeField, ModeField, NameIndexField> m_data;
};
static_assert(sizeof(Lookup) == 4, "Lookup structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct JSClassMember
{
    JSClassMember() : m_data(QSpecialIntegerBitfieldZero) {}

    void set(quint32 nameOffset, bool isAccessor)
    {
        m_data.set<NameOffsetField>(nameOffset);
        m_data.set<IsAccessorField>(isAccessor ? 1 : 0);
    }

    quint32 nameOffset() const { return m_data.get<NameOffsetField>(); }
    bool isAccessor() const { return m_data.get<IsAccessorField>() != 0; }

private:
    using NameOffsetField = quint32_le_bitfield_member<0, 31>;
    using IsAccessorField = quint32_le_bitfield_member<31, 1>;
    quint32_le_bitfield_union<NameOffsetField, IsAccessorField> m_data;
};
static_assert(sizeof(JSClassMember) == 4, "JSClassMember structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct JSClass
{
    quint32_le nMembers;
    // JSClassMember[nMembers]

    static int calculateSize(int nMembers) { return (sizeof(JSClass) + nMembers * sizeof(JSClassMember) + 7) & ~7; }
};
static_assert(sizeof(JSClass) == 4, "JSClass structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct String
{
    qint32_le size;

    static int calculateSize(const QString &str) {
        // we cannot enconuter strings larger than INT_MAX anyway, as such a string
        // would already break in other parts of the compilation process
        return (sizeof(String) + (int(str.size()) + 1) * sizeof(quint16) + 7) & ~0x7;
    }
};

static_assert (sizeof (String) == 4, "String structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct CodeOffsetToLineAndStatement {
    quint32_le codeOffset;
    qint32_le line; // signed because debug instructions get negative line numbers
    quint32_le statement;
};
static_assert(sizeof(CodeOffsetToLineAndStatement) == 12, "CodeOffsetToLineAndStatement structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Block
{
    quint32_le nLocals;
    quint32_le localsOffset;
    quint16_le sizeOfLocalTemporalDeadZone;
    quint16_le padding;

    const quint32_le *localsTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + localsOffset); }

    static int calculateSize(int nLocals) {
        int trailingData = nLocals*sizeof (quint32);
        size_t size = align(align(sizeof(Block)) + size_t(trailingData));
        Q_ASSERT(size < INT_MAX);
        return int(size);
    }

    static size_t align(size_t a) {
        return (a + 7) & ~size_t(7);
    }
};
static_assert(sizeof(Block) == 12, "Block structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

enum class NamedBuiltin: unsigned int {
    Void, Var, Int, Bool, Real, String, Url, DateTime, RegExp
};

enum class CommonType : unsigned int {
    // Actual named builtins
    Void     = uint(NamedBuiltin::Void),
    Var      = uint(NamedBuiltin::Var),
    Int      = uint(NamedBuiltin::Int),
    Bool     = uint(NamedBuiltin::Bool),
    Real     = uint(NamedBuiltin::Real),
    String   = uint(NamedBuiltin::String),
    Url      = uint(NamedBuiltin::Url),
    DateTime = uint(NamedBuiltin::DateTime),
    RegExp   = uint(NamedBuiltin::RegExp),

    // Optimization for very common other types
    Time, Date, Rect, Point, Size,

    // No type specified or not recognized
    Invalid
};

struct ParameterType
{
    enum Flag {
        NoFlag   = 0x0,
        Common   = 0x1,
        List     = 0x2,
    };
    Q_DECLARE_FLAGS(Flags, Flag);

    void set(Flags flags, quint32 typeNameIndexOrCommonType)
    {
        m_data.set<IsListField>(flags.testFlag(List) ? 1 : 0);
        m_data.set<IndexIsCommonTypeField>(flags.testFlag(Common) ? 1 : 0);
        m_data.set<TypeNameIndexOrCommonTypeField>(typeNameIndexOrCommonType);
    }

    bool indexIsCommonType() const
    {
        return m_data.get<IndexIsCommonTypeField>() != 0;
    }

    bool isList() const
    {
        return m_data.get<IsListField>() != 0;
    }

    quint32 typeNameIndexOrCommonType() const
    {
        return m_data.get<TypeNameIndexOrCommonTypeField>();
    }

private:
    using IndexIsCommonTypeField = quint32_le_bitfield_member<0, 1>;
    using IsListField = quint32_le_bitfield_member<1, 1>;
    using TypeNameIndexOrCommonTypeField = quint32_le_bitfield_member<2, 30>;
    quint32_le_bitfield_union<IndexIsCommonTypeField, IsListField, TypeNameIndexOrCommonTypeField> m_data;
};
static_assert(sizeof(ParameterType) == 4, "ParameterType structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Parameter
{
    quint32_le nameIndex;
    ParameterType type;
};
static_assert(sizeof(Parameter) == 8, "Parameter structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

// Function is aligned on an 8-byte boundary to make sure there are no bus errors or penalties
// for unaligned access. The ordering of the fields is also from largest to smallest.
struct Function
{
    enum Flags : unsigned int {
        IsStrict            = 0x1,
        IsArrowFunction     = 0x2,
        IsGenerator         = 0x4,
        IsClosureWrapper    = 0x8,
    };

    // Absolute offset into file where the code for this function is located.
    quint32_le codeOffset;
    quint32_le codeSize;

    quint32_le nameIndex;
    quint16_le length;
    quint16_le nFormals;
    quint32_le formalsOffset; // Can't turn this into a calculated offset because of the mutation in CompilationUnit::createUnitData.
    ParameterType returnType;
    quint32_le localsOffset;
    quint16_le nLocals;
    quint16_le nLineAndStatementNumbers;
    size_t lineAndStatementNumberOffset() const { return localsOffset + nLocals * sizeof(quint32); }
    quint32_le nestedFunctionIndex; // for functions that only return a single closure, used in signal handlers

    quint32_le nRegisters;
    Location location;
    quint32_le nLabelInfos;

    quint16_le sizeOfLocalTemporalDeadZone;
    quint16_le firstTemporalDeadZoneRegister;
    quint16_le sizeOfRegisterTemporalDeadZone;

    size_t labelInfosOffset() const
    {
        return lineAndStatementNumberOffset() + nLineAndStatementNumbers * sizeof(CodeOffsetToLineAndStatement);
    }

    // Keep all unaligned data at the end
    quint8 flags;
    quint8 padding1;

    //    quint32 formalsIndex[nFormals]
    //    quint32 localsIndex[nLocals]

    const Parameter *formalsTable() const
    {
        return reinterpret_cast<const Parameter *>(
                    reinterpret_cast<const char *>(this) + formalsOffset);
    }
    const quint32_le *localsTable() const
    {
        return reinterpret_cast<const quint32_le *>(
                    reinterpret_cast<const char *>(this) + localsOffset);
    }
    const CodeOffsetToLineAndStatement *lineAndStatementNumberTable() const
    {
        return reinterpret_cast<const CodeOffsetToLineAndStatement *>(
                    reinterpret_cast<const char *>(this) + lineAndStatementNumberOffset());
    }

    // --- QQmlPropertyCacheCreator interface
    const Parameter *formalsBegin() const { return formalsTable(); }
    const Parameter *formalsEnd() const { return formalsTable() + nFormals; }
    // ---

    const quint32_le *labelInfoTable() const { return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + labelInfosOffset()); }

    const char *code() const { return reinterpret_cast<const char *>(this) + codeOffset; }

    static int calculateSize(
            int nFormals, int nLocals, int nLinesAndStatements, int labelInfoSize, int codeSize)
    {
        const size_t trailingData = nFormals * sizeof(Parameter)
                + (nLocals + labelInfoSize) * sizeof (quint32)
                + nLinesAndStatements * sizeof(CodeOffsetToLineAndStatement);
        size_t size = align(align(sizeof(Function)) + size_t(trailingData)) + align(codeSize);
        Q_ASSERT(size < INT_MAX);
        return int(size);
    }

    static size_t align(size_t a) {
        return (a + 7) & ~size_t(7);
    }
};
static_assert(sizeof(Function) == 56, "Function structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Method {
    enum Type {
        Regular,
        Getter,
        Setter
    };

    quint32_le name;
    quint32_le type;
    quint32_le function;
};
static_assert(sizeof(Method) == 12, "Method structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Class
{
    quint32_le nameIndex;
    quint32_le scopeIndex;
    quint32_le constructorFunction;
    quint32_le nStaticMethods;
    quint32_le nMethods;
    quint32_le methodTableOffset;

    const Method *methodTable() const { return reinterpret_cast<const Method *>(reinterpret_cast<const char *>(this) + methodTableOffset); }

    static int calculateSize(int nStaticMethods, int nMethods) {
        int trailingData = (nStaticMethods + nMethods) * sizeof(Method);
        size_t size = align(sizeof(Class) + trailingData);
        Q_ASSERT(size < INT_MAX);
        return int(size);
    }

    static size_t align(size_t a) {
        return (a + 7) & ~size_t(7);
    }
};
static_assert(sizeof(Class) == 24, "Class structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct TemplateObject
{
    quint32_le size;

    static int calculateSize(int size) {
        int trailingData = 2 * size * sizeof(quint32_le);
        size_t s = align(sizeof(TemplateObject) + trailingData);
        Q_ASSERT(s < INT_MAX);
        return int(s);
    }

    static size_t align(size_t a) {
        return (a + 7) & ~size_t(7);
    }

    const quint32_le *stringTable() const {
        return reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this + 1));
    }

    uint stringIndexAt(uint i) const {
        return stringTable()[i];
    }
    uint rawStringIndexAt(uint i) const {
        return stringTable()[size + i];
    }
};
static_assert(sizeof(TemplateObject) == 4, "Template object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct ExportEntry
{
    quint32_le exportName;
    quint32_le moduleRequest;
    quint32_le importName;
    quint32_le localName;
    Location location;
};
static_assert(sizeof(ExportEntry) == 20, "ExportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct ImportEntry
{
    quint32_le moduleRequest;
    quint32_le importName;
    quint32_le localName;
    Location location;
};
static_assert(sizeof(ImportEntry) == 16, "ImportEntry structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

// Qml data structures

struct TranslationData
{
    enum { NoContextIndex = std::numeric_limits<quint32>::max() };
    quint32_le stringIndex;
    quint32_le commentIndex;
    qint32_le number;
    quint32_le contextIndex;
};
static_assert(sizeof(TranslationData) == 16, "TranslationData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Binding
{
    quint32_le propertyNameIndex;

    enum Type : unsigned int {
        Type_Invalid,
        Type_Boolean,
        Type_Number,
        Type_String,
        Type_Null,
        Type_Translation,
        Type_TranslationById,
        Type_Script,
        Type_Object,
        Type_AttachedProperty,
        Type_GroupProperty
    };

    enum Flag : unsigned int {
        IsSignalHandlerExpression = 0x1,
        IsSignalHandlerObject = 0x2,
        IsOnAssignment = 0x4,
        InitializerForReadOnlyDeclaration = 0x8,
        IsResolvedEnum = 0x10,
        IsListItem = 0x20,
        IsBindingToAlias = 0x40,
        IsDeferredBinding = 0x80,
        IsCustomParserBinding = 0x100,
        IsFunctionExpression = 0x200,
        IsPropertyObserver = 0x400
    };
    Q_DECLARE_FLAGS(Flags, Flag);

    using FlagsField = quint32_le_bitfield_member<0, 16>;
    using TypeField = quint32_le_bitfield_member<16, 16>;
    quint32_le_bitfield_union<FlagsField, TypeField> flagsAndType;

    void clearFlags() { flagsAndType.set<FlagsField>(0); }
    void setFlag(Flag flag) { flagsAndType.set<FlagsField>(flagsAndType.get<FlagsField>() | flag); }
    bool hasFlag(Flag flag) const { return Flags(flagsAndType.get<FlagsField>()) & flag; }
    Flags flags() const { return Flags(flagsAndType.get<FlagsField>()); }

    void setType(Type type) { flagsAndType.set<TypeField>(type); }
    Type type() const { return Type(flagsAndType.get<TypeField>()); }

    union {
        bool b;
        quint32_le constantValueIndex;
        quint32_le compiledScriptIndex; // used when Type_Script
        quint32_le objectIndex;
        quint32_le translationDataIndex; // used when Type_Translation
        quint32 nullMarker;
    } value;
    quint32_le stringIndex; // Set for Type_String and Type_Script (the latter because of script strings)

    Location location;
    Location valueLocation;

    bool hasSignalHandlerBindingFlag() const
    {
        const Flags bindingFlags = flags();
        return bindingFlags & IsSignalHandlerExpression
                || bindingFlags & IsSignalHandlerObject
                || bindingFlags & IsPropertyObserver;
    }

    bool isValueBinding() const
    {
        switch (type()) {
        case Type_AttachedProperty:
        case Type_GroupProperty:
            return false;
        default:
            return !hasSignalHandlerBindingFlag();
        }
    }

    bool isValueBindingNoAlias() const { return isValueBinding() && !hasFlag(IsBindingToAlias); }
    bool isValueBindingToAlias() const { return isValueBinding() && hasFlag(IsBindingToAlias); }

    bool isSignalHandler() const
    {
        if (hasSignalHandlerBindingFlag()) {
            Q_ASSERT(!isValueBinding());
            Q_ASSERT(!isAttachedProperty());
            Q_ASSERT(!isGroupProperty());
            return true;
        }
        return false;
    }

    bool isAttachedProperty() const
    {
        if (type() == Type_AttachedProperty) {
            Q_ASSERT(!isValueBinding());
            Q_ASSERT(!isSignalHandler());
            Q_ASSERT(!isGroupProperty());
            return true;
        }
        return false;
    }

    bool isGroupProperty() const
    {
        if (type() == Type_GroupProperty) {
            Q_ASSERT(!isValueBinding());
            Q_ASSERT(!isSignalHandler());
            Q_ASSERT(!isAttachedProperty());
            return true;
        }
        return false;
    }

    bool isFunctionExpression() const { return hasFlag(IsFunctionExpression); }

    //reverse of Lexer::singleEscape()
    static QString escapedString(const QString &string)
    {
        QString tmp = QLatin1String("\"");
        for (int i = 0; i < string.size(); ++i) {
            const QChar &c = string.at(i);
            switch (c.unicode()) {
            case 0x08:
                tmp += QLatin1String("\\b");
                break;
            case 0x09:
                tmp += QLatin1String("\\t");
                break;
            case 0x0A:
                tmp += QLatin1String("\\n");
                break;
            case 0x0B:
                tmp += QLatin1String("\\v");
                break;
            case 0x0C:
                tmp += QLatin1String("\\f");
                break;
            case 0x0D:
                tmp += QLatin1String("\\r");
                break;
            case 0x22:
                tmp += QLatin1String("\\\"");
                break;
            case 0x27:
                tmp += QLatin1String("\\\'");
                break;
            case 0x5C:
                tmp += QLatin1String("\\\\");
                break;
            default:
                tmp += c;
                break;
            }
        }
        tmp += QLatin1Char('\"');
        return tmp;
    }

    bool isTranslationBinding() const
    {
        const Binding::Type bindingType = type();
        return bindingType == Type_Translation || bindingType == Type_TranslationById;
    }
    bool evaluatesToString() const { return type() == Type_String || isTranslationBinding(); }

    bool isNumberBinding() const { return type() == Type_Number; }

    bool valueAsBoolean() const
    {
        if (type() == Type_Boolean)
            return value.b;
        return false;
    }
};

static_assert(sizeof(Binding) == 24, "Binding structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct InlineComponent
{
    quint32_le objectIndex;
    quint32_le nameIndex;
    Location location;
};

static_assert(sizeof(InlineComponent) == 12, "InlineComponent structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct EnumValue
{
    quint32_le nameIndex;
    qint32_le value;
};
static_assert(sizeof(EnumValue) == 8, "EnumValue structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Enum
{
    quint32_le nameIndex;
    quint32_le nEnumValues;
    Location location;

    const EnumValue *enumValueAt(int idx) const {
        return reinterpret_cast<const EnumValue*>(this + 1) + idx;
    }

    static int calculateSize(int nEnumValues) {
        return (sizeof(Enum)
                + nEnumValues * sizeof(EnumValue)
                + 7) & ~0x7;
    }

    // --- QQmlPropertyCacheCreatorInterface
    const EnumValue *enumValuesBegin() const { return enumValueAt(0); }
    const EnumValue *enumValuesEnd() const { return enumValueAt(nEnumValues); }
    int enumValueCount() const { return nEnumValues; }
    // ---
};
static_assert(sizeof(Enum) == 12, "Enum structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Signal
{
    quint32_le nameIndex;
    quint32_le nParameters;
    Location location;
    // Parameter parameters[1];

    const Parameter *parameterAt(int idx) const {
        return reinterpret_cast<const Parameter*>(this + 1) + idx;
    }

    static int calculateSize(int nParameters) {
        return (sizeof(Signal)
                + nParameters * sizeof(Parameter)
                + 7) & ~0x7;
    }

    // --- QQmlPropertyCacheCceatorInterface
    const Parameter *parametersBegin() const { return parameterAt(0); }
    const Parameter *parametersEnd() const { return parameterAt(nParameters); }
    int parameterCount() const { return nParameters; }
    // ---
};
static_assert(sizeof(Signal) == 12, "Signal structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

/*
 * We aim to minimize the size of a struct as much as possible, while preserving at least 28 bits
 * for each index.
 *
 * Note that Name and Type indices are provided by StringTableGenerator, containing a hashmap
 * (and a list) of unique strings within one Compilation Unit. It sounds rather unrealistic to have
 * 2^28 (260+ million) unique strings within 1 CU (even if it's some form of global registry), not
 * to mention the amount of memory needed to maintain such a StringTableGenerator.
 * Therefore, after some preliminary analysis, it seems that even 20 bits
 * should be a rather conservative cap.
 *
 * However it doesn't seem to easily provide many benefits atm other than better (logically) grouped
 * unions like, let's say nameIndexAndAttributes.
 *
 * Atm 32-bit member nameIndexAndVirtSpecifiers looks smth like this:
 *
 *          NameIndexField        IsVirtualField IsOverrideField IsFinalField
 * |10100000101000111000001110000|     0        |      1        |     0      |
 *
 */
struct Property
{
private:
    using NameIndexField = quint32_le_bitfield_member<0, 29>;
    using IsVirtualField = quint32_le_bitfield_member<29, 1>;
    using IsOverrideField = quint32_le_bitfield_member<30, 1>;
    using IsFinalField = quint32_le_bitfield_member<31, 1>;

    using CommonTypeOrTypeNameIndexField = quint32_le_bitfield_member<0, 28>;
    using IsRequiredField = quint32_le_bitfield_member<28, 1>;
    using IsCommonTypeField = quint32_le_bitfield_member<29, 1>;
    using IsListField = quint32_le_bitfield_member<30, 1>;
    using IsReadOnlyField = quint32_le_bitfield_member<31, 1>;

public:
    quint32_le_bitfield_union<NameIndexField, IsVirtualField, IsOverrideField, IsFinalField>
            nameIndexAndVirtSpecifiers;
    quint32_le_bitfield_union<
            CommonTypeOrTypeNameIndexField,
            IsRequiredField,
            IsCommonTypeField,
            IsListField,
            IsReadOnlyField> data;
    Location location;

    quint32 nameIndex() const { return nameIndexAndVirtSpecifiers.get<NameIndexField>(); }
    void setNameIndex(int nameIndex) { nameIndexAndVirtSpecifiers.set<NameIndexField>(nameIndex); }

    bool isVirtual() const { return nameIndexAndVirtSpecifiers.get<IsVirtualField>(); }
    void setIsVirtual(bool isVirtual)
    {
        nameIndexAndVirtSpecifiers.set<IsVirtualField>(isVirtual ? 1 : 0);
    }

    bool isOverride() const { return nameIndexAndVirtSpecifiers.get<IsOverrideField>(); }
    void setIsOverride(bool isOverride)
    {
        nameIndexAndVirtSpecifiers.set<IsOverrideField>(isOverride ? 1 : 0);
    }

    bool isFinal() const { return nameIndexAndVirtSpecifiers.get<IsFinalField>(); }
    void setIsFinal(bool isFinal) { nameIndexAndVirtSpecifiers.set<IsFinalField>(isFinal ? 1 : 0); }

    void setCommonType(CommonType t)
    {
        data.set<CommonTypeOrTypeNameIndexField>(static_cast<quint32>(t));
        data.set<IsCommonTypeField>(true);
    }

    CommonType commonType() const {
        if (isCommonType())
            return CommonType(data.get<CommonTypeOrTypeNameIndexField>());
        return CommonType::Invalid;
    }

    void setTypeNameIndex(int nameIndex)
    {
        data.set<CommonTypeOrTypeNameIndexField>(nameIndex);
        data.set<IsCommonTypeField>(false);
    }

    int typeNameIndex() const
    {
        return data.get<IsCommonTypeField>() ? -1 : data.get<CommonTypeOrTypeNameIndexField>();
    }

    bool isCommonType() const { return data.get<IsCommonTypeField>(); }
    uint commonTypeOrTypeNameIndex() const { return data.get<CommonTypeOrTypeNameIndexField>(); }

    bool isList() const { return data.get<IsListField>(); }
    void setIsList(bool isList) { data.set<IsListField>(isList); }

    bool isRequired() const { return data.get<IsRequiredField>(); }
    void setIsRequired(bool isRequired) { data.set<IsRequiredField>(isRequired); }

    bool isReadOnly() const { return data.get<IsReadOnlyField>(); }
    void setIsReadOnly(bool isReadOnly) { data.set<IsReadOnlyField>(isReadOnly); }
};
static_assert(sizeof(Property) == 12, "Property structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct RequiredPropertyExtraData {
    quint32_le nameIndex;
};

static_assert (sizeof(RequiredPropertyExtraData) == 4, "RequiredPropertyExtraData structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Alias
{
    bool isReadOnly() const { return m_nameIndexAndFlags.get<IsReadOnlyField>(); }
    void setIsReadOnly(bool isReadOnly) { m_nameIndexAndFlags.set<IsReadOnlyField>(isReadOnly); }

    quint32 nameIndex() const { return m_nameIndexAndFlags.get<NameIndexField>(); }
    void setNameIndex(quint32 nameIndex)
    {
        m_nameIndexAndFlags.set<NameIndexField>(nameIndex);
    }

    quint32 idIndex() const { return m_idIndexField; }
    void setIdIndex(quint32 idIndex) { m_idIndexField = idIndex; }

    quint32 propertyNameIndex() const { return m_propertyNameIndex; }
    void setPropertyNameIndex(quint32 propertyNameIndex)
    {
        m_propertyNameIndex = propertyNameIndex;
    }

    Location location() const { return m_location; }
    void setLocation(Location location) { m_location = location; }

    Location referenceLocation() const { return m_referenceLocation; }
    void setReferenceLocation(Location referenceLocation)
    {
        m_referenceLocation = referenceLocation;
    }

private:
    using NameIndexField = quint32_le_bitfield_member<0, 31>;
    using IsReadOnlyField = quint32_le_bitfield_member<31, 1>;

    quint32_le_bitfield_union<NameIndexField, IsReadOnlyField> m_nameIndexAndFlags;
    quint32_le m_idIndexField;
    quint32_le m_propertyNameIndex; // string index
    Location m_location;
    Location m_referenceLocation;
};
static_assert(sizeof(Alias) == 20, "Alias structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Object
{
private:
    using FlagsField = quint32_le_bitfield_member<0, 15>;
    using DefaultPropertyIsAliasField = quint32_le_bitfield_member<15, 1>;
    using IdField = quint32_le_bitfield_member<16, 16, qint32>;
public:
    enum Flag : unsigned int {
        NoFlag = 0x0,
        IsComponent = 0x1, // object was identified to be an explicit or implicit component boundary
        HasDeferredBindings = 0x2, // any of the bindings are deferred
        HasCustomParserBindings = 0x4,
        IsInlineComponentRoot = 0x8,
        IsPartOfInlineComponent = 0x10
    };
    Q_DECLARE_FLAGS(Flags, Flag);

    // Depending on the use, this may be the type name to instantiate before instantiating this
    // object. For grouped properties the type name will be empty and for attached properties
    // it will be the name of the attached type.
    quint32_le inheritedTypeNameIndex;
    quint32_le idNameIndex;
    quint32_le_bitfield_union<FlagsField, DefaultPropertyIsAliasField, IdField>
            flagsAndDefaultPropertyIsAliasAndId;
    qint32_le indexOfDefaultPropertyOrAlias; // -1 means no default property declared in this object
    quint16_le nFunctions;
    quint16_le nProperties;
    quint32_le offsetToFunctions;
    quint32_le offsetToProperties;
    quint32_le offsetToAliases;
    quint16_le nAliases;
    quint16_le nEnums;
    quint32_le offsetToEnums; // which in turn will be a table with offsets to variable-sized Enum objects
    quint32_le offsetToSignals; // which in turn will be a table with offsets to variable-sized Signal objects
    quint16_le nSignals;
    quint16_le nBindings;
    quint32_le offsetToBindings;
    quint32_le nNamedObjectsInComponent;
    quint32_le offsetToNamedObjectsInComponent;
    Location location;
    Location locationOfIdProperty;
    quint32_le offsetToInlineComponents;
    quint16_le nInlineComponents;
    quint32_le offsetToRequiredPropertyExtraData;
    quint16_le nRequiredPropertyExtraData;
//    Function[]
//    Property[]
//    Signal[]
//    Binding[]
//    InlineComponent[]
//    RequiredPropertyExtraData[]

    Flags flags() const
    {
        return Flags(flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>());
    }

    bool hasFlag(Flag flag) const
    {
        return flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>() & flag;
    }

    void setFlag(Flag flag)
    {
        flagsAndDefaultPropertyIsAliasAndId.set<FlagsField>(
                flagsAndDefaultPropertyIsAliasAndId.get<FlagsField>() | flag);
    }

    void setFlags(Flags flags)
    {
        flagsAndDefaultPropertyIsAliasAndId.set<FlagsField>(flags);
    }

    bool hasAliasAsDefaultProperty() const
    {
        return flagsAndDefaultPropertyIsAliasAndId.get<DefaultPropertyIsAliasField>();
    }

    void setHasAliasAsDefaultProperty(bool defaultAlias)
    {
        flagsAndDefaultPropertyIsAliasAndId.set<DefaultPropertyIsAliasField>(defaultAlias);
    }

    qint32 objectId() const
    {
        return flagsAndDefaultPropertyIsAliasAndId.get<IdField>();
    }

    void setObjectId(qint32 id)
    {
        flagsAndDefaultPropertyIsAliasAndId.set<IdField>(id);
    }


    static int calculateSizeExcludingSignalsAndEnums(int nFunctions, int nProperties, int nAliases, int nEnums, int nSignals, int nBindings, int nNamedObjectsInComponent, int nInlineComponents, int nRequiredPropertyExtraData)
    {
        return ( sizeof(Object)
                 + nFunctions * sizeof(quint32)
                 + nProperties * sizeof(Property)
                 + nAliases * sizeof(Alias)
                 + nEnums * sizeof(quint32)
                 + nSignals * sizeof(quint32)
                 + nBindings * sizeof(Binding)
                 + nNamedObjectsInComponent * sizeof(int)
                 + nInlineComponents * sizeof(InlineComponent)
                 + nRequiredPropertyExtraData * sizeof(RequiredPropertyExtraData)
                 + 0x7
               ) & ~0x7;
    }

    const quint32_le *functionOffsetTable() const
    {
        return reinterpret_cast<const quint32_le*>(reinterpret_cast<const char *>(this) + offsetToFunctions);
    }

    const Property *propertyTable() const
    {
        return reinterpret_cast<const Property*>(reinterpret_cast<const char *>(this) + offsetToProperties);
    }

    const Alias *aliasTable() const
    {
        return reinterpret_cast<const Alias*>(reinterpret_cast<const char *>(this) + offsetToAliases);
    }

    const Binding *bindingTable() const
    {
        return reinterpret_cast<const Binding*>(reinterpret_cast<const char *>(this) + offsetToBindings);
    }

    const Enum *enumAt(int idx) const
    {
        const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToEnums);
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Enum*>(reinterpret_cast<const char*>(this) + offset);
    }

    const Signal *signalAt(int idx) const
    {
        const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToSignals);
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Signal*>(reinterpret_cast<const char*>(this) + offset);
    }

    const InlineComponent *inlineComponentAt(int idx) const
    {
        return inlineComponentTable() + idx;
    }

    const quint32_le *namedObjectsInComponentTable() const
    {
        return reinterpret_cast<const quint32_le*>(reinterpret_cast<const char *>(this) + offsetToNamedObjectsInComponent);
    }

    const InlineComponent *inlineComponentTable() const
    {
        return reinterpret_cast<const InlineComponent*>(reinterpret_cast<const char *>(this) + offsetToInlineComponents);
    }

    const RequiredPropertyExtraData *requiredPropertyExtraDataAt(int idx) const
    {
        return requiredPropertyExtraDataTable() + idx;
    }

    const RequiredPropertyExtraData *requiredPropertyExtraDataTable() const
    {
        return reinterpret_cast<const RequiredPropertyExtraData*>(reinterpret_cast<const char *>(this) + offsetToRequiredPropertyExtraData);
    }

    // --- QQmlPropertyCacheCreator interface
    int propertyCount() const { return nProperties; }
    int aliasCount() const { return nAliases; }
    int enumCount() const { return nEnums; }
    int signalCount() const { return nSignals; }
    int functionCount() const { return nFunctions; }
    int inlineComponentCount() const { return nInlineComponents; }

    const Binding *bindingsBegin() const { return bindingTable(); }
    const Binding *bindingsEnd() const { return bindingTable() + nBindings; }
    int bindingCount() const { return nBindings; }

    const Property *propertiesBegin() const { return propertyTable(); }
    const Property *propertiesEnd() const { return propertyTable() + nProperties; }

    const Alias *aliasesBegin() const { return aliasTable(); }
    const Alias *aliasesEnd() const { return aliasTable() + nAliases; }

    typedef TableIterator<Enum, Object, &Object::enumAt> EnumIterator;
    EnumIterator enumsBegin() const { return EnumIterator(this, 0); }
    EnumIterator enumsEnd() const { return EnumIterator(this, nEnums); }

    typedef TableIterator<Signal, Object, &Object::signalAt> SignalIterator;
    SignalIterator signalsBegin() const { return SignalIterator(this, 0); }
    SignalIterator signalsEnd() const { return SignalIterator(this, nSignals); }

    typedef TableIterator<InlineComponent, Object, &Object::inlineComponentAt> InlineComponentIterator;
    InlineComponentIterator inlineComponentsBegin() const {return InlineComponentIterator(this, 0);}
    InlineComponentIterator inlineComponentsEnd() const {return InlineComponentIterator(this, nInlineComponents);}

    typedef TableIterator<RequiredPropertyExtraData, Object, &Object::requiredPropertyExtraDataAt> RequiredPropertyExtraDataIterator;
    RequiredPropertyExtraDataIterator requiredPropertyExtraDataBegin() const {return RequiredPropertyExtraDataIterator(this, 0); }
    RequiredPropertyExtraDataIterator requiredPropertyExtraDataEnd() const {return RequiredPropertyExtraDataIterator(this, nRequiredPropertyExtraData); }

    int namedObjectsInComponentCount() const { return nNamedObjectsInComponent; }
    // ---
};
static_assert(sizeof(Object) == 84, "Object structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct Import
{
    enum ImportType : unsigned int {
        ImportLibrary = 0x1,
        ImportFile = 0x2,
        ImportScript = 0x3,
        ImportInlineComponent = 0x4
    };
    quint32_le type;

    quint32_le uriIndex;
    quint32_le qualifierIndex;

    Location location;
    QTypeRevision version;
    quint16_le reserved;

    Import()
    {
        type = 0; uriIndex = 0; qualifierIndex = 0; version = QTypeRevision::zero(); reserved = 0;
    }
};
static_assert(sizeof(Import) == 20, "Import structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct QmlUnit
{
    quint32_le nImports;
    quint32_le offsetToImports;
    quint32_le nObjects;
    quint32_le offsetToObjects;

    const Import *importAt(int idx) const {
        return reinterpret_cast<const Import*>((reinterpret_cast<const char *>(this)) + offsetToImports + idx * sizeof(Import));
    }

    const Object *objectAt(int idx) const {
        const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToObjects);
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Object*>(reinterpret_cast<const char*>(this) + offset);
    }
};
static_assert(sizeof(QmlUnit) == 16, "QmlUnit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

static const char magic_str[] = "qv4cdata";

struct Unit
{
    // DO NOT CHANGE THESE FIELDS EVER
    char magic[8];
    quint32_le version;
    // END DO NOT CHANGE THESE FIELDS EVER

    quint32_le reserved; // For predictable alignment and size. Used to be Qt version.
    qint64_le sourceTimeStamp;
    quint32_le unitSize; // Size of the Unit and any depending data.

    char md5Checksum[16]; // checksum of all bytes following this field.
    char dependencyMD5Checksum[16];

    enum : unsigned int {
        IsJavascript = 0x1,
        StaticData = 0x2, // Unit data persistent in memory?
        IsSingleton = 0x4,
        IsSharedLibrary = 0x8, // .pragma shared?
        IsESModule = 0x10,
        PendingTypeCompilation = 0x20, // the QML data structures present are incomplete and require type compilation
        IsStrict = 0x40,
        ListPropertyAssignReplaceIfDefault = 0x80,
        ListPropertyAssignReplaceIfNotDefault = 0x100,
        ListPropertyAssignReplace
                = ListPropertyAssignReplaceIfDefault | ListPropertyAssignReplaceIfNotDefault,
        ComponentsBound = 0x200,
        FunctionSignaturesIgnored = 0x400,
        NativeMethodsAcceptThisObject = 0x800,
        ValueTypesCopied = 0x1000,
        ValueTypesAddressable = 0x2000,
        ValueTypesAssertable = 0x4000,
    };
    quint32_le flags;
    quint32_le stringTableSize;
    quint32_le offsetToStringTable;
    quint32_le functionTableSize;
    quint32_le offsetToFunctionTable;
    quint32_le classTableSize;
    quint32_le offsetToClassTable;
    quint32_le templateObjectTableSize;
    quint32_le offsetToTemplateObjectTable;
    quint32_le blockTableSize;
    quint32_le offsetToBlockTable;
    quint32_le lookupTableSize;
    quint32_le offsetToLookupTable;
    quint32_le regexpTableSize;
    quint32_le offsetToRegexpTable;
    quint32_le constantTableSize;
    quint32_le offsetToConstantTable;
    quint32_le jsClassTableSize;
    quint32_le offsetToJSClassTable;
    quint32_le translationTableSize;
    quint32_le offsetToTranslationTable;
    quint32_le localExportEntryTableSize;
    quint32_le offsetToLocalExportEntryTable;
    quint32_le indirectExportEntryTableSize;
    quint32_le offsetToIndirectExportEntryTable;
    quint32_le starExportEntryTableSize;
    quint32_le offsetToStarExportEntryTable;
    quint32_le importEntryTableSize;
    quint32_le offsetToImportEntryTable;
    quint32_le moduleRequestTableSize;
    quint32_le offsetToModuleRequestTable;
    qint32_le indexOfRootFunction;
    quint32_le sourceFileIndex;
    quint32_le finalUrlIndex;

    quint32_le offsetToQmlUnit;

    /* QML specific fields */

    const QmlUnit *qmlUnit() const {
        return reinterpret_cast<const QmlUnit *>(reinterpret_cast<const char *>(this) + offsetToQmlUnit);
    }

    QmlUnit *qmlUnit() {
        return reinterpret_cast<QmlUnit *>(reinterpret_cast<char *>(this) + offsetToQmlUnit);
    }

    bool isSingleton() const {
        return flags & Unit::IsSingleton;
    }
    /* end QML specific fields*/

    QString stringAtInternal(uint idx) const {
        Q_ASSERT(idx < stringTableSize);
        const quint32_le *offsetTable = reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToStringTable);
        const quint32_le offset = offsetTable[idx];
        const String *str = reinterpret_cast<const String*>(reinterpret_cast<const char *>(this) + offset);
        Q_ASSERT(str->size >= 0);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
        const QChar *characters = reinterpret_cast<const QChar *>(str + 1);
        if (flags & StaticData)
            return QString::fromRawData(characters, str->size);
        return QString(characters, str->size);
#else
        const quint16_le *characters = reinterpret_cast<const quint16_le *>(str + 1);
        QString qstr(str->size, Qt::Uninitialized);
        QChar *ch = qstr.data();
        for (int i = 0; i < str->size; ++i)
             ch[i] = QChar(quint16(characters[i]));
         return qstr;
#endif
    }

    const quint32_le *functionOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToFunctionTable); }
    const quint32_le *classOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToClassTable); }
    const quint32_le *templateObjectOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToTemplateObjectTable); }
    const quint32_le *blockOffsetTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToBlockTable); }

    const Function *functionAt(int idx) const {
        const quint32_le *offsetTable = functionOffsetTable();
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Function*>(reinterpret_cast<const char *>(this) + offset);
    }

    const Class *classAt(int idx) const {
        const quint32_le *offsetTable = classOffsetTable();
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Class *>(reinterpret_cast<const char *>(this) + offset);
    }

    const TemplateObject *templateObjectAt(int idx) const {
        const quint32_le *offsetTable = templateObjectOffsetTable();
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const TemplateObject *>(reinterpret_cast<const char *>(this) + offset);
    }

    const Block *blockAt(int idx) const {
        const quint32_le *offsetTable = blockOffsetTable();
        const quint32_le offset = offsetTable[idx];
        return reinterpret_cast<const Block *>(reinterpret_cast<const char *>(this) + offset);
    }

    const Lookup *lookupTable() const { return reinterpret_cast<const Lookup*>(reinterpret_cast<const char *>(this) + offsetToLookupTable); }
    const RegExp *regexpAt(int index) const {
        return reinterpret_cast<const RegExp*>(reinterpret_cast<const char *>(this) + offsetToRegexpTable + index * sizeof(RegExp));
    }
    const quint64_le *constants() const {
        return reinterpret_cast<const quint64_le*>(reinterpret_cast<const char *>(this) + offsetToConstantTable);
    }

    const JSClassMember *jsClassAt(int idx, int *nMembers) const {
        const quint32_le *offsetTable = reinterpret_cast<const quint32_le *>(reinterpret_cast<const char *>(this) + offsetToJSClassTable);
        const quint32_le offset = offsetTable[idx];
        const char *ptr = reinterpret_cast<const char *>(this) + offset;
        const JSClass *klass = reinterpret_cast<const JSClass *>(ptr);
        *nMembers = klass->nMembers;
        return reinterpret_cast<const JSClassMember*>(ptr + sizeof(JSClass));
    }

    const TranslationData *translations() const {
        return reinterpret_cast<const TranslationData *>(reinterpret_cast<const char *>(this) + offsetToTranslationTable);
    }

    const quint32_le *translationContextIndex() const{
        if ( translationTableSize == 0)
            return nullptr;
        return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this))
                                                    + offsetToTranslationTable
                                                    + translationTableSize * sizeof(CompiledData::TranslationData)); }

    quint32_le *translationContextIndex() {
        if ( translationTableSize == 0)
            return nullptr;
        return reinterpret_cast<quint32_le*>((reinterpret_cast<char *>(this))
                                                    + offsetToTranslationTable
                                                    + translationTableSize * sizeof(CompiledData::TranslationData)); }

    const ImportEntry *importEntryTable() const { return reinterpret_cast<const ImportEntry *>(reinterpret_cast<const char *>(this) + offsetToImportEntryTable); }
    const ExportEntry *localExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToLocalExportEntryTable); }
    const ExportEntry *indirectExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToIndirectExportEntryTable); }
    const ExportEntry *starExportEntryTable() const { return reinterpret_cast<const ExportEntry *>(reinterpret_cast<const char *>(this) + offsetToStarExportEntryTable); }

    const quint32_le *moduleRequestTable() const { return reinterpret_cast<const quint32_le*>((reinterpret_cast<const char *>(this)) + offsetToModuleRequestTable); }

    bool verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) const;
};

static_assert(sizeof(Unit) == 200, "Unit structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target");

struct TypeReference
{
    TypeReference(const Location &loc)
        : location(loc)
        , needsCreation(false)
        , errorWhenNotFound(false)
    {}
    Location location; // first use
    bool needsCreation : 1; // whether the type needs to be creatable or not
    bool errorWhenNotFound: 1;
};

// Map from name index to location of first use.
struct TypeReferenceMap : QHash<int, TypeReference>
{
    TypeReference &add(int nameIndex, const Location &loc) {
        Iterator it = find(nameIndex);
        if (it != end())
            return *it;
        return *insert(nameIndex, loc);
    }

    template <typename CompiledObject>
    void collectFromObject(const CompiledObject *obj)
    {
        if (obj->inheritedTypeNameIndex != 0) {
            TypeReference &r = this->add(obj->inheritedTypeNameIndex, obj->location);
            r.needsCreation = true;
            r.errorWhenNotFound = true;
        }

        auto binding = obj->bindingsBegin();
        auto const bindingEnd = obj->bindingsEnd();
        for ( ; binding != bindingEnd; ++binding) {
            if (binding->type() == QV4::CompiledData::Binding::Type_AttachedProperty)
                this->add(binding->propertyNameIndex, binding->location);
        }

        auto ic = obj->inlineComponentsBegin();
        auto const icEnd = obj->inlineComponentsEnd();
        for (; ic != icEnd; ++ic) {
            this->add(ic->nameIndex, ic->location);
        }
    }

    template <typename Iterator>
    void collectFromObjects(Iterator it, Iterator end)
    {
        for (; it != end; ++it)
            collectFromObject(*it);
    }
};

using DependentTypesHasher = std::function<QByteArray()>;

struct InlineComponentData {

    InlineComponentData() = default;
    InlineComponentData(const QQmlType &qmlType, int objectIndex, int nameIndex)
        : qmlType(qmlType)
        , objectIndex(objectIndex)
        , nameIndex(nameIndex)
    {}

    QQmlType qmlType;
    int objectIndex = -1;
    int nameIndex = -1;
};

struct CompilationUnit final : public QQmlRefCounted<CompilationUnit>
{
    Q_DISABLE_COPY_MOVE(CompilationUnit)

    const Unit *data = nullptr;
    const QmlUnit *qmlData = nullptr;
    QStringList dynamicStrings;
    const QQmlPrivate::AOTCompiledFunction *aotCompiledFunctions = nullptr;
    LookupValidationFn validateLookupSignatures = nullptr;

    // pointers either to data->constants() or little-endian memory copy.
    const StaticValue *constants = nullptr;

    std::unique_ptr<CompilationUnitMapper> backingFile;

    QHash<QString, InlineComponentData> inlineComponentData;

    // index is object index. This allows fast access to the
    // property data when initializing bindings, avoiding expensive
    // lookups by string (property name).
    QList<BindingPropertyData> bindingPropertyDataPerObject;

    ResolvedTypeReferenceMap resolvedTypes;
    QQmlRefPointer<QQmlTypeNameCache> typeNameCache;

    QQmlPropertyCacheVector propertyCaches;

    QQmlType qmlType;

    QList<QQmlRefPointer<QQmlScriptData>> dependentScripts;

public:
    // --- interface for QQmlPropertyCacheCreator
    using CompiledObject = const CompiledData::Object;
    using CompiledFunction = const CompiledData::Function;
    using CompiledBinding = const CompiledData::Binding;

    // Empty dummy. We don't need to do this when loading from cache.
    class IdToObjectMap
    {
    public:
        void insert(int, int) {}
        void clear() {}

        // We have already checked uniqueness of IDs when creating the CU
        bool contains(int) { return false; }
    };

    explicit CompilationUnit(const Unit *unitData,
                             const QQmlPrivate::AOTCompiledFunction *aotCompiledFunctions,
                             LookupValidationFn validateLookupSignatures,
                             const QString &fileName = QString(),
                             const QString &finalUrlString = QString())
        : CompilationUnit(unitData, fileName, finalUrlString)
    {
        this->aotCompiledFunctions = aotCompiledFunctions;
        this->validateLookupSignatures = validateLookupSignatures;
    }

    Q_QML_EXPORT CompilationUnit(
            const Unit *unitData = nullptr, const QString &fileName = QString(),
            const QString &finalUrlString = QString());

    Q_QML_EXPORT ~CompilationUnit();

    const Unit *unitData() const { return data; }

    void setUnitData(const Unit *unitData, const QmlUnit *qmlUnit = nullptr,
                     const QString &fileName = QString(), const QString &finalUrlString = QString())
    {
        data = unitData;
        qmlData = nullptr;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        delete [] constants;
#endif
        constants = nullptr;
        m_fileName.clear();
        m_finalUrlString.clear();
        if (!data)
            return;

        qmlData = qmlUnit ? qmlUnit : data->qmlUnit();

#if Q_BYTE_ORDER == Q_BIG_ENDIAN
        StaticValue *bigEndianConstants = new StaticValue[data->constantTableSize];
        const quint64_le *littleEndianConstants = data->constants();
        for (uint i = 0; i < data->constantTableSize; ++i)
            bigEndianConstants[i] = StaticValue::fromReturnedValue(littleEndianConstants[i]);
        constants = bigEndianConstants;
#else
        constants = reinterpret_cast<const StaticValue*>(data->constants());
#endif

        m_fileName = !fileName.isEmpty() ? fileName : stringAt(data->sourceFileIndex);
        m_finalUrlString = !finalUrlString.isEmpty() ? finalUrlString : stringAt(data->finalUrlIndex);
    }

    QString stringAt(uint index) const
    {
        if (index < data->stringTableSize)
            return data->stringAtInternal(index);

        const qsizetype dynamicIndex = index - data->stringTableSize;
        Q_ASSERT(dynamicIndex < dynamicStrings.size());
        return dynamicStrings.at(dynamicIndex);
    }

    QString fileName() const { return m_fileName; }
    QString finalUrlString() const { return m_finalUrlString; }

    QString bindingValueAsString(const CompiledData::Binding *binding) const
    {
        using namespace CompiledData;
        switch (binding->type()) {
        case Binding::Type_Script:
        case Binding::Type_String:
            return stringAt(binding->stringIndex);
        case Binding::Type_Null:
            return QStringLiteral("null");
        case Binding::Type_Boolean:
            return binding->value.b ? QStringLiteral("true") : QStringLiteral("false");
        case Binding::Type_Number:
            return QString::number(bindingValueAsNumber(binding), 'g', QLocale::FloatingPointShortest);
        case Binding::Type_Invalid:
            return QString();
        case Binding::Type_TranslationById:
        case Binding::Type_Translation:
            return stringAt(data->translations()[binding->value.translationDataIndex].stringIndex);
        default:
            break;
        }
        return QString();
    }

    QString bindingValueAsScriptString(const CompiledData::Binding *binding) const
    {
        return (binding->type() == CompiledData::Binding::Type_String)
                ? CompiledData::Binding::escapedString(stringAt(binding->stringIndex))
                : bindingValueAsString(binding);
    }

    double bindingValueAsNumber(const CompiledData::Binding *binding) const
    {
        if (binding->type() != CompiledData::Binding::Type_Number)
            return 0.0;
        return constants[binding->value.constantValueIndex].doubleValue();
    }

    Q_QML_EXPORT static QString localCacheFilePath(const QUrl &url);
    Q_QML_EXPORT bool loadFromDisk(
            const QUrl &url, const QDateTime &sourceTimeStamp, QString *errorString);
    Q_QML_EXPORT bool saveToDisk(const QUrl &unitUrl, QString *errorString) const;

    int importCount() const { return qmlData->nImports; }
    const CompiledData::Import *importAt(int index) const { return qmlData->importAt(index); }

    Q_QML_EXPORT QStringList moduleRequests() const;

    // url() and fileName() shall be used to load the actual QML/JS code or to show errors or
    // warnings about that code. They include any potential URL interceptions and thus represent the
    // "physical" location of the code.
    //
    // finalUrl() and finalUrlString() shall be used to resolve further URLs referred to in the code
    // They are _not_ intercepted and thus represent the "logical" name for the code.

    QUrl url() const
    {
        if (!m_url.isValid())
            m_url = QUrl(fileName());
        return m_url;
    }

    QUrl finalUrl() const
    {
        if (!m_finalUrl.isValid())
            m_finalUrl = QUrl(finalUrlString());
        return m_finalUrl;
    }

    ResolvedTypeReference *resolvedType(int id) const { return resolvedTypes.value(id); }
    ResolvedTypeReference *resolvedType(QMetaType type) const;

    QQmlPropertyCache::ConstPtr rootPropertyCache() const
    {
        return propertyCaches.isEmpty()
                ? QQmlPropertyCache::ConstPtr()
                : propertyCaches.at(/*root object*/0);
    }

    int objectCount() const { return qmlData->nObjects; }

    // Resolves implicit component wrapper indices (>= objectCount()) to the
    // wrapped child object index.  Returns the index unchanged for real objects.
    int resolvedIndex(int index) const
    {
        if (index < objectCount())
            return index;
        const QQmlPropertyData *p = propertyCaches.at(index)->defaultProperty();
        Q_ASSERT(p && p->isComponentWrapper());
        return p->wrappedObjectIndex();
    }

    const CompiledObject *objectAt(int index) const
    {
        return qmlData->objectAt(resolvedIndex(index));
    }

    // Returns the index of the implicit component wrapper, or -1.
    // Scans property caches at indices >= objectCount().
    int implicitComponentForObject(int index) const
    {
        for (int i = objectCount(), end = propertyCaches.count(); i < end; ++i) {
            const QQmlPropertyData *p = propertyCaches.at(i)->defaultProperty();
            Q_ASSERT(p && p->isComponentWrapper());
            if (p->wrappedObjectIndex() == index)
                return i;
        }
        return -1;
    }

    int inlineComponentId(const QString &inlineComponentName) const
    {
        for (uint i = 0; i < qmlData->nObjects; ++i) {
            auto *object = qmlData->objectAt(i);
            for (auto it = object->inlineComponentsBegin(), end = object->inlineComponentsEnd();
                 it != end; ++it) {
                if (stringAt(it->nameIndex) == inlineComponentName)
                    return it->objectIndex;
            }
        }
        return -1;
    }

    void finalizeCompositeType(const QQmlType &type);

    bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const;

    enum class ListPropertyAssignBehavior { Append, Replace, ReplaceIfNotDefault };
    ListPropertyAssignBehavior listPropertyAssignBehavior() const
    {
        if (unitData()->flags & CompiledData::Unit::ListPropertyAssignReplace)
            return ListPropertyAssignBehavior::Replace;
        if (unitData()->flags & CompiledData::Unit::ListPropertyAssignReplaceIfNotDefault)
            return ListPropertyAssignBehavior::ReplaceIfNotDefault;
        return ListPropertyAssignBehavior::Append;
    }

    bool ignoresFunctionSignature() const
    {
        return unitData()->flags & CompiledData::Unit::FunctionSignaturesIgnored;
    }

    bool nativeMethodsAcceptThisObjects() const
    {
        return unitData()->flags & CompiledData::Unit::NativeMethodsAcceptThisObject;
    }

    bool valueTypesAreCopied() const
    {
        return unitData()->flags & CompiledData::Unit::ValueTypesCopied;
    }

    bool valueTypesAreAddressable() const
    {
        return unitData()->flags & CompiledData::Unit::ValueTypesAddressable;
    }

    bool valueTypesAreAssertable() const
    {
        return unitData()->flags & CompiledData::Unit::ValueTypesAssertable;
    }

    bool componentsAreBound() const
    {
        return unitData()->flags & CompiledData::Unit::ComponentsBound;
    }

    bool isESModule() const
    {
        return unitData()->flags & CompiledData::Unit::IsESModule;
    }

    bool isSharedLibrary() const
    {
        return unitData()->flags & CompiledData::Unit::IsSharedLibrary;
    }

    struct FunctionIterator
    {
        FunctionIterator(const CompiledData::Unit *unit, const CompiledObject *object, int index)
            : unit(unit), object(object), index(index) {}
        const CompiledData::Unit *unit;
        const CompiledObject *object;
        int index;

        const CompiledFunction *operator->() const
        {
            return unit->functionAt(object->functionOffsetTable()[index]);
        }

        void operator++() { ++index; }
        bool operator==(const FunctionIterator &rhs) const { return index == rhs.index; }
        bool operator!=(const FunctionIterator &rhs) const { return index != rhs.index; }
    };

    FunctionIterator objectFunctionsBegin(const CompiledObject *object) const
    {
        return FunctionIterator(unitData(), object, 0);
    }

    FunctionIterator objectFunctionsEnd(const CompiledObject *object) const
    {
        return FunctionIterator(unitData(), object, object->nFunctions);
    }

    QQmlType qmlTypeForComponent(const QString &inlineComponentName = QString()) const;
    QMetaType metaType() const { return qmlType.typeId(); }

private:
    QString m_fileName; // initialized from data->sourceFileIndex
    QString m_finalUrlString; // initialized from data->finalUrlIndex

    mutable QQmlNullableValue<QUrl> m_url;
    mutable QQmlNullableValue<QUrl> m_finalUrl;
};

class SaveableUnitPointer
{
    Q_DISABLE_COPY_MOVE(SaveableUnitPointer)
public:
    SaveableUnitPointer(const Unit *unit, quint32 temporaryFlags = Unit::StaticData) :
          unit(unit),
          temporaryFlags(temporaryFlags)
    {
    }

    ~SaveableUnitPointer() = default;

    template<typename Char>
    bool saveToDisk(const std::function<bool(const Char *, quint32)> &writer) const
    {
        const quint32_le oldFlags = mutableFlags();
        auto cleanup = qScopeGuard([this, oldFlags]() { mutableFlags() = oldFlags; });
        mutableFlags() |= temporaryFlags;
        return writer(data<Char>(), size());
    }

    static bool writeDataToFile(const QString &outputFileName, const char *data, quint32 size,
                                QString *errorString)
    {
#if QT_CONFIG(temporaryfile)
        QSaveFile cacheFile(outputFileName);
        if (!cacheFile.open(QIODevice::WriteOnly | QIODevice::Truncate)
                || cacheFile.write(data, size) != size
                || !cacheFile.commit()) {
            *errorString = cacheFile.errorString();
            return false;
        }

        errorString->clear();
        return true;
#else
        Q_UNUSED(outputFileName);
        *errorString = QStringLiteral("features.temporaryfile is disabled.");
        return false;
#endif
    }

private:
    const Unit *unit;
    quint32 temporaryFlags;

    quint32_le &mutableFlags() const
    {
        return const_cast<Unit *>(unit)->flags;
    }

    template<typename Char>
    const Char *data() const
    {
        Q_STATIC_ASSERT(sizeof(Char) == 1);
        const Char *dataPtr;
        memcpy(&dataPtr, &unit, sizeof(dataPtr));
        return dataPtr;
    }

    quint32 size() const
    {
        return unit->unitSize;
    }
};


} // CompiledData namespace
} // QV4 namespace

Q_DECLARE_OPERATORS_FOR_FLAGS(QV4::CompiledData::ParameterType::Flags);
Q_DECLARE_TYPEINFO(QV4::CompiledData::JSClassMember, Q_PRIMITIVE_TYPE);

QT_END_NAMESPACE

#endif
