/* 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" #include "listing.h" #include "srcfile.h" #include "strlist.h" struct error_format { const char *beforeline; /* Before line number, if present */ const char *afterline; /* After line number, if present */ const char *beforemsg; /* Before actual message */ }; enum error_formats { ERRFMT_GNU, ERRFMT_MSVC }; static const struct error_format errfmts[] = { { ":", "", ": " }, /* ERRFMT_GNU */ { "(", ")", " : " } /* ERRFMT_MSVC */ }; static const struct error_format *errfmt = &errfmts[ERRFMT_GNU]; static void usage(void); /* * 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; static struct strlist *warn_list; /* 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); } } /* Called after the command line is parsed, but before the first pass */ static void init_warnings(void) { push_warnings(); warning_state_init = warning_stack; } void error_init(void) { erropt.worst = 0; init_warnings(); } /* Called before each pass. Buffer warnings if "final" is false. */ void error_pass_start(bool final) { nasm_assert(!warn_list); erropt.worst = 0; if (!final) warn_list = strlist_alloc(false); } /* * Called after the completion of each pass. This MUST preserve erropt.worst! */ static 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); } void error_pass_end(void) { strlist_free(&warn_list); reset_warnings(); } /* * 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: "; } } static bool skip_this_pass(errflags severity) { errflags type = severity & ERR_MASK; /* * See if it's a pass-specific error or warning which should be skipped. * We can never skip fatal errors as by definition they cannot be * resumed from. */ if (type >= ERR_FATAL) return false; /* * ERR_LISTMSG and ERR_NOTE messages are always skipped; the list * file receives them anyway as this function is not consulted for * sending to the list file. */ if (type <= ERR_NOTE) return true; /* * This message is not applicable unless it is the last pass we * are going to execute; this can be either the final * code-generation pass or the single pass executed in * preproc-only mode. */ return (severity & ERR_PASS2) && !pass_final_or_preproc(); } /** * check for suppressed message (usually warnings or notes) * * @param severity the severity of the warning or error * @return true if we should abort error/warning printing */ static bool is_suppressed(errflags flags) { const errflags severity = flags & ERR_MASK; const errflags level = WARN_IDX(flags); if (severity >= ERR_FATAL) { /* Fatal errors or higher can never be suppressed */ return false; } if (flags & erropt.never) return true; switch (severity) { case ERR_WARNING: if (!(warning_state[level] & WARN_ST_ENABLED)) return true; break; case ERR_INFO: if (!info_level(level)) return true; break; case ERR_DEBUG: if (!debug_level(level)) return true; break; default: break; } /* Suppressed by the preprocessor? */ if (!(flags & ERR_PP_LISTMACRO)) return pp_suppress_error(flags); return false; } /** * Return the true error type (the ERR_MASK part) of the given * severity, accounting for warnings that may need to be promoted to * error. * * @param severity the severity of the warning or error * @return true if we should error out */ static errflags pure_func true_error_type(errflags severity) { const uint8_t warn_is_err = WARN_ST_ENABLED|WARN_ST_ERROR; int type; type = severity & ERR_MASK; if (type == ERR_WARNING) { /* Promote warning to error? */ uint8_t state = warning_state[WARN_IDX(severity)]; if ((state & warn_is_err) == warn_is_err) type = ERR_NONFATAL; } return type; } static const char no_file_name[] = "nasm"; /* What to print if no file name */ /* * For fatal/critical/panic errors, kill this process. * * For FATAL errors doing cleanups, tidying up the list process, * and so in is acceptable. * * For CRITICAL errors, minimize dependencies on memory allocation * and/or having a system valid state. * * For PANIC, if abort_on_panic is set, abort without any other action. */ static_fatal_func die_hard(errflags true_type, errflags severity) { if (true_type < ERR_PANIC || !erropt.abort_on_panic) { if (true_type < ERR_CRITICAL) { /* FATAL shutdown, general cleanup actions are valid */ print_final_report(true); lfmt->cleanup(); } fflush(NULL); close_output(true); if (severity & ERR_USAGE) usage(); /* Terminate immediately (exit closes any still open files) */ exit(true_type - ERR_FATAL + 1); } /* * abort() shouldn't ever return, but be paranoid about this, * plus it helps some compilers clue in to the fact that this * function can never, ever return. */ while (1) abort(); } /* * Returns the struct src_location appropriate for use, after some * potential filename mangling. */ static struct src_location error_where(errflags severity) { struct src_location where; if (severity & ERR_NOFILE) { where.filename = NULL; where.lineno = 0; } else { where = src_where_error(); if (!where.filename) { where.filename = inname && inname[0] ? inname : outname && outname[0] ? outname : NULL; where.lineno = 0; } } return where; } /* * error reporting for critical and panic errors: minimize * the amount of system dependencies for getting a message out, * and in particular try to avoid memory allocations. */ fatal_func nasm_verror_critical(errflags severity, const char *fmt, va_list args) { struct src_location where; errflags true_type = severity & ERR_MASK; static bool been_here = false; while (unlikely(been_here)) abort(); /* Recursive critical error... just die */ been_here = true; erropt.worst = true_type; where = error_where(severity); if (!where.filename) where.filename = no_file_name; fputs(error_pfx(severity), erropt.file); fputs(where.filename, erropt.file); if (where.lineno) { fprintf(erropt.file, "%s%"PRId32"%s", errfmt->beforeline, where.lineno, errfmt->afterline); } fputs(errfmt->beforemsg, erropt.file); vfprintf(erropt.file, fmt, args); fputc('\n', erropt.file); die_hard(true_type, severity); unreachable(); } /** * Stack of tentative error hold lists. */ struct nasm_errtext { struct nasm_errtext *next; char *msg; /* Owned by this structure */ struct src_location where; /* Owned by the srcfile system */ errflags severity; errflags true_type; int c_errno; /* Saved errno (for ERR_PERROR) */ }; struct nasm_errhold { struct nasm_errhold *up; struct nasm_errtext *head, **tail; }; static struct strlist *warn_list; static struct nasm_errhold *errhold_stack; static void nasm_free_error(struct nasm_errtext *et) { nasm_free(et->msg); nasm_free(et); } static void nasm_issue_error(struct nasm_errtext *et); struct nasm_errhold *nasm_error_hold_push(void) { struct nasm_errhold *eh; nasm_new(eh); eh->up = errhold_stack; eh->tail = &eh->head; errhold_stack = eh; return eh; } /* Pop an error hold. Returns the highest severity issued or dropped. */ errflags nasm_error_hold_pop(struct nasm_errhold *eh, bool issue) { struct nasm_errtext *et, *etmp; errflags worst = 0; /* * Allow calling with a null argument saying no hold in the first place. */ if (!eh) return worst; /* This *must* be the current top of the errhold stack */ nasm_assert(eh == errhold_stack); if (eh->head) { if (issue) { if (eh->up) { /* Commit the current hold list to the previous level */ *eh->up->tail = eh->head; eh->up->tail = eh->tail; } else { /* Issue errors */ list_for_each_safe(et, etmp, eh->head) { if (et->true_type > worst) worst = et->true_type; nasm_issue_error(et); } } } else { /* Free the list, drop errors */ list_for_each_safe(et, etmp, eh->head) { if (et->true_type > worst) worst = et->true_type; nasm_free_error(et); } } } errhold_stack = eh->up; nasm_free(eh); return worst; } /** * common error reporting * This is the common back end of the error reporting schemes currently * implemented. It prints the nature of the warning and then the * specific error message to erropt.file and may or may not return. It * doesn't return if the error severity is a "panic" or "debug" type. * * @param severity the severity of the warning or error * @param fmt the printf style format string */ void nasm_verror(errflags severity, const char *fmt, va_list args) { struct nasm_errtext *et; int c_errno = errno; errflags true_type = true_error_type(severity); if (true_type >= ERR_CRITICAL) { nasm_verror_critical(severity, fmt, args); abort(); } if (is_suppressed(severity)) return; nasm_new(et); et->c_errno = c_errno; et->severity = severity; et->true_type = true_type; et->msg = nasm_vasprintf(fmt, args); et->where = error_where(severity); if (errhold_stack && true_type <= ERR_NONFATAL) { /* It is a tentative error */ *errhold_stack->tail = et; errhold_stack->tail = &et->next; } else { nasm_issue_error(et); } /* * Don't do this before then, if we do, we lose messages in the list * file, as the list file is only generated in the last pass. */ if (skip_this_pass(severity)) return; if (!(severity & (ERR_HERE|ERR_PP_LISTMACRO))) pp_error_list_macros(severity); } /* * Actually print, list and take action on an error */ static void nasm_issue_error(struct nasm_errtext *et) { const char *pfx; char warnsuf[64]; /* Warning suffix */ char linestr[64]; /* Formatted line number if applicable */ const errflags severity = et->severity; const errflags true_type = et->true_type; const struct src_location where = et->where; const char *cerrsep = ""; const char *cerrmsg = ""; bool buffer = true_type < ERR_NONFATAL || (severity & ERR_HOLD); if (severity & ERR_NO_SEVERITY) pfx = ""; else pfx = error_pfx(true_type); *warnsuf = 0; if (!(severity & (ERR_HERE|ERR_PP_LISTMACRO))) { switch (severity & ERR_MASK) { case ERR_WARNING: { const unsigned int level = WARN_IDX(severity); snprintf(warnsuf, sizeof warnsuf, " [-w+%s%s]", (true_type >= ERR_NONFATAL) ? "error=" : "", warning_name[level]); break; } case ERR_DEBUG: snprintf(warnsuf, sizeof warnsuf, " [--debug=%u]", erropt.debug_nasm); break; case ERR_INFO: snprintf(warnsuf, sizeof warnsuf, " [--info=%u]", erropt.verbose_info); break; default: /* Not WARNING, DEBUG or INFO, not suppressible */ break; } if (severity & ERR_PERROR) { cerrsep = ":"; cerrmsg = strerror(et->c_errno); } } *linestr = 0; if (where.lineno) { snprintf(linestr, sizeof linestr, "%s%"PRId32"%s", errfmt->beforeline, where.lineno, errfmt->afterline); } if (!skip_this_pass(severity)) { const char *file = where.filename ? where.filename : no_file_name; const char *here = ""; if (severity & ERR_HERE) { here = where.filename ? " here" : " in an unknown location"; } if (!warn_list) buffer = false; if (buffer) { /* * Buffer up warnings and held errors until we either get * an error or we are on the code-generation pass. */ strlist_printf(warn_list, "%s%s%s%s%s%s%s%s%s", file, linestr, errfmt->beforemsg, pfx, et->msg, cerrsep, cerrmsg, here, warnsuf); } else { /* * Actually output an error. If we have buffered * warnings, and this is a non-warning, output them now. */ if (warn_list) { strlist_write(warn_list, "\n", erropt.file); strlist_free(&warn_list); } fprintf(erropt.file, "%s%s%s%s%s%s%s%s%s\n", file, linestr, errfmt->beforemsg, pfx, et->msg, cerrsep, cerrmsg, here, warnsuf); } } /* Are we recursing from error_list_macros? */ if (severity & ERR_PP_LISTMACRO) goto done; /* * Don't suppress this with skip_this_pass(), or we don't get * pass1 or preprocessor warnings in the list file */ if (severity & ERR_HERE) { if (where.lineno) lfmt->error(severity, "%s%s at %s:%"PRId32"%s", pfx, et->msg, where.filename, where.lineno, warnsuf); else if (where.filename) lfmt->error(severity, "%s%s in file %s%s", pfx, et->msg, where.filename, warnsuf); else lfmt->error(severity, "%s%s in an unknown location%s", pfx, et->msg, warnsuf); } else { lfmt->error(severity, "%s%s%s", pfx, et->msg, warnsuf); } if (skip_this_pass(severity)) goto done; if (true_type >= ERR_FATAL) { die_hard(true_type, severity); } else if (!buffer) { if (true_type > erropt.worst) erropt.worst = true_type; if (true_type >= ERR_NONFATAL) erropt.never |= ERR_UNDEAD; } done: nasm_free_error(et); } int set_error_format(const char *fmt) { if (!nasm_stricmp("vc", fmt) || !nasm_stricmp("msvc", fmt) || !nasm_stricmp("ms", fmt)) errfmt = &errfmts[ERRFMT_MSVC]; else if (!nasm_stricmp("gnu", fmt) || !nasm_stricmp("gcc", fmt)) errfmt = &errfmts[ERRFMT_GNU]; else return -1; return 0; } static void usage(void) { fprintf(erropt.file, "Usage: %s [-@ response_file] [options...] [--] filename\n" " For additional help:\n" " %s -h [run|topics|all|-option]\n", _progname, _progname); } void warn_dollar_hex(void) { nasm_warn(WARN_NUMBER_DEPRECATED_HEX, "$ prefix for hexadecimal is deprecated"); }