diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index be7916e957d..440287f3f71 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1485,6 +1485,10 @@ Wtemplate-id-cdtor C++ ObjC++ Var(warn_template_id_cdtor) Warning Warn about simple-template-id in a constructor or destructor. +Wtemplate-names-tu-local +C++ ObjC++ Var(warn_template_names_tu_local) Warning EnabledBy(Wextra) +Warn about templates naming TU-local entities in a module. + Wterminate C++ ObjC++ Warning Var(warn_terminate) Init(1) Warn if a throw expression will always result in a call to terminate(). diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc index 6ae021f07b0..c0c45410710 100644 --- a/gcc/cp/cp-objcp-common.cc +++ b/gcc/cp/cp-objcp-common.cc @@ -233,6 +233,7 @@ cp_tree_size (enum tree_code code) case ASSERTION_STMT: return sizeof (tree_exp); case PRECONDITION_STMT: return sizeof (tree_exp); case POSTCONDITION_STMT: return sizeof (tree_exp); + case TU_LOCAL_ENTITY: return sizeof (tree_tu_local_entity); default: switch (TREE_CODE_CLASS (code)) { diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index c4da7026eea..d7fd8dcac1a 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -575,6 +575,12 @@ DEFTREECODE (ASSERTION_STMT, "assertion_stmt", tcc_statement, 3) DEFTREECODE (PRECONDITION_STMT, "precondition_stmt", tcc_statement, 3) DEFTREECODE (POSTCONDITION_STMT, "postcondition_stmt", tcc_statement, 4) +/* A reference to a translation-unit local entity. + + This is emitted by modules streaming when writing a TU-local entity that + wasn't an exposure (e.g. in a non-inline function template). */ +DEFTREECODE (TU_LOCAL_ENTITY, "tu_local_entity", tcc_exceptional, 0) + /* Local variables: mode:c diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 32e6b014b67..c60d0ac014e 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -1827,6 +1827,22 @@ check_constraint_info (tree t) it for unscoped enums. */ #define DECL_MODULE_EXPORT_P(NODE) TREE_LANG_FLAG_3 (NODE) +/* Represents a streamed-in translation-unit-local entity. Any use of + this node when instantiating a template should emit an error. */ +struct GTY(()) tree_tu_local_entity { + struct tree_base base; + tree name; + location_t loc; +}; + +/* The name of a translation-unit-local entity. */ +#define TU_LOCAL_ENTITY_NAME(NODE) \ + (((struct tree_tu_local_entity *)TU_LOCAL_ENTITY_CHECK (NODE))->name) + +/* The source location of the translation-unit-local entity. */ +#define TU_LOCAL_ENTITY_LOCATION(NODE) \ + (((struct tree_tu_local_entity *)TU_LOCAL_ENTITY_CHECK (NODE))->loc) + /* The list of local parameters introduced by this requires-expression, in the form of a chain of PARM_DECLs. */ @@ -1860,7 +1876,8 @@ enum cp_tree_node_structure_enum { TS_CP_LAMBDA_EXPR, TS_CP_TEMPLATE_INFO, TS_CP_CONSTRAINT_INFO, - TS_CP_USERDEF_LITERAL + TS_CP_USERDEF_LITERAL, + TS_CP_TU_LOCAL_ENTITY }; /* The resulting tree type. */ @@ -1891,6 +1908,8 @@ union GTY((desc ("cp_tree_node_structure (&%h)"), constraint_info; struct tree_userdef_literal GTY ((tag ("TS_CP_USERDEF_LITERAL"))) userdef_literal; + struct tree_tu_local_entity GTY ((tag ("TS_CP_TU_LOCAL_ENTITY"))) + tu_local_entity; }; @@ -5141,18 +5160,19 @@ get_vec_init_expr (tree t) tree is converted to C++ class hiearchy. */ #define DECL_TEMPLATE_RESULT(NODE) \ ((struct tree_template_decl *)CONST_CAST_TREE(TEMPLATE_DECL_CHECK (NODE)))->result -/* For a function template at namespace scope, DECL_TEMPLATE_INSTANTIATIONS - lists all instantiations and specializations of the function so that - tsubst_friend_function can reassign them to another template if we find - that the namespace-scope template is really a partial instantiation of a - friend template. +/* For a forward-declared function template at namespace scope, or for any + function template in an exporting module, DECL_TEMPLATE_INSTANTIATIONS lists + all instantiations and specializations of the function so that + tsubst_friend_function can reassign them to another template if we find that + the namespace-scope template is really a partial instantiation of a friend + template. - For a class template the DECL_TEMPLATE_INSTANTIATIONS lists holds - all instantiations and specializations of the class type, including - partial instantiations and partial specializations, so that if we - explicitly specialize a partial instantiation we can walk the list - in maybe_process_partial_specialization and reassign them or complain - as appropriate. + For a class or variable template the DECL_TEMPLATE_INSTANTIATIONS lists + holds all instantiations and specializations, including partial + instantiations and partial specializations, so that if we explicitly + specialize a partial instantiation we can walk the list in + maybe_process_partial_specialization and reassign them or complain as + appropriate. In both cases, the TREE_PURPOSE of each node contains the arguments used; the TREE_VALUE contains the generated variable. The template diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 09158b0f0e6..6774d472f19 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -2353,6 +2353,8 @@ private: DB_DEFN_BIT = DB_KIND_BIT + DB_KIND_BITS, DB_IS_PENDING_BIT, /* Is a maybe-pending entity. */ DB_TU_LOCAL_BIT, /* It is a TU-local entity. */ + DB_REFS_TU_LOCAL_BIT, /* Refers to a TU-local entity (but is not + necessarily an exposure.) */ DB_EXPOSURE_BIT, /* Exposes a TU-local entity. */ DB_IMPORTED_BIT, /* An imported entity. */ DB_UNREACHED_BIT, /* A yet-to-be reached entity. */ @@ -2425,7 +2427,9 @@ public: public: bool has_defn () const { - return get_flag_bit (); + /* Never consider TU-local entities as having definitions, since + we will never be accessing them from importers anyway. */ + return get_flag_bit () && !is_tu_local (); } public: @@ -2443,6 +2447,10 @@ public: { return get_flag_bit (); } + bool refs_tu_local () const + { + return get_flag_bit (); + } bool is_exposure () const { return get_flag_bit (); @@ -2579,11 +2587,14 @@ public: unsigned section; /* When writing out, the section. */ bool reached_unreached; /* We reached an unreached entity. */ bool writing_merge_key; /* We're writing merge key information. */ + bool ignore_tu_local; /* In a context where referencing a TU-local + entity is not an exposure. */ public: hash (size_t size, hash *c = NULL) : parent (size), chain (c), current (NULL), section (0), - reached_unreached (false), writing_merge_key (false) + reached_unreached (false), writing_merge_key (false), + ignore_tu_local (false) { worklist.create (size); } @@ -2784,6 +2795,7 @@ static GTY((cache)) decl_tree_cache_map *imported_temploid_friends; /* Tree tags. */ enum tree_tag { tt_null, /* NULL_TREE. */ + tt_tu_local, /* A TU-local entity. */ tt_fixed, /* Fixed vector index. */ tt_node, /* By-value node. */ @@ -3068,6 +3080,8 @@ private: depset::hash *dep_hash; /* Dependency table. */ int ref_num; /* Back reference number. */ unsigned section; + bool writing_local_entities; /* Whether we might walk into a TU-local + entity we need to emit placeholders for. */ #if CHECKING_P int importedness; /* Checker that imports not occurring inappropriately. +ve imports ok, @@ -3097,6 +3111,18 @@ public: }; public: + /* The walk is used for three similar purposes: + + 1. The initial scan for dependencies. + 2. Once dependencies have been found, ordering them. + 3. Writing dependencies to file (streaming_p). + + For cases where it matters, these accessers can be used to determine + which state we're in. */ + bool is_initial_scan () const + { + return !streaming_p () && !is_key_order (); + } bool is_key_order () const { return dep_hash->is_key_order (); @@ -3137,6 +3163,10 @@ private: void tree_pair_vec (vec *); void tree_list (tree, bool has_purpose); +private: + bool has_tu_local_dep (tree) const; + tree find_tu_local_decl (tree); + public: /* Mark a node for by-value walking. */ void mark_by_value (tree); @@ -3174,7 +3204,7 @@ public: public: /* Serialize various definitions. */ - void write_definition (tree decl); + void write_definition (tree decl, bool refs_tu_local = false); void mark_declaration (tree decl, bool do_defn); private: @@ -3202,6 +3232,7 @@ private: static unsigned tree_val_count; static unsigned decl_val_count; static unsigned back_ref_count; + static unsigned tu_local_count; static unsigned null_count; }; } // anon namespace @@ -3210,12 +3241,14 @@ private: unsigned trees_out::tree_val_count; unsigned trees_out::decl_val_count; unsigned trees_out::back_ref_count; +unsigned trees_out::tu_local_count; unsigned trees_out::null_count; trees_out::trees_out (allocator *mem, module_state *state, depset::hash &deps, unsigned section) :parent (mem), state (state), tree_map (500), - dep_hash (&deps), ref_num (0), section (section) + dep_hash (&deps), ref_num (0), section (section), + writing_local_entities (false) { #if CHECKING_P importedness = 0; @@ -4355,6 +4388,9 @@ dumper::impl::nested_name (tree t) int origin = -1; tree name = NULL_TREE; + if (t && TREE_CODE (t) == TU_LOCAL_ENTITY) + t = TU_LOCAL_ENTITY_NAME (t); + if (t && TREE_CODE (t) == TREE_BINFO) t = BINFO_TYPE (t); @@ -4915,6 +4951,7 @@ trees_out::instrument () dump (" %u decl trees", decl_val_count); dump (" %u other trees", tree_val_count); dump (" %u back references", back_ref_count); + dump (" %u TU-local entities", tu_local_count); dump (" %u null trees", null_count); } } @@ -7873,6 +7910,17 @@ trees_out::decl_value (tree decl, depset *dep) || DECL_ORIGINAL_TYPE (decl) || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl))); + /* There's no need to walk any of the contents of a known TU-local entity, + since importers should never see any of it regardless. But make sure we + at least note its location so importers can use it for diagnostics. */ + if (dep && dep->is_tu_local ()) + { + gcc_checking_assert (is_initial_scan ()); + insert (decl, WK_value); + state->note_location (DECL_SOURCE_LOCATION (decl)); + return; + } + merge_kind mk = get_merge_kind (decl, dep); bool is_imported_temploid_friend = imported_temploid_friends->get (decl); @@ -8457,14 +8505,17 @@ trees_in::decl_value () /* Frob it to be ready for cloning. */ TREE_TYPE (inner) = DECL_ORIGINAL_TYPE (inner); DECL_ORIGINAL_TYPE (inner) = NULL_TREE; - set_underlying_type (inner); - if (tdef_flags & 2) + if (TREE_CODE (TREE_TYPE (inner)) != TU_LOCAL_ENTITY) { - /* Match instantiate_alias_template's handling. */ - tree type = TREE_TYPE (inner); - TYPE_DEPENDENT_P (type) = true; - TYPE_DEPENDENT_P_VALID (type) = true; - SET_TYPE_STRUCTURAL_EQUALITY (type); + set_underlying_type (inner); + if (tdef_flags & 2) + { + /* Match instantiate_alias_template's handling. */ + tree type = TREE_TYPE (inner); + TYPE_DEPENDENT_P (type) = true; + TYPE_DEPENDENT_P_VALID (type) = true; + SET_TYPE_STRUCTURAL_EQUALITY (type); + } } } @@ -8969,10 +9020,14 @@ trees_out::decl_node (tree decl, walk_kind ref) } tree_node (tpl); - /* Streaming TPL caused us to visit DECL and maybe its type. */ - gcc_checking_assert (TREE_VISITED (decl)); - if (DECL_IMPLICIT_TYPEDEF_P (decl)) - gcc_checking_assert (TREE_VISITED (TREE_TYPE (decl))); + /* Streaming TPL caused us to visit DECL and maybe its type, + if it wasn't TU-local. */ + if (CHECKING_P && !has_tu_local_dep (tpl)) + { + gcc_checking_assert (TREE_VISITED (decl)); + if (DECL_IMPLICIT_TYPEDEF_P (decl)) + gcc_checking_assert (TREE_VISITED (TREE_TYPE (decl))); + } return false; } @@ -8992,10 +9047,10 @@ trees_out::decl_node (tree decl, walk_kind ref) dep = dep_hash->add_dependency (decl, kind); } - if (!dep) + if (!dep || dep->is_tu_local ()) { /* Some internal entity of context. Do by value. */ - decl_value (decl, NULL); + decl_value (decl, dep); return false; } @@ -9151,7 +9206,10 @@ trees_out::type_node (tree type) if (streaming_p ()) dump (dumper::TREE) && dump ("Wrote typedef %C:%N%S", TREE_CODE (name), name, name); - gcc_checking_assert (TREE_VISITED (type)); + + /* We'll have either visited this type or have newly discovered + that it's TU-local; either way we won't need to visit it again. */ + gcc_checking_assert (TREE_VISITED (type) || has_tu_local_dep (name)); return; } @@ -9435,6 +9493,64 @@ trees_in::tree_value () return existing; } +/* Whether DECL has a TU-local dependency in the hash. */ + +bool +trees_out::has_tu_local_dep (tree decl) const +{ + /* Only the contexts of fields or enums remember that they're + TU-local. */ + if (DECL_CONTEXT (decl) + && (TREE_CODE (decl) == FIELD_DECL + || TREE_CODE (decl) == CONST_DECL)) + decl = TYPE_NAME (DECL_CONTEXT (decl)); + + depset *dep = dep_hash->find_dependency (decl); + return dep && dep->is_tu_local (); +} + +/* If T depends on a TU-local entity, return that decl. */ + +tree +trees_out::find_tu_local_decl (tree t) +{ + /* We need to have walked all deps first before we can check. */ + gcc_checking_assert (!is_initial_scan ()); + + auto walker = [](tree *tp, int *walk_subtrees, void *data) -> tree + { + auto self = (trees_out *)data; + + tree decl = NULL_TREE; + if (TYPE_P (*tp)) + { + /* A PMF type is a record type, which we otherwise wouldn't walk; + return whether the function type is TU-local. */ + if (TYPE_PTRMEMFUNC_P (*tp)) + { + *walk_subtrees = 0; + return self->find_tu_local_decl (TYPE_PTRMEMFUNC_FN_TYPE (*tp)); + } + else + decl = TYPE_MAIN_DECL (*tp); + } + else if (DECL_P (*tp)) + decl = *tp; + + if (decl) + { + /* We found a DECL, this will tell us whether we're TU-local. */ + *walk_subtrees = 0; + return self->has_tu_local_dep (decl) ? decl : NULL_TREE; + } + return NULL_TREE; + }; + + /* We need to walk without duplicates so that we step into the pointed-to + types of array types. */ + return cp_walk_tree_without_duplicates (&t, walker, this); +} + /* Stream out tree node T. We automatically create local back references, which is essentially a single pass lisp self-referential structure pretty-printer. */ @@ -9447,6 +9563,46 @@ trees_out::tree_node (tree t) if (ref == WK_none) goto done; + /* Find TU-local entities and intercept streaming to instead write a + placeholder value; this way we don't need to emit such decls. + We only need to do this when writing a definition of an entity + that we know names a TU-local entity. */ + if (!is_initial_scan () && writing_local_entities) + { + tree local_decl = NULL_TREE; + if (DECL_P (t) && has_tu_local_dep (t)) + local_decl = t; + /* Consider a type to be TU-local if it refers to any TU-local decl, + no matter how deep. + + This worsens diagnostics slightly, as we often no longer point + directly to the at-fault entity when instantiating. However, this + reduces the module size slightly and means that much less of pt.cc + needs to know about us. */ + else if (TYPE_P (t)) + local_decl = find_tu_local_decl (t); + else if (EXPR_P (t)) + local_decl = find_tu_local_decl (TREE_TYPE (t)); + + if (local_decl) + { + int tag = insert (t, WK_value); + if (streaming_p ()) + { + tu_local_count++; + i (tt_tu_local); + dump (dumper::TREE) + && dump ("Writing TU-local entity:%d %C:%N", + tag, TREE_CODE (t), t); + } + /* TODO: Get a more descriptive name? */ + tree_node (DECL_NAME (local_decl)); + if (state) + state->write_location (*this, DECL_SOURCE_LOCATION (local_decl)); + goto done; + } + } + if (ref != WK_normal) goto skip_normal; @@ -9603,6 +9759,18 @@ trees_in::tree_node (bool is_use) /* NULL_TREE. */ break; + case tt_tu_local: + { + /* A translation-unit-local entity. */ + res = make_node (TU_LOCAL_ENTITY); + int tag = insert (res); + + TU_LOCAL_ENTITY_NAME (res) = tree_node (); + TU_LOCAL_ENTITY_LOCATION (res) = state->read_location (*this); + dump (dumper::TREE) && dump ("Read TU-local entity:%d %N", tag, res); + } + break; + case tt_fixed: /* A fixed ref, find it in the fixed_ref array. */ { @@ -10228,7 +10396,8 @@ trees_in::tree_node (bool is_use) /* A template. */ if (tree tpl = tree_node ()) { - res = DECL_TEMPLATE_RESULT (tpl); + res = (TREE_CODE (tpl) == TU_LOCAL_ENTITY ? + tpl : DECL_TEMPLATE_RESULT (tpl)); dump (dumper::TREE) && dump ("Read template %C:%N", TREE_CODE (res), res); } @@ -12054,8 +12223,18 @@ void trees_out::write_function_def (tree decl) { tree_node (DECL_RESULT (decl)); - tree_node (DECL_INITIAL (decl)); - tree_node (DECL_SAVED_TREE (decl)); + + { + /* The function body for a non-inline function or function template + is ignored for determining exposures. This should only matter + for templates (we don't emit the bodies of non-inline functions + to begin with). */ + auto ovr = make_temp_override (dep_hash->ignore_tu_local, + !DECL_DECLARED_INLINE_P (decl)); + tree_node (DECL_INITIAL (decl)); + tree_node (DECL_SAVED_TREE (decl)); + } + tree_node (DECL_FRIEND_CONTEXT (decl)); constexpr_fundef *cexpr = retrieve_constexpr_fundef (decl); @@ -12157,6 +12336,10 @@ trees_in::read_function_def (tree decl, tree maybe_template) void trees_out::write_var_def (tree decl) { + /* The initializer of a variable or variable template is ignored for + determining exposures. */ + auto ovr = make_temp_override (dep_hash->ignore_tu_local, VAR_P (decl)); + tree init = DECL_INITIAL (decl); tree_node (init); if (!init) @@ -12344,21 +12527,28 @@ trees_out::write_class_def (tree defn) for (; vtables; vtables = TREE_CHAIN (vtables)) write_definition (vtables); - /* Write the friend classes. */ - tree_list (CLASSTYPE_FRIEND_CLASSES (type), false); + { + /* Friend declarations in class definitions are ignored when + determining exposures. */ + auto ovr = make_temp_override (dep_hash->ignore_tu_local, true); - /* Write the friend functions. */ - for (tree friends = DECL_FRIENDLIST (defn); - friends; friends = TREE_CHAIN (friends)) - { - /* Name of these friends. */ - tree_node (TREE_PURPOSE (friends)); - tree_list (TREE_VALUE (friends), false); - } - /* End of friend fns. */ - tree_node (NULL_TREE); + /* Write the friend classes. */ + tree_list (CLASSTYPE_FRIEND_CLASSES (type), false); - /* Write the decl list. */ + /* Write the friend functions. */ + for (tree friends = DECL_FRIENDLIST (defn); + friends; friends = TREE_CHAIN (friends)) + { + tree_node (FRIEND_NAME (friends)); + tree_list (FRIEND_DECLS (friends), false); + } + /* End of friend fns. */ + tree_node (NULL_TREE); + } + + /* Write the decl list. We don't need to ignore exposures of friend + decls here as any such decls should already have been added and + ignored above. */ tree_list (CLASSTYPE_DECL_LIST (type), true); if (TYPE_CONTAINS_VPTR_P (type)) @@ -12729,6 +12919,8 @@ trees_in::read_class_def (tree defn, tree maybe_template) friend_decls; friend_decls = TREE_CHAIN (friend_decls)) { tree f = TREE_VALUE (friend_decls); + if (TREE_CODE (f) == TU_LOCAL_ENTITY) + continue; DECL_BEFRIENDING_CLASSES (f) = tree_cons (NULL_TREE, type, DECL_BEFRIENDING_CLASSES (f)); @@ -12877,8 +13069,11 @@ trees_in::read_enum_def (tree defn, tree maybe_template) /* Write out the body of DECL. See above circularity note. */ void -trees_out::write_definition (tree decl) +trees_out::write_definition (tree decl, bool refs_tu_local) { + auto ovr = make_temp_override (writing_local_entities, + writing_local_entities || refs_tu_local); + if (streaming_p ()) { assert_definition (decl); @@ -13477,7 +13672,11 @@ depset::hash::add_dependency (depset *dep) current->deps.safe_push (dep); if (dep->is_tu_local ()) - current->set_flag_bit (); + { + current->set_flag_bit (); + if (!ignore_tu_local) + current->set_flag_bit (); + } if (current->get_entity_kind () == EK_USING && DECL_IMPLICIT_TYPEDEF_P (dep->get_entity ()) @@ -14138,7 +14337,7 @@ depset::hash::find_dependencies (module_state *module) { walker.mark_declaration (decl, current->has_defn ()); - if (!walker.is_key_order () + if (!is_key_order () && item->is_pending_entity ()) { tree ns = find_pending_key (decl, nullptr); @@ -14147,15 +14346,15 @@ depset::hash::find_dependencies (module_state *module) walker.decl_value (decl, current); if (current->has_defn ()) - walker.write_definition (decl); + walker.write_definition (decl, current->refs_tu_local ()); } walker.end (); - if (!walker.is_key_order () + if (!is_key_order () && DECL_CLASS_TEMPLATE_P (decl)) add_deduction_guides (decl); - if (!walker.is_key_order () + if (!is_key_order () && TREE_CODE (decl) == TEMPLATE_DECL && !DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl)) { @@ -14288,6 +14487,24 @@ binding_cmp (const void *a_, const void *b_) return DECL_UID (a_ent) < DECL_UID (b_ent) ? -1 : +1; } +/* True iff TMPL has an explicit instantiation definition. + + This is local to module.cc because register_specialization skips adding most + instantiations unless module_maybe_has_cmi_p. */ + +static bool +template_has_explicit_inst (tree tmpl) +{ + for (tree t = DECL_TEMPLATE_INSTANTIATIONS (tmpl); t; t = TREE_CHAIN (t)) + { + tree spec = TREE_VALUE (t); + if (DECL_EXPLICIT_INSTANTIATION (spec) + && !DECL_REALLY_EXTERN (spec)) + return true; + } + return false; +} + /* Sort the bindings, issue errors about bad internal refs. */ bool @@ -14344,6 +14561,39 @@ depset::hash::finalize_dependencies () /* We should have emitted an error above. */ gcc_checking_assert (explained); } + else if (warn_template_names_tu_local + && dep->refs_tu_local () && !dep->is_tu_local ()) + { + tree decl = dep->get_entity (); + + /* Friend decls in a class body are ignored, but this is harmless: + it should not impact any consumers. */ + if (RECORD_OR_UNION_TYPE_P (TREE_TYPE (decl))) + continue; + + /* We should now only be warning about templates. */ + gcc_checking_assert + (TREE_CODE (decl) == TEMPLATE_DECL + && VAR_OR_FUNCTION_DECL_P (DECL_TEMPLATE_RESULT (decl))); + + /* Don't warn if we've seen any explicit instantiation definitions, + the intent might be for importers to only use those. */ + if (template_has_explicit_inst (decl)) + continue; + + for (depset *rdep : dep->deps) + if (!rdep->is_binding () && rdep->is_tu_local ()) + { + tree ref = rdep->get_entity (); + auto_diagnostic_group d; + if (warning_at (DECL_SOURCE_LOCATION (decl), + OPT_Wtemplate_names_tu_local, + "%qD refers to TU-local entity %qD and cannot " + "be instantiated in other TUs", decl, ref)) + is_tu_local_entity (ref, /*explain=*/true); + break; + } + } } return ok; @@ -15513,8 +15763,9 @@ enum ct_bind_flags void module_state::intercluster_seed (trees_out &sec, unsigned index_hwm, depset *dep) { - if (dep->is_import () - || dep->cluster < index_hwm) + if (dep->is_tu_local ()) + /* We only stream placeholders for TU-local entities anyway. */; + else if (dep->is_import () || dep->cluster < index_hwm) { tree ent = dep->get_entity (); if (!TREE_VISITED (ent)) @@ -15735,7 +15986,7 @@ module_state::write_cluster (elf_out *to, depset *scc[], unsigned size, sec.u (ct_defn); sec.tree_node (decl); dump () && dump ("Writing definition %N", decl); - sec.write_definition (decl); + sec.write_definition (decl, b->refs_tu_local ()); if (!namer->has_defn ()) namer = b; diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 648eceac2f5..a8d0d8c0296 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -1657,11 +1657,14 @@ register_specialization (tree spec, tree tmpl, tree args, bool is_friend, if ((TREE_CODE (spec) == FUNCTION_DECL && DECL_NAMESPACE_SCOPE_P (spec) && PRIMARY_TEMPLATE_P (tmpl) && DECL_SAVED_TREE (DECL_TEMPLATE_RESULT (tmpl)) == NULL_TREE) + || module_maybe_has_cmi_p () || variable_template_p (tmpl)) /* If TMPL is a forward declaration of a template function, keep a list of all specializations in case we need to reassign them to a friend template later in tsubst_friend_function. + If we're building a CMI, keep a list for all function templates. + Also keep a list of all variable template instantiations so that process_partial_specialization can check whether a later partial specialization would have used it. */ @@ -9903,6 +9906,71 @@ add_pending_template (tree d) pop_tinst_level (); } +/* Emit a diagnostic about instantiating a reference to TU-local entity E. */ + +static void +complain_about_tu_local_entity (tree e) +{ + auto_diagnostic_group d; + error ("instantiation exposes TU-local entity %qD", + TU_LOCAL_ENTITY_NAME (e)); + inform (TU_LOCAL_ENTITY_LOCATION (e), "declared here"); +} + +/* Checks if T contains a TU-local entity. */ + +static bool +expr_contains_tu_local_entity (tree t) +{ + if (!modules_p ()) + return false; + + auto walker = [](tree *tp, int *walk_subtrees, void *) -> tree + { + if (TREE_CODE (*tp) == TU_LOCAL_ENTITY) + return *tp; + if (!EXPR_P (*tp)) + *walk_subtrees = false; + return NULL_TREE; + }; + return cp_walk_tree (&t, walker, nullptr, nullptr); +} + +/* Errors and returns TRUE if X is a function that contains a TU-local + entity in its overload set. */ + +static bool +function_contains_tu_local_entity (tree x) +{ + if (!modules_p ()) + return false; + + if (!x || x == error_mark_node) + return false; + + if (TREE_CODE (x) == OFFSET_REF + || TREE_CODE (x) == COMPONENT_REF) + x = TREE_OPERAND (x, 1); + x = MAYBE_BASELINK_FUNCTIONS (x); + if (TREE_CODE (x) == TEMPLATE_ID_EXPR) + x = TREE_OPERAND (x, 0); + + if (OVL_P (x)) + for (tree ovl : lkp_range (x)) + if (TREE_CODE (ovl) == TU_LOCAL_ENTITY) + { + x = ovl; + break; + } + + if (TREE_CODE (x) == TU_LOCAL_ENTITY) + { + complain_about_tu_local_entity (x); + return true; + } + + return false; +} /* Return a TEMPLATE_ID_EXPR corresponding to the indicated FNS and ARGLIST. Valid choices for FNS are given in the cp-tree.def @@ -12777,8 +12845,10 @@ instantiate_class_template (tree type) } else { - if (TYPE_P (t) || DECL_CLASS_TEMPLATE_P (t) - || DECL_TEMPLATE_TEMPLATE_PARM_P (t)) + if (TREE_CODE (t) == TU_LOCAL_ENTITY) + /* Ignore. */; + else if (TYPE_P (t) || DECL_CLASS_TEMPLATE_P (t) + || DECL_TEMPLATE_TEMPLATE_PARM_P (t)) { /* Build new CLASSTYPE_FRIEND_CLASSES. */ @@ -15588,7 +15658,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain, RETURN (error_mark_node); if (TREE_CODE (t) == TYPE_DECL - && t == TYPE_MAIN_DECL (TREE_TYPE (t))) + && (TREE_CODE (TREE_TYPE (t)) == TU_LOCAL_ENTITY + || t == TYPE_MAIN_DECL (TREE_TYPE (t)))) { /* If this is the canonical decl, we don't have to mess with instantiations, and often we can't (for @@ -16316,6 +16387,14 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) || TREE_CODE (t) == TRANSLATION_UNIT_DECL) return t; + /* Any instantiation of a template containing a TU-local entity is an + exposure, so always issue a hard error irrespective of complain. */ + if (TREE_CODE (t) == TU_LOCAL_ENTITY) + { + complain_about_tu_local_entity (t); + return error_mark_node; + } + tsubst_flags_t tst_ok_flag = (complain & tf_tst_ok); complain &= ~tf_tst_ok; @@ -18596,6 +18675,12 @@ dependent_operand_p (tree t) { while (TREE_CODE (t) == IMPLICIT_CONV_EXPR) t = TREE_OPERAND (t, 0); + + /* If we contain a TU_LOCAL_ENTITY assume we're non-dependent; we'll error + later when instantiating. */ + if (expr_contains_tu_local_entity (t)) + return false; + ++processing_template_decl; bool r = (potential_constant_expression (t) ? value_dependent_expression_p (t) @@ -20383,6 +20468,9 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) else object = NULL_TREE; + if (function_contains_tu_local_entity (templ)) + RETURN (error_mark_node); + tree tid = lookup_template_function (templ, targs); protected_set_expr_location (tid, EXPR_LOCATION (t)); @@ -21075,6 +21163,9 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) qualified_p = true; } + if (function_contains_tu_local_entity (function)) + RETURN (error_mark_node); + nargs = call_expr_nargs (t); releasing_vec call_args; tsubst_call_args (t, args, complain, in_decl, call_args); @@ -22139,6 +22230,10 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) RETURN (op); } + case TU_LOCAL_ENTITY: + complain_about_tu_local_entity (t); + RETURN (error_mark_node); + default: /* Handle Objective-C++ constructs, if appropriate. */ if (tree subst = objcp_tsubst_expr (t, args, complain, in_decl)) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8ed5536365f..6e4e9f3fb59 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -272,7 +272,7 @@ in the following sections. -Woverloaded-virtual -Wno-pmf-conversions -Wself-move -Wsign-promo -Wsized-deallocation -Wsuggest-final-methods -Wsuggest-final-types -Wsuggest-override -Wno-template-body --Wno-template-id-cdtor +-Wno-template-id-cdtor -Wtemplate-names-tu-local -Wno-terminate -Wno-vexing-parse -Wvirtual-inheritance -Wno-virtual-move-assign -Wvolatile -Wzero-as-null-pointer-constant} @@ -4625,6 +4625,24 @@ template struct S @{ @option{-Wtemplate-id-cdtor} is enabled by default with @option{-std=c++20}; it is also enabled by @option{-Wc++20-compat}. +@opindex Wtemplate-names-tu-local +@opindex Wno-template-names-tu-local +@item -Wtemplate-names-tu-local +Warn when a template body hides an exposure of a translation-unit-local +entity. In most cases, referring to a translation-unit-local entity +(such as an internal linkage declaration) within an entity that is +emitted into a module's CMI is an error. However, within the +initializer of a variable, or in the body of a non-inline function, +this is not an exposure and no error is emitted. + +This can cause variable or function templates to accidentally become +unusable if they reference such an entity, because other translation +units that import the template will never be able to instantiate it. +This warning attempts to detect cases where this might occur. +The presence of an explicit instantiation silences the warning. + +This flag is enabled by @option{-Wextra}. + @opindex Wterminate @opindex Wno-terminate @item -Wno-terminate @r{(C++ and Objective-C++ only)} diff --git a/gcc/testsuite/g++.dg/modules/internal-5_a.C b/gcc/testsuite/g++.dg/modules/internal-5_a.C new file mode 100644 index 00000000000..be97ffa471a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-5_a.C @@ -0,0 +1,110 @@ +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" } +// { dg-module-cmi M } +// Ignore exposures in these cases + +export module M; + +namespace { + inline namespace ns { + struct internal_t {}; + template struct internal_tmpl_t {}; + + int internal_x; + void internal_ovl(int&) {} + void internal_ovl(internal_t) {} + + template void internal_tmpl() {} + } +} +export struct ok_inst_tag {}; + + +// The function body for a non-inline function or function template +export void function() { + internal_t i {}; + internal_tmpl_t ii {}; + internal_ovl(internal_x); + internal_tmpl(); +} + +export template void function_tmpl() { + internal_t i {}; + internal_tmpl_t ii {}; + internal_ovl(internal_x); + internal_tmpl(); +} +template void function_tmpl(); +template <> void function_tmpl() {} + + +// The initializer for a variable or variable template +export int var + = (internal_t{}, internal_tmpl_t{}, + internal_ovl(internal_x), internal_tmpl(), 0); + +export template int var_tmpl + = (internal_t{}, internal_tmpl_t{}, + internal_ovl(internal_x), internal_tmpl(), 0); + +template int var_tmpl // { dg-warning "refers to TU-local entity" } + = (internal_t{}, internal_tmpl_t{}, + internal_ovl(internal_x), internal_tmpl(), 0); + +template int var_tmpl; +template <> int var_tmpl = 0; + +export int& constant_ref = internal_x; +static_assert (&constant_ref == &internal_x); + + +// Friend declarations in a class definition +export struct klass { // { dg-bogus "TU-local" } + friend ns::internal_t; + friend ns::internal_tmpl_t; + friend void ns::internal_ovl(int&); + friend void ns::internal_ovl(internal_t); + friend void ns::internal_tmpl(); + + template friend struct ns::internal_tmpl_t; + template friend void ns::internal_tmpl(); +}; + +export template +class klass_tmpl { // { dg-bogus "TU-local" } + friend ns::internal_t; + friend ns::internal_tmpl_t; + friend void ns::internal_ovl(int&); + friend void ns::internal_ovl(internal_t); + friend void ns::internal_tmpl(); + + template friend struct ns::internal_tmpl_t; + template friend void ns::internal_tmpl(); +}; + +template class klass_tmpl { // { dg-bogus "TU-local" } + friend ns::internal_t; + friend ns::internal_tmpl_t; + friend void ns::internal_ovl(int&); + friend void ns::internal_ovl(internal_t); + friend void ns::internal_tmpl(); + + template friend struct ns::internal_tmpl_t; + template friend void ns::internal_tmpl(); +}; + + +// Any reference to a non-volatile const object or reference with internal or +// no linkage initialized with a constant expression that is not an ODR-use +static const int value = 123; +static const int& ref = 456; +static const internal_t internal {}; +void f(int) {} +export inline void no_odr_use() { + int x = value; + int y = ref; + int z = (internal, 0); + + value; + bool b = value < value; + f(value); +} diff --git a/gcc/testsuite/g++.dg/modules/internal-5_b.C b/gcc/testsuite/g++.dg/modules/internal-5_b.C new file mode 100644 index 00000000000..baf60fdafa2 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-5_b.C @@ -0,0 +1,30 @@ +// { dg-additional-options "-fmodules-ts" } + +import M; + +int main() { + // These are all OK + function(); + int a = var; + klass k; + klass_tmpl kt; + klass_tmpl ktp; + no_odr_use(); + + function_tmpl(); + function_tmpl(); + int b = var_tmpl; + int c = var_tmpl; + + // But don't ignore exposures in these cases + function_tmpl(); // { dg-message "required from here" } + int x = var_tmpl; // { dg-message "required from here" } + int y = var_tmpl; // { dg-message "required from here" } + + // And decls initialized to a TU-local value are not constant here + // Unfortunately the error does not currently point to this decl + constexpr int& r = constant_ref; + // { dg-error "is not a constant expression" "" { target *-*-* } 0 } +} + +// { dg-error "instantiation exposes TU-local entity" "" { target *-*-* } 0 } diff --git a/gcc/testsuite/g++.dg/modules/internal-6.C b/gcc/testsuite/g++.dg/modules/internal-6.C new file mode 100644 index 00000000000..0f138781ad5 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-6.C @@ -0,0 +1,24 @@ +// { dg-additional-options "-fmodules-ts" } +// { dg-module-cmi !M } +// Exposures (or not) of TU-local values + +export module M; + +static void f() {} +auto& fr = f; // OK +constexpr auto& fr2 = fr; // { dg-error "initialized to a TU-local value" } +static constexpr auto fp2 = fr; // OK + +struct S { void (&ref)(); } s{ f }; // OK, value is TU-local +constexpr extern struct W { S& s; } wrap{ s }; // OK, value is not TU-local +constexpr S s2{ f }; // { dg-error "initialized to a TU-local value" } + +constexpr int a = 123; +static constexpr int b = 456; +struct X { + union { + const int* p[2]; + }; +}; +constexpr X x { &a }; // OK +constexpr X y { &a, &b }; // { dg-error "initialized to a TU-local value" } diff --git a/gcc/testsuite/g++.dg/modules/internal-7_a.C b/gcc/testsuite/g++.dg/modules/internal-7_a.C new file mode 100644 index 00000000000..39f53ea382e --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-7_a.C @@ -0,0 +1,75 @@ +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" } +// Test streaming and instantiations of various kinds of exposures + +export module M; + +namespace { + int x; + constexpr int y = 1; + + struct S { int m; void d(); }; + enum class E { e }; + + template int f(T t) { return (int)t; } +} + +template void g() {} + +template +int expose_1() { // { dg-warning "TU-local" } + return x; +} + +template +void expose_2() { // { dg-warning "TU-local" } + T t = &y; +} + +template +bool expose_3() { // { dg-warning "TU-local" } + return !(T{} * (x + 5) > 123); +} + +template +bool expose_4() { // { dg-warning "TU-local" } + return __is_same(S, T); +} + +template +void expose_5() { // { dg-warning "TU-local" } + static_assert(T{} == (int)E::e); +} + +template +void expose_6() { // { dg-warning "TU-local" } + f(T{}); +} + +template +void expose_7() { // { dg-warning "TU-local" } + g<&y>(); +} + +template +void expose_8() { // { dg-warning "TU-local" } + decltype(T{} .* &S::m)* (*x)[5][10]; +}; + +template +bool expose_9() { // { dg-warning "TU-local" } + return noexcept((T{} .* &S::d)()); +} + +template +void expose_10() { // { dg-warning "TU-local" } + using U = decltype(f()); +} + +template +void expose_11() { // { dg-warning "TU-local" } + static thread_local E r; +} + +template +int expose_var // { dg-warning "TU-local" } + = f(sizeof(T)); diff --git a/gcc/testsuite/g++.dg/modules/internal-7_b.C b/gcc/testsuite/g++.dg/modules/internal-7_b.C new file mode 100644 index 00000000000..2a11e449d6e --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-7_b.C @@ -0,0 +1,21 @@ +// { dg-additional-options "-fmodules-ts" } + +module M; + +void inst() { + expose_1(); // { dg-message "required from here" } + expose_2(); // { dg-message "required from here" } + expose_3(); // { dg-message "required from here" } + expose_4(); // { dg-message "required from here" } + expose_5(); // { dg-message "required from here" } + expose_6(); // { dg-message "required from here" } + expose_7(); // { dg-message "required from here" } + expose_8(); // { dg-message "required from here" } + expose_9(); // { dg-message "required from here" } + expose_10(); // { dg-message "required from here" } + expose_11(); // { dg-message "required from here" } + + expose_var; // { dg-message "required from here" } +} + +// { dg-error "instantiation exposes TU-local entity" "" { target *-*-* } 0 } diff --git a/gcc/testsuite/g++.dg/modules/internal-8_a.C b/gcc/testsuite/g++.dg/modules/internal-8_a.C new file mode 100644 index 00000000000..46e07a23cf0 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/internal-8_a.C @@ -0,0 +1,35 @@ +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" } +// Non-ODR usages of const variables currently are erroneously +// reported in templates and constexpr functions; this test +// XFAILS them until we can implement a fix. + +export module M; + +namespace { struct internal_t {}; }; +static const int value = 123; +static const int& ref = 456; +static const internal_t internal {}; + +constexpr void f(int) {} + +export constexpr +void no_odr_use_cexpr() { // { dg-bogus "TU-local" "" { xfail *-*-* } } + int x = value; + int y = ref; + int z = (internal, 0); + + value; + bool b = value < value; + f(value); +} + +export template +void no_odr_use_templ() { // { dg-bogus "TU-local" "" { xfail *-*-* } } + int x = value; + int y = ref; + int z = (internal, 0); + + value; + bool b = value < value; + f(value); +} diff --git a/gcc/testsuite/g++.dg/modules/xtreme-header-8.C b/gcc/testsuite/g++.dg/modules/xtreme-header-8.C new file mode 100644 index 00000000000..82c0b59fefe --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/xtreme-header-8.C @@ -0,0 +1,8 @@ +// PR c++/115126 +// { dg-additional-options "-fmodules-ts -Wtemplate-names-tu-local" } +// { dg-module-cmi xstd } + +export module xstd; +extern "C++" { + #include "xtreme-header.h" +}