Skip to content

Commit c368ea5

Browse files
committed
c++/modules: Ignore exposures in lambdas in initializers [PR122994]
As the PR rightly points out, a lambda is not really a declaration in and of itself by the standard, and so a lambda only used in a context where exposures are ignored should not itself cause an error. This patch implements this by way of a new flag set on deps that are first found in an ignored context. This flag gets cleared if we ever see the dep in a context where exposures are not ignored. Then, while walking a declaration with this flag set, we re-establish an ignored context. This is done for all decls (not just lambdas) to handle block-scope classes as well. Additionally, we prevent walking of attached declarations for a DECL_MODULE_KEYED_DECLS_P entity during dependency gathering, so that we don't think we've seen the decl at this point. This means we may not have an appropriate entity to stream for this walk; to prevent any potential issues with merging we stream a NULL_TREE 'hole' in the vector and handle this carefully on import. This requires a small amount of testsuite adjustment because we no longer diagnose errors we used to. Because our ABI for inline variables with dynamic initialization is to just do the initialization in the module's initializer function (and importers only perform the static initialization) we don't bother to walk the definition of inline variables containing lambdas and so don't see the exposures, despite us considering TU-local entities in static initializers of inline variables being exposures (see PR c++/119551). This is legal by the current wording of the standard, which does not consider the definition of any variable to be an exposure (even an inline one). PR c++/122994 gcc/cp/ChangeLog: * module.cc (depset::disc_bits): New flag DB_IGNORED_EXPOSURE_BIT. (depset::is_ignored_exposure_context): New getter. (depset::hash::ignore_tu_local): Rename to... (depset::hash::ignore_exposure): ...this, and make private. (depset::hash::hash): Rename ignore_tu_local. (depset::hash::ignore_exposure_if): New function. (trees_out::decl_value): Don't build deps for keyed entities. (trees_in::decl_value): Handle missing keys. (trees_out::write_function_def): Use ignore_exposure_if. (trees_out::write_var_def): Likewise. (trees_out::write_class_def): Likewise. (depset::hash::make_dependency): Set DB_IGNORED_EXPOSURE_BIT if appropriate, or clear it otherwise. (depset::hash::add_dependency): Rename ignore_tu_local. (depset::hash::find_dependencies): Set ignore_exposure if in such a context. gcc/testsuite/ChangeLog: * g++.dg/modules/internal-17_b.C: Use functions and internal types rather than lambdas. * g++.dg/modules/internal-4_b.C: Correct expected result. * g++.dg/modules/internal-20_a.C: New test. * g++.dg/modules/internal-20_b.C: New test. * g++.dg/modules/internal-20_c.C: New test. * g++.dg/modules/internal-21_a.C: New test. * g++.dg/modules/internal-21_b.C: New test. Signed-off-by: Nathaniel Shead <[email protected]> Reviewed-by: Jason Merrill <[email protected]>
1 parent 8ff212d commit c368ea5

File tree

8 files changed

+177
-22
lines changed

8 files changed

+177
-22
lines changed

