<commit_before>#include "macro.h"
#include "input.h"
#include "../core/cli.h"
#include "../core/string.h"

#include <assert.h>
#include <string.h>
#include <ctype.h>

#define HASH_TABLE_LENGTH 256

static struct macro
    macro_hash_table[HASH_TABLE_LENGTH];

static int macrocmp(const struct macro *a, const struct macro *b)
{
    int i;

    if (strcmp(a->name.strval, b->name.strval) ||
        a->type != b->type ||
        a->params != b->params)
    {
        return 1;
    }

    for (i = 0; i < a->size; ++i) {
        if (a->replacement[i].param || b->replacement[i].param) {
            if (a->replacement[i].param != b->replacement[i].param) {
                return 1;
            }
        } else if (
            a->replacement[i].token.token != b->replacement[i].token.token ||
            strcmp(a->replacement[i].token.strval,
                b->replacement[i].token.strval))
        {
            return 1;
        }
    }

    return 0;
}

static void hash_node_free(struct macro *ref)
{
    assert(ref);
    if (ref->replacement)
        free(ref->replacement);
    free(ref);
}

static void hash_chain_cleanup(struct macro *ref)
{
    if (ref->hash.next)
        hash_chain_cleanup(ref->hash.next);
    hash_node_free(ref);
}

static void cleanup(void)
{
    int i;
    struct macro *ref;

    for (i = 0; i < HASH_TABLE_LENGTH; ++i) {
        ref = &macro_hash_table[i];
        if (ref->hash.next)
            hash_chain_cleanup(ref->hash.next);
        if (ref->replacement)
            free(ref->replacement);
    }
}

const struct macro *definition(struct token name)
{
    struct macro *ref;
    unsigned long hash, pos;

    if (name.token != IDENTIFIER)
        return NULL;

    hash = djb2_hash(name.strval);
    pos = hash % HASH_TABLE_LENGTH;
    ref = &macro_hash_table[pos];
    if (!ref->name.strval) {
        return NULL;
    }

    while ((ref->hash.val != hash || strcmp(ref->name.strval, name.strval)) &&
            ref->hash.next)
        ref = ref->hash.next;

    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        if (!strcmp(ref->name.strval, "__LINE__")) {
            ref->replacement[0].token.intval = current_file.line;
        }
        return ref;
    }

    return NULL;
}

void define(struct macro macro)
{
    static int clean_on_exit;

    struct macro *ref;
    unsigned long
        hash = djb2_hash(macro.name.strval),
        pos = hash % HASH_TABLE_LENGTH;

    if (!clean_on_exit) {
        atexit(cleanup);
        clean_on_exit = 1;
    }

    ref = &macro_hash_table[pos];
    if (!ref->name.strval) {
        *ref = macro;
        ref->hash.val = hash;
        return;
    }

    while ((ref->hash.val != hash
            || strcmp(ref->name.strval, macro.name.strval)) && ref->hash.next)
        ref = ref->hash.next;

    if (ref->hash.val == hash && !strcmp(ref->name.strval, macro.name.strval)) {
        if (macrocmp(ref, &macro)) {
            error("Redefinition of macro '%s' with different substitution.",
                macro.name.strval);
            exit(1);
        }
        /* Already have this definition, but need to clean up memory that we
         * took ownership of. */
        if (macro.size) {
            free(macro.replacement);
        }
        return;
    }

    assert(!ref->hash.next);
    ref->hash.next = calloc(1, sizeof(*ref));
    ref = ref->hash.next;
    *ref = macro;
    ref->hash.val = hash;
}

void undef(struct token name)
{
    struct macro *ref, *prev;
    unsigned long hash, pos;

    if (name.token != IDENTIFIER)
        return;

    hash = djb2_hash(name.strval);
    pos = hash % HASH_TABLE_LENGTH;
    ref = &macro_hash_table[pos];
    prev = ref;
    if (!ref->name.strval) {
        return;
    }

    /* Special case if matched on static memory. */
    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        prev = ref->hash.next;
        if (prev) {
            *ref = *prev;
            hash_node_free(prev);
        } else {
            if (ref->replacement)
                free(ref->replacement);
            memset(ref, 0, sizeof(*ref));
        }
        return;
    }

    /* Get pointer to match, and predecessor. */
    while ((ref->hash.val != hash || strcmp(ref->name.strval, name.strval))
            && ref->hash.next)
    {
        prev = ref;
        ref = ref->hash.next;
    }

    /* Remove node in middle of list. */
    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        assert(ref != prev);
        prev->hash.next = ref->hash.next;
        hash_node_free(ref);
    }<commit_msg>Fix double free in macro undef<commit_after>#include "macro.h"
#include "input.h"
#include "../core/cli.h"
#include "../core/string.h"

#include <assert.h>
#include <string.h>
#include <ctype.h>

