/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright 1996-2025 The NASM Authors - All Rights Reserved */

/*
 * error.c - error message handling routines for the assembler
 */

#include "compiler.h"
#include "nasmlib.h"
#include "error.h"

unsigned int debug_nasm;        /* Debugging messages? */
unsigned int opt_verbose_info;  /* Informational messages? */

/* Common function body */
#define nasm_do_error(_sev,_flags)				\
    do {                                                        \
        va_list ap;                                             \
        va_start(ap, fmt);                                      \
        if ((_sev) >= ERR_CRITICAL)                             \
            nasm_verror_critical((_sev)|(_flags), fmt, ap);     \
        else							\
            nasm_verror((_sev)|(_flags), fmt, ap);              \
        va_end(ap);                                             \
        if ((_sev) >= ERR_FATAL)                                \
            abort();                                            \
    } while (0)

/*
 * This is the generic function to use when the error type is not
 * known a priori. For ERR_DEBUG and ERR_INFO the level can be
 * included by
 */
void nasm_error(errflags flags, const char *fmt, ...)
{
    nasm_do_error(flags & ERR_MASK, flags);
}

#define nasm_err_helpers(_type, _name, _sev)				\
_type nasm_ ## _name ## f (errflags flags, const char *fmt, ...)	\
{									\
	nasm_do_error(_sev, flags);					\
}									\
_type nasm_ ## _name (const char *fmt, ...)				\
{									\
	nasm_do_error(_sev, 0);						\
}

nasm_err_helpers(void,       listmsg,  ERR_LISTMSG)
nasm_err_helpers(void,       note,     ERR_NOTE)
nasm_err_helpers(void,       nonfatal, ERR_NONFATAL)
nasm_err_helpers(fatal_func, fatal,    ERR_FATAL)
nasm_err_helpers(fatal_func, critical, ERR_CRITICAL)
nasm_err_helpers(fatal_func, panic,    ERR_PANIC)

/*
 * Strongly discourage warnings without level by require flags on warnings.
 * This means nasm_warn() is the equivalent of the -f variants of the
 * other ones.
 *
 * This is wrapped in a macro to be able to elide it if the warning is
 * disabled, hence the extra underscore.
 */
void nasm_warn_(errflags flags, const char *fmt, ...)
{
    nasm_do_error(ERR_WARNING, flags);
}

/*
 * nasm_info() and nasm_debug() takes mandatory enabling levels.
 */
void nasm_info_(unsigned int level, const char *fmt, ...)
{
    if (info_level(level))
        nasm_do_error(ERR_INFO, LEVEL(level));
}

void nasm_debug_(unsigned int level, const char *fmt, ...)
{
    if (debug_level(level))
        nasm_do_error(ERR_DEBUG, LEVEL(level));
}

fatal_func nasm_panic_from_macro(const char *func, const char *file, int line)
{
    if (!func)
        func = "<unknown>";

    nasm_panic("internal error in %s at %s:%d\n", func, file, line);
}

fatal_func nasm_assert_failed(const char *msg, const char *func,
                              const char *file, int line)
{
    if (!func)
        func = "<unknown>";

    nasm_panic("assertion %s failed in %s at %s:%d", msg, func, file, line);
}


/*
 * Warning stack management. Note that there is an implicit "push"
 * after the command line has been parsed, but this particular push
 * cannot be popped.
 */
struct warning_stack {
	struct warning_stack *next;
	uint8_t state[sizeof warning_state];
};
static struct warning_stack *warning_stack, *warning_state_init;

/* Push the warning status onto the warning stack */
void push_warnings(void)
{
	struct warning_stack *ws;

	ws = nasm_malloc(sizeof *ws);
	memcpy(ws->state, warning_state, sizeof warning_state);
	ws->next = warning_stack;
	warning_stack = ws;
}

/* Pop the warning status off the warning stack */
void pop_warnings(void)
{
	struct warning_stack *ws = warning_stack;

	memcpy(warning_state, ws->state, sizeof warning_state);
	if (!ws->next) {
		nasm_warn(WARN_WARN_STACK_EMPTY, "warning stack empty");
	} else {
		warning_stack = ws->next;
		nasm_free(ws);
	}
}

