This adds a warning for returning the address of a nested function
where this is dangerous.


    c: Warn when returning nested functions that require a non-local context.
    
    This patch adds a mechanism to keep track whether nested functions require
    non-local context because they reference a variable of an enclosing scope
    or not.  This is used for extending the existing -Wreturn-address warning
    to also warn for such nested functions.  Certain exceptions are implemented
    for functions that do not requite a non-local context, because they 
reference
    only static variables, named constants, non-local variables in unevaluated
    sizeof, typeof or countof operators, or typedef.  The logic is based on the
    existing infrastructure for determining use of undeclared static functions.
    Finally, To make sure that no trampolines are generated even when not using
    optimization, we mark the exempt functions with TREE_NO_TRAMPOLINE.
    
    gcc/c/ChangeLog
            * c-tree.h: Add new macro C_DECL_NONLOCAL_CONTEXT and prototype
              for mark_decl_used.
            * c-typeck.cc (mark_decl_used): New function.
              (function_to_pointer_conversion): Mark exempt functions.
              (record_maybe_used_decl): Add `address' parameter.
              (build_external_ref): Change to mark_decl_used.
              (pop_maybe_used): Call mark_decl_used.
              (c_mark_addressable): Ditto.
              (c_finish_goto_label): Ditto.
              (c_finish_return): Add warning.
            * c-decl.cc (declspecs_add_type): Ditto.
    
    gcc/testsuite/ChangeLog
            * gcc.dg/Wreturn-nested-1.c: New test.
            * gcc.dg/Wreturn-nested-2.c: New test.
            * gcc.dg/Wtrampolines-2.c: New test.
            * gcc.dg/Wtrampolines-3.c: New test.

diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 7850365f35c..0e57b762919 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -12799,6 +12799,7 @@ declspecs_add_type (location_t loc, struct c_declspecs 
*specs,
     error_at (loc, "two or more data types in declaration specifiers");
   else if (TREE_CODE (type) == TYPE_DECL)
     {
+      mark_decl_used (type, false);
       specs->type = TREE_TYPE (type);
       if (TREE_TYPE (type) != error_mark_node)
        {
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index bb0b113754e..372fcbd8aa1 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -80,6 +80,11 @@ along with GCC; see the file COPYING3.  If not see
 /* For a PARM_DECL, nonzero if it was declared as an array.  */
 #define C_ARRAY_PARAMETER(NODE) DECL_LANG_FLAG_0 (NODE)
 
+/* For FUNCTION_DECLs, evaluates true if the decl is a nested
+   function that requires a non-local context.  */
+#define C_FUNC_NONLOCAL_CONTEXT(EXP)           \
+  DECL_LANG_FLAG_4 (FUNCTION_DECL_CHECK (EXP))
+
 /* For FUNCTION_DECLs, evaluates true if the decl is built-in but has
    been declared.  */
 #define C_DECL_DECLARED_BUILTIN(EXP)           \
@@ -826,6 +831,7 @@ extern tree build_array_ref (location_t, tree, tree);
 extern tree build_omp_array_section (location_t, tree, tree, tree);
 extern tree build_external_ref (location_t, tree, bool, tree *);
 extern void pop_maybe_used (bool);
+extern void mark_decl_used (tree, bool);
 extern struct c_expr c_expr_sizeof_expr (location_t, struct c_expr);
 extern struct c_expr c_expr_sizeof_type (location_t, struct c_type_name *);
 extern struct c_expr c_expr_countof_expr (location_t, struct c_expr);
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index d1d217e1998..887ba0074b7 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -133,7 +133,7 @@ static void set_nonincremental_init_from_string (tree, 
struct obstack *);
 static tree find_init_member (tree, struct obstack *);
 static void readonly_warning (tree, enum lvalue_use);
 static int lvalue_or_else (location_t, const_tree, enum lvalue_use);
-static void record_maybe_used_decl (tree);
+static void record_maybe_used_decl (tree, bool address);
 static bool comptypes_internal (const_tree, const_tree,
                                struct comptypes_data *data);
 static bool comptypes_check_for_composite (tree t1, tree t2);
@@ -2327,7 +2327,15 @@ function_to_pointer_conversion (location_t loc, tree exp)
 
   copy_warning (exp, orig_exp);
 
-  return build_unary_op (loc, ADDR_EXPR, exp, false);
+  tree exp2 = build_unary_op (loc, ADDR_EXPR, exp, false);
+
+  /* If the function is defined and known to not to require a non-local
+     context, make sure no trampoline is generated.  */
+  if (TREE_CODE (exp) == FUNCTION_DECL
+      && DECL_INITIAL (exp) && !C_FUNC_NONLOCAL_CONTEXT (exp))
+   TREE_NO_TRAMPOLINE (exp2) = 1;
+
+  return exp2;
 }
 
 /* Mark EXP as read, not just set, for set but not used -Wunused
@@ -3525,6 +3533,91 @@ build_omp_array_section (location_t loc, tree array, 
tree index, tree length)
   return build3_loc (loc, OMP_ARRAY_SECTION, sectype, array, index, length);
 }
 
+
+/* Record that REF is used.  This is relevant for undeclared static
+   function and declarations referenced from a non-local context.
+   ADDRESS indicates whether the address is taken.  */
+
+void
+mark_decl_used (tree ref, bool address)
+{
+  if (!ref || !DECL_P (ref) || in_alignof)
+    return;
+
+  /* Non-file-scope and non-local reference in nested function.  */
+  bool nonloc_p = current_function_decl && DECL_CONTEXT (ref)
+                 && DECL_CONTEXT (current_function_decl)
+                 && DECL_CONTEXT (ref) != current_function_decl;
+
+  /* An undeclared static function.  */
+  bool static_p = TREE_CODE (ref) == FUNCTION_DECL
+                 && DECL_INITIAL (ref) == NULL_TREE
+                 && DECL_EXTERNAL (ref)
+                 && !TREE_PUBLIC (ref);
+
+  if (!static_p && !nonloc_p)
+    return;
+
+  /* If we may be in an unevaluated context, delay the decision.  */
+  if (in_sizeof || in_typeof || in_countof)
+    return record_maybe_used_decl (ref, address);
+
+  if (static_p)
+    C_DECL_USED (ref) = 1;
+
+  if (nonloc_p)
+    DECL_NONLOCAL (ref) = 1;
+
+  /* Nothing to do anymore.  */
+  if (!nonloc_p || C_FUNC_NONLOCAL_CONTEXT (current_function_decl))
+    return;
+
+  /* Filter out the cases where referencing a non-local variable does not
+     require a non-local context passed via the static chain.  */
+  if (!C_TYPE_VARIABLY_MODIFIED (TREE_TYPE (ref)))
+    switch (TREE_CODE (ref))
+      {
+      case FUNCTION_DECL:
+       /* Use of another local function that requires no context is ok.  */
+       if (!C_FUNC_NONLOCAL_CONTEXT (ref) && DECL_INITIAL (ref))
+         return;
+       break;
+      case VAR_DECL:
+       /* Static variables and constexpr are ok, but for the later only
+          when the address is not taken.  */
+       if (TREE_STATIC (ref) || (C_DECL_DECLARED_CONSTEXPR (ref) && !address))
+         return;
+       break;
+      case TYPE_DECL:
+       /* A typedef is ok when not for a variably-modified type. */
+       return;
+      case CONST_DECL:
+       /* An enumeration constant is ok. */
+       return;
+      case PARM_DECL:
+       break;
+      case LABEL_DECL:
+       break;
+      default:
+       gcc_unreachable ();
+    }
+
+  /* Mark all parent functions up to the nesting level of the variable as
+     as needing the non-local context.  */
+  for (tree cont = current_function_decl; cont; cont = DECL_CONTEXT (cont))
+    {
+      if (cont == DECL_CONTEXT (ref))
+       break;
+
+      /* There should not be any other type of context used for function
+        except TRANSLATION_UNIT_DECL which we should be able to reach.  */
+      gcc_checking_assert (TREE_CODE (cont) == FUNCTION_DECL);
+
+      if (TREE_CODE (cont) == FUNCTION_DECL)
+       C_FUNC_NONLOCAL_CONTEXT (cont) = 1;
+    }
+}
+
 
 /* Build an external reference to identifier ID.  FUN indicates
    whether this will be used for a function call.  LOC is the source
@@ -3582,15 +3675,7 @@ build_external_ref (location_t loc, tree id, bool fun, 
tree *type)
       TREE_USED (ref) = 1;
     }
 
-  if (TREE_CODE (ref) == FUNCTION_DECL && !in_alignof)
-    {
-      if (!in_sizeof && !in_typeof && !in_countof)
-       C_DECL_USED (ref) = 1;
-      else if (DECL_INITIAL (ref) == NULL_TREE
-              && DECL_EXTERNAL (ref)
-              && !TREE_PUBLIC (ref))
-       record_maybe_used_decl (ref);
-    }
+  mark_decl_used (ref, false);
 
   if (TREE_CODE (ref) == CONST_DECL)
     {
@@ -3609,16 +3694,6 @@ build_external_ref (location_t loc, tree id, bool fun, 
tree *type)
       ref = DECL_INITIAL (ref);
       TREE_CONSTANT (ref) = 1;
     }
-  else if (current_function_decl != NULL_TREE
-          && !DECL_FILE_SCOPE_P (current_function_decl)
-          && (VAR_OR_FUNCTION_DECL_P (ref)
-              || TREE_CODE (ref) == PARM_DECL))
-    {
-      tree context = decl_function_context (ref);
-
-      if (context != NULL_TREE && context != current_function_decl)
-       DECL_NONLOCAL (ref) = 1;
-    }
   /* C99 6.7.4p3: An inline definition of a function with external
      linkage ... shall not contain a reference to an identifier with
      internal linkage.  */
@@ -3642,23 +3717,25 @@ struct maybe_used_decl
   tree decl;
   /* The level seen at (in_sizeof + in_typeof + in_countof).  */
   int level;
+  /* Seen in address-of.  */
+  bool address;
   /* The next one at this level or above, or NULL.  */
   struct maybe_used_decl *next;
 };
 
 static struct maybe_used_decl *maybe_used_decls;
 
-/* Record that DECL, an undefined static function reference seen
-   inside sizeof or typeof, might be used if the operand of sizeof is
-   a VLA type or the operand of typeof is a variably modified
-   type.  */
+/* Record that DECL, a reference seen inside sizeof or typeof, might be used
+   if the operand of sizeof is a VLA type or the operand of typeof is a 
variably
+   modified type.  */
 
 static void
-record_maybe_used_decl (tree decl)
+record_maybe_used_decl (tree decl, bool address)
 {
   struct maybe_used_decl *t = XOBNEW (&parser_obstack, struct maybe_used_decl);
   t->decl = decl;
   t->level = in_sizeof + in_typeof + in_countof;
+  t->address = address;
   t->next = maybe_used_decls;
   maybe_used_decls = t;
 }
@@ -3678,7 +3755,7 @@ pop_maybe_used (bool used)
       if (used)
        {
          if (cur_level == 0)
-           C_DECL_USED (p->decl) = 1;
+           mark_decl_used (p->decl, p->address);
          else
            p->level = cur_level;
        }
@@ -6092,6 +6169,7 @@ c_mark_addressable (tree exp, bool array_ref_p)
        /* FALLTHRU */
       case FUNCTION_DECL:
        TREE_ADDRESSABLE (x) = 1;
+       mark_decl_used (x, true);
        /* FALLTHRU */
       default:
        return true;
@@ -12852,6 +12930,7 @@ c_finish_goto_label (location_t loc, tree label)
   if (!decl)
     return NULL_TREE;
   TREE_USED (decl) = 1;
+  mark_decl_used (decl, false);
   {
     add_stmt (build_predict_expr (PRED_GOTO, NOT_TAKEN));
     tree t = build1 (GOTO_EXPR, void_type_node, decl);
@@ -13027,14 +13106,20 @@ c_finish_return (location_t loc, tree retval, tree 
origtype, bool musttail_p)
 
              if (DECL_P (inner)
                  && !DECL_EXTERNAL (inner)
-                 && !TREE_STATIC (inner)
                  && DECL_CONTEXT (inner) == current_function_decl
                  && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE 
(current_function_decl))))
                {
                  if (TREE_CODE (inner) == LABEL_DECL)
                    warning_at (loc, OPT_Wreturn_local_addr,
                                "function returns address of label");
-                 else
+                 else if (TREE_CODE (inner) == FUNCTION_DECL
+                          && (C_FUNC_NONLOCAL_CONTEXT (inner)
+                              || !DECL_INITIAL (inner)))
+                   warning_at (loc, OPT_Wreturn_local_addr,
+                               "function returns address of nested function "
+                               "referencing local context");
+                 else if (TREE_CODE (inner) != FUNCTION_DECL
+                          && !TREE_STATIC (inner))
                    {
                      warning_at (loc, OPT_Wreturn_local_addr,
                                  "function returns address of local variable");
diff --git a/gcc/testsuite/gcc.dg/Wreturn-nested-1.c 
b/gcc/testsuite/gcc.dg/Wreturn-nested-1.c
new file mode 100644
index 00000000000..36413ffa849
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-nested-1.c
@@ -0,0 +1,56 @@
+/* { dg-do compile } */
+/* { dg-options "-Wreturn-local-addr" } */
+
+typedef int (*fun_t)();
+
+fun_t foo()
+{
+       int bar() { return 1; }
+       return bar;
+}
+
+fun_t foo0(int n)
+{
+       int bar() { return sizeof(n); }
+       return bar;
+}
+
+fun_t foo1()
+{
+       constexpr int i = 1;
+       int bar() { return i; }
+       return bar;
+}
+
+fun_t foo2()
+{
+       static int i = 1;
+       int bar() { return i; }
+       return bar;
+}
+
+fun_t foo3()
+{
+       static int i = 1;
+       int bar() { return i; }
+       int bar2() { return bar(); }
+       return bar2;
+}
+
+fun_t foo4()
+{
+       enum { E = 1 };
+       int bar() { return E; }
+       return bar;
+}
+
+fun_t foo5()
+{
+       int bar() {
+               int n = 3;
+               int foo() { return n; };
+               return foo();
+       }
+       return bar;
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wreturn-nested-2.c 
b/gcc/testsuite/gcc.dg/Wreturn-nested-2.c
new file mode 100644
index 00000000000..74f72fa6849
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-nested-2.c
@@ -0,0 +1,81 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target trampolines } */
+/* { dg-options "-Wreturn-local-addr" } */
+/* { dg-skip-if "" { *-*-darwin2* } } */
+
+typedef int (*fun_t)();
+
+fun_t foo4(int n)
+{
+       typedef int a[n];
+       int bar() { return sizeof(a); }         
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo5(int n)
+{
+       int a[n];
+       int bar() { return sizeof(a); }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo6(int n)
+{
+       typedef int a[n];
+       int bar() { a x; return 1; }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo7(int n)
+{
+       typedef int a[n];
+       int bar() { (a*)nullptr; return 1; }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo8(int n)
+{
+       auto int bar();
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+       int bar() { return n; }
+}
+
+fun_t foo9(int n)
+{
+       int bar() { return n; }
+       int bar2() { return bar(); }
+       return bar2;                            /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo10(int n)
+{
+       int bar()
+       {
+               int bar2() { return n; }
+               return bar2();
+       }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo11()
+{
+       constexpr int a = 1;
+       int bar() { const int *ap = &a; return *ap; }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo12(int n)
+{
+       static int (*a)[n];
+       int bar() { typeof(a) b; return sizeof *b; }
+       return bar;                             /* { dg-warning "address of 
nested function" } */
+}
+
+fun_t foo13(int n)
+{
+       int (*bar())[n] { return nullptr; }
+       int bar2() { return sizeof *(*bar)(); }
+       return bar2;                            /* { dg-warning "address of 
nested function" } */
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/Wtrampolines-2.c 
b/gcc/testsuite/gcc.dg/Wtrampolines-2.c
new file mode 100644
index 00000000000..bc2b9314b27
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wtrampolines-2.c
@@ -0,0 +1,62 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target trampolines } */
+/* { dg-options "-O0 -Wtrampolines" } */
+/* { dg-skip-if "" { *-*-darwin2* } } */
+
+
+
+// Test that we do not create a trampoline even without optimization.
+
+void g(int (*)());
+
+void foo()
+{
+       int bar() { return 1; }
+       g(bar);
+}
+
+void foo0(int n)
+{
+       int bar() { return sizeof(n); }
+       g(bar); 
+}
+
+void foo1()
+{
+       constexpr int i = 1;
+       int bar() { return i; }
+       g(bar); 
+}
+
+void foo2()
+{
+       static int i = 1;
+       int bar() { return i; }
+       g(bar); 
+}
+
+void foo3()
+{
+       static int i = 1;
+       int bar() { return i; }
+       int bar2() { return bar(); }
+       g(bar2);        
+}
+
+void foo4()
+{
+       enum { E = 1 };
+       int bar() { return E; }
+       g(bar);
+}
+
+void foo5()
+{
+       int bar() {
+               int n = 3;
+               int foo() { return n; };
+               return foo();
+       }
+       g(bar);
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wtrampolines-3.c 
b/gcc/testsuite/gcc.dg/Wtrampolines-3.c
new file mode 100644
index 00000000000..43d6bcefcf8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wtrampolines-3.c
@@ -0,0 +1,88 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target trampolines } */
+/* { dg-options "-Wtrampolines" } */
+/* { dg-skip-if "" { *-*-darwin2* } } */
+
+
+// Test that we do generate trampolines where this is required.
+
+void g(int (*)());
+
+void foo4(int n)
+{
+       typedef int a[n];
+       int bar() { return sizeof(a); }         /* { dg-warning "trampoline 
generated" } */
+       g(bar); 
+}
+
+void foo5(int n)
+{
+       int a[n];
+       int bar() { return sizeof(a); }         /* { dg-warning "trampoline 
generated" } */
+       g(bar); 
+}
+
+void foo6(int n)
+{
+       typedef int a[n];
+       int bar() { a x; return 1; }            /* { dg-warning "trampoline 
generated" } */
+       g(bar); 
+}
+
+void foo7(int n)
+{
+       typedef int a[n];
+       int bar() { (a*)nullptr; return 1; }    /* { dg-warning "trampoline 
generated" } */
+       g(bar); 
+}
+
+void foo8(int n)
+{
+       auto int bar();
+       g(bar); 
+       int bar() { return n; }                 /* { dg-warning "trampoline 
generated" } */
+}
+
+void foo9(int n)
+{
+       int bar() { return n; }
+       int bar2() { return bar(); }            /* { dg-warning "trampoline 
generated" } */
+       g(bar2);
+}
+
+void foo10(int n)
+{
+       int bar()                               /* { dg-warning "trampoline 
generated" } */
+       {
+               int bar2() { return n; }
+               return bar2();
+       }
+       g(bar);                 
+}
+
+void foo11()
+{
+       constexpr int a = 1;
+       int bar()                               /* { dg-warning "trampoline 
generated" } */
+       {
+               const int *ap = &a;
+               return *ap; 
+       }
+       g(bar);
+}
+
+void foo12(int n)
+{
+       static int (*a)[n];
+       int bar() { typeof(a) b; return sizeof *b; }    /* { dg-warning 
"trampoline generated" } */
+       g(bar); 
+}
+
+void foo13(int n)
+{
+       int (*bar())[n] { return nullptr; }
+       int bar2() { return sizeof *(*bar)(); }         /* { dg-warning 
"trampoline generated" } */
+       g(bar2);
+}
+
+

Reply via email to