220 lines
7.8 KiB
C++
220 lines
7.8 KiB
C++
//===-- CachedConstAccessorsLattice.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 the lattice mixin that additionally maintains a cache of
|
|
// stable method call return values to model const accessor member functions.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
|
|
#define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
|
|
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
|
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
|
|
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
|
|
#include "clang/Analysis/FlowSensitive/Value.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/STLFunctionalExtras.h"
|
|
|
|
namespace clang {
|
|
namespace dataflow {
|
|
|
|
/// A mixin for a lattice that additionally maintains a cache of stable method
|
|
/// call return values to model const accessors methods. When a non-const method
|
|
/// is called, the cache should be cleared causing the next call to a const
|
|
/// method to be considered a different value. NOTE: The user is responsible for
|
|
/// clearing the cache.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// class Bar {
|
|
/// public:
|
|
/// const std::optional<Foo>& getFoo() const;
|
|
/// void clear();
|
|
/// };
|
|
//
|
|
/// void func(Bar& s) {
|
|
/// if (s.getFoo().has_value()) {
|
|
/// use(s.getFoo().value()); // safe (checked earlier getFoo())
|
|
/// s.clear();
|
|
/// use(s.getFoo().value()); // unsafe (invalidate cache for s)
|
|
/// }
|
|
/// }
|
|
template <typename Base> class CachedConstAccessorsLattice : public Base {
|
|
public:
|
|
using Base::Base; // inherit all constructors
|
|
|
|
/// Creates or returns a previously created `Value` associated with a const
|
|
/// method call `obj.getFoo()` where `RecordLoc` is the
|
|
/// `RecordStorageLocation` of `obj`.
|
|
/// Returns nullptr if unable to find or create a value.
|
|
///
|
|
/// Requirements:
|
|
///
|
|
/// - `CE` should return a value (not a reference or record type)
|
|
Value *
|
|
getOrCreateConstMethodReturnValue(const RecordStorageLocation &RecordLoc,
|
|
const CallExpr *CE, Environment &Env);
|
|
|
|
/// Creates or returns a previously created `StorageLocation` associated with
|
|
/// a const method call `obj.getFoo()` where `RecordLoc` is the
|
|
/// `RecordStorageLocation` of `obj`, `Callee` is the decl for `getFoo`.
|
|
///
|
|
/// The callback `Initialize` runs on the storage location if newly created.
|
|
///
|
|
/// Requirements:
|
|
///
|
|
/// - `Callee` should return a location (return type is a reference type or a
|
|
/// record type).
|
|
StorageLocation &getOrCreateConstMethodReturnStorageLocation(
|
|
const RecordStorageLocation &RecordLoc, const FunctionDecl *Callee,
|
|
Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize);
|
|
|
|
void clearConstMethodReturnValues(const RecordStorageLocation &RecordLoc) {
|
|
ConstMethodReturnValues.erase(&RecordLoc);
|
|
}
|
|
|
|
void clearConstMethodReturnStorageLocations(
|
|
const RecordStorageLocation &RecordLoc) {
|
|
ConstMethodReturnStorageLocations.erase(&RecordLoc);
|
|
}
|
|
|
|
bool operator==(const CachedConstAccessorsLattice &Other) const {
|
|
return Base::operator==(Other);
|
|
}
|
|
|
|
LatticeEffect join(const CachedConstAccessorsLattice &Other);
|
|
|
|
private:
|
|
// Maps a record storage location and const method to the value to return
|
|
// from that const method.
|
|
using ConstMethodReturnValuesType =
|
|
llvm::SmallDenseMap<const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, Value *>>;
|
|
ConstMethodReturnValuesType ConstMethodReturnValues;
|
|
|
|
// Maps a record storage location and const method to the record storage
|
|
// location to return from that const method.
|
|
using ConstMethodReturnStorageLocationsType = llvm::SmallDenseMap<
|
|
const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, StorageLocation *>>;
|
|
ConstMethodReturnStorageLocationsType ConstMethodReturnStorageLocations;
|
|
};
|
|
|
|
namespace internal {
|
|
|
|
template <typename T>
|
|
llvm::SmallDenseMap<const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, T *>>
|
|
joinConstMethodMap(
|
|
const llvm::SmallDenseMap<const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, T *>>
|
|
&Map1,
|
|
const llvm::SmallDenseMap<const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, T *>>
|
|
&Map2,
|
|
LatticeEffect &Effect) {
|
|
// Intersect the two maps, and note if change was made.
|
|
llvm::SmallDenseMap<const RecordStorageLocation *,
|
|
llvm::SmallDenseMap<const FunctionDecl *, T *>>
|
|
Result;
|
|
for (auto &[Loc, DeclToT] : Map1) {
|
|
auto It = Map2.find(Loc);
|
|
if (It == Map2.end()) {
|
|
Effect = LatticeEffect::Changed;
|
|
continue;
|
|
}
|
|
const auto &OtherDeclToT = It->second;
|
|
auto &JoinedDeclToT = Result[Loc];
|
|
for (auto [Func, Var] : DeclToT) {
|
|
T *OtherVar = OtherDeclToT.lookup(Func);
|
|
if (OtherVar == nullptr || OtherVar != Var) {
|
|
Effect = LatticeEffect::Changed;
|
|
continue;
|
|
}
|
|
JoinedDeclToT.insert({Func, Var});
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
template <typename Base>
|
|
LatticeEffect CachedConstAccessorsLattice<Base>::join(
|
|
const CachedConstAccessorsLattice<Base> &Other) {
|
|
|
|
LatticeEffect Effect = Base::join(Other);
|
|
|
|
// For simplicity, we only retain values that are identical, but not ones that
|
|
// are non-identical but equivalent. This is likely to be sufficient in
|
|
// practice, and it reduces implementation complexity considerably.
|
|
|
|
ConstMethodReturnValues =
|
|
clang::dataflow::internal::joinConstMethodMap<dataflow::Value>(
|
|
ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect);
|
|
|
|
ConstMethodReturnStorageLocations =
|
|
clang::dataflow::internal::joinConstMethodMap<dataflow::StorageLocation>(
|
|
ConstMethodReturnStorageLocations,
|
|
Other.ConstMethodReturnStorageLocations, Effect);
|
|
|
|
return Effect;
|
|
}
|
|
|
|
template <typename Base>
|
|
Value *CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnValue(
|
|
const RecordStorageLocation &RecordLoc, const CallExpr *CE,
|
|
Environment &Env) {
|
|
QualType Type = CE->getType();
|
|
assert(!Type.isNull());
|
|
assert(!Type->isReferenceType());
|
|
assert(!Type->isRecordType());
|
|
|
|
auto &ObjMap = ConstMethodReturnValues[&RecordLoc];
|
|
const FunctionDecl *DirectCallee = CE->getDirectCallee();
|
|
if (DirectCallee == nullptr)
|
|
return nullptr;
|
|
auto it = ObjMap.find(DirectCallee);
|
|
if (it != ObjMap.end())
|
|
return it->second;
|
|
|
|
Value *Val = Env.createValue(Type);
|
|
if (Val != nullptr)
|
|
ObjMap.insert({DirectCallee, Val});
|
|
return Val;
|
|
}
|
|
|
|
template <typename Base>
|
|
StorageLocation &
|
|
CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnStorageLocation(
|
|
const RecordStorageLocation &RecordLoc, const FunctionDecl *Callee,
|
|
Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize) {
|
|
assert(Callee != nullptr);
|
|
QualType Type = Callee->getReturnType();
|
|
assert(!Type.isNull());
|
|
assert(Type->isReferenceType() || Type->isRecordType());
|
|
auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc];
|
|
auto it = ObjMap.find(Callee);
|
|
if (it != ObjMap.end())
|
|
return *it->second;
|
|
|
|
StorageLocation &Loc = Env.createStorageLocation(Type.getNonReferenceType());
|
|
Initialize(Loc);
|
|
|
|
ObjMap.insert({Callee, &Loc});
|
|
return Loc;
|
|
}
|
|
|
|
} // namespace dataflow
|
|
} // namespace clang
|
|
|
|
#endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H
|