gcc/cp/module.cc

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,6 +2370,7 @@ class depset {
23702370
DB_REF_PURVIEW_BIT, /* Refers to a purview TU-local entity. */
23712371
DB_EXPOSE_GLOBAL_BIT, /* Exposes a GMF TU-local entity. */
23722372
DB_EXPOSE_PURVIEW_BIT, /* Exposes a purview TU-local entity. */
2373+
DB_IGNORED_EXPOSURE_BIT, /* Only seen where exposures are ignored. */
23732374
DB_IMPORTED_BIT, /* An imported entity. */
23742375
DB_UNREACHED_BIT, /* A yet-to-be reached entity. */
23752376
DB_MAYBE_RECURSIVE_BIT, /* An entity maybe in a recursive cluster. */
@@ -2491,6 +2492,10 @@ class depset {
24912492
return (get_flag_bit<DB_EXPOSE_PURVIEW_BIT> ()
24922493
|| (strict && get_flag_bit <DB_EXPOSE_GLOBAL_BIT> ()));
24932494
}
2495+
bool is_ignored_exposure_context () const
2496+
{
2497+
return get_flag_bit<DB_IGNORED_EXPOSURE_BIT> ();
2498+
}
24942499

24952500
public:
24962501
bool is_import () const
@@ -2625,7 +2630,9 @@ class depset {
26252630
unsigned section; /* When writing out, the section. */
26262631
bool reached_unreached; /* We reached an unreached entity. */
26272632
bool writing_merge_key; /* We're writing merge key information. */
2628-
bool ignore_tu_local; /* In a context where referencing a TU-local
2633+
2634+
private:
2635+
bool ignore_exposure; /* In a context where referencing a TU-local
26292636
entity is not an exposure. */
26302637

26312638
private:
@@ -2646,7 +2653,7 @@ class depset {
26462653
hash (size_t size, hash *c = NULL)
26472654
: parent (size), chain (c), current (NULL), section (0),
26482655
reached_unreached (false), writing_merge_key (false),
2649-
ignore_tu_local (false)
2656+
ignore_exposure (false)
26502657
{
26512658
worklist.create (size);
26522659
dep_adl_entity_list.create (16);
@@ -2663,6 +2670,15 @@ class depset {
26632670
return chain != NULL;
26642671
}
26652672

2673+
public:
2674+
/* Returns a temporary override that will additionally consider this
2675+
to be a context where exposures of TU-local entities are ignored
2676+
if COND is true. */
2677+
temp_override<bool> ignore_exposure_if (bool cond)
2678+
{
2679+
return make_temp_override (ignore_exposure, ignore_exposure || cond);
2680+
}
2681+
26662682
private:
26672683
depset **entity_slot (tree entity, bool = true);
26682684
depset **binding_slot (tree ctx, tree name, bool = true);
@@ -8519,20 +8535,30 @@ trees_out::decl_value (tree decl, depset *dep)
85198535

85208536
if (DECL_LANG_SPECIFIC (inner)
85218537
&& DECL_MODULE_KEYED_DECLS_P (inner)
8522-
&& !is_key_order ())
8538+
&& streaming_p ())
85238539
{
8524-
/* Stream the keyed entities. */
8540+
/* Stream the keyed entities. There may be keyed entities that we
8541+
choose not to stream, such as a lambda in a non-inline variable's
8542+
initializer, so don't build dependencies for them here; any deps
8543+
we need should be acquired during write_definition (possibly
8544+
indirectly). */
85258545
auto *attach_vec = keyed_table->get (inner);
85268546
unsigned num = attach_vec->length ();
8527-
if (streaming_p ())
8528-
u (num);
8547+
u (num);
85298548
for (unsigned ix = 0; ix != num; ix++)
85308549
{
85318550
tree attached = (*attach_vec)[ix];
8551+
if (attached)
8552+
{
8553+
tree ti = TYPE_TEMPLATE_INFO (TREE_TYPE (attached));
8554+
if (!dep_hash->find_dependency (attached)
8555+
&& !(ti && dep_hash->find_dependency (TI_TEMPLATE (ti))))
8556+
attached = NULL_TREE;
8557+
}
8558+
85328559
tree_node (attached);
8533-
if (streaming_p ())
8534-
dump (dumper::MERGE)
8535-
&& dump ("Written %d[%u] attached decl %N", tag, ix, attached);
8560+
dump (dumper::MERGE)
8561+
&& dump ("Written %d[%u] attached decl %N", tag, ix, attached);
85368562
}
85378563
}
85388564

@@ -8844,7 +8870,17 @@ trees_in::decl_value ()
88448870
if (is_new)
88458871
set.quick_push (attached);
88468872
else if (set[ix] != attached)
8847-
set_overrun ();
8873+
{
8874+
if (!set[ix] || !attached)
8875+
/* One import left a hole for a lambda dep we chose not
8876+
to stream, but another import chose to stream that lambda.
8877+
Let's not error here: hopefully we'll complain later in
8878+
is_matching_decl about whatever caused us to make a
8879+
different decision. */
8880+
;
8881+
else
8882+
set_overrun ();
8883+
}
88488884
}
88498885
}
88508886

@@ -12948,8 +12984,7 @@ trees_out::write_function_def (tree decl)
1294812984
is ignored for determining exposures. This should only matter
1294912985
for templates (we don't emit the bodies of non-inline functions
1295012986
to begin with). */
12951-
auto ovr = make_temp_override (dep_hash->ignore_tu_local,
12952-
!DECL_DECLARED_INLINE_P (decl));
12987+
auto ovr = dep_hash->ignore_exposure_if (!DECL_DECLARED_INLINE_P (decl));
1295312988
tree_node (DECL_INITIAL (decl));
1295412989
tree_node (DECL_SAVED_TREE (decl));
1295512990
}
@@ -13087,8 +13122,8 @@ trees_out::write_var_def (tree decl)
1308713122
{
1308813123
/* The initializer of a non-inline variable or variable template is
1308913124
ignored for determining exposures. */
13090-
auto ovr = make_temp_override (dep_hash->ignore_tu_local,
13091-
VAR_P (decl) && !DECL_INLINE_VAR_P (decl));
13125+
auto ovr = dep_hash->ignore_exposure_if (VAR_P (decl)
13126+
&& !DECL_INLINE_VAR_P (decl));
1309213127

1309313128
tree init = DECL_INITIAL (decl);
1309413129
tree_node (init);
@@ -13287,7 +13322,7 @@ trees_out::write_class_def (tree defn)
1328713322
{
1328813323
/* Friend declarations in class definitions are ignored when
1328913324
determining exposures. */
13290-
auto ovr = make_temp_override (dep_hash->ignore_tu_local, true);
13325+
auto ovr = dep_hash->ignore_exposure_if (true);
1329113326

1329213327
/* Write the friend classes. */
1329313328
tree_list (CLASSTYPE_FRIEND_CLASSES (type), false);
@@ -14446,6 +14481,9 @@ depset::hash::make_dependency (tree decl, entity_kind ek)
1444614481
gcc_checking_assert ((*eslot)->get_entity_kind () == EK_REDIRECT
1444714482
&& !(*eslot)->deps.length ());
1444814483

14484+
if (ignore_exposure)
14485+
dep->set_flag_bit<DB_IGNORED_EXPOSURE_BIT> ();
14486+
1444914487
if (ek != EK_USING)
1445014488
{
1445114489
tree not_tmpl = STRIP_TEMPLATE (decl);
@@ -14569,6 +14607,8 @@ depset::hash::make_dependency (tree decl, entity_kind ek)
1456914607
if (!dep->is_import ())
1457014608
worklist.safe_push (dep);
1457114609
}
14610+
else if (!ignore_exposure)
14611+
dep->clear_flag_bit<DB_IGNORED_EXPOSURE_BIT> ();
1457214612

1457314613
dump (dumper::DEPEND)
1457414614
&& dump ("%s on %s %C:%N found",
@@ -14627,7 +14667,7 @@ depset::hash::add_dependency (depset *dep)
1462714667
else
1462814668
current->set_flag_bit<DB_REF_GLOBAL_BIT> ();
1462914669

14630-
if (!ignore_tu_local && !is_exposure_of_member_type (current, dep))
14670+
if (!ignore_exposure && !is_exposure_of_member_type (current, dep))
1463114671
{
1463214672
if (dep->is_tu_local ())
1463314673
current->set_flag_bit<DB_EXPOSE_PURVIEW_BIT> ();
@@ -15428,6 +15468,8 @@ depset::hash::find_dependencies (module_state *module)
1542815468
add_namespace_context (item, ns);
1542915469
}
1543015470

15471+
auto ovr = make_temp_override
15472+
(ignore_exposure, item->is_ignored_exposure_context ());
1543115473
walker.decl_value (decl, current);
1543215474
if (current->has_defn ())
1543315475
walker.write_definition (decl, current->refs_tu_local ());

gcc/testsuite/g++.dg/modules/internal-17_b.C

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@
44

55
module;
66

7-
static inline int x // { dg-error "TU-local" }
8-
// { dg-message "exposed elsewhere" "" { target *-*-* } .-1 }
9-
= []{ return 1; }(); // { dg-message "internal" }
7+
namespace {
8+
struct InternalX {}; // { dg-message "internal" }
9+
// Only used by '::y', so should be discarded and not complain
10+
struct InternalY {}; // { dg-bogus "" }
11+
}
1012

11-
static inline int y = []{ return 2; }(); // { dg-bogus "" }
13+
static inline int x() { // { dg-error "TU-local" }
14+
// { dg-message "exposed elsewhere" "" { target *-*-* } .-1 }
15+
InternalX x;
16+
return 1;
17+
}
18+
19+
static inline int y() { // { dg-bogus "" }
20+
InternalY y;
21+
return 2;
22+
}
1223

1324
namespace {
1425
struct S {};
@@ -38,7 +49,7 @@ void test_usage() {
3849
}
3950

4051
inline void expose() { // { dg-warning "exposes TU-local" }
41-
int result = x;
52+
int result = x();
4253
}
4354

4455
// Internal linkage types always hard error
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// PR c++/122994
2+
// { dg-additional-options "-fmodules -Wtemplate-names-tu-local" }
3+
// { dg-module-cmi m }
4+
5+
export module m;
6+
7+
extern "C++" {
8+
static int internal() { return 42; }
9+
}
10+
11+
export int a = internal();
12+
export int b = []{ return internal(); }();
13+
14+
export template <typename T> int c
15+
= []{ return internal(); }(); // { dg-warning "refers to TU-local entity" }
16+
export template <typename T> int d
17+
= []{ return internal(); }(); // { dg-warning "refers to TU-local entity" }
18+
template int d<int>;
19+
20+
export int e = []{
21+
return []{
22+
return internal();
23+
}();
24+
}();
25+
26+
export int f = []{
27+
struct S {
28+
inline int foo() {
29+
return internal();
30+
}
31+
};
32+
return S{}.foo();
33+
}();
34+
35+
export extern "C++" {
36+
int merge = []{ return internal(); }();
37+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// PR c++/122994
2+
// { dg-additional-options "-fmodules" }
3+
4+
static int internal() { return 42; }
5+
int merge = []{ return internal(); }();
6+
7+
import m;
8+
9+
int& use_a = a;
10+
int& use_b = b;
11+
int& use_c = c<int>; // { dg-message "required from here" }
12+
int& use_d = d<int>; // { dg-bogus "" }
13+
int& use_d2 = d<double>; // { dg-message "required from here" }
14+
int& use_e = e;
15+
int& use_f = f;
16+
int& use_merge = merge;
17+
18+
// { dg-error "instantiation exposes TU-local entity" "" { target *-*-* } 0 }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// PR c++/122994
2+
// { dg-additional-options "-fmodules" }
3+
// { dg-module-cmi !x }
4+
5+
export module x;
6+
7+
static int internal() { return 42; }
8+
9+
auto a
10+
= []{ return internal(); }; // { dg-error "exposes TU-local" }
11+
12+
auto b = []{
13+
struct S {
14+
inline int foo() { // { dg-error "exposes TU-local" }
15+
return internal();
16+
}
17+
};
18+
return S{};
19+
}();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// { dg-additional-options "-fmodules" }
2+
// { dg-module-cmi M }
3+
4+
export module M;
5+
6+
static int internal() { return 42; }
7+
8+
// We don't error here, despite this being an exposure we really should
9+
// probably complain about, because we never actually expose the lambda
10+
// (the dynamic initialization just occurs in this TU and nowhere else)
11+
// and so this appears to function "correctly"...
12+
export inline int x = internal(); // { dg-error "exposes TU-local" "" { xfail *-*-* } }
13+
export inline int y
14+
= []{ return internal(); }(); // { dg-error "exposes TU-local" "" { xfail *-*-* } }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// { dg-module-do run }
2+
// { dg-additional-options "-fmodules" }
3+
4+
import M;
5+
6+
// Ideally M was not built and so this file is not needed at all,
7+
// but while it is, let's at least check we function correctly.
8+
int main() {
9+
if (x != 42)
10+
__builtin_abort ();
11+
if (y != 42)
12+
__builtin_abort ();
13+
}

gcc/testsuite/g++.dg/modules/internal-4_b.C

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ V* expose_v; // { dg-error "exposes TU-local entity" }
6666
static auto internal_lambda = []{ internal_f(); }; // OK
6767
auto expose_lambda = internal_lambda; // { dg-error "exposes TU-local entity" }
6868

69+
// This is OK because we ignore exposures in an initializer.
6970
int not_in_tu_local
70-
= ([]{ internal_f(); }(), // { dg-error "exposes TU-local entity" }
71+
= ([]{ internal_f(); }(), // { dg-bogus "exposes TU-local entity" }
7172
0);
7273

7374

0 commit comments

Comments
 (0)