Skip to content

Commit e260d65

Browse files
committed
Add @safe(unchecked) to allow unsafe code within a declaration.
Introduce an attribute to allow unsafe code within the annotated declaration without presenting an unsafe interface to users. This is, by its nature, and unsafe construct, and is used to document where unsafe behavior is encapsulated in safe constructs. There is an optional message that can be used as part of an audit trail.
1 parent cf7fcf2 commit e260d65

File tree

13 files changed

+138
-5
lines changed

13 files changed

+138
-5
lines changed

include/swift/AST/Attr.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,6 +2837,26 @@ class RawLayoutAttr final : public DeclAttribute {
28372837
UNIMPLEMENTED_CLONE(RawLayoutAttr)
28382838
};
28392839

2840+
class SafeAttr final : public DeclAttribute {
2841+
public:
2842+
/// The optional message.
2843+
const StringRef message;
2844+
2845+
SafeAttr(SourceLoc atLoc, SourceRange range, StringRef message,
2846+
bool isImplicit = false)
2847+
: DeclAttribute(DeclAttrKind::Safe, atLoc, range, isImplicit),
2848+
message(message) { }
2849+
2850+
static bool classof(const DeclAttribute *DA) {
2851+
return DA->getKind() == DeclAttrKind::Safe;
2852+
}
2853+
2854+
/// Create a copy of this attribute.
2855+
SafeAttr *clone(ASTContext &ctx) const {
2856+
return new (ctx) SafeAttr(AtLoc, Range, message, isImplicit());
2857+
}
2858+
};
2859+
28402860
class LifetimeAttr final : public DeclAttribute {
28412861
LifetimeEntry *entry;
28422862

include/swift/AST/DeclAttr.def

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,8 @@ SIMPLE_DECL_ATTR(sensitive, Sensitive,
504504
159)
505505

506506
SIMPLE_DECL_ATTR(unsafe, Unsafe,
507-
OnAbstractFunction | OnSubscript | OnVar | OnMacro | OnNominalType | OnExtension |
508-
UserInaccessible |
507+
OnAbstractFunction | OnSubscript | OnVar | OnMacro | OnNominalType |
508+
OnExtension | OnTypeAlias | UserInaccessible |
509509
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
510510
160)
511511

@@ -517,7 +517,13 @@ SIMPLE_DECL_ATTR(_addressableSelf, AddressableSelf,
517517
OnAccessor | OnConstructor | OnFunc | OnSubscript | ABIBreakingToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | UserInaccessible,
518518
162)
519519

520-
LAST_DECL_ATTR(AddressableSelf)
520+
DECL_ATTR(safe, Safe,
521+
OnAbstractFunction | OnSubscript | OnVar | OnMacro | OnNominalType |
522+
OnExtension | OnTypeAlias | UserInaccessible |
523+
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove,
524+
163)
525+
526+
LAST_DECL_ATTR(Safe)
521527

522528
#undef DECL_ATTR_ALIAS
523529
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/DiagnosticsParse.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2106,6 +2106,9 @@ ERROR(parser_new_parser_errors,none,
21062106
"new Swift parser generated errors for code that C++ parser accepted",
21072107
())
21082108

2109+
ERROR(safe_attr_unchecked,none,
2110+
"'@safe' attribute must be written as '@safe(unchecked)'", ())
2111+
21092112
// MARK: Reference Binding Diagnostics
21102113
ERROR(sil_markuncheckedreferencebinding_requires_attribute,none,
21112114
"mark_unchecked_reference_binding requires an attribute like [inout]", ())

lib/AST/ASTDumper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4116,6 +4116,11 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, StringRef>,
41164116
}
41174117
printFoot();
41184118
}
4119+
void visitSafeAttr(SafeAttr *Attr, StringRef label) {
4120+
printCommon(Attr, "safe_attr", label);
4121+
printFieldQuoted(Attr->message, "message");
4122+
printFoot();
4123+
}
41194124
void visitSILGenNameAttr(SILGenNameAttr *Attr, StringRef label) {
41204125
printCommon(Attr, "silgen_name_attr", label);
41214126
printFlag(Attr->Raw, "raw");

lib/AST/Attr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1715,7 +1715,8 @@ StringRef DeclAttribute::getAttrName() const {
17151715
AccessLevel access = cast<AbstractAccessControlAttr>(this)->getAccess();
17161716
return getAccessLevelSpelling(access);
17171717
}
1718-
1718+
case DeclAttrKind::Safe:
1719+
return "safe";
17191720
case DeclAttrKind::SPIAccessControl:
17201721
return "_spi";
17211722
case DeclAttrKind::ReferenceOwnership:

lib/AST/Decl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ bool Decl::isUnsafe() const {
10801080
}
10811081

10821082
bool Decl::allowsUnsafe() const {
1083-
return isUnsafe();
1083+
return getAttrs().hasAttribute<SafeAttr>() || isUnsafe();
10841084
}
10851085

10861086
Type AbstractFunctionDecl::getThrownInterfaceType() const {

lib/ASTGen/Sources/ASTGen/DeclAttrs.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ extension ASTGenVisitor {
164164
return handle(self.generateProjectedValuePropertyAttr(attribute: node)?.asDeclAttribute)
165165
case .rawLayout:
166166
fatalError("unimplemented")
167+
case .safe:
168+
fatalError("unimplemented")
167169
case .section:
168170
return handle(self.generateSectionAttr(attribute: node)?.asDeclAttribute)
169171
case .semantics:

lib/Parse/ParseDecl.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,6 +3214,69 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
32143214
break;
32153215
}
32163216

3217+
case DeclAttrKind::Safe: {
3218+
if (!consumeIfAttributeLParen()) {
3219+
diagnose(Loc, diag::attr_expected_lparen, AttrName,
3220+
DeclAttribute::isDeclModifier(DK));
3221+
return makeParserError();
3222+
}
3223+
3224+
StringRef parsedName = Tok.getText();
3225+
if (!consumeIf(tok::identifier) || parsedName != "unchecked") {
3226+
diagnose(Loc, diag::safe_attr_unchecked);
3227+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3228+
return makeParserError();
3229+
}
3230+
3231+
StringRef message;
3232+
if (consumeIf(tok::comma)) {
3233+
if (!Tok.is(tok::identifier)) {
3234+
diagnose(Tok, diag::attr_expected_label, "message", AttrName);
3235+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3236+
return makeParserError();
3237+
}
3238+
3239+
StringRef flag = Tok.getText();
3240+
3241+
if (flag != "message") {
3242+
diagnose(Tok.getLoc(), diag::attr_unknown_option, flag, AttrName);
3243+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3244+
return makeParserError();
3245+
}
3246+
consumeToken();
3247+
if (!consumeIf(tok::colon)) {
3248+
diagnose(Tok.getLoc(), diag::attr_expected_colon_after_label, flag);
3249+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3250+
return makeParserError();
3251+
}
3252+
if (!Tok.is(tok::string_literal)) {
3253+
diagnose(Tok.getLoc(), diag::attr_expected_string_literal, AttrName);
3254+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3255+
return makeParserSuccess();
3256+
}
3257+
3258+
std::optional<StringRef> value =
3259+
getStringLiteralIfNotInterpolated(Tok.getLoc(), flag);
3260+
if (!value) {
3261+
errorAndSkipUntilConsumeRightParen(*this, AttrName);
3262+
return makeParserError();
3263+
}
3264+
consumeToken();
3265+
message = *value;
3266+
}
3267+
3268+
if (!consumeIf(tok::r_paren)) {
3269+
diagnose(PreviousLoc, diag::attr_expected_rparen,
3270+
AttrName, /*isModifier*/false)
3271+
.fixItInsertAfter(PreviousLoc, ")");
3272+
}
3273+
AttrRange = SourceRange(Loc, PreviousLoc);
3274+
3275+
if (!DiscardAttribute)
3276+
Attributes.add(new (Context) SafeAttr(AtLoc, AttrRange, message));
3277+
break;
3278+
}
3279+
32173280
case DeclAttrKind::Section: {
32183281
if (!consumeIfAttributeLParen()) {
32193282
diagnose(Loc, diag::attr_expected_lparen, AttrName,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
387387
void visitWeakLinkedAttr(WeakLinkedAttr *attr);
388388
void visitSILGenNameAttr(SILGenNameAttr *attr);
389389
void visitUnsafeAttr(UnsafeAttr *attr);
390+
void visitSafeAttr(SafeAttr *attr);
390391
void visitLifetimeAttr(LifetimeAttr *attr);
391392
void visitAddressableSelfAttr(AddressableSelfAttr *attr);
392393
};
@@ -7775,6 +7776,13 @@ void AttributeChecker::visitUnsafeAttr(UnsafeAttr *attr) {
77757776
diagnoseAndRemoveAttr(attr, diag::unsafe_attr_disabled);
77767777
}
77777778

7779+
void AttributeChecker::visitSafeAttr(SafeAttr *attr) {
7780+
if (Ctx.LangOpts.hasFeature(Feature::AllowUnsafeAttribute))
7781+
return;
7782+
7783+
diagnoseAndRemoveAttr(attr, diag::unsafe_attr_disabled);
7784+
}
7785+
77787786
void AttributeChecker::visitLifetimeAttr(LifetimeAttr *attr) {}
77797787

77807788
void AttributeChecker::visitAddressableSelfAttr(AddressableSelfAttr *attr) {

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,7 @@ namespace {
17371737
UNINTERESTING_ATTR(Lifetime)
17381738
UNINTERESTING_ATTR(AddressableSelf)
17391739
UNINTERESTING_ATTR(Unsafe)
1740+
UNINTERESTING_ATTR(Safe)
17401741
#undef UNINTERESTING_ATTR
17411742

17421743
void visitAvailableAttr(AvailableAttr *attr) {

0 commit comments

Comments
 (0)