[3/3] diagnostics: use the .opt.urls files to urlify quoted text

Message ID 20231110214246.3087291-4-dmalcolm@redhat.com
State Unresolved
Headers
Series Option handling: add documentation URLs |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

David Malcolm Nov. 10, 2023, 9:42 p.m. UTC
  This patch adds machinery for using the .opt.urls files linking
to the documentation of our options in gcc_urlifier.

For every enabled .opt file, the corresponding .opt.urls file
will also be used when constructing the "optionslist" file.
The patch adds a new awk script to process the optionslist file,
options-urls-cc-gen.awk, which generates a options-urls.cc file,
containing a big array of const char * of the form:

const char * const opt_url_suffixes[] =
{
 [...snip...]

 /* [563] (OPT_Wclass_memaccess) = */
    "gcc/C_002b_002b-Dialect-Options.html#index-Wclass-memaccess",
 /* [564] (OPT_Wclobbered) = */
    "gcc/Warning-Options.html#index-Wclobbered",

[...snip...]
};

The patch wires up gcc_urlifier so that for quoted strings beginning
with '-' it will look up the option, and, if found, build a URL
using one of the above suffixes.

For example, given:

  ./xgcc -B. -S t.c -Wctad-maybe-unsupported
  cc1: warning: command-line option ‘-Wctad-maybe-unsupported’ is valid for C++/ObjC++ but not for C

the quoted string -Wctad-maybe-unsupported is automatically URLified in
my terminal to:
	https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-Wctad-maybe-unsupported

This approach seems to allow us to get URLs automatically from the
documentation, whilst:
- integrating with the existing .opt mechanisms but keeping
autogenerated material (.opt.urls) separate from human-maintained
files (.opt)
- not adding any build-time requirements (by using awk at build time)
- only requiring Python 3 when regenerating the in-tree opt.urls files,
when the .texi or .opt files change enough to warrant it

gcc/ChangeLog:
	* Makefile.in (ALL_OPT_URL_FILES): New.
	(GCC_OBJS): Add options-urls.o.
	(OBJS): Likewise.
	(OBJS-libcommon): Likewise.
	(s-options): Depend on $(ALL_OPT_URL_FILES), and add this to
	inputs to opt-gather.awk.
	(options-urls.cc): New Makefile target.
	* gcc-urlifier.cc: Include "opts.h" and "options.h".
	(gcc_urlifier::gcc_urlifier): Add lang_mask param.
	(gcc_urlifier::m_lang_mask): New field.
	(doc_urls): Make static.
	(gcc_urlifier::get_url_for_quoted_text): Use label_text.
	(gcc_urlifier::get_url_suffix_for_quoted_text): Use label_text.
	Look for an option by name before trying a binary search in
	doc_urls.
	(gcc_urlifier::get_url_suffix_for_quoted_text): Use label_text.
	(gcc_urlifier::get_url_suffix_for_option): New.
	(make_gcc_urlifier): Add lang_mask param.
	(selftest::gcc_urlifier_cc_tests): Update for above changes.
	Verify that a URL is found for "-fpack-struct".
	* gcc-urlifier.def: Drop options "--version" and "-fpack-struct".
	* gcc-urlifier.h (make_gcc_urlifier): Add lang_mask param.
	* gcc.cc (driver::global_initializations): Pass 0 for lang_mask
	to make_gcc_urlifier.
	* opt-functions.awk (url_suffix): New function.
	* options-urls-cc-gen.awk: New file.
	* opts.cc (get_option_html_page): Remove special-casing for
	analyzer and LTO.
	(get_option_url_suffix): New.
	(get_option_url): Reimplement.
	(selftest::test_get_option_html_page): Rename to...
	(selftest::test_get_option_url_suffix): ...this and update for
	above changes.
	(selftest::opts_cc_tests): Update for renaming.
	* opts.h (opt_url_suffixes): New decl.
	(get_option_url_suffix): New decl.

gcc/testsuite/ChangeLog:
	* lib/gcc-dg.exp: Set TERM to xterm.

gcc/ChangeLog:
	* toplev.cc (general_init): Pass global_dc->m_lang_mask to
	make_gcc_urlifier.
---
 gcc/Makefile.in              |  18 ++++--
 gcc/gcc-urlifier.cc          | 106 ++++++++++++++++++++++++++++-------
 gcc/gcc-urlifier.def         |   2 -
 gcc/gcc-urlifier.h           |   2 +-
 gcc/gcc.cc                   |   2 +-
 gcc/opt-functions.awk        |   7 +++
 gcc/options-urls-cc-gen.awk  |  79 ++++++++++++++++++++++++++
 gcc/opts.cc                  |  75 ++++++++++++++-----------
 gcc/opts.h                   |   4 ++
 gcc/testsuite/lib/gcc-dg.exp |   6 ++
 gcc/toplev.cc                |   2 +-
 11 files changed, 242 insertions(+), 61 deletions(-)
 create mode 100644 gcc/options-urls-cc-gen.awk
  

Comments

Arthur Cohen Nov. 14, 2023, 5:44 p.m. UTC | #1
Hi David,

ACK for the Rust patches. Thanks a lot for working on this :)

Kindly,

Arthur

