root/scripts/gendwarfksyms/types.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2024 Google LLC
 */

#define _GNU_SOURCE
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

#include "gendwarfksyms.h"

static struct cache expansion_cache;

/*
 * A simple linked list of shared or owned strings to avoid copying strings
 * around when not necessary.
 */
struct type_list_entry {
        const char *str;
        void *owned;
        struct list_head list;
};

static void type_list_free(struct list_head *list)
{
        struct type_list_entry *entry;
        struct type_list_entry *tmp;

        list_for_each_entry_safe(entry, tmp, list, list) {
                if (entry->owned)
                        free(entry->owned);
                free(entry);
        }

        INIT_LIST_HEAD(list);
}

static int type_list_append(struct list_head *list, const char *s, void *owned)
{
        struct type_list_entry *entry;

        if (!s)
                return 0;

        entry = xmalloc(sizeof(*entry));
        entry->str = s;
        entry->owned = owned;
        list_add_tail(&entry->list, list);

        return strlen(entry->str);
}

static void type_list_write(struct list_head *list, FILE *file)
{
        struct type_list_entry *entry;

        list_for_each_entry(entry, list, list) {
                if (entry->str)
                        checkp(fputs(entry->str, file));
        }
}

/*
 * An expanded type string in symtypes format.
 */
struct type_expansion {
        char *name;
        size_t len;
        struct list_head expanded;
        struct hlist_node hash;
};

static void type_expansion_init(struct type_expansion *type)
{
        type->name = NULL;
        type->len = 0;
        INIT_LIST_HEAD(&type->expanded);
}

static inline void type_expansion_free(struct type_expansion *type)
{
        free(type->name);
        type->name = NULL;
        type->len = 0;
        type_list_free(&type->expanded);
}

static void type_expansion_append(struct type_expansion *type, const char *s,
                                  void *owned)
{
        type->len += type_list_append(&type->expanded, s, owned);
}

/*
 * type_map -- the longest expansions for each type.
 *
 * const char *name -> struct type_expansion *
 */
#define TYPE_HASH_BITS 12
static HASHTABLE_DEFINE(type_map, 1 << TYPE_HASH_BITS);

static int __type_map_get(const char *name, struct type_expansion **res)
{
        struct type_expansion *e;

        hash_for_each_possible(type_map, e, hash, hash_str(name)) {
                if (!strcmp(name, e->name)) {
                        *res = e;
                        return 0;
                }
        }

        return -1;
}

static struct type_expansion *type_map_add(const char *name,
                                           struct type_expansion *type)
{
        struct type_expansion *e;

        if (__type_map_get(name, &e)) {
                e = xmalloc(sizeof(*e));
                type_expansion_init(e);
                e->name = xstrdup(name);

                hash_add(type_map, &e->hash, hash_str(e->name));

                if (dump_types)
                        debug("adding %s", e->name);
        } else {
                /* Use the longest available expansion */
                if (type->len <= e->len)
                        return e;

                type_list_free(&e->expanded);

                if (dump_types)
                        debug("replacing %s", e->name);
        }

        /* Take ownership of type->expanded */
        list_replace_init(&type->expanded, &e->expanded);
        e->len = type->len;

        if (dump_types) {
                checkp(fputs(e->name, stderr));
                checkp(fputs(" ", stderr));
                type_list_write(&e->expanded, stderr);
                checkp(fputs("\n", stderr));
        }

        return e;
}

static void type_parse(const char *name, const char *str,
                       struct type_expansion *type);

static int type_map_get(const char *name, struct type_expansion **res)
{
        struct type_expansion type;
        const char *override;

        if (!__type_map_get(name, res))
                return 0;

        /*
         * If die_map didn't contain a type, we might still have
         * a type_string kABI rule that defines it.
         */
        if (stable && kabi_get_type_string(name, &override)) {
                type_expansion_init(&type);
                type_parse(name, override, &type);
                *res = type_map_add(name, &type);
                type_expansion_free(&type);
                return 0;
        }

        return -1;
}

static int cmp_expansion_name(const void *p1, const void *p2)
{
        struct type_expansion *const *e1 = p1;
        struct type_expansion *const *e2 = p2;

        return strcmp((*e1)->name, (*e2)->name);
}

static void type_map_write(FILE *file)
{
        struct type_expansion *e;
        struct hlist_node *tmp;
        struct type_expansion **es;
        size_t count = 0;
        size_t i = 0;

        if (!file)
                return;

        hash_for_each_safe(type_map, e, tmp, hash)
                ++count;
        es = xmalloc(count * sizeof(*es));
        hash_for_each_safe(type_map, e, tmp, hash)
                es[i++] = e;

        qsort(es, count, sizeof(*es), cmp_expansion_name);

        for (i = 0; i < count; ++i) {
                checkp(fputs(es[i]->name, file));
                checkp(fputs(" ", file));
                type_list_write(&es[i]->expanded, file);
                checkp(fputs("\n", file));
        }

        free(es);
}

