Attached is a very rough and only superficially barely tested
prototype of the __builtin_warning intrinsic we talked about
the other day.  The built-in is declared like so:

  int __builtin_warning (int loc,
                         const char *option,
                         const char *txt, ...);

If it's still present during RTL expansion the built-in calls

  bool ret = warning_at (LOC, find_opt (option), txt, ...);

and expands to the constant value of RET (which could be used
to issue __builtin_note but there may be better ways to deal
with those than that).

LOC is the location of the warning or zero for the location of
the built-in itself (when called by user code), OPTION is either
the name of the warning option (e.g., "-Wnonnull", when called
by user code) or the index of the option (e.g., OPT_Wnonnull,
when emitted by GCC into the IL), and TXT is the format string
to format the warning text.  The rest of the arguments are not
used but I expect to extract and pass them to the diagnostic
pretty printer to format the text of the warning.

Using the built-in in user code should be obvious.  To show
how it might be put to use within GCC, I added code to
gimple-ssa-isolate-paths.c to emit -Wnonnull in response to
invalid null pointer accesses.  For this demo, when compiled
with the patch applied and with -Wnull-dereference (which is
not in -Wall or -Wextra), GCC issues three warnings: two
instances of -Wnull-dereference one of which is a false positive,
and one -Wnonnull (the one I added, which is included in -Wall),
which is a true positive:

  int f (void)
  {
    char a[4] = "12";
    char *p = __builtin_strlen (a) < 3 ? a : 0;
    return *p;
  }

  int g (void)
  {
    char a[4] = "12";
    char *p = __builtin_strlen (a) > 3 ? a : 0;
    return *p;
  }

  In function ‘f’:
  warning: potential null pointer dereference [-Wnull-dereference]
    7 |   return *p;
      |          ^~
  In function ‘g’:
  warning: null pointer dereference [-Wnull-dereference]
   14 |   return *p;
      |          ^~
  warning: invalid use of a null pointer [-Wnonnull]

The -Wnull-dereference instance in f is a false positive because
the strlen result is clearly less than two.  The strlen pass folds
the strlen result to a constant but it runs after path isolation
which will have already issued the bogus warning.

Martin

PS I tried compiling GCC with the patch.  It fails in libgomp
with:

  libgomp/oacc-mem.c: In function ‘gomp_acc_remove_pointer’:
  cc1: warning: invalid use of a null pointer [-Wnonnull]

so clearly it's missing location information.  With
-Wnull-dereference enabled we get more detail:

  libgomp/oacc-mem.c: In function ‘gomp_acc_remove_pointer’:
libgomp/oacc-mem.c:1013:31: warning: potential null pointer dereference [-Wnull-dereference]
   1013 |       for (size_t i = 0; i < t->list_count; i++)
        |                              ~^~~~~~~~~~~~
libgomp/oacc-mem.c:1012:19: warning: potential null pointer dereference [-Wnull-dereference]
   1012 |       t->refcount = minrefs;
        |       ~~~~~~~~~~~~^~~~~~~~~
libgomp/oacc-mem.c:1013:31: warning: potential null pointer dereference [-Wnull-dereference]
   1013 |       for (size_t i = 0; i < t->list_count; i++)
        |                              ~^~~~~~~~~~~~
libgomp/oacc-mem.c:1012:19: warning: potential null pointer dereference [-Wnull-dereference]
   1012 |       t->refcount = minrefs;
        |       ~~~~~~~~~~~~^~~~~~~~~
  cc1: warning: invalid use of a null pointer [-Wnonnull]

I didn't spend too long examining the code but it seems like
the warnings might actually be justified.  When the first loop
terminates with t being null the subsequent dereferences are
invalid:

      if (t->refcount == minrefs)
        {
          /* This is the last reference, so pull the descriptor off the
             chain. This prevents gomp_unmap_vars via gomp_unmap_tgt from
             freeing the device memory. */
          struct target_mem_desc *tp;
          for (tp = NULL, t = acc_dev->openacc.data_environ; t != NULL;
               tp = t, t = t->prev)
            {
              if (n->tgt == t)
                {
                  if (tp)
                    tp->prev = t->prev;
                  else
                    acc_dev->openacc.data_environ = t->prev;
                  break;
                }
            }
        }

      /* Set refcount to 1 to allow gomp_unmap_vars to unmap it.  */
      n->refcount = 1;
      t->refcount = minrefs;
      for (size_t i = 0; i < t->list_count; i++)