On 11/10/23 22:42, David Malcolm wrote:
> This patch adds machinery for using the .opt.urls files linking
> to the documentation of our options in gcc_urlifier.
> 
> For every enabled .opt file, the corresponding .opt.urls file
> will also be used when constructing the "optionslist" file.
> The patch adds a new awk script to process the optionslist file,
> options-urls-cc-gen.awk, which generates a options-urls.cc file,
> containing a big array of const char * of the form:
> 
> const char * const opt_url_suffixes[] =
> {
>   [...snip...]
> 
>   /* [563] (OPT_Wclass_memaccess) = */
>      "gcc/C_002b_002b-Dialect-Options.html#index-Wclass-memaccess",
>   /* [564] (OPT_Wclobbered) = */
>      "gcc/Warning-Options.html#index-Wclobbered",
> 
> [...snip...]
> };
> 
> The patch wires up gcc_urlifier so that for quoted strings beginning
> with '-' it will look up the option, and, if found, build a URL
> using one of the above suffixes.
> 
> For example, given:
> 
>    ./xgcc -B. -S t.c -Wctad-maybe-unsupported
>    cc1: warning: command-line option ‘-Wctad-maybe-unsupported’ is valid for C++/ObjC++ but not for C
> 
> the quoted string -Wctad-maybe-unsupported is automatically URLified in
> my terminal to:
> 	https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-Wctad-maybe-unsupported
> 
> This approach seems to allow us to get URLs automatically from the
> documentation, whilst:
> - integrating with the existing .opt mechanisms but keeping
> autogenerated material (.opt.urls) separate from human-maintained
> files (.opt)
> - not adding any build-time requirements (by using awk at build time)
> - only requiring Python 3 when regenerating the in-tree opt.urls files,
> when the .texi or .opt files change enough to warrant it
> 
> gcc/ChangeLog:
> 	* Makefile.in (ALL_OPT_URL_FILES): New.
> 	(GCC_OBJS): Add options-urls.o.
> 	(OBJS): Likewise.
> 	(OBJS-libcommon): Likewise.
> 	(s-options): Depend on $(ALL_OPT_URL_FILES), and add this to
> 	inputs to opt-gather.awk.
> 	(options-urls.cc): New Makefile target.
> 	* gcc-urlifier.cc: Include "opts.h" and "options.h".
> 	(gcc_urlifier::gcc_urlifier): Add lang_mask param.
> 	(gcc_urlifier::m_lang_mask): New field.
> 	(doc_urls): Make static.
> 	(gcc_urlifier::get_url_for_quoted_text): Use label_text.
> 	(gcc_urlifier::get_url_suffix_for_quoted_text): Use label_text.
> 	Look for an option by name before trying a binary search in
> 	doc_urls.
> 	(gcc_urlifier::get_url_suffix_for_quoted_text): Use label_text.
> 	(gcc_urlifier::get_url_suffix_for_option): New.
> 	(make_gcc_urlifier): Add lang_mask param.
> 	(selftest::gcc_urlifier_cc_tests): Update for above changes.
> 	Verify that a URL is found for "-fpack-struct".
> 	* gcc-urlifier.def: Drop options "--version" and "-fpack-struct".
> 	* gcc-urlifier.h (make_gcc_urlifier): Add lang_mask param.
> 	* gcc.cc (driver::global_initializations): Pass 0 for lang_mask
> 	to make_gcc_urlifier.
> 	* opt-functions.awk (url_suffix): New function.
> 	* options-urls-cc-gen.awk: New file.
> 	* opts.cc (get_option_html_page): Remove special-casing for
> 	analyzer and LTO.
> 	(get_option_url_suffix): New.
> 	(get_option_url): Reimplement.
> 	(selftest::test_get_option_html_page): Rename to...
> 	(selftest::test_get_option_url_suffix): ...this and update for
> 	above changes.
> 	(selftest::opts_cc_tests): Update for renaming.
> 	* opts.h (opt_url_suffixes): New decl.
> 	(get_option_url_suffix): New decl.
> 
> gcc/testsuite/ChangeLog:
> 	* lib/gcc-dg.exp: Set TERM to xterm.
> 
> gcc/ChangeLog:
> 	* toplev.cc (general_init): Pass global_dc->m_lang_mask to
> 	make_gcc_urlifier.
> ---
>   gcc/Makefile.in              |  18 ++++--
>   gcc/gcc-urlifier.cc          | 106 ++++++++++++++++++++++++++++-------
>   gcc/gcc-urlifier.def         |   2 -
>   gcc/gcc-urlifier.h           |   2 +-
>   gcc/gcc.cc                   |   2 +-
>   gcc/opt-functions.awk        |   7 +++
>   gcc/options-urls-cc-gen.awk  |  79 ++++++++++++++++++++++++++
>   gcc/opts.cc                  |  75 ++++++++++++++-----------
>   gcc/opts.h                   |   4 ++
>   gcc/testsuite/lib/gcc-dg.exp |   6 ++
>   gcc/toplev.cc                |   2 +-
>   11 files changed, 242 insertions(+), 61 deletions(-)
>   create mode 100644 gcc/options-urls-cc-gen.awk
> 
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index 29cec21c8258..ebb59680d69b 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -1270,6 +1270,8 @@ FLAGS_TO_PASS = \
>   # All option source files
>   ALL_OPT_FILES=$(lang_opt_files) $(extra_opt_files)
>   
> +ALL_OPT_URL_FILES=$(patsubst %, %.urls, $(ALL_OPT_FILES))
> +
>   # Target specific, C specific object file
>   C_TARGET_OBJS=@c_target_objs@
>   
> @@ -1286,7 +1288,7 @@ FORTRAN_TARGET_OBJS=@fortran_target_objs@
>   RUST_TARGET_OBJS=@rust_target_objs@
>   
>   # Object files for gcc many-languages driver.
> -GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o
> +GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o options-urls.o
>   
>   c-family-warn = $(STRICT_WARN)
>   
> @@ -1606,6 +1608,7 @@ OBJS = \
>   	optinfo.o \
>   	optinfo-emit-json.o \
>   	options-save.o \
> +	options-urls.o \
>   	opts-global.o \
>   	ordered-hash-map-tests.o \
>   	passes.o \
> @@ -1832,7 +1835,8 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
>   # compiler and containing target-dependent code.
>   OBJS-libcommon-target = $(common_out_object_file) prefix.o \
>   	opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \
> -	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o
> +	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o \
> +	options-urls.o
>   
>   # This lists all host objects for the front ends.
>   ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANGUAGES),$($(v)_OBJS))
> @@ -2435,9 +2439,9 @@ s-specs : Makefile
>   	$(STAMP) s-specs
>   
>   optionlist: s-options ; @true
> -s-options: $(ALL_OPT_FILES) Makefile $(srcdir)/opt-gather.awk
> +s-options: $(ALL_OPT_FILES) $(ALL_OPT_URL_FILES) Makefile $(srcdir)/opt-gather.awk
>   	LC_ALL=C ; export LC_ALL ; \
> -	$(AWK) -f $(srcdir)/opt-gather.awk $(ALL_OPT_FILES) > tmp-optionlist
> +	$(AWK) -f $(srcdir)/opt-gather.awk $(ALL_OPT_FILES) $(ALL_OPT_URL_FILES) > tmp-optionlist
>   	$(SHELL) $(srcdir)/../move-if-change tmp-optionlist optionlist
>   	$(STAMP) s-options
>   
> @@ -2453,6 +2457,12 @@ options-save.cc: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
>   	       -f $(srcdir)/optc-save-gen.awk \
>   	       -v header_name="config.h system.h coretypes.h tm.h" < $< > $@
>   
> +options-urls.cc: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
> +    $(srcdir)/options-urls-cc-gen.awk
> +	$(AWK) -f $(srcdir)/opt-functions.awk -f $(srcdir)/opt-read.awk \
> +	       -f $(srcdir)/options-urls-cc-gen.awk \
> +	       -v header_name="config.h system.h coretypes.h tm.h" < $< > $@
> +
>   options.h: s-options-h ; @true
>   s-options-h: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
>       $(srcdir)/opth-gen.awk
> diff --git a/gcc/gcc-urlifier.cc b/gcc/gcc-urlifier.cc
> index 0dbff9853132..da704bf814ad 100644
> --- a/gcc/gcc-urlifier.cc
> +++ b/gcc/gcc-urlifier.cc
> @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
>   #include "pretty-print.h"
>   #include "pretty-print-urlifier.h"
>   #include "gcc-urlifier.h"
> +#include "opts.h"
> +#include "options.h"
>   #include "selftest.h"
>   
>   namespace {
> @@ -34,23 +36,34 @@ namespace {
>   class gcc_urlifier : public urlifier
>   {
>   public:
> +  gcc_urlifier (unsigned int lang_mask)
> +  : m_lang_mask (lang_mask)
> +  {}
> +
>     char *get_url_for_quoted_text (const char *p, size_t sz) const final override;
>   
> -  const char *get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
> +  label_text get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
>     /* We use ATTRIBUTE_UNUSED as this helper is called only from ASSERTs.  */
> -  const char *get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
> +  label_text get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
>   
>   private:
> +  label_text get_url_suffix_for_option (const char *p, size_t sz) const;
> +
>     static char *
>     make_doc_url (const char *doc_url_suffix);
> +
> +  unsigned int m_lang_mask;
>   };
>   
>   /* class gcc_urlifier : public urlifier.  */
>   
> +/* Manage a hard-coded mapping from quoted string to URL suffixes
> +   in gcc-urlifier.def  */
> +
>   #define DOC_URL(QUOTED_TEXT, URL_SUFFIX) \
>     { (QUOTED_TEXT), (URL_SUFFIX) }
>   
> -const struct
> +static const struct
>   {
>     const char *quoted_text;
>     const char *url_suffix;
> @@ -60,32 +73,53 @@ const struct
>   
>   };
>   
> +/* Implementation of urlifier::get_url_for_quoted_text vfunc for GCC
> +   diagnostics.  */
> +
>   char *
>   gcc_urlifier::get_url_for_quoted_text (const char *p, size_t sz) const
>   {
> -  if (const char *url_suffix = get_url_suffix_for_quoted_text (p, sz))
> -    return make_doc_url (url_suffix);
> +  label_text url_suffix = get_url_suffix_for_quoted_text (p, sz);
> +  if (url_suffix.get ())
> +    return make_doc_url (url_suffix.get ());
>     return nullptr;
>   }
>   
> -const char *
> +/* Look for a URL for the quoted string (P, SZ).
> +   Return the url suffix if found, or nullptr otherwise.  */
> +
> +label_text
>   gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
>   {
> -  /* Binary search.  This assumes that the quoted_text fields of doc_urls
> +  if (sz == 0)
> +    return label_text ();
> +
> +  /* If this is an option, look up the option and see if we have
> +     a URL for it.  */
> +  if (p[0] == '-')
> +    {
> +      label_text suffix = get_url_suffix_for_option (p, sz);
> +      if (suffix.get ())
> +	return suffix;
> +    }
> +
> +  /* Otherwise, look within the hard-coded data table in gcc-urlifier.def.
> +
> +     Binary search.  This assumes that the quoted_text fields of doc_urls
>        are in sorted order.  */
>     int min = 0;
>     int max = ARRAY_SIZE (doc_urls) - 1;
>     while (true)
>       {
>         if (min > max)
> -	return nullptr;
> +	return label_text ();
>         int midpoint = (min + max) / 2;
>         gcc_assert ((size_t)midpoint < ARRAY_SIZE (doc_urls));
>         int cmp = strncmp (p, doc_urls[midpoint].quoted_text, sz);
>         if (cmp == 0)
>   	{
>   	  if (doc_urls[midpoint].quoted_text[sz] == '\0')
> -	    return doc_urls[midpoint].url_suffix;
> +	    return label_text::borrow (doc_urls[midpoint].url_suffix);
>   	  else
>   	    max = midpoint - 1;
>   	}
> @@ -94,15 +128,45 @@ gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
>         else
>   	min = midpoint + 1;
>       }
> -  return nullptr;
> +
> +  /* Not found.  */
> +  return label_text ();
>   }
>   
> -const char *
> +/* For use in selftests.  */
> +
> +label_text
>   gcc_urlifier::get_url_suffix_for_quoted_text (const char *p) const
>   {
>     return get_url_suffix_for_quoted_text (p, strlen (p));
>   }
>   
> +/* Look for a URL for the quoted string (P, SZ) that appears to be
> +   an option.
> +   Return the url suffix if found, or nullptr otherwise.  */
> +
> +label_text
> +gcc_urlifier::get_url_suffix_for_option (const char *p, size_t sz) const
> +{
> +  /* Look up this option
> +
> +     find_opt does a binary search, taking a 0-terminated string,
> +     and skipping the leading '-'.
> +
> +     We have a (pointer,size) pair that doesn't necessarily have a
> +     terminator, so create a 0-terminated clone of the string.  */
> +  gcc_assert (sz > 0);
> +  char *tmp = xstrndup (p + 1, sz - 1); // skip the leading '-'
> +  size_t opt = find_opt (tmp, m_lang_mask);
> +  free (tmp);
> +
> +  if (opt >= N_OPTS)
> +    /* Option not recognized.  */
> +    return label_text ();
> +
> +  return get_option_url_suffix (opt);
> +}
> +
>   char *
>   gcc_urlifier::make_doc_url (const char *doc_url_suffix)
>   {
> @@ -115,9 +179,9 @@ gcc_urlifier::make_doc_url (const char *doc_url_suffix)
>   } // anonymous namespace
>   
>   urlifier *
> -make_gcc_urlifier ()
> +make_gcc_urlifier (unsigned int lang_mask)
>   {
> -  return new gcc_urlifier ();
> +  return new gcc_urlifier (lang_mask);
>   }
>   
>   #if CHECKING_P
> @@ -137,22 +201,26 @@ gcc_urlifier_cc_tests ()
>   			doc_urls[idx].quoted_text)
>   		< 0);
>   
> -  gcc_urlifier u;
> +  gcc_urlifier u (0);
>   
> -  ASSERT_EQ (u.get_url_suffix_for_quoted_text (""), nullptr);
> -  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")"), nullptr);
> +  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("").get (), nullptr);
> +  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")").get (), nullptr);
>   
> -  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message"),
> +  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message").get (),
>   		"gcc/Diagnostic-Pragmas.html");
>   
>     // Incomplete prefix of a quoted_text
> -  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess"), nullptr);
> +  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess").get (), nullptr);
>   
>     /* Check that every element is findable.  */
>     for (size_t idx = 0; idx < ARRAY_SIZE (doc_urls); idx++)
>       ASSERT_STREQ
> -      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text),
> +      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text).get (),
>          doc_urls[idx].url_suffix);
> +
> +  /* Check an option.  */
> +  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("-fpack-struct").get (),
> +		"gcc/Code-Gen-Options.html#index-fpack-struct");
>   }
>   
>   } // namespace selftest
> diff --git a/gcc/gcc-urlifier.def b/gcc/gcc-urlifier.def
> index 360de930e9ec..de6d9a3eb962 100644
> --- a/gcc/gcc-urlifier.def
> +++ b/gcc/gcc-urlifier.def
> @@ -16,5 +16,3 @@ DOC_URL ("#pragma pack", "gcc/Structure-Layout-Pragmas.html"),
>   DOC_URL ("#pragma redefine_extname", "gcc/Symbol-Renaming-Pragmas.html"),
>   DOC_URL ("#pragma scalar_storage_order", "gcc/Structure-Layout-Pragmas.html"),
>   DOC_URL ("#pragma weak", "gcc/Weak-Pragmas.html"),
> -DOC_URL ("--version", "gcc/Overall-Options.html#index-version"),
> -DOC_URL ("-fpack-struct", "gcc/Code-Gen-Options.html#index-fpack-struct"),
> diff --git a/gcc/gcc-urlifier.h b/gcc/gcc-urlifier.h
> index 614e1c64b94d..77eb13463928 100644
> --- a/gcc/gcc-urlifier.h
> +++ b/gcc/gcc-urlifier.h
> @@ -21,6 +21,6 @@ along with GCC; see the file COPYING3.  If not see
>   #ifndef GCC_GCC_URLIFIER_H
>   #define GCC_GCC_URLIFIER_H
>   
> -extern urlifier *make_gcc_urlifier ();
> +extern urlifier *make_gcc_urlifier (unsigned int lang_mask);
>   
>   #endif /* GCC_GCC_URLIFIER_H */
> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
> index 51120c1489e3..781cb343f18b 100644
> --- a/gcc/gcc.cc
> +++ b/gcc/gcc.cc
> @@ -8292,7 +8292,7 @@ driver::global_initializations ()
>     diagnostic_initialize (global_dc, 0);
>     diagnostic_color_init (global_dc);
>     diagnostic_urls_init (global_dc);
> -  global_dc->set_urlifier (make_gcc_urlifier ());
> +  global_dc->set_urlifier (make_gcc_urlifier (0));
>   
>   #ifdef GCC_DRIVER_HOST_INITIALIZATION
>     /* Perform host dependent initialization when needed.  */
> diff --git a/gcc/opt-functions.awk b/gcc/opt-functions.awk
> index a58e93815e30..794aee74af8e 100644
> --- a/gcc/opt-functions.awk
> +++ b/gcc/opt-functions.awk
> @@ -193,6 +193,13 @@ function var_name(flags)
>   	return nth_arg(0, opt_args("Var", flags))
>   }
>   
> +# If FLAGS includes a UrlSuffix flag, return the value it specifies.
> +# Return the empty string otherwise.
> +function url_suffix(flags)
> +{
> +	return nth_arg(0, opt_args("UrlSuffix", flags))
> +}
> +
>   # Return the name of the variable if FLAGS has a HOST_WIDE_INT variable.
>   # Return the empty string otherwise.
>   function host_wide_int_var_name(flags)
> diff --git a/gcc/options-urls-cc-gen.awk b/gcc/options-urls-cc-gen.awk
> new file mode 100644
> index 000000000000..3c23a646a76d
> --- /dev/null
> +++ b/gcc/options-urls-cc-gen.awk
> @@ -0,0 +1,79 @@
> +#  Copyright (C) 2023 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify it
> +# under the terms of the GNU General Public License as published by the
> +# Free Software Foundation; either version 3, or (at your option) any
> +# later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; see the file COPYING3.  If not see
> +# <http://www.gnu.org/licenses/>.
> +
> +# This Awk script reads in the option records generated from
> +# opt-gather.awk, and generates a C++ file containing an array
> +# of URL suffixes (possibly NULL), one per option.
> +
> +# This program uses functions from opt-functions.awk and code from
> +# opt-read.awk.
> +#
> +# Usage: awk -f opt-functions.awk -f opt-read.awk -f options-urls-cc-gen.awk \
> +#            [-v header_name=header.h] < inputfile > options-urls.cc
> +
> +END {
> +
> +
> +print "/* This file is auto-generated by options-urls-cc-gen.awk.  */"
> +print ""
> +n_headers = split(header_name, headers, " ")
> +for (i = 1; i <= n_headers; i++)
> +	print "#include " quote headers[i] quote
> +print "#include " quote "opts.h" quote
> +print "#include " quote "intl.h" quote
> +print "#include " quote "insn-attr-common.h" quote
> +print ""
> +
> +if (n_extra_c_includes > 0) {
> +	for (i = 0; i < n_extra_c_includes; i++) {
> +		print "#include " quote extra_c_includes[i] quote
> +	}
> +	print ""
> +}
> +
> +print "const char * const opt_url_suffixes[] =\n{"
> +
> +optindex = 0
> +for (i = 0; i < n_opts; i++) {
> +	# With identical flags, pick only the last one.  The
> +	# earlier loop ensured that it has all flags merged,
> +	# and a nonempty help text if one of the texts was nonempty.
> +	while( i + 1 != n_opts && opts[i] == opts[i + 1] ) {
> +		i++;
> +	}
> +
> +	len = length (opts[i]);
> +	enum = opt_enum(opts[i])
> +
> +	s = substr("                                  ", length (opts[i]))
> +	if (i + 1 == n_opts)
> +		comma = ""
> +
> +	u = url_suffix(flags[i])
> +	if (u == "")
> +		c_str = "NULL"
> +	else
> +		c_str = quote u quote;
> +
> +	printf(" /* [%i] (%s) = */\n", optindex, opt_enum(opts[i]))
> +	printf("    %s%s\n", c_str, comma)
> +
> +	# Bump up the informational option index.
> +	++optindex
> + }
> +
> +print "};"
> +}
> diff --git a/gcc/opts.cc b/gcc/opts.cc
> index 32fdfc3e3ce1..87f041386264 100644
> --- a/gcc/opts.cc
> +++ b/gcc/opts.cc
> @@ -3647,14 +3647,6 @@ get_option_html_page (int option_index)
>   {
>     const cl_option *cl_opt = &cl_options[option_index];
>   
> -  /* Analyzer options are on their own page.  */
> -  if (strstr (cl_opt->opt_text, "analyzer-"))
> -    return "gcc/Static-Analyzer-Options.html";
> -
> -  /* Handle -flto= option.  */
> -  if (strstr (cl_opt->opt_text, "flto"))
> -    return "gcc/Optimize-Options.html";
> -
>   #ifdef CL_Fortran
>     if ((cl_opt->flags & CL_Fortran) != 0
>         /* If it is option common to both C/C++ and Fortran, it is documented
> @@ -3667,7 +3659,30 @@ get_option_html_page (int option_index)
>       return "gfortran/Error-and-Warning-Options.html";
>   #endif
>   
> -  return "gcc/Warning-Options.html";
> +  return nullptr;
> +}
> +
> +/* Get the url within the documentation for this option, or NULL.  */
> +
> +label_text
> +get_option_url_suffix (int option_index)
> +{
> +  if (const char *url = opt_url_suffixes[option_index])
> +    return label_text::borrow (url);
> +
> +  /* Fallback code for some options that aren't handled byt opt_url_suffixes
> +     e.g. links below "gfortran/".  */
> +  if (const char *html_page = get_option_html_page (option_index))
> +    return label_text::take
> +      (concat (html_page,
> +	       /* Expect an anchor of the form "index-Wfoo" e.g.
> +		  <a name="index-Wformat"></a>, and thus an id within
> +		  the page of "#index-Wformat".  */
> +	       "#index",
> +	       cl_options[option_index].opt_text,
> +	       NULL));
> +
> +  return label_text ();
>   }
>   
>   /* Return malloced memory for a URL describing the option OPTION_INDEX
> @@ -3677,22 +3692,13 @@ char *
>   get_option_url (diagnostic_context *, int option_index)
>   {
>     if (option_index)
> -    return concat (/* DOCUMENTATION_ROOT_URL should be supplied via
> -		      #include "config.h" (see --with-documentation-root-url),
> -		      and should have a trailing slash.  */
> -		   DOCUMENTATION_ROOT_URL,
> -
> -		   /* get_option_html_page will return something like
> -		      "gcc/Warning-Options.html".  */
> -		   get_option_html_page (option_index),
> -
> -		   /* Expect an anchor of the form "index-Wfoo" e.g.
> -		      <a name="index-Wformat"></a>, and thus an id within
> -		      the URL of "#index-Wformat".  */
> -		   "#index", cl_options[option_index].opt_text,
> -		   NULL);
> -  else
> -    return NULL;
> +    {
> +      label_text url_suffix = get_option_url_suffix (option_index);
> +      if (url_suffix.get ())
> +	return concat (DOCUMENTATION_ROOT_URL, url_suffix.get (), nullptr);
> +    }
> +
> +  return nullptr;
>   }
>   
>   /* Return a heap allocated producer with command line options.  */
> @@ -3823,17 +3829,20 @@ gen_producer_string (const char *language_string, cl_decoded_option *options,
>   
>   namespace selftest {
>   
> -/* Verify that get_option_html_page works as expected.  */
> +/* Verify that get_option_url_suffix works as expected.  */
>   
>   static void
> -test_get_option_html_page ()
> +test_get_option_url_suffix ()
>   {
> -  ASSERT_STREQ (get_option_html_page (OPT_Wcpp), "gcc/Warning-Options.html");
> -  ASSERT_STREQ (get_option_html_page (OPT_Wanalyzer_double_free),
> -	     "gcc/Static-Analyzer-Options.html");
> +  ASSERT_STREQ (get_option_url_suffix (OPT_Wcpp).get (),
> +		"gcc/Warning-Options.html#index-Wcpp");
> +  ASSERT_STREQ (get_option_url_suffix (OPT_Wanalyzer_double_free).get (),
> +	     "gcc/Static-Analyzer-Options.html#index-Wanalyzer-double-free");
> +
>   #ifdef CL_Fortran
> -  ASSERT_STREQ (get_option_html_page (OPT_Wline_truncation),
> -		"gfortran/Error-and-Warning-Options.html");
> +  ASSERT_STREQ
> +    (get_option_url_suffix (OPT_Wline_truncation).get (),
> +     "gfortran/Error-and-Warning-Options.html#index-Wline-truncation");
>   #endif
>   }
>   
> @@ -3896,7 +3905,7 @@ test_enum_sets ()
>   void
>   opts_cc_tests ()
>   {
> -  test_get_option_html_page ();
> +  test_get_option_url_suffix ();
>     test_enum_sets ();
>   }
>   
> diff --git a/gcc/opts.h b/gcc/opts.h
> index 00f377f9ca7e..97668e7d9206 100644
> --- a/gcc/opts.h
> +++ b/gcc/opts.h
> @@ -152,6 +152,7 @@ struct cl_option_state {
>   
>   extern const struct cl_option cl_options[];
>   extern const unsigned int cl_options_count;
> +extern const char *const opt_url_suffixes[];
>   #ifdef ENABLE_PLUGIN
>   extern const struct cl_var cl_vars[];
>   #endif
> @@ -564,4 +565,7 @@ struct switchstr
>     bool ordering;
>   };
>   
> +extern label_text
> +get_option_url_suffix (int option_index);
> +
>   #endif
> diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
> index 28529f57ef60..30642a72b756 100644
> --- a/gcc/testsuite/lib/gcc-dg.exp
> +++ b/gcc/testsuite/lib/gcc-dg.exp
> @@ -46,6 +46,12 @@ if { [ishost "*-*-cygwin*"] } {
>     setenv LANG C.ASCII
>   }
>   
> +# Set TERM to xterm to ensure that URL escapes are disabled.
> +# This avoids issues where a diagnostic which could embed a URL
> +# is emitted before -fdiagnostics-plain-output is handled, where
> +# otherwise the output could be affected by the environment.
> +setenv TERM xterm
> +
>   # Avoid sporadic data-losses with expect
>   match_max -d 10000
>   
> diff --git a/gcc/toplev.cc b/gcc/toplev.cc
> index d8e8978dd555..7a0d808b8c83 100644
> --- a/gcc/toplev.cc
> +++ b/gcc/toplev.cc
> @@ -1049,7 +1049,7 @@ general_init (const char *argv0, bool init_signals)
>     global_dc->m_option_state = &global_options;
>     global_dc->m_option_name = option_name;
>     global_dc->m_get_option_url = get_option_url;
> -  global_dc->set_urlifier (make_gcc_urlifier ());
> +  global_dc->set_urlifier (make_gcc_urlifier (global_dc->m_lang_mask));
>   
>     if (init_signals)
>       {
  

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 29cec21c8258..ebb59680d69b 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1270,6 +1270,8 @@  FLAGS_TO_PASS = \
 # All option source files
 ALL_OPT_FILES=$(lang_opt_files) $(extra_opt_files)
 
+ALL_OPT_URL_FILES=$(patsubst %, %.urls, $(ALL_OPT_FILES))
+
 # Target specific, C specific object file
 C_TARGET_OBJS=@c_target_objs@
 
@@ -1286,7 +1288,7 @@  FORTRAN_TARGET_OBJS=@fortran_target_objs@
 RUST_TARGET_OBJS=@rust_target_objs@
 
 # Object files for gcc many-languages driver.
-GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o
+GCC_OBJS = gcc.o gcc-main.o ggc-none.o gcc-urlifier.o options-urls.o
 
 c-family-warn = $(STRICT_WARN)
 
@@ -1606,6 +1608,7 @@  OBJS = \
 	optinfo.o \
 	optinfo-emit-json.o \
 	options-save.o \
+	options-urls.o \
 	opts-global.o \
 	ordered-hash-map-tests.o \
 	passes.o \
@@ -1832,7 +1835,8 @@  OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
 # compiler and containing target-dependent code.
 OBJS-libcommon-target = $(common_out_object_file) prefix.o \
 	opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \
-	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o
+	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o \
+	options-urls.o
 
 # This lists all host objects for the front ends.
 ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANGUAGES),$($(v)_OBJS))
@@ -2435,9 +2439,9 @@  s-specs : Makefile
 	$(STAMP) s-specs
 
 optionlist: s-options ; @true
-s-options: $(ALL_OPT_FILES) Makefile $(srcdir)/opt-gather.awk
+s-options: $(ALL_OPT_FILES) $(ALL_OPT_URL_FILES) Makefile $(srcdir)/opt-gather.awk
 	LC_ALL=C ; export LC_ALL ; \
-	$(AWK) -f $(srcdir)/opt-gather.awk $(ALL_OPT_FILES) > tmp-optionlist
+	$(AWK) -f $(srcdir)/opt-gather.awk $(ALL_OPT_FILES) $(ALL_OPT_URL_FILES) > tmp-optionlist
 	$(SHELL) $(srcdir)/../move-if-change tmp-optionlist optionlist
 	$(STAMP) s-options
 
@@ -2453,6 +2457,12 @@  options-save.cc: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
 	       -f $(srcdir)/optc-save-gen.awk \
 	       -v header_name="config.h system.h coretypes.h tm.h" < $< > $@
 
+options-urls.cc: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
+    $(srcdir)/options-urls-cc-gen.awk
+	$(AWK) -f $(srcdir)/opt-functions.awk -f $(srcdir)/opt-read.awk \
+	       -f $(srcdir)/options-urls-cc-gen.awk \
+	       -v header_name="config.h system.h coretypes.h tm.h" < $< > $@
+
 options.h: s-options-h ; @true
 s-options-h: optionlist $(srcdir)/opt-functions.awk $(srcdir)/opt-read.awk \
     $(srcdir)/opth-gen.awk
diff --git a/gcc/gcc-urlifier.cc b/gcc/gcc-urlifier.cc
index 0dbff9853132..da704bf814ad 100644
--- a/gcc/gcc-urlifier.cc
+++ b/gcc/gcc-urlifier.cc
@@ -24,6 +24,8 @@  along with GCC; see the file COPYING3.  If not see
 #include "pretty-print.h"
 #include "pretty-print-urlifier.h"
 #include "gcc-urlifier.h"
+#include "opts.h"
+#include "options.h"
 #include "selftest.h"
 
 namespace {
@@ -34,23 +36,34 @@  namespace {
 class gcc_urlifier : public urlifier
 {
 public:
+  gcc_urlifier (unsigned int lang_mask)
+  : m_lang_mask (lang_mask)
+  {}
+
   char *get_url_for_quoted_text (const char *p, size_t sz) const final override;
 
-  const char *get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
+  label_text get_url_suffix_for_quoted_text (const char *p, size_t sz) const;
   /* We use ATTRIBUTE_UNUSED as this helper is called only from ASSERTs.  */
-  const char *get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
+  label_text get_url_suffix_for_quoted_text (const char *p) const ATTRIBUTE_UNUSED;
 
 private:
+  label_text get_url_suffix_for_option (const char *p, size_t sz) const;
+
   static char *
   make_doc_url (const char *doc_url_suffix);
+
+  unsigned int m_lang_mask;
 };
 
 /* class gcc_urlifier : public urlifier.  */
 
+/* Manage a hard-coded mapping from quoted string to URL suffixes
+   in gcc-urlifier.def  */
+
 #define DOC_URL(QUOTED_TEXT, URL_SUFFIX) \
   { (QUOTED_TEXT), (URL_SUFFIX) }
 
-const struct
+static const struct
 {
   const char *quoted_text;
   const char *url_suffix;
@@ -60,32 +73,53 @@  const struct
 
 };
 
+/* Implementation of urlifier::get_url_for_quoted_text vfunc for GCC
+   diagnostics.  */
+
 char *
 gcc_urlifier::get_url_for_quoted_text (const char *p, size_t sz) const
 {
-  if (const char *url_suffix = get_url_suffix_for_quoted_text (p, sz))
-    return make_doc_url (url_suffix);
+  label_text url_suffix = get_url_suffix_for_quoted_text (p, sz);
+  if (url_suffix.get ())
+    return make_doc_url (url_suffix.get ());
   return nullptr;
 }
 
-const char *
+/* Look for a URL for the quoted string (P, SZ).
+   Return the url suffix if found, or nullptr otherwise.  */
+
+label_text
 gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
 {
-  /* Binary search.  This assumes that the quoted_text fields of doc_urls
+  if (sz == 0)
+    return label_text ();
+
+  /* If this is an option, look up the option and see if we have
+     a URL for it.  */
+  if (p[0] == '-')
+    {
+      label_text suffix = get_url_suffix_for_option (p, sz);
+      if (suffix.get ())
+	return suffix;
+    }
+
+  /* Otherwise, look within the hard-coded data table in gcc-urlifier.def.
+
+     Binary search.  This assumes that the quoted_text fields of doc_urls
      are in sorted order.  */
   int min = 0;
   int max = ARRAY_SIZE (doc_urls) - 1;
   while (true)
     {
       if (min > max)
-	return nullptr;
+	return label_text ();
       int midpoint = (min + max) / 2;
       gcc_assert ((size_t)midpoint < ARRAY_SIZE (doc_urls));
       int cmp = strncmp (p, doc_urls[midpoint].quoted_text, sz);
       if (cmp == 0)
 	{
 	  if (doc_urls[midpoint].quoted_text[sz] == '\0')
-	    return doc_urls[midpoint].url_suffix;
+	    return label_text::borrow (doc_urls[midpoint].url_suffix);
 	  else
 	    max = midpoint - 1;
 	}
@@ -94,15 +128,45 @@  gcc_urlifier::get_url_suffix_for_quoted_text (const char *p, size_t sz) const
       else
 	min = midpoint + 1;
     }
-  return nullptr;
+
+  /* Not found.  */
+  return label_text ();
 }
 
-const char *
+/* For use in selftests.  */
+
+label_text
 gcc_urlifier::get_url_suffix_for_quoted_text (const char *p) const
 {
   return get_url_suffix_for_quoted_text (p, strlen (p));
 }
 
+/* Look for a URL for the quoted string (P, SZ) that appears to be
+   an option.
+   Return the url suffix if found, or nullptr otherwise.  */
+
+label_text
+gcc_urlifier::get_url_suffix_for_option (const char *p, size_t sz) const
+{
+  /* Look up this option
+
+     find_opt does a binary search, taking a 0-terminated string,
+     and skipping the leading '-'.
+
+     We have a (pointer,size) pair that doesn't necessarily have a
+     terminator, so create a 0-terminated clone of the string.  */
+  gcc_assert (sz > 0);
+  char *tmp = xstrndup (p + 1, sz - 1); // skip the leading '-'
+  size_t opt = find_opt (tmp, m_lang_mask);
+  free (tmp);
+
+  if (opt >= N_OPTS)
+    /* Option not recognized.  */
+    return label_text ();
+
+  return get_option_url_suffix (opt);
+}
+
 char *
 gcc_urlifier::make_doc_url (const char *doc_url_suffix)
 {
@@ -115,9 +179,9 @@  gcc_urlifier::make_doc_url (const char *doc_url_suffix)
 } // anonymous namespace
 
 urlifier *
-make_gcc_urlifier ()
+make_gcc_urlifier (unsigned int lang_mask)
 {
-  return new gcc_urlifier ();
+  return new gcc_urlifier (lang_mask);
 }
 
 #if CHECKING_P
@@ -137,22 +201,26 @@  gcc_urlifier_cc_tests ()
 			doc_urls[idx].quoted_text)
 		< 0);
 
-  gcc_urlifier u;
+  gcc_urlifier u (0);
 
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text (""), nullptr);
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")"), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("").get (), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text (")").get (), nullptr);
 
