Skip to content

Commit 955ede9

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 63fc4a1 commit 955ede9

File tree

9 files changed

+1554
-2
lines changed

9 files changed

+1554
-2
lines changed

include/swift/AST/DiagnosticsSema.def

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ namespace swift {
263263
/// Emit a remark on early exit in explicit interface build
264264
bool EnableSkipExplicitInterfaceModuleBuildRemarks = false;
265265

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

include/swift/Option/Options.td

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

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

Lines changed: 210 additions & 1 deletion
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
@@ -9072,13 +9079,215 @@ class ABIDeclChecker : public ASTComparisonVisitor<ABIDeclChecker> {
90729079

90739080
// MARK: @abi checking - attributes
90749081

9082+
/// Are these attributes similar enough that they should be checked against
9083+
/// one another? At minimum this means they're of the same kind, but for some
9084+
/// attrs there are additional criteria.
9085+
bool canCompareAttrs(DeclAttribute *api, DeclAttribute *abi,
9086+
Decl *apiDecl, Decl *abiDecl) {
9087+
if (api->getKind() != abi->getKind())
9088+
return false;
9089+
9090+
auto getAvailableDomain = [](Decl *D, DeclAttribute *A) {
9091+
return D->getSemanticAvailableAttr(cast<AvailableAttr>(A))->getDomain();
9092+
};
9093+
9094+
// Extra logic for specific attributes.
9095+
switch (api->getKind()) {
9096+
case DeclAttrKind::Expose:
9097+
return cast<ExposeAttr>(api)->getExposureKind()
9098+
== cast<ExposeAttr>(abi)->getExposureKind();
9099+
9100+
case DeclAttrKind::Extern:
9101+
return cast<ExternAttr>(api)->getExternKind()
9102+
== cast<ExternAttr>(abi)->getExternKind();
9103+
9104+
case DeclAttrKind::Available:
9105+
return getAvailableDomain(apiDecl, api)
9106+
== getAvailableDomain(abiDecl, abi);
9107+
return true;
9108+
9109+
default:
9110+
break;
9111+
}
9112+
9113+
return true;
9114+
}
9115+
9116+
/// Check two attribute lists against one another.
9117+
///
9118+
/// This pairs up attributes which are sufficiently similar (as determined by
9119+
/// \c canCompareAttrs() ) and then checks them. Attributes which
9120+
/// have no counterpart are checked individually.
90759121
bool checkAttrs(DeclAttributes api, DeclAttributes abi,
90769122
Decl *apiDecl, Decl *abiDecl) {
90779123
bool didDiagnose = false;
9078-
// TODO: Actually check attributes
9124+
9125+
// Collect all ABI attrs.
9126+
SmallVector<DeclAttribute*, 32> remainingABIDeclAttrs;
9127+
for (auto *abiDeclAttr : abi) {
9128+
remainingABIDeclAttrs.push_back(abiDeclAttr);
9129+
}
9130+
9131+
// Visit each API attr, pairing it with an ABI attr if possible.
9132+
// Note that this will visit even invalid attributes.
9133+
for (auto *apiDeclAttr : api) {
9134+
auto abiAttrIter = llvm::find_if(remainingABIDeclAttrs,
9135+
[&](DeclAttribute *abiDeclAttr) {
9136+
return abiDeclAttr && canCompareAttrs(apiDeclAttr, abiDeclAttr,
9137+
apiDecl, abiDecl);
9138+
});
9139+
DeclAttribute *abiDeclAttr = nullptr;
9140+
if (abiAttrIter != remainingABIDeclAttrs.end()) {
9141+
// Found a matching ABI attr. Claim and use it.
9142+
std::swap(abiDeclAttr, *abiAttrIter);
9143+
}
9144+
didDiagnose |= checkAttr(apiDeclAttr, abiDeclAttr, apiDecl, abiDecl);
9145+
}
9146+
9147+
// Visit leftover ABI attrs.
9148+
for (auto *abiDeclAttr : remainingABIDeclAttrs) {
9149+
if (abiDeclAttr)
9150+
didDiagnose |= checkAttr(nullptr, abiDeclAttr, apiDecl, abiDecl);
9151+
}
90799152
return didDiagnose;
90809153
}
90819154

9155+
/// Check a single attribute against its counterpart. If an attribute has no
9156+
/// counterpart, the counterpart may be \c nullptr ; either \p abi or \p abi
9157+
/// may be \c nullptr , but never both.
9158+
bool checkAttr(DeclAttribute *api, DeclAttribute *abi,
9159+
Decl *apiDecl, Decl *abiDecl) {
9160+
ASSERT(api || abi && "checkAttr() should have at least one attribute");
9161+
9162+
// If either attribute has already been diagnosed, don't check here.
9163+
if ((api && api->isInvalid()) || (abi && abi->isInvalid()))
9164+
return true;
9165+
9166+
auto kind = api ? api->getKind() : abi->getKind();
9167+
auto behaviors = DeclAttribute::getBehaviors(kind);
9168+
9169+
switch (behaviors & DeclAttribute::InABIAttrMask) {
9170+
case DeclAttribute::UnreachableInABIAttr:
9171+
ASSERT(abiAttr->canAppearOnDecl(apiDecl)
9172+
&& "checking @abi on decl that can't have it???");
9173+
ASSERT(!abiAttr->canAppearOnDecl(apiDecl)
9174+
&& "unreachable-in-@abi attr on reachable decl???");
9175+
9176+
// If the asserts are disabled, fall through to no checking.
9177+
LLVM_FALLTHROUGH;
9178+
9179+
case DeclAttribute::UnconstrainedInABIAttr:
9180+
// No checking required.
9181+
return false;
9182+
9183+
case DeclAttribute::ForbiddenInABIAttr:
9184+
// Diagnose if ABI has attribute.
9185+
if (abi) {
9186+
// A shorthand `@available(foo 1, bar 2, *)` attribute gets parsed into
9187+
// several separate `AvailableAttr`s, each with the full range of the
9188+
// shorthand attribute. If we've already diagnosed one of them, don't
9189+
// diagnose the rest; otherwise, record that we've diagnosed this one.
9190+
if (isa<AvailableAttr>(abi) &&
9191+
!diagnosedAvailableAttrSourceLocs.insert(abi->getLocation()))
9192+
return true;
9193+
9194+
diagnoseAndRemoveAttr(abi, diag::attr_abi_forbidden_attr,
9195+
abi->getAttrName(), abi->isDeclModifier());
9196+
return true;
9197+
}
9198+
9199+
return false;
9200+
9201+
case DeclAttribute::InferredInABIAttr:
9202+
if (!abi && api->canClone()) {
9203+
// Infer an identical attribute.
9204+
abi = api->clone(ctx);
9205+
abi->setImplicit(true);
9206+
abiDecl->getAttrs().add(abi);
9207+
9208+
if (ctx.LangOpts.EnableABIInferenceRemarks) {
9209+
SmallString<64> scratch;
9210+
auto abiAttrAsString = printAttr(abi, abiDecl, scratch);
9211+
9212+
abiDecl->diagnose(diag::abi_attr_inferred_attribute,
9213+
abiAttrAsString, api->isDeclModifier());
9214+
noteAttrHere(api, apiDecl, /*isMatch=*/true);
9215+
}
9216+
}
9217+
9218+
// Other than the cloning behavior, Inferred behaves like Equivalent.
9219+
LLVM_FALLTHROUGH;
9220+
9221+
case DeclAttribute::EquivalentInABIAttr:
9222+
// Diagnose if API doesn't have attribute.
9223+
if (!api) {
9224+
diagnoseAndRemoveAttr(abi, diag::attr_abi_extra_attr,
9225+
abi->getAttrName(), abi->isDeclModifier(),
9226+
abi->isImplicit());
9227+
return true;
9228+
}
9229+
9230+
// Diagnose if ABI doesn't have attribute.
9231+
if (!abi) {
9232+
SmallString<64> scratch;
9233+
auto apiAttrAsString = printAttr(api, apiDecl, scratch,
9234+
/*toInsert=*/true);
9235+
9236+
ctx.Diags.diagnose(abiDecl, diag::attr_abi_missing_attr,
9237+
api->getAttrName(), api->isDeclModifier())
9238+
.fixItInsert(abiDecl->getAttributeInsertionLoc(api->isDeclModifier()),
9239+
apiAttrAsString);
9240+
noteAttrHere(api, apiDecl);
9241+
return true;
9242+
}
9243+
9244+
// Diagnose if two attributes are mismatched.
9245+
if (!api->isEquivalent(abi, apiDecl)) {
9246+
SmallString<64> scratch;
9247+
auto apiAttrAsString = printAttr(api, apiDecl, scratch);
9248+
9249+
ctx.Diags.diagnose(abi->getLocation(), diag::attr_abi_mismatched_attr,
9250+
abi->getAttrName(), abi->isDeclModifier(),
9251+
apiAttrAsString)
9252+
.fixItReplace(abi->getRangeWithAt(), apiAttrAsString);
9253+
noteAttrHere(api, apiDecl);
9254+
return true;
9255+
}
9256+
9257+
return false;
9258+
}
9259+
9260+
llvm_unreachable("unknown InABIAttrMask behavior");
9261+
}
9262+
9263+
StringRef printAttr(DeclAttribute *attr,
9264+
Decl *decl,
9265+
SmallVectorImpl<char> &scratch,
9266+
bool toInsert = false) {
9267+
auto opts = PrintOptions::printForDiagnostics(AccessLevel::Private,
9268+
ctx.TypeCheckerOpts.PrintFullConvention);
9269+
opts.PrintLongAttrsOnSeparateLines = false;
9270+
9271+
llvm::raw_svector_ostream os{scratch};
9272+
StreamPrinter printer{os};
9273+
attr->print(printer, opts, decl);
9274+
9275+
auto str = StringRef(scratch.begin(), scratch.size());
9276+
if (!toInsert)
9277+
str = str.trim(' ');
9278+
return str;
9279+
}
9280+
9281+
void noteAttrHere(DeclAttribute *attr, Decl *decl, bool isMatch = false) {
9282+
SourceLoc loc = attr->getLocation();
9283+
if (loc.isValid())
9284+
ctx.Diags.diagnose(loc, diag::attr_abi_matching_attr_here,
9285+
isMatch, attr->isDeclModifier(), attr->isImplicit());
9286+
else
9287+
ctx.Diags.diagnose(decl, diag::attr_abi_matching_attr_here,
9288+
isMatch, attr->isDeclModifier(), attr->isImplicit());
9289+
}
9290+
90829291
// MARK: @abi checking - types
90839292

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

lib/Sema/TypeCheckDeclPrimary.cpp

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

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