gcc/ChangeLog:

	* builtin-types.def (BT_FN_INT_INT_CONST_STRING_CONST_STRING): New.
	* builtins.c (expand_builtin): Handle BUILT_IN_WARNING.
	(expand_builtin_memory_chk): Same.
	(is_simple_builtin): Same.
	* builtins.def (BUILT_IN_WARNING): New.
	* gimple-ssa-isolate-paths.c (insert_warning): New function.
	(isolate_path): Call insert_warning.
	(stmt_uses_name_in_undefined_way): Make static.
	(find_explicit_erroneous_behavior): Call insert_warning.

Index: gcc/builtin-types.def
===================================================================
--- gcc/builtin-types.def	(revision 274238)
+++ gcc/builtin-types.def	(working copy)
@@ -811,6 +811,7 @@ DEF_FUNCTION_TYPE_VAR_3 (BT_FN_SSIZE_STRING_SIZE_C
 			 BT_SSIZE, BT_STRING, BT_SIZE, BT_CONST_STRING)
 DEF_FUNCTION_TYPE_VAR_3 (BT_FN_INT_FILEPTR_INT_CONST_STRING_VAR,
 			 BT_INT, BT_FILEPTR, BT_INT, BT_CONST_STRING)
+DEF_FUNCTION_TYPE_VAR_3 (BT_FN_INT_INT_CONST_STRING_CONST_STRING, BT_INT, BT_INT, BT_CONST_STRING, BT_CONST_STRING)
 
 DEF_FUNCTION_TYPE_VAR_4 (BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR,
 			 BT_INT, BT_STRING, BT_INT, BT_SIZE, BT_CONST_STRING)
Index: gcc/builtins.c
===================================================================
--- gcc/builtins.c	(revision 274238)
+++ gcc/builtins.c	(working copy)
@@ -72,6 +72,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "file-prefix-map.h" /* remap_macro_filename()  */
 #include "gomp-constants.h"
 #include "omp-general.h"
+#include "opts.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -178,6 +179,7 @@ static tree fold_builtin_strcspn (location_t, tree
 static rtx expand_builtin_object_size (tree);
 static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
 				      enum built_in_function);
+static rtx expand_builtin_warning (tree);
 static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static void maybe_emit_free_warning (tree);
@@ -8349,6 +8351,9 @@ expand_builtin (tree exp, rtx target, rtx subtarge
       mode = get_builtin_sync_mode (fcode - BUILT_IN_SPECULATION_SAFE_VALUE_1);
       return expand_speculation_safe_value (mode, exp, target, ignore);
 
+    case BUILT_IN_WARNING:
+      return expand_builtin_warning (exp);
+
     default:	/* just do library call, if unknown builtin */
       break;
     }
@@ -10486,6 +10491,46 @@ expand_builtin_memory_chk (tree exp, rtx target, m
     }
 }
 
+/* Expand a call to __builtin_warning into its constant result.  */
+
+static rtx
+expand_builtin_warning (tree exp)
+{
+  tree loc = CALL_EXPR_ARG (exp, 0);
+  tree opt = CALL_EXPR_ARG (exp, 1);
+  tree str = CALL_EXPR_ARG (exp, 2);
+
+  tree byteoff, memsize, decl;
+
+  /* The option index.  GCC internal calls pass in an an integer
+     but user calls pass in the option name as a string.  */
+  int iopt;
+  if (TREE_CODE (opt) == INTEGER_CST)
+    iopt = tree_to_shwi (opt);
+  else
+    {
+      /* Look up the option name.  */
+      tree byteoff, memsize, decl;
+      tree optstr = string_constant (opt, &byteoff, &memsize, &decl);
+      gcc_assert (optstr && decl == NULL);
+      const char *opt = TREE_STRING_POINTER (optstr);
+      size_t idx = find_opt (opt + 1, -1);
+      gcc_assert (idx != OPT_SPECIAL_unknown);
+      iopt = idx;
+    }
+
+  str = string_constant (str, &byteoff, &memsize, &decl);
+
+  /* FIXME: handle invalid arguments.  */
+  gcc_assert (str != 0 && decl == NULL);
+
+  location_t iloc = tree_to_shwi (loc);
+  if (iloc == 0)
+    iloc = EXPR_LOCATION (exp);
+  bool res = warning_at (iloc, iopt, "%s", TREE_STRING_POINTER (str));
+  return res ? const1_rtx : const0_rtx;
+}
+
 /* Emit warning if a buffer overflow is detected at compile time.  */
 
 static void