-  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message"),
+  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("#pragma message").get (),
 		"gcc/Diagnostic-Pragmas.html");
 
   // Incomplete prefix of a quoted_text
-  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess"), nullptr);
+  ASSERT_EQ (u.get_url_suffix_for_quoted_text ("#pragma mess").get (), nullptr);
 
   /* Check that every element is findable.  */
   for (size_t idx = 0; idx < ARRAY_SIZE (doc_urls); idx++)
     ASSERT_STREQ
-      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text),
+      (u.get_url_suffix_for_quoted_text (doc_urls[idx].quoted_text).get (),
        doc_urls[idx].url_suffix);
+
+  /* Check an option.  */
+  ASSERT_STREQ (u.get_url_suffix_for_quoted_text ("-fpack-struct").get (),
+		"gcc/Code-Gen-Options.html#index-fpack-struct");
 }
 
 } // namespace selftest
diff --git a/gcc/gcc-urlifier.def b/gcc/gcc-urlifier.def
index 360de930e9ec..de6d9a3eb962 100644
--- a/gcc/gcc-urlifier.def
+++ b/gcc/gcc-urlifier.def
@@ -16,5 +16,3 @@  DOC_URL ("#pragma pack", "gcc/Structure-Layout-Pragmas.html"),
 DOC_URL ("#pragma redefine_extname", "gcc/Symbol-Renaming-Pragmas.html"),
 DOC_URL ("#pragma scalar_storage_order", "gcc/Structure-Layout-Pragmas.html"),
 DOC_URL ("#pragma weak", "gcc/Weak-Pragmas.html"),
