/*
 * api.c: a libFuzzer target to test node-related API functions.
 *
 * See Copyright for the status of this software.
 *
 * This is a simple virtual machine which runs fuzz data as a program.
 * An important design goal is to execute as many API calls as possible
 * per input byte.
 *
 * We use a fixed number of registers for basic types like integers
 * or strings as well as libxml2 objects like xmlNode. The opcodes are
 * single bytes which typically result in a call to an API function
 * using the freshest registers for each argument type and storing the
 * result in the stalest register. This can be implemented using a ring
 * buffer.
 *
 * There are a few other opcodes to initialize or duplicate registers,
 * so all kinds of API calls can potentially be generated from fuzz
 * data.
 *
 * This architecture is similar to stack machine and benefits from
 * great code density. The main difference is that values aren't
 * destroyed when popping arguments from the stack and that the bottom
 * of the stack is eventually overwritten if the ring buffer overflows.
 *
 * The main complication is memory management of nodes. Whenever a
 * reference between two nodes is removed, whether by an API call or
 * the VM clearing a register, we must check whether this leaves
 * unreferenced nodes which can then be freed. There are no opcodes
 * to free a node explicitly. The FIFO patterns generated by
 * overflowing the ring buffer and freeing the registers at the end of
 * a program seem to do a good enough job.
 */

#include <stdlib.h>
#include <string.h>

#ifndef XML_DEPRECATED
  #define XML_DEPRECATED
#endif

#include <libxml/catalog.h>
#include <libxml/HTMLtree.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlerror.h>
#include "fuzz.h"

#if 0
  #define DEBUG printf
#else
  #define DEBUG(...)
#endif

#define MAX_CONTENT     100
#define MAX_COPY_NODES   50
#define MAX_COPY_OPS     20

typedef enum {
    /* Basic operations */
    OP_CREATE_INTEGER,
    OP_CREATE_STRING,
    OP_DUP_INTEGER,
    OP_DUP_STRING,
    OP_DUP_NODE,

    /*** tree.h ***/

    /* Tree constructors */
    OP_XML_NEW_DOC,
    OP_XML_NEW_NODE,
    OP_XML_NEW_NODE_EAT_NAME,
    OP_XML_NEW_DOC_NODE,
    OP_XML_NEW_DOC_NODE_EAT_NAME,
    OP_XML_NEW_DOC_RAW_NODE,
    OP_XML_NEW_CHILD,
    OP_XML_NEW_TEXT_CHILD,
    OP_XML_NEW_PROP,
    OP_XML_NEW_DOC_PROP,
    OP_XML_NEW_NS_PROP,
    OP_XML_NEW_NS_PROP_EAT_NAME,
    OP_XML_NEW_TEXT,
    OP_XML_NEW_TEXT_LEN,
    OP_XML_NEW_DOC_TEXT,
    OP_XML_NEW_DOC_TEXT_LEN,
    OP_XML_NEW_PI,
    OP_XML_NEW_DOC_PI,
    OP_XML_NEW_COMMENT,
    OP_XML_NEW_DOC_COMMENT,
    OP_XML_NEW_CDATA_BLOCK,
    OP_XML_NEW_CHAR_REF,
    OP_XML_NEW_REFERENCE,
    OP_XML_NEW_DOC_FRAGMENT,
    OP_XML_CREATE_INT_SUBSET,
    OP_XML_NEW_DTD,

    /* Node copying */
    OP_XML_COPY_DOC,
    OP_XML_COPY_NODE,
    OP_XML_COPY_NODE_LIST,
    OP_XML_DOC_COPY_NODE,
    OP_XML_DOC_COPY_NODE_LIST,
    OP_XML_COPY_PROP,
    OP_XML_COPY_PROP_LIST,
    OP_XML_COPY_DTD,

    /* Node accessors */
    OP_NODE_PARENT,
    OP_NODE_NEXT_SIBLING,
    OP_NODE_PREV_SIBLING,
    OP_NODE_FIRST_CHILD,
    OP_XML_GET_LAST_CHILD,
    OP_NODE_NAME,
    OP_XML_NODE_SET_NAME,
    OP_XML_NODE_GET_CONTENT,
    OP_XML_NODE_SET_CONTENT,
    OP_XML_NODE_SET_CONTENT_LEN,
    OP_XML_NODE_ADD_CONTENT,
    OP_XML_NODE_ADD_CONTENT_LEN,
    OP_XML_GET_INT_SUBSET,
    OP_XML_GET_LINE_NO,
    OP_XML_GET_NODE_PATH,
    OP_XML_DOC_GET_ROOT_ELEMENT,
    OP_XML_DOC_SET_ROOT_ELEMENT,
    OP_XML_NODE_IS_TEXT,
    OP_XML_NODE_GET_ATTR_VALUE,
    OP_XML_NODE_GET_LANG,
    OP_XML_NODE_SET_LANG,
    OP_XML_NODE_GET_SPACE_PRESERVE,
    OP_XML_NODE_SET_SPACE_PRESERVE,
    OP_XML_NODE_GET_BASE,
    OP_XML_NODE_GET_BASE_SAFE,
    OP_XML_NODE_SET_BASE,
    OP_XML_IS_BLANK_NODE,

    /* Attributes */
    OP_XML_HAS_PROP,
    OP_XML_HAS_NS_PROP,
    OP_XML_GET_PROP,
    OP_XML_GET_NS_PROP,
    OP_XML_GET_NO_NS_PROP,
    OP_XML_SET_PROP,
    OP_XML_SET_NS_PROP,
    OP_XML_REMOVE_PROP,
    OP_XML_UNSET_PROP,
    OP_XML_UNSET_NS_PROP,

    /* Namespaces */
    OP_XML_NEW_NS,
    OP_XML_SEARCH_NS,
    OP_XML_SEARCH_NS_BY_HREF,
    OP_XML_GET_NS_LIST,
    OP_XML_GET_NS_LIST_SAFE,
    OP_XML_SET_NS,
    OP_XML_COPY_NAMESPACE,
    OP_XML_COPY_NAMESPACE_LIST,

    /* Tree manipulation */
    OP_XML_UNLINK_NODE,
    OP_XML_ADD_CHILD,
    OP_XML_ADD_CHILD_LIST,
    OP_XML_REPLACE_NODE,
    OP_XML_ADD_SIBLING,
    OP_XML_ADD_PREV_SIBLING,
    OP_XML_ADD_NEXT_SIBLING,

    /* String output */
    OP_XML_DOC_DUMP_MEMORY,
    OP_XML_DOC_DUMP_MEMORY_ENC,
    OP_XML_DOC_DUMP_FORMAT_MEMORY,
    OP_XML_DOC_DUMP_FORMAT_MEMORY_ENC,

    /* FILE output, TODO, use fmemopen */
    OP_XML_DOC_DUMP,
    OP_XML_DOC_FORMAT_DUMP,
    OP_XML_ELEM_DUMP,

    /* xmlBuf output, TODO, no public API */
    OP_XML_BUF_NODE_DUMP,
    OP_XML_BUF_GET_NODE_CONTENT,

    /* xmlBuffer output */
    OP_XML_NODE_DUMP,
    OP_XML_NODE_BUF_GET_CONTENT,
    OP_XML_ATTR_SERIALIZE_TXT_CONTENT,
    OP_XML_DUMP_ELEMENT_DECL,
    OP_XML_DUMP_ELEMENT_TABLE,
    OP_XML_DUMP_ATTRIBUTE_DECL,
    OP_XML_DUMP_ATTRIBUTE_TABLE,
    OP_XML_DUMP_NOTATION_DECL,
    OP_XML_DUMP_NOTATION_TABLE,
    OP_XML_DUMP_ENTITY_DECL,
    OP_XML_DUMP_ENTITIES_TABLE,

    /* xmlOutputBuffer */
    OP_XML_SAVE_FILE_TO,
    OP_XML_SAVE_FORMAT_FILE_TO,
    OP_XML_NODE_DUMP_OUTPUT,

    /* Misc */
    OP_XML_TEXT_MERGE,
    OP_XML_TEXT_CONCAT,
    OP_XML_STRING_GET_NODE_LIST,
    OP_XML_STRING_LEN_GET_NODE_LIST,
    OP_XML_NODE_LIST_GET_STRING,
    OP_XML_NODE_LIST_GET_RAW_STRING,
    OP_XML_IS_XHTML,

    /* DOM */
    OP_XML_DOM_WRAP_RECONCILE_NAMESPACES,
    OP_XML_DOM_WRAP_ADOPT_NODE,
    OP_XML_DOM_WRAP_REMOVE_NODE,
    OP_XML_DOM_WRAP_CLONE_NODE,
    OP_XML_CHILD_ELEMENT_COUNT,
    OP_XML_FIRST_ELEMENT_CHILD,
    OP_XML_LAST_ELEMENT_CHILD,
    OP_XML_NEXT_ELEMENT_SIBLING,
    OP_XML_PREVIOUS_ELEMENT_SIBLING,

    /*** parser.h ***/

    OP_PARSE_DOCUMENT,

    /*** valid.h ***/

    OP_XML_ADD_ELEMENT_DECL,
    OP_XML_ADD_ATTRIBUTE_DECL,
    OP_XML_ADD_NOTATION_DECL,

    OP_XML_GET_DTD_ELEMENT_DESC,
    OP_XML_GET_DTD_QELEMENT_DESC,
    OP_XML_GET_DTD_ATTR_DESC,
    OP_XML_GET_DTD_QATTR_DESC,
    OP_XML_GET_DTD_NOTATION_DESC,

    OP_XML_ADD_ID,
    OP_XML_ADD_ID_SAFE,
    OP_XML_GET_ID,
    OP_XML_IS_ID,
    OP_XML_REMOVE_ID,

    OP_XML_ADD_REF,
    OP_XML_GET_REFS,
    OP_XML_IS_REF,
    OP_XML_REMOVE_REF,

    OP_XML_IS_MIXED_ELEMENT,

    OP_VALIDATE,
    OP_XML_VALIDATE_ATTRIBUTE_VALUE,
    OP_XML_VALIDATE_DTD,
    OP_XML_VALIDATE_NOTATION_USE,

    OP_XML_VALIDATE_NAME_VALUE,
    OP_XML_VALIDATE_NAMES_VALUE,
    OP_XML_VALIDATE_NMTOKEN_VALUE,
    OP_XML_VALIDATE_NMTOKENS_VALUE,

    OP_XML_VALID_NORMALIZE_ATTRIBUTE_VALUE,
    OP_XML_VALID_CTXT_NORMALIZE_ATTRIBUTE_VALUE,
    OP_XML_VALID_GET_POTENTIAL_CHILDREN,
    OP_XML_VALID_GET_VALID_ELEMENTS,

    /*** entities.h ***/

    OP_XML_NEW_ENTITY,
    OP_XML_ADD_ENTITY,
    OP_XML_ADD_DOC_ENTITY,
    OP_XML_ADD_DTD_ENTITY,

    OP_XML_GET_PREDEFINED_ENTITY,
    OP_XML_GET_DOC_ENTITY,
    OP_XML_GET_DTD_ENTITY,
    OP_XML_GET_PARAMETER_ENTITY,

    OP_XML_ENCODE_ENTITIES_REENTRANT,
    OP_XML_ENCODE_SPECIAL_CHARS,

    /*** HTMLtree.h ***/

    OP_HTML_NEW_DOC,
    OP_HTML_NEW_DOC_NO_DTD,
    OP_HTML_GET_META_ENCODING,
    OP_HTML_SET_META_ENCODING,
    OP_HTML_IS_BOOLEAN_ATTR,

    OP_HTML_DOC_DUMP_MEMORY,
    OP_HTML_DOC_DUMP_MEMORY_FORMAT,
    OP_HTML_DOC_DUMP,
    OP_HTML_NODE_DUMP_FILE,
    OP_HTML_NODE_DUMP_FILE_FORMAT,
    OP_HTML_NODE_DUMP,
    OP_HTML_DOC_CONTENT_DUMP_OUTPUT,
    OP_HTML_DOC_CONTENT_DUMP_FORMAT_OUTPUT,
    OP_HTML_NODE_DUMP_OUTPUT,
    OP_HTML_NODE_DUMP_FORMAT_OUTPUT,

    OP_MAX
} opType;

#define NODE_MASK_TEXT_CONTENT ( \
    (1 << XML_TEXT_NODE) | \
    (1 << XML_CDATA_SECTION_NODE) | \
    (1 << XML_COMMENT_NODE) | \
    (1 << XML_PI_NODE))

#define CHILD_MASK_DOCUMENT ( \
    (1 << XML_ELEMENT_NODE) | \
    (1 << XML_PI_NODE) | \
    (1 << XML_COMMENT_NODE))