/* Call after the command line is parsed, but before the first pass */
void init_warnings(void)
{
	push_warnings();
	warning_state_init = warning_stack;
}


/* Call after each pass */
void reset_warnings(void)
{
	struct warning_stack *ws = warning_stack;

	/* Unwind the warning stack. We do NOT delete the last entry! */
	while (ws->next) {
		struct warning_stack *wst = ws;
		ws = ws->next;
		nasm_free(wst);
	}
	warning_stack = ws;
	memcpy(warning_state, ws->state, sizeof warning_state);
}

/*
 * This is called when processing a -w or -W option, or a warning directive.
 * Returns ok if the action was successful.
 *
 * Special pseudo-warnings (see warnings.dat):
 * all   - all possible warnings
 * other - any warning not specifically assigned a class
 */
bool set_warning_status(const char *value)
{
	enum warn_action { WID_OFF, WID_ON, WID_RESET };
	enum warn_action action;
        const struct warning_alias *wa;
        size_t vlen;
	bool ok = false;
	uint8_t mask;

	value = nasm_skip_spaces(value);

	switch (*value) {
	case '-':
		action = WID_OFF;
		value++;
		break;
	case '+':
		action = WID_ON;
		value++;
		break;
	case '*':
		action = WID_RESET;
		value++;
		break;
	case 'N':
	case 'n':
		if (!nasm_strnicmp(value, "no-", 3)) {
			action = WID_OFF;
			value += 3;
			break;
		} else if (!nasm_stricmp(value, "none")) {
			action = WID_OFF;
			value = NULL;
			break;
		}
		/* else fall through */
	default:
		action = WID_ON;
		break;
	}

	mask = WARN_ST_ENABLED;

	if (value && !nasm_strnicmp(value, "error", 5)) {
		switch (value[5]) {
		case '=':
			mask = WARN_ST_ERROR;
			value += 6;
			break;
		case '\0':
			mask = WARN_ST_ERROR;
			value = NULL;
			break;
		default:
			/* Just an accidental prefix? */
			break;
		}
	}

	if (value && !nasm_stricmp(value, "all"))
		value = NULL;

        vlen = value ? strlen(value) : 0;

	/*
         * This is inefficient, but it shouldn't matter.
         * Note: warning_alias[0] is "all".
         */
	for (wa = warning_alias+1;
             wa < &warning_alias[NUM_WARNING_ALIAS]; wa++) {
            enum warn_index i = wa->warning;

            if (value) {
                char sep;

                if (nasm_strnicmp(value, wa->name, vlen))
                    continue;   /* Not a prefix */

                sep = wa->name[vlen];
                if (sep != '\0' && sep != '-')
                    continue;   /* Not a valid prefix */
            }

            ok = true; /* At least one action taken */
            switch (action) {
            case WID_OFF:
                warning_state[i] &= ~mask;
                break;
            case WID_ON:
                warning_state[i] |= mask;
                break;
            case WID_RESET:
                warning_state[i] &= ~mask;
                warning_state[i] |= warning_state_init->state[i] & mask;
                break;
            }
        }

        if (!ok && value) {
            nasm_warn(WARN_UNKNOWN_WARNING, "unknown warning name: %s", value);
	}

	return ok;
}

/*
 * The various error type prefixes
 */
const char *error_pfx(errflags severity)
{
    switch (severity & ERR_MASK) {
    case ERR_LISTMSG:
        return ";;; ";
    case ERR_NOTE:
        return "note: ";
    case ERR_DEBUG:
        return "debug: ";
    case ERR_INFO:
        return "info: ";
    case ERR_WARNING:
        return "warning: ";
    case ERR_NONFATAL:
        return "error: ";
    case ERR_FATAL:
        return "fatal: ";
    case ERR_CRITICAL:
        return "critical: ";
    case ERR_PANIC:
        return "panic: ";
    default:
        return "internal error: ";
    }
}