-DOC_URL ("--version", "gcc/Overall-Options.html#index-version"),
-DOC_URL ("-fpack-struct", "gcc/Code-Gen-Options.html#index-fpack-struct"),
diff --git a/gcc/gcc-urlifier.h b/gcc/gcc-urlifier.h
index 614e1c64b94d..77eb13463928 100644
--- a/gcc/gcc-urlifier.h
+++ b/gcc/gcc-urlifier.h
@@ -21,6 +21,6 @@  along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_GCC_URLIFIER_H
 #define GCC_GCC_URLIFIER_H
 
-extern urlifier *make_gcc_urlifier ();
+extern urlifier *make_gcc_urlifier (unsigned int lang_mask);
 
 #endif /* GCC_GCC_URLIFIER_H */
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 51120c1489e3..781cb343f18b 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -8292,7 +8292,7 @@  driver::global_initializations ()
   diagnostic_initialize (global_dc, 0);
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
-  global_dc->set_urlifier (make_gcc_urlifier ());
+  global_dc->set_urlifier (make_gcc_urlifier (0));
 
 #ifdef GCC_DRIVER_HOST_INITIALIZATION
   /* Perform host dependent initialization when needed.  */
diff --git a/gcc/opt-functions.awk b/gcc/opt-functions.awk
index a58e93815e30..794aee74af8e 100644
--- a/gcc/opt-functions.awk
+++ b/gcc/opt-functions.awk
@@ -193,6 +193,13 @@  function var_name(flags)
 	return nth_arg(0, opt_args("Var", flags))
 }
 