static void type_map_free(void)
{
        struct type_expansion *e;
        struct hlist_node *tmp;

        hash_for_each_safe(type_map, e, tmp, hash) {
                type_expansion_free(e);
                free(e);
        }

        hash_init(type_map);
}

/*
 * CRC for a type, with an optional fully expanded type string for
 * debugging.
 */
struct version {
        struct type_expansion type;
        unsigned long crc;
};

static void version_init(struct version *version)
{
        version->crc = crc32(0, NULL, 0);
        type_expansion_init(&version->type);
}

static void version_free(struct version *version)
{
        type_expansion_free(&version->type);
}

static void version_add(struct version *version, const char *s)
{
        version->crc = crc32(version->crc, (void *)s, strlen(s));
        if (dump_versions)
                type_expansion_append(&version->type, s, NULL);
}

/*
 * Type reference format: <prefix>#<name>, where prefix:
 *      s -> structure
 *      u -> union
 *      e -> enum
 *      t -> typedef
 *
 * Names with spaces are additionally wrapped in single quotes.
 */
static inline bool is_type_prefix(const char *s)
{
        return (s[0] == 's' || s[0] == 'u' || s[0] == 'e' || s[0] == 't') &&
               s[1] == '#';
}

static char get_type_prefix(int tag)
{
        switch (tag) {
        case DW_TAG_class_type:
        case DW_TAG_structure_type:
                return 's';
        case DW_TAG_union_type:
                return 'u';
        case DW_TAG_enumeration_type:
                return 'e';
        case DW_TAG_typedef_type:
                return 't';
        default:
                return 0;
        }
}

static char *get_type_name(struct die *cache)
{
        const char *quote;
        char prefix;
        char *name;

        if (cache->state == DIE_INCOMPLETE) {
                warn("found incomplete cache entry: %p", cache);
                return NULL;
        }
        if (cache->state == DIE_SYMBOL || cache->state == DIE_FQN)
                return NULL;
        if (!cache->fqn || !*cache->fqn)
                return NULL;

        prefix = get_type_prefix(cache->tag);
        if (!prefix)
                return NULL;

        /* Wrap names with spaces in single quotes */
        quote = strstr(cache->fqn, " ") ? "'" : "";

        /* <prefix>#<type_name>\0 */
        if (asprintf(&name, "%c#%s%s%s", prefix, quote, cache->fqn, quote) < 0)
                error("asprintf failed for '%s'", cache->fqn);

        return name;
}

static void __calculate_version(struct version *version,
                                struct type_expansion *type)
{
        struct type_list_entry *entry;
        struct type_expansion *e;

        /* Calculate a CRC over an expanded type string */
        list_for_each_entry(entry, &type->expanded, list) {
                if (is_type_prefix(entry->str)) {
                        if (type_map_get(entry->str, &e))
                                error("unknown type reference to '%s' when expanding '%s'",
                                      entry->str, type->name);

                        /*
                         * It's sufficient to expand each type reference just
                         * once to detect changes.
                         */
                        if (cache_was_expanded(&expansion_cache, e)) {
                                version_add(version, entry->str);
                        } else {
                                cache_mark_expanded(&expansion_cache, e);
                                __calculate_version(version, e);
                        }
                } else {
                        version_add(version, entry->str);
                }
        }
}

static void calculate_version(struct version *version,
                              struct type_expansion *type)
{
        version_init(version);
        __calculate_version(version, type);
        cache_free(&expansion_cache);
}

static void __type_expand(struct die *cache, struct type_expansion *type)
{
        struct die_fragment *df;
        struct die *child;
        char *name;

        list_for_each_entry(df, &cache->fragments, list) {
                switch (df->type) {
                case FRAGMENT_STRING:
                        type_expansion_append(type, df->data.str, NULL);
                        break;
                case FRAGMENT_DIE:
                        /* Use a complete die_map expansion if available */
                        if (__die_map_get(df->data.addr, DIE_COMPLETE,
                                          &child) &&
                            __die_map_get(df->data.addr, DIE_UNEXPANDED,
                                          &child))
                                error("unknown child: %" PRIxPTR,
                                      df->data.addr);

                        name = get_type_name(child);
                        if (name)
                                type_expansion_append(type, name, name);
                        else
                                __type_expand(child, type);

                        break;
                case FRAGMENT_LINEBREAK:
                        /*
                         * Keep whitespace in the symtypes format, but avoid
                         * repeated spaces.
                         */
                        if (list_is_last(&df->list, &cache->fragments) ||
                            list_next_entry(df, list)->type !=
                                    FRAGMENT_LINEBREAK)
                                type_expansion_append(type, " ", NULL);
                        break;
                default:
                        error("empty die_fragment in %p", cache);
                }
        }
}

