Skip to content

Commit 721e6a3

Browse files
hnrklssndelcypher
authored andcommitted
[BoundsSafety][APINotes] Add support for bounds safety annotations
This cherry-picks ``` commit e5e04cd Author: Henrik G. Olsson <[email protected]> Date: Mon Feb 3 10:25:30 2025 -0800 Revert "Revert "[APINotes] Add support for bounds safety annotations"" This reverts commit 7cbd1e7. ``` from the `swift/release/6.2` branch (landed in #9942). Conflicts: clang/include/clang/Parse/Parser.h clang/include/clang/Sema/Sema.h clang/lib/APINotes/APINotesFormat.h Below is the original commit message. rdar://151820159 --- [APINotes] Add support for bounds safety annotations This adds support for annotating function parameters with __counted_by, __sized_by, __counted_by_or_null, __sized_by_or_null, and __ended_by, using API notes. The main content of handlePtrCountedByEndedByAttr is extracted to applyPtrCountedByEndedByAttr and decoupled from ParsedAttr. The helper function ParseBoundsAttributeArgFromString is added to make it possible to parse count expressions from SemaAPINotes. The current implementation of __terminated_by/__null_terminated makes it harder to extract from the iterative type processing, but since it doesn't require any extra context to parse the attribute, it can be applied using the normal Type override instead. rdar://139830881
1 parent 72cf567 commit 721e6a3

20 files changed

+1080
-100
lines changed

clang/include/clang/APINotes/Types.h

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,72 @@ inline bool operator!=(const ContextInfo &LHS, const ContextInfo &RHS) {
300300
return !(LHS == RHS);
301301
}
302302

303+
/* TO_UPSTREAM(BoundsSafety) ON */
304+
class BoundsSafetyInfo {
305+
public:
306+
enum class BoundsSafetyKind {
307+
CountedBy = 0,
308+
CountedByOrNull,
309+
SizedBy,
310+
SizedByOrNull,
311+
EndedBy,
312+
};
313+
314+
private:
315+
/// Whether this property has been audited for nullability.
316+
LLVM_PREFERRED_TYPE(bool)
317+
unsigned KindAudited : 1;
318+
319+
/// The kind of nullability for this property. Only valid if the nullability
320+
/// has been audited.
321+
LLVM_PREFERRED_TYPE(BoundsSafetyKind)
322+
unsigned Kind : 3;
323+
324+
LLVM_PREFERRED_TYPE(bool)
325+
unsigned LevelAudited : 1;
326+
327+
unsigned Level : 3;
328+
329+
public:
330+
std::string ExternalBounds;
331+
332+
BoundsSafetyInfo()
333+
: KindAudited(false), Kind(0), LevelAudited(false), Level(0),
334+
ExternalBounds("") {}
335+
336+
std::optional<BoundsSafetyKind> getKind() const {
337+
return KindAudited ? std::optional<BoundsSafetyKind>(
338+
static_cast<BoundsSafetyKind>(Kind))
339+
: std::nullopt;
340+
}
341+
342+
void setKindAudited(BoundsSafetyKind kind) {
343+
KindAudited = true;
344+
Kind = static_cast<unsigned>(kind);
345+
}
346+
347+
std::optional<unsigned> getLevel() const {
348+
return LevelAudited ? std::optional<unsigned>(Level) : std::nullopt;
349+
}
350+
351+
void setLevelAudited(unsigned level) {
352+
LevelAudited = true;
353+
Level = level;
354+
}
355+
356+
friend bool operator==(const BoundsSafetyInfo &, const BoundsSafetyInfo &);
357+
358+
LLVM_DUMP_METHOD void dump(llvm::raw_ostream &OS) const;
359+
};
360+
361+
inline bool operator==(const BoundsSafetyInfo &LHS,
362+
const BoundsSafetyInfo &RHS) {
363+
return LHS.KindAudited == RHS.KindAudited && LHS.Kind == RHS.Kind &&
364+
LHS.LevelAudited == RHS.LevelAudited && LHS.Level == RHS.Level &&
365+
LHS.ExternalBounds == RHS.ExternalBounds;
366+
}
367+
/* TO_UPSTREAM(BoundsSafety) OFF */
368+
303369
/// API notes for a variable/property.
304370
class VariableInfo : public CommonEntityInfo {
305371
/// Whether this property has been audited for nullability.
@@ -439,10 +505,14 @@ class ParamInfo : public VariableInfo {
439505
unsigned RawRetainCountConvention : 3;
440506

441507
public:
508+
/* TO_UPSTREAM(BoundsSafety) ON */
509+
std::optional<BoundsSafetyInfo> BoundsSafety;
510+
/* TO_UPSTREAM(BoundsSafety) OFF */
511+
442512
ParamInfo()
443513
: NoEscapeSpecified(false), NoEscape(false),
444514
LifetimeboundSpecified(false), Lifetimebound(false),
445-
RawRetainCountConvention() {}
515+
RawRetainCountConvention(), BoundsSafety(std::nullopt) {}
446516

447517
std::optional<bool> isNoEscape() const {
448518
return NoEscapeSpecified ? std::optional<bool>(NoEscape) : std::nullopt;
@@ -488,6 +558,11 @@ class ParamInfo : public VariableInfo {
488558
if (!RawRetainCountConvention)
489559
RawRetainCountConvention = RHS.RawRetainCountConvention;
490560

561+
/* TO_UPSTREAM(BoundsSafety) ON */
562+
if (!BoundsSafety)
563+
BoundsSafety = RHS.BoundsSafety;
564+
/* TO_UPSTREAM(BoundsSafety) OFF */
565+
491566
return *this;
492567
}
493568

@@ -502,7 +577,10 @@ inline bool operator==(const ParamInfo &LHS, const ParamInfo &RHS) {
502577
LHS.NoEscape == RHS.NoEscape &&
503578
LHS.LifetimeboundSpecified == RHS.LifetimeboundSpecified &&
504579
LHS.Lifetimebound == RHS.Lifetimebound &&
505-
LHS.RawRetainCountConvention == RHS.RawRetainCountConvention;
580+
LHS.RawRetainCountConvention == RHS.RawRetainCountConvention &&
581+
/* TO_UPSTREAM(BoundsSafety) ON */
582+
LHS.BoundsSafety == RHS.BoundsSafety;
583+
/* TO_UPSTREAM(BoundsSafety) OFF */
506584
}
507585

508586
inline bool operator!=(const ParamInfo &LHS, const ParamInfo &RHS) {

clang/include/clang/Parse/Parser.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2842,6 +2842,26 @@ class Parser : public CodeCompletionHandler {
28422842
TypeResult ParseTypeFromString(StringRef TypeStr, StringRef Context,
28432843
SourceLocation IncludeLoc);
28442844

2845+
// TO_UPSTREAM(BoundsSafety) ON
2846+
/// Parse the given string as an expression in the argument position for a
2847+
/// bounds safety attribute.
2848+
///
2849+
/// This is a dangerous utility function currently employed only by API notes.
2850+
/// It is not a general entry-point for safely parsing expressions from
2851+
/// strings.
2852+
///
2853+
/// \param ExprStr The string to be parsed as an expression.
2854+
/// \param Context The name of the context in which this string is being
2855+
/// parsed, which will be used in diagnostics.
2856+
/// \param ParentDecl If a function or method is provided, the parameters are
2857+
/// added to the current parsing context.
2858+
/// \param IncludeLoc The location at which this parse was triggered.
2859+
ExprResult ParseBoundsAttributeArgFromString(StringRef ExprStr,
2860+
StringRef Context,
2861+
Decl *ParentDecl,
2862+
SourceLocation IncludeLoc);
2863+
// TO_UPSTREAM(BoundsSafety) OFF
2864+
28452865
///@}
28462866

28472867
//

clang/include/clang/Sema/Sema.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,12 @@ class Sema final : public SemaBase {
15291529
std::function<TypeResult(StringRef, StringRef, SourceLocation)>
15301530
ParseTypeFromStringCallback;
15311531

1532+
/* TO_UPSTREAM(BoundsSafety) ON */
1533+
/// Callback to the parser to parse a type expressed as a string.
1534+
std::function<ExprResult(StringRef, StringRef, Decl *, SourceLocation)>
1535+
ParseBoundsAttributeArgFromStringCallback;
1536+
/* TO_UPSTREAM(BoundsSafety) OFF */
1537+
15321538
/// VAListTagName - The declaration name corresponding to __va_list_tag.
15331539
/// This is used as part of a hack to omit that class from ADL results.
15341540
DeclarationName VAListTagName;
@@ -2781,6 +2787,12 @@ class Sema final : public SemaBase {
27812787
///
27822788
bool BoundsSafetyFixItWasEmittedFor(const DeclaratorDecl *DD,
27832789
bool Set = false);
2790+
2791+
void applyPtrCountedByEndedByAttr(Decl *D, unsigned Level,
2792+
AttributeCommonInfo::Kind Kind,
2793+
Expr *AttrArg, SourceLocation Loc,
2794+
SourceRange Range, StringRef DiagName,
2795+
bool OriginatesInAPINotes = false);
27842796
/* TO_UPSTREAM(BoundsSafety) OFF*/
27852797

27862798

clang/lib/APINotes/APINotesFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0;
2424
/// API notes file minor version number.
2525
///
2626
/// When the format changes IN ANY WAY, this number should be incremented.
27-
const uint16_t VERSION_MINOR = 35; // SwiftDefaultOwnership
27+
const uint16_t VERSION_MINOR = 36; // TO_UPSTREAM(BoundsSafety)
2828

2929
const uint8_t kSwiftConforms = 1;
3030
const uint8_t kSwiftDoesNotConform = 2;

clang/lib/APINotes/APINotesReader.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,31 @@ class FieldTableInfo
322322
}
323323
};
324324

325+
/* TO_UPSTREAM(BoundsSafety) ON */
326+
/// Read serialized BoundsSafetyInfo.
327+
void ReadBoundsSafetyInfo(const uint8_t *&Data, BoundsSafetyInfo &Info) {
328+
uint8_t Payload = endian::readNext<uint8_t, llvm::endianness::little>(Data);
329+
330+
if (Payload & 0x01) {
331+
uint8_t Level = (Payload >> 1) & 0x7;
332+
Info.setLevelAudited(Level);
333+
}
334+
Payload >>= 4;
335+
336+
if (Payload & 0x01) {
337+
uint8_t Kind = (Payload >> 1) & 0x7;
338+
assert(Kind >= (uint8_t)BoundsSafetyInfo::BoundsSafetyKind::CountedBy);
339+
assert(Kind <= (uint8_t)BoundsSafetyInfo::BoundsSafetyKind::EndedBy);
340+
Info.setKindAudited((BoundsSafetyInfo::BoundsSafetyKind)Kind);
341+
}
342+
343+
uint16_t ExternalBoundsLen =
344+
endian::readNext<uint16_t, llvm::endianness::little>(Data);
345+
Info.ExternalBounds = std::string(Data, Data + ExternalBoundsLen);
346+
Data += ExternalBoundsLen;
347+
}
348+
/* TO_UPSTREAM(BoundsSafety) OFF */
349+
325350
/// Read serialized ParamInfo.
326351
void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) {
327352
ReadVariableInfo(Data, Info);
@@ -338,7 +363,13 @@ void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) {
338363
if (Payload & 0x01)
339364
Info.setNoEscape(Payload & 0x02);
340365
Payload >>= 2;
341-
assert(Payload == 0 && "Bad API notes");
366+
/* TO_UPSTREAM(BoundsSafety) ON */
367+
if (Payload & 0x01) {
368+
BoundsSafetyInfo BSI;
369+
ReadBoundsSafetyInfo(Data, BSI);
370+
Info.BoundsSafety = BSI;
371+
}
372+
/* TO_UPSTREAM(BoundsSafety) OFF */
342373
}
343374

344375
/// Read serialized FunctionInfo.

clang/lib/APINotes/APINotesTypes.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,34 @@ LLVM_DUMP_METHOD void ObjCPropertyInfo::dump(llvm::raw_ostream &OS) const {
6161
OS << '\n';
6262
}
6363

64+
LLVM_DUMP_METHOD void BoundsSafetyInfo::dump(llvm::raw_ostream &OS) const {
65+
if (KindAudited) {
66+
assert((BoundsSafetyKind)Kind >= BoundsSafetyKind::CountedBy);
67+
assert((BoundsSafetyKind)Kind <= BoundsSafetyKind::EndedBy);
68+
switch ((BoundsSafetyKind)Kind) {
69+
case BoundsSafetyKind::CountedBy:
70+
OS << "[counted_by] ";
71+
break;
72+
case BoundsSafetyKind::CountedByOrNull:
73+
OS << "[counted_by_or_null] ";
74+
break;
75+
case BoundsSafetyKind::SizedBy:
76+
OS << "[sized_by] ";
77+
break;
78+
case BoundsSafetyKind::SizedByOrNull:
79+
OS << "[sized_by_or_null] ";
80+
break;
81+
case BoundsSafetyKind::EndedBy:
82+
OS << "[ended_by] ";
83+
break;
84+
}
85+
}
86+
if (LevelAudited)
87+
OS << "Level: " << Level << " ";
88+
OS << "ExternalBounds: "
89+
<< (ExternalBounds.empty() ? "<missing>" : ExternalBounds) << '\n';
90+
}
91+
6492
LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const {
6593
static_cast<const VariableInfo &>(*this).dump(OS);
6694
if (NoEscapeSpecified)
@@ -69,6 +97,8 @@ LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const {
6997
OS << (Lifetimebound ? "[Lifetimebound] " : "");
7098
OS << "RawRetainCountConvention: " << RawRetainCountConvention << ' ';
7199
OS << '\n';
100+
if (BoundsSafety)
101+
BoundsSafety->dump(OS);
72102
}
73103

74104
LLVM_DUMP_METHOD void FunctionInfo::dump(llvm::raw_ostream &OS) const {

clang/lib/APINotes/APINotesWriter.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,15 +1064,48 @@ void APINotesWriter::Implementation::writeGlobalVariableBlock(
10641064
}
10651065
}
10661066

1067+
/* TO_UPSTREAM(BoundsSafety) ON */
10671068
namespace {
1069+
void emitBoundsSafetyInfo(raw_ostream &OS, const BoundsSafetyInfo &BSI) {
1070+
llvm::support::endian::Writer writer(OS, llvm::endianness::little);
1071+
uint8_t flags = 0;
1072+
if (auto kind = BSI.getKind()) {
1073+
flags |= 0x01; // 1 bit
1074+
flags |= (uint8_t)*kind << 1; // 3 bits
1075+
}
1076+
flags <<= 4;
1077+
if (auto level = BSI.getLevel()) {
1078+
flags |= 0x01; // 1 bit
1079+
flags |= *level << 1; // 3 bits
1080+
}
1081+
1082+
writer.write<uint8_t>(flags);
1083+
writer.write<uint16_t>(BSI.ExternalBounds.size());
1084+
writer.write(
1085+
ArrayRef<char>{BSI.ExternalBounds.data(), BSI.ExternalBounds.size()});
1086+
}
1087+
1088+
unsigned getBoundsSafetyInfoSize(const BoundsSafetyInfo &BSI) {
1089+
return 1 + sizeof(uint16_t) + BSI.ExternalBounds.size();
1090+
}
1091+
/* TO_UPSTREAM(BoundsSafety) OFF */
1092+
10681093
unsigned getParamInfoSize(const ParamInfo &PI) {
1069-
return getVariableInfoSize(PI) + 1;
1094+
unsigned BSISize = 0;
1095+
/* TO_UPSTREAM(BoundsSafety) ON */
1096+
if (auto BSI = PI.BoundsSafety)
1097+
BSISize = getBoundsSafetyInfoSize(*BSI);
1098+
/* TO_UPSTREAM(BoundsSafety) OFF */
1099+
return getVariableInfoSize(PI) + 1 + BSISize;
10701100
}
10711101

10721102
void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) {
10731103
emitVariableInfo(OS, PI);
10741104

10751105
uint8_t flags = 0;
1106+
if (PI.BoundsSafety)
1107+
flags |= 0x01;
1108+
flags <<= 2;
10761109
if (auto noescape = PI.isNoEscape()) {
10771110
flags |= 0x01;
10781111
if (*noescape)
@@ -1090,6 +1123,10 @@ void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) {
10901123

10911124
llvm::support::endian::Writer writer(OS, llvm::endianness::little);
10921125
writer.write<uint8_t>(flags);
1126+
/* TO_UPSTREAM(BoundsSafety) ON */
1127+
if (auto BSI = PI.BoundsSafety)
1128+
emitBoundsSafetyInfo(OS, *PI.BoundsSafety);
1129+
/* TO_UPSTREAM(BoundsSafety) OFF */
10931130
}
10941131

10951132
/// Retrieve the serialized size of the given FunctionInfo, for use in on-disk

0 commit comments

Comments
 (0)