Skip to content

Commit

Permalink
c++: block-scope externs get an alias [PR95677,PR31775,PR95677]
Browse files Browse the repository at this point in the history
This patch improves block-scope extern handling by always injecting a
hidden copy into the enclosing namespace (or using a match already
there).  This hidden copy will be revealed if the user explicitly
declares it later.  We can get from the DECL_LOCAL_DECL_P local extern
to the alias via DECL_LOCAL_DECL_ALIAS.  This fixes several bugs and
removes the kludgy per-function extern_decl_map.  We only do this
pushing for non-dependent local externs -- dependent ones will be
pushed during instantiation.

User code that expected to be able to handle incompatible local
externs in different block-scopes will no longer work.  That code is
ill-formed.  (always was, despite what 31775 claimed).  I had to
adjust a number of testcases that fell into this.

I tried using DECL_VALUE_EXPR, but that didn't work out.  Due to
constexpr requirements we have to do the replacement very late (it
happens in the gimplifier).   Consider:

extern int l[]; // #1
constexpr bool foo ()
{
   extern int l[3]; // this does not complete the type of decl #1
   constexpr int *p = &l[2]; // ok
   return !p;
}

This requirement, coupled with our use of the common folding machinery
makes pr97306 hard to fix, as we end up with an expression containing
the two different decls for 'l', and only the c++ FE knows how to
reconcile those.  I punted on this.

	gcc/cp/
	* cp-tree.h (struct language_function): Delete extern_decl_map.
	(DECL_LOCAL_DECL_ALIAS): New.
	* name-lookup.h (is_local_extern): Delete.
	* name-lookup.c (set_local_extern_decl_linkage): Replace with ...
	(push_local_extern_decl): ... this new function.
	(do_pushdecl): Call new function after pushing new decl.  Unhide
	hidden non-functions.
	(is_local_extern): Delete.
	* decl.c (layout_var_decl): Do not allow VLA local externs.
	* decl2.c (mark_used): Also mark DECL_LOCAL_DECL_ALIAS. Drop old
	local-extern treatment.
	* parser.c (cp_parser_oacc_declare): Deal with local extern aliases.
	* pt.c (tsubst_expr): Adjust local extern instantiation.
	* cp-gimplify.c (cp_genericize_r): Remap DECL_LOCAL_DECLs.
	gcc/testsuite/
	* g++.dg/cpp0x/lambda/lambda-sfinae1.C: Avoid ill-formed local extern
	* g++.dg/init/pr42844.C: Add expected error.
	* g++.dg/lookup/extern-redecl1.C: Likewise.
	* g++.dg/lookup/koenig15.C: Avoid ill-formed.
	* g++.dg/lto/pr95677.C: New.
	* g++.dg/other/nested-extern-1.C: Correct expected behabviour.
	* g++.dg/other/nested-extern-2.C: Likewise.
	* g++.dg/other/nested-extern.cc: Split ...
	* g++.dg/other/nested-extern-1.cc: ... here ...
	* g++.dg/other/nested-extern-2.cc: ... here.
	* g++.dg/template/scope5.C: Avoid ill-formed
	* g++.old-deja/g++.law/missed-error2.C: Allow extension.
	* g++.old-deja/g++.pt/crash3.C: Add expected error.
  • Loading branch information
urnathan committed Oct 7, 2020
1 parent e089e43 commit 4e62aca
Show file tree
Hide file tree
Showing 21 changed files with 206 additions and 178 deletions.
26 changes: 11 additions & 15 deletions gcc/cp/cp-gimplify.c
Original file line number Diff line number Diff line change
Expand Up @@ -980,21 +980,17 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)

