diff mbox series

[RFC] Implement #pragma GCC noexpand

Message ID 87pmres76p.fsf@oldenburg.str.redhat.com
State New
Headers show
Series [RFC] Implement #pragma GCC noexpand | expand

Commit Message

Florian Weimer Nov. 5, 2021, 8:01 p.m. UTC
This can be used to avoid excessive __ mangling of identifiers
merely to guard against accidental macro expansion.  (Identifiers in
the global scope or with external linkage may still need mangling.)

In order to support -fdirectives-only without introducing
new line change flags, #include is not permitted in noexpand mode.

I think this could be particularly useful for writing libstdc++ headers.
Is this something we want?  Then I'll figure out how to add some tests.

Thanks,
Florian

libcpp/ChangeLog

	* include/cpplib. (enum cpp_warning_reason): Add
	CPP_W_EXPANSION_TO_DEFINED.
	(NODE_EXPAND): Define new node flag.
	(struct cpp_hashnode): Expand width of flags member.
	* internal.h (struct cpp_read): Add noexpand member.
	* directives.c (struct pragma_entry): Add
	pass_if_directives_only.
	(do_include_common): Error out in noexpand mode.
	(do_linemarker): Reset noexpand flag when leaving files.
	(_cpp_pop_buffer): Likewise.
	(register_pragma_internal_not_directive_only)
	(do_pragma_expand, do_pragma_noexpand): New functions.
	(_cpp_init_internal_pragmas): Register "GCC expand",
	"GCC noexpand" pragmata.
	(do_pragma): Use default callback for pass_if_directives_only
	pragmata with -fdirectives-only.
	* init.c (cpp_init_builtins): Define _Expand helper macro.
	* macro.c (enter_macro_context): Enable nested macro
	expansion by disabling noexpand.
	(cpp_get_token_1): Skip macro expansion in noexpand mode,
	except for NODE_EXPAND macros.

gcc/ChangeLog

	* c-family/c.opt (Wcpp-noexpand): Add.
	* common.opt (Wcpp-noexpand): Likewise.
	* doc/cpp.texi (Common Predefined Macros): Document _Expand.
	(Pragmas): Document "GCC noexpand", "GCC expand".
	* doc/invoke.texi (Option Summary): Add -Wno-cpp-noexpand.
	(Warning Options): Document -Wno-cpp-noexpand.

---
 gcc/c-family/c.opt      |  4 +++
 gcc/common.opt          |  4 +++
 gcc/doc/cpp.texi        | 26 +++++++++++++++++++
 gcc/doc/invoke.texi     |  7 +++++-
 libcpp/directives.c     | 66 +++++++++++++++++++++++++++++++++++++++++++++++++
 libcpp/include/cpplib.h |  6 +++--
 libcpp/init.c           |  6 +++++
 libcpp/internal.h       |  3 +++
 libcpp/macro.c          | 14 ++++++++---
 9 files changed, 130 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 06457ac739e..4cb9811db3a 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -511,6 +511,10 @@  Wcpp
 C ObjC C++ ObjC++ CppReason(CPP_W_WARNING_DIRECTIVE)
 ; Documented in common.opt
 
+Wcpp-noexpand
+C ObjC C++ ObjC++ CppReason(CPP_W_NOEXPAND)
+; Documented in common.opt.
+
 Wctad-maybe-unsupported
 C++ ObjC++ Var(warn_ctad_maybe_unsupported) Warning
 Warn when performing class template argument deduction on a type with no
diff --git a/gcc/common.opt b/gcc/common.opt
index 1a5b9bfcca9..d5f6a5c296d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -574,6 +574,10 @@  Wcpp
 Common Var(warn_cpp) Init(1) Warning
 Warn when a #warning directive is encountered.
 
+Wcpp-noexpand
+Common Var(warn_cpp_noexpand) Init(1) Warning
+Warn about misuses of the noexpand preprocessor feature.
+
 Wattribute-warning
 Common Var(warn_attribute_warning) Init(1) Warning
 Warn about uses of __attribute__((warning)) declarations.
