Skip to content

Commit 3cb4219

Browse files
committed
[clang] Fix the post-filtering heuristics for GSLPointer case.
1 parent 0653698 commit 3cb4219

File tree

3 files changed

+139
-24
lines changed

3 files changed

+139
-24
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,8 @@ Improvements to Clang's diagnostics
464464

465465
- Clang now diagnoses ``[[deprecated]]`` attribute usage on local variables (#GH90073).
466466

467+
- Fix false positives when `[[gsl::Owner/Pointer]]` and `[[clang::lifetimebound]]` are used together.
468+
467469
Improvements to Clang's time-trace
468470
----------------------------------
469471

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,87 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
10931093
}
10941094
return false;
10951095
}
1096+
// Result of analyzing the Path for GSLPointer.
1097+
enum AnalysisResult {
1098+
// Path does not correspond to a GSLPointer.
1099+
NotGSLPointer,
1100+
1101+
// A relevant case was identified.
1102+
Report,
1103+
// Stop the entire traversal.
1104+
Abandon,
1105+
// Skip this step and continue traversing inner AST nodes.
1106+
Skip,
1107+
};
1108+
// Analyze cases where a GSLPointer is initialized or assigned from a
1109+
// temporary owner object.
1110+
static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
1111+
Local L) {
1112+
if (!pathOnlyHandlesGslPointer(Path))
1113+
return NotGSLPointer;
1114+
1115+
// At this point, Path represents a series of operations involving a
1116+
// GSLPointer, either in the process of initialization or assignment.
1117+
1118+
// Note: A LifetimeBoundCall can appear interleaved in this sequence.
1119+
// For example:
1120+
// const std::string& Ref(const std::string& a [[clang::lifetimebound]]);
1121+
// string_view abc = Ref(std::string());
1122+
// The "Path" is [GSLPointerInit, LifetimeboundCall], where "L" is the
1123+
// temporary "std::string()" object. We need to check if the function with the
1124+
// lifetimebound attribute returns a "owner" type.
1125+
if (Path.back().Kind == IndirectLocalPathEntry::LifetimeBoundCall) {
1126+
// The lifetimebound applies to the implicit object parameter of a method.
1127+
if (const auto *Method = llvm::dyn_cast<CXXMethodDecl>(Path.back().D)) {
1128+
if (Method->getReturnType()->isReferenceType() &&
1129+
isRecordWithAttr<OwnerAttr>(
1130+
Method->getReturnType()->getPointeeType()))
1131+
return Report;
1132+
return Abandon;
1133+
}
1134+
// The lifetimebound applies to a function parameter.
1135+
const auto *PD = llvm::dyn_cast<ParmVarDecl>(Path.back().D);
1136+
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(PD->getDeclContext())) {
1137+
if (isa<CXXConstructorDecl>(FD)) {
1138+
// Constructor case: the parameter is annotated with lifetimebound
1139+
// e.g., GSLPointer(const S& s [[clang::lifetimebound]])
1140+
// We still respect this case even the type S is not an owner.
1141+
return Report;
1142+
}
1143+
// For regular functions, check if the return type has an Owner attribute.
1144+
// e.g., const GSLOwner& func(const Foo& foo [[clang::lifetimebound]])
1145+
if (FD->getReturnType()->isReferenceType() &&
1146+
isRecordWithAttr<OwnerAttr>(FD->getReturnType()->getPointeeType()))
1147+
return Report;
1148+
}
1149+
return Abandon;
1150+
}
1151+
1152+
if (isa<DeclRefExpr>(L)) {
1153+
// We do not want to follow the references when returning a pointer
1154+
// originating from a local owner to avoid the following false positive:
1155+
// int &p = *localUniquePtr;
1156+
// someContainer.add(std::move(localUniquePtr));
1157+
// return p;
1158+
if (!pathContainsInit(Path) && isRecordWithAttr<OwnerAttr>(L->getType()))
1159+
return Report;
1160+
return Abandon;
1161+
}
1162+
1163+
// The GSLPointer is from a temporary object.
1164+
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
1165+
1166+
bool IsGslPtrValueFromGslTempOwner =
1167+
MTE && !MTE->getExtendingDecl() &&
1168+
isRecordWithAttr<OwnerAttr>(MTE->getType());
1169+
// Skipping a chain of initializing gsl::Pointer annotated objects.
1170+
// We are looking only for the final source to find out if it was
1171+
// a local or temporary owner or the address of a local
1172+
// variable/param.
1173+
if (!IsGslPtrValueFromGslTempOwner)
1174+
return Skip;
1175+
return Report;
1176+
}
10961177

10971178
static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
10981179
if (!CMD)
@@ -1131,27 +1212,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11311212

11321213
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
11331214