#define CHILD_MASK_CONTENT ( \
    (1 << XML_ELEMENT_NODE) | \
    (1 << XML_TEXT_NODE) | \
    (1 << XML_CDATA_SECTION_NODE) | \
    (1 << XML_ENTITY_REF_NODE) | \
    (1 << XML_PI_NODE) | \
    (1 << XML_COMMENT_NODE))

#define CHILD_MASK_ELEMENT ( \
    CHILD_MASK_CONTENT | \
    (1 << XML_ATTRIBUTE_NODE))

#define CHILD_MASK_ATTRIBUTE ( \
    (1 << XML_TEXT_NODE) | \
    (1 << XML_ENTITY_REF_NODE))

#define CHILD_MASK_DTD ( \
    (1 << XML_ELEMENT_DECL) | \
    (1 << XML_ATTRIBUTE_DECL) | \
    (1 << XML_ENTITY_DECL))

static const int childMasks[] = {
    0,
    CHILD_MASK_ELEMENT, /* XML_ELEMENT_NODE */
    CHILD_MASK_ATTRIBUTE, /* XML_ATTRIBUTE_NODE */
    0, /* XML_TEXT_NODE */
    0, /* XML_CDATA_SECTION_NODE */
    0, /* XML_ENTITY_REF_NODE */
    0, /* XML_ENTITY_NODE */
    0, /* XML_PI_NODE */
    0, /* XML_COMMENT_NODE */
    CHILD_MASK_DOCUMENT, /* XML_DOCUMENT_NODE */
    0, /* XML_DOCUMENT_TYPE_NODE */
    CHILD_MASK_CONTENT, /* XML_DOCUMENT_FRAG_NODE */
    0, /* XML_NOTATION_NODE */
    CHILD_MASK_DOCUMENT, /* XML_HTML_DOCUMENT_NODE */
    0, /* XML_DTD_NODE */
    0, /* XML_ELEMENT_DECL */
    0, /* XML_ATTRIBUTE_DECL */
    0, /* XML_ENTITY_DECL */
    0, /* XML_NAMESPACE_DECL */
    0, /* XML_XINCLUDE_START */
    0, /* XML_XINCLUDE_END */
    CHILD_MASK_DOCUMENT /* XML_DOCB_DOCUMENT_NODE */
};

#define REG_MAX 8
#define REG_MASK (REG_MAX - 1)

typedef struct {
    /* Indexes point beyond the most recent item */
    int intIdx;
    int stringIdx;
    int nodeIdx;

    int numCopyOps;

    const char *opName;

    /* Registers */
    int integers[REG_MAX];
    xmlChar *strings[REG_MAX];
    xmlNodePtr nodes[REG_MAX];
} xmlFuzzApiVars;

static xmlFuzzApiVars varsStruct;
static xmlFuzzApiVars *const vars = &varsStruct;

/* Debug output */

static void
startOp(const char *name) {
    vars->opName = name;
    DEBUG("%s(", name);
}

static void
endOp(void) {
    DEBUG(" )\n");
}

/* Integers */

static int
getInt(int offset) {
    int idx = (vars->intIdx - offset - 1) & REG_MASK;
    DEBUG(" %d", vars->integers[idx]);
    return vars->integers[idx];
}

static void
setInt(int offset, int n) {
    int idx = (vars->intIdx - offset - 1) & REG_MASK;
    vars->integers[idx] = n;
}

static void
incIntIdx(void) {
    vars->intIdx = (vars->intIdx + 1) & REG_MASK;
}

/* Strings */

static const xmlChar *
getStr(int offset) {
    int idx = (vars->stringIdx - offset - 1) & REG_MASK;
    const xmlChar *str = vars->strings[idx];

    if (str == NULL)
        DEBUG(" NULL");
    else
        DEBUG(" \"%.20s\"", str);

    return str;
}

static const char *
getCStr(int offset) {
    return (const char *) getStr(offset);
}

static void
setStr(int offset, xmlChar *str) {
    xmlChar **strings = vars->strings;
    int idx = (vars->stringIdx - offset - 1) & REG_MASK;
    xmlChar *oldString = strings[idx];

    strings[idx] = str;
    if (oldString)
        xmlFree(oldString);
}

static void
moveStr(int offset, xmlChar *str) {
    if (xmlStrlen(str) > 1000) {
        setStr(offset, NULL);
        xmlFree(str);
    } else {
        setStr(offset, str);
    }
}

/*
 * This doesn't use xmlMalloc and can't fail because of malloc failure
 * injection.
 */
static xmlChar *
uncheckedStrndup(const xmlChar *str, int size) {
    xmlChar *copy;

    if (str == NULL)
        return NULL;

    copy = BAD_CAST strndup((const char *) str, size);
    if (copy == NULL) {
        fprintf(stderr, "out of memory\n");
        abort();
    }

    return copy;
}

static xmlChar *
uncheckedStrdup(const xmlChar *str) {
    return uncheckedStrndup(str, MAX_CONTENT);
}

static void
copyStr(int offset, const xmlChar *str) {
    setStr(offset, uncheckedStrdup(str));
}

static void
incStrIdx(void) {
    vars->stringIdx = (vars->stringIdx + 1) & REG_MASK;
}

/* Nodes */

static void
dropNode(xmlNodePtr node);

static xmlNodePtr
getNode(int offset) {
    int idx = (vars->nodeIdx - offset - 1) & REG_MASK;
    if (vars->nodes[idx])
        DEBUG(" n%d", idx);
    else
        DEBUG(" NULL");
    fflush(stdout);
    return vars->nodes[idx];
}

static xmlDocPtr
getDoc(int offset) {
    xmlNodePtr node = getNode(offset);

    if (node == NULL)
        return NULL;
    return node->doc;
}

static xmlAttrPtr
getAttr(int offset) {
    xmlNodePtr node = getNode(offset);

    if (node == NULL)
        return NULL;
    if (node->type == XML_ATTRIBUTE_NODE)
        return (xmlAttrPtr) node;
    if (node->type == XML_ELEMENT_NODE)
        return node->properties;

    return NULL;
}

static xmlDtdPtr
getDtd(int offset) {
    xmlNodePtr node = getNode(offset);
    xmlDocPtr doc;

    if (node == NULL)
        return NULL;

    if (node->type == XML_DTD_NODE)
        return (xmlDtdPtr) node;

    doc = node->doc;
    if (doc == NULL)
        return NULL;
    if (doc->intSubset != NULL)
        return doc->intSubset;
    return doc->extSubset;
}

static void
setNode(int offset, xmlNodePtr node) {
    int idx = (vars->nodeIdx - offset - 1) & REG_MASK;
    xmlNodePtr oldNode = vars->nodes[idx];

    if (node != oldNode) {
        vars->nodes[idx] = node;
        dropNode(oldNode);
    }

    if (node == NULL)
        DEBUG(" ) /* NULL */\n");
    else
        DEBUG(" ) -> n%d\n", idx);
}

static void
incNodeIdx(void) {
    xmlNodePtr oldNode;
    int idx;

    idx = vars->nodeIdx & REG_MASK;
    vars->nodeIdx = (idx + 1) & REG_MASK;
    oldNode = vars->nodes[idx];

    if (oldNode != NULL) {
        vars->nodes[idx] = NULL;
        dropNode(oldNode);
    }
}

static int
isValidChildType(xmlNodePtr parent, int childType) {
    return ((1 << childType) & childMasks[parent->type]) != 0;
}

static int
isValidChild(xmlNodePtr parent, xmlNodePtr child) {
    xmlNodePtr cur;

    if (child == NULL || parent == NULL)
        return 1;

    if (parent == child)
        return 0;

    if (((1 << child->type) & childMasks[parent->type]) == 0)
        return 0;

    if (child->children == NULL)
        return 1;

    for (cur = parent->parent; cur != NULL; cur = cur->parent)
        if (cur == child)
            return 0;

    return 1;
}

static int
isTextContentNode(xmlNodePtr child) {
    if (child == NULL)
        return 0;

    return ((1 << child->type) & NODE_MASK_TEXT_CONTENT) != 0;
}

static int
isDtdChild(xmlNodePtr child) {
    if (child == NULL)
        return 0;

    return ((1 << child->type) & CHILD_MASK_DTD) != 0;
}

static xmlNodePtr
nodeGetTree(xmlNodePtr node) {
    xmlNodePtr cur = node;

    while (cur->parent)
        cur = cur->parent;
    return cur;
}

/*
 * This function is called whenever a reference to a node is removed.
 * It checks whether the node is still reachable and frees unreferenced
 * nodes.
 *
 * A node is reachable if its tree, identified by the root node,
 * is reachable. If a non-document tree is unreachable, it can be
 * freed.
 *
 * Multiple trees can share the same document, so a document tree
 * can only be freed if no other trees reference the document.
 */
static void
dropNode(xmlNodePtr node) {
    xmlNodePtr *nodes = vars->nodes;
    xmlNodePtr tree;
    xmlDocPtr doc;
    int docReferenced = 0;
    int i;

    if (node == NULL)
        return;

    tree = nodeGetTree(node);
    doc = node->doc;

    for (i = 0; i < REG_MAX; i++) {
        xmlNodePtr other;

        other = nodes[i];
        if (other == NULL)
            continue;

        /*
         * Return if tree is referenced from another node
         */
        if (nodeGetTree(other) == tree)
            return;
        if (doc != NULL && other->doc == doc)
            docReferenced = 1;
    }

    if (tree != (xmlNodePtr) doc && !isDtdChild(tree)) {
        if (doc == NULL || tree->type != XML_DTD_NODE ||
            ((xmlDtdPtr) tree != doc->intSubset &&
             (xmlDtdPtr) tree != doc->extSubset))
            xmlFreeNode(tree);
    }

    /*
     * Also free document if it isn't referenced from other nodes
     */
    if (doc != NULL && !docReferenced)
        xmlFreeDoc(doc);
}

/*
 * removeNode and removeChildren remove all references to a node
 * or its children from the registers. These functions should be
 * called if an API function destroys nodes, for example by merging
 * text nodes.
 */

static void
removeNode(xmlNodePtr node) {
    int i;

    for (i = 0; i < REG_MAX; i++)
        if (vars->nodes[i] == node)
            vars->nodes[i] = NULL;
}

static void
removeChildren(xmlNodePtr parent, int self) {
    int i;

    if (parent == NULL || (!self && parent->children == NULL))
        return;

    for (i = 0; i < REG_MAX; i++) {
        xmlNodePtr node = vars->nodes[i];

        if (node == parent) {
            if (self)
                vars->nodes[i] = NULL;
            continue;
        }

        while (node != NULL) {
            node = node->parent;
            if (node == parent) {
                vars->nodes[i] = NULL;
                break;
            }
        }
    }
}

static xmlNsPtr
nodeGetNs(xmlNodePtr node, int k) {
    int i = 0;
    xmlNsPtr ns, next;

    if (node == NULL || node->type != XML_ELEMENT_NODE)
        return NULL;

    ns = NULL;
    next = node->nsDef;
    while (1) {
        while (next == NULL) {
            node = node->parent;
            if (node == NULL || node->type != XML_ELEMENT_NODE)
                break;
            next = node->nsDef;
        }

        if (next == NULL)
            break;

        ns = next;
        if (i == k)
            break;

        next = ns->next;
        i += 1;
    }

    return ns;
}

/*
 * It's easy for programs to exhibit exponential growth patterns.
 * For example, a tree being copied and added to the original source
 * node doubles memory usage with two operations. Repeating these
 * operations leads to 2^n nodes. Similar issues can arise when
 * concatenating strings.
 *
 * We simply ignore tree copies or truncate text if they grow too
 * large.
 */

static void
checkContent(xmlNodePtr node) {
    if (node != NULL &&
        (node->type == XML_TEXT_NODE ||
         node->type == XML_CDATA_SECTION_NODE ||
         node->type == XML_ENTITY_NODE ||
         node->type == XML_PI_NODE ||
         node->type == XML_COMMENT_NODE ||
         node->type == XML_NOTATION_NODE) &&
        xmlStrlen(node->content) > MAX_CONTENT) {
        xmlNodeSetContent(node, NULL);
        node->content = uncheckedStrdup(BAD_CAST "");
    }
}

static int
countNodes(xmlNodePtr node) {
    xmlNodePtr cur;
    int numNodes;

    if (node == NULL)
        return 0;

    cur = node;
    numNodes = 0;

    while (1) {
        numNodes += 1;

        if (cur->children != NULL &&
            cur->type != XML_ENTITY_REF_NODE) {
            cur = cur->children;
        } else {
            while (cur->next == NULL) {
                if (cur == node)
                    goto done;
                cur = cur->parent;
            }
            cur = cur->next;
        }
    }

done:
    return numNodes;
}