/* Map block scope extern declarations to visible declarations with the
same name and type in outer scopes if any. */
if (cp_function_chain->extern_decl_map
&& VAR_OR_FUNCTION_DECL_P (stmt)
&& DECL_EXTERNAL (stmt))
{
struct cxx_int_tree_map *h, in;
in.uid = DECL_UID (stmt);
h = cp_function_chain->extern_decl_map->find_with_hash (&in, in.uid);
if (h)
{
*stmt_p = h->to;
TREE_USED (h->to) |= TREE_USED (stmt);
*walk_subtrees = 0;
return NULL;
}
}
if (VAR_OR_FUNCTION_DECL_P (stmt) && DECL_LOCAL_DECL_P (stmt))
if (tree alias = DECL_LOCAL_DECL_ALIAS (stmt))
{
if (alias != error_mark_node)
{
*stmt_p = alias;
TREE_USED (alias) |= TREE_USED (stmt);
}
*walk_subtrees = 0;
return NULL;
}

if (TREE_CODE (stmt) == INTEGER_CST
&& TYPE_REF_P (TREE_TYPE (stmt))
Expand Down
6 changes: 5 additions & 1 deletion gcc/cp/cp-tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -1926,7 +1926,6 @@ struct GTY(()) language_function {
/* Tracking possibly infinite loops. This is a vec<tree> only because
vec<bool> doesn't work with gtype. */
vec<tree, va_gc> *infinite_loops;
hash_table<cxx_int_tree_map_hasher> *extern_decl_map;
};

/* The current C++-specific per-function global variables. */
Expand Down Expand Up @@ -2697,6 +2696,7 @@ struct GTY(()) lang_decl_min {
In a lambda-capture proxy VAR_DECL, this is DECL_CAPTURED_VARIABLE.
In a function-scope TREE_STATIC VAR_DECL or IMPLICIT_TYPEDEF_P TYPE_DECL,
this is DECL_DISCRIMINATOR.
In a DECL_LOCAL_DECL_P decl, this is the namespace decl it aliases.
Otherwise, in a class-scope DECL, this is DECL_ACCESS. */
tree access;
};
Expand Down Expand Up @@ -4023,6 +4023,10 @@ more_aggr_init_expr_args_p (const aggr_init_expr_arg_iterator *iter)
#define DECL_LOCAL_DECL_P(NODE) \
DECL_LANG_FLAG_0 (VAR_OR_FUNCTION_DECL_CHECK (NODE))

/* The namespace-scope decl a DECL_LOCAL_DECL_P aliases. */
#define DECL_LOCAL_DECL_ALIAS(NODE) \
DECL_ACCESS ((gcc_checking_assert (DECL_LOCAL_DECL_P (NODE)), NODE))

/* Nonzero if NODE is the target for genericization of 'return' stmts
in constructors/destructors of targetm.cxx.cdtor_returns_this targets. */
#define LABEL_DECL_CDTOR(NODE) \
Expand Down
3 changes: 2 additions & 1 deletion gcc/cp/decl.c
Original file line number Diff line number Diff line change
Expand Up @@ -5830,7 +5830,8 @@ layout_var_decl (tree decl)
&& DECL_SIZE (decl) != NULL_TREE
&& ! TREE_CONSTANT (DECL_SIZE (decl)))
{
if (TREE_CODE (DECL_SIZE (decl)) == INTEGER_CST)
if (TREE_CODE (DECL_SIZE (decl)) == INTEGER_CST
&& !DECL_LOCAL_DECL_P (decl))
constant_expression_warning (DECL_SIZE (decl));
else
{
Expand Down
25 changes: 17 additions & 8 deletions gcc/cp/decl2.c
Original file line number Diff line number Diff line change
Expand Up @@ -5567,6 +5567,22 @@ mark_used (tree decl, tsubst_flags_t complain)
return false;
}

if (VAR_OR_FUNCTION_DECL_P (decl) && DECL_LOCAL_DECL_P (decl))
{
if (!DECL_LANG_SPECIFIC (decl))
/* An unresolved dependent local extern. */
return true;

DECL_ODR_USED (decl) = 1;
auto alias = DECL_LOCAL_DECL_ALIAS (decl);
if (!alias || alias == error_mark_node)
return true;

/* Process the underlying decl. */
decl = alias;
TREE_USED (decl) = true;
}

cp_warn_deprecated_use (decl, complain);

/* We can only check DECL_ODR_USED on variables or functions with
Expand Down Expand Up @@ -5650,14 +5666,7 @@ mark_used (tree decl, tsubst_flags_t complain)
&& !DECL_ARTIFICIAL (decl)
&& !decl_defined_p (decl)
&& no_linkage_check (TREE_TYPE (decl), /*relaxed_p=*/false))
{
if (is_local_extern (decl))
/* There's no way to define a local extern, and adding it to
the vector interferes with GC, so give an error now. */
no_linkage_error (decl);
else
vec_safe_push (no_linkage_decls, decl);
}
vec_safe_push (no_linkage_decls, decl);

