Skip to content

Commit 9824954

Browse files
committed
[Macros] Ban freestanding and peer macro expansions that can produce arbitrary
names at global scope. Freestanding and peer macros applied at top-level scope cannot introduce arbitrary names. Introducing arbitrary names means that any lookup into this scope must expand the macro. This is a problem, because resolving the macro can invoke type checking other declarations, e.g. anything that the macro arguments depend on. If _anything_ the macro depends on performs name unqualified name lookup, e.g. type resolution, we'll get circularity errors. It's better to prevent this by banning these macros at global scope if any of the macro candidates introduce arbitrary names.
1 parent f9f63e3 commit 9824954

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

include/swift/AST/DiagnosticsSema.def

+4
Original file line numberDiff line numberDiff line change
@@ -7188,6 +7188,10 @@ ERROR(macro_accessor_missing_from_expansion,none,
71887188
ERROR(macro_init_accessor_not_documented,none,
71897189
"expansion of macro %0 produced an unexpected 'init' accessor",
71907190
(DeclName))
7191+
ERROR(global_arbitrary_name,none,
7192+
"'%0' macros are not allowed to introduce arbitrary names "
7193+
"at global scope",
7194+
(StringRef))
71917195

71927196
ERROR(macro_resolve_circular_reference, none,
71937197
"circular reference resolving %select{freestanding|attached}0 macro %1",

lib/Sema/TypeCheckMacros.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -1470,6 +1470,40 @@ swift::expandConformances(CustomAttr *attr, MacroDecl *macro,
14701470
return macroSourceFile->getBufferID();
14711471
}
14721472

1473+
/// Emits an error and returns \c true if the maro reference may
1474+
/// introduce arbitrary names at global scope.
1475+
static bool diagnoseArbitraryGlobalNames(DeclContext *dc,
1476+
UnresolvedMacroReference macroRef,
1477+
MacroRole macroRole) {
1478+
auto &ctx = dc->getASTContext();
1479+
assert(macroRole == MacroRole::Declaration ||
1480+
macroRole == MacroRole::Peer);
1481+
1482+
if (!dc->isModuleScopeContext())
1483+
return false;
1484+
1485+
bool isInvalid = false;
1486+
namelookup::forEachPotentialResolvedMacro(
1487+
dc, macroRef.getMacroName(), macroRole,
1488+
[&](MacroDecl *decl, const MacroRoleAttr *attr) {
1489+
if (!isInvalid &&
1490+
attr->hasNameKind(MacroIntroducedDeclNameKind::Arbitrary)) {
1491+
ctx.Diags.diagnose(macroRef.getSigilLoc(),
1492+
diag::global_arbitrary_name,
1493+
getMacroRoleString(macroRole));
1494+
isInvalid = true;
1495+
1496+
// If this is an attached macro, mark the attribute as invalid
1497+
// to avoid diagnosing an unknown attribute later.
1498+
if (auto *attr = macroRef.getAttr()) {
1499+
attr->setInvalid();
1500+
}
1501+
}
1502+
});
1503+
1504+
return isInvalid;
1505+
}
1506+
14731507
ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
14741508
UnresolvedMacroReference macroRef,
14751509
DeclContext *dc) const {
@@ -1492,6 +1526,19 @@ ConcreteDeclRef ResolveMacroRequest::evaluate(Evaluator &evaluator,
14921526
return ConcreteDeclRef();
14931527
}
14941528

1529+
// Freestanding and peer macros applied at top-level scope cannot introduce
1530+
// arbitrary names. Introducing arbitrary names means that any lookup
1531+
// into this scope must expand the macro. This is a problem, because
1532+
// resolving the macro can invoke type checking other declarations, e.g.
1533+
// anything that the macro arguments depend on. If _anything_ the macro
1534+
// depends on performs name unqualified name lookup, e.g. type resolution,
1535+
// we'll get circularity errors. It's better to prevent this by banning
1536+
// these macros at global scope if any of the macro candidates introduce
1537+
// arbitrary names.
1538+
if (diagnoseArbitraryGlobalNames(dc, macroRef, MacroRole::Declaration) ||
1539+
diagnoseArbitraryGlobalNames(dc, macroRef, MacroRole::Peer))
1540+
return ConcreteDeclRef();
1541+
14951542
// If we already have a MacroExpansionExpr, use that. Otherwise,
14961543
// create one.
14971544
MacroExpansionExpr *macroExpansion;

test/Macros/macro_expand_peers.swift

+10
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ struct S2 {
162162
#endif
163163
}
164164

165+
#if TEST_DIAGNOSTICS
166+
// Peer macros cannot introduce arbitrary names at global scope
167+
168+
//expected-error@+1 {{'peer' macros are not allowed to introduce arbitrary names at global scope}}
169+
@addCompletionHandlerArbitrarily(x)
170+
func h(a: Int, for b: String, _ value: Double) async -> String {
171+
return b
172+
}
173+
#endif
174+
165175
// Stored properties generated by a peer macro
166176
@attached(accessor)
167177
@attached(peer, names: prefixed(_))

test/Macros/top_level_freestanding.swift

+4
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,17 @@ struct HasInnerClosure {
7272
#freestandingWithClosure(1) { x in x }
7373
}
7474

75+
#if TEST_DIAGNOSTICS
7576
// Arbitrary names at global scope
7677

7778
#bitwidthNumberedStructs("MyIntGlobal")
79+
// expected-error@-1 {{'declaration' macros are not allowed to introduce arbitrary names at global scope}}
7880

7981
func testArbitraryAtGlobal() {
8082
_ = MyIntGlobal16()
83+
// expected-error@-1 {{cannot find 'MyIntGlobal16' in scope}}
8184
}
85+
#endif
8286

8387
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser33_{{.*}}9stringifyfMf1_{{.*}}warning: 'deprecated()' is deprecated
8488
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser33_{{.*}}9stringifyfMf2_{{.*}}warning: 'deprecated()' is deprecated

0 commit comments

Comments
 (0)