diff --git a/gcc/doc/cpp.texi b/gcc/doc/cpp.texi
index 53f7204504c..037dc51cba3 100644
--- a/gcc/doc/cpp.texi
+++ b/gcc/doc/cpp.texi
@@ -1941,6 +1941,11 @@  generate unique identifiers.  Care must be taken to ensure that
 @code{__COUNTER__} is not expanded prior to inclusion of precompiled headers
 which use it.  Otherwise, the precompiled headers will not be used.
 
+@item _Expand
+@samp{_Expand (@var{source})} can be used to macro-expand @var{source}
+after a @samp{#pragma GCC noexpand} directive, where macro expansion is
+inhibited otherwise.
+
 @item __GFORTRAN__
 The GNU Fortran compiler defines this.
 
@@ -3843,6 +3848,27 @@  file will never be read again, no matter what.  It is a less-portable
 alternative to using @samp{#ifndef} to guard the contents of header files
 against multiple inclusions.
 
+@item #pragma GCC noexpand
+Temporarily turns off macro expansion.  In order to expand @var{source}
+in this mode, write @samp{_Expand (@var{source})}.  For example,
+
+@smallexample
+#define A a
+#define B b
+#define C() A B
+#pragma GCC noexpand
+A _Expand (A B C()) B
+@end smallexample
+
+expands to @samp{A a b a b B}.
+
+It is an error to include other files in @code{noexpand}, until macro
+expansion has been turned on again using @samp{#pragma GCC expand}.
+
+@item #pragma GCC expand
+Turns on macro expansion again after it has been turned off using
+@samp{#pragma GCC noexpand}.
+
 @end ftable
 
 @node Other Directives
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9fb74d34920..4e6c5210161 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -338,7 +338,7 @@  Objective-C and Objective-C++ Dialects}.
 -Wcast-align  -Wcast-align=strict  -Wcast-function-type  -Wcast-qual  @gol
 -Wchar-subscripts @gol
 -Wclobbered  -Wcomment @gol
--Wconversion  -Wno-coverage-mismatch  -Wno-cpp @gol
+-Wconversion  -Wno-coverage-mismatch  -Wno-cpp -Wno-cpp-noexpand @gol
 -Wdangling-else  -Wdate-time @gol
 -Wno-deprecated  -Wno-deprecated-declarations  -Wno-designated-init @gol
 -Wdisabled-optimization @gol
@@ -5903,6 +5903,11 @@  disable the error.
 @opindex Wcpp
 Suppress warning messages emitted by @code{#warning} directives.
 
+@item -Wno-cpp-noexpand @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
+@opindex Wno-cpp
+@opindex Wcpp
+Suppress warning messages due to misuse of @code{#pragma GCC noexpand}.
+
 @item -Wdouble-promotion @r{(C, C++, Objective-C and Objective-C++ only)}
 @opindex Wdouble-promotion
 @opindex Wno-double-promotion
diff --git a/libcpp/directives.c b/libcpp/directives.c
index 34f7677f718..8335e1ea1be 100644
--- a/libcpp/directives.c
+++ b/libcpp/directives.c
@@ -46,6 +46,7 @@  struct pragma_entry
   bool is_nspace;
   bool is_internal;
   bool is_deferred;
+  bool pass_if_directives_only;
   bool allow_expansion;
   union {
     pragma_cb handler;
@@ -126,6 +127,8 @@  static void do_pragma_dependency (cpp_reader *);
 static void do_pragma_warning_or_error (cpp_reader *, bool error);
 static void do_pragma_warning (cpp_reader *);
 static void do_pragma_error (cpp_reader *);
+static void do_pragma_expand (cpp_reader *);
+static void do_pragma_noexpand (cpp_reader *);
 static void do_linemarker (cpp_reader *);
 static const cpp_token *get_token_no_padding (cpp_reader *);
 static const cpp_token *get__Pragma_string (cpp_reader *);
@@ -828,6 +831,14 @@  do_include_common (cpp_reader *pfile, enum include_type type)
   const cpp_token **buf = NULL;
   location_t location;
 
+  if (pfile->noexpand)
+    {
+      cpp_error (pfile, CPP_DL_ERROR,
+		 "#%s not allowed after #pragma GCC noexpand",
+		 pfile->directive->name);
+      return;
+    }
+
   /* Re-enable saving of comments if requested, so that the include
      callback can dump comments which follow #include.  */
   pfile->state.save_comments = ! CPP_OPTION (pfile, discard_comments);
@@ -1104,6 +1115,10 @@  do_linemarker (cpp_reader *pfile)
 
   if (reason == LC_LEAVE)
     {
+      /* Include directives are not allowed in noexpand mode.
+	 Clear the flag unconditionally.  */
+      pfile->noexpand = false;
+
       /* Reread map since cpp_get_token can invalidate it with a
 	 reallocation.  */
       map = LINEMAPS_LAST_ORDINARY_MAP (line_table);
@@ -1341,6 +1356,23 @@  register_pragma_internal (cpp_reader *pfile, const char *space,
   entry->u.handler = handler;
 }
 
+/* Register a cpplib internal pragma SPACE NAME with HANDLER.  Unlike
+   register_pragma_internal, such pragmata are passed through with
+   -fdirectives-internal.  */
+static void
+register_pragma_internal_not_directive_only (cpp_reader *pfile,
+					     const char *space,
+					     const char *name,
+					     pragma_cb handler)
+{
+  struct pragma_entry *entry;
+
+  entry = register_pragma_1 (pfile, space, name, false);
+  entry->is_internal = true;
+  entry->pass_if_directives_only = true;
+  entry->u.handler = handler;
+}
+
 /* Register a pragma NAME in namespace SPACE.  If SPACE is null, it
    goes in the global namespace.  HANDLER is the handler it will call,
    which must be non-NULL.  If ALLOW_EXPANSION is set, allow macro
@@ -1401,6 +1433,10 @@  _cpp_init_internal_pragmas (cpp_reader *pfile)
   register_pragma_internal (pfile, "GCC", "dependency", do_pragma_dependency);
   register_pragma_internal (pfile, "GCC", "warning", do_pragma_warning);
   register_pragma_internal (pfile, "GCC", "error", do_pragma_error);
+  register_pragma_internal_not_directive_only (pfile, "GCC", "expand",
+					       do_pragma_expand);
+  register_pragma_internal_not_directive_only (pfile, "GCC", "noexpand",
+					       do_pragma_noexpand);
 }
 
 /* Return the number of registered pragmas in PE.  */
@@ -1515,6 +1551,9 @@  do_pragma (cpp_reader *pfile)
 	}
     }
 
+  if (p && p->pass_if_directives_only && CPP_OPTION (pfile, directives_only))
+    p = NULL;
+
   if (p)
     {
       if (p->is_deferred)
@@ -1810,6 +1849,29 @@  do_pragma_error (cpp_reader *pfile)
   do_pragma_warning_or_error (pfile, true);
 }
 
+/* Enable macro expansion.  */
+static void
+do_pragma_expand (cpp_reader *pfile)
+{
+  check_eol (pfile, false);
+  if (pfile->noexpand)
+    pfile->noexpand = false;
+  else
+    cpp_warning (pfile, CPP_W_NOEXPAND,
+		 "#pragma GCC expand without previous #pragma GCC noexpand");
+}
+
+/* Disable macro expansion.  */
+static void
+do_pragma_noexpand (cpp_reader *pfile)
+{
+  check_eol (pfile, false);
+  if (!pfile->noexpand)
+    pfile->noexpand = true;
+  else
+    cpp_warning (pfile, CPP_W_NOEXPAND, "redundant #pragma GCC noexpand");
+}
+
 /* Get a token but skip padding.  */
 static const cpp_token *
 get_token_no_padding (cpp_reader *pfile)
@@ -2769,6 +2831,10 @@  _cpp_pop_buffer (cpp_reader *pfile)
 
   if (inc)
     {
+      /* Include directives are not allowed in noexpand mode.  Clear the flag
+	 unconditionally.  */
+      pfile->noexpand = false;
+
       _cpp_pop_file_buffer (pfile, inc, to_free);
 
       _cpp_do_file_change (pfile, LC_LEAVE, 0, 0, 0);
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 176f8c5bbce..3118283fae0 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -643,7 +643,8 @@  enum cpp_warning_reason {
   CPP_W_C90_C99_COMPAT,
   CPP_W_C11_C2X_COMPAT,
   CPP_W_CXX11_COMPAT,
-  CPP_W_EXPANSION_TO_DEFINED
+  CPP_W_EXPANSION_TO_DEFINED,
+  CPP_W_NOEXPAND
 };
 
 /* Callback for header lookup for HEADER, which is the name of a
@@ -873,6 +874,7 @@  struct GTY(()) cpp_macro {
 #define NODE_CONDITIONAL (1 << 6)	/* Conditional macro */
 #define NODE_WARN_OPERATOR (1 << 7)	/* Warn about C++ named operator.  */
 #define NODE_MODULE (1 << 8)		/* C++-20 module-related name.  */
+#define NODE_EXPAND (1 << 9)		/* Expand even in in noexpand mode.  */
 
 /* Different flavors of hash node.  */
 enum node_type
@@ -933,7 +935,7 @@  struct GTY(()) cpp_hashnode {
 					   then index into directive table.
 					   Otherwise, a NODE_OPERATOR.  */
   unsigned int rid_code : 8;		/* Rid code - for front ends.  */
-  unsigned int flags : 9;		/* CPP flags.  */
+  unsigned int flags : 10;		/* CPP flags.  */
   ENUM_BITFIELD(node_type) type : 2;	/* CPP node type.  */
 
   /* 5 bits spare.  */
diff --git a/libcpp/init.c b/libcpp/init.c
index 5a424e23553..b4b368c46cd 100644
--- a/libcpp/init.c
+++ b/libcpp/init.c
@@ -601,6 +601,12 @@  cpp_init_builtins (cpp_reader *pfile, int hosted)
 
   if (CPP_OPTION (pfile, objc))
     _cpp_define_builtin (pfile, "__OBJC__ 1");
+
+  if (!CPP_OPTION (pfile, traditional))
+    {
+      cpp_lookup (pfile, DSC("_Expand"))->flags |= NODE_EXPAND;
+      _cpp_define_builtin (pfile, "_Expand(x) x");
+    }
 }
 
 /* Sanity-checks are dependent on command-line options, so it is
diff --git a/libcpp/internal.h b/libcpp/internal.h
index 8577cab6c83..ba44da21f7f 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -455,6 +455,9 @@  struct cpp_reader
      one.  */
   bool about_to_expand_macro_p;
 
+  /* If true, #pragma GCC noexpand is active.  */
+  bool noexpand;
+
   /* Search paths for include files.  */
   struct cpp_dir *quote_include;	/* "" */
   struct cpp_dir *bracket_include;	/* <> */
diff --git a/libcpp/macro.c b/libcpp/macro.c
index b2f797cae35..e19a629102c 100644
--- a/libcpp/macro.c
+++ b/libcpp/macro.c
@@ -1493,9 +1493,14 @@  enter_macro_context (cpp_reader *pfile, cpp_hashnode *node,
 	    }
 
 	  if (macro->paramc > 0)
-	    replace_args (pfile, node, macro,
-			  (macro_arg *) buff->base,
-			  location);
+	    {
+	      bool saved_noexpand = pfile->noexpand;
+	      pfile->noexpand = false;
+	      replace_args (pfile, node, macro,
+			    (macro_arg *) buff->base,
+			    location);
+	      pfile->noexpand = saved_noexpand;
+	    }
 	  /* Free the memory used by the arguments of this
 	     function-like macro.  This memory has been allocated by
 	     funlike_invocation_p and by replace_args.  */
@@ -2930,6 +2935,9 @@  cpp_get_token_1 (cpp_reader *pfile, location_t *location)
 
       node = result->val.node.node;
 
+      if (pfile->noexpand && !(node->flags & NODE_EXPAND))
+	break;
+
       if (node->type == NT_VOID || (result->flags & NO_EXPAND))
 	break;