+# If FLAGS includes a UrlSuffix flag, return the value it specifies.
+# Return the empty string otherwise.
+function url_suffix(flags)
+{
+	return nth_arg(0, opt_args("UrlSuffix", flags))
+}
+
 # Return the name of the variable if FLAGS has a HOST_WIDE_INT variable. 
 # Return the empty string otherwise.
 function host_wide_int_var_name(flags)
diff --git a/gcc/options-urls-cc-gen.awk b/gcc/options-urls-cc-gen.awk
new file mode 100644
index 000000000000..3c23a646a76d
--- /dev/null
+++ b/gcc/options-urls-cc-gen.awk
@@ -0,0 +1,79 @@ 
+#  Copyright (C) 2023 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 3, or (at your option) any
+# later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# This Awk script reads in the option records generated from 
+# opt-gather.awk, and generates a C++ file containing an array
+# of URL suffixes (possibly NULL), one per option.
+
+# This program uses functions from opt-functions.awk and code from
+# opt-read.awk.
+#
+# Usage: awk -f opt-functions.awk -f opt-read.awk -f options-urls-cc-gen.awk \
+#            [-v header_name=header.h] < inputfile > options-urls.cc
+
+END {
+
+
+print "/* This file is auto-generated by options-urls-cc-gen.awk.  */"
+print ""
+n_headers = split(header_name, headers, " ")
+for (i = 1; i <= n_headers; i++)
+	print "#include " quote headers[i] quote
+print "#include " quote "opts.h" quote
+print "#include " quote "intl.h" quote
+print "#include " quote "insn-attr-common.h" quote
+print ""
+
+if (n_extra_c_includes > 0) {
+	for (i = 0; i < n_extra_c_includes; i++) {
+		print "#include " quote extra_c_includes[i] quote
+	}
+	print ""
+}
+
+print "const char * const opt_url_suffixes[] =\n{"
+
+optindex = 0
+for (i = 0; i < n_opts; i++) {
+	# With identical flags, pick only the last one.  The
+	# earlier loop ensured that it has all flags merged,
+	# and a nonempty help text if one of the texts was nonempty.
+	while( i + 1 != n_opts && opts[i] == opts[i + 1] ) {
+		i++;
+	}
+
+	len = length (opts[i]);
+	enum = opt_enum(opts[i])
+
+	s = substr("                                  ", length (opts[i]))
+	if (i + 1 == n_opts)
+		comma = ""
+
+	u = url_suffix(flags[i])
+	if (u == "")
+		c_str = "NULL"
+	else
+		c_str = quote u quote;
+
+	printf(" /* [%i] (%s) = */\n", optindex, opt_enum(opts[i]))
+	printf("    %s%s\n", c_str, comma)
+
+	# Bump up the informational option index.
+	++optindex
+ }
+
+print "};"
+}
diff --git a/gcc/opts.cc b/gcc/opts.cc
index 32fdfc3e3ce1..87f041386264 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -3647,14 +3647,6 @@  get_option_html_page (int option_index)
 {
   const cl_option *cl_opt = &cl_options[option_index];
 
-  /* Analyzer options are on their own page.  */
-  if (strstr (cl_opt->opt_text, "analyzer-"))
-    return "gcc/Static-Analyzer-Options.html";
-
-  /* Handle -flto= option.  */
-  if (strstr (cl_opt->opt_text, "flto"))
-    return "gcc/Optimize-Options.html";
-
 #ifdef CL_Fortran
   if ((cl_opt->flags & CL_Fortran) != 0
       /* If it is option common to both C/C++ and Fortran, it is documented
@@ -3667,7 +3659,30 @@  get_option_html_page (int option_index)
     return "gfortran/Error-and-Warning-Options.html";
 #endif
 
-  return "gcc/Warning-Options.html";
+  return nullptr;
+}
+
+/* Get the url within the documentation for this option, or NULL.  */
+
+label_text
+get_option_url_suffix (int option_index)
+{
+  if (const char *url = opt_url_suffixes[option_index])
+    return label_text::borrow (url);
+
+  /* Fallback code for some options that aren't handled byt opt_url_suffixes
+     e.g. links below "gfortran/".  */
+  if (const char *html_page = get_option_html_page (option_index))
+    return label_text::take
+      (concat (html_page,
+	       /* Expect an anchor of the form "index-Wfoo" e.g.
+		  <a name="index-Wformat"></a>, and thus an id within
+		  the page of "#index-Wformat".  */
+	       "#index",
+	       cl_options[option_index].opt_text,
+	       NULL));
+
+  return label_text ();
 }
 
 /* Return malloced memory for a URL describing the option OPTION_INDEX
@@ -3677,22 +3692,13 @@  char *
 get_option_url (diagnostic_context *, int option_index)
 {
   if (option_index)
-    return concat (/* DOCUMENTATION_ROOT_URL should be supplied via
-		      #include "config.h" (see --with-documentation-root-url),
-		      and should have a trailing slash.  */
-		   DOCUMENTATION_ROOT_URL,
-
-		   /* get_option_html_page will return something like
-		      "gcc/Warning-Options.html".  */
-		   get_option_html_page (option_index),
-
-		   /* Expect an anchor of the form "index-Wfoo" e.g.
-		      <a name="index-Wformat"></a>, and thus an id within
-		      the URL of "#index-Wformat".  */
-		   "#index", cl_options[option_index].opt_text,
-		   NULL);
-  else
-    return NULL;
+    {
+      label_text url_suffix = get_option_url_suffix (option_index);
+      if (url_suffix.get ())
+	return concat (DOCUMENTATION_ROOT_URL, url_suffix.get (), nullptr);
+    }
+
+  return nullptr;
 }
 
 /* Return a heap allocated producer with command line options.  */