if (TREE_CODE (decl) == FUNCTION_DECL
&& DECL_DECLARED_INLINE_P (decl)
Expand Down
186 changes: 73 additions & 113 deletions gcc/cp/name-lookup.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ along with GCC; see the file COPYING3. If not see

static cxx_binding *cxx_binding_make (tree value, tree type);
static cp_binding_level *innermost_nonclass_level (void);
static tree do_pushdecl_with_scope (tree x, cp_binding_level *, bool hiding);
static void set_identifier_type_value_with_scope (tree id, tree decl,
cp_binding_level *b);
static name_hint maybe_suggest_missing_std_header (location_t location,
Expand Down Expand Up @@ -2921,108 +2922,66 @@ set_decl_context_in_fn (tree ctx, tree decl)
DECL_CONTEXT (decl) = ctx;
}

/* DECL is a local-scope decl with linkage. SHADOWED is true if the
name is already bound at the current level.
[basic.link] If there is a visible declaration of an entity with
linkage having the same name and type, ignoring entities declared
outside the innermost enclosing namespace scope, the block scope
declaration declares that same entity and receives the linkage of
the previous declaration.
Also, make sure that this decl matches any existing external decl
in the enclosing namespace. */
/* DECL is a local extern decl. Find or create the namespace-scope
decl that it aliases. Also, determines the linkage of DECL. */

static void
set_local_extern_decl_linkage (tree decl, bool shadowed)
push_local_extern_decl_alias (tree decl)
{
tree ns_value = decl; /* Unique marker. */

if (!shadowed)
{
tree loc_value = innermost_non_namespace_value (DECL_NAME (decl));
if (!loc_value)
{
ns_value
= find_namespace_value (current_namespace, DECL_NAME (decl));
loc_value = ns_value;
}
if (loc_value == error_mark_node
/* An ambiguous lookup. */
|| (loc_value && TREE_CODE (loc_value) == TREE_LIST))
loc_value = NULL_TREE;

for (ovl_iterator iter (loc_value); iter; ++iter)
if (!iter.hidden_p ()
&& (TREE_STATIC (*iter) || DECL_EXTERNAL (*iter))
&& decls_match (*iter, decl))
{
/* The standard only says that the local extern inherits
linkage from the previous decl; in particular, default
args are not shared. Add the decl into a hash table to
make sure only the previous decl in this case is seen
by the middle end. */
struct cxx_int_tree_map *h;

/* We inherit the outer decl's linkage. But we're a
different decl. */
TREE_PUBLIC (decl) = TREE_PUBLIC (*iter);

if (cp_function_chain->extern_decl_map == NULL)
cp_function_chain->extern_decl_map
= hash_table<cxx_int_tree_map_hasher>::create_ggc (20);

h = ggc_alloc<cxx_int_tree_map> ();
h->uid = DECL_UID (decl);
h->to = *iter;
cxx_int_tree_map **loc = cp_function_chain->extern_decl_map
->find_slot (h, INSERT);
*loc = h;
break;
}
}
if (dependent_type_p (TREE_TYPE (decl)))
return;
/* EH specs were not part of the function type prior to c++17, but
we still can't go pushing dependent eh specs into the namespace. */
if (cxx_dialect < cxx17
&& TREE_CODE (decl) == FUNCTION_DECL
&& (value_dependent_expression_p
(TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)))))
return;