static xmlNodePtr
checkCopy(xmlNodePtr copy) {
    vars->numCopyOps += 1;

    if (copy != NULL &&
        (vars->numCopyOps > MAX_COPY_OPS ||
         countNodes(copy) > MAX_COPY_NODES)) {
        if (copy->type == XML_DOCUMENT_NODE ||
            copy->type == XML_HTML_DOCUMENT_NODE)
            xmlFreeDoc((xmlDocPtr) copy);
        else
            xmlFreeNode(copy);
        copy = NULL;
    }

    return copy;
}

/*
 * Fix namespaces, for example after unlinking a node. This makes
 * sure that the node only references namespaces declared in ancestor
 * nodes.
 */
static int
fixNs(xmlNodePtr node) {
    if (node == NULL)
        return 0;

    if (node->type == XML_ELEMENT_NODE) {
        return xmlReconciliateNs(node->doc, node);
    } else if (node->type == XML_ATTRIBUTE_NODE) {
        xmlNodePtr parent = node->parent;

        if (parent != NULL)
            return xmlReconciliateNs(parent->doc, parent);
        else
            node->ns = NULL;
    }

    return 0;
}

/* Node operations */

static void
opNodeAccessor(int op) {
    xmlNodePtr node;

    switch (op) {
        case OP_NODE_PARENT:
            startOp("parent"); break;
        case OP_NODE_NEXT_SIBLING:
            startOp("next"); break;
        case OP_NODE_PREV_SIBLING:
            startOp("prev"); break;
        case OP_NODE_FIRST_CHILD:
            startOp("children"); break;
        case OP_XML_GET_LAST_CHILD:
            startOp("xmlGetLastChild"); break;
        case OP_XML_GET_INT_SUBSET:
            startOp("xmlGetIntSubset"); break;
        case OP_XML_DOC_GET_ROOT_ELEMENT:
            startOp("xmlDocGetRootElement"); break;
        default:
            break;
    }

    incNodeIdx();
    node = getNode(1);

    if (node != NULL) {
        switch (op) {
            case OP_NODE_PARENT:
                node = node->parent; break;
            case OP_NODE_NEXT_SIBLING:
                node = node->next; break;
            case OP_NODE_PREV_SIBLING:
                node = node->prev; break;
            case OP_NODE_FIRST_CHILD:
                node = node->children; break;
            case OP_XML_GET_LAST_CHILD:
                node = xmlGetLastChild(node); break;
            case OP_XML_GET_INT_SUBSET:
                node = (xmlNodePtr) xmlGetIntSubset(node->doc); break;
            case OP_XML_DOC_GET_ROOT_ELEMENT:
                node = xmlDocGetRootElement(node->doc); break;
            default:
                break;
        }

        /*
         * Don't descend into predefined entities
         */
        if (node != NULL && node->type == XML_ENTITY_DECL) {
            xmlEntityPtr ent = (xmlEntityPtr) node;

            if (ent->etype == XML_INTERNAL_PREDEFINED_ENTITY)
                node = NULL;
        }
    }

    setNode(0, node);
}

static void
opDup(int op) {
    int offset;

    switch (op) {
        case OP_DUP_INTEGER:
            incIntIdx(); break;
        case OP_DUP_STRING:
            incStrIdx(); break;
        case OP_DUP_NODE:
            incNodeIdx(); break;
        default:
            break;
    }

    offset = (xmlFuzzReadInt(1) + 1) & REG_MASK;

    if (offset != 0) {
        startOp("dup");
        switch (op) {
            case OP_DUP_INTEGER:
                setInt(0, getInt(offset));
                endOp();
                break;
            case OP_DUP_STRING:
                copyStr(0, getStr(offset));
                endOp();
                break;
            case OP_DUP_NODE:
                setNode(0, getNode(offset));
                break;
            default:
                break;
        }
    }
}

int
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
                     char ***argv ATTRIBUTE_UNUSED) {
    xmlFuzzMemSetup();
    xmlInitParser();
#ifdef LIBXML_CATALOG_ENABLED
    xmlInitializeCatalog();
    xmlCatalogSetDefaults(XML_CATA_ALLOW_NONE);
#endif
    xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);

    return 0;
}

