Skip to content

[clang][dataflow] Add matchers for smart pointer accessors to be cached #120102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===-- SmartPointerAccessorCaching.h ---------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines utilities to help cache accessors for smart pointer
// like objects.
//
// These should be combined with CachedConstAccessorsLattice.
// Beyond basic const accessors, smart pointers may have the following two
// additional issues:
//
// 1) There may be multiple accessors for the same underlying object, e.g.
// `operator->`, `operator*`, and `get`. Users may use a mixture of these
// accessors, so the cache should unify them.
//
// 2) There may be non-const overloads of accessors. They are still safe to
// cache, as they don't modify the container object.
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H

#include <cassert>

#include "clang/AST/Decl.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchers.h"

namespace clang::dataflow {

/// Matchers:
/// For now, these match on any class with an `operator*` or `operator->`
/// where the return types have a similar shape as std::unique_ptr
/// and std::optional.
///
/// - `*` returns a reference to a type `T`
/// - `->` returns a pointer to `T`
/// - `get` returns a pointer to `T`
/// - `value` returns a reference `T`
///
/// (1) The `T` should all match across the accessors (ignoring qualifiers).
///
/// (2) The specific accessor used in a call isn't required to be const,
/// but the class must have a const overload of each accessor.
///
/// For now, we don't have customization to ignore certain classes.
/// For example, if writing a ClangTidy check for `std::optional`, these
/// would also match `std::optional`. In order to have special handling
/// for `std::optional`, we assume the (Matcher, TransferFunction) case
/// with custom handling is ordered early so that these generic cases
/// do not trigger.
ast_matchers::StatementMatcher isSmartPointerLikeOperatorStar();
ast_matchers::StatementMatcher isSmartPointerLikeOperatorArrow();
ast_matchers::StatementMatcher isSmartPointerLikeValueMethodCall();
ast_matchers::StatementMatcher isSmartPointerLikeGetMethodCall();

} // namespace clang::dataflow

#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_SMARTPOINTERACCESSORCACHING_H
1 change: 1 addition & 0 deletions clang/lib/Analysis/FlowSensitive/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_clang_library(clangAnalysisFlowSensitive
Logger.cpp
RecordOps.cpp
SimplifyConstraints.cpp
SmartPointerAccessorCaching.cpp
Transfer.cpp
TypeErasedDataflowAnalysis.cpp
Value.cpp
Expand Down
147 changes: 147 additions & 0 deletions clang/lib/Analysis/FlowSensitive/SmartPointerAccessorCaching.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#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();
}
}
}
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()))));
}

} // namespace clang::dataflow
1 change: 1 addition & 0 deletions clang/unittests/Analysis/FlowSensitive/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_clang_unittest(ClangAnalysisFlowSensitiveTests
SignAnalysisTest.cpp
SimplifyConstraintsTest.cpp
SingleVarConstantPropagationTest.cpp
SmartPointerAccessorCachingTest.cpp
TestingSupport.cpp
TestingSupportTest.cpp
TransferBranchTest.cpp
Expand Down
Loading
Loading