if (TREE_PUBLIC (decl))
{
/* DECL is externally visible. Make sure it matches a matching
decl in the namespace scope. We only really need to check
this when inserting the decl, not when we find an existing
match in the current scope. However, in practice we're
going to be inserting a new decl in the majority of cases --
who writes multiple extern decls for the same thing in the
same local scope? Doing it here often avoids a duplicate
namespace lookup. */
gcc_checking_assert (!DECL_LANG_SPECIFIC (decl)
|| !DECL_TEMPLATE_INFO (decl));
if (DECL_LANG_SPECIFIC (decl) && DECL_LOCAL_DECL_ALIAS (decl))
/* We're instantiating a non-dependent local decl, it already
knows the alias. */
return;

/* Avoid repeating a lookup. */
if (ns_value == decl)
ns_value = find_namespace_value (current_namespace, DECL_NAME (decl));
tree alias = NULL_TREE;

if (ns_value == error_mark_node
|| (ns_value && TREE_CODE (ns_value) == TREE_LIST))
ns_value = NULL_TREE;
if (DECL_SIZE (decl) && !TREE_CONSTANT (DECL_SIZE (decl)))
/* Do not let a VLA creep into a namespace. Diagnostic will be
emitted in layout_var_decl later. */
alias = error_mark_node;
else
{
/* First look for a decl that matches. */
tree ns = CP_DECL_CONTEXT (decl);
tree binding = find_namespace_value (ns, DECL_NAME (decl));

for (ovl_iterator iter (ns_value); iter; ++iter)
{
tree other = *iter;

if (!(TREE_PUBLIC (other) || DECL_EXTERNAL (other)))
; /* Not externally visible. */
else if (DECL_EXTERN_C_P (decl) && DECL_EXTERN_C_P (other))
; /* Both are extern "C", we'll check via that mechanism. */
else if (TREE_CODE (other) != TREE_CODE (decl)
|| ((VAR_P (decl) || matching_fn_p (other, decl))
&& !comptypes (TREE_TYPE (decl), TREE_TYPE (other),
COMPARE_REDECLARATION)))
if (binding && TREE_CODE (binding) != TREE_LIST)
for (ovl_iterator iter (binding); iter; ++iter)
if (decls_match (*iter, decl))
{
auto_diagnostic_group d;
if (permerror (DECL_SOURCE_LOCATION (decl),
"local external declaration %q#D", decl))
inform (DECL_SOURCE_LOCATION (other),
"does not match previous declaration %q#D", other);
alias = *iter;
break;
}

if (!alias)
{
/* No existing namespace-scope decl. Make one. */
alias = copy_decl (decl);

/* This is the real thing. */
DECL_LOCAL_DECL_P (alias) = false;

/* Expected default linkage is from the namespace. */
TREE_PUBLIC (alias) = TREE_PUBLIC (ns);
alias = do_pushdecl_with_scope (alias, NAMESPACE_LEVEL (ns),
/* hiding= */true);
}
}

retrofit_lang_decl (decl);
DECL_LOCAL_DECL_ALIAS (decl) = alias;
}

/* Record DECL as belonging to the current lexical scope. Check for
Expand Down Expand Up @@ -3080,10 +3039,6 @@ do_pushdecl (tree decl, bool hiding)
old = binding->value;
}

if (current_function_decl && VAR_OR_FUNCTION_DECL_P (decl)
&& DECL_EXTERNAL (decl))
set_local_extern_decl_linkage (decl, old != NULL_TREE);

if (old == error_mark_node)
old = NULL_TREE;

Expand Down Expand Up @@ -3115,6 +3070,16 @@ do_pushdecl (tree decl, bool hiding)
/* We need to check and register the decl now. */
check_extern_c_conflict (match);
}
else if (slot && !hiding
&& STAT_HACK_P (*slot) && STAT_DECL_HIDDEN_P (*slot))
{
/* Unhide the non-function. */
gcc_checking_assert (old == match);
if (!STAT_TYPE (*slot))
*slot = match;
else
STAT_DECL (*slot) = match;
}
return match;
}