@@ -3823,17 +3829,20 @@  gen_producer_string (const char *language_string, cl_decoded_option *options,
 
 namespace selftest {
 
-/* Verify that get_option_html_page works as expected.  */
+/* Verify that get_option_url_suffix works as expected.  */
 
 static void
-test_get_option_html_page ()
+test_get_option_url_suffix ()
 {
-  ASSERT_STREQ (get_option_html_page (OPT_Wcpp), "gcc/Warning-Options.html");
-  ASSERT_STREQ (get_option_html_page (OPT_Wanalyzer_double_free),
-	     "gcc/Static-Analyzer-Options.html");
+  ASSERT_STREQ (get_option_url_suffix (OPT_Wcpp).get (),
+		"gcc/Warning-Options.html#index-Wcpp");
+  ASSERT_STREQ (get_option_url_suffix (OPT_Wanalyzer_double_free).get (),
+	     "gcc/Static-Analyzer-Options.html#index-Wanalyzer-double-free");
+
 #ifdef CL_Fortran
-  ASSERT_STREQ (get_option_html_page (OPT_Wline_truncation),
-		"gfortran/Error-and-Warning-Options.html");
+  ASSERT_STREQ
+    (get_option_url_suffix (OPT_Wline_truncation).get (),
+     "gfortran/Error-and-Warning-Options.html#index-Wline-truncation");
 #endif
 }
 
@@ -3896,7 +3905,7 @@  test_enum_sets ()
 void
 opts_cc_tests ()
 {
-  test_get_option_html_page ();
+  test_get_option_url_suffix ();
   test_enum_sets ();
 }
 
diff --git a/gcc/opts.h b/gcc/opts.h
index 00f377f9ca7e..97668e7d9206 100644
--- a/gcc/opts.h
+++ b/gcc/opts.h
@@ -152,6 +152,7 @@  struct cl_option_state {
 
 extern const struct cl_option cl_options[];
 extern const unsigned int cl_options_count;
+extern const char *const opt_url_suffixes[];
 #ifdef ENABLE_PLUGIN
 extern const struct cl_var cl_vars[];
 #endif
@@ -564,4 +565,7 @@  struct switchstr
   bool ordering;
 };
 
+extern label_text
+get_option_url_suffix (int option_index);
+
 #endif
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 28529f57ef60..30642a72b756 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -46,6 +46,12 @@  if { [ishost "*-*-cygwin*"] } {
   setenv LANG C.ASCII
 }
 
+# Set TERM to xterm to ensure that URL escapes are disabled.
+# This avoids issues where a diagnostic which could embed a URL
+# is emitted before -fdiagnostics-plain-output is handled, where
+# otherwise the output could be affected by the environment.
+setenv TERM xterm
+
 # Avoid sporadic data-losses with expect
 match_max -d 10000
 
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index d8e8978dd555..7a0d808b8c83 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -1049,7 +1049,7 @@  general_init (const char *argv0, bool init_signals)
   global_dc->m_option_state = &global_options;
   global_dc->m_option_name = option_name;
   global_dc->m_get_option_url = get_option_url;
-  global_dc->set_urlifier (make_gcc_urlifier ());
+  global_dc->set_urlifier (make_gcc_urlifier (global_dc->m_lang_mask));
 
   if (init_signals)
     {