Skip to content

Commit 0ad8532

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. (cherry picked from commit 9824954)
1 parent 3f9fc44 commit 0ad8532

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7102,6 +7102,10 @@ ERROR(macro_accessor_missing_from_expansion,none,
71027102
ERROR(macro_init_accessor_not_documented,none,
71037103
"expansion of macro %0 produced an unexpected 'init' accessor",
71047104
(DeclName))
7105+
ERROR(global_arbitrary_name,none,
7106+
"'%0' macros are not allowed to introduce arbitrary names "
7107+
"at global scope",
7108+
(StringRef))
71057109

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

lib/Sema/TypeCheckMacros.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,40 @@ swift::expandConformances(CustomAttr *attr, MacroDecl *macro,
14711471
return macroSourceFile->getBufferID();
14721472
}
14731473

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

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

test/Macros/macro_expand_peers.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ struct S2 {
158158
#endif
159159
}
160160

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

test/Macros/top_level_freestanding.swift

Lines changed: 4 additions & 0 deletions
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)