blob: 71813e820709877cfa2511e7ad2e3466dd87d949 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/pointer_nullability_lattice.h"
#include <cassert>
#include <functional>
#include <optional>
#include "absl/base/nullability.h"
#include "absl/log/check.h"
#include "nullability/pointer_nullability.h"
#include "nullability/type_nullability.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.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 "clang/Basic/LLVM.h"
#include "llvm/ADT/DenseMap.h"
namespace clang::tidy::nullability {
namespace {
using dataflow::LatticeJoinEffect;
using dataflow::PointerValue;
using dataflow::RecordStorageLocation;
using dataflow::StorageLocation;
using dataflow::Value;
// Returns overridden nullability information associated with a declaration.
// For now we only track top-level decl nullability symbolically and check for
// concrete nullability override results.
absl::Nullable<const PointerTypeNullability *> getDeclNullability(
absl::Nullable<const Decl *> D,
const PointerNullabilityLattice::NonFlowSensitiveState &NFS) {
if (!D) return nullptr;
if (const auto *VD = dyn_cast_or_null<ValueDecl>(D->getCanonicalDecl())) {
auto It = NFS.DeclTopLevelNullability.find(VD);
if (It != NFS.DeclTopLevelNullability.end()) return &It->second;
}
if (const std::optional<const PointerTypeNullability *> N =
NFS.ConcreteNullabilityOverride(*D->getCanonicalDecl()))
return *N;
return nullptr;
}
template <typename T>
llvm::SmallDenseMap<const dataflow::RecordStorageLocation *,
llvm::SmallDenseMap<const FunctionDecl *, T *>>
joinConstMethodMap(
const llvm::SmallDenseMap<const dataflow::RecordStorageLocation *,
llvm::SmallDenseMap<const FunctionDecl *, T *>>
&Map1,
const llvm::SmallDenseMap<const dataflow::RecordStorageLocation *,
llvm::SmallDenseMap<const FunctionDecl *, T *>>
&Map2,
LatticeJoinEffect &Effect) {
llvm::SmallDenseMap<const dataflow::RecordStorageLocation *,
llvm::SmallDenseMap<const FunctionDecl *, T *>>
Result;
for (auto &[Loc, DeclToT] : Map1) {
auto It = Map2.find(Loc);
if (It == Map2.end()) {
Effect = LatticeJoinEffect::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 = LatticeJoinEffect::Changed;
continue;
}
JoinedDeclToT.insert({Func, Var});
}
}
return Result;
}
} // namespace
const TypeNullability &PointerNullabilityLattice::insertExprNullabilityIfAbsent(
absl::Nonnull<const Expr *> E,
const std::function<TypeNullability()> &GetNullability) {
E = &dataflow::ignoreCFGOmittedNodes(*E);
if (auto It = NFS.ExprToNullability.find(E);
It != NFS.ExprToNullability.end())
return It->second;
// Deliberately perform a separate lookup after calling GetNullability.
// It may invalidate iterators, e.g. inserting missing vectors for children.
auto [Iterator, Inserted] =
NFS.ExprToNullability.insert({E, GetNullability()});
CHECK(Inserted) << "GetNullability inserted same " << E->getStmtClassName();
return Iterator->second;
}
absl::Nullable<dataflow::Value *>
PointerNullabilityLattice::getConstMethodReturnValue(
const dataflow::RecordStorageLocation &RecordLoc,
absl::Nonnull<const CallExpr *> CE, dataflow::Environment &Env) {
assert(CE->getType()->isPointerType() || CE->getType()->isBooleanType());
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;
dataflow::Value *Val = Env.createValue(CE->getType());
if (Val != nullptr) ObjMap.insert({DirectCallee, Val});
return Val;
}
absl::Nullable<dataflow::StorageLocation *>
PointerNullabilityLattice::getConstMethodReturnStorageLocation(
const dataflow::RecordStorageLocation &RecordLoc,
absl::Nonnull<const CallExpr *> CE, dataflow::Environment &Env) {
assert(isSupportedSmartPointerType(CE->getType()));
auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc];
const FunctionDecl *DirectCallee = CE->getDirectCallee();
if (DirectCallee == nullptr) return nullptr;
auto it = ObjMap.find(DirectCallee);
if (it != ObjMap.end()) return it->second;
StorageLocation &Loc = Env.createStorageLocation(CE->getType());
setSmartPointerValue(cast<RecordStorageLocation>(Loc),
cast<PointerValue>(Env.createValue(
underlyingRawPointerType(CE->getType()))),
Env);
ObjMap.insert({DirectCallee, &Loc});
return &Loc;
}
void PointerNullabilityLattice::overrideNullabilityFromDecl(
absl::Nullable<const Decl *> D, TypeNullability &N) const {
// For now, overrides are always for pointer values only, and override only
// the top-level nullability.
if (N.empty()) return;
if (auto *PN = getDeclNullability(D, NFS)) {
N.front() = *PN;
}
}
LatticeJoinEffect PointerNullabilityLattice::join(
const PointerNullabilityLattice &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.
LatticeJoinEffect Effect = LatticeJoinEffect::Unchanged;
ConstMethodReturnValues = joinConstMethodMap<Value>(
ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect);
;
ConstMethodReturnStorageLocations = joinConstMethodMap<StorageLocation>(
ConstMethodReturnStorageLocations,
Other.ConstMethodReturnStorageLocations, Effect);
return Effect;
}
} // namespace clang::tidy::nullability