mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-30 20:26:05 +00:00

Part 2 (and final part) following https://github.com/llvm/llvm-project/pull/120102 Allows users to do things like: ``` if (o->x.has_value()) { ((*o).x).value(); } ``` where the `->` and `*` are operator overload calls. A user could instead extract the nested optional into a local variable once instead of doing two accessor calls back to back, but currently they are unsure why the code is flagged.
169 lines
5.6 KiB
C++
169 lines
5.6 KiB
C++
#include "clang/Analysis/FlowSensitive/SmartPointerAccessorCaching.h"
|
|
|
|
#include "clang/AST/CanonicalType.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/ASTMatchers/ASTMatchersMacros.h"
|
|
#include "clang/Basic/OperatorKinds.h"
|
|
|
|
namespace clang::dataflow {
|
|
|
|
namespace {
|
|
|
|
using ast_matchers::callee;
|
|
using ast_matchers::cxxMemberCallExpr;
|
|
using ast_matchers::cxxMethodDecl;
|
|
using ast_matchers::cxxOperatorCallExpr;
|
|
using ast_matchers::hasName;
|
|
using ast_matchers::hasOverloadedOperatorName;
|
|
using ast_matchers::ofClass;
|
|
using ast_matchers::parameterCountIs;
|
|
using ast_matchers::pointerType;
|
|
using ast_matchers::referenceType;
|
|
using ast_matchers::returns;
|
|
|
|
bool hasSmartPointerClassShape(const CXXRecordDecl &RD, bool &HasGet,
|
|
bool &HasValue) {
|
|
// We may want to cache this search, but in current profiles it hasn't shown
|
|
// up as a hot spot (possibly because there aren't many hits, relatively).
|
|
bool HasArrow = false;
|
|
bool HasStar = false;
|
|
CanQualType StarReturnType, ArrowReturnType, GetReturnType, ValueReturnType;
|
|
for (const auto *MD : RD.methods()) {
|
|
// We only consider methods that are const and have zero parameters.
|
|
// It may be that there is a non-const overload for the method, but
|
|
// there should at least be a const overload as well.
|
|
if (!MD->isConst() || MD->getNumParams() != 0)
|
|
continue;
|
|
switch (MD->getOverloadedOperator()) {
|
|
case OO_Star:
|
|
if (MD->getReturnType()->isReferenceType()) {
|
|
HasStar = true;
|
|
StarReturnType = MD->getReturnType()
|
|
.getNonReferenceType()
|
|
->getCanonicalTypeUnqualified();
|
|
}
|
|
break;
|
|
case OO_Arrow:
|
|
if (MD->getReturnType()->isPointerType()) {
|
|
HasArrow = true;
|
|
ArrowReturnType = MD->getReturnType()
|
|
->getPointeeType()
|
|
->getCanonicalTypeUnqualified();
|
|
}
|
|
break;
|
|
case OO_None: {
|
|
IdentifierInfo *II = MD->getIdentifier();
|
|
if (II == nullptr)
|
|
continue;
|
|
if (II->isStr("get")) {
|
|
if (MD->getReturnType()->isPointerType()) {
|
|
HasGet = true;
|
|
GetReturnType = MD->getReturnType()
|
|
->getPointeeType()
|
|
->getCanonicalTypeUnqualified();
|
|
}
|
|
} else if (II->isStr("value")) {
|
|
if (MD->getReturnType()->isReferenceType()) {
|
|
HasValue = true;
|
|
ValueReturnType = MD->getReturnType()
|
|
.getNonReferenceType()
|
|
->getCanonicalTypeUnqualified();
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!HasStar || !HasArrow || StarReturnType != ArrowReturnType)
|
|
return false;
|
|
HasGet = HasGet && (GetReturnType == StarReturnType);
|
|
HasValue = HasValue && (ValueReturnType == StarReturnType);
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clang::dataflow
|
|
|
|
// AST_MATCHER macros create an "internal" namespace, so we put it in
|
|
// its own anonymous namespace instead of in clang::dataflow.
|
|
namespace {
|
|
|
|
AST_MATCHER(clang::CXXRecordDecl, smartPointerClassWithGet) {
|
|
bool HasGet = false;
|
|
bool HasValue = false;
|
|
bool HasStarAndArrow =
|
|
clang::dataflow::hasSmartPointerClassShape(Node, HasGet, HasValue);
|
|
return HasStarAndArrow && HasGet;
|
|
}
|
|
|
|
AST_MATCHER(clang::CXXRecordDecl, smartPointerClassWithValue) {
|
|
bool HasGet = false;
|
|
bool HasValue = false;
|
|
bool HasStarAndArrow =
|
|
clang::dataflow::hasSmartPointerClassShape(Node, HasGet, HasValue);
|
|
return HasStarAndArrow && HasValue;
|
|
}
|
|
|
|
AST_MATCHER(clang::CXXRecordDecl, smartPointerClassWithGetOrValue) {
|
|
bool HasGet = false;
|
|
bool HasValue = false;
|
|
bool HasStarAndArrow =
|
|
clang::dataflow::hasSmartPointerClassShape(Node, HasGet, HasValue);
|
|
return HasStarAndArrow && (HasGet || HasValue);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace clang::dataflow {
|
|
|
|
ast_matchers::StatementMatcher isSmartPointerLikeOperatorStar() {
|
|
return cxxOperatorCallExpr(
|
|
hasOverloadedOperatorName("*"),
|
|
callee(cxxMethodDecl(parameterCountIs(0), returns(referenceType()),
|
|
ofClass(smartPointerClassWithGetOrValue()))));
|
|
}
|
|
|
|
ast_matchers::StatementMatcher isSmartPointerLikeOperatorArrow() {
|
|
return cxxOperatorCallExpr(
|
|
hasOverloadedOperatorName("->"),
|
|
callee(cxxMethodDecl(parameterCountIs(0), returns(pointerType()),
|
|
ofClass(smartPointerClassWithGetOrValue()))));
|
|
}
|
|
|
|
ast_matchers::StatementMatcher isSmartPointerLikeValueMethodCall() {
|
|
return cxxMemberCallExpr(callee(
|
|
cxxMethodDecl(parameterCountIs(0), returns(referenceType()),
|
|
hasName("value"), ofClass(smartPointerClassWithValue()))));
|
|
}
|
|
|
|
ast_matchers::StatementMatcher isSmartPointerLikeGetMethodCall() {
|
|
return cxxMemberCallExpr(callee(
|
|
cxxMethodDecl(parameterCountIs(0), returns(pointerType()), hasName("get"),
|
|
ofClass(smartPointerClassWithGet()))));
|
|
}
|
|
|
|
const FunctionDecl *
|
|
getCanonicalSmartPointerLikeOperatorCallee(const CallExpr *CE) {
|
|
const FunctionDecl *CanonicalCallee = nullptr;
|
|
const CXXMethodDecl *Callee =
|
|
cast_or_null<CXXMethodDecl>(CE->getDirectCallee());
|
|
if (Callee == nullptr)
|
|
return nullptr;
|
|
const CXXRecordDecl *RD = Callee->getParent();
|
|
if (RD == nullptr)
|
|
return nullptr;
|
|
for (const auto *MD : RD->methods()) {
|
|
if (MD->getOverloadedOperator() == OO_Star && MD->isConst() &&
|
|
MD->getNumParams() == 0 && MD->getReturnType()->isReferenceType()) {
|
|
CanonicalCallee = MD;
|
|
break;
|
|
}
|
|
}
|
|
return CanonicalCallee;
|
|
}
|
|
|
|
} // namespace clang::dataflow
|