Skip to content

Commit accdfd0

Browse files
committed
Check attributes in @abi attr
This commit compares the attributes on the decl inside the `@abi` attribute to those in the decl it’s attached to, diagnosing ABI-incompatible differences. It also rejects many attributes that don’t need to be specified in the `@abi` attribute, such as ObjC-ness, access control, or ABI-neutral traits like `@discardableResult`, so developers know to remove them.
1 parent 38d1ca4 commit accdfd0

File tree

9 files changed

+1554
-2
lines changed

9 files changed

+1554
-2
lines changed

include/swift/AST/DiagnosticsSema.def

+21
Original file line numberDiff line numberDiff line change
@@ -8316,6 +8316,27 @@ ERROR(attr_abi_incompatible_with_silgen_name,none,
83168316
"the same purpose",
83178317
(DescriptiveDeclKind))
83188318

8319+
ERROR(attr_abi_missing_attr,none,
8320+
"missing '%0' %select{attribute|modifier}1 in '@abi'",
8321+
(StringRef, bool))
8322+
ERROR(attr_abi_extra_attr,none,
8323+
"extra %select{|implicit }2'%0' %select{attribute|modifier}1 in '@abi'",
8324+
(StringRef, bool, /*isImplicit=*/bool))
8325+
ERROR(attr_abi_forbidden_attr,none,
8326+
"unused '%0' %select{attribute|modifier}1 in '@abi'",
8327+
(StringRef, bool))
8328+
REMARK(abi_attr_inferred_attribute,none,
8329+
"inferred '%0' in '@abi' to match %select{attribute|modifier}1 on API",
8330+
(StringRef, bool))
8331+
8332+
ERROR(attr_abi_mismatched_attr,none,
8333+
"'%0' %select{attribute|modifier}1 in '@abi' should match '%2'",
8334+
(StringRef, bool, StringRef))
8335+
NOTE(attr_abi_matching_attr_here,none,
8336+
"%select{should match|matches}0 %select{attribute|modifier}1 "
8337+
"%select{|implicitly added }2here",
8338+
(/*matches=*/bool, /*isModifier=*/bool, /*isImplicit=*/bool))
8339+
83198340
ERROR(attr_abi_mismatched_type,none,
83208341
"type %0 in '@abi' should match %1",
83218342
(Type, Type))

include/swift/Basic/LangOptions.h

