summaryrefslogtreecommitdiffstats
path: root/tools/gcc-plugins
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gcc-plugins')
-rw-r--r--tools/gcc-plugins/.gitignore7
-rw-r--r--tools/gcc-plugins/Makefile23
-rw-r--r--tools/gcc-plugins/README.md102
-rw-r--r--tools/gcc-plugins/debian/changelog11
-rw-r--r--tools/gcc-plugins/debian/compat1
-rw-r--r--tools/gcc-plugins/debian/control19
-rw-r--r--tools/gcc-plugins/debian/copyright9
-rwxr-xr-xtools/gcc-plugins/debian/rules11
-rw-r--r--tools/gcc-plugins/debian/source/format1
-rw-r--r--tools/gcc-plugins/format-test.c113
-rw-r--r--tools/gcc-plugins/format-test.py74
-rw-r--r--tools/gcc-plugins/frr-format.c4468
-rw-r--r--tools/gcc-plugins/frr-format.h352
-rw-r--r--tools/gcc-plugins/gcc-common.h994
-rw-r--r--tools/gcc-plugins/gcc-retain-typeinfo.patch11
15 files changed, 6196 insertions, 0 deletions
diff --git a/tools/gcc-plugins/.gitignore b/tools/gcc-plugins/.gitignore
new file mode 100644
index 0000000..dd8d0cb
--- /dev/null
+++ b/tools/gcc-plugins/.gitignore
@@ -0,0 +1,7 @@
+*.so
+*.o
+debian/.debhelper
+debian/files
+debian/*.substvars
+debian/gcc-9-frr-plugin
+!gcc-retain-typeinfo.patch
diff --git a/tools/gcc-plugins/Makefile b/tools/gcc-plugins/Makefile
new file mode 100644
index 0000000..2af28fe
--- /dev/null
+++ b/tools/gcc-plugins/Makefile
@@ -0,0 +1,23 @@
+all: frr-format.so
+
+CXX=g++-9
+
+PLUGBASE=`$(CXX) -print-file-name=plugin`
+CPPFLAGS=-I$(PLUGBASE)/include -I$(PLUGBASE)/include/c-family
+
+# NB: compiler flags must match those used to build gcc, otherwise inlining
+# behavior is different and linker errors will result due to missing symbols
+# (which should in fact be inlined)
+
+frr-format.so: frr-format.o
+ $(CXX) -fno-rtti -fno-exceptions -fasynchronous-unwind-tables -ggdb -shared -o $@ $^
+
+frr-format.o: frr-format.c gcc-common.h
+ $(CXX) -fno-rtti -fno-exceptions -fasynchronous-unwind-tables -ggdb $(CPPFLAGS) -fPIC -Wall -Wextra -Wno-unused-parameter -c -o $@ $<
+
+install:
+ install -d $(DESTDIR)$(PLUGBASE)
+ install frr-format.so $(DESTDIR)$(PLUGBASE)
+
+clean:
+ rm -f frr-format.so frr-format.o
diff --git a/tools/gcc-plugins/README.md b/tools/gcc-plugins/README.md
new file mode 100644
index 0000000..ab31d0e
--- /dev/null
+++ b/tools/gcc-plugins/README.md
@@ -0,0 +1,102 @@
+frr-format GCC plugin
+=====================
+
+Context
+-------
+
+This plugin provides improved type checking for Linux kernel style printf
+extensions (i.e. `%pI4` printing `struct in_addr *` as `1.2.3.4`.)
+
+Other than additional warnings, (non-)usage of this plugin should not affect
+the build outcome. It is perfectly fine to build FRR without this plugin.
+
+
+Binary Debian packages
+----------------------
+
+Can be found at [https://deb.nox.tf/devel/].
+
+
+GCC requirements
+----------------
+
+To use this plugin, you need a **patched 9.3.0** or a **patched 10.1.0**
+version of GCC using the [gcc-retain-typeinfo.patch] provided in this repo.
+
+Without this patch, GCC strips type information too early during compilation,
+leaving to the plugin being unable to perform more meaningful type checks.
+(Specifically, all `typedef` casts will be "cooked down" to their final type.)
+If the patch is missing, `format-test.c` will show 4 false negative/positive
+warnings marked with `(need retain-typeinfo patch)`.
+
+(@eqvinox has discussed this one-line diff with some GCC people on their
+IRC channel around mid 2019, the consensus was that the line is an "early
+optimization" and removing it should not be harmful. However, doing so is
+likely to break GCC's unit tests since warnings would print different types.)
+
+Other versions of gcc are not supported. gcc 8 previously did work but isn't
+actively tested/maintained.
+
+
+Usage
+-----
+
+First, all plugin-specific statements should be wrapped by an ifdef:
+
+```
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+...
+#endif
+```
+
+`_FRR_ATTRIBUTE_PRINTFRR` will be defined to the plugin's version (currently
+0x10000) whenever the plugin is loaded.
+
+Then, annotate extended printf functions with the `frr_format` attribute.
+This works exactly like the `format` attribute:
+
+```
+int printfn(const char *fmt, ...) __attribute__((frr_format("frr_printf", 1, 2)));
+```
+
+In the FRR codebase, use the `PRINTFRR` macro provided in
+[../../lib/compiler.h].
+
+Lastly, "declare" extensions with `#pragma FRR printfrr_ext`:
+```
+#ifdef _FRR_ATTRIBUTE_PRINTFRR
+#pragma FRR printfrr_ext "%pI4" (struct in_addr *)
+#pragma FRR printfrr_ext "%pI4" (in_addr_t *)
+#endif
+```
+
+Note that you can use multiple such lines if a particular extended printer
+works for more than one type (as seen above.)
+
+The pragma type "parameter" looks like a C cast but unfortunately due to GCC
+not exporting a good interface to proper type parsing, it is "ghetto parsed",
+with only `struct`, `union`, `enum` being properly supported. `const` is
+ignored if it occurs as the first token. (The plugin always accepts `const`
+parameters for printf since printf shouldn't change the passed data it's
+printing.) The last token may be zero or more counts of `*`, note that
+qualifiers on the intermediate pointers (e.g. `const char * const *`) are not
+supported.
+
+
+TODOs and future direction
+--------------------------
+
+* support two-parameter extension printers that use the precision field
+ (e.g. `"%.*pI5" (int af, void *addr)` to print an IP address with the
+ address family in the "precision".
+
+* port to future GCC versions
+
+* get the one-liner patch upstreamed
+
+
+License
+-------
+
+This plugin is **derivative of GCC 9.x**. It was created by copying off
+`c-format.c`. It must therefore adhere to GCC's GPLv3+ license.
diff --git a/tools/gcc-plugins/debian/changelog b/tools/gcc-plugins/debian/changelog
new file mode 100644
index 0000000..a772803
--- /dev/null
+++ b/tools/gcc-plugins/debian/changelog
@@ -0,0 +1,11 @@
+gcc-frr-plugin (9.3.0d15+equi1) unstable; urgency=medium
+
+ * update & rebuild for gcc 9.3.0-15+equi1
+
+ -- David Lamparter <equinox-debian@diac24.net> Tue, 14 Jul 2020 19:49:24 +0200
+
+gcc-frr-plugin (9.3.0d8+equi2) unstable; urgency=medium
+
+ * package created (+equi1 used during development, never released.)
+
+ -- David Lamparter <equinox-debian@diac24.net> Sun, 29 Mar 2020 08:32:24 +0200
diff --git a/tools/gcc-plugins/debian/compat b/tools/gcc-plugins/debian/compat
new file mode 100644
index 0000000..48082f7
--- /dev/null
+++ b/tools/gcc-plugins/debian/compat
@@ -0,0 +1 @@
+12
diff --git a/tools/gcc-plugins/debian/control b/tools/gcc-plugins/debian/control
new file mode 100644
index 0000000..b9b5134
--- /dev/null
+++ b/tools/gcc-plugins/debian/control
@@ -0,0 +1,19 @@
+Source: gcc-frr-plugin
+Section: devel
+Priority: optional
+Maintainer: David Lamparter <equinox-debian@diac24.net>
+Build-Depends:
+ gcc-9-plugin-dev (=9.3.0-15+equi1),
+ debhelper (>= 12)
+Standards-Version: 4.4.1
+Homepage: https://www.frrouting.org/
+Vcs-Browser: https://github.com/FRRouting/frr/
+Vcs-Git: https://github.com/FRRouting/frr.git
+
+Package: gcc-9-frr-plugin
+Architecture: linux-any
+Depends:
+ gcc-9 (=9.3.0-15+equi1),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: GCC plugin for FRRouting
diff --git a/tools/gcc-plugins/debian/copyright b/tools/gcc-plugins/debian/copyright
new file mode 100644
index 0000000..dcd9fa1
--- /dev/null
+++ b/tools/gcc-plugins/debian/copyright
@@ -0,0 +1,9 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: FRR
+Upstream-Contact: maintainers@frrouting.org, security@frrouting.org
+Source: https://www.frrouting.org/
+
+Files: *
+Copyright:
+ 2019-2020 by David Lamparter
+ Code derived from GCC, please refer to gcc package copyright.
diff --git a/tools/gcc-plugins/debian/rules b/tools/gcc-plugins/debian/rules
new file mode 100755
index 0000000..f8f42ad
--- /dev/null
+++ b/tools/gcc-plugins/debian/rules
@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+
+# standard Debian options & profiles
+
+export DEB_BUILD_MAINT_OPTIONS = hardening=+all
+
+%:
+ dh $@
+
+override_dh_auto_test:
+ true
diff --git a/tools/gcc-plugins/debian/source/format b/tools/gcc-plugins/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/tools/gcc-plugins/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/tools/gcc-plugins/format-test.c b/tools/gcc-plugins/format-test.c
new file mode 100644
index 0000000..fb7e41c
--- /dev/null
+++ b/tools/gcc-plugins/format-test.c
@@ -0,0 +1,113 @@
+#include <stddef.h>
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+typedef unsigned long mytype;
+typedef size_t mysize;
+
+typedef unsigned int not_in_addr_t;
+typedef in_addr_t yes_in_addr_t;
+typedef struct in_addr in_addr_s;
+
+struct other {
+ int x;
+};
+
+int testfn(const char *fmt, ...) __attribute__((frr_format("frr_printf", 1, 2)));
+
+#ifndef _FRR_ATTRIBUTE_PRINTFRR
+#error please load the frr-format plugin
+#endif
+
+#pragma FRR printfrr_ext "%pI4" (struct in_addr *)
+#pragma FRR printfrr_ext "%pI4" (in_addr_t *)
+
+int test(unsigned long long ay)
+{
+ size_t v_size_t = 0;
+ long v_long = 0;
+ int v_int = 0;
+ uint64_t v_uint64_t = 0;
+ mytype v_mytype = 0;
+ mysize v_mysize = 0;
+ pid_t v_pid_t = 0;
+
+ testfn("%zu", v_size_t); // NOWARN
+ testfn("%zu", v_long); // WARN
+ testfn("%zu", v_int); // WARN
+ testfn("%zu", sizeof(v_int)); // NOWARN
+ testfn("%zu", v_mytype); // WARN
+ testfn("%zu", v_mysize); // NOWARN
+ testfn("%zu", v_uint64_t); // WARN
+ testfn("%zu", v_pid_t); // WARN
+
+ testfn("%lu", v_long); // NOWARN PEDANTIC
+ testfn("%lu", v_int); // WARN
+ testfn("%lu", v_size_t); // WARN
+ testfn("%lu", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%lu", v_uint64_t); // WARN
+ testfn("%lu", v_pid_t); // WARN
+
+ testfn("%ld", v_long); // NOWARN
+ testfn("%ld", v_int); // WARN
+ testfn("%ld", v_size_t); // WARN
+ testfn("%ld", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%ld", v_uint64_t); // WARN
+ testfn("%ld", v_pid_t); // WARN
+
+ testfn("%d", v_int); // NOWARN
+ testfn("%d", v_long); // WARN
+ testfn("%d", v_size_t); // WARN
+ testfn("%d", sizeof(v_int)); // WARN
+ testfn("%d", v_uint64_t); // WARN
+ testfn("%d", v_pid_t); // WARN
+
+ testfn("%Lu", v_size_t); // WARN
+ testfn("%Lu", v_long); // WARN
+ testfn("%Lu", v_int); // WARN
+ testfn("%Lu", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%Lu", v_mytype); // WARN
+ testfn("%Lu", v_mysize); // WARN
+ testfn("%Lu", v_pid_t); // WARN
+ testfn("%Lu", v_uint64_t); // NOWARN
+
+ testfn("%Ld", v_size_t); // WARN
+ testfn("%Ld", v_long); // WARN
+ testfn("%Ld", v_int); // WARN
+ testfn("%Ld", sizeof(v_int)); // NOWARN (integer constant)
+ testfn("%Ld", v_mytype); // WARN
+ testfn("%Ld", v_mysize); // WARN
+ testfn("%Ld", v_pid_t); // WARN
+ testfn("%Ld", v_uint64_t); // NOWARN
+
+ /* retain-typeinfo patch */
+ testfn("%zu", (size_t)v_pid_t); // NOWARN (need retain-typeinfo patch)
+ testfn("%lu", (size_t)v_pid_t); // WARN (need retain-typeinfo patch)
+ testfn("%Lu", (uint64_t)v_pid_t); // NOWARN (need retain-typeinfo patch)
+ testfn("%lu", (uint64_t)v_pid_t); // WARN (need retain-typeinfo patch)
+
+ testfn("%pI4", &v_long); // WARN
+
+ in_addr_t v_in_addr_t;
+ yes_in_addr_t v_yes_in_addr_t;
+ not_in_addr_t v_not_in_addr_t;
+ void *v_voidp = &v_in_addr_t;
+
+ testfn("%pI4", &v_in_addr_t); // NOWARN
+ testfn("%pI4", &v_yes_in_addr_t); // NOWARN
+ testfn("%pI4", &v_not_in_addr_t); // WARN
+ testfn("%pI4", v_voidp); // WARN
+
+ struct in_addr v_in_addr;
+ in_addr_s v_in_addr_s;
+ struct other v_other;
+ const struct in_addr *v_in_addr_const = &v_in_addr;
+
+ testfn("%pI4", &v_in_addr); // NOWARN
+ testfn("%pI4", &v_in_addr_s); // NOWARN
+ testfn("%pI4", &v_other); // WARN
+ testfn("%pI4", v_in_addr_const); // NOWARN
+ return 0;
+}
diff --git a/tools/gcc-plugins/format-test.py b/tools/gcc-plugins/format-test.py
new file mode 100644
index 0000000..ddf71aa
--- /dev/null
+++ b/tools/gcc-plugins/format-test.py
@@ -0,0 +1,74 @@
+import subprocess
+import sys
+import shlex
+import os
+import re
+
+os.environ["LC_ALL"] = "C"
+os.environ["LANG"] = "C"
+for k in list(os.environ.keys()):
+ if k.startswith("LC_"):
+ os.environ.pop(k)
+
+if len(sys.argv) < 2:
+ sys.stderr.write("start as format-test.py gcc-123.45 [-options ...]\n")
+ sys.exit(1)
+
+c_re = re.compile(r"//\s+(NO)?WARN")
+expect = {}
+lines = {}
+
+with open("format-test.c", "r") as fd:
+ for lno, line in enumerate(fd.readlines(), 1):
+ lines[lno] = line.strip()
+ m = c_re.search(line)
+ if m is None:
+ continue
+ if m.group(1) is None:
+ expect[lno] = "warn"
+ else:
+ expect[lno] = "nowarn"
+
+cmd = shlex.split(
+ "-Wall -Wextra -Wno-unused -fplugin=./frr-format.so -fno-diagnostics-show-caret -c -o format-test.o format-test.c"
+)
+
+gcc = subprocess.Popen(
+ sys.argv[1:] + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+)
+sout, serr = gcc.communicate()
+gcc.wait()
+
+gcclines = serr.decode("UTF-8").splitlines()
+line_re = re.compile(r"^format-test\.c:(\d+):(.*)$")
+gcc_warns = {}
+
+for line in gcclines:
+ if line.find("In function") >= 0:
+ continue
+ m = line_re.match(line)
+ if m is None:
+ sys.stderr.write("cannot process GCC output: %s\n" % line)
+ continue
+
+ lno = int(m.group(1))
+ gcc_warns.setdefault(lno, []).append(line)
+
+for lno, val in expect.items():
+ if val == "nowarn" and lno in gcc_warns:
+ sys.stderr.write(
+ "unexpected gcc warning on line %d:\n\t%s\n\t%s\n"
+ % (lno, lines[lno], "\n\t".join(gcc_warns[lno]))
+ )
+ if val == "warn" and lno not in gcc_warns:
+ sys.stderr.write(
+ "expected warning on line %d but did not get one\n\t%s\n"
+ % (lno, lines[lno])
+ )
+
+leftover = set(gcc_warns.keys()) - set(expect.keys())
+for lno in sorted(leftover):
+ sys.stderr.write(
+ "unmarked gcc warning on line %d:\n\t%s\n\t%s\n"
+ % (lno, lines[lno], "\n\t".join(gcc_warns[lno]))
+ )
diff --git a/tools/gcc-plugins/frr-format.c b/tools/gcc-plugins/frr-format.c
new file mode 100644
index 0000000..4e2c2d3
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.c
@@ -0,0 +1,4468 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/* Check calls to formatted I/O functions (-Wformat).
+ Copyright (C) 1992-2019 Free Software Foundation, Inc.
+
+ Extended for FRR's printfrr() with Linux kernel style extensions
+ Copyright (C) 2019-2020 David Lamparter, for NetDEF, Inc.
+ */
+
+#include "gcc-common.h"
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+//include "c-target.h"
+#include "c-common.h"
+#include "alloc-pool.h"
+#include "stringpool.h"
+#include "c-tree.h"
+#include "c-objc.h"
+#include "intl.h"
+#include "langhooks.h"
+#include "frr-format.h"
+#include "diagnostic.h"
+#include "substring-locations.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#ifndef FIRST_PSEUDO_REGISTER
+#define FIRST_PSEUDO_REGISTER 0
+#endif
+#include "builtins.h"
+#include "attribs.h"
+#include "gcc-rich-location.h"
+#include "c-pretty-print.h"
+#include "c-pragma.h"
+
+extern struct cpp_reader *parse_in;
+
+#pragma GCC visibility push(hidden)
+
+/* Handle attributes associated with format checking. */
+
+/* This must be in the same order as format_types, except for
+ format_type_error. Target-specific format types do not have
+ matching enum values. */
+enum format_type { frr_printf_format_type,
+ format_type_error = -1};
+
+struct function_format_info
+{
+ int format_type; /* type of format (printf, scanf, etc.) */
+ unsigned HOST_WIDE_INT format_num; /* number of format argument */
+ unsigned HOST_WIDE_INT first_arg_num; /* number of first arg (zero for varargs) */
+};
+
+static GTY(()) tree local_uint64_t_node;
+static GTY(()) tree local_int64_t_node;
+
+static GTY(()) tree local_size_t_node;
+static GTY(()) tree local_ssize_t_node;
+static GTY(()) tree local_atomic_size_t_node;
+static GTY(()) tree local_atomic_ssize_t_node;
+static GTY(()) tree local_ptrdiff_t_node;
+
+static GTY(()) tree local_pid_t_node;
+static GTY(()) tree local_uid_t_node;
+static GTY(()) tree local_gid_t_node;
+static GTY(()) tree local_time_t_node;
+
+static GTY(()) tree local_socklen_t_node;
+static GTY(()) tree local_in_addr_t_node;
+
+static struct type_special {
+ tree *match;
+ tree *replace;
+ tree *cousin;
+} special_types[] = {
+ { &local_atomic_size_t_node, &local_size_t_node, &local_ssize_t_node, },
+ { &local_atomic_ssize_t_node, &local_ssize_t_node, &local_size_t_node, },
+ { &local_size_t_node, NULL, &local_ssize_t_node, },
+ { &local_ssize_t_node, NULL, &local_size_t_node, },
+ { &local_uint64_t_node, NULL, &local_int64_t_node, },
+ { &local_int64_t_node, NULL, &local_uint64_t_node, },
+ { &local_pid_t_node, NULL, &local_pid_t_node, },
+ { &local_uid_t_node, NULL, &local_uid_t_node, },
+ { &local_gid_t_node, NULL, &local_gid_t_node, },
+ { &local_time_t_node, NULL, &local_time_t_node, },
+ { NULL, NULL, NULL, }
+};
+
+static bool decode_format_attr (tree, function_format_info *, int);
+static int decode_format_type (const char *);
+
+static bool check_format_string (tree argument,
+ unsigned HOST_WIDE_INT format_num,
+ int flags, bool *no_add_attrs,
+ int expected_format_type);
+static bool get_constant (tree expr, unsigned HOST_WIDE_INT *value,
+ int validated_p);
+static const char *convert_format_name_to_system_name (const char *attr_name);
+
+static int first_target_format_type;
+static const char *format_name (int format_num);
+static int format_flags (int format_num);
+
+/* Emit a warning as per format_warning_va, but construct the substring_loc
+ for the character at offset (CHAR_IDX - 1) within a string constant
+ FORMAT_STRING_CST at FMT_STRING_LOC. */
+
+ATTRIBUTE_GCC_DIAG (5,6)
+static bool
+format_warning_at_char (location_t fmt_string_loc, tree format_string_cst,
+ int char_idx, int opt, const char *gmsgid, ...)
+{
+ va_list ap;
+ va_start (ap, gmsgid);
+ tree string_type = TREE_TYPE (format_string_cst);
+
+ /* The callers are of the form:
+ format_warning (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ where format_chars has already been incremented, so that
+ CHAR_IDX is one character beyond where the warning should
+ be emitted. Fix it. */
+ char_idx -= 1;
+
+ substring_loc fmt_loc (fmt_string_loc, string_type, char_idx, char_idx,
+ char_idx);
+#if BUILDING_GCC_VERSION >= 9000
+ format_string_diagnostic_t diag (fmt_loc, NULL, UNKNOWN_LOCATION, NULL,
+ NULL);
+ bool warned = diag.emit_warning_va (opt, gmsgid, &ap);
+#else
+ bool warned = format_warning_va (fmt_loc, UNKNOWN_LOCATION, NULL,
+ opt, gmsgid, &ap);
+#endif
+ va_end (ap);
+
+ return warned;
+}
+
+/* Check that we have a pointer to a string suitable for use as a format.
+ The default is to check for a char type.
+ For objective-c dialects, this is extended to include references to string
+ objects validated by objc_string_ref_type_p ().
+ Targets may also provide a string object type that can be used within c and
+ c++ and shared with their respective objective-c dialects. In this case the
+ reference to a format string is checked for validity via a hook.
+
+ The function returns true if strref points to any string type valid for the
+ language dialect and target. */
+
+static bool
+valid_stringptr_type_p (tree strref)
+{
+ return (strref != NULL
+ && TREE_CODE (strref) == POINTER_TYPE
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (strref)) == char_type_node
+ || objc_string_ref_type_p (strref)));
+// || (*targetcm.string_object_ref_type_p) ((const_tree) strref)));
+}
+
+/* Handle a "format_arg" attribute; arguments as in
+ struct attribute_spec.handler. */
+tree
+handle_frr_format_arg_attribute (tree *node, tree ARG_UNUSED (name),
+ tree args, int flags, bool *no_add_attrs)
+{
+ tree type = *node;
+ tree format_num_expr = TREE_VALUE (args);
+ unsigned HOST_WIDE_INT format_num = 0;
+
+ if (!get_constant (format_num_expr, &format_num, 0))
+ {
+ error ("format string has invalid operand number");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ if (prototype_p (type))
+ {
+ /* The format arg can be any string reference valid for the language and
+ target. We cannot be more specific in this case. */
+ if (!check_format_string (type, format_num, flags, no_add_attrs, -1))
+ return NULL_TREE;
+ }
+
+ if (!valid_stringptr_type_p (TREE_TYPE (type)))
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("function does not return string type");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
+
+/* Verify that the format_num argument is actually a string reference suitable,
+ for the language dialect and target (in case the format attribute is in
+ error). When we know the specific reference type expected, this is also
+ checked. */
+static bool
+check_format_string (tree fntype, unsigned HOST_WIDE_INT format_num,
+ int flags, bool *no_add_attrs, int expected_format_type)
+{
+ unsigned HOST_WIDE_INT i;
+ bool is_target_sref, is_char_ref;
+ tree ref;
+ int fmt_flags;
+ function_args_iterator iter;
+
+ i = 1;
+ FOREACH_FUNCTION_ARGS (fntype, ref, iter)
+ {
+ if (i == format_num)
+ break;
+ i++;
+ }
+
+ if (!ref
+ || !valid_stringptr_type_p (ref))
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("format string argument is not a string type");
+ *no_add_attrs = true;
+ return false;
+ }
+
+ /* We only know that we want a suitable string reference. */
+ if (expected_format_type < 0)
+ return true;
+
+ /* Now check that the arg matches the expected type. */
+ is_char_ref =
+ (TYPE_MAIN_VARIANT (TREE_TYPE (ref)) == char_type_node);
+
+ fmt_flags = format_flags (expected_format_type);
+ is_target_sref = false;
+
+ if (!(fmt_flags & FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL))
+ {
+ if (is_char_ref)
+ return true; /* OK, we expected a char and found one. */
+ else
+ {
+ error ("found a %qT but the format argument should be a string",
+ ref);
+ *no_add_attrs = true;
+ return false;
+ }
+ }
+
+ /* We expect a string object type as the format arg. */
+ if (is_char_ref)
+ {
+ error ("format argument should be a %qs reference but a string was found", format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ /* We will allow a target string ref to match only itself. */
+ if (first_target_format_type
+ && expected_format_type >= first_target_format_type
+ && is_target_sref)
+ return true;
+ else
+ {
+ error ("format argument should be a %qs reference",
+ format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ gcc_unreachable ();
+}
+
+/* Verify EXPR is a constant, and store its value.
+ If validated_p is true there should be no errors.
+ Returns true on success, false otherwise. */
+static bool
+get_constant (tree expr, unsigned HOST_WIDE_INT *value, int validated_p)
+{
+ if (!tree_fits_uhwi_p (expr))
+ {
+ gcc_assert (!validated_p);
+ return false;
+ }
+
+ *value = TREE_INT_CST_LOW (expr);
+
+ return true;
+}
+
+/* Decode the arguments to a "format" attribute into a
+ function_format_info structure. It is already known that the list
+ is of the right length. If VALIDATED_P is true, then these
+ attributes have already been validated and must not be erroneous;
+ if false, it will give an error message. Returns true if the
+ attributes are successfully decoded, false otherwise. */
+
+static bool
+decode_format_attr (tree args, function_format_info *info, int validated_p)
+{
+ tree format_type_id = TREE_VALUE (args);
+ tree format_num_expr = TREE_VALUE (TREE_CHAIN (args));
+ tree first_arg_num_expr
+ = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
+
+ if (TREE_CODE (format_type_id) != STRING_CST)
+ {
+ gcc_assert (!validated_p);
+ error ("unrecognized format specifier");
+ return false;
+ }
+ else
+ {
+ const char *p = TREE_STRING_POINTER (format_type_id);
+
+ p = convert_format_name_to_system_name (p);
+
+ info->format_type = decode_format_type (p);
+
+ if (info->format_type == format_type_error)
+ {
+ gcc_assert (!validated_p);
+ warning (OPT_Wformat_, "%qE is an unrecognized format function type",
+ format_type_id);
+ return false;
+ }
+ }
+
+ if (!get_constant (format_num_expr, &info->format_num, validated_p))
+ {
+ error ("format string has invalid operand number");
+ return false;
+ }
+
+ if (!get_constant (first_arg_num_expr, &info->first_arg_num, validated_p))
+ {
+ error ("%<...%> has invalid operand number");
+ return false;
+ }
+
+ if (info->first_arg_num != 0 && info->first_arg_num <= info->format_num)
+ {
+ gcc_assert (!validated_p);
+ error ("format string argument follows the arguments to be formatted");
+ return false;
+ }
+
+ return true;
+}
+
+/* Check a call to a format function against a parameter list. */
+
+/* The C standard version C++ is treated as equivalent to
+ or inheriting from, for the purpose of format features supported. */
+#define CPLUSPLUS_STD_VER (cxx_dialect < cxx11 ? STD_C94 : STD_C99)
+/* The C standard version we are checking formats against when pedantic. */
+#define C_STD_VER ((int) (c_dialect_cxx () \
+ ? CPLUSPLUS_STD_VER \
+ : (flag_isoc99 \
+ ? STD_C99 \
+ : (flag_isoc94 ? STD_C94 : STD_C89))))
+/* The name to give to the standard version we are warning about when
+ pedantic. FEATURE_VER is the version in which the feature warned out
+ appeared, which is higher than C_STD_VER. */
+#define C_STD_NAME(FEATURE_VER) (c_dialect_cxx () \
+ ? (cxx_dialect < cxx11 ? "ISO C++98" \
+ : "ISO C++11") \
+ : ((FEATURE_VER) == STD_EXT \
+ ? "ISO C" \
+ : "ISO C90"))
+/* Adjust a C standard version, which may be STD_C9L, to account for
+ -Wno-long-long. Returns other standard versions unchanged. */
+#define ADJ_STD(VER) ((int) ((VER) == STD_C9L \
+ ? (warn_long_long ? STD_C99 : STD_C89) \
+ : (VER)))
+
+/* Enum describing the kind of specifiers present in the format and
+ requiring an argument. */
+enum format_specifier_kind {
+ CF_KIND_FORMAT,
+ CF_KIND_FIELD_WIDTH,
+ CF_KIND_FIELD_PRECISION
+};
+
+static const char *kind_descriptions[] = {
+ N_("format"),
+ N_("field width specifier"),
+ N_("field precision specifier")
+};
+
+/* Structure describing details of a type expected in format checking,
+ and the type to check against it. */
+struct format_wanted_type
+{
+ /* The type wanted. */
+ tree wanted_type;
+ /* The name of this type to use in diagnostics. */
+ const char *wanted_type_name;
+ /* Should be type checked just for scalar width identity. */
+ int scalar_identity_flag;
+ /* The level of indirection through pointers at which this type occurs. */
+ int pointer_count;
+ /* Whether, when pointer_count is 1, to allow any character type when
+ pedantic, rather than just the character or void type specified. */
+ int char_lenient_flag;
+ /* Whether the argument, dereferenced once, is written into and so the
+ argument must not be a pointer to a const-qualified type. */
+ int writing_in_flag;
+ /* Whether the argument, dereferenced once, is read from and so
+ must not be a NULL pointer. */
+ int reading_from_flag;
+ /* The kind of specifier that this type is used for. */
+ enum format_specifier_kind kind;
+ /* The starting character of the specifier. This never includes the
+ initial percent sign. */
+ const char *format_start;
+ /* The length of the specifier. */
+ int format_length;
+ /* The actual parameter to check against the wanted type. */
+ tree param;
+ /* The argument number of that parameter. */
+ int arg_num;
+ /* The offset location of this argument with respect to the format
+ string location. */
+ unsigned int offset_loc;
+ /* The next type to check for this format conversion, or NULL if none. */
+ struct format_wanted_type *next;
+};
+
+/* Convenience macro for format_length_info meaning unused. */
+#define NO_FMT NULL, FMT_LEN_none, STD_C89
+
+static const format_length_info printf_length_specs[] =
+{
+ { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99, 0 },
+ { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C9L, 0 },
+ { "q", FMT_LEN_ll, STD_EXT, NO_FMT, 0 },
+ { "L", FMT_LEN_L, STD_C89, NO_FMT, 0 },
+ { "z", FMT_LEN_z, STD_C99, NO_FMT, 0 },
+ { "Z", FMT_LEN_z, STD_EXT, NO_FMT, 0 },
+ { "t", FMT_LEN_t, STD_C99, NO_FMT, 0 },
+ { "j", FMT_LEN_j, STD_C99, NO_FMT, 0 },
+ { "H", FMT_LEN_H, STD_EXT, NO_FMT, 0 },
+ { "D", FMT_LEN_D, STD_EXT, "DD", FMT_LEN_DD, STD_EXT, 0 },
+ { NO_FMT, NO_FMT, 0 }
+};
+
+static const format_flag_spec printf_flag_specs[] =
+{
+ { ' ', 0, 0, 0, N_("' ' flag"), N_("the ' ' printf flag"), STD_C89 },
+ { '+', 0, 0, 0, N_("'+' flag"), N_("the '+' printf flag"), STD_C89 },
+ { '#', 0, 0, 0, N_("'#' flag"), N_("the '#' printf flag"), STD_C89 },
+ { '0', 0, 0, 0, N_("'0' flag"), N_("the '0' printf flag"), STD_C89 },
+ { '-', 0, 0, 0, N_("'-' flag"), N_("the '-' printf flag"), STD_C89 },
+ { '\'', 0, 0, 0, N_("''' flag"), N_("the ''' printf flag"), STD_EXT },
+ { 'I', 0, 0, 0, N_("'I' flag"), N_("the 'I' printf flag"), STD_EXT },
+ { 'w', 0, 0, 0, N_("field width"), N_("field width in printf format"), STD_C89 },
+ { 'p', 0, 0, 0, N_("precision"), N_("precision in printf format"), STD_C89 },
+ { 'L', 0, 0, 0, N_("length modifier"), N_("length modifier in printf format"), STD_C89 },
+ { 0, 0, 0, 0, NULL, NULL, STD_C89 }
+};
+
+
+static const format_flag_pair printf_flag_pairs[] =
+{
+ { ' ', '+', 1, 0 },
+ { '0', '-', 1, 0 },
+ { '0', 'p', 1, 'i' },
+ { 0, 0, 0, 0 }
+};
+
+#define ETAB_SZ 128
+static kernel_ext_fmt ext_p[ETAB_SZ] = {
+ { }
+};
+static kernel_ext_fmt ext_d[ETAB_SZ] = {
+ { }
+};
+
+static const format_char_info print_char_table[] =
+{
+ /* C89 conversion specifiers. */
+ /* none, hh, h, l, ll, L, z, t, j, H, D, DD */
+ { "di", 0, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, TEX_S64, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "-wp0 +'I", "i", NULL, ext_d },
+ { "oxXb",0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_U64, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0#", "i", NULL, NULL },
+ { "u", 0, STD_C89, { T89_UI, T99_UC, T89_US, T89_UL, T9L_ULL, TEX_U64, T99_ST, T99_UPD, T99_UIM, BADLEN, BADLEN, BADLEN }, "-wp0'I", "i", NULL, NULL },
+ { "fgG", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, NULL },
+ { "eE", 0, STD_C89, { T89_D, BADLEN, BADLEN, T99_D, BADLEN, T89_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I", "", NULL, NULL },
+ { "c", 0, STD_C89, { T89_I, BADLEN, BADLEN, T94_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL, NULL },
+ { "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, T94_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "cR", NULL, NULL },
+ { "p", 1, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "c", NULL, ext_p },
+ { "n", 1, STD_C89, { T89_I, T99_SC, T89_S, T89_L, T9L_LL, BADLEN, T99_SST, T99_PD, T99_IM, BADLEN, BADLEN, BADLEN }, "", "W", NULL, NULL },
+ /* C99 conversion specifiers. */
+ { "F", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "", NULL, NULL },
+ { "aA", 0, STD_C99, { T99_D, BADLEN, BADLEN, T99_D, BADLEN, T99_LD, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp0 +#", "", NULL, NULL },
+ /* X/Open conversion specifiers. */
+ { "C", 0, STD_EXT, { TEX_WI, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-w", "", NULL, NULL },
+ { "S", 1, STD_EXT, { TEX_W, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "R", NULL, NULL },
+ /* GNU conversion specifiers. */
+ { "m", 0, STD_EXT, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "-wp", "", NULL, NULL },
+ { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL, NULL }
+};
+
+/* This must be in the same order as enum format_type. */
+static const format_kind_info format_types_orig[] =
+{
+ { "frr_printf", printf_length_specs, print_char_table, " +#0-'I", NULL,
+ printf_flag_specs, printf_flag_pairs,
+ FMT_FLAG_ARG_CONVERT|FMT_FLAG_DOLLAR_MULTIPLE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_EMPTY_PREC_OK,
+ 'w', 0, 'p', 0, 'L', 0,
+ &integer_type_node, &integer_type_node
+ },
+};
+
+/* This layer of indirection allows GCC to reassign format_types with
+ new data if necessary, while still allowing the original data to be
+ const. */
+static const format_kind_info *format_types = format_types_orig;
+
+static int n_format_types = ARRAY_SIZE (format_types_orig);
+
+/* Structure detailing the results of checking a format function call
+ where the format expression may be a conditional expression with
+ many leaves resulting from nested conditional expressions. */
+struct format_check_results
+{
+ /* Number of leaves of the format argument that could not be checked
+ as they were not string literals. */
+ int number_non_literal;
+ /* Number of leaves of the format argument that were null pointers or
+ string literals, but had extra format arguments. */
+ int number_extra_args;
+ location_t extra_arg_loc;
+ /* Number of leaves of the format argument that were null pointers or
+ string literals, but had extra format arguments and used $ operand
+ numbers. */
+ int number_dollar_extra_args;
+ /* Number of leaves of the format argument that were wide string
+ literals. */
+ int number_wide;
+ /* Number of leaves of the format argument that are not array of "char". */
+ int number_non_char;
+ /* Number of leaves of the format argument that were empty strings. */
+ int number_empty;
+ /* Number of leaves of the format argument that were unterminated
+ strings. */
+ int number_unterminated;
+ /* Number of leaves of the format argument that were not counted above. */
+ int number_other;
+ /* Location of the format string. */
+ location_t format_string_loc;
+};
+
+struct format_check_context
+{
+ format_check_results *res;
+ function_format_info *info;
+ tree params;
+ vec<location_t> *arglocs;
+};
+
+/* Return the format name (as specified in the original table) for the format
+ type indicated by format_num. */
+static const char *
+format_name (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].name;
+ gcc_unreachable ();
+}
+
+/* Return the format flags (as specified in the original table) for the format
+ type indicated by format_num. */
+static int
+format_flags (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].flags;
+ gcc_unreachable ();
+}
+
+static void check_format_info (function_format_info *, tree,
+ vec<location_t> *);
+static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
+static void check_format_info_main (format_check_results *,
+ function_format_info *, const char *,
+ location_t, tree,
+ int, tree,
+ unsigned HOST_WIDE_INT,
+ object_allocator<format_wanted_type> &,
+ vec<location_t> *);
+
+static void init_dollar_format_checking (int, tree);
+static int maybe_read_dollar_number (const char **, int,
+ tree, tree *, const format_kind_info *);
+static bool avoid_dollar_number (const char *);
+static void finish_dollar_format_checking (format_check_results *, int);
+
+static const format_flag_spec *get_flag_spec (const format_flag_spec *,
+ int, const char *);
+
+static void check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs);
+static void format_type_warning (const substring_loc &fmt_loc,
+ location_t param_loc,
+ format_wanted_type *, tree,
+ tree,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ const char *extra = NULL);
+
+static bool check_kef_type (const substring_loc &fmt_loc,
+ const struct kernel_ext_fmt *kef,
+ unsigned arg_num,
+ tree cur_param,
+ tree wanted_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs);
+
+/* Decode a format type from a string, returning the type, or
+ format_type_error if not valid, in which case the caller should print an
+ error message. */
+static int
+decode_format_type (const char *s)
+{
+ int i;
+ int slen;
+
+ s = convert_format_name_to_system_name (s);
+ slen = strlen (s);
+ for (i = 0; i < n_format_types; i++)
+ {
+ int alen;
+ if (!strcmp (s, format_types[i].name))
+ return i;
+ alen = strlen (format_types[i].name);
+ if (slen == alen + 4 && s[0] == '_' && s[1] == '_'
+ && s[slen - 1] == '_' && s[slen - 2] == '_'
+ && !strncmp (s + 2, format_types[i].name, alen))
+ return i;
+ }
+ return format_type_error;
+}
+
+
+/* Check the argument list of a call to printf, scanf, etc.
+ ATTRS are the attributes on the function type. There are NARGS argument
+ values in the array ARGARRAY.
+ Also, if -Wsuggest-attribute=format,
+ warn for calls to vprintf or vscanf in functions with no such format
+ attribute themselves. */
+
+void
+check_function_format (tree attrs, int nargs, tree *argarray,
+ vec<location_t> *arglocs)
+{
+ tree a;
+
+ /* See if this function has any format attributes. */
+ for (a = attrs; a; a = TREE_CHAIN (a))
+ {
+ if (is_attribute_p ("frr_format", TREE_PURPOSE (a)))
+ {
+ /* Yup; check it. */
+ function_format_info info;
+ decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true);
+ if (warn_format)
+ {
+ /* FIXME: Rewrite all the internal functions in this file
+ to use the ARGARRAY directly instead of constructing this
+ temporary list. */
+ tree params = NULL_TREE;
+ int i;
+ for (i = nargs - 1; i >= 0; i--)
+ params = tree_cons (NULL_TREE, argarray[i], params);
+ check_format_info (&info, params, arglocs);
+ }
+
+ /* Attempt to detect whether the current function might benefit
+ from the format attribute if the called function is decorated
+ with it. Avoid using calls with string literal formats for
+ guidance since those are unlikely to be viable candidates. */
+ if (warn_suggest_attribute_format
+ && current_function_decl != NULL_TREE
+ && info.first_arg_num == 0
+ && (format_types[info.format_type].flags
+ & (int) FMT_FLAG_ARG_CONVERT)
+ /* c_strlen will fail for a function parameter but succeed
+ for a literal or constant array. */
+ && !c_strlen (argarray[info.format_num - 1], 1))
+ {
+ tree c;
+ for (c = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl));
+ c;
+ c = TREE_CHAIN (c))
+ if (is_attribute_p ("frr_format", TREE_PURPOSE (c))
+ && (decode_format_type (IDENTIFIER_POINTER
+ (TREE_VALUE (TREE_VALUE (c))))
+ == info.format_type))
+ break;
+ if (c == NULL_TREE)
+ {
+ /* Check if the current function has a parameter to which
+ the format attribute could be attached; if not, it
+ can't be a candidate for a format attribute, despite
+ the vprintf-like or vscanf-like call. */
+ tree args;
+ for (args = DECL_ARGUMENTS (current_function_decl);
+ args != 0;
+ args = DECL_CHAIN (args))
+ {
+ if (TREE_CODE (TREE_TYPE (args)) == POINTER_TYPE
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (args)))
+ == char_type_node))
+ break;
+ }
+ if (args != 0)
+ warning (OPT_Wsuggest_attribute_format,
+ "function %qD might be a candidate for %qs %<frr_format%> attribute",
+ current_function_decl,
+ format_types[info.format_type].name);
+ }
+ }
+ }
+ }
+}
+
+
+/* Variables used by the checking of $ operand number formats. */
+static char *dollar_arguments_used = NULL;
+static char *dollar_arguments_pointer_p = NULL;
+static int dollar_arguments_alloc = 0;
+static int dollar_arguments_count;
+static int dollar_first_arg_num;
+static int dollar_max_arg_used;
+static int dollar_format_warned;
+
+/* Initialize the checking for a format string that may contain $
+ parameter number specifications; we will need to keep track of whether
+ each parameter has been used. FIRST_ARG_NUM is the number of the first
+ argument that is a parameter to the format, or 0 for a vprintf-style
+ function; PARAMS is the list of arguments starting at this argument. */
+
+static void
+init_dollar_format_checking (int first_arg_num, tree params)
+{
+ tree oparams = params;
+
+ dollar_first_arg_num = first_arg_num;
+ dollar_arguments_count = 0;
+ dollar_max_arg_used = 0;
+ dollar_format_warned = 0;
+ if (first_arg_num > 0)
+ {
+ while (params)
+ {
+ dollar_arguments_count++;
+ params = TREE_CHAIN (params);
+ }
+ }
+ if (dollar_arguments_alloc < dollar_arguments_count)
+ {
+ free (dollar_arguments_used);
+ free (dollar_arguments_pointer_p);
+ dollar_arguments_alloc = dollar_arguments_count;
+ dollar_arguments_used = XNEWVEC (char, dollar_arguments_alloc);
+ dollar_arguments_pointer_p = XNEWVEC (char, dollar_arguments_alloc);
+ }
+ if (dollar_arguments_alloc)
+ {
+ memset (dollar_arguments_used, 0, dollar_arguments_alloc);
+ if (first_arg_num > 0)
+ {
+ int i = 0;
+ params = oparams;
+ while (params)
+ {
+ dollar_arguments_pointer_p[i] = (TREE_CODE (TREE_TYPE (TREE_VALUE (params)))
+ == POINTER_TYPE);
+ params = TREE_CHAIN (params);
+ i++;
+ }
+ }
+ }
+}
+
+
+/* Look for a decimal number followed by a $ in *FORMAT. If DOLLAR_NEEDED
+ is set, it is an error if one is not found; otherwise, it is OK. If
+ such a number is found, check whether it is within range and mark that
+ numbered operand as being used for later checking. Returns the operand
+ number if found and within range, zero if no such number was found and
+ this is OK, or -1 on error. PARAMS points to the first operand of the
+ format; PARAM_PTR is made to point to the parameter referred to. If
+ a $ format is found, *FORMAT is updated to point just after it. */
+
+static int
+maybe_read_dollar_number (const char **format,
+ int dollar_needed, tree params, tree *param_ptr,
+ const format_kind_info *fki)
+{
+ int argnum;
+ int overflow_flag;
+ const char *fcp = *format;
+ if (!ISDIGIT (*fcp))
+ {
+ if (dollar_needed)
+ {
+ warning (OPT_Wformat_, "missing $ operand number in format");
+ return -1;
+ }
+ else
+ return 0;
+ }
+ argnum = 0;
+ overflow_flag = 0;
+ while (ISDIGIT (*fcp))
+ {
+ int nargnum;
+ nargnum = 10 * argnum + (*fcp - '0');
+ if (nargnum < 0 || nargnum / 10 != argnum)
+ overflow_flag = 1;
+ argnum = nargnum;
+ fcp++;
+ }
+ if (*fcp != '$')
+ {
+ if (dollar_needed)
+ {
+ warning (OPT_Wformat_, "missing $ operand number in format");
+ return -1;
+ }
+ else
+ return 0;
+ }
+ *format = fcp + 1;
+ if (pedantic && !dollar_format_warned)
+ {
+ warning (OPT_Wformat_, "%s does not support %%n$ operand number formats",
+ C_STD_NAME (STD_EXT));
+ dollar_format_warned = 1;
+ }
+ if (overflow_flag || argnum == 0
+ || (dollar_first_arg_num && argnum > dollar_arguments_count))
+ {
+ warning (OPT_Wformat_, "operand number out of range in format");
+ return -1;
+ }
+ if (argnum > dollar_max_arg_used)
+ dollar_max_arg_used = argnum;
+ /* For vprintf-style functions we may need to allocate more memory to
+ track which arguments are used. */
+ while (dollar_arguments_alloc < dollar_max_arg_used)
+ {
+ int nalloc;
+ nalloc = 2 * dollar_arguments_alloc + 16;
+ dollar_arguments_used = XRESIZEVEC (char, dollar_arguments_used,
+ nalloc);
+ dollar_arguments_pointer_p = XRESIZEVEC (char, dollar_arguments_pointer_p,
+ nalloc);
+ memset (dollar_arguments_used + dollar_arguments_alloc, 0,
+ nalloc - dollar_arguments_alloc);
+ dollar_arguments_alloc = nalloc;
+ }
+ if (!(fki->flags & (int) FMT_FLAG_DOLLAR_MULTIPLE)
+ && dollar_arguments_used[argnum - 1] == 1)
+ {
+ dollar_arguments_used[argnum - 1] = 2;
+ warning (OPT_Wformat_, "format argument %d used more than once in %s format",
+ argnum, fki->name);
+ }
+ else
+ dollar_arguments_used[argnum - 1] = 1;
+ if (dollar_first_arg_num)
+ {
+ int i;
+ *param_ptr = params;
+ for (i = 1; i < argnum && *param_ptr != 0; i++)
+ *param_ptr = TREE_CHAIN (*param_ptr);
+
+ /* This case shouldn't be caught here. */
+ gcc_assert (*param_ptr);
+ }
+ else
+ *param_ptr = 0;
+ return argnum;
+}
+
+/* Ensure that FORMAT does not start with a decimal number followed by
+ a $; give a diagnostic and return true if it does, false otherwise. */
+
+static bool
+avoid_dollar_number (const char *format)
+{
+ if (!ISDIGIT (*format))
+ return false;
+ while (ISDIGIT (*format))
+ format++;
+ if (*format == '$')
+ {
+ warning (OPT_Wformat_, "%<$%> operand number used after format without operand number");
+ return true;
+ }
+ return false;
+}
+
+
+/* Finish the checking for a format string that used $ operand number formats
+ instead of non-$ formats. We check for unused operands before used ones
+ (a serious error, since the implementation of the format function
+ can't know what types to pass to va_arg to find the later arguments).
+ and for unused operands at the end of the format (if we know how many
+ arguments the format had, so not for vprintf). If there were operand
+ numbers out of range on a non-vprintf-style format, we won't have reached
+ here. If POINTER_GAP_OK, unused arguments are OK if all arguments are
+ pointers. */
+
+static void
+finish_dollar_format_checking (format_check_results *res, int pointer_gap_ok)
+{
+ int i;
+ bool found_pointer_gap = false;
+ for (i = 0; i < dollar_max_arg_used; i++)
+ {
+ if (!dollar_arguments_used[i])
+ {
+ if (pointer_gap_ok && (dollar_first_arg_num == 0
+ || dollar_arguments_pointer_p[i]))
+ found_pointer_gap = true;
+ else
+ warning_at (res->format_string_loc, OPT_Wformat_,
+ "format argument %d unused before used argument %d in %<$%>-style format",
+ i + 1, dollar_max_arg_used);
+ }
+ }
+ if (found_pointer_gap
+ || (dollar_first_arg_num
+ && dollar_max_arg_used < dollar_arguments_count))
+ {
+ res->number_other--;
+ res->number_dollar_extra_args++;
+ }
+}
+
+
+/* Retrieve the specification for a format flag. SPEC contains the
+ specifications for format flags for the applicable kind of format.
+ FLAG is the flag in question. If PREDICATES is NULL, the basic
+ spec for that flag must be retrieved and must exist. If
+ PREDICATES is not NULL, it is a string listing possible predicates
+ for the spec entry; if an entry predicated on any of these is
+ found, it is returned, otherwise NULL is returned. */
+
+static const format_flag_spec *
+get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
+{
+ int i;
+ for (i = 0; spec[i].flag_char != 0; i++)
+ {
+ if (spec[i].flag_char != flag)
+ continue;
+ if (predicates != NULL)
+ {
+ if (spec[i].predicate != 0
+ && strchr (predicates, spec[i].predicate) != 0)
+ return &spec[i];
+ }
+ else if (spec[i].predicate == 0)
+ return &spec[i];
+ }
+ gcc_assert (predicates);
+ return NULL;
+}
+
+
+/* Check the argument list of a call to printf, scanf, etc.
+ INFO points to the function_format_info structure.
+ PARAMS is the list of argument values. */
+
+static void
+check_format_info (function_format_info *info, tree params,
+ vec<location_t> *arglocs)
+{
+ format_check_context format_ctx;
+ unsigned HOST_WIDE_INT arg_num;
+ tree format_tree;
+ format_check_results res;
+ /* Skip to format argument. If the argument isn't available, there's
+ no work for us to do; prototype checking will catch the problem. */
+ for (arg_num = 1; ; ++arg_num)
+ {
+ if (params == 0)
+ return;
+ if (arg_num == info->format_num)
+ break;
+ params = TREE_CHAIN (params);
+ }
+ format_tree = TREE_VALUE (params);
+ params = TREE_CHAIN (params);
+ if (format_tree == 0)
+ return;
+
+ res.number_non_literal = 0;
+ res.number_extra_args = 0;
+ res.extra_arg_loc = UNKNOWN_LOCATION;
+ res.number_dollar_extra_args = 0;
+ res.number_wide = 0;
+ res.number_non_char = 0;
+ res.number_empty = 0;
+ res.number_unterminated = 0;
+ res.number_other = 0;
+ res.format_string_loc = input_location;
+
+ format_ctx.res = &res;
+ format_ctx.info = info;
+ format_ctx.params = params;
+ format_ctx.arglocs = arglocs;
+
+ check_function_arguments_recurse (check_format_arg, &format_ctx,
+ format_tree, arg_num, OPT_Wformat_);
+
+ location_t loc = format_ctx.res->format_string_loc;
+
+ if (res.number_non_literal > 0)
+ {
+ /* Functions taking a va_list normally pass a non-literal format
+ string. These functions typically are declared with
+ first_arg_num == 0, so avoid warning in those cases. */
+ if (!(format_types[info->format_type].flags & (int) FMT_FLAG_ARG_CONVERT))
+ {
+ /* For strftime-like formats, warn for not checking the format
+ string; but there are no arguments to check. */
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal, format string not checked");
+ }
+ else if (info->first_arg_num != 0)
+ {
+ /* If there are no arguments for the format at all, we may have
+ printf (foo) which is likely to be a security hole. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ break;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ if (params == 0 && warn_format_security)
+ warning_at (loc, OPT_Wformat_security,
+ "format not a string literal and no format arguments");
+ else if (params == 0 && warn_format_nonliteral)
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal and no format arguments");
+ else
+ warning_at (loc, OPT_Wformat_nonliteral,
+ "format not a string literal, argument types not checked");
+ }
+ }
+
+ /* If there were extra arguments to the format, normally warn. However,
+ the standard does say extra arguments are ignored, so in the specific
+ case where we have multiple leaves (conditional expressions or
+ ngettext) allow extra arguments if at least one leaf didn't have extra
+ arguments, but was otherwise OK (either non-literal or checked OK).
+ If the format is an empty string, this should be counted similarly to the
+ case of extra format arguments. */
+ if (res.number_extra_args > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ {
+ if (res.extra_arg_loc == UNKNOWN_LOCATION)
+ res.extra_arg_loc = loc;
+ warning_at (res.extra_arg_loc, OPT_Wformat_extra_args,
+ "too many arguments for format");
+ }
+ if (res.number_dollar_extra_args > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ warning_at (loc, OPT_Wformat_extra_args, "unused arguments in %<$%>-style format");
+ if (res.number_empty > 0 && res.number_non_literal == 0
+ && res.number_other == 0)
+ warning_at (loc, OPT_Wformat_zero_length, "zero-length %s format string",
+ format_types[info->format_type].name);
+
+ if (res.number_wide > 0)
+ warning_at (loc, OPT_Wformat_, "format is a wide character string");
+
+ if (res.number_non_char > 0)
+ warning_at (loc, OPT_Wformat_,
+ "format string is not an array of type %qs", "char");
+
+ if (res.number_unterminated > 0)
+ warning_at (loc, OPT_Wformat_, "unterminated format string");
+}
+
+/* Callback from check_function_arguments_recurse to check a
+ format string. FORMAT_TREE is the format parameter. ARG_NUM
+ is the number of the format argument. CTX points to a
+ format_check_context. */
+
+static void
+check_format_arg (void *ctx, tree format_tree,
+ unsigned HOST_WIDE_INT arg_num)
+{
+ format_check_context *format_ctx = (format_check_context *) ctx;
+ format_check_results *res = format_ctx->res;
+ function_format_info *info = format_ctx->info;
+ tree params = format_ctx->params;
+ vec<location_t> *arglocs = format_ctx->arglocs;
+
+ int format_length;
+ HOST_WIDE_INT offset;
+ const char *format_chars;
+ tree array_size = 0;
+ tree array_init;
+
+ location_t fmt_param_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+
+ /* Pull out a constant value if the front end didn't, and handle location
+ wrappers. */
+ format_tree = fold_for_warn (format_tree);
+ STRIP_NOPS (format_tree);
+
+ if (integer_zerop (format_tree))
+ {
+ /* Skip to first argument to check, so we can see if this format
+ has any arguments (it shouldn't). */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+
+ if (params == 0)
+ res->number_other++;
+ else
+ {
+ if (res->number_extra_args == 0)
+ res->extra_arg_loc = EXPR_LOC_OR_LOC (TREE_VALUE (params),
+ input_location);
+ res->number_extra_args++;
+ }
+ return;
+ }
+
+ offset = 0;
+ if (TREE_CODE (format_tree) == POINTER_PLUS_EXPR)
+ {
+ tree arg0, arg1;
+
+ arg0 = TREE_OPERAND (format_tree, 0);
+ arg1 = TREE_OPERAND (format_tree, 1);
+ STRIP_NOPS (arg0);
+ STRIP_NOPS (arg1);
+ if (TREE_CODE (arg1) == INTEGER_CST)
+ format_tree = arg0;
+ else
+ {
+ res->number_non_literal++;
+ return;
+ }
+ /* POINTER_PLUS_EXPR offsets are to be interpreted signed. */
+ if (!cst_and_fits_in_hwi (arg1))
+ {
+ res->number_non_literal++;
+ return;
+ }
+ offset = int_cst_value (arg1);
+ }
+ if (TREE_CODE (format_tree) != ADDR_EXPR)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ res->format_string_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+ format_tree = TREE_OPERAND (format_tree, 0);
+ if (format_types[info->format_type].flags
+ & (int) FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL)
+ {
+ /* We cannot examine this string here - but we can check that it is
+ a valid type. */
+ if (TREE_CODE (format_tree) != CONST_DECL)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ /* Skip to first argument to check. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ return;
+ }
+ if (TREE_CODE (format_tree) == ARRAY_REF
+ && tree_fits_shwi_p (TREE_OPERAND (format_tree, 1))
+ && (offset += tree_to_shwi (TREE_OPERAND (format_tree, 1))) >= 0)
+ format_tree = TREE_OPERAND (format_tree, 0);
+ if (offset < 0)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ if (VAR_P (format_tree)
+ && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE
+ && (array_init = decl_constant_value (format_tree)) != format_tree
+ && TREE_CODE (array_init) == STRING_CST)
+ {
+ /* Extract the string constant initializer. Note that this may include
+ a trailing NUL character that is not in the array (e.g.
+ const char a[3] = "foo";). */
+ array_size = DECL_SIZE_UNIT (format_tree);
+ format_tree = array_init;
+ }
+ if (TREE_CODE (format_tree) != STRING_CST)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ tree underlying_type
+ = TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree)));
+ if (underlying_type != char_type_node)
+ {
+ if (underlying_type == char16_type_node
+ || underlying_type == char32_type_node
+ || underlying_type == wchar_type_node)
+ res->number_wide++;
+ else
+ res->number_non_char++;
+ return;
+ }
+ format_chars = TREE_STRING_POINTER (format_tree);
+ format_length = TREE_STRING_LENGTH (format_tree);
+ if (array_size != 0)
+ {
+ /* Variable length arrays can't be initialized. */
+ gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+
+ if (tree_fits_shwi_p (array_size))
+ {
+ HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
+ if (array_size_value > 0
+ && array_size_value == (int) array_size_value
+ && format_length > array_size_value)
+ format_length = array_size_value;
+ }
+ }
+ if (offset)
+ {
+ if (offset >= format_length)
+ {
+ res->number_non_literal++;
+ return;
+ }
+ format_chars += offset;
+ format_length -= offset;
+ }
+ if (format_length < 1 || format_chars[--format_length] != 0)
+ {
+ res->number_unterminated++;
+ return;
+ }
+ if (format_length == 0)
+ {
+ res->number_empty++;
+ return;
+ }
+
+ /* Skip to first argument to check. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ /* Provisionally increment res->number_other; check_format_info_main
+ will decrement it if it finds there are extra arguments, but this way
+ need not adjust it for every return. */
+ res->number_other++;
+ object_allocator <format_wanted_type> fwt_pool ("format_wanted_type pool");
+ check_format_info_main (res, info, format_chars, fmt_param_loc, format_tree,
+ format_length, params, arg_num, fwt_pool, arglocs);
+}
+
+/* Support class for argument_parser and check_format_info_main.
+ Tracks any flag characters that have been applied to the
+ current argument. */
+
+class flag_chars_t
+{
+ public:
+ flag_chars_t ();
+ bool has_char_p (char ch) const;
+ void add_char (char ch);
+ void validate (const format_kind_info *fki,
+ const format_char_info *fci,
+ const format_flag_spec *flag_specs,
+ const char * const format_chars,
+ tree format_string_cst,
+ location_t format_string_loc,
+ const char * const orig_format_chars,
+ char format_char,
+ bool quoted);
+ int get_alloc_flag (const format_kind_info *fki);
+ int assignment_suppression_p (const format_kind_info *fki);
+
+ private:
+ char m_flag_chars[256];
+};
+
+/* Support struct for argument_parser and check_format_info_main.
+ Encapsulates any length modifier applied to the current argument. */
+
+struct length_modifier
+{
+ length_modifier ()
+ : chars (NULL), val (FMT_LEN_none), std (STD_C89),
+ scalar_identity_flag (0)
+ {
+ }
+
+ length_modifier (const char *chars_,
+ enum format_lengths val_,
+ enum format_std_version std_,
+ int scalar_identity_flag_)
+ : chars (chars_), val (val_), std (std_),
+ scalar_identity_flag (scalar_identity_flag_)
+ {
+ }
+
+ const char *chars;
+ enum format_lengths val;
+ enum format_std_version std;
+ int scalar_identity_flag;
+};
+
+/* Parsing one argument within a format string. */
+
+class argument_parser
+{
+ public:
+ argument_parser (function_format_info *info, const char *&format_chars,
+ tree format_string_cst,
+ const char * const orig_format_chars,
+ location_t format_string_loc, flag_chars_t &flag_chars,
+ int &has_operand_number, tree first_fillin_param,
+ object_allocator <format_wanted_type> &fwt_pool_,
+ vec<location_t> *arglocs);
+
+ bool read_any_dollar ();
+
+ bool read_format_flags ();
+
+ bool
+ read_any_format_width (tree &params,
+ unsigned HOST_WIDE_INT &arg_num);
+
+ void
+ read_any_format_left_precision ();
+
+ bool
+ read_any_format_precision (tree &params,
+ unsigned HOST_WIDE_INT &arg_num);
+
+ void handle_alloc_chars ();
+
+ length_modifier read_any_length_modifier ();
+
+ void read_any_other_modifier ();
+
+ const format_char_info *find_format_char_info (char format_char);
+
+ void
+ validate_flag_pairs (const format_char_info *fci,
+ char format_char);
+
+ void
+ give_y2k_warnings (const format_char_info *fci,
+ char format_char);
+
+ void parse_any_scan_set (const format_char_info *fci);
+
+ bool handle_conversions (const format_char_info *fci,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ char format_char);
+
+ bool
+ check_argument_type (const format_char_info *fci,
+ const struct kernel_ext_fmt *kef,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ const bool suppressed,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ const int alloc_flag,
+ const char * const format_start,
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char);
+
+ private:
+ const function_format_info *const info;
+ const format_kind_info * const fki;
+ const format_flag_spec * const flag_specs;
+ const char *start_of_this_format;
+ const char *&format_chars;
+ const tree format_string_cst;
+ const char * const orig_format_chars;
+ const location_t format_string_loc;
+ object_allocator <format_wanted_type> &fwt_pool;
+ flag_chars_t &flag_chars;
+ int main_arg_num;
+ tree main_arg_params;
+ int &has_operand_number;
+ const tree first_fillin_param;
+ format_wanted_type width_wanted_type;
+ format_wanted_type precision_wanted_type;
+ public:
+ format_wanted_type main_wanted_type;
+ private:
+ format_wanted_type *first_wanted_type;
+ format_wanted_type *last_wanted_type;
+ vec<location_t> *arglocs;
+};
+
+/* flag_chars_t's constructor. */
+
+flag_chars_t::flag_chars_t ()
+{
+ m_flag_chars[0] = 0;
+}
+
+/* Has CH been seen as a flag within the current argument? */
+
+bool
+flag_chars_t::has_char_p (char ch) const
+{
+ return strchr (m_flag_chars, ch) != 0;
+}
+
+/* Add CH to the flags seen within the current argument. */
+
+void
+flag_chars_t::add_char (char ch)
+{
+ int i = strlen (m_flag_chars);
+ m_flag_chars[i++] = ch;
+ m_flag_chars[i] = 0;
+}
+
+/* Validate the individual flags used, removing any that are invalid. */
+
+void
+flag_chars_t::validate (const format_kind_info *fki,
+ const format_char_info *fci,
+ const format_flag_spec *flag_specs,
+ const char * const format_chars,
+ tree format_string_cst,
+ location_t format_string_loc,
+ const char * const orig_format_chars,
+ char format_char,
+ bool quoted)
+{
+ int i;
+ int d = 0;
+ bool quotflag = false;
+
+ for (i = 0; m_flag_chars[i] != 0; i++)
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ m_flag_chars[i], NULL);
+ m_flag_chars[i - d] = m_flag_chars[i];
+ if (m_flag_chars[i] == fki->length_code_char)
+ continue;
+
+ /* Remember if a quoting flag is seen. */
+ quotflag |= s->quoting;
+
+ if (strchr (fci->flag_chars, m_flag_chars[i]) == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s used with %<%%%c%> %s format",
+ _(s->name), format_char, fki->name);
+ d++;
+ continue;
+ }
+ if (pedantic)
+ {
+ const format_flag_spec *t;
+ if (ADJ_STD (s->std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support %s",
+ C_STD_NAME (s->std), _(s->long_name));
+ t = get_flag_spec (flag_specs, m_flag_chars[i], fci->flags2);
+ if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std))
+ {
+ const char *long_name = (t->long_name != NULL
+ ? t->long_name
+ : s->long_name);
+ if (ADJ_STD (t->std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support %s with the %<%%%c%> %s format",
+ C_STD_NAME (t->std), _(long_name),
+ format_char, fki->name);
+ }
+ }
+
+ /* Detect quoting directives used within a quoted sequence, such
+ as GCC's "%<...%qE". */
+ if (quoted && s->quoting)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars - 1,
+ OPT_Wformat_,
+ "%s used within a quoted sequence",
+ _(s->name));
+ }
+ }
+ m_flag_chars[i - d] = 0;
+
+ if (!quoted
+ && !quotflag
+ && strchr (fci->flags2, '\''))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc conversion used unquoted",
+ format_char);
+ }
+}
+
+/* Determine if an assignment-allocation has been set, requiring
+ an extra char ** for writing back a dynamically-allocated char *.
+ This is for handling the optional 'm' character in scanf. */
+
+int
+flag_chars_t::get_alloc_flag (const format_kind_info *fki)
+{
+ if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+ && has_char_p ('a'))
+ return 1;
+ if (fki->alloc_char && has_char_p (fki->alloc_char))
+ return 1;
+ return 0;
+}
+
+/* Determine if an assignment-suppression character was seen.
+ ('*' in scanf, for discarding the converted input). */
+
+int
+flag_chars_t::assignment_suppression_p (const format_kind_info *fki)
+{
+ if (fki->suppression_char
+ && has_char_p (fki->suppression_char))
+ return 1;
+ return 0;
+}
+
+/* Constructor for argument_parser. Initialize for parsing one
+ argument within a format string. */
+
+argument_parser::
+argument_parser (function_format_info *info_, const char *&format_chars_,
+ tree format_string_cst_,
+ const char * const orig_format_chars_,
+ location_t format_string_loc_,
+ flag_chars_t &flag_chars_,
+ int &has_operand_number_,
+ tree first_fillin_param_,
+ object_allocator <format_wanted_type> &fwt_pool_,
+ vec<location_t> *arglocs_)
+: info (info_),
+ fki (&format_types[info->format_type]),
+ flag_specs (fki->flag_specs),
+ start_of_this_format (format_chars_),
+ format_chars (format_chars_),
+ format_string_cst (format_string_cst_),
+ orig_format_chars (orig_format_chars_),
+ format_string_loc (format_string_loc_),
+ fwt_pool (fwt_pool_),
+ flag_chars (flag_chars_),
+ main_arg_num (0),
+ main_arg_params (NULL),
+ has_operand_number (has_operand_number_),
+ first_fillin_param (first_fillin_param_),
+ first_wanted_type (NULL),
+ last_wanted_type (NULL),
+ arglocs (arglocs_)
+{
+}
+
+/* Handle dollars at the start of format arguments, setting up main_arg_params
+ and main_arg_num.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::read_any_dollar ()
+{
+ if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
+ {
+ /* Possibly read a $ operand number at the start of the format.
+ If one was previously used, one is required here. If one
+ is not used here, we can't immediately conclude this is a
+ format without them, since it could be printf %m or scanf %*. */
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars, 0,
+ first_fillin_param,
+ &main_arg_params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ main_arg_num = opnum + info->first_arg_num - 1;
+ }
+ }
+ else if (fki->flags & FMT_FLAG_USE_DOLLAR)
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ return true;
+}
+
+/* Read any format flags, but do not yet validate them beyond removing
+ duplicates, since in general validation depends on the rest of
+ the format.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::read_format_flags ()
+{
+ while (*format_chars != 0
+ && strchr (fki->flag_chars, *format_chars) != 0)
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ *format_chars, NULL);
+ if (flag_chars.has_char_p (*format_chars))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
+ }
+ else
+ flag_chars.add_char (*format_chars);
+
+ if (s->skip_next_char)
+ {
+ ++format_chars;
+ if (*format_chars == 0)
+ {
+ warning_at (format_string_loc, OPT_Wformat_,
+ "missing fill character at end of strfmon format");
+ return false;
+ }
+ }
+ ++format_chars;
+ }
+
+ return true;
+}
+
+/* Read any format width, possibly * or *m$.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+read_any_format_width (tree &params,
+ unsigned HOST_WIDE_INT &arg_num)
+{
+ if (!fki->width_char)
+ return true;
+
+ if (fki->width_type != NULL && *format_chars == '*')
+ {
+ flag_chars.add_char (fki->width_char);
+ /* "...a field width...may be indicated by an asterisk.
+ In this case, an int argument supplies the field width..." */
+ ++format_chars;
+ if (has_operand_number != 0)
+ {
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars,
+ has_operand_number == 1,
+ first_fillin_param,
+ &params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ arg_num = opnum + info->first_arg_num - 1;
+ }
+ else
+ has_operand_number = 0;
+ }
+ else
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ if (info->first_arg_num != 0)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ if (has_operand_number <= 0)
+ {
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ }
+ width_wanted_type.wanted_type = *fki->width_type;
+ width_wanted_type.wanted_type_name = NULL;
+ width_wanted_type.pointer_count = 0;
+ width_wanted_type.char_lenient_flag = 0;
+ width_wanted_type.scalar_identity_flag = 0;
+ width_wanted_type.writing_in_flag = 0;
+ width_wanted_type.reading_from_flag = 0;
+ width_wanted_type.kind = CF_KIND_FIELD_WIDTH;
+ width_wanted_type.format_start = format_chars - 1;
+ width_wanted_type.format_length = 1;
+ width_wanted_type.param = cur_param;
+ width_wanted_type.arg_num = arg_num;
+ width_wanted_type.offset_loc =
+ format_chars - orig_format_chars;
+ width_wanted_type.next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = &width_wanted_type;
+ if (first_wanted_type == 0)
+ first_wanted_type = &width_wanted_type;
+ last_wanted_type = &width_wanted_type;
+ }
+ }
+ else
+ {
+ /* Possibly read a numeric width. If the width is zero,
+ we complain if appropriate. */
+ int non_zero_width_char = FALSE;
+ int found_width = FALSE;
+ while (ISDIGIT (*format_chars))
+ {
+ found_width = TRUE;
+ if (*format_chars != '0')
+ non_zero_width_char = TRUE;
+ ++format_chars;
+ }
+ if (found_width && !non_zero_width_char &&
+ (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
+ warning_at (format_string_loc, OPT_Wformat_,
+ "zero width in %s format", fki->name);
+ if (found_width)
+ flag_chars.add_char (fki->width_char);
+ }
+
+ return true;
+}
+
+/* Read any format left precision (must be a number, not *). */
+void
+argument_parser::read_any_format_left_precision ()
+{
+ if (fki->left_precision_char == 0)
+ return;
+ if (*format_chars != '#')
+ return;
+
+ ++format_chars;
+ flag_chars.add_char (fki->left_precision_char);
+ if (!ISDIGIT (*format_chars))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty left precision in %s format", fki->name);
+ while (ISDIGIT (*format_chars))
+ ++format_chars;
+}
+
+/* Read any format precision, possibly * or *m$.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+read_any_format_precision (tree &params,
+ unsigned HOST_WIDE_INT &arg_num)
+{
+ if (fki->precision_char == 0)
+ return true;
+ if (*format_chars != '.')
+ return true;
+
+ ++format_chars;
+ flag_chars.add_char (fki->precision_char);
+ if (fki->precision_type != NULL && *format_chars == '*')
+ {
+ /* "...a...precision...may be indicated by an asterisk.
+ In this case, an int argument supplies the...precision." */
+ ++format_chars;
+ if (has_operand_number != 0)
+ {
+ int opnum;
+ opnum = maybe_read_dollar_number (&format_chars,
+ has_operand_number == 1,
+ first_fillin_param,
+ &params, fki);
+ if (opnum == -1)
+ return false;
+ else if (opnum > 0)
+ {
+ has_operand_number = 1;
+ arg_num = opnum + info->first_arg_num - 1;
+ }
+ else
+ has_operand_number = 0;
+ }
+ else
+ {
+ if (avoid_dollar_number (format_chars))
+ return false;
+ }
+ if (info->first_arg_num != 0)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ if (has_operand_number <= 0)
+ {
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ }
+ precision_wanted_type.wanted_type = *fki->precision_type;
+ precision_wanted_type.wanted_type_name = NULL;
+ precision_wanted_type.pointer_count = 0;
+ precision_wanted_type.char_lenient_flag = 0;
+ precision_wanted_type.scalar_identity_flag = 0;
+ precision_wanted_type.writing_in_flag = 0;
+ precision_wanted_type.reading_from_flag = 0;
+ precision_wanted_type.kind = CF_KIND_FIELD_PRECISION;
+ precision_wanted_type.param = cur_param;
+ precision_wanted_type.format_start = format_chars - 2;
+ precision_wanted_type.format_length = 2;
+ precision_wanted_type.arg_num = arg_num;
+ precision_wanted_type.offset_loc =
+ format_chars - orig_format_chars;
+ precision_wanted_type.next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = &precision_wanted_type;
+ if (first_wanted_type == 0)
+ first_wanted_type = &precision_wanted_type;
+ last_wanted_type = &precision_wanted_type;
+ }
+ }
+ else
+ {
+ if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
+ && !ISDIGIT (*format_chars))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "empty precision in %s format", fki->name);
+ while (ISDIGIT (*format_chars))
+ ++format_chars;
+ }
+
+ return true;
+}
+
+/* Parse any assignment-allocation flags, which request an extra
+ char ** for writing back a dynamically-allocated char *.
+ This is for handling the optional 'm' character in scanf,
+ and, before C99, 'a' (for compatibility with a non-standard
+ GNU libc extension). */
+
+void
+argument_parser::handle_alloc_chars ()
+{
+ if (fki->alloc_char && fki->alloc_char == *format_chars)
+ {
+ flag_chars.add_char (fki->alloc_char);
+ format_chars++;
+ }
+
+ /* Handle the scanf allocation kludge. */
+ if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+ {
+ if (*format_chars == 'a' && !flag_isoc99)
+ {
+ if (format_chars[1] == 's' || format_chars[1] == 'S'
+ || format_chars[1] == '[')
+ {
+ /* 'a' is used as a flag. */
+ flag_chars.add_char ('a');
+ format_chars++;
+ }
+ }
+ }
+}
+
+/* Look for length modifiers within the current format argument,
+ returning a length_modifier instance describing it (or the
+ default if one is not found).
+
+ Issue warnings about non-standard modifiers. */
+
+length_modifier
+argument_parser::read_any_length_modifier ()
+{
+ length_modifier result;
+
+ const format_length_info *fli = fki->length_char_specs;
+ if (!fli)
+ return result;
+
+ while (fli->name != 0
+ && strncmp (fli->name, format_chars, strlen (fli->name)))
+ fli++;
+ if (fli->name != 0)
+ {
+ format_chars += strlen (fli->name);
+ if (fli->double_name != 0 && fli->name[0] == *format_chars)
+ {
+ format_chars++;
+ result = length_modifier (fli->double_name, fli->double_index,
+ fli->double_std, 0);
+ }
+ else
+ {
+ result = length_modifier (fli->name, fli->index, fli->std,
+ fli->scalar_identity_flag);
+ }
+ flag_chars.add_char (fki->length_code_char);
+ }
+ if (pedantic)
+ {
+ /* Warn if the length modifier is non-standard. */
+ if (ADJ_STD (result.std) > C_STD_VER)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s does not support the %qs %s length modifier",
+ C_STD_NAME (result.std), result.chars,
+ fki->name);
+ }
+
+ return result;
+}
+
+/* Read any other modifier (strftime E/O). */
+
+void
+argument_parser::read_any_other_modifier ()
+{
+ if (fki->modifier_chars == NULL)
+ return;
+
+ while (*format_chars != 0
+ && strchr (fki->modifier_chars, *format_chars) != 0)
+ {
+ if (flag_chars.has_char_p (*format_chars))
+ {
+ const format_flag_spec *s = get_flag_spec (flag_specs,
+ *format_chars, NULL);
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "repeated %s in format", _(s->name));
+ }
+ else
+ flag_chars.add_char (*format_chars);
+ ++format_chars;
+ }
+}
+
+/* Return the format_char_info corresponding to FORMAT_CHAR,
+ potentially issuing a warning if the format char is
+ not supported in the C standard version we are checking
+ against.
+
+ Issue a warning and return NULL if it is not found.
+
+ Issue warnings about non-standard modifiers. */
+
+const format_char_info *
+argument_parser::find_format_char_info (char format_char)
+{
+ const format_char_info *fci = fki->conversion_specs;
+
+ while (fci->format_chars != 0
+ && strchr (fci->format_chars, format_char) == 0)
+ ++fci;
+ if (fci->format_chars == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unknown conversion type character %qc in format",
+ format_char);
+ return NULL;
+ }
+
+ if (pedantic)
+ {
+ if (ADJ_STD (fci->std) > C_STD_VER)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%c%> %s format",
+ C_STD_NAME (fci->std), format_char, fki->name);
+ }
+
+ return fci;
+}
+
+/* Validate the pairs of flags used.
+ Issue warnings about incompatible combinations of flags. */
+
+void
+argument_parser::validate_flag_pairs (const format_char_info *fci,
+ char format_char)
+{
+ const format_flag_pair * const bad_flag_pairs = fki->bad_flag_pairs;
+
+ for (int i = 0; bad_flag_pairs[i].flag_char1 != 0; i++)
+ {
+ const format_flag_spec *s, *t;
+ if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char1))
+ continue;
+ if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char2))
+ continue;
+ if (bad_flag_pairs[i].predicate != 0
+ && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0)
+ continue;
+ s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL);
+ t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL);
+ if (bad_flag_pairs[i].ignored)
+ {
+ if (bad_flag_pairs[i].predicate != 0)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s ignored with %s and %<%%%c%> %s format",
+ _(s->name), _(t->name), format_char,
+ fki->name);
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "%s ignored with %s in %s format",
+ _(s->name), _(t->name), fki->name);
+ }
+ else
+ {
+ if (bad_flag_pairs[i].predicate != 0)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "use of %s and %s together with %<%%%c%> %s format",
+ _(s->name), _(t->name), format_char,
+ fki->name);
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "use of %s and %s together in %s format",
+ _(s->name), _(t->name), fki->name);
+ }
+ }
+}
+
+/* Give Y2K warnings. */
+
+void
+argument_parser::give_y2k_warnings (const format_char_info *fci,
+ char format_char)
+{
+ if (!warn_format_y2k)
+ return;
+
+ int y2k_level = 0;
+ if (strchr (fci->flags2, '4') != 0)
+ if (flag_chars.has_char_p ('E'))
+ y2k_level = 3;
+ else
+ y2k_level = 2;
+ else if (strchr (fci->flags2, '3') != 0)
+ y2k_level = 3;
+ else if (strchr (fci->flags2, '2') != 0)
+ y2k_level = 2;
+ if (y2k_level == 3)
+ warning_at (format_string_loc, OPT_Wformat_y2k,
+ "%<%%%c%> yields only last 2 digits of year in some locales", format_char);
+ else if (y2k_level == 2)
+ warning_at (format_string_loc, OPT_Wformat_y2k,
+ "%<%%%c%> yields only last 2 digits of year",
+ format_char);
+}
+
+/* Parse any "scan sets" enclosed in square brackets, e.g.
+ for scanf-style calls. */
+
+void
+argument_parser::parse_any_scan_set (const format_char_info *fci)
+{
+ if (strchr (fci->flags2, '[') == NULL)
+ return;
+
+ /* Skip over scan set, in case it happens to have '%' in it. */
+ if (*format_chars == '^')
+ ++format_chars;
+ /* Find closing bracket; if one is hit immediately, then
+ it's part of the scan set rather than a terminator. */
+ if (*format_chars == ']')
+ ++format_chars;
+ while (*format_chars && *format_chars != ']')
+ ++format_chars;
+ if (*format_chars != ']')
+ /* The end of the format string was reached. */
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "no closing %<]%> for %<%%[%> format");
+}
+
+/* Return true if this argument is to be continued to be parsed,
+ false to skip to next argument. */
+
+bool
+argument_parser::handle_conversions (const format_char_info *fci,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ char format_char)
+{
+ enum format_std_version wanted_type_std;
+
+ if (!(fki->flags & (int) FMT_FLAG_ARG_CONVERT))
+ return true;
+
+ wanted_type = (fci->types[len_modifier.val].type
+ ? *fci->types[len_modifier.val].type : 0);
+ wanted_type_name = fci->types[len_modifier.val].name;
+ wanted_type_std = fci->types[len_modifier.val].std;
+ if (wanted_type == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "use of %qs length modifier with %qc type character has either no effect or undefined behavior",
+ len_modifier.chars, format_char);
+ /* Heuristic: skip one argument when an invalid length/type
+ combination is encountered. */
+ arg_num++;
+ if (params != 0)
+ params = TREE_CHAIN (params);
+ return false;
+ }
+ else if (pedantic
+ /* Warn if non-standard, provided it is more non-standard
+ than the length and type characters that may already
+ have been warned for. */
+ && ADJ_STD (wanted_type_std) > ADJ_STD (len_modifier.std)
+ && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
+ {
+ if (ADJ_STD (wanted_type_std) > C_STD_VER)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%s does not support the %<%%%s%c%> %s format",
+ C_STD_NAME (wanted_type_std),
+ len_modifier.chars,
+ format_char, fki->name);
+ }
+
+ return true;
+}
+
+/* Check type of argument against desired type.
+
+ Return true if format parsing is to continue, false otherwise. */
+
+bool
+argument_parser::
+check_argument_type (const format_char_info *fci,
+ const struct kernel_ext_fmt *kef,
+ const length_modifier &len_modifier,
+ tree &wanted_type,
+ const char *&wanted_type_name,
+ const bool suppressed,
+ unsigned HOST_WIDE_INT &arg_num,
+ tree &params,
+ const int alloc_flag,
+ const char * const format_start,
+ const char * const type_start,
+ location_t fmt_param_loc,
+ char conversion_char)
+{
+ if (info->first_arg_num == 0)
+ return true;
+
+ if ((fci->pointer_count == 0 && wanted_type == void_type_node)
+ || suppressed)
+ {
+ if (main_arg_num != 0)
+ {
+ if (suppressed)
+ warning_at (format_string_loc, OPT_Wformat_,
+ "operand number specified with suppressed assignment");
+ else
+ warning_at (format_string_loc, OPT_Wformat_,
+ "operand number specified for format taking no argument");
+ }
+ }
+ else
+ {
+ format_wanted_type *wanted_type_ptr;
+
+ if (main_arg_num != 0)
+ {
+ arg_num = main_arg_num;
+ params = main_arg_params;
+ }
+ else
+ {
+ ++arg_num;
+ if (has_operand_number > 0)
+ {
+ warning_at (format_string_loc, OPT_Wformat_,
+ "missing $ operand number in format");
+ return false;
+ }
+ else
+ has_operand_number = 0;
+ }
+
+ wanted_type_ptr = &main_wanted_type;
+ while (fci)
+ {
+ tree cur_param;
+ if (params == 0)
+ cur_param = NULL;
+ else
+ {
+ cur_param = TREE_VALUE (params);
+ params = TREE_CHAIN (params);
+ }
+
+ wanted_type_ptr->wanted_type = wanted_type;
+ wanted_type_ptr->wanted_type_name = wanted_type_name;
+ wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+ wanted_type_ptr->char_lenient_flag = 0;
+ if (strchr (fci->flags2, 'c') != 0)
+ wanted_type_ptr->char_lenient_flag = 1;
+ wanted_type_ptr->scalar_identity_flag = 0;
+ if (len_modifier.scalar_identity_flag)
+ wanted_type_ptr->scalar_identity_flag = 1;
+ wanted_type_ptr->writing_in_flag = 0;
+ wanted_type_ptr->reading_from_flag = 0;
+ if (alloc_flag)
+ wanted_type_ptr->writing_in_flag = 1;
+ else
+ {
+ if (strchr (fci->flags2, 'W') != 0)
+ wanted_type_ptr->writing_in_flag = 1;
+ if (strchr (fci->flags2, 'R') != 0)
+ wanted_type_ptr->reading_from_flag = 1;
+ }
+ wanted_type_ptr->kind = CF_KIND_FORMAT;
+ wanted_type_ptr->param = cur_param;
+ wanted_type_ptr->arg_num = arg_num;
+ wanted_type_ptr->format_start = format_start;
+ wanted_type_ptr->format_length = format_chars - format_start;
+ wanted_type_ptr->offset_loc = format_chars - orig_format_chars;
+ wanted_type_ptr->next = NULL;
+ if (last_wanted_type != 0)
+ last_wanted_type->next = wanted_type_ptr;
+ if (first_wanted_type == 0)
+ first_wanted_type = wanted_type_ptr;
+ last_wanted_type = wanted_type_ptr;
+
+ fci = fci->chain;
+ if (fci)
+ {
+ wanted_type_ptr = fwt_pool.allocate ();
+ arg_num++;
+ wanted_type = *fci->types[len_modifier.val].type;
+ wanted_type_name = fci->types[len_modifier.val].name;
+ }
+ }
+ }
+
+ if (first_wanted_type != 0)
+ {
+ ptrdiff_t offset_to_format_start = (start_of_this_format - 1) - orig_format_chars;
+ ptrdiff_t offset_to_format_end = (format_chars - 1) - orig_format_chars;
+ /* By default, use the end of the range for the caret location. */
+ substring_loc fmt_loc (fmt_param_loc, TREE_TYPE (format_string_cst),
+ offset_to_format_end,
+ offset_to_format_start, offset_to_format_end);
+ ptrdiff_t offset_to_type_start = type_start - orig_format_chars;
+ check_format_types (fmt_loc, first_wanted_type, fki,
+ offset_to_type_start,
+ conversion_char, arglocs);
+
+ /* note printf extension type checks are *additional* - %p must always
+ * be pointer compatible, %d always int compatible.
+ */
+ if (first_wanted_type->kind != CF_KIND_FORMAT || !kef)
+ return true;
+
+ const struct kernel_ext_fmt *kef_now;
+ bool success;
+
+ for (kef_now = kef; kef_now->suffix && !strcmp (kef->suffix, kef_now->suffix); kef_now++)
+ {
+ success = check_kef_type (fmt_loc, kef_now,
+ first_wanted_type->arg_num,
+ first_wanted_type->param,
+ kef_now->type, fki, offset_to_type_start, conversion_char, arglocs);
+
+ if (success)
+ return true;
+ }
+
+ location_t param_loc;
+
+ if (EXPR_HAS_LOCATION (first_wanted_type->param))
+ param_loc = EXPR_LOCATION (first_wanted_type->param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (first_wanted_type->arg_num > 0);
+ param_loc = (*arglocs)[first_wanted_type->arg_num - 1];
+ }
+
+ format_type_warning (fmt_loc, param_loc, first_wanted_type,
+ kef->type, TREE_TYPE (first_wanted_type->param),
+ fki, offset_to_type_start, conversion_char);
+ }
+
+ return true;
+}
+
+/* Do the main part of checking a call to a format function. FORMAT_CHARS
+ is the NUL-terminated format string (which at this point may contain
+ internal NUL characters); FORMAT_LENGTH is its length (excluding the
+ terminating NUL character). ARG_NUM is one less than the number of
+ the first format argument to check; PARAMS points to that format
+ argument in the list of arguments. */
+
+static void
+check_format_info_main (format_check_results *res,
+ function_format_info *info, const char *format_chars,
+ location_t fmt_param_loc, tree format_string_cst,
+ int format_length, tree params,
+ unsigned HOST_WIDE_INT arg_num,
+ object_allocator <format_wanted_type> &fwt_pool,
+ vec<location_t> *arglocs)
+{
+ const char * const orig_format_chars = format_chars;
+ const tree first_fillin_param = params;
+
+ const format_kind_info * const fki = &format_types[info->format_type];
+ const format_flag_spec * const flag_specs = fki->flag_specs;
+ const location_t format_string_loc = res->format_string_loc;
+
+ /* -1 if no conversions taking an operand have been found; 0 if one has
+ and it didn't use $; 1 if $ formats are in use. */
+ int has_operand_number = -1;
+
+ /* Vector of pointers to opening quoting directives (like GCC "%<"). */
+ auto_vec<const char*> quotdirs;
+
+ /* Pointers to the most recent color directives (like GCC's "%r or %R").
+ A starting color directive much be terminated before the end of
+ the format string. A terminating directive makes no sense without
+ a prior starting directive. */
+ const char *color_begin = NULL;
+ const char *color_end = NULL;
+
+ init_dollar_format_checking (info->first_arg_num, first_fillin_param);
+
+ while (*format_chars != 0)
+ {
+ if (*format_chars++ != '%')
+ continue;
+ if (*format_chars == 0)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "spurious trailing %<%%%> in format");
+ continue;
+ }
+ if (*format_chars == '%')
+ {
+ ++format_chars;
+ continue;
+ }
+
+ flag_chars_t flag_chars;
+ argument_parser arg_parser (info, format_chars, format_string_cst,
+ orig_format_chars, format_string_loc,
+ flag_chars, has_operand_number,
+ first_fillin_param, fwt_pool, arglocs);
+
+ if (!arg_parser.read_any_dollar ())
+ return;
+
+ if (!arg_parser.read_format_flags ())
+ return;
+
+ /* Read any format width, possibly * or *m$. */
+ if (!arg_parser.read_any_format_width (params, arg_num))
+ return;
+
+ /* Read any format left precision (must be a number, not *). */
+ arg_parser.read_any_format_left_precision ();
+
+ /* Read any format precision, possibly * or *m$. */
+ if (!arg_parser.read_any_format_precision (params, arg_num))
+ return;
+
+ const char *format_start = format_chars;
+
+ arg_parser.handle_alloc_chars ();
+
+ /* The rest of the conversion specification is the length modifier
+ (if any), and the conversion specifier, so this is where the
+ type information starts. If we need to issue a suggestion
+ about a type mismatch, then we should preserve everything up
+ to here. */
+ const char *type_start = format_chars;
+
+ /* Read any length modifier, if this kind of format has them. */
+ const length_modifier len_modifier
+ = arg_parser.read_any_length_modifier ();
+
+ /* Read any modifier (strftime E/O). */
+ arg_parser.read_any_other_modifier ();
+
+ char format_char = *format_chars;
+ if (format_char == 0
+ || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
+ && format_char == '%'))
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "conversion lacks type at end of format");
+ continue;
+ }
+ format_chars++;
+
+ const format_char_info * const fci
+ = arg_parser.find_format_char_info (format_char);
+ if (!fci)
+ continue;
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+
+ if (etab && format_chars[0] >= 'A' && format_chars[0] <= 'Z')
+ {
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ for (; etab < etab_end && etab->suffix; etab++)
+ {
+ if (!strncmp (etab->suffix, format_chars, strlen (etab->suffix)))
+ break;
+ }
+
+ if (!etab->suffix || etab == etab_end)
+ {
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars + 1,
+ OPT_Wformat_,
+ "unrecognized printf extension suffix");
+ etab = NULL;
+ }
+ else
+ {
+ format_chars += strlen (etab->suffix);
+ }
+ }
+ else
+ etab = NULL;
+
+ flag_chars.validate (fki, fci, flag_specs, format_chars,
+ format_string_cst,
+ format_string_loc, orig_format_chars, format_char,
+ quotdirs.length () > 0);
+
+ const int alloc_flag = flag_chars.get_alloc_flag (fki);
+ const bool suppressed = flag_chars.assignment_suppression_p (fki);
+
+ /* Diagnose nested or unmatched quoting directives such as GCC's
+ "%<...%<" and "%>...%>". */
+ bool quot_begin_p = strchr (fci->flags2, '<');
+ bool quot_end_p = strchr (fci->flags2, '>');
+
+ if (quot_begin_p && !quot_end_p)
+ {
+ if (quotdirs.length ())
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "nested quoting directive");
+ quotdirs.safe_push (format_chars);
+ }
+ else if (!quot_begin_p && quot_end_p)
+ {
+ if (quotdirs.length ())
+ quotdirs.pop ();
+ else
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unmatched quoting directive");
+ }
+
+ bool color_begin_p = strchr (fci->flags2, '/');
+ if (color_begin_p)
+ {
+ color_begin = format_chars;
+ color_end = NULL;
+ }
+ else if (strchr (fci->flags2, '\\'))
+ {
+ if (color_end)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc directive redundant after prior occurence of the same", format_char);
+ else if (!color_begin)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "unmatched color reset directive");
+ color_end = format_chars;
+ }
+
+ /* Diagnose directives that shouldn't appear in a quoted sequence.
+ (They are denoted by a double quote in FLAGS2.) */
+ if (quotdirs.length ())
+ {
+ if (strchr (fci->flags2, '"'))
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars - orig_format_chars,
+ OPT_Wformat_,
+ "%qc conversion used within a quoted sequence",
+ format_char);
+ }
+
+ /* Validate the pairs of flags used. */
+ arg_parser.validate_flag_pairs (fci, format_char);
+
+ arg_parser.give_y2k_warnings (fci, format_char);
+
+ arg_parser.parse_any_scan_set (fci);
+
+ tree wanted_type = NULL;
+ const char *wanted_type_name = NULL;
+
+ if (!arg_parser.handle_conversions (fci, len_modifier,
+ wanted_type, wanted_type_name,
+ arg_num,
+ params,
+ format_char))
+ continue;
+
+ arg_parser.main_wanted_type.next = NULL;
+
+ /* Finally. . .check type of argument against desired type! */
+ if (!arg_parser.check_argument_type (fci, etab, len_modifier,
+ wanted_type, wanted_type_name,
+ suppressed,
+ arg_num, params,
+ alloc_flag,
+ format_start, type_start,
+ fmt_param_loc,
+ format_char))
+ return;
+ }
+
+ if (format_chars - orig_format_chars != format_length)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ format_chars + 1 - orig_format_chars,
+ OPT_Wformat_contains_nul,
+ "embedded %<\\0%> in format");
+ if (info->first_arg_num != 0 && params != 0
+ && has_operand_number <= 0)
+ {
+ res->number_other--;
+ res->number_extra_args++;
+ }
+ if (has_operand_number > 0)
+ finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+ if (quotdirs.length ())
+ format_warning_at_char (format_string_loc, format_string_cst,
+ quotdirs.pop () - orig_format_chars,
+ OPT_Wformat_, "unterminated quoting directive");
+ if (color_begin && !color_end)
+ format_warning_at_char (format_string_loc, format_string_cst,
+ color_begin - orig_format_chars,
+ OPT_Wformat_, "unterminated color directive");
+}
+
+/* Check the argument types from a single format conversion (possibly
+ including width and precision arguments).
+
+ FMT_LOC is the location of the format conversion.
+
+ TYPES is a singly-linked list expressing the parts of the format
+ conversion that expect argument types, and the arguments they
+ correspond to.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+
+ then FMT_LOC covers this range:
+
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^^^^^^^
+
+ and TYPES in this case is a three-entry singly-linked list consisting of:
+ (1) the check for the field width here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^^^^
+ against arg3, and
+ (2) the check for the field precision here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^ ^^^^
+ against arg4, and
+ (3) the check for the length modifier and conversion char here:
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^^^ ^^^^
+ against arg5.
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST:
+
+ 0000000000111111111122
+ 0123456789012345678901
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here. */
+tree type_normalize (tree type, tree *cousin, tree target = NULL)
+{
+ while (1)
+ {
+ if (TREE_CODE (type) == FUNCTION_TYPE || TREE_CODE (type) == POINTER_TYPE)
+ return type;
+ if (target)
+ /* Strip off any "const" etc. */
+ type = build_qualified_type (type, 0);
+ if (TREE_CODE (TYPE_NAME (type)) != TYPE_DECL)
+ return type;
+
+ if (target && (type == target || TYPE_NAME (type) == target))
+ return target;
+
+ struct type_special *t;
+ for (t = special_types; t->match; t++)
+ {
+ if (!*t->match)
+ continue;
+ if (TYPE_NAME (type) != *t->match)
+ continue;
+ if (t->cousin && *t->cousin)
+ *cousin = *t->cousin;
+ if (t->replace)
+ return *t->replace ? *t->replace : type;
+ return type;
+ }
+
+ tree orig = DECL_ORIGINAL_TYPE (TYPE_NAME (type));
+ if (!orig)
+ return type;
+
+ type = orig;
+ }
+ return type;
+}
+
+/* gcc-10 asserts when you give a TYPE_DECL instead of the actual TYPE */
+static tree
+decl_deref(tree typ)
+{
+ while (TREE_CODE (typ) == TYPE_DECL)
+ typ = DECL_ORIGINAL_TYPE (typ);
+
+ return typ;
+}
+
+static void
+check_format_types (const substring_loc &fmt_loc,
+ format_wanted_type *types, const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs)
+{
+ for (; types != 0; types = types->next)
+ {
+ tree cur_param;
+ tree cur_type;
+ tree cur_type_cousin = NULL;
+ tree orig_cur_type;
+ tree wanted_type;
+ int arg_num;
+ int i;
+ int char_type_flag;
+
+ wanted_type = types->wanted_type;
+ arg_num = types->arg_num;
+
+ wanted_type = decl_deref(wanted_type);
+
+ /* The following should not occur here. */
+ gcc_assert (wanted_type);
+ gcc_assert (wanted_type != void_type_node || types->pointer_count);
+
+ if (types->pointer_count == 0)
+ wanted_type = lang_hooks.types.type_promotes_to (wanted_type);
+
+ switch (TREE_CODE (wanted_type))
+ {
+ case IDENTIFIER_NODE:
+ break;
+ case TYPE_DECL:
+ wanted_type = TYPE_MAIN_VARIANT (DECL_ORIGINAL_TYPE (wanted_type));
+ break;
+ default:
+ wanted_type = TYPE_MAIN_VARIANT (wanted_type);
+ break;
+ }
+
+ cur_param = types->param;
+ if (!cur_param)
+ {
+ format_type_warning (fmt_loc, UNKNOWN_LOCATION, types, wanted_type,
+ NULL, fki, offset_to_type_start,
+ conversion_char);
+ continue;
+ }
+
+ cur_type = TREE_TYPE (cur_param);
+ if (cur_type == error_mark_node)
+ continue;
+ orig_cur_type = cur_type;
+ char_type_flag = 0;
+
+ location_t param_loc = UNKNOWN_LOCATION;
+ if (EXPR_HAS_LOCATION (cur_param))
+ param_loc = EXPR_LOCATION (cur_param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (types->arg_num > 0);
+ param_loc = (*arglocs)[types->arg_num - 1];
+ }
+
+ STRIP_NOPS (cur_param);
+
+ /* Check the types of any additional pointer arguments
+ that precede the "real" argument. */
+ for (i = 0; i < types->pointer_count; ++i)
+ {
+ if (TREE_CODE (cur_type) == POINTER_TYPE)
+ {
+ cur_type = TREE_TYPE (cur_type);
+ if (cur_type == error_mark_node)
+ break;
+
+ /* Check for writing through a NULL pointer. */
+ if (types->writing_in_flag
+ && i == 0
+ && cur_param != 0
+ && integer_zerop (cur_param))
+ warning (OPT_Wformat_, "writing through null pointer (argument %d)", arg_num);
+
+ /* Check for reading through a NULL pointer. */
+ if (types->reading_from_flag
+ && i == 0
+ && cur_param != 0
+ && integer_zerop (cur_param))
+ warning (OPT_Wformat_, "reading through null pointer (argument %d)", arg_num);
+
+ if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR)
+ cur_param = TREE_OPERAND (cur_param, 0);
+ else
+ cur_param = 0;
+
+ /* See if this is an attempt to write into a const type with
+ scanf or with printf "%n". Note: the writing in happens
+ at the first indirection only, if for example
+ void * const * is passed to scanf %p; passing
+ const void ** is simply passing an incompatible type. */
+ if (types->writing_in_flag
+ && i == 0
+ && (TYPE_READONLY (cur_type)
+ || (cur_param != 0
+ && (CONSTANT_CLASS_P (cur_param)
+ || (DECL_P (cur_param)
+ && TREE_READONLY (cur_param))))))
+ warning (OPT_Wformat_, "writing into constant object (argument %d)", arg_num);
+
+ /* If there are extra type qualifiers beyond the first
+ indirection, then this makes the types technically
+ incompatible. */
+ if (i > 0
+ && pedantic
+ && (TYPE_READONLY (cur_type)
+ || TYPE_VOLATILE (cur_type)
+ || TYPE_ATOMIC (cur_type)
+ || TYPE_RESTRICT (cur_type)))
+ warning (OPT_Wformat_, "extra type qualifiers in format argument (argument %d)",
+ arg_num);
+
+ }
+ else
+ {
+ format_type_warning (fmt_loc, param_loc,
+ types, wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
+ break;
+ }
+ }
+
+ if (i < types->pointer_count)
+ continue;
+
+ cur_type = type_normalize (cur_type, &cur_type_cousin);
+
+ /* Check whether the argument type is a character type. This leniency
+ only applies to certain formats, flagged with 'c'. */
+ if (types->char_lenient_flag)
+ char_type_flag = (cur_type == char_type_node
+ || cur_type == signed_char_type_node
+ || cur_type == unsigned_char_type_node);
+
+ int compat = lang_hooks.types_compatible_p (decl_deref (wanted_type), decl_deref (cur_type));
+ /* Check the type of the "real" argument, if there's a type we want. */
+ if ((TREE_CODE (wanted_type) != INTEGER_TYPE || types->pointer_count)
+ && compat)
+ continue;
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE && !types->pointer_count
+ && compat)
+ {
+compat_inner:
+ if (TREE_CODE (cur_param) == INTEGER_CST)
+ continue;
+
+ if (TREE_CODE (types->wanted_type) == TYPE_DECL
+ && TREE_CODE (cur_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == cur_type)
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [A])");
+ continue;
+ }
+ else if (TREE_CODE (types->wanted_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == TYPE_NAME(cur_type))
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [B])");
+ continue;
+ }
+ else if (wanted_type == cur_type)
+ continue;
+ else if (cur_type_cousin)
+ {
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [C])");
+ }
+
+ /*
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (ultra-pedantic mode)");
+ */
+ continue;
+ }
+
+ /* If we want 'void *', allow any pointer type.
+ (Anything else would already have got a warning.)
+ With -Wpedantic, only allow pointers to void and to character
+ types. */
+ if (wanted_type == void_type_node
+ && (!pedantic || (i == 1 && char_type_flag)))
+ continue;
+ /* Don't warn about differences merely in signedness, unless
+ -Wpedantic. With -Wpedantic, warn if the type is a pointer
+ target and not a character type, and for character types at
+ a second level of indirection. */
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE
+ && TREE_CODE (cur_type) == INTEGER_TYPE
+ && ((!pedantic && !warn_format_signedness)
+ || (i == 0 && !warn_format_signedness)
+ || (i == 1 && char_type_flag))
+ && (TYPE_UNSIGNED (wanted_type)
+ ? wanted_type == c_common_unsigned_type (cur_type)
+ : wanted_type == c_common_signed_type (cur_type)))
+ {
+ if (cur_type_cousin)
+ {
+ if (TREE_CODE (types->wanted_type) == TYPE_DECL
+ && TREE_CODE (cur_type_cousin) == TYPE_DECL)
+ {
+ if (types->wanted_type == cur_type_cousin)
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [X])");
+ continue;
+ }
+ else if (TREE_CODE (types->wanted_type) == TYPE_DECL)
+ {
+ if (types->wanted_type == TYPE_NAME(cur_type_cousin))
+ continue;
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [Y])");
+ continue;
+ }
+ else if (wanted_type == cur_type_cousin)
+ continue;
+ else
+ {
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char,
+ " (strict match required [Z])");
+ }
+ }
+
+ goto compat_inner;
+ }
+ /* Don't warn about differences merely in signedness if we know
+ that the current type is integer-promoted and its original type
+ was unsigned such as that it is in the range of WANTED_TYPE. */
+ if (TREE_CODE (wanted_type) == INTEGER_TYPE
+ && TREE_CODE (cur_type) == INTEGER_TYPE
+ && warn_format_signedness
+ && TYPE_UNSIGNED (wanted_type)
+ && cur_param != NULL_TREE
+ && TREE_CODE (cur_param) == NOP_EXPR)
+ {
+ tree t = TREE_TYPE (TREE_OPERAND (cur_param, 0));
+ if (TYPE_UNSIGNED (t)
+ && cur_type == lang_hooks.types.type_promotes_to (t))
+ continue;
+ }
+ /* Likewise, "signed char", "unsigned char" and "char" are
+ equivalent but the above test won't consider them equivalent. */
+ if (wanted_type == char_type_node
+ && (!pedantic || i < 2)
+ && char_type_flag)
+ continue;
+ if (types->scalar_identity_flag
+ && (TREE_CODE (cur_type) == TREE_CODE (wanted_type)
+ || (INTEGRAL_TYPE_P (cur_type)
+ && INTEGRAL_TYPE_P (wanted_type)))
+ && TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type))
+ continue;
+ /* Now we have a type mismatch. */
+ format_type_warning (fmt_loc, param_loc, types,
+ wanted_type, orig_cur_type, fki,
+ offset_to_type_start, conversion_char);
+ }
+}
+
+static bool
+check_kef_type (const substring_loc &fmt_loc,
+ const struct kernel_ext_fmt *kef,
+ unsigned arg_num,
+ tree cur_param,
+ tree wanted_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ vec<location_t> *arglocs)
+{
+ tree cur_type;
+ bool ok = true;
+ int i;
+
+ /* The following should not occur here. */
+ gcc_assert (wanted_type);
+ gcc_assert (wanted_type != void_type_node || kef->ptrlevel);
+
+ if (TREE_CODE (wanted_type) == TYPE_DECL)
+ wanted_type = DECL_ORIGINAL_TYPE (wanted_type);
+
+ if (!cur_param)
+ return false;
+
+ cur_type = TREE_TYPE (cur_param);
+ if (cur_type == error_mark_node)
+ return false;
+
+ location_t param_loc = UNKNOWN_LOCATION;
+ if (EXPR_HAS_LOCATION (cur_param))
+ param_loc = EXPR_LOCATION (cur_param);
+ else if (arglocs)
+ {
+ /* arg_num is 1-based. */
+ gcc_assert (arg_num > 0);
+ param_loc = (*arglocs)[arg_num - 1];
+ }
+ (void)param_loc;
+
+ STRIP_NOPS (cur_param);
+
+ /* Check the types of any additional pointer arguments
+ that precede the "real" argument. */
+ for (i = 0; i < kef->ptrlevel; ++i)
+ {
+ if (TREE_CODE (cur_type) == POINTER_TYPE)
+ {
+ cur_type = TREE_TYPE (cur_type);
+ if (cur_type == error_mark_node)
+ break;
+
+ if (cur_param != 0 && TREE_CODE (cur_param) == ADDR_EXPR)
+ cur_param = TREE_OPERAND (cur_param, 0);
+ else
+ cur_param = 0;
+
+ /* If there are extra type qualifiers beyond the first
+ indirection, then this makes the types technically
+ incompatible. */
+ if (i > 0
+ && pedantic
+ && (TYPE_READONLY (cur_type)
+ || TYPE_VOLATILE (cur_type)
+ || TYPE_ATOMIC (cur_type)
+ || TYPE_RESTRICT (cur_type)))
+ warning (OPT_Wformat_, "extra type qualifiers in format argument (argument %d)",
+ arg_num);
+
+ }
+ else
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ if (i < kef->ptrlevel)
+ return ok;
+
+ int compat = lang_hooks.types_compatible_p (wanted_type, cur_type);
+
+ if (!compat)
+ return false;
+
+ tree cousin;
+ tree normal_type;
+
+ normal_type = type_normalize (cur_type, &cousin, wanted_type);
+
+ return normal_type == wanted_type;
+}
+
+
+/* Given type TYPE, attempt to dereference the type N times
+ (e.g. from ("int ***", 2) to "int *")
+
+ Return the derefenced type, with any qualifiers
+ such as "const" stripped from the result, or
+ NULL if unsuccessful (e.g. TYPE is not a pointer type). */
+
+static tree
+deref_n_times (tree type, int n)
+{
+ gcc_assert (type);
+
+ for (int i = n; i > 0; i--)
+ {
+ if (TREE_CODE (type) != POINTER_TYPE)
+ return NULL_TREE;
+ type = TREE_TYPE (type);
+ }
+ /* Strip off any "const" etc. */
+ return build_qualified_type (type, 0);
+}
+
+/* Lookup the format code for FORMAT_LEN within FLI,
+ returning the string code for expressing it, or NULL
+ if it is not found. */
+
+static const char *
+get_modifier_for_format_len (const format_length_info *fli,
+ enum format_lengths format_len)
+{
+ for (; fli->name; fli++)
+ {
+ if (fli->index == format_len)
+ return fli->name;
+ if (fli->double_index == format_len)
+ return fli->double_name;
+ }
+ return NULL;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_get_modifier_for_format_len ()
+{
+ ASSERT_STREQ ("h",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_h));
+ ASSERT_STREQ ("hh",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_hh));
+ ASSERT_STREQ ("L",
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_L));
+ ASSERT_EQ (NULL,
+ get_modifier_for_format_len (printf_length_specs, FMT_LEN_none));
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+/* Determine if SPEC_TYPE and ARG_TYPE are sufficiently similar for a
+ format_type_detail using SPEC_TYPE to be offered as a suggestion for
+ Wformat type errors where the argument has type ARG_TYPE. */
+
+static bool
+matching_type_p (tree spec_type, tree arg_type)
+{
+ gcc_assert (spec_type);
+ gcc_assert (arg_type);
+
+ spec_type = decl_deref (spec_type);
+ arg_type = decl_deref (arg_type);
+
+ /* If any of the types requires structural equality, we can't compare
+ their canonical types. */
+ if (TYPE_STRUCTURAL_EQUALITY_P (spec_type)
+ || TYPE_STRUCTURAL_EQUALITY_P (arg_type))
+ return false;
+
+ spec_type = TYPE_CANONICAL (spec_type);
+ arg_type = TYPE_CANONICAL (arg_type);
+
+ if (TREE_CODE (spec_type) == INTEGER_TYPE
+ && TREE_CODE (arg_type) == INTEGER_TYPE
+ && (TYPE_UNSIGNED (spec_type)
+ ? spec_type == c_common_unsigned_type (arg_type)
+ : spec_type == c_common_signed_type (arg_type)))
+ return true;
+
+ return spec_type == arg_type;
+}
+
+/* Subroutine of get_format_for_type.
+
+ Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If CONVERSION_CHAR is not zero (the first pass), the resulting suggestion
+ is required to use it, for correcting bogus length modifiers.
+ If CONVERSION_CHAR is zero (the second pass), then allow any suggestion
+ that matches ARG_TYPE.
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_format_for_type_1 (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
+{
+ gcc_assert (arg_type);
+
+ const format_char_info *spec;
+ for (spec = &fki->conversion_specs[0];
+ spec->format_chars;
+ spec++)
+ {
+ if (conversion_char)
+ if (!strchr (spec->format_chars, conversion_char))
+ continue;
+
+ tree effective_arg_type = deref_n_times (arg_type,
+ spec->pointer_count);
+ if (!effective_arg_type)
+ continue;
+ for (int i = 0; i < FMT_LEN_MAX; i++)
+ {
+ const format_type_detail *ftd = &spec->types[i];
+ if (!ftd->type)
+ continue;
+ if (matching_type_p (*ftd->type, effective_arg_type))
+ {
+ const char *len_modifier
+ = get_modifier_for_format_len (fki->length_char_specs,
+ (enum format_lengths)i);
+ if (!len_modifier)
+ len_modifier = "";
+
+ if (conversion_char)
+ /* We found a match, using the given conversion char - the
+ length modifier was incorrect (or absent).
+ Provide a suggestion using the conversion char with the
+ correct length modifier for the type. */
+ return xasprintf ("%s%c", len_modifier, conversion_char);
+ else
+ /* 2nd pass: no match was possible using the user-provided
+ conversion char, but we do have a match without using it.
+ Provide a suggestion using the first conversion char
+ listed for the given type. */
+ return xasprintf ("%s%c", len_modifier, spec->format_chars[0]);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/* Generate a string containing the length modifier and conversion specifier
+ that should be used to format arguments of type ARG_TYPE within FKI
+ (effectively the inverse of the checking code).
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_format_for_type (const format_kind_info *fki, tree arg_type,
+ char conversion_char)
+{
+ gcc_assert (arg_type);
+ gcc_assert (conversion_char);
+
+ /* First pass: look for a format_char_info containing CONVERSION_CHAR
+ If we find one, then presumably the length modifier was incorrect
+ (or absent). */
+ char *result = get_format_for_type_1 (fki, arg_type, conversion_char);
+ if (result)
+ return result;
+
+ /* Second pass: we didn't find a match for CONVERSION_CHAR, so try
+ matching just on the type. */
+ return get_format_for_type_1 (fki, arg_type, '\0');
+}
+
+/* Attempt to get a string for use as a replacement fix-it hint for the
+ source range in FMT_LOC.
+
+ Preserve all of the text within the range of FMT_LOC up to
+ OFFSET_TO_TYPE_START, replacing the rest with an appropriate
+ length modifier and conversion specifier for ARG_TYPE, attempting
+ to keep the user-provided CONVERSION_CHAR if possible.
+
+ For example, given a long vs long long mismatch for arg5 here:
+
+ 000000000111111111122222222223333333333|
+ 123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` string offsets
+ V~~~~~~~~ : range of FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", arg3, arg4, arg5);
+ ^ ^
+ | ` CONVERSION_CHAR: 'd'
+ type starts here
+
+ where OFFSET_TO_TYPE_START is 13 (the offset to the "lld" within the
+ STRING_CST), where the user provided:
+ %-+*.*lld
+ the result (assuming "long" argument 5) should be:
+ %-+*.*ld
+
+ If successful, returns a non-NULL string which should be freed
+ by the caller.
+ Otherwise, returns NULL. */
+
+static char *
+get_corrected_substring (const substring_loc &fmt_loc,
+ format_wanted_type *type, tree arg_type,
+ const format_kind_info *fki,
+ int offset_to_type_start, char conversion_char)
+{
+ /* Attempt to provide hints for argument types, but not for field widths
+ and precisions. */
+ if (!arg_type)
+ return NULL;
+ if (type->kind != CF_KIND_FORMAT)
+ return NULL;
+
+ /* Locate the current code within the source range, rejecting
+ any awkward cases where the format string occupies more than
+ one line.
+ Lookup the place where the type starts (including any length
+ modifiers), getting it as the caret location. */
+ substring_loc type_loc (fmt_loc);
+ type_loc.set_caret_index (offset_to_type_start);
+
+ location_t fmt_substring_loc;
+ const char *err = type_loc.get_location (&fmt_substring_loc);
+ if (err)
+ return NULL;
+
+ source_range fmt_substring_range
+ = get_range_from_loc (line_table, fmt_substring_loc);
+
+ expanded_location caret
+ = expand_location_to_spelling_point (fmt_substring_loc);
+ expanded_location start
+ = expand_location_to_spelling_point (fmt_substring_range.m_start);
+ expanded_location finish
+ = expand_location_to_spelling_point (fmt_substring_range.m_finish);
+ if (caret.file != start.file)
+ return NULL;
+ if (start.file != finish.file)
+ return NULL;
+ if (caret.line != start.line)
+ return NULL;
+ if (start.line != finish.line)
+ return NULL;
+ if (start.column > caret.column)
+ return NULL;
+ if (start.column > finish.column)
+ return NULL;
+ if (caret.column > finish.column)
+ return NULL;
+
+#if BUILDING_GCC_VERSION >= 9000
+ char_span line = location_get_source_line (start.file, start.line);
+ if (!line)
+ return NULL;
+
+ /* If we got this far, then we have the line containing the
+ existing conversion specification.
+
+ Generate a trimmed copy, containing the prefix part of the conversion
+ specification, up to the (but not including) the length modifier.
+ In the above example, this would be "%-+*.*". */
+ int length_up_to_type = caret.column - start.column;
+ char_span prefix_span = line.subspan (start.column - 1, length_up_to_type);
+ char *prefix = prefix_span.xstrdup ();
+#else
+ char *prefix = NULL;
+#endif
+
+ /* Now attempt to generate a suggestion for the rest of the specification
+ (length modifier and conversion char), based on ARG_TYPE and
+ CONVERSION_CHAR.
+ In the above example, this would be "ld". */
+ char *format_for_type = get_format_for_type (fki, arg_type, conversion_char);
+ if (!format_for_type)
+ {
+ free (prefix);
+ return NULL;
+ }
+
+ /* Success. Generate the resulting suggestion for the whole range of
+ FMT_LOC by concatenating the two strings.
+ In the above example, this would be "%-+*.*ld". */
+ char *result = concat (prefix, format_for_type, NULL);
+ free (format_for_type);
+ free (prefix);
+ return result;
+}
+
+/* Helper class for adding zero or more trailing '*' to types.
+
+ The format type and name exclude any '*' for pointers, so those
+ must be formatted manually. For all the types we currently have,
+ this is adequate, but formats taking pointers to functions or
+ arrays would require the full type to be built up in order to
+ print it with %T. */
+
+class indirection_suffix
+{
+ public:
+ indirection_suffix (int pointer_count) : m_pointer_count (pointer_count) {}
+
+ /* Determine the size of the buffer (including NUL-terminator). */
+
+ size_t get_buffer_size () const
+ {
+ return m_pointer_count + 2;
+ }
+
+ /* Write the '*' to DST and add a NUL-terminator. */
+
+ void fill_buffer (char *dst) const
+ {
+ if (m_pointer_count == 0)
+ dst[0] = 0;
+ else if (c_dialect_cxx ())
+ {
+ memset (dst, '*', m_pointer_count);
+ dst[m_pointer_count] = 0;
+ }
+ else
+ {
+ dst[0] = ' ';
+ memset (dst + 1, '*', m_pointer_count);
+ dst[m_pointer_count + 1] = 0;
+ }
+ }
+
+ private:
+ int m_pointer_count;
+};
+
+#if BUILDING_GCC_VERSION >= 9000
+/* not exported by GCC... need a local copy :( */
+class frr_range_label_for_type_mismatch : public range_label
+{
+ public:
+ frr_range_label_for_type_mismatch (tree labelled_type, tree other_type)
+ : m_labelled_type (labelled_type), m_other_type (other_type)
+ {
+ }
+
+ label_text get_text (unsigned range_idx) const override;
+
+ protected:
+ tree m_labelled_type;
+ tree m_other_type;
+};
+
+/* Print T to CPP. */
+
+static void
+print_type (c_pretty_printer *cpp, tree t, bool *quoted)
+{
+ gcc_assert (TYPE_P (t));
+ struct obstack *ob = pp_buffer (cpp)->obstack;
+ char *p = (char *) obstack_base (ob);
+ /* Remember the end of the initial dump. */
+ int len = obstack_object_size (ob);
+
+ tree name = TYPE_NAME (t);
+ if (name && TREE_CODE (name) == TYPE_DECL && DECL_NAME (name))
+ pp_identifier (cpp, lang_hooks.decl_printable_name (name, 2));
+ else
+ cpp->type_id (t);
+
+ /* If we're printing a type that involves typedefs, also print the
+ stripped version. But sometimes the stripped version looks
+ exactly the same, so we don't want it after all. To avoid
+ printing it in that case, we play ugly obstack games. */
+ if (TYPE_CANONICAL (t) && t != TYPE_CANONICAL (t))
+ {
+ c_pretty_printer cpp2;
+ /* Print the stripped version into a temporary printer. */
+ cpp2.type_id (TYPE_CANONICAL (t));
+ struct obstack *ob2 = cpp2.buffer->obstack;
+ /* Get the stripped version from the temporary printer. */
+ const char *aka = (char *) obstack_base (ob2);
+ int aka_len = obstack_object_size (ob2);
+ int type1_len = obstack_object_size (ob) - len;
+
+ /* If they are identical, bail out. */
+ if (aka_len == type1_len && memcmp (p + len, aka, aka_len) == 0)
+ return;
+
+ /* They're not, print the stripped version now. */
+ if (*quoted)
+ pp_end_quote (cpp, pp_show_color (cpp));
+ pp_c_whitespace (cpp);
+ pp_left_brace (cpp);
+ pp_c_ws_string (cpp, _("aka"));
+ pp_c_whitespace (cpp);
+ if (*quoted)
+ pp_begin_quote (cpp, pp_show_color (cpp));
+ cpp->type_id (TYPE_CANONICAL (t));
+ if (*quoted)
+ pp_end_quote (cpp, pp_show_color (cpp));
+ pp_right_brace (cpp);
+ /* No further closing quotes are needed. */
+ *quoted = false;
+ }
+}
+
+/* C-specific implementation of range_label::get_text () vfunc for
+ range_label_for_type_mismatch. */
+#if BUILDING_GCC_VERSION >= 10000
+#define label_borrow(text) label_text::borrow(text)
+#define label_take(text) label_text::take(text)
+#else
+#define label_borrow(text) label_text((char *)text, false)
+#define label_take(text) label_text(text, true)
+#endif
+
+label_text
+frr_range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
+{
+ if (m_labelled_type == NULL_TREE)
+ return label_borrow("(null tree)");
+
+ c_pretty_printer cpp;
+ bool quoted = false;
+ print_type (&cpp, m_labelled_type, &quoted);
+ return label_take(xstrdup (pp_formatted_text (&cpp)));
+}
+
+#define range_label_for_type_mismatch frr_range_label_for_type_mismatch
+#endif
+
+/* Subclass of range_label for labelling the range in the format string
+ with the type in question, adding trailing '*' for pointer_count. */
+
+class range_label_for_format_type_mismatch
+ : public range_label_for_type_mismatch
+{
+ public:
+ range_label_for_format_type_mismatch (tree labelled_type, tree other_type,
+ int pointer_count)
+ : range_label_for_type_mismatch (labelled_type, other_type),
+ m_pointer_count (pointer_count)
+ {
+ }
+
+#if BUILDING_GCC_VERSION >= 13000
+#define text_get(text) text.get()
+#define text_return(text, result) return label_text::take(result)
+#else
+#define text_get(text) text.m_buffer
+#define text_return(text, result) text.maybe_free(); return label_take(result)
+#endif
+
+ label_text get_text (unsigned range_idx) const final override
+ {
+ label_text text = range_label_for_type_mismatch::get_text (range_idx);
+ if (text_get(text) == NULL)
+ return text;
+
+ indirection_suffix suffix (m_pointer_count);
+ char *p = (char *) alloca (suffix.get_buffer_size ());
+ suffix.fill_buffer (p);
+
+ char *result = concat (text_get(text), p, NULL);
+ text_return(text, result);
+ }
+
+ private:
+ int m_pointer_count;
+};
+
+/* Give a warning about a format argument of different type from that expected.
+ The range of the diagnostic is taken from WHOLE_FMT_LOC; the caret location
+ is based on the location of the char at TYPE->offset_loc.
+ PARAM_LOC is the location of the relevant argument, or UNKNOWN_LOCATION
+ if this is unavailable.
+ WANTED_TYPE is the type the argument should have,
+ possibly stripped of pointer dereferences. The description (such as "field
+ precision"), the placement in the format string, a possibly more
+ friendly name of WANTED_TYPE, and the number of pointer dereferences
+ are taken from TYPE. ARG_TYPE is the type of the actual argument,
+ or NULL if it is missing.
+
+ OFFSET_TO_TYPE_START is the offset within the execution-charset encoded
+ format string to where type information begins for the conversion
+ (the length modifier and conversion specifier).
+ CONVERSION_CHAR is the user-provided conversion specifier.
+
+ For example, given a type mismatch for argument 5 here:
+
+ 00000000011111111112222222222333333333344444444445555555555|
+ 12345678901234567890123456789012345678901234567890123456789` column numbers
+ 0000000000111111111122|
+ 0123456789012345678901` offsets within STRING_CST
+ V~~~~~~~~ : range of WHOLE_FMT_LOC, from cols 23-31
+ sprintf (d, "before %-+*.*lld after", int_expr, int_expr, long_expr);
+ ^ ^ ^~~~~~~~~
+ | ` CONVERSION_CHAR: 'd' PARAM_LOC
+ type starts here
+
+ OFFSET_TO_TYPE_START is 13, the offset to the "lld" within the
+ STRING_CST. */
+
+static void
+format_type_warning (const substring_loc &whole_fmt_loc,
+ location_t param_loc,
+ format_wanted_type *type,
+ tree wanted_type, tree arg_type,
+ const format_kind_info *fki,
+ int offset_to_type_start,
+ char conversion_char,
+ const char *extra)
+{
+ enum format_specifier_kind kind = type->kind;
+ const char *wanted_type_name = type->wanted_type_name;
+ const char *format_start = type->format_start;
+ int format_length = type->format_length;
+ int pointer_count = type->pointer_count;
+ int arg_num = type->arg_num;
+
+ if (!extra)
+ extra = "";
+
+ /* If ARG_TYPE is a typedef with a misleading name (for example,
+ size_t but not the standard size_t expected by printf %zu), avoid
+ printing the typedef name. */
+ if (wanted_type_name
+ && arg_type
+ && TYPE_NAME (arg_type)
+ && TREE_CODE (TYPE_NAME (arg_type)) == TYPE_DECL
+ && DECL_NAME (TYPE_NAME (arg_type))
+ && !strcmp (wanted_type_name,
+ lang_hooks.decl_printable_name (TYPE_NAME (arg_type), 2)))
+ arg_type = TYPE_MAIN_VARIANT (arg_type);
+
+ indirection_suffix suffix (pointer_count);
+ char *p = (char *) alloca (suffix.get_buffer_size ());
+ suffix.fill_buffer (p);
+
+ /* WHOLE_FMT_LOC has the caret at the end of the range.
+ Set the caret to be at the offset from TYPE. Subtract one
+ from the offset for the same reason as in format_warning_at_char. */
+ substring_loc fmt_loc (whole_fmt_loc);
+ fmt_loc.set_caret_index (type->offset_loc - 1);
+
+#if BUILDING_GCC_VERSION >= 9000
+ range_label_for_format_type_mismatch fmt_label (wanted_type, arg_type,
+ pointer_count);
+ range_label_for_type_mismatch param_label (arg_type, wanted_type);
+
+ /* Get a string for use as a replacement fix-it hint for the range in
+ fmt_loc, or NULL. */
+ char *corrected_substring
+ = get_corrected_substring (fmt_loc, type, arg_type, fki,
+ offset_to_type_start, conversion_char);
+ format_string_diagnostic_t diag (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring);
+# define format_warning_at_substring(a,b,c,d,e,...) \
+ diag.emit_warning(__VA_ARGS__)
+#else
+# define format_warning_at_substring(a,b,c,d,...) \
+ format_warning_at_substring(a,c,__VA_ARGS__)
+ /* Get a string for use as a replacement fix-it hint for the range in
+ fmt_loc, or NULL. */
+ char *corrected_substring
+ = get_corrected_substring (fmt_loc, type, arg_type, fki,
+ offset_to_type_start, conversion_char);
+
+#endif
+
+ if (wanted_type_name)
+ {
+ if (arg_type)
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%s%s%>, but argument %d has type %qT%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type_name, p, arg_num, arg_type, extra);
+ else
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%s%s%> argument%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type_name, p, extra);
+ }
+ else
+ {
+ if (arg_type)
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects argument of type %<%T%s%>, but argument %d has type %qT%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start,
+ wanted_type, p, arg_num, arg_type, extra);
+ else
+ format_warning_at_substring
+ (fmt_loc, &fmt_label, param_loc, &param_label,
+ corrected_substring, OPT_Wformat_,
+ "%s %<%s%.*s%> expects a matching %<%T%s%> argument%s",
+ gettext (kind_descriptions[kind]),
+ (kind == CF_KIND_FORMAT ? "%" : ""),
+ format_length, format_start, wanted_type, p, extra);
+ }
+
+ free (corrected_substring);
+}
+
+
+#if 0
+/* Given a format_char_info array FCI, and a character C, this function
+ returns the index into the conversion_specs where that specifier's
+ data is located. The character must exist. */
+static unsigned int
+find_char_info_specifier_index (const format_char_info *fci, int c)
+{
+ unsigned i;
+
+ for (i = 0; fci->format_chars; i++, fci++)
+ if (strchr (fci->format_chars, c))
+ return i;
+
+ /* We shouldn't be looking for a non-existent specifier. */
+ gcc_unreachable ();
+}
+
+/* Given a format_length_info array FLI, and a character C, this
+ function returns the index into the conversion_specs where that
+ modifier's data is located. The character must exist. */
+static unsigned int
+find_length_info_modifier_index (const format_length_info *fli, int c)
+{
+ unsigned i;
+
+ for (i = 0; fli->name; i++, fli++)
+ if (strchr (fli->name, c))
+ return i;
+
+ /* We shouldn't be looking for a non-existent modifier. */
+ gcc_unreachable ();
+}
+#endif
+
+#ifdef TARGET_FORMAT_TYPES
+extern const format_kind_info TARGET_FORMAT_TYPES[];
+#endif
+
+#ifdef TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+extern const target_ovr_attr TARGET_OVERRIDES_FORMAT_ATTRIBUTES[];
+#endif
+#ifdef TARGET_OVERRIDES_FORMAT_INIT
+ extern void TARGET_OVERRIDES_FORMAT_INIT (void);
+#endif
+
+/* Attributes such as "printf" are equivalent to those such as
+ "gnu_printf" unless this is overridden by a target. */
+static const target_ovr_attr gnu_target_overrides_format_attributes[] =
+{
+ { NULL, NULL }
+};
+
+/* Translate to unified attribute name. This is used in decode_format_type and
+ decode_format_attr. In attr_name the user specified argument is passed. It
+ returns the unified format name from TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+ or the attr_name passed to this function, if there is no matching entry. */
+static const char *
+convert_format_name_to_system_name (const char *attr_name)
+{
+ int i;
+
+ if (attr_name == NULL || *attr_name == 0
+ || strncmp (attr_name, "gcc_", 4) == 0)
+ return attr_name;
+#ifdef TARGET_OVERRIDES_FORMAT_INIT
+ TARGET_OVERRIDES_FORMAT_INIT ();
+#endif
+
+#ifdef TARGET_OVERRIDES_FORMAT_ATTRIBUTES
+ /* Check if format attribute is overridden by target. */
+ if (TARGET_OVERRIDES_FORMAT_ATTRIBUTES != NULL
+ && TARGET_OVERRIDES_FORMAT_ATTRIBUTES_COUNT > 0)
+ {
+ for (i = 0; i < TARGET_OVERRIDES_FORMAT_ATTRIBUTES_COUNT; ++i)
+ {
+ if (cmp_attribs (TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_src,
+ attr_name))
+ return attr_name;
+ if (cmp_attribs (TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_dst,
+ attr_name))
+ return TARGET_OVERRIDES_FORMAT_ATTRIBUTES[i].named_attr_src;
+ }
+ }
+#endif
+ /* Otherwise default to gnu format. */
+ for (i = 0;
+ gnu_target_overrides_format_attributes[i].named_attr_src != NULL;
+ ++i)
+ {
+ if (cmp_attribs (gnu_target_overrides_format_attributes[i].named_attr_src,
+ attr_name))
+ return attr_name;
+ if (cmp_attribs (gnu_target_overrides_format_attributes[i].named_attr_dst,
+ attr_name))
+ return gnu_target_overrides_format_attributes[i].named_attr_src;
+ }
+
+ return attr_name;
+}
+
+/* Handle a "format" attribute; arguments as in
+ struct attribute_spec.handler. */
+tree
+handle_frr_format_attribute (tree *node, tree ARG_UNUSED (name), tree args,
+ int flags, bool *no_add_attrs)
+{
+ tree type = *node;
+ function_format_info info;
+
+ /* Canonicalize name of format function. */
+ if (TREE_CODE (TREE_VALUE (args)) == IDENTIFIER_NODE)
+ TREE_VALUE (args) = canonicalize_attr_name (TREE_VALUE (args));
+
+ if (!decode_format_attr (args, &info, 0))
+ {
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ if (prototype_p (type))
+ {
+ if (!check_format_string (type, info.format_num, flags,
+ no_add_attrs, info.format_type))
+ return NULL_TREE;
+
+ if (info.first_arg_num != 0)
+ {
+ unsigned HOST_WIDE_INT arg_num = 1;
+ function_args_iterator iter;
+ tree arg_type;
+
+ /* Verify that first_arg_num points to the last arg,
+ the ... */
+ FOREACH_FUNCTION_ARGS (type, arg_type, iter)
+ arg_num++;
+
+ if (arg_num != info.first_arg_num)
+ {
+ if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+ error ("arguments to be formatted is not %<...%>");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+ }
+ }
+
+ /* Check if this is a strftime variant. Just for this variant
+ FMT_FLAG_ARG_CONVERT is not set. */
+ if ((format_types[info.format_type].flags & (int) FMT_FLAG_ARG_CONVERT) == 0
+ && info.first_arg_num != 0)
+ {
+ error ("strftime formats cannot format arguments");
+ *no_add_attrs = true;
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests of location handling. */
+
+/* Get the format_kind_info with the given name. */
+
+static const format_kind_info *
+get_info (const char *name)
+{
+ int idx = decode_format_type (name);
+ const format_kind_info *fki = &format_types[idx];
+ ASSERT_STREQ (fki->name, name);
+ return fki;
+}
+
+/* Verify that get_format_for_type (FKI, TYPE, CONVERSION_CHAR)
+ is EXPECTED_FORMAT. */
+
+static void
+assert_format_for_type_streq (const location &loc, const format_kind_info *fki,
+ const char *expected_format, tree type,
+ char conversion_char)
+{
+ gcc_assert (fki);
+ gcc_assert (expected_format);
+ gcc_assert (type);
+
+ char *actual_format = get_format_for_type (fki, type, conversion_char);
+ ASSERT_STREQ_AT (loc, expected_format, actual_format);
+ free (actual_format);
+}
+
+/* Selftests for get_format_for_type. */
+
+#define ASSERT_FORMAT_FOR_TYPE_STREQ(EXPECTED_FORMAT, TYPE, CONVERSION_CHAR) \
+ assert_format_for_type_streq (SELFTEST_LOCATION, (fki), (EXPECTED_FORMAT), \
+ (TYPE), (CONVERSION_CHAR))
+
+/* Selftest for get_format_for_type for "printf"-style functions. */
+
+static void
+test_get_format_for_type_printf ()
+{
+ const format_kind_info *fki = get_info ("gnu_printf");
+ ASSERT_NE (fki, NULL);
+
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("f", double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("Lf", long_double_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", integer_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", integer_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", unsigned_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("i", unsigned_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("o", unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("x", unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("X", unsigned_type_node, 'X');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld", long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("li", long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_integer_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lo", long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lx", long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lld", long_long_integer_type_node, 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lli", long_long_integer_type_node, 'i');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llo", long_long_unsigned_type_node, 'o');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("llx", long_long_unsigned_type_node, 'x');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("s", build_pointer_type (char_type_node), 'i');
+}
+
+/* Selftest for get_format_for_type for "scanf"-style functions. */
+
+static void
+test_get_format_for_type_scanf ()
+{
+ const format_kind_info *fki = get_info ("gnu_scanf");
+ ASSERT_NE (fki, NULL);
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("d", build_pointer_type (integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("u", build_pointer_type (unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("ld",
+ build_pointer_type (long_integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("lu",
+ build_pointer_type (long_unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ
+ ("lld", build_pointer_type (long_long_integer_type_node), 'd');
+ ASSERT_FORMAT_FOR_TYPE_STREQ
+ ("llu", build_pointer_type (long_long_unsigned_type_node), 'u');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("e", build_pointer_type (float_type_node), 'e');
+ ASSERT_FORMAT_FOR_TYPE_STREQ ("le", build_pointer_type (double_type_node), 'e');
+}
+
+#undef ASSERT_FORMAT_FOR_TYPE_STREQ
+
+/* Exercise the type-printing label code, to give some coverage
+ under "make selftest-valgrind" (in particular, to ensure that
+ the label-printing machinery doesn't leak). */
+
+static void
+test_type_mismatch_range_labels ()
+{
+ /* Create a tempfile and write some text to it.
+ ....................0000000001 11111111 12 22222222
+ ....................1234567890 12345678 90 12345678. */
+ const char *content = " printf (\"msg: %i\\n\", msg);\n";
+ temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+ line_table_test ltt;
+
+ linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
+
+ location_t c17 = linemap_position_for_column (line_table, 17);
+ ASSERT_EQ (LOCATION_COLUMN (c17), 17);
+ location_t c18 = linemap_position_for_column (line_table, 18);
+ location_t c24 = linemap_position_for_column (line_table, 24);
+ location_t c26 = linemap_position_for_column (line_table, 26);
+
+ /* Don't attempt to run the tests if column data might be unavailable. */
+ if (c26 > LINE_MAP_MAX_LOCATION_WITH_COLS)
+ return;
+
+ location_t fmt = make_location (c18, c17, c18);
+ ASSERT_EQ (LOCATION_COLUMN (fmt), 18);
+
+ location_t param = make_location (c24, c24, c26);
+ ASSERT_EQ (LOCATION_COLUMN (param), 24);
+
+ range_label_for_format_type_mismatch fmt_label (char_type_node,
+ integer_type_node, 1);
+ range_label_for_type_mismatch param_label (integer_type_node,
+ char_type_node);
+ gcc_rich_location richloc (fmt, &fmt_label);
+ richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, &param_label);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ if (c_dialect_cxx ())
+ /* "char*", without a space. */
+ ASSERT_STREQ ("\n"
+ " printf (\"msg: %i\\n\", msg);\n"
+ " ~^ ~~~\n"
+ " | |\n"
+ " char* int\n",
+ pp_formatted_text (dc.printer));
+ else
+ /* "char *", with a space. */
+ ASSERT_STREQ ("\n"
+ " printf (\"msg: %i\\n\", msg);\n"
+ " ~^ ~~~\n"
+ " | |\n"
+ " | int\n"
+ " char *\n",
+ pp_formatted_text (dc.printer));
+}
+
+/* Run all of the selftests within this file. */
+
+void
+c_format_c_tests ()
+{
+ test_get_modifier_for_format_len ();
+ test_get_format_for_type_printf ();
+ test_get_format_for_type_scanf ();
+ test_type_mismatch_range_labels ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+// include "gt-c-family-c-format.h"
+
+static const struct attribute_spec frr_format_attribute_table[] =
+{
+ /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
+ affects_type_identity, handler, exclude } */
+ { "frr_format", 3, 3, false, true, true, false,
+ handle_frr_format_attribute, NULL },
+ { "frr_format_arg", 1, 1, false, true, true, false,
+ handle_frr_format_arg_attribute, NULL },
+ { NULL, 0, 0, false, false, false, false, NULL, NULL }
+};
+
+static void
+register_attributes (void *event_data, void *data)
+{
+ // warning (0, G_("Callback to register attributes"));
+ register_attribute (frr_format_attribute_table);
+}
+
+tree
+cb_walk_tree_fn (tree * tp, int * walk_subtrees, void * data ATTRIBUTE_UNUSED)
+{
+ if (TREE_CODE (*tp) != CALL_EXPR)
+ return NULL_TREE;
+
+ tree call_expr = *tp;
+
+ int nargs = call_expr_nargs(call_expr);
+ tree fn = CALL_EXPR_FN(call_expr);
+
+ if (!fn || TREE_CODE (fn) != ADDR_EXPR)
+ return NULL_TREE;
+
+ tree fndecl = TREE_OPERAND (fn, 0);
+ if (TREE_CODE (fndecl) != FUNCTION_DECL)
+ return NULL_TREE;
+
+#if 0
+ warning (0, G_("function call to %s, %d args"),
+ IDENTIFIER_POINTER (DECL_NAME (fndecl)),
+ nargs);
+#endif
+
+ tree *fargs = (tree *) alloca (nargs * sizeof (tree));
+
+ for (int j = 0; j < nargs; j++)
+ {
+ tree arg = CALL_EXPR_ARG(call_expr, j);
+
+ /* For -Wformat undo the implicit passing by hidden reference
+ done by convert_arg_to_ellipsis. */
+ if (TREE_CODE (arg) == ADDR_EXPR
+ && TREE_CODE (TREE_TYPE (arg)) == REFERENCE_TYPE)
+ fargs[j] = TREE_OPERAND (arg, 0);
+ else
+ fargs[j] = arg;
+ }
+
+ check_function_format (TYPE_ATTRIBUTES (TREE_TYPE (fndecl)), nargs, fargs, NULL);
+ return NULL_TREE;
+}
+
+static void
+setup_type (const char *name, tree *dst)
+{
+ tree tmp;
+
+ if (*dst && *dst != void_type_node)
+ return;
+
+ *dst = maybe_get_identifier (name);
+ if (!*dst)
+ return;
+
+ tmp = identifier_global_value (*dst);
+ if (tmp && TREE_CODE (tmp) != TYPE_DECL)
+ {
+ warning (0, "%qs is not defined as a type", name);
+ *dst = NULL;
+ return;
+ }
+ if (tmp && TREE_CODE (tmp) == TYPE_DECL)
+ *dst = tmp;
+ else
+ *dst = NULL;
+}
+
+static void
+handle_finish_parse (void *event_data, void *data)
+{
+ tree fndecl = (tree) event_data;
+ gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL);
+
+ setup_type ("uint64_t", &local_uint64_t_node);
+ setup_type ("int64_t", &local_int64_t_node);
+
+ setup_type ("size_t", &local_size_t_node);
+ setup_type ("ssize_t", &local_ssize_t_node);
+ setup_type ("atomic_size_t", &local_atomic_size_t_node);
+ setup_type ("atomic_ssize_t", &local_atomic_ssize_t_node);
+ setup_type ("ptrdiff_t", &local_ptrdiff_t_node);
+
+ setup_type ("pid_t", &local_pid_t_node);
+ setup_type ("uid_t", &local_uid_t_node);
+ setup_type ("gid_t", &local_gid_t_node);
+ setup_type ("time_t", &local_time_t_node);
+
+ setup_type ("socklen_t", &local_socklen_t_node);
+ setup_type ("in_addr_t", &local_in_addr_t_node);
+
+ const format_char_info *fci;
+
+ for (fci = print_char_table; fci->format_chars; fci++)
+ {
+ if (!fci->kernel_ext)
+ continue;
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ for (; etab->suffix && etab < etab_end; etab++)
+ {
+ tree identifier, node;
+
+ if (etab->type && etab->type != void_type_node)
+ continue;
+
+ identifier = maybe_get_identifier (etab->type_str);
+
+ if (!identifier || identifier == error_mark_node)
+ continue;
+
+ if (etab->type_code)
+ {
+ node = identifier_global_tag (identifier);
+ if (!node)
+ continue;
+
+ if (node->base.code != etab->type_code)
+ {
+ if (!etab->warned)
+ {
+ warning (0, "%qs tag category (struct/union/enum) mismatch", etab->type_str);
+ etab->warned = true;
+ }
+ continue;
+ }
+ }
+ else
+ {
+ node = identifier_global_value (identifier);
+ if (!node)
+ continue;
+
+ if (TREE_CODE (node) != TYPE_DECL)
+ {
+ if (!etab->warned)
+ {
+ warning (0, "%qs is defined as a non-type", etab->type_str);
+ etab->warned = true;
+ }
+ continue;
+ }
+ node = TREE_TYPE (node);
+
+ if (etab->t_unsigned)
+ node = c_common_unsigned_type (node);
+ else if (etab->t_signed)
+ node = c_common_signed_type (node);
+ }
+
+ etab->type = node;
+ }
+ }
+
+ walk_tree (&DECL_SAVED_TREE (fndecl), cb_walk_tree_fn, NULL, NULL);
+}
+
+static void
+handle_pragma_printfrr_ext (cpp_reader *dummy)
+{
+ tree token = 0;
+ location_t loc;
+ enum cpp_ttype ttype;
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_STRING)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%> requires string argument");
+ return;
+ }
+
+ const char *s = TREE_STRING_POINTER (token);
+
+ if (s[0] != '%')
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, needs to start with '%%'");
+ return;
+ }
+
+ switch (s[1])
+ {
+ case 'p':
+ case 'd':
+ case 'i':
+ break;
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, needs to be %%p, %%d or %%i");
+ return;
+ }
+
+ const format_char_info *fci;
+
+ for (fci = print_char_table; fci->format_chars; fci++)
+ if (strchr (fci->format_chars, s[1]))
+ break;
+
+ gcc_assert (fci->format_chars);
+ gcc_assert (fci->kernel_ext);
+
+ struct kernel_ext_fmt *etab = fci->kernel_ext;
+ struct kernel_ext_fmt *etab_end = etab + ETAB_SZ;
+
+ switch (s[2])
+ {
+ case 'A' ... 'Z':
+ break;
+
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: invalid format string, suffix must start with an uppercase letter");
+ return;
+ }
+
+ /* -2 -- need to keep the sentinel at the end */
+ if (etab[ETAB_SZ - 2].suffix)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: out of space for format suffixes");
+ return;
+ }
+
+ for (; etab->suffix && etab < etab_end; etab++)
+ {
+ if (!strcmp(s + 2, etab->suffix))
+ {
+ memmove (etab + 1, etab, (etab_end - etab - 1) * sizeof (*etab));
+
+ if (0)
+ {
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: duplicate printf format suffix %qs", s);
+ warning_at (etab->origin_loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: previous definition was here");
+ return;
+ }
+
+ break;
+ }
+
+ if (!strncmp(s + 2, etab->suffix, MIN(strlen(s + 2), strlen(etab->suffix))))
+ {
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: overlapping printf format suffix %qs", s);
+ warning_at (etab->origin_loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: previous definition for %<%%%c%s%> was here", s[1], etab->suffix);
+ return;
+ }
+ }
+
+ gcc_assert (etab < etab_end);
+
+ memset (etab, 0, sizeof (*etab));
+ etab->suffix = xstrdup(s + 2);
+ etab->origin_loc = loc;
+ etab->type = void_type_node;
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_OPEN_PAREN)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%> expected %<(%>");
+ goto out_drop;
+ }
+
+ ttype = pragma_lex (&token, &loc);
+
+ /* qualifiers */
+ while (ttype == CPP_NAME)
+ {
+ if (!strcmp (IDENTIFIER_POINTER (token), "const"))
+ etab->t_const = true;
+ else if (!strcmp (IDENTIFIER_POINTER (token), "signed"))
+ etab->t_signed = true;
+ else if (!strcmp (IDENTIFIER_POINTER (token), "unsigned"))
+ etab->t_unsigned = true;
+ else
+ break;
+
+ ttype = pragma_lex (&token, &loc);
+ }
+
+ /* tagged types */
+ if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "struct"))
+ {
+ etab->type_code = RECORD_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+ else if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "union"))
+ {
+ etab->type_code = UNION_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+ else if (ttype == CPP_NAME && !strcmp (IDENTIFIER_POINTER (token), "enum"))
+ {
+ etab->type_code = ENUMERAL_TYPE;
+ ttype = pragma_lex (&token, &loc);
+ }
+
+ /* type name */
+ if (ttype != CPP_NAME)
+ {
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: expected typename identifier");
+ goto out_drop;
+ }
+
+ etab->type_str = xstrdup (IDENTIFIER_POINTER (token));
+
+ while ((ttype = pragma_lex (&token, &loc)) != CPP_CLOSE_PAREN)
+ {
+ switch (ttype) {
+ case CPP_NAME:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: unexpected identifier. Note the only supported qualifier is %<const%>");
+ goto out_drop;
+
+ case CPP_MULT:
+ etab->ptrlevel++;
+ break;
+
+ case CPP_EOF:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: premature end of line, missing %<)%>");
+ goto out_drop;
+
+ default:
+ error_at (loc, "%<#pragma FRR printfrr_ext%>: unsupported token");
+ goto out_drop;
+ }
+ }
+
+ ttype = pragma_lex (&token, &loc);
+ if (ttype != CPP_EOF)
+ warning_at (loc, OPT_Wformat_,
+ "%<#pragma FRR printfrr_ext%>: garbage at end of line");
+
+ return;
+
+out_drop:
+ memset (etab, 0, sizeof (*etab));
+}
+
+static void
+register_pragma_printfrr_ext (void *event_data, void *data)
+{
+ c_register_pragma_with_expansion ("FRR", "printfrr_ext", handle_pragma_printfrr_ext);
+}
+
+static void
+define_vars (void *gcc_data, void *user_data)
+{
+ cpp_define (parse_in, "_FRR_ATTRIBUTE_PRINTFRR=0x10000");
+}
+
+#ifndef __visible
+#define __visible __attribute__((visibility("default")))
+#endif
+
+__visible int plugin_is_GPL_compatible;
+
+__visible int
+plugin_init (struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ const char *plugin_name = plugin_info->base_name;
+
+ if (!plugin_default_version_check(version, &gcc_version))
+ {
+ error(G_("incompatible gcc/plugin versions"));
+ return 1;
+ }
+
+ memset (ext_p, 0, sizeof (ext_p));
+ memset (ext_d, 0, sizeof (ext_d));
+
+ register_callback (plugin_name, PLUGIN_FINISH_PARSE_FUNCTION, handle_finish_parse, NULL);
+ register_callback (plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
+ register_callback (plugin_name, PLUGIN_START_UNIT, define_vars, NULL);
+ register_callback (plugin_name, PLUGIN_PRAGMAS, register_pragma_printfrr_ext, NULL);
+ return 0;
+}
diff --git a/tools/gcc-plugins/frr-format.h b/tools/gcc-plugins/frr-format.h
new file mode 100644
index 0000000..373ef07
--- /dev/null
+++ b/tools/gcc-plugins/frr-format.h
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/* Check calls to formatted I/O functions (-Wformat).
+ Copyright (C) 1992-2018 Free Software Foundation, Inc.
+ */
+
+#ifndef GCC_C_FORMAT_H
+#define GCC_C_FORMAT_H
+
+/* The meaningfully distinct length modifiers for format checking recognized
+ by GCC. */
+enum format_lengths
+{
+ FMT_LEN_none,
+ FMT_LEN_hh,
+ FMT_LEN_h,
+ FMT_LEN_l,
+ FMT_LEN_ll,
+ FMT_LEN_L,
+ FMT_LEN_z,
+ FMT_LEN_t,
+ FMT_LEN_j,
+ FMT_LEN_H,
+ FMT_LEN_D,
+ FMT_LEN_DD,
+ FMT_LEN_MAX
+};
+
+
+/* The standard versions in which various format features appeared. */
+enum format_std_version
+{
+ STD_C89,
+ STD_C94,
+ STD_C9L, /* C99, but treat as C89 if -Wno-long-long. */
+ STD_C99,
+ STD_EXT
+};
+
+/* Flags that may apply to a particular kind of format checked by GCC. */
+enum
+{
+ /* This format converts arguments of types determined by the
+ format string. */
+ FMT_FLAG_ARG_CONVERT = 1,
+ /* The scanf allocation 'a' kludge applies to this format kind. */
+ FMT_FLAG_SCANF_A_KLUDGE = 2,
+ /* A % during parsing a specifier is allowed to be a modified % rather
+ that indicating the format is broken and we are out-of-sync. */
+ FMT_FLAG_FANCY_PERCENT_OK = 4,
+ /* With $ operand numbers, it is OK to reference the same argument more
+ than once. */
+ FMT_FLAG_DOLLAR_MULTIPLE = 8,
+ /* This format type uses $ operand numbers (strfmon doesn't). */
+ FMT_FLAG_USE_DOLLAR = 16,
+ /* Zero width is bad in this type of format (scanf). */
+ FMT_FLAG_ZERO_WIDTH_BAD = 32,
+ /* Empty precision specification is OK in this type of format (printf). */
+ FMT_FLAG_EMPTY_PREC_OK = 64,
+ /* Gaps are allowed in the arguments with $ operand numbers if all
+ arguments are pointers (scanf). */
+ FMT_FLAG_DOLLAR_GAP_POINTER_OK = 128,
+ /* The format arg is an opaque object that will be parsed by an external
+ facility. */
+ FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL = 256
+ /* Not included here: details of whether width or precision may occur
+ (controlled by width_char and precision_char); details of whether
+ '*' can be used for these (width_type and precision_type); details
+ of whether length modifiers can occur (length_char_specs). */
+};
+
+/* Structure describing a length modifier supported in format checking, and
+ possibly a doubled version such as "hh". */
+struct format_length_info
+{
+ /* Name of the single-character length modifier. If prefixed by
+ a zero character, it describes a multi character length
+ modifier, like I64, I32, etc. */
+ const char *name;
+ /* Index into a format_char_info.types array. */
+ enum format_lengths index;
+ /* Standard version this length appears in. */
+ enum format_std_version std;
+ /* Same, if the modifier can be repeated, or NULL if it can't. */
+ const char *double_name;
+ enum format_lengths double_index;
+ enum format_std_version double_std;
+
+ /* If this flag is set, just scalar width identity is checked, and
+ not the type identity itself. */
+ int scalar_identity_flag;
+};
+
+
+struct kernel_ext_fmt
+{
+ const char *suffix;
+
+ /* RECORD_TYPE, UNION_TYPE, ENUMERAL_TYPE, or NULL for typedef */
+ tree_code type_code;
+ int ptrlevel;
+ bool t_const;
+ bool t_unsigned;
+ bool t_signed;
+ bool warned;
+
+ const char *type_str;
+ GTY(()) tree type;
+
+ location_t origin_loc;
+};
+
+
+/* Structure describing the combination of a conversion specifier
+ (or a set of specifiers which act identically) and a length modifier. */
+struct format_type_detail
+{
+ /* The standard version this combination of length and type appeared in.
+ This is only relevant if greater than those for length and type
+ individually; otherwise it is ignored. */
+ enum format_std_version std;
+ /* The name to use for the type, if different from that generated internally
+ (e.g., "signed size_t"). */
+ const char *name;
+ /* The type itself. */
+ tree *type;
+};
+
+
+/* Macros to fill out tables of these. */
+#define NOARGUMENTS { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+#define BADLEN { STD_C89, NULL, NULL }
+#define NOLENGTHS { BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+
+
+/* Structure describing a format conversion specifier (or a set of specifiers
+ which act identically), and the length modifiers used with it. */
+struct format_char_info
+{
+ const char *format_chars;
+ int pointer_count;
+ enum format_std_version std;
+ /* Types accepted for each length modifier. */
+ format_type_detail types[FMT_LEN_MAX];
+ /* List of other modifier characters allowed with these specifiers.
+ This lists flags, and additionally "w" for width, "p" for precision
+ (right precision, for strfmon), "#" for left precision (strfmon),
+ "a" for scanf "a" allocation extension (not applicable in C99 mode),
+ "*" for scanf suppression, and "E" and "O" for those strftime
+ modifiers. */
+ const char *flag_chars;
+ /* List of additional flags describing these conversion specifiers.
+ "c" for generic character pointers being allowed, "2" for strftime
+ two digit year formats, "3" for strftime formats giving two digit
+ years in some locales, "4" for "2" which becomes "3" with an "E" modifier,
+ "o" if use of strftime "O" is a GNU extension beyond C99,
+ "W" if the argument is a pointer which is dereferenced and written into,
+ "R" if the argument is a pointer which is dereferenced and read from,
+ "i" for printf integer formats where the '0' flag is ignored with
+ precision, and "[" for the starting character of a scanf scanset,
+ "<" if the specifier introduces a quoted sequence (such as "%<"),
+ ">" if the specifier terminates a quoted sequence (such as "%>"),
+ "[" if the specifier introduces a color sequence (such as "%r"),
+ "]" if the specifier terminates a color sequence (such as "%R"),
+ "'" (single quote) if the specifier is expected to be quoted when
+ it appears outside a quoted sequence and unquoted otherwise (such
+ as the GCC internal printf format directive "%T"), and
+ "\"" (double quote) if the specifier is not expected to appear in
+ a quoted sequence (such as the GCC internal format directive "%K". */
+ const char *flags2;
+ /* If this format conversion character consumes more than one argument,
+ CHAIN points to information about the next argument. For later
+ arguments, only POINTER_COUNT, TYPES, and the "c", "R", and "W" flags
+ in FLAGS2 are used. */
+ const struct format_char_info *chain;
+
+ struct kernel_ext_fmt *kernel_ext;
+};
+
+
+/* Structure describing a flag accepted by some kind of format. */
+struct format_flag_spec
+{
+ /* The flag character in question (0 for end of array). */
+ int flag_char;
+ /* Zero if this entry describes the flag character in general, or a
+ nonzero character that may be found in flags2 if it describes the
+ flag when used with certain formats only. If the latter, only
+ the first such entry found that applies to the current conversion
+ specifier is used; the values of 'name' and 'long_name' it supplies
+ will be used, if non-NULL and the standard version is higher than
+ the unpredicated one, for any pedantic warning. For example, 'o'
+ for strftime formats (meaning 'O' is an extension over C99). */
+ int predicate;
+ /* Nonzero if the next character after this flag in the format should
+ be skipped ('=' in strfmon), zero otherwise. */
+ int skip_next_char;
+ /* True if the flag introduces quoting (as in GCC's %qE). */
+ bool quoting;
+ /* The name to use for this flag in diagnostic messages. For example,
+ N_("'0' flag"), N_("field width"). */
+ const char *name;
+ /* Long name for this flag in diagnostic messages; currently only used for
+ "ISO C does not support ...". For example, N_("the 'I' printf flag"). */
+ const char *long_name;
+ /* The standard version in which it appeared. */
+ enum format_std_version std;
+};
+
+
+/* Structure describing a combination of flags that is bad for some kind
+ of format. */
+struct format_flag_pair
+{
+ /* The first flag character in question (0 for end of array). */
+ int flag_char1;
+ /* The second flag character. */
+ int flag_char2;
+ /* Nonzero if the message should say that the first flag is ignored with
+ the second, zero if the combination should simply be objected to. */
+ int ignored;
+ /* Zero if this entry applies whenever this flag combination occurs,
+ a nonzero character from flags2 if it only applies in some
+ circumstances (e.g. 'i' for printf formats ignoring 0 with precision). */
+ int predicate;
+};
+
+
+/* Structure describing a particular kind of format processed by GCC. */
+struct format_kind_info
+{
+ /* The name of this kind of format, for use in diagnostics. Also
+ the name of the attribute (without preceding and following __). */
+ const char *name;
+ /* Specifications of the length modifiers accepted; possibly NULL. */
+ const format_length_info *length_char_specs;
+ /* Details of the conversion specification characters accepted. */
+ const format_char_info *conversion_specs;
+ /* String listing the flag characters that are accepted. */
+ const char *flag_chars;
+ /* String listing modifier characters (strftime) accepted. May be NULL. */
+ const char *modifier_chars;
+ /* Details of the flag characters, including pseudo-flags. */
+ const format_flag_spec *flag_specs;
+ /* Details of bad combinations of flags. */
+ const format_flag_pair *bad_flag_pairs;
+ /* Flags applicable to this kind of format. */
+ int flags;
+ /* Flag character to treat a width as, or 0 if width not used. */
+ int width_char;
+ /* Flag character to treat a left precision (strfmon) as,
+ or 0 if left precision not used. */
+ int left_precision_char;
+ /* Flag character to treat a precision (for strfmon, right precision) as,
+ or 0 if precision not used. */
+ int precision_char;
+ /* If a flag character has the effect of suppressing the conversion of
+ an argument ('*' in scanf), that flag character, otherwise 0. */
+ int suppression_char;
+ /* Flag character to treat a length modifier as (ignored if length
+ modifiers not used). Need not be placed in flag_chars for conversion
+ specifiers, but is used to check for bad combinations such as length
+ modifier with assignment suppression in scanf. */
+ int length_code_char;
+ /* Assignment-allocation flag character ('m' in scanf), otherwise 0. */
+ int alloc_char;
+ /* Pointer to type of argument expected if '*' is used for a width,
+ or NULL if '*' not used for widths. */
+ tree *width_type;
+ /* Pointer to type of argument expected if '*' is used for a precision,
+ or NULL if '*' not used for precisions. */
+ tree *precision_type;
+};
+
+#define T_I &integer_type_node
+#define T89_I { STD_C89, NULL, T_I }
+#define T_L &long_integer_type_node
+#define T89_L { STD_C89, NULL, T_L }
+#define T_LL &long_long_integer_type_node
+#define T9L_LL { STD_C9L, NULL, T_LL }
+#define TEX_LL { STD_EXT, NULL, T_LL }
+#define T_U64 &local_uint64_t_node
+#define TEX_U64 { STD_EXT, "uint64_t", T_U64 }
+#define T_S64 &local_int64_t_node
+#define TEX_S64 { STD_EXT, "int64_t", T_S64 }
+#define T_S &short_integer_type_node
+#define T89_S { STD_C89, NULL, T_S }
+#define T_UI &unsigned_type_node
+#define T89_UI { STD_C89, NULL, T_UI }
+#define T_UL &long_unsigned_type_node
+#define T89_UL { STD_C89, NULL, T_UL }
+#define T_ULL &long_long_unsigned_type_node
+#define T9L_ULL { STD_C9L, NULL, T_ULL }
+#define TEX_ULL { STD_EXT, NULL, T_ULL }
+#define T_US &short_unsigned_type_node
+#define T89_US { STD_C89, NULL, T_US }
+#define T_F &float_type_node
+#define T89_F { STD_C89, NULL, T_F }
+#define T99_F { STD_C99, NULL, T_F }
+#define T_D &double_type_node
+#define T89_D { STD_C89, NULL, T_D }
+#define T99_D { STD_C99, NULL, T_D }
+#define T_LD &long_double_type_node
+#define T89_LD { STD_C89, NULL, T_LD }
+#define T99_LD { STD_C99, NULL, T_LD }
+#define T_C &char_type_node
+#define T89_C { STD_C89, NULL, T_C }
+#define T_SC &signed_char_type_node
+#define T99_SC { STD_C99, NULL, T_SC }
+#define T_UC &unsigned_char_type_node
+#define T99_UC { STD_C99, NULL, T_UC }
+#define T_V &void_type_node
+#define T89_G { STD_C89, NULL, &local_gimple_ptr_node }
+#define T89_T { STD_C89, NULL, &local_tree_type_node }
+#define T89_V { STD_C89, NULL, T_V }
+#define T_W &wchar_type_node
+#define T94_W { STD_C94, "wchar_t", T_W }
+#define TEX_W { STD_EXT, "wchar_t", T_W }
+#define T_WI &wint_type_node
+#define T94_WI { STD_C94, "wint_t", T_WI }
+#define TEX_WI { STD_EXT, "wint_t", T_WI }
+#define T_ST &local_size_t_node
+#define T99_ST { STD_C99, "size_t", T_ST }
+#define T_SST &local_ssize_t_node
+#define T99_SST { STD_C99, "ssize_t", T_SST }
+#define T_PD &ptrdiff_type_node
+#define T99_PD { STD_C99, "ptrdiff_t", T_PD }
+#define T_UPD &unsigned_ptrdiff_type_node
+#define T99_UPD { STD_C99, "unsigned ptrdiff_t", T_UPD }
+#define T_IM &intmax_type_node
+#define T99_IM { STD_C99, "intmax_t", T_IM }
+#define T_UIM &uintmax_type_node
+#define T99_UIM { STD_C99, "uintmax_t", T_UIM }
+#define T_D32 &dfloat32_type_node
+#define TEX_D32 { STD_EXT, "_Decimal32", T_D32 }
+#define T_D64 &dfloat64_type_node
+#define TEX_D64 { STD_EXT, "_Decimal64", T_D64 }
+#define T_D128 &dfloat128_type_node
+#define TEX_D128 { STD_EXT, "_Decimal128", T_D128 }
+
+/* Structure describing how format attributes such as "printf" are
+ interpreted as "gnu_printf" or "ms_printf" on a particular system.
+ TARGET_OVERRIDES_FORMAT_ATTRIBUTES is used to specify target-specific
+ defaults. */
+struct target_ovr_attr
+{
+ /* The name of the to be copied format attribute. */
+ const char *named_attr_src;
+ /* The name of the to be overridden format attribute. */
+ const char *named_attr_dst;
+};
+
+#endif /* GCC_C_FORMAT_H */
diff --git a/tools/gcc-plugins/gcc-common.h b/tools/gcc-plugins/gcc-common.h
new file mode 100644
index 0000000..6eaea9b
--- /dev/null
+++ b/tools/gcc-plugins/gcc-common.h
@@ -0,0 +1,994 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* FRR: imported from Linux kernel on 2020-07-14 */
+
+#ifndef GCC_COMMON_H_INCLUDED
+#define GCC_COMMON_H_INCLUDED
+
+#include "bversion.h"
+#if BUILDING_GCC_VERSION >= 6000
+#include "gcc-plugin.h"
+#else
+#include "plugin.h"
+#endif
+#include "plugin-version.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "line-map.h"
+#include "input.h"
+#include "tree.h"
+
+#include "tree-inline.h"
+#include "version.h"
+#include "rtl.h"
+#include "tm_p.h"
+#include "flags.h"
+#include "hard-reg-set.h"
+#include "output.h"
+#include "except.h"
+#include "function.h"
+#include "toplev.h"
+#if BUILDING_GCC_VERSION >= 5000
+#include "expr.h"
+#endif
+#include "basic-block.h"
+#include "intl.h"
+#include "ggc.h"
+#include "timevar.h"
+
+#if BUILDING_GCC_VERSION < 10000
+#include "params.h"
+#endif
+
+#if BUILDING_GCC_VERSION <= 4009
+#include "pointer-set.h"
+#else
+#include "hash-map.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 7000
+#include "memmodel.h"
+#endif
+#include "emit-rtl.h"
+#include "debug.h"
+#include "target.h"
+#include "langhooks.h"
+#include "cfgloop.h"
+#include "cgraph.h"
+#include "opts.h"
+
+#if BUILDING_GCC_VERSION == 4005
+#include <sys/mman.h>
+#endif
+
+#if BUILDING_GCC_VERSION >= 4007
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4006
+/*
+ * The c-family headers were moved into a subdirectory in GCC version
+ * 4.7, but most plugin-building users of GCC 4.6 are using the Debian
+ * or Ubuntu package, which has an out-of-tree patch to move this to the
+ * same location as found in 4.7 and later:
+ * https://sources.debian.net/src/gcc-4.6/4.6.3-14/debian/patches/pr45078.diff/
+ */
+#include "c-family/c-common.h"
+#else
+#include "c-common.h"
+#endif
+
+#if BUILDING_GCC_VERSION <= 4008
+#include "tree-flow.h"
+#else
+#include "tree-cfgcleanup.h"
+#include "tree-ssa-operands.h"
+#include "tree-into-ssa.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4008
+#include "is-a.h"
+#endif
+
+#include "diagnostic.h"
+#include "tree-dump.h"
+#include "tree-pass.h"
+#if BUILDING_GCC_VERSION >= 4009
+#include "pass_manager.h"
+#endif
+#include "predict.h"
+#include "ipa-utils.h"
+
+#if BUILDING_GCC_VERSION >= 8000
+#include "stringpool.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 4009
+#include "attribs.h"
+#include "varasm.h"
+#include "stor-layout.h"
+#include "internal-fn.h"
+#if BUILDING_GCC_VERSION >= 13000
+#include "gimple.h"
+#include "gimple-iterator.h"
+#endif
+#include "gimple-expr.h"
+#include "gimple-fold.h"
+#include "context.h"
+#include "tree-ssa-alias.h"
+#include "tree-ssa.h"
+#include "stringpool.h"
+#if BUILDING_GCC_VERSION >= 7000
+#include "tree-vrp.h"
+#endif
+#include "tree-ssanames.h"
+#include "print-tree.h"
+#include "tree-eh.h"
+#include "stmt.h"
+#include "gimplify.h"
+#endif
+
+#include "gimple.h"
+
+#if BUILDING_GCC_VERSION >= 4009
+#include "tree-ssa-operands.h"
+#include "tree-phinodes.h"
+#include "tree-cfg.h"
+#include "gimple-iterator.h"
+#include "gimple-ssa.h"
+#include "ssa-iterators.h"
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000
+#include "builtins.h"
+#endif
+
+/* missing from basic_block.h... */
+void debug_dominance_info(enum cdi_direction dir);
+void debug_dominance_tree(enum cdi_direction dir, basic_block root);
+
+#if BUILDING_GCC_VERSION == 4006
+void debug_gimple_stmt(gimple);
+void debug_gimple_seq(gimple_seq);
+void print_gimple_seq(FILE *, gimple_seq, int, int);
+void print_gimple_stmt(FILE *, gimple, int, int);
+void print_gimple_expr(FILE *, gimple, int, int);
+void dump_gimple_stmt(pretty_printer *, gimple, int, int);
+#endif
+
+#ifndef __unused
+#define __unused __attribute__((__unused__))
+#endif
+#ifndef __visible
+#define __visible __attribute__((visibility("default")))
+#endif
+
+#define DECL_NAME_POINTER(node) IDENTIFIER_POINTER(DECL_NAME(node))
+#define DECL_NAME_LENGTH(node) IDENTIFIER_LENGTH(DECL_NAME(node))
+#define TYPE_NAME_POINTER(node) IDENTIFIER_POINTER(TYPE_NAME(node))
+#define TYPE_NAME_LENGTH(node) IDENTIFIER_LENGTH(TYPE_NAME(node))
+
+#if BUILDING_GCC_VERSION < 9000
+/* should come from c-tree.h if only it were installed for gcc 4.5... */
+#define C_TYPE_FIELDS_READONLY(TYPE) TREE_LANG_FLAG_1(TYPE)
+#endif
+
+static inline tree build_const_char_string(int len, const char *str)
+{
+ tree cstr, elem, index, type;
+
+ cstr = build_string(len, str);
+ elem = build_type_variant(char_type_node, 1, 0);
+ index = build_index_type(size_int(len - 1));
+ type = build_array_type(elem, index);
+ TREE_TYPE(cstr) = type;
+ TREE_CONSTANT(cstr) = 1;
+ TREE_READONLY(cstr) = 1;
+ TREE_STATIC(cstr) = 1;
+ return cstr;
+}
+
+#define PASS_INFO(NAME, REF, ID, POS) \
+struct register_pass_info NAME##_pass_info = { \
+ .pass = make_##NAME##_pass(), \
+ .reference_pass_name = REF, \
+ .ref_pass_instance_number = ID, \
+ .pos_op = POS, \
+}
+
+#if BUILDING_GCC_VERSION == 4005
+#define FOR_EACH_LOCAL_DECL(FUN, I, D) \
+ for (tree vars = (FUN)->local_decls, (I) = 0; \
+ vars && ((D) = TREE_VALUE(vars)); \
+ vars = TREE_CHAIN(vars), (I)++)
+#define DECL_CHAIN(NODE) (TREE_CHAIN(DECL_MINIMAL_CHECK(NODE)))
+#define FOR_EACH_VEC_ELT(T, V, I, P) \
+ for (I = 0; VEC_iterate(T, (V), (I), (P)); ++(I))
+#define TODO_rebuild_cgraph_edges 0
+#define SCOPE_FILE_SCOPE_P(EXP) (!(EXP))
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+typedef struct varpool_node *varpool_node_ptr;
+
+static inline bool gimple_call_builtin_p(gimple stmt, enum built_in_function code)
+{
+ tree fndecl;
+
+ if (!is_gimple_call(stmt))
+ return false;
+ fndecl = gimple_call_fndecl(stmt);
+ if (!fndecl || DECL_BUILT_IN_CLASS(fndecl) != BUILT_IN_NORMAL)
+ return false;
+ return DECL_FUNCTION_CODE(fndecl) == code;
+}
+
+static inline bool is_simple_builtin(tree decl)
+{
+ if (decl && DECL_BUILT_IN_CLASS(decl) != BUILT_IN_NORMAL)
+ return false;
+
+ switch (DECL_FUNCTION_CODE(decl)) {
+ /* Builtins that expand to constants. */
+ case BUILT_IN_CONSTANT_P:
+ case BUILT_IN_EXPECT:
+ case BUILT_IN_OBJECT_SIZE:
+ case BUILT_IN_UNREACHABLE:
+ /* Simple register moves or loads from stack. */
+ case BUILT_IN_RETURN_ADDRESS:
+ case BUILT_IN_EXTRACT_RETURN_ADDR:
+ case BUILT_IN_FROB_RETURN_ADDR:
+ case BUILT_IN_RETURN:
+ case BUILT_IN_AGGREGATE_INCOMING_ADDRESS:
+ case BUILT_IN_FRAME_ADDRESS:
+ case BUILT_IN_VA_END:
+ case BUILT_IN_STACK_SAVE:
+ case BUILT_IN_STACK_RESTORE:
+ /* Exception state returns or moves registers around. */
+ case BUILT_IN_EH_FILTER:
+ case BUILT_IN_EH_POINTER:
+ case BUILT_IN_EH_COPY_VALUES:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static inline void add_local_decl(struct function *fun, tree d)
+{
+ gcc_assert(TREE_CODE(d) == VAR_DECL);
+ fun->local_decls = tree_cons(NULL_TREE, d, fun->local_decls);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4006
+#define ANY_RETURN_P(rtx) (GET_CODE(rtx) == RETURN)
+#define C_DECL_REGISTER(EXP) DECL_LANG_FLAG_4(EXP)
+#define EDGE_PRESERVE 0ULL
+#define HOST_WIDE_INT_PRINT_HEX_PURE "%" HOST_WIDE_INT_PRINT "x"
+#define flag_fat_lto_objects true
+
+#define get_random_seed(noinit) ({ \
+ unsigned HOST_WIDE_INT seed; \
+ sscanf(get_random_seed(noinit), "%" HOST_WIDE_INT_PRINT "x", &seed); \
+ seed * seed; })
+
+#define int_const_binop(code, arg1, arg2) \
+ int_const_binop((code), (arg1), (arg2), 0)
+
+static inline bool gimple_clobber_p(gimple s __unused)
+{
+ return false;
+}
+
+static inline bool gimple_asm_clobbers_memory_p(const_gimple stmt)
+{
+ unsigned i;
+
+ for (i = 0; i < gimple_asm_nclobbers(stmt); i++) {
+ tree op = gimple_asm_clobber_op(stmt, i);
+
+ if (!strcmp(TREE_STRING_POINTER(TREE_VALUE(op)), "memory"))
+ return true;
+ }
+
+ return false;
+}
+
+static inline tree builtin_decl_implicit(enum built_in_function fncode)
+{
+ return implicit_built_in_decls[fncode];
+}
+
+static inline int ipa_reverse_postorder(struct cgraph_node **order)
+{
+ return cgraph_postorder(order);
+}
+
+static inline struct cgraph_node *cgraph_create_node(tree decl)
+{
+ return cgraph_node(decl);
+}
+
+static inline struct cgraph_node *cgraph_get_create_node(tree decl)
+{
+ struct cgraph_node *node = cgraph_get_node(decl);
+
+ return node ? node : cgraph_node(decl);
+}
+
+static inline bool cgraph_function_with_gimple_body_p(struct cgraph_node *node)
+{
+ return node->analyzed && !node->thunk.thunk_p && !node->alias;
+}
+
+static inline struct cgraph_node *cgraph_first_function_with_gimple_body(void)
+{
+ struct cgraph_node *node;
+
+ for (node = cgraph_nodes; node; node = node->next)
+ if (cgraph_function_with_gimple_body_p(node))
+ return node;
+ return NULL;
+}
+
+static inline struct cgraph_node *cgraph_next_function_with_gimple_body(struct cgraph_node *node)
+{
+ for (node = node->next; node; node = node->next)
+ if (cgraph_function_with_gimple_body_p(node))
+ return node;
+ return NULL;
+}
+
+static inline bool cgraph_for_node_and_aliases(cgraph_node_ptr node, bool (*callback)(cgraph_node_ptr, void *), void *data, bool include_overwritable)
+{
+ cgraph_node_ptr alias;
+
+ if (callback(node, data))
+ return true;
+
+ for (alias = node->same_body; alias; alias = alias->next) {
+ if (include_overwritable || cgraph_function_body_availability(alias) > AVAIL_OVERWRITABLE)
+ if (cgraph_for_node_and_aliases(alias, callback, data, include_overwritable))
+ return true;
+ }
+
+ return false;
+}
+
+#define FOR_EACH_FUNCTION_WITH_GIMPLE_BODY(node) \
+ for ((node) = cgraph_first_function_with_gimple_body(); (node); \
+ (node) = cgraph_next_function_with_gimple_body(node))
+
+static inline void varpool_add_new_variable(tree decl)
+{
+ varpool_finalize_decl(decl);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4007
+#define FOR_EACH_FUNCTION(node) \
+ for (node = cgraph_nodes; node; node = node->next)
+#define FOR_EACH_VARIABLE(node) \
+ for (node = varpool_nodes; node; node = node->next)
+#define PROP_loops 0
+#define NODE_SYMBOL(node) (node)
+#define NODE_DECL(node) (node)->decl
+#define INSN_LOCATION(INSN) RTL_LOCATION(INSN)
+#define vNULL NULL
+
+static inline int bb_loop_depth(const_basic_block bb)
+{
+ return bb->loop_father ? loop_depth(bb->loop_father) : 0;
+}
+
+static inline bool gimple_store_p(gimple gs)
+{
+ tree lhs = gimple_get_lhs(gs);
+
+ return lhs && !is_gimple_reg(lhs);
+}
+
+static inline void gimple_init_singleton(gimple g __unused)
+{
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4007 || BUILDING_GCC_VERSION == 4008
+static inline struct cgraph_node *cgraph_alias_target(struct cgraph_node *n)
+{
+ return cgraph_alias_aliased_node(n);
+}
+#endif
+
+#if BUILDING_GCC_VERSION <= 4008
+#define ENTRY_BLOCK_PTR_FOR_FN(FN) ENTRY_BLOCK_PTR_FOR_FUNCTION(FN)
+#define EXIT_BLOCK_PTR_FOR_FN(FN) EXIT_BLOCK_PTR_FOR_FUNCTION(FN)
+#define basic_block_info_for_fn(FN) ((FN)->cfg->x_basic_block_info)
+#define n_basic_blocks_for_fn(FN) ((FN)->cfg->x_n_basic_blocks)
+#define n_edges_for_fn(FN) ((FN)->cfg->x_n_edges)
+#define last_basic_block_for_fn(FN) ((FN)->cfg->x_last_basic_block)
+#define label_to_block_map_for_fn(FN) ((FN)->cfg->x_label_to_block_map)
+#define profile_status_for_fn(FN) ((FN)->cfg->x_profile_status)
+#define BASIC_BLOCK_FOR_FN(FN, N) BASIC_BLOCK_FOR_FUNCTION((FN), (N))
+#define NODE_IMPLICIT_ALIAS(node) (node)->same_body_alias
+#define VAR_P(NODE) (TREE_CODE(NODE) == VAR_DECL)
+
+static inline bool tree_fits_shwi_p(const_tree t)
+{
+ if (t == NULL_TREE || TREE_CODE(t) != INTEGER_CST)
+ return false;
+
+ if (TREE_INT_CST_HIGH(t) == 0 && (HOST_WIDE_INT)TREE_INT_CST_LOW(t) >= 0)
+ return true;
+
+ if (TREE_INT_CST_HIGH(t) == -1 && (HOST_WIDE_INT)TREE_INT_CST_LOW(t) < 0 && !TYPE_UNSIGNED(TREE_TYPE(t)))
+ return true;
+
+ return false;
+}
+
+static inline bool tree_fits_uhwi_p(const_tree t)
+{
+ if (t == NULL_TREE || TREE_CODE(t) != INTEGER_CST)
+ return false;
+
+ return TREE_INT_CST_HIGH(t) == 0;
+}
+
+static inline HOST_WIDE_INT tree_to_shwi(const_tree t)
+{
+ gcc_assert(tree_fits_shwi_p(t));
+ return TREE_INT_CST_LOW(t);
+}
+
+static inline unsigned HOST_WIDE_INT tree_to_uhwi(const_tree t)
+{
+ gcc_assert(tree_fits_uhwi_p(t));
+ return TREE_INT_CST_LOW(t);
+}
+
+static inline const char *get_tree_code_name(enum tree_code code)
+{
+ gcc_assert(code < MAX_TREE_CODES);
+ return tree_code_name[code];
+}
+
+#define ipa_remove_stmt_references(cnode, stmt)
+
+typedef union gimple_statement_d gasm;
+typedef union gimple_statement_d gassign;
+typedef union gimple_statement_d gcall;
+typedef union gimple_statement_d gcond;
+typedef union gimple_statement_d gdebug;
+typedef union gimple_statement_d ggoto;
+typedef union gimple_statement_d gphi;
+typedef union gimple_statement_d greturn;
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcond *as_a_gcond(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcond *as_a_const_gcond(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gdebug *as_a_gdebug(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gdebug *as_a_const_gdebug(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return stmt;
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4008
+#define NODE_SYMBOL(node) (&(node)->symbol)
+#define NODE_DECL(node) (node)->symbol.decl
+#endif
+
+#if BUILDING_GCC_VERSION >= 4008
+#define add_referenced_var(var)
+#define mark_sym_for_renaming(var)
+#define varpool_mark_needed_node(node)
+#define create_var_ann(var)
+#define TODO_dump_func 0
+#define TODO_dump_cgraph 0
+#endif
+
+#if BUILDING_GCC_VERSION <= 4009
+#define TODO_verify_il 0
+#define AVAIL_INTERPOSABLE AVAIL_OVERWRITABLE
+
+#define section_name_prefix LTO_SECTION_NAME_PREFIX
+#define fatal_error(loc, gmsgid, ...) fatal_error((gmsgid), __VA_ARGS__)
+
+rtx emit_move_insn(rtx x, rtx y);
+
+typedef struct rtx_def rtx_insn;
+
+static inline const char *get_decl_section_name(const_tree decl)
+{
+ if (DECL_SECTION_NAME(decl) == NULL_TREE)
+ return NULL;
+
+ return TREE_STRING_POINTER(DECL_SECTION_NAME(decl));
+}
+
+static inline void set_decl_section_name(tree node, const char *value)
+{
+ if (value)
+ DECL_SECTION_NAME(node) = build_string(strlen(value) + 1, value);
+ else
+ DECL_SECTION_NAME(node) = NULL;
+}
+#endif
+
+#if BUILDING_GCC_VERSION == 4009
+typedef struct gimple_statement_asm gasm;
+typedef struct gimple_statement_base gassign;
+typedef struct gimple_statement_call gcall;
+typedef struct gimple_statement_base gcond;
+typedef struct gimple_statement_base gdebug;
+typedef struct gimple_statement_base ggoto;
+typedef struct gimple_statement_phi gphi;
+typedef struct gimple_statement_base greturn;
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return as_a<gasm>(stmt);
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return as_a<const gasm>(stmt);
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return as_a<gcall>(stmt);
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return as_a<const gcall>(stmt);
+}
+
+static inline gcond *as_a_gcond(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gcond *as_a_const_gcond(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gdebug *as_a_gdebug(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const gdebug *as_a_const_gdebug(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return stmt;
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return as_a<gphi>(stmt);
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return as_a<const gphi>(stmt);
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return stmt;
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return stmt;
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 4009
+#define TODO_ggc_collect 0
+#define NODE_SYMBOL(node) (node)
+#define NODE_DECL(node) (node)->decl
+#define cgraph_node_name(node) (node)->name()
+#define NODE_IMPLICIT_ALIAS(node) (node)->cpp_implicit_alias
+
+static inline opt_pass *get_pass_for_id(int id)
+{
+ return g->get_passes()->get_pass_for_id(id);
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000 && BUILDING_GCC_VERSION < 6000
+/* gimple related */
+template <>
+template <>
+inline bool is_a_helper<const gassign *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_ASSIGN;
+}
+#endif
+
+#if BUILDING_GCC_VERSION >= 5000
+#define TODO_verify_ssa TODO_verify_il
+#define TODO_verify_flow TODO_verify_il
+#define TODO_verify_stmts TODO_verify_il
+#define TODO_verify_rtl_sharing TODO_verify_il
+
+#define INSN_DELETED_P(insn) (insn)->deleted()
+
+static inline const char *get_decl_section_name(const_tree decl)
+{
+ return DECL_SECTION_NAME(decl);
+}
+
+/* symtab/cgraph related */
+#define debug_cgraph_node(node) (node)->debug()
+#define cgraph_get_node(decl) cgraph_node::get(decl)
+#define cgraph_get_create_node(decl) cgraph_node::get_create(decl)
+#define cgraph_create_node(decl) cgraph_node::create(decl)
+#define cgraph_n_nodes symtab->cgraph_count
+#define cgraph_max_uid symtab->cgraph_max_uid
+#define varpool_get_node(decl) varpool_node::get(decl)
+#define dump_varpool_node(file, node) (node)->dump(file)
+
+#if BUILDING_GCC_VERSION >= 8000
+#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \
+ (caller)->create_edge((callee), (call_stmt), (count))
+
+#define cgraph_create_edge_including_clones(caller, callee, \
+ old_call_stmt, call_stmt, count, freq, reason) \
+ (caller)->create_edge_including_clones((callee), \
+ (old_call_stmt), (call_stmt), (count), (reason))
+#else
+#define cgraph_create_edge(caller, callee, call_stmt, count, freq) \
+ (caller)->create_edge((callee), (call_stmt), (count), (freq))
+
+#define cgraph_create_edge_including_clones(caller, callee, \
+ old_call_stmt, call_stmt, count, freq, reason) \
+ (caller)->create_edge_including_clones((callee), \
+ (old_call_stmt), (call_stmt), (count), (freq), (reason))
+#endif
+
+typedef struct cgraph_node *cgraph_node_ptr;
+typedef struct cgraph_edge *cgraph_edge_p;
+typedef struct varpool_node *varpool_node_ptr;
+
+static inline void change_decl_assembler_name(tree decl, tree name)
+{
+ symtab->change_decl_assembler_name(decl, name);
+}
+
+static inline void varpool_finalize_decl(tree decl)
+{
+ varpool_node::finalize_decl(decl);
+}
+
+static inline void varpool_add_new_variable(tree decl)
+{
+ varpool_node::add(decl);
+}
+
+static inline unsigned int rebuild_cgraph_edges(void)
+{
+ return cgraph_edge::rebuild_edges();
+}
+
+static inline cgraph_node_ptr cgraph_function_node(cgraph_node_ptr node, enum availability *availability)
+{
+ return node->function_symbol(availability);
+}
+
+static inline cgraph_node_ptr cgraph_function_or_thunk_node(cgraph_node_ptr node, enum availability *availability = NULL)
+{
+ return node->ultimate_alias_target(availability);
+}
+
+static inline bool cgraph_only_called_directly_p(cgraph_node_ptr node)
+{
+ return node->only_called_directly_p();
+}
+
+static inline enum availability cgraph_function_body_availability(cgraph_node_ptr node)
+{
+ return node->get_availability();
+}
+
+static inline cgraph_node_ptr cgraph_alias_target(cgraph_node_ptr node)
+{
+ return node->get_alias_target();
+}
+
+static inline bool cgraph_for_node_and_aliases(cgraph_node_ptr node, bool (*callback)(cgraph_node_ptr, void *), void *data, bool include_overwritable)
+{
+ return node->call_for_symbol_thunks_and_aliases(callback, data, include_overwritable);
+}
+
+static inline struct cgraph_node_hook_list *cgraph_add_function_insertion_hook(cgraph_node_hook hook, void *data)
+{
+ return symtab->add_cgraph_insertion_hook(hook, data);
+}
+
+static inline void cgraph_remove_function_insertion_hook(struct cgraph_node_hook_list *entry)
+{
+ symtab->remove_cgraph_insertion_hook(entry);
+}
+
+static inline struct cgraph_node_hook_list *cgraph_add_node_removal_hook(cgraph_node_hook hook, void *data)
+{
+ return symtab->add_cgraph_removal_hook(hook, data);
+}
+
+static inline void cgraph_remove_node_removal_hook(struct cgraph_node_hook_list *entry)
+{
+ symtab->remove_cgraph_removal_hook(entry);
+}
+
+static inline struct cgraph_2node_hook_list *cgraph_add_node_duplication_hook(cgraph_2node_hook hook, void *data)
+{
+ return symtab->add_cgraph_duplication_hook(hook, data);
+}
+
+static inline void cgraph_remove_node_duplication_hook(struct cgraph_2node_hook_list *entry)
+{
+ symtab->remove_cgraph_duplication_hook(entry);
+}
+
+static inline void cgraph_call_node_duplication_hooks(cgraph_node_ptr node, cgraph_node_ptr node2)
+{
+ symtab->call_cgraph_duplication_hooks(node, node2);
+}
+
+static inline void cgraph_call_edge_duplication_hooks(cgraph_edge *cs1, cgraph_edge *cs2)
+{
+ symtab->call_edge_duplication_hooks(cs1, cs2);
+}
+
+#if BUILDING_GCC_VERSION >= 6000
+typedef gimple *gimple_ptr;
+typedef const gimple *const_gimple_ptr;
+#define gimple gimple_ptr
+#define const_gimple const_gimple_ptr
+#undef CONST_CAST_GIMPLE
+#define CONST_CAST_GIMPLE(X) CONST_CAST(gimple, (X))
+#endif
+
+/* gimple related */
+static inline gimple gimple_build_assign_with_ops(enum tree_code subcode, tree lhs, tree op1, tree op2 MEM_STAT_DECL)
+{
+ return gimple_build_assign(lhs, subcode, op1, op2 PASS_MEM_STAT);
+}
+
+#if BUILDING_GCC_VERSION < 10000
+template <>
+template <>
+inline bool is_a_helper<const ggoto *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_GOTO;
+}
+
+template <>
+template <>
+inline bool is_a_helper<const greturn *>::test(const_gimple gs)
+{
+ return gs->code == GIMPLE_RETURN;
+}
+#endif
+
+static inline gasm *as_a_gasm(gimple stmt)
+{
+ return as_a<gasm *>(stmt);
+}
+
+static inline const gasm *as_a_const_gasm(const_gimple stmt)
+{
+ return as_a<const gasm *>(stmt);
+}
+
+static inline gassign *as_a_gassign(gimple stmt)
+{
+ return as_a<gassign *>(stmt);
+}
+
+static inline const gassign *as_a_const_gassign(const_gimple stmt)
+{
+ return as_a<const gassign *>(stmt);
+}
+
+static inline gcall *as_a_gcall(gimple stmt)
+{
+ return as_a<gcall *>(stmt);
+}
+
+static inline const gcall *as_a_const_gcall(const_gimple stmt)
+{
+ return as_a<const gcall *>(stmt);
+}
+
+static inline ggoto *as_a_ggoto(gimple stmt)
+{
+ return as_a<ggoto *>(stmt);
+}
+
+static inline const ggoto *as_a_const_ggoto(const_gimple stmt)
+{
+ return as_a<const ggoto *>(stmt);
+}
+
+static inline gphi *as_a_gphi(gimple stmt)
+{
+ return as_a<gphi *>(stmt);
+}
+
+static inline const gphi *as_a_const_gphi(const_gimple stmt)
+{
+ return as_a<const gphi *>(stmt);
+}
+
+static inline greturn *as_a_greturn(gimple stmt)
+{
+ return as_a<greturn *>(stmt);
+}
+
+static inline const greturn *as_a_const_greturn(const_gimple stmt)
+{
+ return as_a<const greturn *>(stmt);
+}
+
+/* IPA/LTO related */
+#define ipa_ref_list_referring_iterate(L, I, P) \
+ (L)->referring.iterate((I), &(P))
+#define ipa_ref_list_reference_iterate(L, I, P) \
+ (L)->reference.iterate((I), &(P))
+
+static inline cgraph_node_ptr ipa_ref_referring_node(struct ipa_ref *ref)
+{
+ return dyn_cast<cgraph_node_ptr>(ref->referring);
+}
+
+static inline void ipa_remove_stmt_references(symtab_node *referring_node, gimple stmt)
+{
+ referring_node->remove_stmt_references(stmt);
+}
+#endif
+
+#if BUILDING_GCC_VERSION < 6000
+#define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \
+ get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, pvolatilep, keep_aligning)
+#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET(VOIDmode, (ARG0), (ARG1))
+#endif
+
+#if BUILDING_GCC_VERSION >= 6000
+#define gen_rtx_set(ARG0, ARG1) gen_rtx_SET((ARG0), (ARG1))
+#endif
+
+#ifdef __cplusplus
+static inline void debug_tree(const_tree t)
+{
+ debug_tree(CONST_CAST_TREE(t));
+}
+
+static inline void debug_gimple_stmt(const_gimple s)
+{
+ debug_gimple_stmt(CONST_CAST_GIMPLE(s));
+}
+#else
+#define debug_tree(t) debug_tree(CONST_CAST_TREE(t))
+#define debug_gimple_stmt(s) debug_gimple_stmt(CONST_CAST_GIMPLE(s))
+#endif
+
+#if BUILDING_GCC_VERSION >= 7000
+#define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning) \
+ get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep)
+#endif
+
+#if BUILDING_GCC_VERSION < 7000
+#define SET_DECL_ALIGN(decl, align) DECL_ALIGN(decl) = (align)
+#define SET_DECL_MODE(decl, mode) DECL_MODE(decl) = (mode)
+#endif
+
+#if BUILDING_GCC_VERSION < 12000
+#define check_function_arguments_recurse(arg, ctx, tree, num, opt) \
+ check_function_arguments_recurse(arg, ctx, tree, num)
+#endif
+
+#endif
diff --git a/tools/gcc-plugins/gcc-retain-typeinfo.patch b/tools/gcc-plugins/gcc-retain-typeinfo.patch
new file mode 100644
index 0000000..ec51f0b
--- /dev/null
+++ b/tools/gcc-plugins/gcc-retain-typeinfo.patch
@@ -0,0 +1,11 @@
+--- a/src/gcc/c/c-typeck.c
++++ b/src/gcc/c/c-typeck.c
+@@ -5716,8 +5716,6 @@ build_c_cast (location_t loc, tree type, tree expr)
+ if (objc_is_object_ptr (type) && objc_is_object_ptr (TREE_TYPE (expr)))
+ return build1 (NOP_EXPR, type, expr);
+
+- type = TYPE_MAIN_VARIANT (type);
+-
+ if (TREE_CODE (type) == ARRAY_TYPE)
+ {
+ error_at (loc, "cast specifies array type");