| /* Control warning output in GNU Make. | |
| Copyright (C) 2023-2025 Free Software Foundation, Inc. | |
| This file is part of GNU Make. | |
| GNU Make is free software; you can redistribute it and/or modify it under the | |
| terms of the GNU General Public License as published by the Free Software | |
| Foundation; either version 3 of the License, or (at your option) any later | |
| version. | |
| GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY | |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License along with | |
| this program. If not, see <https://www.gnu.org/licenses/>. */ | |
| #include "makeint.h" | |
| #include "warning.h" | |
| #include "variable.h" | |
| /* Current action for each warning. */ | |
| enum warning_action warnings[wt_max]; | |
| /* The default behavior of warnings. */ | |
| static struct warning_data warn_default; | |
| /* Warning settings from the .WARNING variable. */ | |
| static struct warning_data warn_variable; | |
| /* Warning settings from the command line. */ | |
| static struct warning_data warn_flag; | |
| static const char *w_action_map[w_error+1] = {NULL, "ignore", "warn", "error"}; | |
| static const char *w_name_map[wt_max] = { | |
| "circular-dep", | |
| "invalid-ref", | |
| "invalid-var", | |
| "undefined-var" | |
| }; | |
| #define encode_warn_action(_b,_s) \ | |
| variable_buffer_output (_b, w_action_map[_s], strlen (w_action_map[_s])) | |
| #define encode_warn_name(_b,_t) \ | |
| variable_buffer_output (_b, w_name_map[_t], strlen (w_name_map[_t])) | |
| static void set_warnings () | |
| { | |
| /* Called whenever any warnings could change; resets the current actions. */ | |
| for (enum warning_type wt = 0; wt < wt_max; ++wt) | |
| warnings[wt] = | |
| warn_flag.actions[wt] != w_unset ? warn_flag.actions[wt] | |
| : warn_flag.global != w_unset ? warn_flag.global | |
| : warn_variable.actions[wt] != w_unset ? warn_variable.actions[wt] | |
| : warn_variable.global != w_unset ? warn_variable.global | |
| : warn_default.actions[wt]; | |
| } | |
| void | |
| warn_init () | |
| { | |
| memset (&warn_default, '\0', sizeof (warn_default)); | |
| memset (&warn_variable, '\0', sizeof (warn_variable)); | |
| memset (&warn_flag, '\0', sizeof (warn_flag)); | |
| /* All warnings must have a default. */ | |
| warn_default.global = w_warn; | |
| warn_default.actions[wt_circular_dep] = w_warn; | |
| warn_default.actions[wt_invalid_ref] = w_warn; | |
| warn_default.actions[wt_invalid_var] = w_warn; | |
| warn_default.actions[wt_undefined_var] = w_ignore; | |
| set_warnings (); | |
| } | |
| static void | |
| init_data (struct warning_data *data) | |
| { | |
| data->global = w_unset; | |
| for (enum warning_type wt = 0; wt < wt_max; ++wt) | |
| data->actions[wt] = w_unset; | |
| } | |
| static enum warning_action | |
| decode_warn_action (const char *action, size_t length) | |
| { | |
| for (enum warning_action st = w_ignore; st <= w_error; ++st) | |
| { | |
| size_t len = strlen (w_action_map[st]); | |
| if (length == len && strncasecmp (action, w_action_map[st], length) == 0) | |
| return st; | |
| } | |
| return w_unset; | |
| } | |
| static enum warning_type | |
| decode_warn_name (const char *name, size_t length) | |
| { | |
| for (enum warning_type wt = 0; wt < wt_max; ++wt) | |
| { | |
| size_t len = strlen (w_name_map[wt]); | |
| if (length == len && strncasecmp (name, w_name_map[wt], length) == 0) | |
| return wt; | |
| } | |
| return wt_max; | |
| } | |
| void | |
| decode_warn_actions (const char *value, const floc *flocp) | |
| { | |
| struct warning_data *data = &warn_flag; | |
| NEXT_TOKEN (value); | |
| if (flocp) | |
| { | |
| data = &warn_variable; | |
| /* When a variable is set to empty, reset everything. */ | |
| if (*value == '\0') | |
| init_data (data); | |
| } | |
| while (*value != '\0') | |
| { | |
| enum warning_action action; | |
| /* Find the end of the next warning definition. */ | |
| const char *ep = value; | |
| while (! STOP_SET (*ep, MAP_BLANK|MAP_COMMA|MAP_NUL)) | |
| ++ep; | |
| /* If the value is just an action set it globally. */ | |
| action = decode_warn_action (value, ep - value); | |
| if (action != w_unset) | |
| data->global = action; | |
| else | |
| { | |
| enum warning_type type; | |
| const char *cp = memchr (value, ':', ep - value); | |
| int wl, al; | |
| if (!cp) | |
| cp = ep; | |
| wl = (int)(cp - value); | |
| type = decode_warn_name (value, wl); | |
| if (cp == ep) | |
| action = w_warn; | |
| else | |
| { | |
| /* There's a warning action: decode it. */ | |
| ++cp; | |
| al = (int)(ep - cp); | |
| action = decode_warn_action (cp, al); | |
| } | |
| if (type == wt_max) | |
| { | |
| if (!flocp) | |
| ONS (fatal, NILF, _("unknown warning '%.*s'"), wl, value); | |
| ONS (error, flocp, | |
| _("unknown warning '%.*s': ignored"), wl, value); | |
| } | |
| else if (action == w_unset) | |
| { | |
| if (!flocp) | |
| ONS (fatal, NILF, | |
| _("unknown warning action '%.*s'"), al, cp); | |
| ONS (error, flocp, | |
| _("unknown warning action '%.*s': ignored"), al, cp); | |
| } | |
| else | |
| data->actions[type] = action; | |
| } | |
| value = ep; | |
| while (STOP_SET (*value, MAP_BLANK|MAP_COMMA)) | |
| ++value; | |
| } | |
| set_warnings (); | |
| } | |
| char * | |
| encode_warn_flag (char *fp) | |
| { | |
| enum warning_type wt; | |
| char sp = '='; | |
| /* See if any warning options are set. */ | |
| for (wt = 0; wt < wt_max; ++wt) | |
| if (warn_flag.actions[wt] != w_unset) | |
| break; | |
| if (wt == wt_max && warn_flag.global == w_unset) | |
| return fp; | |
| /* Something is set so construct a --warn option. */ | |
| fp = variable_buffer_output (fp, STRING_SIZE_TUPLE (" --warn")); | |
| /* If only a global action set to warn, we're done. */ | |
| if (wt == wt_max && warn_flag.global == w_warn) | |
| return fp; | |
| /* If a global action is set, add it. */ | |
| if (warn_flag.global > w_unset) | |
| { | |
| fp = variable_buffer_output (fp, &sp, 1); | |
| sp = ','; | |
| fp = encode_warn_action (fp, warn_flag.global); | |
| } | |
| /* Add any specific actions. */ | |
| if (wt != wt_max) | |
| for (wt = 0; wt < wt_max; ++wt) | |
| { | |
| enum warning_action act = warn_flag.actions[wt]; | |
| if (act > w_unset) | |
| { | |
| fp = variable_buffer_output (fp, &sp, 1); | |
| sp = ','; | |
| fp = encode_warn_name (fp, wt); | |
| if (act != w_warn) | |
| fp = encode_warn_action (variable_buffer_output (fp, ":", 1), act); | |
| } | |
| } | |
| return fp; | |
| } | |
| void | |
| warn_get_vardata (struct warning_data *data) | |
| { | |
| memcpy (data, &warn_variable, sizeof (warn_variable)); | |
| } | |
| void | |
| warn_set_vardata (const struct warning_data *data) | |
| { | |
| memcpy (&warn_variable, data, sizeof (warn_variable)); | |
| set_warnings (); | |
| } |