Expand Down Expand Up @@ -3190,12 +3155,21 @@ do_pushdecl (tree decl, bool hiding)
if (!instantiating_current_function_p ())
record_locally_defined_typedef (decl);
}
else if (VAR_P (decl))
maybe_register_incomplete_var (decl);
else
{
if (VAR_P (decl) && !DECL_LOCAL_DECL_P (decl))
maybe_register_incomplete_var (decl);

if (VAR_OR_FUNCTION_DECL_P (decl))
{
if (DECL_LOCAL_DECL_P (decl)
&& TREE_CODE (CP_DECL_CONTEXT (decl)) == NAMESPACE_DECL)
push_local_extern_decl_alias (decl);

if ((VAR_P (decl) || TREE_CODE (decl) == FUNCTION_DECL)
&& DECL_EXTERN_C_P (decl))
check_extern_c_conflict (decl);
if (DECL_EXTERN_C_P (decl))
check_extern_c_conflict (decl);
}
}
}
else
add_decl_to_level (level, decl);
Expand Down Expand Up @@ -6871,20 +6845,6 @@ lookup_elaborated_type (tree name, TAG_how how)
return ret;
}

/* Returns true iff DECL is a block-scope extern declaration of a function
or variable. We will already have determined validity of the decl
when pushing it. So we do not have to redo that lookup. */

bool
is_local_extern (tree decl)
{
if ((TREE_CODE (decl) == FUNCTION_DECL
|| TREE_CODE (decl) == VAR_DECL))
return DECL_LOCAL_DECL_P (decl);

return false;
}

/* The type TYPE is being declared. If it is a class template, or a
specialization of a class template, do any processing required and
perform error-checking. If IS_FRIEND is nonzero, this TYPE is
Expand Down
1 change: 0 additions & 1 deletion gcc/cp/name-lookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ extern tree lookup_qualified_name (tree scope, tree name,
extern tree lookup_qualified_name (tree scope, const char *name,
LOOK_want = LOOK_want::NORMAL,
bool = true);
extern bool is_local_extern (tree);
extern bool pushdecl_class_level (tree);
extern tree pushdecl_namespace_level (tree, bool hiding = false);
extern bool push_class_level_binding (tree, tree);
Expand Down
14 changes: 14 additions & 0 deletions gcc/cp/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -41176,6 +41176,10 @@ cp_parser_oacc_declare (cp_parser *parser, cp_token *pragma_tok)
}

if (!found_in_scope)
/* This seems to ignore the existence of cleanup scopes?
What is the meaning for local extern decls? The local
extern is in this scope, but it is referring to a decl that
is namespace scope. */
for (tree d = current_binding_level->names; d; d = TREE_CHAIN (d))
if (d == decl)
{
Expand Down Expand Up @@ -41205,6 +41209,16 @@ cp_parser_oacc_declare (cp_parser *parser, cp_token *pragma_tok)
{
tree id;

if (DECL_LOCAL_DECL_P (decl))
/* We need to mark the aliased decl, as that is the entity
that is being referred to. This won't work for
dependent variables, but it didn't work for them before
DECL_LOCAL_DECL_P was a thing either. But then
dependent local extern variable decls are as rare as
hen's teeth. */
if (auto alias = DECL_LOCAL_DECL_ALIAS (decl))
decl = alias;

if (OMP_CLAUSE_MAP_KIND (t) == GOMP_MAP_LINK)
id = get_identifier ("omp declare target link");
else
Expand Down
Loading

0 comments on commit 4e62aca

Please sign in to comment.