+3
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ namespace swift {
264264
/// Emit a remark on early exit in explicit interface build
265265
bool EnableSkipExplicitInterfaceModuleBuildRemarks = false;
266266

267+
/// Emit a remark when \c \@abi infers an attribute or modifier.
268+
bool EnableABIInferenceRemarks = false;
269+
267270
///
268271
/// Support for alternate usage modes
269272
///

include/swift/Option/Options.td

+4
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,10 @@ def remark_module_serialization : Flag<["-"], "Rmodule-serialization">,
468468
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
469469
HelpText<"Emit remarks about module serialization">;
470470

471+
def remark_abi_inference : Flag<["-"], "Rabi-inference">,
472+
Flags<[FrontendOption, DoesNotAffectIncrementalBuild]>,
473+
HelpText<"Emit a remark when an '@abi' attribute adds an attribute or modifier to the ABI declaration based on its presence in the API">;
474+
471475
def emit_tbd : Flag<["-"], "emit-tbd">,
472476
HelpText<"Emit a TBD file">,
473477
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>;

lib/Frontend/CompilerInvocation.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
13721372

13731373
Opts.EnableSkipExplicitInterfaceModuleBuildRemarks = Args.hasArg(OPT_remark_skip_explicit_interface_build);
13741374

1375+
Opts.EnableABIInferenceRemarks = Args.hasArg(OPT_remark_abi_inference);
1376+
13751377
if (Args.hasArg(OPT_experimental_skip_non_exportable_decls)) {
13761378
// Only allow -experimental-skip-non-exportable-decls if either library
13771379
// evolution is enabled (in which case the module's ABI is independent of

lib/Sema/TypeCheckAttr.cpp

+210-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "swift/AST/PropertyWrappers.h"
4141
#include "swift/AST/SourceFile.h"
4242
#include "swift/AST/StorageImpl.h"
43+
#include "swift/AST/ASTPrinter.h"
4344
#include "swift/AST/SwiftNameTranslation.h"
4445
#include "swift/AST/TypeCheckRequests.h"
4546
#include "swift/AST/Types.h"
@@ -6682,6 +6683,12 @@ static bool typeCheckDerivativeAttr(DerivativeAttr *attr) {
66826683
// Note: Implementation must be idempotent because it may be called multiple
66836684
// times for the same attribute.
66846685
Decl *D = attr->getOriginalDeclaration();
6686+
6687+
// ABI-only decls can't have @derivative; bail out and let ABIDeclChecker
6688+
// diagnose this.
6689+
if (!ABIRoleInfo(D).providesAPI())
6690+
return false;
6691+
66856692
auto &Ctx = D->getASTContext();
66866693
auto &diags = Ctx.Diags;
66876694
// `@derivative` attribute requires experimental differentiable programming
@@ -9114,13 +9121,215 @@ class ABIDeclChecker : public ASTComparisonVisitor<ABIDeclChecker> {
91149121

91159122
// MARK: @abi checking - attributes
91169123

9124+
/// Are these attributes similar enough that they should be checked against
9125+
/// one another? At minimum this means they're of the same kind, but for some
9126+
/// attrs there are additional criteria.
9127+
bool canCompareAttrs(DeclAttribute *api, DeclAttribute *abi,
9128+
Decl *apiDecl, Decl *abiDecl) {
9129+
if (api->getKind() != abi->getKind())
9130+
return false;
9131+
9132+
auto getAvailableDomain = [](Decl *D, DeclAttribute *A) {
9133+
return D->getSemanticAvailableAttr(cast<AvailableAttr>(A))->getDomain();
9134+
};
9135+
9136+
// Extra logic for specific attributes.
9137+
switch (api->getKind()) {
9138+
case DeclAttrKind::Expose:
9139+
return cast<ExposeAttr>(api)->getExposureKind()
9140+
== cast<ExposeAttr>(abi)->getExposureKind();
9141+
9142+
case DeclAttrKind::Extern:
9143+
return cast<ExternAttr>(api)->getExternKind()
9144+
== cast<ExternAttr>(abi)->getExternKind();
9145+
9146+
case DeclAttrKind::Available:
9147+
return getAvailableDomain(apiDecl, api)
9148+
== getAvailableDomain(abiDecl, abi);
9149+
return true;
9150+
9151+
default:
9152+
break;
9153+
}
9154+
9155+
return true;
9156+
}
9157+
9158+
/// Check two attribute lists against one another.
9159+
///
9160+
/// This pairs up attributes which are sufficiently similar (as determined by
9161+
/// \c canCompareAttrs() ) and then checks them. Attributes which
9162+
/// have no counterpart are checked individually.
91179163
bool checkAttrs(DeclAttributes api, DeclAttributes abi,
91189164
Decl *apiDecl, Decl *abiDecl) {
91199165
bool didDiagnose = false;
9120-
// TODO: Actually check attributes
9166+
9167+
// Collect all ABI attrs.
9168+
SmallVector<DeclAttribute*, 32> remainingABIDeclAttrs;
9169+
for (auto *abiDeclAttr : abi) {
9170+
remainingABIDeclAttrs.push_back(abiDeclAttr);
9171+
}
9172+
9173+
// Visit each API attr, pairing it with an ABI attr if possible.
9174+
// Note that this will visit even invalid attributes.
9175+
for (auto *apiDeclAttr : api) {
9176+
auto abiAttrIter = llvm::find_if(remainingABIDeclAttrs,
9177+
[&](DeclAttribute *abiDeclAttr) {
9178+
return abiDeclAttr && canCompareAttrs(apiDeclAttr, abiDeclAttr,
9179+
apiDecl, abiDecl);
9180+
});
9181+
DeclAttribute *abiDeclAttr = nullptr;
9182+
if (abiAttrIter != remainingABIDeclAttrs.end()) {
9183+
// Found a matching ABI attr. Claim and use it.
9184+
std::swap(abiDeclAttr, *abiAttrIter);
9185+
}
9186+
didDiagnose |= checkAttr(apiDeclAttr, abiDeclAttr, apiDecl, abiDecl);
9187+
}
9188+
9189+
// Visit leftover ABI attrs.
9190+
for (auto *abiDeclAttr : remainingABIDeclAttrs) {
9191+
if (abiDeclAttr)
9192+
didDiagnose |= checkAttr(nullptr, abiDeclAttr, apiDecl, abiDecl);
9193+
}
91219194
return didDiagnose;
91229195
}
91239196

9197+
/// Check a single attribute against its counterpart. If an attribute has no
9198+
/// counterpart, the counterpart may be \c nullptr ; either \p abi or \p abi
9199+
/// may be \c nullptr , but never both.
9200+
bool checkAttr(DeclAttribute *api, DeclAttribute *abi,
9201+
Decl *apiDecl, Decl *abiDecl) {
9202+
ASSERT(api || abi && "checkAttr() should have at least one attribute");
9203+
9204+
// If either attribute has already been diagnosed, don't check here.
9205+
if ((api && api->isInvalid()) || (abi && abi->isInvalid()))
9206+
return true;
9207+
9208+
auto kind = api ? api->getKind() : abi->getKind();
9209+
auto behaviors = DeclAttribute::getBehaviors(kind);
9210+
9211+
switch (behaviors & DeclAttribute::InABIAttrMask) {
9212+
case DeclAttribute::UnreachableInABIAttr:
9213+
ASSERT(abiAttr->canAppearOnDecl(apiDecl)
9214+
&& "checking @abi on decl that can't have it???");
9215+
ASSERT(!abiAttr->canAppearOnDecl(apiDecl)
9216+
&& "unreachable-in-@abi attr on reachable decl???");
9217+
9218+
// If the asserts are disabled, fall through to no checking.
9219+
LLVM_FALLTHROUGH;
9220+
9221+
case DeclAttribute::UnconstrainedInABIAttr:
9222+
// No checking required.
9223+
return false;
9224+
9225+
case DeclAttribute::ForbiddenInABIAttr:
9226+
// Diagnose if ABI has attribute.
9227+
if (abi) {
9228+
// A shorthand `@available(foo 1, bar 2, *)` attribute gets parsed into
9229+
// several separate `AvailableAttr`s, each with the full range of the
9230+
// shorthand attribute. If we've already diagnosed one of them, don't
9231+
// diagnose the rest; otherwise, record that we've diagnosed this one.
9232+
if (isa<AvailableAttr>(abi) &&
9233+
!diagnosedAvailableAttrSourceLocs.insert(abi->getLocation()))
9234+
return true;
9235+
9236+
diagnoseAndRemoveAttr(abi, diag::attr_abi_forbidden_attr,
9237+
abi->getAttrName(), abi->isDeclModifier());
9238+
return true;
9239+
}
9240+
9241+
return false;
9242+
9243+
case DeclAttribute::InferredInABIAttr:
9244+
if (!abi && api->canClone()) {
9245+
// Infer an identical attribute.
9246+
abi = api->clone(ctx);
9247+
abi->setImplicit(true);
9248+
abiDecl->getAttrs().add(abi);
9249+
9250+
if (ctx.LangOpts.EnableABIInferenceRemarks) {
9251+
SmallString<64> scratch;
9252+
auto abiAttrAsString = printAttr(abi, abiDecl, scratch);
9253+
9254+
abiDecl->diagnose(diag::abi_attr_inferred_attribute,
9255+
abiAttrAsString, api->isDeclModifier());
9256+
noteAttrHere(api, apiDecl, /*isMatch=*/true);
9257+
}
9258+
}
9259+
9260+
// Other than the cloning behavior, Inferred behaves like Equivalent.
9261+
LLVM_FALLTHROUGH;
9262+
9263+
case DeclAttribute::EquivalentInABIAttr:
9264+
// Diagnose if API doesn't have attribute.
9265+
if (!api) {
9266+
diagnoseAndRemoveAttr(abi, diag::attr_abi_extra_attr,
9267+
abi->getAttrName(), abi->isDeclModifier(),
9268+
abi->isImplicit());
9269+
return true;
9270+
}
9271+
9272+
// Diagnose if ABI doesn't have attribute.
9273+
if (!abi) {
9274+
SmallString<64> scratch;
9275+
auto apiAttrAsString = printAttr(api, apiDecl, scratch,
9276+
/*toInsert=*/true);
9277+
9278+
ctx.Diags.diagnose(abiDecl, diag::attr_abi_missing_attr,
9279+
api->getAttrName(), api->isDeclModifier())
9280+
.fixItInsert(abiDecl->getAttributeInsertionLoc(api->isDeclModifier()),
9281+
apiAttrAsString);
9282+
noteAttrHere(api, apiDecl);
9283+
return true;
9284+
}
9285+
9286+
// Diagnose if two attributes are mismatched.
9287+
if (!api->isEquivalent(abi, apiDecl)) {
9288+
SmallString<64> scratch;
9289+
auto apiAttrAsString = printAttr(api, apiDecl, scratch);
9290+
9291+
ctx.Diags.diagnose(abi->getLocation(), diag::attr_abi_mismatched_attr,
9292+
abi->getAttrName(), abi->isDeclModifier(),
9293+
apiAttrAsString)
9294+
.fixItReplace(abi->getRangeWithAt(), apiAttrAsString);
9295+
noteAttrHere(api, apiDecl);
9296+
return true;
9297+
}
9298+
9299+
return false;
9300+
}
9301+
9302+
llvm_unreachable("unknown InABIAttrMask behavior");
9303+
}
9304+
9305+
StringRef printAttr(DeclAttribute *attr,
9306+
Decl *decl,
9307+
SmallVectorImpl<char> &scratch,
9308+
bool toInsert = false) {
9309+
auto opts = PrintOptions::printForDiagnostics(AccessLevel::Private,
9310+
ctx.TypeCheckerOpts.PrintFullConvention);
9311+
opts.PrintLongAttrsOnSeparateLines = false;
9312+
9313+
llvm::raw_svector_ostream os{scratch};
9314+
StreamPrinter printer{os};
9315+
attr->print(printer, opts, decl);
9316+
9317+
auto str = StringRef(scratch.begin(), scratch.size());
9318+
if (!toInsert)
9319+
str = str.trim(' ');
9320+
return str;
9321+
}
9322+
9323+
void noteAttrHere(DeclAttribute *attr, Decl *decl, bool isMatch = false) {
9324+
SourceLoc loc = attr->getLocation();
9325+
if (loc.isValid())
9326+
ctx.Diags.diagnose(loc, diag::attr_abi_matching_attr_here,
9327+
isMatch, attr->isDeclModifier(), attr->isImplicit());
9328+
else
9329+
ctx.Diags.diagnose(decl, diag::attr_abi_matching_attr_here,
9330+
isMatch, attr->isDeclModifier(), attr->isImplicit());
9331+
}
9332+
91249333
// MARK: @abi checking - types
91259334

91269335
bool checkType(Type api, Type abi, SourceLoc apiLoc, SourceLoc abiLoc) {

lib/Sema/TypeCheckDeclPrimary.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -2173,6 +2173,10 @@ void swift::diagnoseAttrsAddedByAccessNote(SourceFile &SF) {
21732173

21742174
evaluator::SideEffect
21752175
ApplyAccessNoteRequest::evaluate(Evaluator &evaluator, ValueDecl *VD) const {
2176+
// Access notes don't apply to ABI-only attributes.
2177+
if (!ABIRoleInfo(VD).providesAPI())
2178+
return {};
2179+
21762180
AccessNotesFile &notes = VD->getModuleContext()->getAccessNotes();
21772181
if (auto note = notes.lookup(VD))
21782182
applyAccessNote(VD, *note.get(), notes);

test/attr/Inputs/attr_abi.h

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
void implementation1(void);
2+
void implementation2(void);
3+
void implementation3(void);

0 commit comments

Comments
 (0)