int
LLVMFuzzerTestOneInput(const char *data, size_t size) {
    size_t failurePos;
    int i;

    if (size > 1000)
        return 0;

    memset(vars, 0, sizeof(*vars));

    xmlFuzzDataInit(data, size);

    failurePos = xmlFuzzReadInt(4) % (size * 50 + 10);
    xmlFuzzInjectFailure(failurePos);

    /*
     * Interpreter loop
     *
     * Processing an opcode typically involves
     *
     * - startOp for debugging
     * - increase output register index if non-void
     * - get arguments from input registers
     * - invoke API function
     * - set oomReport
     * - set output register
     * - memory management and other adjustments
     * - endOp for void functions
     */

    while (xmlFuzzBytesRemaining()) {
        size_t readSize;
        int op = xmlFuzzReadInt(1);
        int oomReport = -1; /* -1 means unknown */
        int ioReport = 0;

        vars->opName = "[unset]";

        switch (op) {
            case OP_CREATE_INTEGER:
                incIntIdx();
                setInt(0, (int) xmlFuzzReadInt(4));
                break;

            case OP_CREATE_STRING:
                incStrIdx();
                copyStr(0, BAD_CAST xmlFuzzReadString(&readSize));
                break;

            case OP_DUP_INTEGER:
            case OP_DUP_STRING:
            case OP_DUP_NODE:
                opDup(op);
                break;

            case OP_PARSE_DOCUMENT:
                /*
                 * We don't really want to test the parser but exposing
                 * xmlReadDoc seems like a useful way generate or
                 * round-trip documents.
                 *
                 * This also creates documents with a dictionary which
                 * is crucial to hit some code paths.
                 */
                startOp("xmlReadDoc");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlReadDoc(
                    getStr(0),
                    getCStr(1),
                    getCStr(2),
                    getInt(0)));
                break;

            case OP_XML_NEW_DOC: {
                xmlDocPtr doc;

                /*
                 * TODO: There's no public API function to generate a
                 * document with a dictionary. We should add an extra
                 * opcode that sets doc->dict.
                 */
                startOp("xmlNewDoc");
                incNodeIdx();
                doc = xmlNewDoc(getStr(0));
                oomReport = (doc == NULL);
                setNode(0, (xmlNodePtr) doc);
                break;
            }

            case OP_XML_NEW_NODE: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewNode");
                incNodeIdx();
                node = xmlNewNode(
                    nodeGetNs(getNode(1), getInt(0)),
                    name = getStr(0));
                oomReport = (name != NULL && node == NULL);
                if (fixNs(node) < 0)
                    oomReport = 1;
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_NODE_EAT_NAME: {
                xmlNodePtr node;
                xmlChar *name;

                startOp("xmlNewNodeEatName");
                incNodeIdx();
                node = xmlNewNodeEatName(
                    nodeGetNs(getNode(1), getInt(0)),
                    name = uncheckedStrdup(getStr(0)));
                oomReport = (name != NULL && node == NULL);
                if (fixNs(node) < 0)
                    oomReport = 1;
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_NODE: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewDocNode");
                incNodeIdx();
                node = xmlNewDocNode(
                    getDoc(1),
                    nodeGetNs(getNode(2), getInt(0)),
                    name = getStr(0),
                    getStr(1));
                oomReport = (name != NULL && node == NULL);
                if (fixNs(node) < 0)
                    oomReport = 1;
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_NODE_EAT_NAME: {
                xmlNodePtr node;
                xmlChar *name;

                startOp("xmlNewDocNodeEatName");
                incNodeIdx();
                node = xmlNewDocNodeEatName(
                    getDoc(1),
                    nodeGetNs(getNode(2), getInt(0)),
                    name = uncheckedStrdup(getStr(0)),
                    getStr(1));
                oomReport = (name != NULL && node == NULL);
                if (fixNs(node) < 0)
                    oomReport = 1;
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_RAW_NODE: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewDocRawNode");
                incNodeIdx();
                node = xmlNewDocRawNode(
                    getDoc(1),
                    nodeGetNs(getNode(2), getInt(0)),
                    name = getStr(0),
                    getStr(1));
                oomReport = (name != NULL && node == NULL);
                if (fixNs(node) < 0)
                    oomReport = 1;
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_CHILD: {
                xmlNodePtr parent, node;
                const xmlChar *name;

                startOp("xmlNewChild");
                incNodeIdx();
                /* Use parent namespace without fixup */
                node = xmlNewChild(
                    parent = getNode(1),
                    nodeGetNs(getNode(1), getInt(0)),
                    name = getStr(0),
                    getStr(1));
                oomReport =
                    (parent != NULL &&
                     isValidChildType(parent, XML_ELEMENT_NODE) &&
                     name != NULL &&
                     node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_TEXT_CHILD: {
                xmlNodePtr parent, node;
                const xmlChar *name;

                startOp("xmlNewTextChild");
                incNodeIdx();
                /* Use parent namespace without fixup */
                node = xmlNewTextChild(
                    parent = getNode(1),
                    nodeGetNs(getNode(1), getInt(0)),
                    name = getStr(0),
                    getStr(1));
                oomReport =
                    (parent != NULL &&
                     isValidChildType(parent, XML_ELEMENT_NODE) &&
                     name != NULL &&
                     node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_PROP: {
                xmlNodePtr parent;
                xmlAttrPtr attr;
                const xmlChar *name;

                startOp("xmlNewProp");
                incNodeIdx();
                attr = xmlNewProp(
                    parent = getNode(1),
                    name = getStr(0),
                    getStr(1));
                oomReport =
                    ((parent == NULL || parent->type == XML_ELEMENT_NODE) &&
                     name != NULL &&
                     attr == NULL);
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_NEW_DOC_PROP: {
                xmlAttrPtr attr;
                const xmlChar *name;

                startOp("xmlNewDocProp");
                incNodeIdx();
                attr = xmlNewDocProp(
                    getDoc(1),
                    name = getStr(0),
                    getStr(1));
                oomReport = (name != NULL && attr == NULL);
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_NEW_NS_PROP: {
                xmlAttrPtr attr;

                startOp("xmlNewNsProp");
                incNodeIdx();
                attr = xmlNewNsProp(
                    getNode(1),
                    nodeGetNs(getNode(1), getInt(0)),
                    getStr(0),
                    getStr(1));
                /* xmlNewNsProp returns NULL on duplicate prefixes. */
                if (attr != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_NEW_NS_PROP_EAT_NAME: {
                xmlAttrPtr attr;

                startOp("xmlNewNsPropEatName");
                incNodeIdx();
                attr = xmlNewNsPropEatName(
                    getNode(1),
                    nodeGetNs(getNode(1), getInt(0)),
                    uncheckedStrdup(getStr(0)),
                    getStr(1));
                if (attr != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_NEW_TEXT: {
                xmlNodePtr node;

                startOp("xmlNewText");
                incNodeIdx();
                node = xmlNewText(getStr(0));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_TEXT_LEN: {
                xmlNodePtr node;
                const xmlChar *text;

                startOp("xmlNewTextLen");
                incNodeIdx();
                text = getStr(0);
                node = xmlNewTextLen(text, xmlStrlen(text));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_TEXT: {
                xmlNodePtr node;

                startOp("xmlNewDocText");
                incNodeIdx();
                node = xmlNewDocText(getDoc(1), getStr(0));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_TEXT_LEN: {
                xmlDocPtr doc;
                xmlNodePtr node;
                const xmlChar *text;

                startOp("xmlNewDocTextLen");
                incNodeIdx();
                doc = getDoc(1);
                text = getStr(0);
                node = xmlNewDocTextLen(doc, text, xmlStrlen(text));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_PI: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewPI");
                incNodeIdx();
                node = xmlNewPI(
                    name = getStr(0),
                    getStr(1));
                oomReport = (name != NULL && node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_PI: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewDocPI");
                incNodeIdx();
                node = xmlNewDocPI(
                    getDoc(1),
                    name = getStr(0),
                    getStr(1));
                oomReport = (name != NULL && node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_COMMENT: {
                xmlNodePtr node;

                startOp("xmlNewComment");
                incNodeIdx();
                node = xmlNewComment(getStr(0));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_COMMENT: {
                xmlNodePtr node;

                startOp("xmlNewDocComment");
                incNodeIdx();
                node = xmlNewDocComment(
                    getDoc(1),
                    getStr(0));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_CDATA_BLOCK: {
                xmlDocPtr doc;
                xmlNodePtr node;
                const xmlChar *text;

                startOp("xmlNewCDataBlock");
                incNodeIdx();
                doc = getDoc(1);
                text = getStr(0);
                node = xmlNewDocTextLen(
                    doc,
                    text,
                    xmlStrlen(text));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_CHAR_REF: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewCharRef");
                incNodeIdx();
                node = xmlNewCharRef(
                    getDoc(1),
                    name = getStr(0));
                oomReport = (name != NULL && node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_REFERENCE: {
                xmlNodePtr node;
                const xmlChar *name;

                startOp("xmlNewReference");
                incNodeIdx();
                node = xmlNewReference(
                    getDoc(1),
                    name = getStr(0));
                oomReport = (name != NULL && node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_NEW_DOC_FRAGMENT: {
                xmlNodePtr node;

                startOp("xmlNewDocFragment");
                incNodeIdx();
                node = xmlNewDocFragment(getDoc(1));
                oomReport = (node == NULL);
                setNode(0, node);
                break;
            }

            case OP_XML_CREATE_INT_SUBSET: {
                xmlDocPtr doc;
                xmlDtdPtr dtd = NULL;

                startOp("xmlCreateIntSubset");
                incNodeIdx();
                doc = getDoc(1);
                if (doc == NULL || doc->intSubset == NULL) {
                    dtd = xmlCreateIntSubset(
                        doc,
                        getStr(0),
                        getStr(1),
                        getStr(2));
                    oomReport = (dtd == NULL);
                }
                setNode(0, (xmlNodePtr) dtd);
                break;
            }

            case OP_XML_NEW_DTD: {
                xmlDocPtr doc;
                xmlDtdPtr dtd = NULL;

                startOp("xmlNewDtd");
                incNodeIdx();
                doc = getDoc(1);
                if (doc == NULL || doc->extSubset == NULL) {
                    dtd = xmlNewDtd(
                        doc,
                        getStr(0),
                        getStr(1),
                        getStr(2));
                    oomReport = (dtd == NULL);
                }
                setNode(0, (xmlNodePtr) dtd);
                break;
            }

            case OP_XML_COPY_DOC: {
                xmlDocPtr copy;

                startOp("xmlCopyDoc");
                incNodeIdx();
                copy = xmlCopyDoc(
                    getDoc(1),
                    getInt(0));
                /*
                 * TODO: Copying DTD nodes without a document can
                 * result in an empty list.
                 */
                if (copy != NULL)
                    oomReport = 0;
                setNode(0, checkCopy((xmlNodePtr) copy));
                break;
            }

            case OP_XML_COPY_NODE: {
                xmlNodePtr copy;

                startOp("xmlCopyNode");
                incNodeIdx();
                copy = xmlCopyNode(
                    getNode(1),
                    getInt(0));
                if (copy != NULL)
                    oomReport = 0;
                setNode(0, checkCopy((xmlNodePtr) copy));
                break;
            }

            case OP_XML_COPY_NODE_LIST: {
                xmlNodePtr copy;

                startOp("xmlCopyNodeList");
                copy = xmlCopyNodeList(getNode(0));
                if (copy != NULL)
                    oomReport = 0;
                xmlFreeNodeList(copy);
                endOp();
                break;
            }

            case OP_XML_DOC_COPY_NODE: {
                xmlNodePtr node, copy;
                xmlDocPtr doc;

                startOp("xmlDocCopyNode");
                incNodeIdx();
                copy = xmlDocCopyNode(
                    node = getNode(1),
                    doc = getDoc(2),
                    getInt(0));
                if (copy != NULL)
                    oomReport = 0;
                setNode(0, checkCopy((xmlNodePtr) copy));
                break;
            }

            case OP_XML_DOC_COPY_NODE_LIST: {
                xmlNodePtr copy;

                startOp("xmlDocCopyNodeList");
                copy = xmlDocCopyNodeList(
                    getDoc(0),
                    getNode(1));
                if (copy != NULL)
                    oomReport = 0;
                xmlFreeNodeList(copy);
                endOp();
                break;
            }

            case OP_XML_COPY_PROP: {
                xmlAttrPtr copy;

                startOp("xmlCopyProp");
                incNodeIdx();
                copy = xmlCopyProp(
                    getNode(1),
                    getAttr(2));
                /*
                 * TODO: Copying attributes can result in an empty list
                 * if there's a duplicate namespace prefix.
                 */
                if (copy != NULL)
                    oomReport = 0;
                if (copy != NULL) {
                    /* Quirk */
                    copy->parent = NULL;
                    /* Fix namespace */
                    copy->ns = NULL;
                }
                setNode(0, checkCopy((xmlNodePtr) copy));
                break;
            }

            case OP_XML_COPY_PROP_LIST: {
                xmlAttrPtr copy;

                startOp("xmlCopyPropList");
                copy = xmlCopyPropList(
                    getNode(0),
                    getAttr(1));
                if (copy != NULL)
                    oomReport = 0;
                xmlFreePropList(copy);
                endOp();
                break;
            }

            case OP_XML_COPY_DTD: {
                xmlDtdPtr dtd, copy;

                startOp("xmlCopyDtd");
                incNodeIdx();
                copy = xmlCopyDtd(
                    dtd = getDtd(1));
                oomReport = (dtd != NULL && copy == NULL);
                setNode(0, checkCopy((xmlNodePtr) copy));
                break;
            }

            case OP_NODE_PARENT:
            case OP_NODE_NEXT_SIBLING:
            case OP_NODE_PREV_SIBLING:
            case OP_NODE_FIRST_CHILD:
            case OP_XML_GET_LAST_CHILD:
            case OP_XML_GET_INT_SUBSET:
            case OP_XML_DOC_GET_ROOT_ELEMENT:
                opNodeAccessor(op);
                oomReport = 0;
                break;

            case OP_NODE_NAME: {
                xmlNodePtr node;

                startOp("name");
                incStrIdx();
                node = getNode(0);
                copyStr(0, node ? node->name : NULL);
                oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_NODE_SET_NAME:
                startOp("xmlNodeSetName");
                xmlNodeSetName(
                    getNode(0),
                    getStr(0));
                endOp();
                break;

            case OP_XML_NODE_GET_CONTENT: {
                xmlChar *content;

                incStrIdx();
                startOp("xmlNodeGetContent");
                content = xmlNodeGetContent(getNode(0));
                if (content != NULL)
                    oomReport = 0;
                moveStr(0, content);
                endOp();
                break;
            }

            case OP_XML_NODE_SET_CONTENT: {
                xmlNodePtr node;
                int res;

                startOp("xmlNodeSetContent");
                node = getNode(0);
                removeChildren(node, 0);
                res = xmlNodeSetContent(
                    node,
                    getStr(0));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_NODE_SET_CONTENT_LEN: {
                xmlNodePtr node;
                const xmlChar *content;
                int res;

                startOp("xmlNodeSetContentLen");
                node = getNode(0);
                content = getStr(0);
                removeChildren(node, 0);
                res = xmlNodeSetContentLen(
                    node,
                    content,
                    xmlStrlen(content));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_NODE_ADD_CONTENT: {
                xmlNodePtr node, text;
                int res;

                startOp("xmlNodeAddContent");
                node = getNode(0);
                res = xmlNodeAddContent(
                    node,
                    getStr(0));
                oomReport = (res < 0);
                if (node != NULL) {
                    if (node->type == XML_ELEMENT_NODE ||
                        node->type == XML_DOCUMENT_FRAG_NODE)
                        text = node->last;
                    else
                        text = node;
                    checkContent(text);
                }
                endOp();
                break;
            }

            case OP_XML_NODE_ADD_CONTENT_LEN: {
                xmlNodePtr node, text;
                const xmlChar *content;
                int res;

                startOp("xmlNodeAddContentLen");
                node = getNode(0);
                content = getStr(0);
                res = xmlNodeAddContentLen(
                    node,
                    content,
                    xmlStrlen(content));
                oomReport = res < 0;
                if (node != NULL) {
                    if (node->type == XML_ELEMENT_NODE ||
                        node->type == XML_DOCUMENT_FRAG_NODE)
                        text = node->last;
                    else
                        text = node;
                    checkContent(text);
                }
                endOp();
                break;
            }

            case OP_XML_GET_LINE_NO:
                incIntIdx();
                startOp("xmlGetLineNo");
                setInt(0, xmlGetLineNo(getNode(0)));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_GET_NODE_PATH: {
                xmlChar *path;

                incStrIdx();
                startOp("xmlGetNodePath");
                path = xmlGetNodePath(getNode(0));
                if (path != NULL)
                    oomReport = 0;
                moveStr(0, path);
                endOp();
                break;
            }

            case OP_XML_DOC_SET_ROOT_ELEMENT: {
                xmlDocPtr oldDoc, doc;
                xmlNodePtr oldRoot, oldParent, root;

                startOp("xmlDocSetRootElement");
                incNodeIdx();
                doc = getDoc(1);
                root = getNode(2);
                if (doc != NULL && doc->parent != NULL)
                    doc = NULL;
                if (!isValidChild((xmlNodePtr) doc, root))
                    root = NULL;
                oldDoc = root ? root->doc : NULL;
                oldParent = root ? root->parent : NULL;

                oldRoot = xmlDocSetRootElement(doc, root);
                /* We can't really know whether xmlSetTreeDoc failed */
                if (oldRoot != NULL ||
                    root == NULL ||
                    root->doc == oldDoc)
                    oomReport = 0;
                setNode(0, oldRoot);

                if (root &&
                    (root->parent != oldParent ||
                     root->doc != oldDoc)) {
                    if (fixNs(root) < 0)
                        oomReport = 1;
                    if (oldParent != NULL)
                        dropNode(oldParent);
                    else
                        dropNode((xmlNodePtr) oldDoc);
                }
                endOp();
                break;
            }

            case OP_XML_NODE_IS_TEXT:
                incIntIdx();
                startOp("xmlNodeIsText");
                setInt(0, xmlNodeIsText(getNode(0)));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_NODE_GET_ATTR_VALUE: {
                xmlChar *value = NULL;
                int res;

                incStrIdx();
                startOp("xmlNodeGetAttrValue");
                res = xmlNodeGetAttrValue(
                    getNode(0),
                    getStr(1),
                    getStr(2),
                    &value);
                oomReport = (res < 0);
                moveStr(0, value);
                endOp();
                break;
            }

            case OP_XML_NODE_GET_LANG: {
                xmlChar *lang;

                incStrIdx();
                startOp("xmlNodeGetLang");
                lang = xmlNodeGetLang(getNode(0));
                if (lang != NULL)
                    oomReport = 0;
                moveStr(0, lang);
                endOp();
                break;
            }

            case OP_XML_NODE_SET_LANG: {
                xmlNodePtr node;
                xmlAttrPtr attr;
                int res;

                startOp("xmlNodeSetLang");
                node = getNode(0);
                attr = xmlHasNsProp(
                    node,
                    BAD_CAST "lang",
                    XML_XML_NAMESPACE);
                xmlFuzzResetFailure();
                removeChildren((xmlNodePtr) attr, 0);
                res = xmlNodeSetLang(
                    node,
                    getStr(0));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_NODE_GET_SPACE_PRESERVE: {
                int res;

                incIntIdx();
                startOp("xmlNodeGetSpacePreserve");
                res = xmlNodeGetSpacePreserve(getNode(0));
                if (res >= 0)
                    oomReport = 0;
                setInt(0, res);
                endOp();
                break;
            }

            case OP_XML_NODE_SET_SPACE_PRESERVE: {
                xmlNodePtr node;
                xmlAttrPtr attr;
                int res;

                startOp("xmlNodeSetSpacePreserve");
                node = getNode(0);
                attr = xmlHasNsProp(
                    node,
                    BAD_CAST "space",
                    XML_XML_NAMESPACE);
                xmlFuzzResetFailure();
                removeChildren((xmlNodePtr) attr, 0);
                res = xmlNodeSetSpacePreserve(
                    node,
                    getInt(0));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_NODE_GET_BASE: {
                xmlChar *base;

                incStrIdx();
                startOp("xmlNodeGetBase");
                base = xmlNodeGetBase(
                    getDoc(0),
                    getNode(1));
                if (base != NULL)
                    oomReport = 0;
                moveStr(0, base);
                endOp();
                break;
            }

            case OP_XML_NODE_GET_BASE_SAFE: {
                xmlChar *base;
                int res;

                startOp("xmlNodeGetBaseSafe");
                incStrIdx();
                res = xmlNodeGetBaseSafe(
                    getDoc(0),
                    getNode(1),
                    &base);
                oomReport = (res < 0);
                moveStr(0, base);
                endOp();
                break;
            }

            case OP_XML_NODE_SET_BASE: {
                xmlNodePtr node;
                xmlAttrPtr attr;
                int res;

                startOp("xmlNodeSetBase");
                node = getNode(0);
                attr = xmlHasNsProp(
                    node,
                    BAD_CAST "base",
                    XML_XML_NAMESPACE);
                xmlFuzzResetFailure();
                removeChildren((xmlNodePtr) attr, 0);
                res = xmlNodeSetBase(
                    node,
                    getStr(0));
                if (res == 0)
                    oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_IS_BLANK_NODE:
                startOp("xmlIsBlankNode");
                incNodeIdx();
                setInt(0, xmlIsBlankNode(getNode(0)));
                oomReport = 0;
                break;

            case OP_XML_HAS_PROP: {
                xmlNodePtr node;
                xmlAttrPtr attr;

                startOp("xmlHasProp");
                incNodeIdx();
                attr = xmlHasProp(
                    node = getNode(1),
                    getStr(0));
                if (node != NULL &&
                    node->doc != NULL &&
                    node->doc->intSubset != NULL) {
                    /*
                     * xmlHasProp tries to look up default attributes,
                     * requiring a memory allocation which isn't
                     * checked.
                     */
                    if (attr != NULL)
                        oomReport = 0;
                } else {
                    oomReport = 0;
                }
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_HAS_NS_PROP: {
                xmlNodePtr node;
                xmlAttrPtr attr;

                startOp("xmlHasNsProp");
                incNodeIdx();
                attr = xmlHasNsProp(
                    node = getNode(1),
                    getStr(0),
                    getStr(1));
                if (node != NULL &&
                    node->doc != NULL &&
                    node->doc->intSubset != NULL) {
                    if (attr != NULL)
                        oomReport = 0;
                } else {
                    oomReport = 0;
                }
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_GET_PROP: {
                xmlChar *content;

                startOp("xmlGetProp");
                incStrIdx();
                content = xmlGetProp(
                    getNode(0),
                    getStr(1));
                if (content != NULL)
                    oomReport = 0;
                moveStr(0, content);
                endOp();
                break;
            }

            case OP_XML_GET_NS_PROP: {
                xmlChar *content;

                startOp("xmlGetNsProp");
                incStrIdx();
                content = xmlGetNsProp(
                    getNode(0),
                    getStr(1),
                    getStr(2));
                if (content != NULL)
                    oomReport = 0;
                moveStr(0, content);
                endOp();
                break;
            }

            case OP_XML_GET_NO_NS_PROP: {
                xmlChar *content;

                startOp("xmlGetNoNsProp");
                incStrIdx();
                content = xmlGetNoNsProp(
                    getNode(0),
                    getStr(1));
                if (content != NULL)
                    oomReport = 0;
                moveStr(0, content);
                endOp();
                break;
            }

            case OP_XML_SET_PROP: {
                xmlNodePtr node;
                xmlAttrPtr oldAttr, attr;
                xmlNsPtr ns = NULL;
                const xmlChar *name, *value, *localName;
                xmlChar *prefix;
                int prefixLen;

                startOp("xmlSetProp");
                incNodeIdx();
                node = getNode(1);
                name = getStr(0);
                value = getStr(1);

                /*
                 * Find the old attribute node which will be deleted.
                 */
                localName = xmlSplitQName3(name, &prefixLen);
                if (localName != NULL) {
                    prefix = uncheckedStrndup(name, prefixLen);
                    ns = xmlSearchNs(NULL, node, prefix);
                    xmlFree(prefix);
                }
                if (ns == NULL)
                    oldAttr = xmlHasNsProp(node, name, NULL);
                else
                    oldAttr = xmlHasNsProp(node, localName, ns->href);
                xmlFuzzResetFailure();
                if (oldAttr != NULL)
                    removeChildren((xmlNodePtr) oldAttr, 0);

                attr = xmlSetProp(node, name, value);

                oomReport =
                    (node != NULL && node->type == XML_ELEMENT_NODE &&
                     name != NULL &&
                     attr == NULL);
                setNode(0, (xmlNodePtr) attr);
                break;
            }

            case OP_XML_SET_NS_PROP: {
                xmlNodePtr node;
                xmlNsPtr ns;
                xmlAttrPtr oldAttr, attr;
                const xmlChar *name, *value;

                startOp("xmlSetNsProp");
                incNodeIdx();
                node = getNode(1);
                ns = nodeGetNs(getNode(2), getInt(0));
                name = getStr(0);
                value = getStr(1);
                oldAttr = xmlHasNsProp(node, name, ns ? ns->href : NULL);
                xmlFuzzResetFailure();
                if (oldAttr != NULL)
                    removeChildren((xmlNodePtr) oldAttr, 0);
                attr = xmlSetNsProp(node, ns, name, value);
                oomReport =
                    ((node == NULL || node->type == XML_ELEMENT_NODE) &&
                     (ns == NULL || ns->href != NULL) &&
                     name != NULL &&
                     attr == NULL);
                setNode(0, (xmlNodePtr) attr);
                if (ns != NULL) {
                    if (fixNs((xmlNodePtr) attr) < 0)
                        oomReport = 1;
                }
                break;
            }

            case OP_XML_REMOVE_PROP: {
                xmlNodePtr attr, parent = NULL;

                startOp("xmlRemoveProp");
                incIntIdx();
                attr = getNode(0);
                if (attr != NULL) {
                    if (attr->parent != NULL &&
                        attr->type == XML_ATTRIBUTE_NODE)
                        removeChildren(attr, 1);
                    else
                        attr = NULL;
                }
                if (attr != NULL)
                    parent = attr->parent;
                setInt(0, xmlRemoveProp((xmlAttrPtr) attr));
                oomReport = 0;
                dropNode(parent);
                endOp();
                break;
            }

            case OP_XML_UNSET_PROP: {
                xmlNodePtr node;
                xmlAttrPtr attr;
                const xmlChar *name;

                startOp("xmlUnsetProp");
                incIntIdx();
                node = getNode(0);
                name = getStr(0);
                attr = xmlHasNsProp(node, name, NULL);
                xmlFuzzResetFailure();
                if (attr != NULL)
                    removeChildren((xmlNodePtr) attr, 1);
                setInt(0, xmlUnsetProp(node, name));
                oomReport = 0;
                dropNode(node);
                endOp();
                break;
            }

            case OP_XML_UNSET_NS_PROP: {
                xmlNodePtr node;
                xmlNsPtr ns;
                xmlAttrPtr attr;
                const xmlChar *name;

                startOp("xmlUnsetNsProp");
                incIntIdx();
                node = getNode(0);
                ns = nodeGetNs(getNode(1), getInt(1));
                name = getStr(0);
                attr = xmlHasNsProp(node, name, ns ? ns->href : NULL);
                xmlFuzzResetFailure();
                if (attr != NULL)
                    removeChildren((xmlNodePtr) attr, 1);
                setInt(0, xmlUnsetNsProp(node, ns, name));
                oomReport = 0;
                dropNode(node);
                endOp();
                break;
            }

            case OP_XML_NEW_NS: {
                xmlNodePtr node;
                xmlNsPtr ns;

                startOp("xmlNewNs");
                ns = xmlNewNs(
                    node = getNode(0),
                    getStr(0),
                    getStr(1));
                if (ns != NULL)
                    oomReport = 0;
                if (node == NULL)
                    xmlFreeNs(ns);
                endOp();
                break;
            }

            case OP_XML_SEARCH_NS: {
                xmlNsPtr ns;

                startOp("xmlSearchNs");
                ns = xmlSearchNs(
                    getDoc(0),
                    getNode(1),
                    getStr(0));
                if (ns != NULL)
                    oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_SEARCH_NS_BY_HREF: {
                xmlNsPtr ns;

                startOp("xmlSearchNsByHref");
                ns = xmlSearchNsByHref(
                    getDoc(0),
                    getNode(1),
                    getStr(0));
                if (ns != NULL)
                    oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_GET_NS_LIST: {
                xmlNsPtr *list;

                startOp("xmlGetNsList");
                list = xmlGetNsList(
                    getDoc(0),
                    getNode(1));
                if (list != NULL)
                    oomReport = 0;
                xmlFree(list);
                endOp();
                break;
            }

            case OP_XML_GET_NS_LIST_SAFE: {
                xmlNsPtr *list;
                int res;

                startOp("xmlGetNsList");
                res = xmlGetNsListSafe(
                    getDoc(0),
                    getNode(1),
                    &list);
                oomReport = (res < 0);
                xmlFree(list);
                endOp();
                break;
            }

            case OP_XML_SET_NS: {
                xmlNodePtr node;
                xmlNsPtr ns;

                startOp("xmlSetNs");
                node = getNode(0),
                ns = nodeGetNs(getNode(1), getInt(0));
                xmlSetNs(node, ns);
                oomReport = 0;
                if (ns != NULL) {
                    if (fixNs(node) < 0)
                        oomReport = 1;
                }
                endOp();
                break;
            }

            case OP_XML_COPY_NAMESPACE: {
                xmlNsPtr ns, copy;

                startOp("xmlCopyNamespace");
                copy = xmlCopyNamespace(
                    ns = nodeGetNs(getNode(0), getInt(0)));
                oomReport = (ns != NULL && copy == NULL);
                xmlFreeNs(copy);
                endOp();
                break;
            }

            case OP_XML_COPY_NAMESPACE_LIST: {
                xmlNsPtr list, copy;

                startOp("xmlCopyNamespaceList");
                copy = xmlCopyNamespaceList(
                    list = nodeGetNs(getNode(0), getInt(0)));
                oomReport = (list != NULL && copy == NULL);
                xmlFreeNsList(copy);
                endOp();
                break;
            }

            case OP_XML_UNLINK_NODE: {
                xmlNodePtr node, oldParent;
                xmlDocPtr doc;

                startOp("xmlUnlinkNode");
                node = getNode(0);
                doc = node ? node->doc : NULL;
                /*
                 * Unlinking DTD children can cause invalid references
                 * which would be expensive to fix.
                 *
                 * Don't unlink DTD if it is the internal or external
                 * subset of the document.
                 */
                if (node != NULL &&
                    (isDtdChild(node) ||
                     (node->type == XML_DTD_NODE &&
                      doc != NULL &&
                      ((xmlDtdPtr) node == doc->intSubset ||
                       (xmlDtdPtr) node == doc->extSubset))))
                    node = NULL;
                oldParent = node ? node->parent : NULL;
                xmlUnlinkNode(node);
                oomReport = 0;
                if (node != NULL && node->parent != oldParent) {
                    if (fixNs(node) < 0)
                        oomReport = 1;
                    dropNode(oldParent);
                }
                endOp();
                break;
            }

            case OP_XML_REPLACE_NODE: {
                xmlNodePtr old, oldParent, node, oldNodeParent, result;
                xmlDocPtr oldDoc, oldNodeDoc;

                startOp("xmlReplaceNode");
                old = getNode(0);
                node = getNode(1);

                /*
                 * Unlinking DTD children can cause invalid references
                 * which would be expensive to fix.
                 *
                 * Don't unlink DTD if it is the internal or external
                 * subset of the document.
                 */
                old = old ? old->parent : NULL;
                oldDoc = old ? old->doc : NULL;
                if (old != NULL &&
                    (isDtdChild(old) ||
                     (old->type == XML_DTD_NODE &&
                      oldDoc != NULL &&
                      ((xmlDtdPtr) old == oldDoc->intSubset ||
                       (xmlDtdPtr) old == oldDoc->extSubset))))
                    old = NULL;
                if (old != NULL && !isValidChild(old->parent, node))
                    node = NULL;

                oldParent = old ? old->parent : NULL;
                oldNodeParent = node ? node->parent : NULL;
                oldNodeDoc = node ? node->doc : NULL;

                result = xmlReplaceNode(old, node);
                oomReport =
                    (old != NULL && old->parent != NULL &&
                     node != NULL &&
                     old != node &&
                     result == NULL);

                if (old != NULL && old->parent != oldParent) {
                    if (fixNs(old) < 0)
                        oomReport = 1;
                }
                if (node == NULL) {
                    /* Old node was unlinked */
                    dropNode(oldParent);
                } else if (node->parent != oldNodeParent ||
                           node->doc != oldNodeDoc) {
                    if (fixNs(node) < 0)
                        oomReport = 1;
                    /* Drop old parent of new node */
                    if (oldNodeParent != NULL)
                        dropNode(oldNodeParent);
                    else
                        dropNode((xmlNodePtr) oldNodeDoc);
                }
                endOp();
                break;
            }

            case OP_XML_ADD_CHILD:
            case OP_XML_ADD_SIBLING:
            case OP_XML_ADD_PREV_SIBLING:
            case OP_XML_ADD_NEXT_SIBLING: {
                xmlNodePtr target, parent, node, oldNodeParent, result;
                xmlDocPtr oldNodeDoc;
                int argsOk;

                switch (op) {
                    case OP_XML_ADD_CHILD:
                        startOp("xmlAddChild"); break;
                    case OP_XML_ADD_SIBLING:
                        startOp("xmlAddSibling"); break;
                    case OP_XML_ADD_PREV_SIBLING:
                        startOp("xmlAddPrevSibling"); break;
                    case OP_XML_ADD_NEXT_SIBLING:
                        startOp("xmlAddNextSibling"); break;
                }

                if (op == OP_XML_ADD_CHILD) {
                    target = NULL;
                    parent = getNode(0);
                } else {
                    target = getNode(0);
                    parent = target ? target->parent : NULL;
                }
                node = getNode(1);

                /* Don't append to root node */
                if (target != NULL && parent == NULL)
                    node = NULL;

                /* Check tree structure */
                if (isDtdChild(node) ||
                    !isValidChild(parent, node))
                    node = NULL;

                /* Attributes */
                if (node != NULL && node->type == XML_ATTRIBUTE_NODE) {
                    if ((op == OP_XML_ADD_CHILD) ||
                        ((target != NULL &&
                         (target->type == XML_ATTRIBUTE_NODE)))) {
                        xmlAttrPtr attr = xmlHasNsProp(parent, node->name,
                            node->ns ? node->ns->href : NULL);

                        xmlFuzzResetFailure();
                        /* Attribute might be replaced */
                        if (attr != NULL && attr != (xmlAttrPtr) node)
                            removeChildren((xmlNodePtr) attr, 1);
                    } else {
                        target = NULL;
                    }
                } else if (target != NULL &&
                           target->type == XML_ATTRIBUTE_NODE) {
                    node = NULL;
                }

                oldNodeParent = node ? node->parent : NULL;
                oldNodeDoc = node ? node->doc : NULL;
                argsOk =
                    (target != NULL &&
                     node != NULL &&
                     target != node);

                switch (op) {
                    case OP_XML_ADD_CHILD:
                        argsOk = (parent != NULL && node != NULL);
                        result = xmlAddChild(parent, node);
                        break;
                    case OP_XML_ADD_SIBLING:
                        result = xmlAddSibling(target, node);
                        break;
                    case OP_XML_ADD_PREV_SIBLING:
                        result = xmlAddPrevSibling(target, node);
                        break;
                    case OP_XML_ADD_NEXT_SIBLING:
                        result = xmlAddNextSibling(target, node);
                        break;
                }
                oomReport = (argsOk && result == NULL);

                if (result != NULL && result != node) {
                    /* Text node was merged */
                    removeNode(node);
                    checkContent(result);
                    /* Drop old parent of node */
                    if (oldNodeParent != NULL)
                        dropNode(oldNodeParent);
                    else
                        dropNode((xmlNodePtr) oldNodeDoc);
                } else if (node != NULL &&
                           (node->parent != oldNodeParent ||
                            node->doc != oldNodeDoc)) {
                    if (fixNs(node) < 0)
                        oomReport = 1;
                    /* Drop old parent of node */
                    if (oldNodeParent != NULL)
                        dropNode(oldNodeParent);
                    else
                        dropNode((xmlNodePtr) oldNodeDoc);
                }

                endOp();
                break;
            }

            case OP_XML_TEXT_MERGE: {
                xmlNodePtr first, second, parent = NULL, res;
                int argsOk;

                startOp("xmlTextMerge");
                first = getNode(0);
                second = getNode(1);
                argsOk =
                    first == NULL ?
                        second != NULL :
                        second == NULL ||
                        (first->type == XML_TEXT_NODE &&
                         second->type == XML_TEXT_NODE &&
                         first != second &&
                         first->name == second->name);
                if (argsOk && second != NULL) {
                    if (second->parent != NULL)
                        parent = second->parent;
                    else
                        parent = (xmlNodePtr) second->doc;

                }
                res = xmlTextMerge(first, second);
                oomReport = (argsOk && res == NULL);
                if (res != NULL && first != NULL) {
                    removeNode(second);
                    dropNode(parent);
                    checkContent(first);
                }
                endOp();
                break;
            }

            case OP_XML_TEXT_CONCAT: {
                xmlNodePtr node;
                const xmlChar *text;
                int res;

                startOp("xmlTextConcat");
                node = getNode(0);
                text = getStr(0);
                res = xmlTextConcat(
                    node,
                    text,
                    xmlStrlen(text));
                oomReport = (isTextContentNode(node) && res < 0);
                checkContent(node);
                endOp();
                break;
            }

            case OP_XML_STRING_GET_NODE_LIST: {
                xmlNodePtr list;
                const xmlChar *value;

                startOp("xmlStringGetNodeList");
                list = xmlStringGetNodeList(
                    getDoc(0),
                    value = getStr(0));
                oomReport = (value != NULL && value[0] != 0 && list == NULL);
                xmlFreeNodeList(list);
                endOp();
                break;
            }

            case OP_XML_STRING_LEN_GET_NODE_LIST: {
                xmlDocPtr doc;
                xmlNodePtr list;
                const xmlChar *value;

                startOp("xmlStringLenGetNodeList");
                doc = getDoc(0);
                value = getStr(0);
                list = xmlStringLenGetNodeList(
                    doc,
                    value,
                    xmlStrlen(value));
                oomReport = (value != NULL && value[0] != 0 && list == NULL);
                xmlFreeNodeList(list);
                endOp();
                break;
            }

            case OP_XML_NODE_LIST_GET_STRING: {
                xmlDocPtr doc;
                xmlNodePtr list;
                xmlChar *string;

                startOp("xmlNodeListGetString");
                incStrIdx();
                doc = getDoc(0);
                list = getNode(1);
                string = xmlNodeListGetString(
                    doc,
                    list,
                    getInt(0));
                oomReport = (list != NULL && string == NULL);
                moveStr(0, string);
                endOp();
                break;
            }

            case OP_XML_NODE_LIST_GET_RAW_STRING: {
                xmlDocPtr doc;
                xmlNodePtr list;
                xmlChar *string;

                startOp("xmlNodeListGetRawString");
                incStrIdx();
                doc = getDoc(0);
                list = getNode(1);
                string = xmlNodeListGetRawString(
                    doc,
                    list,
                    getInt(0));
                oomReport = (list != NULL && string == NULL);
                moveStr(0, string);
                endOp();
                break;
            }

            case OP_XML_IS_XHTML:
                startOp("xmlIsXHTML");
                incIntIdx();
                setInt(0, xmlIsXHTML(
                    getStr(0),
                    getStr(1)));
                oomReport = 0;
                break;

            case OP_XML_ADD_ELEMENT_DECL: {
                xmlElementPtr decl;

                startOp("xmlAddElementDecl");
                incNodeIdx();
                decl = xmlAddElementDecl(
                    NULL,
                    getDtd(1),
                    getStr(0),
                    (xmlElementTypeVal) getInt(0),
                    NULL);
                if (decl != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) decl);
                break;
            }

            case OP_XML_ADD_ATTRIBUTE_DECL: {
                xmlAttributePtr decl;

                startOp("xmlAddAttributeDecl");
                incNodeIdx();
                decl = xmlAddAttributeDecl(
                    NULL,
                    getDtd(1),
                    getStr(0),
                    getStr(1),
                    getStr(2),
                    (xmlAttributeType) getInt(0),
                    (xmlAttributeDefault) getInt(1),
                    getStr(3),
                    NULL);
                if (decl != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) decl);
                break;
            }

            case OP_XML_ADD_NOTATION_DECL: {
                xmlNotationPtr decl;

                startOp("xmlAddNotationDecl");
                decl = xmlAddNotationDecl(
                    NULL,
                    getDtd(1),
                    getStr(0),
                    getStr(1),
                    getStr(2));
                if (decl != NULL)
                    oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_GET_DTD_ELEMENT_DESC: {
                xmlElementPtr elem;

                startOp("xmlGetDtdElementDesc");
                incNodeIdx();
                elem = xmlGetDtdElementDesc(
                    getDtd(1),
                    getStr(0));
                if (elem != NULL)
                    oomReport = 0;
                /*
                 * Don't reference XML_ELEMENT_TYPE_UNDEFINED dummy
                 * declarations.
                 */
                if (elem != NULL && elem->parent == NULL)
                    elem = NULL;
                setNode(0, (xmlNodePtr) elem);
                break;
            }

            case OP_XML_GET_DTD_QELEMENT_DESC: {
                xmlElementPtr elem;

                startOp("xmlGetDtdQElementDesc");
                incNodeIdx();
                elem = xmlGetDtdQElementDesc(
                    getDtd(1),
                    getStr(0),
                    getStr(1));
                oomReport = 0;
                if (elem != NULL && elem->parent == NULL)
                    elem = NULL;
                setNode(0, (xmlNodePtr) elem);
                break;
            }

            case OP_XML_GET_DTD_ATTR_DESC: {
                xmlAttributePtr decl;

                startOp("xmlGetDtdAttrDesc");
                incNodeIdx();
                decl = xmlGetDtdAttrDesc(
                    getDtd(1),
                    getStr(0),
                    getStr(1));
                if (decl != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) decl);
                break;
            }

            case OP_XML_GET_DTD_QATTR_DESC: {
                xmlAttributePtr decl;

                startOp("xmlGetDtdQAttrDesc");
                incNodeIdx();
                decl = xmlGetDtdQAttrDesc(
                    getDtd(1),
                    getStr(0),
                    getStr(1),
                    getStr(2));
                oomReport = 0;
                setNode(0, (xmlNodePtr) decl);
                break;
            }

            case OP_XML_GET_DTD_NOTATION_DESC:
                startOp("xmlGetDtdNotationDesc");
                xmlGetDtdNotationDesc(
                    getDtd(1),
                    getStr(0));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_ADD_ID:
                startOp("xmlAddID");
                xmlAddID(
                    NULL,
                    getDoc(0),
                    getStr(0),
                    getAttr(1));
                endOp();
                break;

            case OP_XML_ADD_ID_SAFE: {
                int res;

                startOp("xmlAddIDSafe");
                res = xmlAddIDSafe(
                    getAttr(0),
                    getStr(0));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_GET_ID:
                startOp("xmlGetID");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlGetID(
                    getDoc(1),
                    getStr(0)));
                oomReport = 0;
                break;

            case OP_XML_IS_ID: {
                int res;

                startOp("xmlIsID");
                res = xmlIsID(
                    getDoc(2),
                    getNode(1),
                    getAttr(0));
                oomReport = (res < 0);
                endOp();
                break;
            }

            case OP_XML_REMOVE_ID:
                startOp("xmlRemoveID");
                xmlRemoveID(
                    getDoc(1),
                    getAttr(0));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_ADD_REF: {
                xmlDocPtr doc;
                xmlAttrPtr attr;
                xmlRefPtr ref;
                const xmlChar *value;

                startOp("xmlAddRef");
                ref = xmlAddRef(
                    NULL,
                    doc = getDoc(0),
                    value = getStr(0),
                    attr = getAttr(1));
                oomReport =
                    (doc != NULL &&
                     value != NULL &&
                     attr != NULL &&
                     ref == NULL);
                endOp();
                break;
            }

            case OP_XML_GET_REFS:
                startOp("xmlGetRefs");
                xmlGetRefs(
                    getDoc(1),
                    getStr(0));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_IS_REF:
                startOp("xmlIsRef");
                xmlIsRef(
                    getDoc(2),
                    getNode(1),
                    getAttr(0));
                oomReport = 0;
                endOp();
                break;

            case OP_XML_REMOVE_REF: {
                int res;

                startOp("xmlRemoveRef");
                res = xmlRemoveRef(
                    getDoc(1),
                    getAttr(0));
                if (res == 0)
                    oomReport = 0;
                endOp();
                break;
            }

            case OP_XML_NEW_ENTITY: {
                xmlDocPtr doc;
                xmlEntityPtr ent;

                startOp("xmlNewEntity");
                incNodeIdx();
                ent = xmlNewEntity(
                    doc = getDoc(1),
                    getStr(0),
                    getInt(0),
                    getStr(1),
                    getStr(2),
                    getStr(3));
                if (ent != NULL)
                    oomReport = 0;
                if (doc == NULL || doc->intSubset == NULL) {
                    xmlFreeEntity(ent);
                    ent = NULL;
                }
                setNode(0, (xmlNodePtr) ent);
                break;
            }

            case OP_XML_ADD_ENTITY: {
                xmlEntityPtr ent;
                int res;

                startOp("xmlAddEntity");
                incNodeIdx();
                res = xmlAddEntity(
                    getDoc(1),
                    getInt(0),
                    getStr(0),
                    getInt(1),
                    getStr(1),
                    getStr(2),
                    getStr(3),
                    &ent);
                oomReport = (res == XML_ERR_NO_MEMORY);
                setNode(0, (xmlNodePtr) ent);
                break;
            }

            case OP_XML_ADD_DOC_ENTITY: {
                xmlEntityPtr ent;

                startOp("xmlAddDocEntity");
                incNodeIdx();
                ent = xmlAddDocEntity(
                    getDoc(1),
                    getStr(0),
                    getInt(1),
                    getStr(1),
                    getStr(2),
                    getStr(3));
                if (ent != NULL)
                    oomReport = 0;
                setNode(0, (xmlNodePtr) ent);
                break;
            }

            case OP_XML_ADD_DTD_ENTITY: {
                xmlEntityPtr ent;

                startOp("xmlAddDtdEntity");
                incNodeIdx();
                ent = xmlAddDtdEntity(
                    getDoc(1),
                    getStr(0),
                    getInt(1),
                    getStr(1),
                    getStr(2),
                    getStr(3));
                setNode(0, (xmlNodePtr) ent);
                break;
            }

            case OP_XML_GET_PREDEFINED_ENTITY:
                startOp("xmlGetPredefinedEntity");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlGetPredefinedEntity(
                    getStr(0)));
                oomReport = 0;
                break;

            case OP_XML_GET_DOC_ENTITY:
                startOp("xmlGetDocEntity");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlGetDocEntity(
                    getDoc(1),
                    getStr(0)));
                oomReport = 0;
                break;

            case OP_XML_GET_DTD_ENTITY:
                startOp("xmlGetDtdEntity");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlGetDtdEntity(
                    getDoc(1),
                    getStr(0)));
                oomReport = 0;
                break;

            case OP_XML_GET_PARAMETER_ENTITY:
                startOp("xmlGetParameterEntity");
                incNodeIdx();
                setNode(0, (xmlNodePtr) xmlGetParameterEntity(
                    getDoc(1),
                    getStr(0)));
                oomReport = 0;
                break;

            case OP_XML_ENCODE_ENTITIES_REENTRANT: {
                const xmlChar *string;
                xmlChar *encoded;

                startOp("xmlEncodeEntitiesReentrant");
                incStrIdx();
                encoded = xmlEncodeEntitiesReentrant(
                    getDoc(0),
                    string = getStr(1));
                oomReport = (string != NULL && encoded == NULL);
                moveStr(0, encoded);
                endOp();
                break;
            }

            case OP_XML_ENCODE_SPECIAL_CHARS: {
                const xmlChar *string;
                xmlChar *encoded;

                startOp("xmlEncodespecialChars");
                incStrIdx();
                encoded = xmlEncodeSpecialChars(
                    getDoc(0),
                    string = getStr(1));
                oomReport = (string != NULL && encoded == NULL);
                moveStr(0, encoded);
                endOp();
                break;
            }

#ifdef LIBXML_HTML_ENABLED
            case OP_HTML_NEW_DOC: {
                htmlDocPtr doc;

                startOp("htmlNewDoc");
                incNodeIdx();
                doc = htmlNewDoc(
                    getStr(0),
                    getStr(1));
                oomReport = (doc == NULL);
                setNode(0, (xmlNodePtr) doc);
                break;
            }

            case OP_HTML_NEW_DOC_NO_DTD: {
                htmlDocPtr doc;

                startOp("htmlNewDocNoDtD");
                incNodeIdx();
                doc = htmlNewDocNoDtD(
                    getStr(0),
                    getStr(1));
                oomReport = (doc == NULL);
                setNode(0, (xmlNodePtr) doc);
                break;
            }

            case OP_HTML_GET_META_ENCODING: {
                const xmlChar *encoding;

                startOp("htmlGetMetaEncoding");
                incStrIdx();
                encoding = htmlGetMetaEncoding(getDoc(0));
                if (encoding != NULL)
                    oomReport = 0;
                copyStr(0, encoding);
                break;
            }

            case OP_HTML_SET_META_ENCODING:
                /* TODO (can destroy inner text) */
                break;

            case OP_HTML_IS_BOOLEAN_ATTR:
                startOp("htmlIsBooleanAttr");
                htmlIsBooleanAttr(getStr(0));
                oomReport = 0;
                endOp();
                break;
#endif

#ifdef LIBXML_VALID_ENABLED
            case OP_VALIDATE: {
                xmlNodePtr node;
                int type;
                int res = 1;

                startOp("validate");
                incIntIdx();
                node = getNode(0);
                type = node ? node->type : 0;
                xmlValidCtxtPtr vctxt = xmlNewValidCtxt();
                xmlFuzzResetFailure();

                switch (type) {
                    case XML_DOCUMENT_NODE:
                    case XML_HTML_DOCUMENT_NODE:
                        res = xmlValidateDocument(vctxt, (xmlDocPtr) node);
                        break;
                    case XML_ELEMENT_DECL:
                        res = xmlValidateElementDecl(vctxt, node->doc,
                            (xmlElementPtr) node);
                        break;
                    case XML_ATTRIBUTE_DECL:
                        res = xmlValidateAttributeDecl(vctxt, node->doc,
                            (xmlAttributePtr) node);
                        break;
                    case XML_ELEMENT_NODE:
                        res = xmlValidateElement(vctxt, node->doc, node);
                        break;
                    default:
                        break;
                }

                if (res != 0)
                    oomReport = 0;
                xmlFreeValidCtxt(vctxt);
                setInt(0, res);
                endOp();
                break;
            }

            case OP_XML_VALIDATE_DTD: {
                xmlValidCtxtPtr vctxt;
                int res;

                startOp("xmlValidateDtd");
                incIntIdx();
                vctxt = xmlNewValidCtxt();
                res = xmlValidateDtd(
                    vctxt,
                    getDoc(0),
                    getDtd(1));
                if (res != 0)
                    oomReport = 0;
                xmlFreeValidCtxt(vctxt);
                setInt(0, res);
                endOp();
                break;
            }
#endif /* LIBXML_VALID_ENABLED */

#ifdef LIBXML_OUTPUT_ENABLED
            case OP_XML_DOC_DUMP_MEMORY:
            case OP_XML_DOC_DUMP_MEMORY_ENC:
            case OP_XML_DOC_DUMP_FORMAT_MEMORY:
            case OP_XML_DOC_DUMP_FORMAT_MEMORY_ENC:
            case OP_HTML_DOC_DUMP_MEMORY:
            case OP_HTML_DOC_DUMP_MEMORY_FORMAT: {
                xmlDocPtr doc;
                xmlChar *out = NULL;
                int outSize = 0;

                switch (op) {
                    case OP_XML_DOC_DUMP_MEMORY:
                        startOp("xmlDocDumpMemory"); break;
                    case OP_XML_DOC_DUMP_MEMORY_ENC:
                        startOp("xmlDocDumpMemoryEnc"); break;
                    case OP_XML_DOC_DUMP_FORMAT_MEMORY:
                        startOp("xmlDocDumpFormatMemory"); break;
                    case OP_XML_DOC_DUMP_FORMAT_MEMORY_ENC:
                        startOp("xmlDocDumpFormatMemoryEnc"); break;
                    case OP_HTML_DOC_DUMP_MEMORY:
                        startOp("htmlDocDumpMemory"); break;
                    case OP_HTML_DOC_DUMP_MEMORY_FORMAT:
                        startOp("htmlDocDumpMemoryFormat"); break;
                }

                incStrIdx();
                doc = getDoc(0);

                switch (op) {
                    case OP_XML_DOC_DUMP_MEMORY:
                        xmlDocDumpMemory(doc, &out, &outSize);
                        break;
                    case OP_XML_DOC_DUMP_MEMORY_ENC:
                        xmlDocDumpMemoryEnc(doc, &out, &outSize,
                            (const char *) getStr(1));
                        break;
                    case OP_XML_DOC_DUMP_FORMAT_MEMORY:
                        xmlDocDumpFormatMemory(doc, &out, &outSize,
                            getInt(0));
                        break;
                    case OP_XML_DOC_DUMP_FORMAT_MEMORY_ENC:
                        xmlDocDumpFormatMemoryEnc(doc, &out, &outSize,
                            (const char *) getStr(1),
                            getInt(0));
                        break;
#ifdef LIBXML_HTML_ENABLED
                    case OP_HTML_DOC_DUMP_MEMORY:
                        htmlDocDumpMemory(doc, &out, &outSize);
                        break;
                    case OP_HTML_DOC_DUMP_MEMORY_FORMAT:
                        htmlDocDumpMemoryFormat(doc, &out, &outSize,
                            getInt(0));
                        break;
#endif /* LIBXML_HTML_ENABLED */
                }

                /* Could be an unknown encoding */
                if (out != NULL)
                    oomReport = 0;
                moveStr(0, out);
                endOp();
                break;
            }

            case OP_XML_NODE_DUMP:
            case OP_XML_NODE_BUF_GET_CONTENT:
            case OP_XML_ATTR_SERIALIZE_TXT_CONTENT:
            case OP_XML_DUMP_ELEMENT_DECL:
            case OP_XML_DUMP_ELEMENT_TABLE:
            case OP_XML_DUMP_ATTRIBUTE_DECL:
            case OP_XML_DUMP_ATTRIBUTE_TABLE:
            case OP_XML_DUMP_ENTITY_DECL:
            case OP_XML_DUMP_ENTITIES_TABLE:
            case OP_XML_DUMP_NOTATION_DECL:
            case OP_XML_DUMP_NOTATION_TABLE:
            case OP_HTML_NODE_DUMP: {
                xmlNodePtr node;
                xmlDocPtr doc;
                xmlBufferPtr buffer;
                xmlChar *dump;
                int level, format, res;

                switch (op) {
                    case OP_XML_NODE_DUMP:
                        startOp("xmlNodeDump"); break;
                    case OP_XML_NODE_BUF_GET_CONTENT:
                        startOp("xmlNodeBufGetContent"); break;
                    case OP_XML_ATTR_SERIALIZE_TXT_CONTENT:
                        startOp("xmlAttrSerializeTxtContent"); break;
                    case OP_XML_DUMP_ELEMENT_DECL:
                        startOp("xmlDumpElementDecl"); break;
                    case OP_XML_DUMP_ELEMENT_TABLE:
                        startOp("xmlDumpElementTable"); break;
                    case OP_XML_DUMP_ATTRIBUTE_DECL:
                        startOp("xmlDumpAttributeDecl"); break;
                    case OP_XML_DUMP_ATTRIBUTE_TABLE:
                        startOp("xmlDumpAttributeTable"); break;
                    case OP_XML_DUMP_ENTITY_DECL:
                        startOp("xmlDumpEntityDecl"); break;
                    case OP_XML_DUMP_ENTITIES_TABLE:
                        startOp("xmlDumpEntitiesTable"); break;
                    case OP_XML_DUMP_NOTATION_DECL:
                        startOp("xmlDumpNotationDecl"); break;
                    case OP_XML_DUMP_NOTATION_TABLE:
                        startOp("xmlDumpNotationTable"); break;
                    case OP_HTML_NODE_DUMP:
                        startOp("htmlNodeDump"); break;
                }

                incStrIdx();
                buffer = xmlBufferCreate();
                xmlFuzzResetFailure();
                node = getNode(0);
                doc = node ? node->doc : NULL;
                level = getInt(0);
                format = getInt(0);
                res = 0;

                switch (op) {
                    case OP_XML_NODE_DUMP:
                        res = xmlNodeDump(buffer, doc, node, level, format);
                        break;
                    case OP_XML_NODE_BUF_GET_CONTENT:
                        res = xmlNodeBufGetContent(buffer, node);
                        break;
                    case OP_XML_ATTR_SERIALIZE_TXT_CONTENT:
                        if (node != NULL && node->type != XML_ATTRIBUTE_NODE)
                            node = NULL;
                        xmlAttrSerializeTxtContent(
                            buffer, doc,
                            (xmlAttrPtr) node,
                            getStr(1));
                        break;
                    case OP_XML_DUMP_ELEMENT_DECL:
                        if (node != NULL && node->type != XML_ELEMENT_DECL)
                            node = NULL;
                        xmlDumpElementDecl(buffer, (xmlElementPtr) node);
                        break;
                    case OP_XML_DUMP_ATTRIBUTE_DECL:
                        if (node != NULL && node->type != XML_ATTRIBUTE_DECL)
                            node = NULL;
                        xmlDumpAttributeDecl(buffer, (xmlAttributePtr) node);
                        break;
                    case OP_XML_DUMP_NOTATION_DECL:
                        /* TODO */
                        break;
                    case OP_XML_DUMP_ENTITY_DECL:
                        if (node != NULL && node->type != XML_ENTITY_DECL)
                            node = NULL;
                        xmlDumpEntityDecl(buffer, (xmlEntityPtr) node);
                        break;
                    case OP_XML_DUMP_ELEMENT_TABLE: {
                        xmlElementTablePtr table;

                        table = node != NULL && node->type == XML_DTD_NODE ?
                                ((xmlDtdPtr) node)->elements :
                                NULL;
                        xmlDumpElementTable(buffer, table);
                        break;
                    }
                    case OP_XML_DUMP_ATTRIBUTE_TABLE: {
                        xmlAttributeTablePtr table;

                        table = node != NULL && node->type == XML_DTD_NODE ?
                                ((xmlDtdPtr) node)->attributes :
                                NULL;
                        xmlDumpAttributeTable(buffer, table);
                        break;
                    }
                    case OP_XML_DUMP_NOTATION_TABLE: {
                        xmlNotationTablePtr table;

                        table = node != NULL && node->type == XML_DTD_NODE ?
                                ((xmlDtdPtr) node)->notations :
                                NULL;
                        xmlDumpNotationTable(buffer, table);
                        break;
                    }
                    case OP_XML_DUMP_ENTITIES_TABLE: {
                        xmlEntitiesTablePtr table;

                        table = node != NULL && node->type == XML_DTD_NODE ?
                                ((xmlDtdPtr) node)->entities :
                                NULL;
                        xmlDumpEntitiesTable(buffer, table);
                        break;
                    }
#ifdef LIBXML_HTML_ENABLED
                    case OP_HTML_NODE_DUMP:
                        res = htmlNodeDump(buffer, doc, node);
                        break;
#endif /* LIBXML_HTML_ENABLED */
                }

                dump = xmlBufferDetach(buffer);
                if (res == 0 && dump != NULL)
                    oomReport = 0;
                moveStr(0, dump);
                xmlBufferFree(buffer);
                endOp();
                break;
            }

            case OP_XML_SAVE_FILE_TO:
            case OP_XML_SAVE_FORMAT_FILE_TO:
            case OP_XML_NODE_DUMP_OUTPUT:
            case OP_HTML_DOC_CONTENT_DUMP_OUTPUT:
            case OP_HTML_DOC_CONTENT_DUMP_FORMAT_OUTPUT:
            case OP_HTML_NODE_DUMP_OUTPUT:
            case OP_HTML_NODE_DUMP_FORMAT_OUTPUT: {
                xmlNodePtr node;
                xmlDocPtr doc;
                xmlOutputBufferPtr output;
                const char *encoding;
                int level, format, argsOk, res, closed;

                switch (op) {
                    case OP_XML_SAVE_FILE_TO:
                        startOp("xmlSaveFileTo"); break;
                    case OP_XML_SAVE_FORMAT_FILE_TO:
                        startOp("xmlSaveFormatFileTo"); break;
                    case OP_XML_NODE_DUMP_OUTPUT:
                        startOp("xmlNodeDumpOutput"); break;
                    case OP_HTML_DOC_CONTENT_DUMP_OUTPUT:
                        startOp("htmlDocContentDumpOutput"); break;
                    case OP_HTML_DOC_CONTENT_DUMP_FORMAT_OUTPUT:
                        startOp("htmlDocContentDumpFormatOutput"); break;
                    case OP_HTML_NODE_DUMP_OUTPUT:
                        startOp("htmlNodeDumpOutput"); break;
                    case OP_HTML_NODE_DUMP_FORMAT_OUTPUT:
                        startOp("htmlNodeDumpFormatOutput"); break;
                }

                incStrIdx();
                output = xmlOutputBufferCreateIO(xmlFuzzOutputWrite,
                                                 xmlFuzzOutputClose, NULL, NULL);
                xmlFuzzResetFailure();
                node = getNode(0);
                doc = node ? node->doc : NULL;
                encoding = (const char *) getStr(1);
                level = getInt(0);
                format = getInt(0);
                argsOk = (output != NULL);
                res = 0;
                closed = 0;

                switch (op) {
                    case OP_XML_SAVE_FILE_TO:
                        argsOk &= (doc != NULL);
                        res = xmlSaveFileTo(output, doc, encoding);
                        closed = 1;
                        break;
                    case OP_XML_SAVE_FORMAT_FILE_TO:
                        argsOk &= (doc != NULL);
                        res = xmlSaveFormatFileTo(output, doc, encoding, format);
                        closed = 1;
                        break;
                    case OP_XML_NODE_DUMP_OUTPUT:
                        argsOk &= (node != NULL);
                        xmlNodeDumpOutput(output, doc, node, level, format,
                                          encoding);
                        break;
#ifdef LIBXML_HTML_ENABLED
                    case OP_HTML_DOC_CONTENT_DUMP_OUTPUT:
                        argsOk &= (doc != NULL);
                        htmlDocContentDumpOutput(output, doc, encoding);
                        break;
                    case OP_HTML_DOC_CONTENT_DUMP_FORMAT_OUTPUT:
                        argsOk &= (doc != NULL);
                        htmlDocContentDumpFormatOutput(output, doc, encoding,
                                                       format);
                        break;
                    case OP_HTML_NODE_DUMP_OUTPUT:
                        argsOk &= (node != NULL);
                        htmlNodeDumpOutput(output, doc, node, encoding);
                        break;
                    case OP_HTML_NODE_DUMP_FORMAT_OUTPUT:
                        argsOk &= (node != NULL);
                        htmlNodeDumpFormatOutput(output, doc, node, encoding,
                                                 format);
                        break;
#endif /* LIBXML_HTML_ENABLED */
                }

                if (closed) {
                    if (res >= 0)
                        oomReport = 0;
                    else
                        ioReport = -1;
                    moveStr(0, NULL);
                } else {
                    if (argsOk && !output->error)
                        copyStr(0, xmlBufContent(output->buffer));
                    else
                        moveStr(0, NULL);
                    res = xmlOutputBufferClose(output);
                    oomReport = (res == -XML_ERR_NO_MEMORY);
                    ioReport  = (res == -XML_IO_EIO);
                }
                endOp();
                break;
            }
#endif /* LIBXML_OUTPUT_ENABLED */

            case OP_XML_DOM_WRAP_RECONCILE_NAMESPACES: {
                xmlNodePtr node;
                int res;

                startOp("xmlDOMWrapReconcileNamespaces");
                res = xmlDOMWrapReconcileNamespaces(
                    NULL,
                    node = getNode(0),
                    getInt(0));
                oomReport =
                    (node != NULL &&
                     node->doc != NULL &&
                     node->type == XML_ELEMENT_NODE &&
                     res < 0);
                endOp();
                break;
            }

            case OP_XML_DOM_WRAP_ADOPT_NODE: {
                xmlDOMWrapCtxtPtr ctxt;
                xmlDocPtr doc, destDoc, oldDoc;
                xmlNodePtr node, destParent, oldParent;
                int res;

                startOp("xmlDOMWrapAdoptNode");
                ctxt = xmlDOMWrapNewCtxt();
                doc = getDoc(0);
                node = getNode(1);
                destDoc = getDoc(2);
                destParent = getNode(3);

                if (!isValidChild(destParent, node))
                    destParent = NULL;

                oldParent = node ? node->parent : NULL;
                oldDoc = node ? node->doc : NULL;

                res = xmlDOMWrapAdoptNode(
                    ctxt,
                    doc,
                    node,
                    destDoc,
                    destParent,
                    getInt(0));
                if (ctxt == NULL)
                    oomReport = 1;
                else if (res == 0)
                    oomReport = 0;

                if (node != NULL) {
                    /* Node can reference destParent's namespaces */
                    if (destParent != NULL &&
                        node->parent == NULL &&
                        node->doc == destParent->doc) {
                        if (node->type == XML_ATTRIBUTE_NODE) {
                            xmlNodePtr prop;

                            /* Insert without removing duplicates */
                            node->parent = destParent;
                            prop = (xmlNodePtr) destParent->properties;
                            node->next = prop;
                            if (prop != NULL)
                                prop->prev = node;
                            destParent->properties = (xmlAttrPtr) node;
                        } else if (node->type != XML_TEXT_NODE) {
                            xmlAddChild(destParent, node);
                        }
                    }

                    /* Node can be unlinked and moved to a new document. */
                    if (oldParent != NULL && node->parent != oldParent)
                        dropNode(oldParent);
                    else if (node->doc != oldDoc)
                        dropNode((xmlNodePtr) oldDoc);
                }

                xmlDOMWrapFreeCtxt(ctxt);
                endOp();
                break;
            }

            case OP_XML_DOM_WRAP_REMOVE_NODE: {
                xmlDocPtr doc;
                xmlNodePtr node, oldParent;
                int res;

                startOp("xmlDOMWrapRemoveNode");
                doc = getDoc(0);
                node = getNode(1);
                oldParent = node ? node->parent : NULL;
                res = xmlDOMWrapRemoveNode(NULL, doc, node, 0);
                oomReport =
                    (node != NULL &&
                     doc != NULL &&
                     node->doc == doc &&
                     res < 0);
                if (node != NULL && node->parent != oldParent) {
                    if (fixNs(node) < 0)
                        oomReport = 1;
                    dropNode(oldParent);
                }
                endOp();
                break;
            }

            case OP_XML_DOM_WRAP_CLONE_NODE: {
                xmlDOMWrapCtxtPtr ctxt;
                xmlDocPtr doc, destDoc;
                xmlNodePtr node, destParent, copy = NULL;
                int res;

                startOp("xmlDOMWrapCloneNode");
                incNodeIdx();
                ctxt = xmlDOMWrapNewCtxt();
                doc = getDoc(1);
                node = getNode(2);
                destDoc = getDoc(3);
                destParent = getNode(4);

                if (destParent != NULL &&
                    node != NULL &&
                    !isValidChildType(destParent, node->type))
                    destParent = NULL;

                /* xmlDOMWrapCloneNode returns a garbage node on error. */
                res = xmlDOMWrapCloneNode(
                    ctxt,
                    doc,
                    node,
                    &copy,
                    destDoc,
                    destParent,
                    getInt(0),
                    0);
                if (ctxt == NULL)
                    oomReport = 1;
                else if (res == 0)
                    oomReport = 0;
                copy = checkCopy(copy);

                /* Copy can reference destParent's namespaces */
                if (destParent != NULL && copy != NULL) {
                    if (copy->type == XML_ATTRIBUTE_NODE) {
                        xmlNodePtr prop;

                        /* Insert without removing duplicates */
                        copy->parent = destParent;
                        prop = (xmlNodePtr) destParent->properties;
                        copy->next = prop;
                        if (prop != NULL)
                            prop->prev = copy;
                        destParent->properties = (xmlAttrPtr) copy;
                    } else if (copy->type != XML_TEXT_NODE) {
                        xmlAddChild(destParent, copy);
                    }
                }

                xmlDOMWrapFreeCtxt(ctxt);
                setNode(0, copy);
                break;
            }

            case OP_XML_CHILD_ELEMENT_COUNT:
                startOp("xmlChildElementCount");
                incIntIdx();
                setInt(0, xmlChildElementCount(getNode(0)));
                oomReport = 0;
                break;

            case OP_XML_FIRST_ELEMENT_CHILD:
                startOp("xmlFirstElementChild");
                incNodeIdx();
                setNode(0, xmlFirstElementChild(getNode(1)));
                oomReport = 0;
                break;

            case OP_XML_LAST_ELEMENT_CHILD:
                startOp("xmlLastElementChild");
                incNodeIdx();
                setNode(0, xmlLastElementChild(getNode(1)));
                oomReport = 0;
                break;

            case OP_XML_NEXT_ELEMENT_SIBLING:
                startOp("xmlNextElementSibling");
                incNodeIdx();
                setNode(0, xmlNextElementSibling(getNode(1)));
                oomReport = 0;
                break;

            case OP_XML_PREVIOUS_ELEMENT_SIBLING:
                startOp("xmlPreviousElementSibling");
                incNodeIdx();
                setNode(0, xmlPreviousElementSibling(getNode(1)));
                oomReport = 0;
                break;

            default:
                break;
        }

        xmlFuzzCheckFailureReport(vars->opName, oomReport, ioReport);
    }

    for (i = 0; i < REG_MAX; i++)
        xmlFree(vars->strings[i]);

    for (i = 0; i < REG_MAX; i++) {
        xmlNodePtr node = vars->nodes[i];

        vars->nodes[i] = NULL;
        dropNode(node);
    }

    xmlFuzzInjectFailure(0);
    xmlFuzzDataCleanup();
    xmlResetLastError();
    return(0);
}

size_t
LLVMFuzzerCustomMutator(char *data, size_t size, size_t maxSize,
                        unsigned seed) {
    static const xmlFuzzChunkDesc chunks[] = {
        { 4, XML_FUZZ_PROB_ONE / 10 }, /* failurePos */
        { 0, 0 }
    };

    return xmlFuzzMutateChunks(chunks, data, size, maxSize, seed,
                               LLVMFuzzerMutate);
}