static void type_expand(const char *name, struct die *cache,
                        struct type_expansion *type)
{
        const char *override;

        type_expansion_init(type);

        if (stable && kabi_get_type_string(name, &override))
                type_parse(name, override, type);
        else
                __type_expand(cache, type);
}

static void type_parse(const char *name, const char *str,
                       struct type_expansion *type)
{
        char *fragment;
        size_t start = 0;
        size_t end;
        size_t pos;

        if (!*str)
                error("empty type string override for '%s'", name);

        for (pos = 0; str[pos]; ++pos) {
                bool empty;
                char marker = ' ';

                if (!is_type_prefix(&str[pos]))
                        continue;

                end = pos + 2;

                /*
                 * Find the end of the type reference. If the type name contains
                 * spaces, it must be in single quotes.
                 */
                if (str[end] == '\'') {
                        marker = '\'';
                        ++end;
                }
                while (str[end] && str[end] != marker)
                        ++end;

                /* Check that we have a non-empty type name */
                if (marker == '\'') {
                        if (str[end] != marker)
                                error("incomplete %c# type reference for '%s' (string : '%s')",
                                      str[pos], name, str);
                        empty = end == pos + 3;
                        ++end;
                } else {
                        empty = end == pos + 2;
                }
                if (empty)
                        error("empty %c# type name for '%s' (string: '%s')",
                              str[pos], name, str);

                /* Append the part of the string before the type reference */
                if (pos > start) {
                        fragment = xstrndup(&str[start], pos - start);
                        type_expansion_append(type, fragment, fragment);
                }

                /*
                 * Append the type reference -- note that if the reference
                 * is invalid, i.e. points to a non-existent type, we will
                 * print out an error when calculating versions.
                 */
                fragment = xstrndup(&str[pos], end - pos);
                type_expansion_append(type, fragment, fragment);

                start = end;
                pos = end - 1;
        }

        /* Append the rest of the type string, if there's any left */
        if (str[start])
                type_expansion_append(type, &str[start], NULL);
}

static void expand_type(struct die *cache, void *arg)
{
        struct type_expansion type;
        char *name;

        if (cache->mapped)
                return;

        cache->mapped = true;

        /*
         * Skip unexpanded die_map entries if there's a complete
         * expansion available for this DIE.
         */
        if (cache->state == DIE_UNEXPANDED &&
            !__die_map_get(cache->addr, DIE_COMPLETE, &cache)) {
                if (cache->mapped)
                        return;

                cache->mapped = true;
        }

        name = get_type_name(cache);
        if (!name)
                return;

        debug("%s", name);

        type_expand(name, cache, &type);
        type_map_add(name, &type);
        type_expansion_free(&type);
        free(name);
}

static void expand_symbol(struct symbol *sym, void *arg)
{
        struct type_expansion type;
        struct version version;
        struct die *cache;

        /*
         * No need to expand again unless we want a symtypes file entry
         * for the symbol. Note that this means `sym` has the same address
         * as another symbol that was already processed.
         */
        if (!symtypes && sym->state == SYMBOL_PROCESSED)
                return;

        if (__die_map_get(sym->die_addr, DIE_SYMBOL, &cache))
                return; /* We'll warn about missing CRCs later. */

        type_expand(sym->name, cache, &type);

        /* If the symbol already has a version, don't calculate it again. */
        if (sym->state != SYMBOL_PROCESSED) {
                calculate_version(&version, &type);
                symbol_set_crc(sym, version.crc);
                debug("%s = %lx", sym->name, version.crc);

                if (dump_versions) {
                        checkp(fputs(sym->name, stderr));
                        checkp(fputs(" ", stderr));
                        type_list_write(&version.type.expanded, stderr);
                        checkp(fputs("\n", stderr));
                }

                version_free(&version);
        }

        /* These aren't needed in type_map unless we want a symtypes file. */
        if (symtypes)
                type_map_add(sym->name, &type);

        type_expansion_free(&type);
}

void generate_symtypes_and_versions(FILE *file)
{
        cache_init(&expansion_cache);

        /*
         * die_map processing:
         *
         *   1. die_map contains all types referenced in exported symbol
         *      signatures, but can contain duplicates just like the original
         *      DWARF, and some references may not be fully expanded depending
         *      on how far we processed the DIE tree for that specific symbol.
         *
         *      For each die_map entry, find the longest available expansion,
         *      and add it to type_map.
         */
        die_map_for_each(expand_type, NULL);

        /*
         *   2. For each exported symbol, expand the die_map type, and use
         *      type_map expansions to calculate a symbol version from the
         *      fully expanded type string.
         */
        symbol_for_each(expand_symbol, NULL);

        /*
         *   3. If a symtypes file is requested, write type_map contents to
         *      the file.
         */
        type_map_write(file);
        type_map_free();
}