|  | /* Parse a printf-style format string. | 
|  |  | 
|  | Copyright (C) 1986-2025 Free Software Foundation, Inc. | 
|  |  | 
|  | This file is part of GDB. | 
|  |  | 
|  | This program 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. | 
|  |  | 
|  | This program 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 <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "format.h" | 
|  |  | 
|  | format_pieces::format_pieces (const char **arg, bool gdb_extensions, | 
|  | bool value_extension) | 
|  | { | 
|  | const char *s; | 
|  | const char *string; | 
|  | const char *prev_start; | 
|  | const char *percent_loc; | 
|  | char *sub_start, *current_substring; | 
|  | enum argclass this_argclass; | 
|  |  | 
|  | s = *arg; | 
|  |  | 
|  | if (gdb_extensions) | 
|  | { | 
|  | string = *arg; | 
|  | *arg += strlen (*arg); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Parse the format-control string and copy it into the string STRING, | 
|  | processing some kinds of escape sequence.  */ | 
|  |  | 
|  | char *f = (char *) alloca (strlen (s) + 1); | 
|  | string = f; | 
|  |  | 
|  | while (*s != '"' && *s != '\0') | 
|  | { | 
|  | int c = *s++; | 
|  | switch (c) | 
|  | { | 
|  | case '\0': | 
|  | continue; | 
|  |  | 
|  | case '\\': | 
|  | switch (c = *s++) | 
|  | { | 
|  | case '\\': | 
|  | *f++ = '\\'; | 
|  | break; | 
|  | case 'a': | 
|  | *f++ = '\a'; | 
|  | break; | 
|  | case 'b': | 
|  | *f++ = '\b'; | 
|  | break; | 
|  | case 'e': | 
|  | *f++ = '\e'; | 
|  | break; | 
|  | case 'f': | 
|  | *f++ = '\f'; | 
|  | break; | 
|  | case 'n': | 
|  | *f++ = '\n'; | 
|  | break; | 
|  | case 'r': | 
|  | *f++ = '\r'; | 
|  | break; | 
|  | case 't': | 
|  | *f++ = '\t'; | 
|  | break; | 
|  | case 'v': | 
|  | *f++ = '\v'; | 
|  | break; | 
|  | case '"': | 
|  | *f++ = '"'; | 
|  | break; | 
|  | default: | 
|  | /* ??? TODO: handle other escape sequences.  */ | 
|  | error (_("Unrecognized escape character \\%c in format string."), | 
|  | c); | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | *f++ = c; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Terminate our escape-processed copy.  */ | 
|  | *f++ = '\0'; | 
|  |  | 
|  | /* Whether the format string ended with double-quote or zero, we're | 
|  | done with it; it's up to callers to complain about syntax.  */ | 
|  | *arg = s; | 
|  | } | 
|  |  | 
|  | /* Need extra space for the '\0's.  Doubling the size is sufficient.  */ | 
|  |  | 
|  | current_substring = (char *) xmalloc (strlen (string) * 2 + 1000); | 
|  | m_storage.reset (current_substring); | 
|  |  | 
|  | /* Now scan the string for %-specs and see what kinds of args they want. | 
|  | argclass classifies the %-specs so we can give printf-type functions | 
|  | something of the right size.  */ | 
|  |  | 
|  | const char *f = string; | 
|  | prev_start = string; | 
|  | while (*f) | 
|  | if (*f++ == '%') | 
|  | { | 
|  | int seen_hash = 0, seen_zero = 0, lcount = 0, seen_prec = 0; | 
|  | int seen_space = 0, seen_plus = 0; | 
|  | int seen_big_l = 0, seen_h = 0, seen_big_h = 0; | 
|  | int seen_big_d = 0, seen_double_big_d = 0; | 
|  | int seen_size_t = 0; | 
|  | int bad = 0; | 
|  | int n_int_args = 0; | 
|  | bool seen_i64 = false; | 
|  |  | 
|  | /* Skip over "%%", it will become part of a literal piece.  */ | 
|  | if (*f == '%') | 
|  | { | 
|  | f++; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | sub_start = current_substring; | 
|  |  | 
|  | strncpy (current_substring, prev_start, f - 1 - prev_start); | 
|  | current_substring += f - 1 - prev_start; | 
|  | *current_substring++ = '\0'; | 
|  |  | 
|  | if (*sub_start != '\0') | 
|  | m_pieces.emplace_back (sub_start, literal_piece, 0); | 
|  |  | 
|  | percent_loc = f - 1; | 
|  |  | 
|  | /* Check the validity of the format specifier, and work | 
|  | out what argument it expects.  We only accept C89 | 
|  | format strings, with the exception of long long (which | 
|  | we autoconf for).  */ | 
|  |  | 
|  | /* The first part of a format specifier is a set of flag | 
|  | characters.  */ | 
|  | while (*f != '\0' && strchr ("0-+ #", *f)) | 
|  | { | 
|  | if (*f == '#') | 
|  | seen_hash = 1; | 
|  | else if (*f == '0') | 
|  | seen_zero = 1; | 
|  | else if (*f == ' ') | 
|  | seen_space = 1; | 
|  | else if (*f == '+') | 
|  | seen_plus = 1; | 
|  | f++; | 
|  | } | 
|  |  | 
|  | /* The next part of a format specifier is a width.  */ | 
|  | if (gdb_extensions && *f == '*') | 
|  | { | 
|  | ++f; | 
|  | ++n_int_args; | 
|  | } | 
|  | else | 
|  | { | 
|  | while (*f != '\0' && strchr ("0123456789", *f)) | 
|  | f++; | 
|  | } | 
|  |  | 
|  | /* The next part of a format specifier is a precision.  */ | 
|  | if (*f == '.') | 
|  | { | 
|  | seen_prec = 1; | 
|  | f++; | 
|  | if (gdb_extensions && *f == '*') | 
|  | { | 
|  | ++f; | 
|  | ++n_int_args; | 
|  | } | 
|  | else | 
|  | { | 
|  | while (*f != '\0' && strchr ("0123456789", *f)) | 
|  | f++; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* The next part of a format specifier is a length modifier.  */ | 
|  | switch (*f) | 
|  | { | 
|  | case 'h': | 
|  | seen_h = 1; | 
|  | f++; | 
|  | break; | 
|  | case 'l': | 
|  | f++; | 
|  | lcount++; | 
|  | if (*f == 'l') | 
|  | { | 
|  | f++; | 
|  | lcount++; | 
|  | } | 
|  | break; | 
|  | case 'L': | 
|  | seen_big_l = 1; | 
|  | f++; | 
|  | break; | 
|  | case 'H': | 
|  | /* Decimal32 modifier.  */ | 
|  | seen_big_h = 1; | 
|  | f++; | 
|  | break; | 
|  | case 'D': | 
|  | /* Decimal64 and Decimal128 modifiers.  */ | 
|  | f++; | 
|  |  | 
|  | /* Check for a Decimal128.  */ | 
|  | if (*f == 'D') | 
|  | { | 
|  | f++; | 
|  | seen_double_big_d = 1; | 
|  | } | 
|  | else | 
|  | seen_big_d = 1; | 
|  | break; | 
|  | case 'z': | 
|  | /* For size_t or ssize_t.  */ | 
|  | seen_size_t = 1; | 
|  | f++; | 
|  | break; | 
|  | case 'I': | 
|  | /* Support the Windows '%I64' extension, because an | 
|  | earlier call to format_pieces might have converted %lld | 
|  | to %I64d.  */ | 
|  | if (f[1] == '6' && f[2] == '4') | 
|  | { | 
|  | f += 3; | 
|  | lcount = 2; | 
|  | seen_i64 = true; | 
|  | } | 
|  | break; | 
|  | } | 
|  |  | 
|  | switch (*f) | 
|  | { | 
|  | case 'u': | 
|  | if (seen_hash) | 
|  | bad = 1; | 
|  | [[fallthrough]]; | 
|  |  | 
|  | case 'o': | 
|  | case 'x': | 
|  | case 'X': | 
|  | if (seen_space || seen_plus) | 
|  | bad = 1; | 
|  | [[fallthrough]]; | 
|  |  | 
|  | case 'd': | 
|  | case 'i': | 
|  | if (seen_size_t) | 
|  | this_argclass = size_t_arg; | 
|  | else if (lcount == 0) | 
|  | this_argclass = int_arg; | 
|  | else if (lcount == 1) | 
|  | this_argclass = long_arg; | 
|  | else | 
|  | this_argclass = long_long_arg; | 
|  |  | 
|  | if (seen_big_l) | 
|  | bad = 1; | 
|  | break; | 
|  |  | 
|  | case 'c': | 
|  | this_argclass = lcount == 0 ? int_arg : wide_char_arg; | 
|  | if (lcount > 1 || seen_h || seen_big_l) | 
|  | bad = 1; | 
|  | if (seen_prec || seen_zero || seen_space || seen_plus) | 
|  | bad = 1; | 
|  | break; | 
|  |  | 
|  | case 'p': | 
|  | this_argclass = ptr_arg; | 
|  | if (lcount || seen_h || seen_big_l) | 
|  | bad = 1; | 
|  | if (seen_prec) | 
|  | bad = 1; | 
|  | if (seen_hash || seen_zero || seen_space || seen_plus) | 
|  | bad = 1; | 
|  |  | 
|  | if (gdb_extensions) | 
|  | { | 
|  | switch (f[1]) | 
|  | { | 
|  | case 's': | 
|  | case 'F': | 
|  | case '[': | 
|  | case ']': | 
|  | f++; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | break; | 
|  |  | 
|  | case 's': | 
|  | this_argclass = lcount == 0 ? string_arg : wide_string_arg; | 
|  | if (lcount > 1 || seen_h || seen_big_l) | 
|  | bad = 1; | 
|  | if (seen_zero || seen_space || seen_plus) | 
|  | bad = 1; | 
|  | break; | 
|  |  | 
|  | case 'e': | 
|  | case 'f': | 
|  | case 'g': | 
|  | case 'E': | 
|  | case 'G': | 
|  | if (seen_double_big_d) | 
|  | this_argclass = dec128float_arg; | 
|  | else if (seen_big_d) | 
|  | this_argclass = dec64float_arg; | 
|  | else if (seen_big_h) | 
|  | this_argclass = dec32float_arg; | 
|  | else if (seen_big_l) | 
|  | this_argclass = long_double_arg; | 
|  | else | 
|  | this_argclass = double_arg; | 
|  |  | 
|  | if (lcount || seen_h) | 
|  | bad = 1; | 
|  | break; | 
|  |  | 
|  | case 'V': | 
|  | if (!value_extension) | 
|  | error (_("Unrecognized format specifier '%c' in printf"), *f); | 
|  |  | 
|  | if (lcount > 1 || seen_h || seen_big_h || seen_big_h | 
|  | || seen_big_d || seen_double_big_d || seen_size_t | 
|  | || seen_prec || seen_zero || seen_space || seen_plus) | 
|  | bad = 1; | 
|  |  | 
|  | this_argclass = value_arg; | 
|  |  | 
|  | if (f[1] == '[') | 
|  | { | 
|  | /* Move F forward to the next ']' character if such a | 
|  | character exists, otherwise leave F unchanged.  */ | 
|  | const char *tmp = strchr (f, ']'); | 
|  | if (tmp != nullptr) | 
|  | f = tmp; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case '*': | 
|  | error (_("`*' not supported for precision or width in printf")); | 
|  |  | 
|  | case 'n': | 
|  | error (_("Format specifier `n' not supported in printf")); | 
|  |  | 
|  | case '\0': | 
|  | error (_("Incomplete format specifier at end of format string")); | 
|  |  | 
|  | default: | 
|  | error (_("Unrecognized format specifier '%c' in printf"), *f); | 
|  | } | 
|  |  | 
|  | if (bad) | 
|  | error (_("Inappropriate modifiers to " | 
|  | "format specifier '%c' in printf"), | 
|  | *f); | 
|  |  | 
|  | f++; | 
|  |  | 
|  | sub_start = current_substring; | 
|  |  | 
|  | if (lcount > 1 && !seen_i64 && USE_PRINTF_I64) | 
|  | { | 
|  | /* Windows' printf does support long long, but not the usual way. | 
|  | Convert %lld to %I64d.  */ | 
|  | int length_before_ll = f - percent_loc - 1 - lcount; | 
|  |  | 
|  | strncpy (current_substring, percent_loc, length_before_ll); | 
|  | strcpy (current_substring + length_before_ll, "I64"); | 
|  | current_substring[length_before_ll + 3] = | 
|  | percent_loc[length_before_ll + lcount]; | 
|  | current_substring += length_before_ll + 4; | 
|  | } | 
|  | else if (this_argclass == wide_string_arg | 
|  | || this_argclass == wide_char_arg) | 
|  | { | 
|  | /* Convert %ls or %lc to %s.  */ | 
|  | int length_before_ls = f - percent_loc - 2; | 
|  |  | 
|  | strncpy (current_substring, percent_loc, length_before_ls); | 
|  | strcpy (current_substring + length_before_ls, "s"); | 
|  | current_substring += length_before_ls + 2; | 
|  | } | 
|  | else | 
|  | { | 
|  | strncpy (current_substring, percent_loc, f - percent_loc); | 
|  | current_substring += f - percent_loc; | 
|  | } | 
|  |  | 
|  | *current_substring++ = '\0'; | 
|  |  | 
|  | prev_start = f; | 
|  |  | 
|  | m_pieces.emplace_back (sub_start, this_argclass, n_int_args); | 
|  | } | 
|  |  | 
|  | /* Record the remainder of the string.  */ | 
|  |  | 
|  | if (f > prev_start) | 
|  | { | 
|  | sub_start = current_substring; | 
|  |  | 
|  | strncpy (current_substring, prev_start, f - prev_start); | 
|  | current_substring += f - prev_start; | 
|  | *current_substring++ = '\0'; | 
|  |  | 
|  | m_pieces.emplace_back (sub_start, literal_piece, 0); | 
|  | } | 
|  | } |