@@ -11137,6 +11182,8 @@ is_simple_builtin (tree decl)
       case BUILT_IN_EH_FILTER:
       case BUILT_IN_EH_POINTER:
       case BUILT_IN_EH_COPY_VALUES:
+	/* Trivially constant.  */
+      case BUILT_IN_WARNING:
 	return true;
 
       default:
Index: gcc/builtins.def
===================================================================
--- gcc/builtins.def	(revision 274238)
+++ gcc/builtins.def	(working copy)
@@ -1038,6 +1038,11 @@ DEF_GCC_BUILTIN (BUILT_IN_FILE, "FILE", BT_FN_CONS
 DEF_GCC_BUILTIN (BUILT_IN_FUNCTION, "FUNCTION", BT_FN_CONST_STRING, ATTR_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN (BUILT_IN_LINE, "LINE", BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
 
+/* __builtin_warning */
+DEF_GCC_BUILTIN (BUILT_IN_WARNING, "warning",
+		BT_FN_INT_INT_CONST_STRING_CONST_STRING,
+		ATTR_NOTHROW_NONNULL_LEAF)	
+
 /* Synchronization Primitives.  */
 #include "sync-builtins.def"
 
Index: gcc/gimple-ssa-isolate-paths.c
===================================================================
--- gcc/gimple-ssa-isolate-paths.c	(revision 274238)
+++ gcc/gimple-ssa-isolate-paths.c	(working copy)
@@ -115,6 +115,26 @@ insert_trap (gimple_stmt_iterator *si_p, tree op)
   *si_p = gsi_for_stmt (stmt);
 }
 
+/* Insert a call to __builtin_warning (LOC, OPT, STR) at IT, with LOC
+   set to the location of the statement at *IT.  */
+
+static void
+insert_warning (gimple_stmt_iterator *it, int opt, const char *str)
+{
+  gimple *stmt = gsi_stmt (*it);
+
+  location_t loc = gimple_location (stmt);
+  tree warnfn = builtin_decl_explicit (BUILT_IN_WARNING);
+  tree locarg = build_int_cst (integer_type_node, loc);
+  tree optarg = build_int_cst (integer_type_node, opt);
+  tree strarg = build_string_literal (strlen (str), str);
+  gcall *warn_stmt = gimple_build_call (warnfn, 3, locarg, optarg, strarg);
+  gimple_seq seq = NULL;
+  gimple_seq_add_stmt (&seq, warn_stmt);
+  gsi_insert_before (it, seq, GSI_NEW_STMT);
+  *it = gsi_for_stmt (stmt);
+}
+
 /* BB when reached via incoming edge E will exhibit undefined behavior
    at STMT.  Isolate and optimize the path which exhibits undefined
    behavior.
@@ -217,7 +237,11 @@ isolate_path (basic_block bb, basic_block duplicat
 	  update_stmt (ret);
 	}
       else
-	insert_trap (&si2, op);
+	{
+	  insert_warning (&si2, OPT_Wnonnull,
+			  "invalid use of a null pointer");
+	  insert_trap (&si2, op);
+	}
     }
 
   return duplicate;
@@ -262,7 +286,7 @@ is_divmod_with_given_divisor (gimple *stmt, tree d
    undefined behavior exposed by path splitting and that's reflected in
    the diagnostic.  */
 
-bool
+static bool
 stmt_uses_name_in_undefined_way (gimple *use_stmt, tree name, location_t loc)
 {
   /* If we are working with a non pointer type, then see
@@ -860,6 +884,8 @@ find_explicit_erroneous_behavior (void)
 
 	  if (stmt_uses_0_or_null_in_undefined_way (stmt))
 	    {
+	      insert_warning (&si, OPT_Wnonnull,
+			      "invalid use of a null pointer");
 	      insert_trap (&si, null_pointer_node);
 	      bb = gimple_bb (gsi_stmt (si));
 

Reply via email to