1134-
bool IsGslPtrValueFromGslTempOwner = false;
1135-
if (pathOnlyHandlesGslPointer(Path)) {
1136-
if (isa<DeclRefExpr>(L)) {
1137-
// We do not want to follow the references when returning a pointer
1138-
// originating from a local owner to avoid the following false positive:
1139-
// int &p = *localUniquePtr;
1140-
// someContainer.add(std::move(localUniquePtr));
1141-
// return p;
1142-
if (pathContainsInit(Path) ||
1143-
!isRecordWithAttr<OwnerAttr>(L->getType()))
1144-
return false;
1145-
} else {
1146-
IsGslPtrValueFromGslTempOwner =
1147-
MTE && !MTE->getExtendingDecl() &&
1148-
isRecordWithAttr<OwnerAttr>(MTE->getType());
1149-
// Skipping a chain of initializing gsl::Pointer annotated objects.
1150-
// We are looking only for the final source to find out if it was
1151-
// a local or temporary owner or the address of a local variable/param.
1152-
if (!IsGslPtrValueFromGslTempOwner)
1153-
return true;
1154-
}
1215+
bool IsGslPtrValueFromGslTempOwner = true;
1216+
switch (analyzePathForGSLPointer(Path, L)) {
1217+
case Abandon:
1218+
return false;
1219+
case Skip:
1220+
return true;
1221+
case NotGSLPointer:
1222+
IsGslPtrValueFromGslTempOwner = false;
1223+
LLVM_FALLTHROUGH;
1224+
case Report:
1225+
break;
11551226
}
11561227

11571228
switch (LK) {

clang/test/Sema/warn-lifetime-analysis-nocfg.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,8 +727,9 @@ struct [[gsl::Pointer]] Span {
727727

728728
// Pointer from Owner<Pointer>
729729
std::string_view test5() {
730-
std::string_view a = StatusOr<std::string_view>().valueLB(); // expected-warning {{object backing the pointer will be dest}}
731-
return StatusOr<std::string_view>().valueLB(); // expected-warning {{returning address of local temporary}}
730+
// The Owner<Pointer> doesn't own the object which its inner pointer points to.
731+
std::string_view a = StatusOr<std::string_view>().valueLB(); // OK
732+
return StatusOr<std::string_view>().valueLB(); // OK
732733

733734
// No dangling diagnostics on non-lifetimebound methods.
734735
std::string_view b = StatusOr<std::string_view>().valueNoLB();
@@ -775,7 +776,7 @@ Span<std::string> test10(StatusOr<std::vector<std::string>> aa) {
775776

776777
// Pointer<Owner>> from Owner<Pointer<Owner>>
777778
Span<std::string> test11(StatusOr<Span<std::string>> aa) {
778-
return aa.valueLB(); // expected-warning {{address of stack memory}}
779+
return aa.valueLB(); // OK
779780
return aa.valueNoLB(); // OK.
780781
}
781782

@@ -793,3 +794,44 @@ void test13() {
793794
}
794795

795796
} // namespace GH100526
797+
798+
namespace LifetimeboundInterleave {
799+
800+
const std::string& Ref(const std::string& abc [[clang::lifetimebound]]);
801+
std::string_view test1() {
802+
std::string_view t1 = Ref(std::string()); // expected-warning {{object backing}}
803+
t1 = Ref(std::string()); // expected-warning {{object backing}}
804+
return Ref(std::string()); // expected-warning {{returning address}}
805+
}
806+
807+
template <typename T>
808+
struct Foo {
809+
const T& get() const [[clang::lifetimebound]];
810+
const T& getNoLB() const;
811+
};
812+
std::string_view test2(Foo<std::string> r1, Foo<std::string_view> r2) {
813+
std::string_view t1 = Foo<std::string>().get(); // expected-warning {{object backing}}
814+
t1 = Foo<std::string>().get(); // expected-warning {{object backing}}
815+
return r1.get(); // expected-warning {{address of stack}}
816+
817+
std::string_view t2 = Foo<std::string_view>().get();
818+
t2 = Foo<std::string_view>().get();
819+
return r2.get();
820+
821+
// no warning on no-LB-annotated method.
822+
std::string_view t3 = Foo<std::string>().getNoLB();
823+
t3 = Foo<std::string>().getNoLB();
824+
return r1.getNoLB();
825+
}
826+
827+
struct Bar {};
828+
struct [[gsl::Pointer]] Pointer {
829+
Pointer(const Bar & bar [[clang::lifetimebound]]);
830+
};
831+
Pointer test3(Bar bar) {
832+
Pointer p = Pointer(Bar()); // expected-warning {{temporary}}
833+
p = Pointer(Bar()); // expected-warning {{object backing}}
834+
return bar; // expected-warning {{address of stack}}
835+
}
836+
837+
} // namespace LifetimeboundInterleave

0 commit comments

Comments
 (0)