#define HASH_TABLE_LENGTH 1024

static struct macro
    macro_hash_table[HASH_TABLE_LENGTH];

static int macrocmp(const struct macro *a, const struct macro *b)
{
    int i;

    if (strcmp(a->name.strval, b->name.strval) ||
        a->type != b->type ||
        a->params != b->params)
    {
        return 1;
    }

    for (i = 0; i < a->size; ++i) {
        if (a->replacement[i].param || b->replacement[i].param) {
            if (a->replacement[i].param != b->replacement[i].param) {
                return 1;
            }
        } else if (
            a->replacement[i].token.token != b->replacement[i].token.token ||
            strcmp(a->replacement[i].token.strval,
                b->replacement[i].token.strval))
        {
            return 1;
        }
    }

    return 0;
}

static void hash_node_free(struct macro *ref)
{
    assert(ref);
    if (ref->replacement)
        free(ref->replacement);
    free(ref);
}

static void hash_chain_cleanup(struct macro *ref)
{
    if (ref->hash.next)
        hash_chain_cleanup(ref->hash.next);
    hash_node_free(ref);
}

static void cleanup(void)
{
    int i;
    struct macro *ref;

    for (i = 0; i < HASH_TABLE_LENGTH; ++i) {
        ref = &macro_hash_table[i];
        if (ref->hash.next)
            hash_chain_cleanup(ref->hash.next);
        if (ref->replacement)
            free(ref->replacement);
    }
}

const struct macro *definition(struct token name)
{
    struct macro *ref;
    unsigned long hash, pos;

    if (name.token != IDENTIFIER)
        return NULL;

    hash = djb2_hash(name.strval);
    pos = hash % HASH_TABLE_LENGTH;
    ref = &macro_hash_table[pos];
    if (!ref->name.strval) {
        return NULL;
    }

    while ((ref->hash.val != hash || strcmp(ref->name.strval, name.strval)) &&
            ref->hash.next)
        ref = ref->hash.next;

    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        if (!strcmp(ref->name.strval, "__LINE__")) {
            ref->replacement[0].token.intval = current_file.line;
        }
        return ref;
    }

    return NULL;
}

void define(struct macro macro)
{
    static int clean_on_exit;

    struct macro *ref;
    unsigned long
        hash = djb2_hash(macro.name.strval),
        pos = hash % HASH_TABLE_LENGTH;

    if (!clean_on_exit) {
        atexit(cleanup);
        clean_on_exit = 1;
    }

    ref = &macro_hash_table[pos];
    if (!ref->name.strval) {
        *ref = macro;
        ref->hash.val = hash;
        return;
    }

    while ((ref->hash.val != hash
            || strcmp(ref->name.strval, macro.name.strval)) && ref->hash.next)
        ref = ref->hash.next;

    if (ref->hash.val == hash && !strcmp(ref->name.strval, macro.name.strval)) {
        if (macrocmp(ref, &macro)) {
            error("Redefinition of macro '%s' with different substitution.",
                macro.name.strval);
            exit(1);
        }
        /* Already have this definition, but need to clean up memory that we
         * took ownership of. */
        if (macro.size) {
            free(macro.replacement);
        }
        return;
    }

    assert(!ref->hash.next);
    ref->hash.next = calloc(1, sizeof(*ref));
    ref = ref->hash.next;
    *ref = macro;
    ref->hash.val = hash;
}

void undef(struct token name)
{
    struct macro *ref, *prev;
    unsigned long hash, pos;

    if (name.token != IDENTIFIER)
        return;

    hash = djb2_hash(name.strval);
    pos = hash % HASH_TABLE_LENGTH;
    ref = &macro_hash_table[pos];
    prev = ref;
    if (!ref->name.strval) {
        return;
    }

    /* Special case if found in static buffer. */
    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        prev = ref->hash.next;
        if (ref->replacement)
            free(ref->replacement);
        memset(ref, 0, sizeof(*ref));
        if (prev) {
            *ref = *prev;
            free(prev);
        }
        return;
    }

    /* Get pointer to match, and predecessor. */
    while ((ref->hash.val != hash || strcmp(ref->name.strval, name.strval))
            && ref->hash.next)
    {
        prev = ref;
        ref = ref->hash.next;
    }

    /* Remove node in middle of list. */
    if (ref->hash.val == hash && !strcmp(ref->name.strval, name.strval)) {
        assert(ref != prev);
        prev->hash.next = ref->hash.next;
        hash_node_free(ref);
    }
{'fraction_non_alphanumeric': 0.1515844495262986, 'fraction_numerical': 0.0038113906130894043, 'mean_word_length': 2.002288329519451, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 125, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7180541', 'n_tokens_mistral': 3158, 'n_tokens_neox': 3063, 'n_words': 664}