[clang] 162edfe - [WebKit Checkers] Add alpha.webkit.UnborrowedCallArgsChecker (#225854)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 24 00:24:13 PDT 2026
Author: geoffreygaren
Date: 2026-09-24T00:24:05-07:00
New Revision: 162edfe631ac3ac87a9b30500a451a0fa853d164
URL: https://github.com/llvm/llvm-project/commit/162edfe631ac3ac87a9b30500a451a0fa853d164
DIFF: https://github.com/llvm/llvm-project/commit/162edfe631ac3ac87a9b30500a451a0fa853d164.diff
LOG: [WebKit Checkers] Add alpha.webkit.UnborrowedCallArgsChecker (#225854)
Like alpha.webkit.UnborrowedLocalVarsChecker, but for function
arguments.
As in the local variable checker, we do not treat an origin that
outlives a function call as sufficient evidence of safety. We need to
prove not only that an object is live, but also that it prevents
interior destruction.
Assisted-by: Claude
Added:
clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
Modified:
clang/docs/analyzer/checkers.md
clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
Removed:
################################################################################
diff --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md
index c598bf05031e9..343b9482f8a62 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -4284,6 +4284,52 @@ The cost is that an identity function is reported even though its result really
> }
> ```
+#### alpha.webkit.UnborrowedCallArgsChecker
+
+The same rule as alpha.webkit.UnborrowedLocalVarsChecker, applied to function arguments.
+
+> ```cpp
+> void someFunction(char* pointer);
+> void someFunction(char& reference);
+> void someFunction(std::span<char> view);
+> void someFunctionByCopy(char value);
+>
+> void foo1(Vector<char>& buffer) {
+> someFunction(buffer.data()); // warn
+> someFunction(buffer[0]); // warn
+> someFunction(buffer.span()); // warn
+> someFunctionByCopy(buffer[0]); // ok, someFunctionByCopy() receives a copy
+> // of the element, not a view
+> }
+>
+> void foo2(Vector<char>& buffer) {
+> buffer.append(buffer[0]); // warn
+> }
+> ```
+
+The implicit object argument counts as an argument:
+
+> ```cpp
+> class Element {
+> public:
+> void someMethod();
+> };
+>
+> void foo3(Vector<Element>& elements) {
+> elements[0].someMethod(); // warn: 'this' is a pointer into elements
+> }
+> ```
+
+These examples do not warn:
+
+> ```cpp
+> void foo4(Vector<char>& buffer) {
+> Borrow<Vector<char>> borrowed(buffer);
+> someFunction(borrowed.get().data()); // ok, guarded by Borrow<T>
+> someFunction(borrowed.get()[0]); // ok, guarded by Borrow<T>
+> }
+> ```
+
#### webkit.RetainPtrCtorAdoptChecker
The goal of this rule is to make sure the constructors of RetainPtr and OSObjectPtr as well as adoptNS, adoptCF, and adoptOSObject are used correctly.
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 24abc8d4a4b8a..3d2428bdf92a5 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1786,6 +1786,10 @@ def UnretainedCallArgsChecker : Checker<"UnretainedCallArgsChecker">,
HelpText<"Check unretained call arguments.">,
Documentation<HasDocumentation>;
+def UnborrowedCallArgsChecker : Checker<"UnborrowedCallArgsChecker">,
+ HelpText<"Check call arguments holding a loan on a CanBorrow object that is not guarded by a Borrow.">,
+ Documentation<HasDocumentation>;
+
def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
HelpText<"Check uncounted local variables.">,
Documentation<HasDocumentation>;
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index 6ff56e9c1ce3b..995667225c961 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -454,7 +454,7 @@ bool tryToFindPtrOrigin(
/*PtrIsLifetimeBoundToOrigin=*/false);
}
-bool isASafeCallArg(const Expr *E) {
+bool originOutlivesCall(const Expr *E) {
assert(E);
auto IsCheckedLocalVarOrParam = [](const VarDecl *Decl) {
auto Ty = Decl->getType();
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
index 6736952134821..68f8623814bcf 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
@@ -71,12 +71,12 @@ bool tryToFindPtrOrigin(
callback);
/// For \p E referring to a ref-countable/-counted pointer/reference we return
-/// whether it's a safe call argument. Examples: function parameter or
-/// this-pointer. The logic relies on the set of recursive rules we enforce for
-/// WebKit codebase.
+/// whether the pointee outlives the current function call. Examples: function
+/// parameter or this-pointer. Outliving the call is not by itself sufficient
+/// evidence of safety for a model that checks for interior destruction.
///
-/// \returns Whether \p E is a safe call arugment.
-bool isASafeCallArg(const clang::Expr *E);
+/// \returns Whether the pointee of \p E outlives the current function call.
+bool originOutlivesCall(const clang::Expr *E);
/// \returns true if E is nullptr or __null.
bool isNullPtr(const clang::Expr *E);
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
index 04c7bb88f5fce..f3450f5129b1b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
@@ -132,24 +132,21 @@ class RawPtrRefCallArgsChecker
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
}
- template <typename CallOrConstrcut>
- void visitCallOrConstructExpr(const CallOrConstrcut *CE,
+ template <typename CallOrConstruct>
+ void visitCallOrConstructExpr(const CallOrConstruct *CE,
const FunctionDecl *F, const Decl *D) const {
if (F) {
- // Skip the first argument for overloaded member operators (e. g. lambda
- // or std::function call operator).
- unsigned ArgIdx =
- isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
-
- if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE))
+ unsigned ArgIdx = 0;
+ if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {
checkThisArg(F, MemberCallExpr, D);
-
- if (ArgIdx) {
- auto *Arg = CE->getArg(0);
- QualType ArgType = Arg->getType().getCanonicalType();
- std::optional<bool> IsUnsafe = Model->isUnsafeType(ArgType);
- if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Arg))
- reportBugOnThis(F, Arg, D);
+ } else if (isa<CXXOperatorCallExpr>(CE) && isa<CXXMethodDecl>(F)) {
+ // An overloaded member operator (e.g. lambda or std::function call
+ // operator) receives the receiver object as argument 0; start the
+ // parameter loop at 1 so we don't match it against the operator's
+ // first declared parameter.
+ auto *Receiver = CE->getArg(0);
+ checkThisArg(F, Receiver, Receiver->getType(), D);
+ ArgIdx = 1;
}
for (auto P = F->param_begin();
@@ -206,10 +203,11 @@ class RawPtrRefCallArgsChecker
if (auto *Receiver = E->getInstanceReceiver()) {
std::optional<bool> IsUnsafe = Model->isUnsafePtr(E->getReceiverType());
- if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) {
+ const Expr *Origin = nullptr;
+ if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver, &Origin)) {
if (isAllocInit(E))
return;
- reportBugOnReceiver(E->getMethodDecl(), Receiver, D);
+ reportBugOnReceiver(E->getMethodDecl(), Receiver, D, Origin);
}
}
@@ -226,33 +224,47 @@ class RawPtrRefCallArgsChecker
std::optional<bool> IsUnsafe = Model->isUnsafePtr(ArgType);
if (!IsUnsafe || !(*IsUnsafe))
continue;
- if (isPtrOriginSafe(Arg))
+ const Expr *Origin = nullptr;
+ if (isPtrOriginSafe(Arg, &Origin))
continue;
- reportBug(MethodDecl, Arg, Param, D);
+ reportBug(MethodDecl, Arg, Param, D, Origin);
}
}
+ static bool isRefCountingOperation(const CXXMethodDecl *MD) {
+ if (!MD)
+ return false;
+ auto name = safeGetName(MD);
+ return name == "ref" || name == "deref" ||
+ name == "incrementCheckedPtrCount" ||
+ name == "decrementCheckedPtrCount";
+ }
+
void checkThisArg(const NamedDecl *Callee,
const CXXMemberCallExpr *MemberCallExpr,
const Decl *DeclWithIssue) const {
- if (auto *MD = MemberCallExpr->getMethodDecl()) {
- auto name = safeGetName(MD);
- if (name == "ref" || name == "deref")
- return;
- if (name == "incrementCheckedPtrCount" ||
- name == "decrementCheckedPtrCount")
- return;
- }
- auto *ThisExpr = MemberCallExpr->getImplicitObjectArgument();
- QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();
- std::optional<bool> IsUnsafe = Model->isUnsafeType(ArgType);
+ if (isRefCountingOperation(MemberCallExpr->getMethodDecl()))
+ return;
+ checkThisArg(Callee, MemberCallExpr->getImplicitObjectArgument(),
+ MemberCallExpr->getObjectType(), DeclWithIssue);
+ }
+
+ void checkThisArg(const NamedDecl *Callee, const Expr *Receiver,
+ QualType ReceiverType, const Decl *DeclWithIssue) const {
+ // There is no ParmVarDecl for the implicit object parameter, so
+ // synthesize its type ('T&' per [over.match.funcs]) for the model to
+ // classify.
+ QualType ParamType = BR->getContext().getLValueReferenceType(
+ ReceiverType.getCanonicalType());
+ std::optional<bool> IsUnsafe = Model->isUnsafePtr(ParamType);
if (!IsUnsafe || !*IsUnsafe)
return;
- if (isPtrOriginSafe(ThisExpr))
+ const Expr *Origin = nullptr;
+ if (isPtrOriginSafe(Receiver, &Origin))
return;
- reportBugOnThis(Callee, ThisExpr, DeclWithIssue);
+ reportBugOnThis(Callee, Receiver, DeclWithIssue, Origin);
}
void checkArg(const NamedDecl *Callee, const Expr *Arg, QualType ParamType,
@@ -264,13 +276,14 @@ class RawPtrRefCallArgsChecker
if (auto *DefaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))
Arg = DefaultArg->getExpr();
- if (isPtrOriginSafe(Arg))
+ const Expr *Origin = nullptr;
+ if (isPtrOriginSafe(Arg, &Origin))
return;
- reportBug(Callee, Arg, Param, DeclWithIssue);
+ reportBug(Callee, Arg, Param, DeclWithIssue, Origin);
}
- bool isPtrOriginSafe(const Expr *Arg) const {
+ bool isPtrOriginSafe(const Expr *Arg, const Expr **Origin = nullptr) const {
return tryToFindPtrOrigin(
Arg, /*StopAtFirstRefCountedObj=*/true,
Model->checksForInteriorDestruction(),
@@ -299,7 +312,8 @@ class RawPtrRefCallArgsChecker
return true;
if (isa<ObjCStringLiteral>(ArgOrigin))
return true;
- if (isASafeCallArg(ArgOrigin))
+ if (!Model->checksForInteriorDestruction() &&
+ originOutlivesCall(ArgOrigin))
return true;
if (EFA.isACallToEnsureFn(ArgOrigin)) {
auto *MCE = dyn_cast<CXXMemberCallExpr>(ArgOrigin);
@@ -309,6 +323,8 @@ class RawPtrRefCallArgsChecker
}
if (Model->isSafeExpr(ArgOrigin, PtrIsLifetimeBoundToOrigin))
return true;
+ if (Origin && !*Origin)
+ *Origin = ArgOrigin;
return false;
});
}
@@ -410,7 +426,8 @@ class RawPtrRefCallArgsChecker
}
void reportBug(const NamedDecl *Callee, const Expr *CallArg,
- const ParmVarDecl *Param, const Decl *DeclWithIssue) const {
+ const ParmVarDecl *Param, const Decl *DeclWithIssue,
+ const Expr *Origin) const {
assert(CallArg);
SmallString<100> Buf;
@@ -434,22 +451,7 @@ class RawPtrRefCallArgsChecker
if (!paramName.empty() || Callee)
Os << ")";
Os << " is a ";
- auto *ArgType = CallArg->getType().getTypePtr();
-
- if (printPointer(Os, ArgType) == PrintDeclKind::Pointer) {
- auto *RTC = Model->retainTypeChecker();
- assert(RTC);
- if (auto *Decl = RTC->getCanonicalDecl(CallArg->getType())) {
- printQuotedQualifiedName(Os, Decl);
- } else {
- auto Typedef = ArgType->getAs<TypedefType>();
- assert(Typedef);
- printQuotedQualifiedName(Os, Typedef->getDecl());
- }
- } else {
- Os << " ";
- printTypeName(Os, CallArg->getType());
- }
+ Model->describeHazard(Os, Origin, CallArg->getType());
bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param;
const SourceLocation SrcLocToReport =
@@ -464,7 +466,7 @@ class RawPtrRefCallArgsChecker
}
void reportBugOnThis(const NamedDecl *Callee, const Expr *CallArg,
- const Decl *DeclWithIssue) const {
+ const Decl *DeclWithIssue, const Expr *Origin) const {
assert(CallArg);
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
@@ -478,8 +480,8 @@ class RawPtrRefCallArgsChecker
Os << " to ";
printQuotedQualifiedName(Os, Callee);
}
- Os << ") is a raw pointer to " << Model->typeName() << " ";
- printTypeName(Os, CallArg->getType());
+ Os << ") is a ";
+ printHazardOrPointerTo(Os, CallArg, Origin);
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -489,7 +491,8 @@ class RawPtrRefCallArgsChecker
}
void reportBugOnReceiver(const NamedDecl *Callee, const Expr *CallArg,
- const Decl *DeclWithIssue) const {
+ const Decl *DeclWithIssue,
+ const Expr *Origin) const {
assert(CallArg);
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
@@ -503,8 +506,8 @@ class RawPtrRefCallArgsChecker
printQuotedQualifiedName(Os, Callee);
Os << ")";
}
- Os << " is a raw pointer to " << Model->typeName() << " ";
- printTypeName(Os, CallArg->getType());
+ Os << " is a ";
+ printHazardOrPointerTo(Os, CallArg, Origin);
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -513,6 +516,18 @@ class RawPtrRefCallArgsChecker
BR->emitReport(std::move(Report));
}
+ void printHazardOrPointerTo(llvm::raw_svector_ostream &Os,
+ const Expr *CallArg, const Expr *Origin) const {
+ if (Model->checksForInteriorDestruction()) {
+ Model->describeHazard(Os, Origin, CallArg->getType());
+ return;
+ }
+ // 'this' is a pointer even when the call is spelled with '.', so don't
+ // infer pointer vs reference from the argument type.
+ Os << "raw pointer to " << Model->typeName() << " ";
+ printTypeName(Os, CallArg->getType());
+ }
+
void printArgument(llvm::raw_svector_ostream &Os, const Expr *Arg) const {
SmallString<100> Buf;
llvm::raw_svector_ostream ArgOs(Buf);
@@ -527,22 +542,6 @@ class RawPtrRefCallArgsChecker
else
Os << " '" << ArgCode << "'";
}
-
- enum class PrintDeclKind { Pointee, Pointer };
- PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,
- const Type *T) const {
- // Retain/OS types are frequently spelled through a typedef (e.g. CFXXXRef);
- // print the typedef name rather than desugaring to the pointee.
- if (Model->retainTypeChecker() && isa<TypedefType>(T)) {
- Os << Model->typeName() << " ";
- return PrintDeclKind::Pointer;
- }
- T = T->getUnqualifiedDesugaredType();
- bool IsPtr = isa<PointerType, ObjCObjectPointerType>(T);
- Os << "raw " << (IsPtr ? "pointer" : "reference") << " to "
- << Model->typeName();
- return PrintDeclKind::Pointee;
- }
};
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
@@ -569,6 +568,14 @@ class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker {
makeRetainPtrSafetyModel()) {}
};
+class UnborrowedCallArgsChecker final : public RawPtrRefCallArgsChecker {
+public:
+ UnborrowedCallArgsChecker()
+ : RawPtrRefCallArgsChecker("Loan on a CanBorrow object not guarded by "
+ "a Borrow",
+ makeBorrowSafetyModel()) {}
+};
+
} // namespace
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -594,3 +601,11 @@ void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {
return true;
}
+
+void ento::registerUnborrowedCallArgsChecker(CheckerManager &Mgr) {
+ Mgr.registerChecker<UnborrowedCallArgsChecker>();
+}
+
+bool ento::shouldRegisterUnborrowedCallArgsChecker(const CheckerManager &) {
+ return true;
+}
diff --git a/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
index 84737b30788a8..0bce868fc11ff 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
@@ -217,4 +217,10 @@ template <typename T> class Registry : public CanBorrow {
unsigned m_size { 0 };
};
+class Element {
+public:
+ void mutate();
+ void inspect() const;
+};
+
#endif
diff --git a/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp b/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
new file mode 100644
index 0000000000000..72cd785725b41
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
@@ -0,0 +1,204 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnborrowedCallArgsChecker -verify %s
+
+#include "mock-canborrow.h"
+
+void process(char &);
+void processConst(const char &);
+void takePtr(char *);
+void takeView(StringView);
+void takeSpan(CharSpan);
+
+void takeContainer(Vector<char> &);
+void takeContainerPtr(Vector<char> *);
+
+Vector<char> makeVector();
+Vector<char> &getVectorRef();
+
+Vector<char> globalVector;
+
+namespace arg_origins {
+
+void from_local() {
+ Vector<char> vec;
+ process(vec[0]);
+ // expected-warning at -1{{Function argument 'vec[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void from_parameter(Vector<char> &vec) {
+ process(vec[0]);
+ // expected-warning at -1{{Function argument 'vec[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void from_global() {
+ process(globalVector[0]);
+ // expected-warning at -1{{Function argument 'globalVector[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void from_static_local() {
+ static Vector<char> cache;
+ process(cache[0]);
+ // expected-warning at -1{{Function argument 'cache[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+class Holder {
+public:
+ void from_member() {
+ process(m_vector[0]);
+ // expected-warning at -1{{Function argument 'this->m_vector[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+ }
+
+private:
+ Vector<char> m_vector;
+};
+
+void from_call_result() {
+ process(makeVector()[0]);
+ // expected-warning at -1{{Function argument 'makeVector()[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void from_returned_reference() {
+ process(getVectorRef()[0]);
+ // expected-warning at -1{{Function argument 'getVectorRef()[0]' (to 'process') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void buffer_pointer() {
+ Vector<char> vec;
+ takePtr(vec.data());
+ // expected-warning at -1{{Function argument 'vec.data()' (to 'takePtr') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void const_parameter() {
+ Vector<char> vec;
+ processConst(vec[0]);
+ // expected-warning at -1{{Function argument 'vec[0]' (to 'processConst') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+} // namespace arg_origins
+
+namespace implicit_object_arg {
+
+void mutating_method() {
+ Vector<Element> vec;
+ vec[0].mutate();
+ // expected-warning at -1{{Function argument 'vec[0]' (parameter 'this' to 'Element::mutate') is a loan on CanBorrow type 'Vector<Element>' that is not guarded by a Borrow}}
+}
+
+void const_method() {
+ Vector<Element> vec;
+ vec[0].inspect();
+ // expected-warning at -1{{Function argument 'vec[0]' (parameter 'this' to 'Element::inspect') is a loan on CanBorrow type 'Vector<Element>' that is not guarded by a Borrow}}
+}
+
+class Holder {
+public:
+ void from_member() {
+ m_elements[0].mutate();
+ // expected-warning at -1{{Function argument 'this->m_elements[0]' (parameter 'this' to 'Element::mutate') is a loan on CanBorrow type 'Vector<Element>' that is not guarded by a Borrow}}
+ }
+
+private:
+ Vector<Element> m_elements;
+};
+
+void nested_container() {
+ Vector<Vector<char>> outer;
+ outer[0].append('x');
+ // expected-warning at -1{{Function argument 'outer[0]' (parameter 'this' to 'Vector<char>::append') is a loan on CanBorrow type 'Vector<Vector<char>>' that is not guarded by a Borrow}}
+}
+
+} // namespace implicit_object_arg
+
+namespace not_a_loan {
+
+void passing_the_container() {
+ Vector<char> vec;
+ takeContainer(vec);
+}
+
+void passing_the_containers_address() {
+ Vector<char> vec;
+ takeContainerPtr(&vec);
+}
+
+void passing_an_alias() {
+ Vector<char> vec;
+ Vector<char> &alias = vec;
+ takeContainer(alias);
+}
+
+void method_on_the_container() {
+ Vector<char> vec;
+ vec.append('x');
+}
+
+void not_borrowable_container() {
+ NotBorrowable nb;
+ process(nb.at(0));
+}
+
+void smart_pointer_accessor(Owner<NotBorrowable> &owner) {
+ process(owner->at(0));
+}
+
+} // namespace not_a_loan
+
+namespace guarded {
+
+void borrowed_element() {
+ Vector<char> vec;
+ Borrow<Vector<char>> b(vec);
+ process(b.get()[0]);
+}
+
+void borrowed_buffer() {
+ Vector<char> vec;
+ Borrow<Vector<char>> b(vec);
+ takePtr(b.get().data());
+}
+
+void borrowed_implicit_object_arg() {
+ Vector<Element> vec;
+ Borrow<Vector<Element>> b(vec);
+ b.get()[0].mutate();
+}
+
+void constructing_the_borrow_is_not_a_loan() {
+ Vector<char> vec;
+ Borrow<Vector<char>> b(vec);
+}
+
+} // namespace guarded
+
+namespace nested_calls {
+
+void annotated_intermediate() {
+ Vector<char> vec;
+ takeView(makeView(vec.data()));
+ // expected-warning at -1{{Function argument 'makeView(vec.data())' (to 'takeView') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+ // expected-warning at -2{{Function argument 'vec.data()' (parameter 'data' to 'makeView') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+void two_loans_one_call() {
+ Vector<char> vec;
+ pick(vec.data(), vec.data());
+ // expected-warning at -1{{Function argument 'vec.data()' (parameter 'a' to 'pick') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+ // expected-warning at -2{{Function argument 'vec.data()' (parameter 'b' to 'pick') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow}}
+}
+
+} // namespace nested_calls
+
+namespace known_gaps {
+
+void unannotated_intermediate() {
+ Vector<char> vec;
+ takeSpan(makeSpanUnannotated(vec));
+}
+
+inline void trivialSink(char &c) {}
+
+void trivial_callee() {
+ Vector<char> vec;
+ trivialSink(vec[0]);
+}
+
+} // namespace known_gaps
More information about the cfe-commits
mailing list