blob: 5946368ebf7c2f460d35f6bc7a8e284eb2a81339 [file] [log] [blame] [edit]
// 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/value_transferer.h"
#include <cassert>
#include <functional>
#include "absl/base/nullability.h"
#include "absl/log/check.h"
#include "nullability/ast_helpers.h"
#include "nullability/macro_arg_capture.h"
#include "nullability/pointer_nullability.h"
#include "nullability/pointer_nullability_lattice.h"
#include "nullability/pointer_nullability_matchers.h"
#include "nullability/type_nullability.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/TypeBase.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/SmartPointerAccessorCaching.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/Specifiers.h"
namespace clang::tidy::nullability {
using ast_matchers::MatchFinder;
using dataflow::Arena;
using dataflow::BoolValue;
using dataflow::DataflowAnalysisContext;
using dataflow::Environment;
using dataflow::Formula;
using dataflow::PointerValue;
using dataflow::RecordStorageLocation;
using dataflow::StorageLocation;
using dataflow::TransferState;
using dataflow::Value;
PointerValue* absl_nullable ensureRawPointerHasValue(const Expr* absl_nonnull E,
Environment& Env) {
if (!isSupportedRawPointerType(E->getType())) return nullptr;
if (E->isPRValue()) {
if (auto* Val = Env.get<PointerValue>(*E)) return Val;
auto* Val = cast<PointerValue>(Env.createValue(E->getType()));
Env.setValue(*E, *Val);
return Val;
}
StorageLocation* Loc = Env.getStorageLocation(*E);
if (Loc == nullptr) {
Loc = &Env.createStorageLocation(*E);
Env.setStorageLocation(*E, *Loc);
}
if (auto* Val = Env.get<PointerValue>(*Loc)) return Val;
auto* Val = cast<PointerValue>(Env.createValue(E->getType()));
Env.setValue(*Loc, *Val);
return Val;
}
static PointerTypeNullability getPointerTypeNullability(
const Expr* absl_nonnull E, PointerNullabilityLattice& L) {
// TODO: handle this in non-flow-sensitive transfer instead
if (auto FromClang = E->getType()->getNullability();
FromClang && *FromClang != NullabilityKind::Unspecified)
return *FromClang;
if (const auto* TyNullability = L.getTypeNullability(E)) {
if (!TyNullability->empty())
// Return the nullability of the topmost pointer in the type.
return TyNullability->front();
}
return NullabilityKind::Unspecified;
}
void initPointerFromTypeNullability(
PointerValue& PointerVal, const Expr* absl_nonnull E,
TransferState<PointerNullabilityLattice>& State) {
initPointerNullState(PointerVal, State.Env.getDataflowAnalysisContext(),
getPointerTypeNullability(E, State.Lattice));
}
/// If the pointer value stored at `PointerLoc` has any "top" nullability
/// properties, creates a new pointer value referencing the same location with
/// the "top" properties unpacked into fresh atoms. Returns:
/// - The unpacked pointer value if unpacking took place.
/// - The original pointer value if no unpacking took place.
/// - Null if `PointerLoc` is not associated with a value.
/// This is analogous to the unpacking done on `TopBoolValue`s in the framework.
static PointerValue* absl_nullable unpackPointerValue(
StorageLocation& PointerLoc, Environment& Env) {
auto* PointerVal = Env.get<PointerValue>(PointerLoc);
if (!PointerVal) return nullptr;
PointerNullState NullState = getPointerNullState(*PointerVal);
if (NullState.FromNullable && NullState.IsNull) return PointerVal;
auto& A = Env.getDataflowAnalysisContext().arena();
if (NullState.FromNullable == nullptr)
NullState.FromNullable = &A.makeAtomRef(A.makeAtom());
if (NullState.IsNull == nullptr)
NullState.IsNull = &A.makeAtomRef(A.makeAtom());
auto& NewPointerVal = Env.create<PointerValue>(PointerVal->getPointeeLoc());
initPointerNullState(NewPointerVal, Env.getDataflowAnalysisContext(),
NullState);
Env.setValue(PointerLoc, NewPointerVal);
return &NewPointerVal;
}
static void setToPointerWithNullability(StorageLocation& PtrLoc,
NullabilityKind NK, Environment& Env) {
auto& Val = *cast<PointerValue>(Env.createValue(PtrLoc.getType()));
initPointerNullState(Val, Env.getDataflowAnalysisContext(), NK);
Env.setValue(PtrLoc, Val);
}
static void initSmartPointerForExpr(
const Expr* E, TransferState<PointerNullabilityLattice>& State) {
RecordStorageLocation* Loc = nullptr;
if (E->isPRValue()) {
Loc = &State.Env.getResultObjectLocation(*E);
} else {
Loc = State.Env.get<RecordStorageLocation>(*E);
if (Loc == nullptr) {
Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));
State.Env.setStorageLocation(*E, *Loc);
}
}
StorageLocation& PtrLoc = Loc->getSyntheticField(PtrField);
auto* Val = State.Env.get<PointerValue>(PtrLoc);
if (Val == nullptr) {
Val = cast<PointerValue>(State.Env.createValue(PtrLoc.getType()));
State.Env.setValue(PtrLoc, *Val);
}
initPointerFromTypeNullability(*Val, E, State);
}
static void transferNullPointer(
const Expr* absl_nonnull NullPointer, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
if (auto* PointerVal = ensureRawPointerHasValue(NullPointer, State.Env)) {
initNullPointer(*PointerVal, State.Env.getDataflowAnalysisContext());
}
}
static void transferPointerIncOrDec(
const UnaryOperator* absl_nonnull UnaryOp, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
// The framework propagates the subexpression's value (in the case of post-
// increment) or storage location (in the case of pre-increment). We just
// need to create a new nonnull value.
if (StorageLocation* Loc =
State.Env.getStorageLocation(*UnaryOp->getSubExpr())) {
auto* Val = cast<PointerValue>(State.Env.createValue(Loc->getType()));
initPointerNullState(*Val, State.Env.getDataflowAnalysisContext(),
NullabilityKind::NonNull);
State.Env.setValue(*Loc, *Val);
}
}
static void transferPointerAddOrSubAssign(
const BinaryOperator* absl_nonnull BinaryOp,
const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
// The framework propagates the storage location of the LHS, so we just need
// to create a new nonnull value.
if (StorageLocation* Loc =
State.Env.getStorageLocation(*BinaryOp->getLHS())) {
auto* Val = cast<PointerValue>(State.Env.createValue(Loc->getType()));
initPointerNullState(*Val, State.Env.getDataflowAnalysisContext(),
NullabilityKind::NonNull);
State.Env.setValue(*Loc, *Val);
}
}
static void transferNotNullPointer(
const Expr* absl_nonnull NotNullPointer, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
if (auto* PointerVal = ensureRawPointerHasValue(NotNullPointer, State.Env)) {
initPointerNullState(*PointerVal, State.Env.getDataflowAnalysisContext(),
NullabilityKind::NonNull);
}
}
static bool isStdWeakPtrType(QualType Ty) {
const CXXRecordDecl* RD = Ty.getCanonicalType()->getAsCXXRecordDecl();
if (RD == nullptr) return false;
if (!RD->getDeclContext()->isStdNamespace()) return false;
const IdentifierInfo* ID = RD->getIdentifier();
if (ID == nullptr) return false;
return ID->getName() == "weak_ptr";
}
static QualType underlyingRawPointerTypeFromSmartPointer(
RecordStorageLocation& Loc) {
return Loc.getSyntheticField(PtrField).getType();
}
static bool isPointerTypeConvertible(QualType From, QualType To) {
assert(isSupportedRawPointerType(From));
assert(isSupportedRawPointerType(To));
if (From->getCanonicalTypeUnqualified() == To->getCanonicalTypeUnqualified())
return true;
auto* FromDecl = From->getPointeeType()->getAsCXXRecordDecl();
auto* ToDecl = To->getPointeeType()->getAsCXXRecordDecl();
// If these aren't pointers to records, don't consider them convertible.
// Otherwise there could be strange type errors.
// We assume array decay to pointers should already be covered.
// - for example, C++ doesn't let you assign a `float*` to a `char*`, or
// assign an `Enum1*` to an `Enum2*`.
// - if we have a scalar type on one side and a record type on the other,
// it could e.g., lead to looking up record member vars on an `int*`.
if (FromDecl == nullptr || ToDecl == nullptr) return false;
if (FromDecl == ToDecl) return true;
// If we don't have the complete definition, we can't check isDerivedFrom.
if (!FromDecl->isCompleteDefinition()) return false;
return FromDecl->isDerivedFrom(ToDecl);
}
static void transferSmartPointerConstructor(
const CXXConstructExpr* Ctor, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
RecordStorageLocation& Loc = State.Env.getResultObjectLocation(*Ctor);
// Default and `nullptr_t` constructor.
if (Ctor->getConstructor()->isDefaultConstructor() ||
(Ctor->getNumArgs() >= 1 &&
Ctor->getArg(0)->getType()->isNullPtrType())) {
setSmartPointerToNull(Loc, State.Env);
return;
}
// Construct from raw pointer, but make sure the pointer types are
// convertible.
if (Ctor->getNumArgs() >= 1 &&
isSupportedRawPointerType(Ctor->getArg(0)->getType()) &&
isPointerTypeConvertible(Ctor->getArg(0)->getType(),
underlyingRawPointerTypeFromSmartPointer(Loc))) {
setSmartPointerValue(Loc, getRawPointerValue(Ctor->getArg(0), State.Env),
State.Env);
return;
}
// Copy or move from an existing smart pointer.
if (Ctor->getNumArgs() >= 1 &&
isSupportedSmartPointerType(Ctor->getArg(0)->getType())) {
auto* SrcLoc = Ctor->getArg(0)->isGLValue()
? State.Env.get<RecordStorageLocation>(*Ctor->getArg(0))
: &State.Env.getResultObjectLocation(*Ctor->getArg(0));
if (Ctor->getNumArgs() == 2 &&
isSupportedRawPointerType(Ctor->getArg(1)->getType())) {
// `shared_ptr` aliasing constructor.
if (isPointerTypeConvertible(
Ctor->getArg(1)->getType(),
underlyingRawPointerTypeFromSmartPointer(Loc))) {
setSmartPointerValue(
Loc, getRawPointerValue(Ctor->getArg(1), State.Env), State.Env);
}
} else {
if (SrcLoc != nullptr &&
isPointerTypeConvertible(
underlyingRawPointerTypeFromSmartPointer(*SrcLoc),
underlyingRawPointerTypeFromSmartPointer(Loc))) {
setSmartPointerValue(
Loc, getPointerValueFromSmartPointer(SrcLoc, State.Env), State.Env);
}
}
// If this is the move constructor, set the source to null.
if (Ctor->getConstructor()
->getParamDecl(0)
->getType()
->isRValueReferenceType() &&
SrcLoc != nullptr) {
setSmartPointerToNull(*SrcLoc, State.Env);
}
return;
}
// Construct from `weak_ptr`. This throws if the `weak_ptr` is empty, so we
// can assume the `shared_ptr` is non-null if the constructor returns.
if (Ctor->getNumArgs() == 1 && isStdWeakPtrType(Ctor->getArg(0)->getType()))
setToPointerWithNullability(Loc.getSyntheticField(PtrField),
NullabilityKind::NonNull, State.Env);
}
static void transferSmartPointerAssignment(
const CXXOperatorCallExpr* OpCall, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* Loc = State.Env.get<RecordStorageLocation>(*OpCall->getArg(0));
if (Loc == nullptr) return;
if (OpCall->getArg(1)->getType()->isNullPtrType()) {
setSmartPointerToNull(*Loc, State.Env);
return;
}
if (!isSupportedSmartPointerType(OpCall->getArg(1)->getType())) {
// We don't know anything about the RHS, so set the LHS to an unspecified
// nullability state.
// TODO(b/376231871): We could handle more RHS cases, for example if RHS
// is a raw pointer. We could potentially assume that, if RHS is anything
// other than a raw pointer, smart pointer, or nullptr_t, then it's
// nonnull (instead of unspecified).
// If we add more cases, expand `diagnoseSmartPointerAssignment` as well.
StorageLocation& PtrLoc = Loc->getSyntheticField(PtrField);
setToPointerWithNullability(PtrLoc, NullabilityKind::Unspecified,
State.Env);
return;
}
auto* SrcLoc = OpCall->getArg(1)->isGLValue()
? State.Env.get<RecordStorageLocation>(*OpCall->getArg(1))
: &State.Env.getResultObjectLocation(*OpCall->getArg(1));
if (SrcLoc != nullptr &&
isPointerTypeConvertible(
underlyingRawPointerTypeFromSmartPointer(*SrcLoc),
underlyingRawPointerTypeFromSmartPointer(*Loc))) {
setSmartPointerValue(
*Loc, getPointerValueFromSmartPointer(SrcLoc, State.Env), State.Env);
}
// If this is the move assignment operator, set the source to null.
auto* Method = dyn_cast_or_null<CXXMethodDecl>(OpCall->getCalleeDecl());
if (SrcLoc != nullptr && Method != nullptr &&
Method->getParamDecl(0)->getType()->isRValueReferenceType()) {
setSmartPointerToNull(*SrcLoc, State.Env);
}
}
static void transferSmartPointerReleaseCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// If the return type isn't what we expect, bail out.
// This can happen if the smart pointer doesn't declare a `pointer` or
// `element_type` type alias and our fallback logic of using `T*` as the
// underlying pointer type (where `T` is the first template argument) is
// incorrect.
if (MCE->getType()->getCanonicalTypeUnqualified() !=
underlyingRawPointerType(MCE->getObjectType())
->getCanonicalTypeUnqualified()) {
return;
}
RecordStorageLocation* Loc = getImplicitObjectLocation(*MCE, State.Env);
if (Loc == nullptr) return;
StorageLocation& PtrLoc = Loc->getSyntheticField(PtrField);
if (auto* Val = State.Env.get<PointerValue>(PtrLoc))
State.Env.setValue(*MCE, *Val);
State.Env.setValue(
PtrLoc, createNullPointer(PtrLoc.getType()->getPointeeType(), State.Env));
}
static void transferSmartPointerResetCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
RecordStorageLocation* Loc = getImplicitObjectLocation(*MCE, State.Env);
if (Loc == nullptr) return;
// Zero-arg and `nullptr_t` overloads, as well as single-argument constructor
// with default argument.
if (MCE->getNumArgs() == 0 ||
(MCE->getNumArgs() == 1 && MCE->getArg(0)->getType()->isNullPtrType()) ||
(MCE->getNumArgs() == 1 && MCE->getArg(0)->isDefaultArgument())) {
setSmartPointerToNull(*Loc, State.Env);
return;
}
// std::shared_ptr::reset can take >1 argument, so we don't restrict to just
// getNumArgs() == 1.
if (MCE->getNumArgs() >= 1 &&
isSupportedRawPointerType(MCE->getArg(0)->getType()) &&
isPointerTypeConvertible(
MCE->getArg(0)->getType(),
underlyingRawPointerTypeFromSmartPointer(*Loc))) {
setSmartPointerValue(*Loc, getRawPointerValue(MCE->getArg(0), State.Env),
State.Env);
}
}
static void swapSmartPointers(RecordStorageLocation* Loc1,
RecordStorageLocation* Loc2, Environment& Env) {
PointerValue* Val1 = getPointerValueFromSmartPointer(Loc1, Env);
PointerValue* Val2 = getPointerValueFromSmartPointer(Loc2, Env);
if (Loc1) {
if (Loc2 == nullptr ||
isPointerTypeConvertible(
underlyingRawPointerTypeFromSmartPointer(*Loc2),
underlyingRawPointerTypeFromSmartPointer(*Loc1))) {
setSmartPointerValue(*Loc1, Val2, Env);
}
}
if (Loc2) {
if (Loc1 == nullptr || isPointerTypeConvertible(
underlyingRawPointerTypeFromSmartPointer(*Loc1),
underlyingRawPointerTypeFromSmartPointer(*Loc2)))
setSmartPointerValue(*Loc2, Val1, Env);
}
}
static void transferSmartPointerMemberSwapCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
swapSmartPointers(getImplicitObjectLocation(*MCE, State.Env),
State.Env.get<RecordStorageLocation>(*MCE->getArg(0)),
State.Env);
}
static void transferSmartPointerFreeSwapCall(
const CallExpr* CE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
swapSmartPointers(State.Env.get<RecordStorageLocation>(*CE->getArg(0)),
State.Env.get<RecordStorageLocation>(*CE->getArg(1)),
State.Env);
}
static void transferSmartPointerGetCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// If the return type isn't what we expect, bail out.
// See `transferSmartPointerReleaseCall()` for more details.
if (MCE->getType()->getCanonicalTypeUnqualified() !=
underlyingRawPointerType(MCE->getObjectType())
->getCanonicalTypeUnqualified()) {
return;
}
if (Value* Val = getPointerValueFromSmartPointer(
getImplicitObjectLocation(*MCE, State.Env), State.Env))
State.Env.setValue(*MCE, *Val);
}
static void transferSmartPointerBoolConversionCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
if (PointerValue* Val = getPointerValueFromSmartPointer(
getImplicitObjectLocation(*MCE, State.Env), State.Env)) {
if (const Formula* IsNull = getPointerNullState(*Val).IsNull)
State.Env.setValue(
*MCE, State.Env.makeNot(State.Env.arena().makeBoolValue(*IsNull)));
}
}
static QualType getReceiverIgnoringImpCastsType(
const CXXOperatorCallExpr* OpCall) {
// Matchers hasArgument() appears to ignore implicit casts, so we ignore them
// here as well to get the same behavior:
// https://github.com/llvm/llvm-project/blob/a58c3d3ac7c6b2fd9710ab2189d7971ef37e714f/clang/include/clang/ASTMatchers/ASTMatchers.h#L4563
const Expr* Receiver = OpCall->getArg(0)->IgnoreImpCasts();
if (Receiver->isPRValue() && Receiver->getType()->isPointerType())
return Receiver->getType()->getPointeeType();
return Receiver->getType();
}
static void transferSmartPointerOperatorStar(
const CXXOperatorCallExpr* OpCall, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// If the return type isn't what we expect, bail out.
// See `transferSmartPointerReleaseCall()` for more details.
// Besides incorrectly guessing the underlyingRawPointerType, we could also
// encounter this if the return type is not the pointee type, or reference
// to the pointee type (e.g., if it is instead the pointer type).
QualType ReturnType = OpCall->getType();
if (ReturnType->isReferenceType()) ReturnType = ReturnType->getPointeeType();
if (ReturnType->getCanonicalTypeUnqualified() !=
underlyingRawPointerType(getReceiverIgnoringImpCastsType(OpCall))
->getPointeeType()
->getCanonicalTypeUnqualified()) {
return;
}
if (PointerValue* Val = getSmartPointerValue(OpCall->getArg(0), State.Env)) {
State.Env.setStorageLocation(*OpCall, Val->getPointeeLoc());
}
}
static void transferSmartPointerOperatorArrow(
const CXXOperatorCallExpr* OpCall, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// If the return type isn't what we expect, bail out.
// See `transferSmartPointerReleaseCall()` for more details.
if (OpCall->getType()->getCanonicalTypeUnqualified() !=
underlyingRawPointerType(getReceiverIgnoringImpCastsType(OpCall))
->getCanonicalTypeUnqualified()) {
return;
}
if (PointerValue* Val = getSmartPointerValue(OpCall->getArg(0), State.Env)) {
State.Env.setValue(*OpCall, *Val);
}
}
static void transferSmartPointerFactoryCall(
const CallExpr* CE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
RecordStorageLocation& Loc = State.Env.getResultObjectLocation(*CE);
StorageLocation& PtrLoc = Loc.getSyntheticField(PtrField);
setToPointerWithNullability(PtrLoc, NullabilityKind::NonNull, State.Env);
// If the smart pointer is a pointer to a raw pointer and is constructed from
// one raw pointer or nullptr_t, initialize the null state of the pointee raw
// pointer from the argument passed in.
if (isSupportedRawPointerType(PtrLoc.getType()->getPointeeType()) &&
CE->getNumArgs() == 1 &&
(CE->getArg(0)->getType()->isPointerType() ||
CE->getArg(0)->getType()->isNullPtrType())) {
auto* SmartPV = State.Env.get<PointerValue>(PtrLoc);
if (!SmartPV) return;
auto* RawPV = State.Env.get<PointerValue>(SmartPV->getPointeeLoc());
if (!RawPV) return;
if (CE->getArg(0)->getType()->isNullPtrType()) {
initNullPointer(*RawPV, State.Env.getDataflowAnalysisContext());
return;
}
auto* ArgPV = getRawPointerValue(CE->getArg(0), State.Env);
if (!ArgPV || !hasPointerNullState(*ArgPV)) return;
initPointerNullState(*RawPV, State.Env.getDataflowAnalysisContext(),
getPointerNullState(*ArgPV));
}
}
static void transferSmartPointerComparisonOpCall(
const CXXOperatorCallExpr* OpCall, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// Formula representing an equality (`==`) comparison of the two operands.
// If the operator is `!=`, this will need to be negated below.
const Formula* EqualityFormula = nullptr;
bool NullPtr1 =
OpCall->getArg(0)->IgnoreImpCasts()->getType()->isNullPtrType();
bool NullPtr2 =
OpCall->getArg(1)->IgnoreImpCasts()->getType()->isNullPtrType();
assert(!NullPtr1 || !NullPtr2);
PointerValue* Val1 = nullptr;
if (!NullPtr1) Val1 = getSmartPointerValue(OpCall->getArg(0), State.Env);
PointerValue* Val2 = nullptr;
if (!NullPtr2) Val2 = getSmartPointerValue(OpCall->getArg(1), State.Env);
if (NullPtr1) {
if (Val2 == nullptr) return;
EqualityFormula = getPointerNullState(*Val2).IsNull;
} else if (NullPtr2) {
if (Val1 == nullptr) return;
EqualityFormula = getPointerNullState(*Val1).IsNull;
} else {
if (Val1 == nullptr || Val2 == nullptr) return;
EqualityFormula = &State.Env.arena().makeLiteral(&Val1->getPointeeLoc() ==
&Val2->getPointeeLoc());
}
if (EqualityFormula == nullptr) return;
BoolValue& EqualityValue = State.Env.arena().makeBoolValue(*EqualityFormula);
if (OpCall->getOperator() == OO_EqualEqual)
State.Env.setValue(*OpCall, EqualityValue);
else
State.Env.setValue(*OpCall, State.Env.makeNot(EqualityValue));
}
static void transferSharedPtrCastCall(
const CallExpr* CE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
Environment& Env = State.Env;
DataflowAnalysisContext& Ctx = Env.getDataflowAnalysisContext();
Arena& A = Env.arena();
auto* Callee = dyn_cast_or_null<FunctionDecl>(CE->getCalleeDecl());
if (Callee == nullptr) return;
auto* SrcLoc = Env.get<RecordStorageLocation>(*CE->getArg(0));
if (SrcLoc == nullptr) return;
StorageLocation& SrcPtrLoc = SrcLoc->getSyntheticField(PtrField);
auto* SrcPtrVal = Env.get<PointerValue>(SrcPtrLoc);
if (SrcPtrVal == nullptr) return;
RecordStorageLocation& DestLoc = Env.getResultObjectLocation(*CE);
StorageLocation& DestPtrLoc = DestLoc.getSyntheticField(PtrField);
if (Callee->getName() == "const_pointer_cast") {
// A `const_pointer_cast` will definitely produce a pointer with the same
// storage location as the source, so we can simply copy the underlying
// pointer value.
Env.setValue(DestPtrLoc, *SrcPtrVal);
} else {
auto& DestPtrVal =
*cast<PointerValue>(Env.createValue(DestPtrLoc.getType()));
initPointerNullState(DestPtrVal, Ctx);
State.Env.setValue(DestPtrLoc, DestPtrVal);
PointerNullState SrcNullability = getPointerNullState(*SrcPtrVal);
PointerNullState DestNullability = getPointerNullState(DestPtrVal);
assert(DestNullability.IsNull != nullptr);
assert(DestNullability.FromNullable != nullptr);
if (Callee->getName() == "dynamic_pointer_cast") {
// A `dynamic_pointer_cast` may fail. So source `IsNull` implies
// destination `IsNull` (but not the other way around), and the result is
// always nullable.
if (SrcNullability.IsNull != nullptr)
Env.assume(
A.makeImplies(*SrcNullability.IsNull, *DestNullability.IsNull));
Env.assume(*DestNullability.FromNullable);
} else {
if (SrcNullability.IsNull != nullptr)
Env.assume(
A.makeEquals(*SrcNullability.IsNull, *DestNullability.IsNull));
if (SrcNullability.FromNullable != nullptr)
Env.assume(A.makeEquals(*SrcNullability.FromNullable,
*DestNullability.FromNullable));
}
}
// Is this an overload taking an rvalue reference?
if (Callee->getParamDecl(0)->getType()->isRValueReferenceType()) {
if (Callee->getName() == "dynamic_pointer_cast") {
// `dynamic_pointer_cast` sets its argument to null only if the cast
// succeeded. So, replace the argument with a new Nullable (but not
// definitely Null) pointer.
setToPointerWithNullability(SrcPtrLoc, NullabilityKind::Nullable,
State.Env);
} else {
setSmartPointerToNull(*SrcLoc, State.Env);
}
}
}
static void transferWeakPtrLockCall(
const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
RecordStorageLocation& Loc = State.Env.getResultObjectLocation(*MCE);
StorageLocation& PtrLoc = Loc.getSyntheticField(PtrField);
setToPointerWithNullability(PtrLoc, NullabilityKind::Nullable, State.Env);
}
static void transferSmartPointerArrowMemberExpr(
const MemberExpr* ME, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// Most accesses of a smart pointer involve a glvalue of smart pointer type,
// and `transferSmartPointer` will ensure in this case that the
// nullability properties of the underlying raw pointer are initialized.
// An exception to this is if we access members of a smart pointer using
// arrow syntax; in this case, there is no glvalue of smart pointer type,
// and this function handles initialization of the underlying raw pointer
// in this case.
const Expr& Base = *ME->getBase();
auto* BasePtrVal = State.Env.get<PointerValue>(Base);
if (BasePtrVal == nullptr) {
BasePtrVal = cast<PointerValue>(State.Env.createValue(Base.getType()));
State.Env.setValue(Base, *BasePtrVal);
}
auto& SmartPtrLoc = cast<RecordStorageLocation>(BasePtrVal->getPointeeLoc());
StorageLocation& PtrLoc = SmartPtrLoc.getSyntheticField(PtrField);
auto* PtrVal = State.Env.get<PointerValue>(PtrLoc);
if (PtrVal == nullptr) {
PtrVal = cast<PointerValue>(State.Env.createValue(PtrLoc.getType()));
State.Env.setValue(PtrLoc, *PtrVal);
}
PointerTypeNullability Nullability = NullabilityKind::Unspecified;
if (const auto* TyNullability =
State.Lattice.getTypeNullability(ME->getBase())) {
if (TyNullability->size() >= 2) Nullability = (*TyNullability)[1];
}
initPointerNullState(*PtrVal, State.Env.getDataflowAnalysisContext(),
Nullability);
}
static void transferPointer(const Expr* absl_nonnull PointerExpr,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* PointerVal = ensureRawPointerHasValue(PointerExpr, State.Env);
if (!PointerVal) return;
initPointerFromTypeNullability(*PointerVal, PointerExpr, State);
if (const auto* Cast = dyn_cast<CastExpr>(PointerExpr);
Cast && Cast->getCastKind() == CK_LValueToRValue) {
if (StorageLocation* Loc =
State.Env.getStorageLocation(*Cast->getSubExpr())) {
if (PointerValue* Val = unpackPointerValue(*Loc, State.Env)) {
State.Env.setValue(*PointerExpr, *Val);
}
}
}
}
// `ComparisonFormula` represents the comparison between the two pointer values.
//
// `LHSNull` and `RHSNull` represent the nullability of the left- and right-hand
// expressions, respectively. A nullptr value is interpreted as Top.
static BoolValue* absl_nullable processPointerComparison(
const Formula& ComparisonFormula, const Formula* absl_nullable LHSNull,
const Formula* absl_nullable RHSNull, BinaryOperatorKind Opcode,
Environment& Env) {
auto& A = Env.arena();
// If the null state of either pointer is "top", the result of the comparison
// is a top bool, and we don't have any knowledge we can add to the flow
// condition.
if (LHSNull == nullptr || RHSNull == nullptr) {
return &A.makeTopValue();
}
// Special case: Are we comparing against `nullptr`?
// We can avoid modifying the flow condition in this case and simply propagate
// the nullability of the other operand (potentially with a negation).
if (LHSNull->isLiteral(true))
return &A.makeBoolValue(Opcode == BO_EQ ? *RHSNull : A.makeNot(*RHSNull));
if (RHSNull->isLiteral(true))
return &A.makeBoolValue(Opcode == BO_EQ ? *LHSNull : A.makeNot(*LHSNull));
CHECK(Opcode == BO_EQ || Opcode == BO_NE);
auto& PointerEQ =
Opcode == BO_EQ ? ComparisonFormula : A.makeNot(ComparisonFormula);
auto& PointerNE =
Opcode == BO_EQ ? A.makeNot(ComparisonFormula) : ComparisonFormula;
// nullptr == nullptr
Env.assume(A.makeImplies(A.makeAnd(*LHSNull, *RHSNull), PointerEQ));
// nullptr != notnull
Env.assume(
A.makeImplies(A.makeAnd(*LHSNull, A.makeNot(*RHSNull)), PointerNE));
// notnull != nullptr
Env.assume(
A.makeImplies(A.makeAnd(A.makeNot(*LHSNull), *RHSNull), PointerNE));
// We used the pre-existing formula, so nothing to return.
return nullptr;
}
// TODO(b/233582219): Implement promotion of nullability for initially
// unknown pointers when there is evidence that it is nullable, for example
// when the pointer is compared to nullptr, or cast to boolean.
static void transferNullCheckComparison(
const BinaryOperator* absl_nonnull BinaryOp,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* LHS = BinaryOp->getLHS();
auto* RHS = BinaryOp->getRHS();
assert(LHS != nullptr && RHS != nullptr);
// Boolean representing the comparison between the two pointer values.
// We can rely on the dataflow framework to have produced a value for this.
auto* ComparisonVal = State.Env.get<BoolValue>(*BinaryOp);
assert(ComparisonVal != nullptr);
auto& ComparisonFormula = ComparisonVal->formula();
auto* LHSVal = getRawPointerValue(LHS, State.Env);
if (!LHSVal || !hasPointerNullState(*LHSVal)) return;
auto* RHSVal = getRawPointerValue(RHS, State.Env);
if (!RHSVal || !hasPointerNullState(*RHSVal)) return;
if (auto* Val = processPointerComparison(ComparisonFormula,
getPointerNullState(*LHSVal).IsNull,
getPointerNullState(*RHSVal).IsNull,
BinaryOp->getOpcode(), State.Env))
State.Env.setValue(*BinaryOp, *Val);
}
static void transferNullCheckImplicitCastPtrToBool(
const Expr* absl_nonnull CastExpr, const MatchFinder::MatchResult&,
TransferState<PointerNullabilityLattice>& State) {
auto& A = State.Env.arena();
if (auto* CE = dyn_cast<clang::CastExpr>(CastExpr);
CE && CE->getSubExpr()->getType()->isNullPtrType()) {
// nullptr_t values might have PointerValues, but never have modeled null
// state. Skip over casts of them to bool.
// We could explicitly set the boolean value for the cast to false, but the
// compiler already detects the branch as unreachable, so we don't traverse
// CFG elements that would use this boolean.
return;
}
auto* PointerVal = getRawPointerValue(CastExpr->IgnoreImplicit(), State.Env);
if (!PointerVal) return;
auto Nullability = getPointerNullState(*PointerVal);
if (Nullability.IsNull != nullptr)
State.Env.setValue(*CastExpr,
A.makeBoolValue(A.makeNot(*Nullability.IsNull)));
else
State.Env.setValue(*CastExpr, A.makeTopValue());
}
static void initializeOutputParameter(
const Expr* absl_nonnull Arg,
TransferState<PointerNullabilityLattice>& State, const VarDecl& Param) {
// When a function has an "output parameter" - a non-const pointer or
// reference to a pointer of unknown nullability - assume that the function
// may set the pointer to non-null.
//
// For example, in the following code sequence we assume that the function may
// modify the pointer in a way that makes a subsequent dereference safe:
//
// void maybeModify(int ** _Nonnull);
//
// int *p = nullptr;
// initializePointer(&p);
// *p; // safe
QualType ParamTy = Param.getType();
if (ParamTy.isNull()) return;
if (!ParamTy->isPointerType() && !ParamTy->isReferenceType()) return;
if (!isSupportedPointerType(ParamTy->getPointeeType())) return;
if (ParamTy->getPointeeType().isConstQualified()) return;
// TODO: if the called function was instantiated from a template, examining
// the instantiated param decl may miss nullability from template params.
// TODO(b/298200521): This should extend support to annotations that suggest
// different in/out state
TypeNullability OuterNullability =
getTypeNullability(Param, State.Lattice.defaults());
auto InnerNullability = ParamTy->getAs<ReferenceType>()
? ArrayRef(OuterNullability)
: ArrayRef(OuterNullability).drop_front();
if (InnerNullability.front().concrete() != NullabilityKind::Unspecified)
return;
StorageLocation* Loc = nullptr;
if (ParamTy->isPointerType()) {
if (PointerValue* OuterPointer = getRawPointerValue(Arg, State.Env))
Loc = &OuterPointer->getPointeeLoc();
} else if (ParamTy->isReferenceType()) {
Loc = State.Env.getStorageLocation(*Arg);
}
if (Loc == nullptr) return;
if (isSupportedRawPointerType(ParamTy->getPointeeType())) {
auto* InnerPointer =
cast<PointerValue>(State.Env.createValue(ParamTy->getPointeeType()));
initPointerNullState(*InnerPointer, State.Env.getDataflowAnalysisContext(),
NullabilityKind::Unspecified);
State.Env.setValue(*Loc, *InnerPointer);
} else {
auto& SmartPointerLoc = *cast<RecordStorageLocation>(Loc);
setToPointerWithNullability(SmartPointerLoc.getSyntheticField(PtrField),
NullabilityKind::Unspecified, State.Env);
}
}
// `D` is declared somewhere in `absl` or `util`, either directly or nested.
static bool isDeclaredInAbseilOrUtil(const Decl& D) {
const auto* DC = D.getDeclContext();
if (DC == nullptr || DC->isTranslationUnit()) return false;
// Find the topmost, non-TU DeclContext.
const DeclContext* Parent = DC->getParent();
while (Parent != nullptr && !Parent->isTranslationUnit()) {
DC = Parent;
Parent = DC->getParent();
}
// Check if it is the `absl` namespace.
const auto* NS = dyn_cast_or_null<NamespaceDecl>(DC);
return NS != nullptr && NS->getDeclName().isIdentifier() &&
(NS->getName() == "absl" || NS->getName() == "util");
}
// Models the `GetReferenceableValue` functions used in Abseil logging and
// elsewhere.
static void modelGetReferenceableValue(const CallExpr& CE, Environment& Env) {
// We only model the `GetReferenceableValue` overload that takes and returns a
// reference.
if (!CE.isGLValue()) return;
assert(CE.getNumArgs() == 1);
assert(CE.getArg(0) != nullptr);
if (StorageLocation* Loc = Env.getStorageLocation(*CE.getArg(0)))
Env.setStorageLocation(CE, *Loc);
}
// Models the Abseil-logging `CheckNE_Impl` function. Essentially, associates
// the `IsNull` of the call result with the comparison `arg0 != arg1`.
static void modelCheckNE(const CallExpr& CE, Environment& Env) {
assert(isSupportedRawPointerType(CE.getType()));
auto* PointerVal = getRawPointerValue(&CE, Env);
if (!PointerVal)
PointerVal = cast<PointerValue>(Env.createValue(CE.getType()));
// Force the pointer state to `Nullable`, which we will then potentially
// refine below.
// TODO Add the annotation in the logging library so that we don't have
// to hard-code this here.
initPointerNullState(*PointerVal, Env.getDataflowAnalysisContext(),
NullabilityKind::Nullable);
Env.setValue(CE, *PointerVal);
const Formula* IsNull = getPointerNullState(*PointerVal).IsNull;
assert(IsNull != nullptr && "`IsNull` can never be 'Top' here");
auto* LHS = CE.getArg(0);
auto* RHS = CE.getArg(1);
assert(LHS != nullptr && RHS != nullptr);
auto LTy = LHS->getType();
auto RTy = RHS->getType();
if (!isSupportedPointerType(LTy) && !LTy->isNullPtrType()) return;
if (!isSupportedPointerType(RTy) && !RTy->isNullPtrType()) return;
const Formula* LHSNull = nullptr;
if (LTy->isNullPtrType()) {
// Values of nullptr type are not themselves pointers and so not
// modeled directly. They are only modeled if and when they are cast
// to pointers. So, we need to supply a formula directly.
LHSNull = &Env.arena().makeLiteral(true);
} else {
auto* V = getPointerValue(LHS, Env);
if (!V) return;
assert(hasPointerNullState(*V));
LHSNull = getPointerNullState(*V).IsNull;
}
const Formula* RHSNull = nullptr;
if (RTy->isNullPtrType()) {
RHSNull = &Env.arena().makeLiteral(true);
} else {
auto* V = getPointerValue(RHS, Env);
if (!V) return;
assert(hasPointerNullState(*V));
RHSNull = getPointerNullState(*V).IsNull;
}
if (auto* Val =
processPointerComparison(*IsNull, LHSNull, RHSNull, BO_NE, Env))
Env.assume(Env.arena().makeEquals(Val->formula(), *IsNull));
}
static void transferCallExpr(const CallExpr* absl_nonnull CE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// The dataflow framework itself generally does not model `CallExpr`s
// (including creating values for the results). We model some specific
// function calls and handle value creation for certain types.
const auto* FuncDecl = CE->getDirectCallee();
if (FuncDecl != nullptr) {
if (const IdentifierInfo* FunII =
FuncDecl->getDeclName().getAsIdentifierInfo()) {
if (FunII->isStr("__assert_nullability")) return;
// This is part of the implementation of `CHECK_NE`.
if (FunII->isStr("GetReferenceableValue") &&
isDeclaredInAbseilOrUtil(*FuncDecl)) {
modelGetReferenceableValue(*CE, State.Env);
return;
}
if (FunII->isStr("Check_NEImpl") && isDeclaredInAbseilOrUtil(*FuncDecl)) {
modelCheckNE(*CE, State.Env);
return;
}
}
}
StorageLocation* Loc = nullptr;
if (CE->isGLValue()) {
// The function returned a reference. Create a storage location for the
// expression so that if code creates a pointer from the reference, we will
// produce a `PointerValue`.
Loc = State.Env.getStorageLocation(*CE);
if (!Loc) {
// This is subtle: We call `createStorageLocation(QualType)`, not
// `createStorageLocation(const Expr &)`, so that we create a new
// storage location every time.
Loc = &State.Env.createStorageLocation(CE->getType());
State.Env.setStorageLocation(*CE, *Loc);
}
}
if (isSupportedRawPointerType(CE->getType())) {
// Create a pointer so that we can attach nullability to it and have the
// nullability propagate with the pointer.
auto* PointerVal = getRawPointerValue(CE, State.Env);
if (!PointerVal) {
PointerVal = cast<PointerValue>(State.Env.createValue(CE->getType()));
}
initPointerFromTypeNullability(*PointerVal, CE, State);
if (Loc != nullptr)
State.Env.setValue(*Loc, *PointerVal);
else
// `Loc` is set iff `CE` is a glvalue, so we know here that it must
// be a prvalue.
State.Env.setValue(*CE, *PointerVal);
}
if (CE->isCallToStdMove() || FuncDecl == nullptr) return;
// Don't treat parameters of our macro replacement argument-capture functions
// as output parameters.
if (const IdentifierInfo* FunII =
FuncDecl->getDeclName().getAsIdentifierInfo();
FunII && (FunII->isStr(ArgCaptureAbortIfFalse) ||
FunII->isStr(ArgCaptureAbortIfEqual)))
return;
// Make output parameters (with unknown nullability) initialized to unknown.
for (ParamAndArgIterator<CallExpr> Iter(*FuncDecl, *CE); Iter; ++Iter)
initializeOutputParameter(&Iter.arg(), State, Iter.param());
}
static void transferAccessorCall(
const CXXMemberCallExpr* absl_nonnull MCE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* Member = Result.Nodes.getNodeAs<clang::ValueDecl>("member-decl");
PointerValue* PointerVal = nullptr;
StorageLocation* FieldLoc = nullptr;
if (dataflow::RecordStorageLocation* RecordLoc =
dataflow::getImplicitObjectLocation(*MCE, State.Env)) {
FieldLoc = RecordLoc->getChild(*Member);
PointerVal = dyn_cast_or_null<PointerValue>(State.Env.getValue(*FieldLoc));
}
if (!PointerVal) {
PointerVal = ensureRawPointerHasValue(MCE, State.Env);
}
if (PointerVal) {
State.Env.setValue(*MCE, *PointerVal);
if (FieldLoc != nullptr) {
State.Env.setValue(*FieldLoc, *PointerVal);
}
initPointerFromTypeNullability(*PointerVal, MCE, State);
}
}
static std::function<void(StorageLocation&)>
initCallbackForStorageLocationIfSmartPointer(const CallExpr* absl_nonnull CE,
dataflow::Environment& Env) {
if (!isSupportedSmartPointerType(CE->getType()))
return [](StorageLocation& Loc) {};
return [CE, &Env](StorageLocation& Loc) {
setSmartPointerValue(cast<RecordStorageLocation>(Loc),
cast<PointerValue>(Env.createValue(
underlyingRawPointerType(CE->getType()))),
Env);
};
}
static void handleConstMemberCall(
const CallExpr* absl_nonnull CE,
dataflow::RecordStorageLocation* absl_nullable RecordLoc,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
if (RecordLoc == nullptr) {
// Perform default handling
transferCallExpr(CE, Result, State);
return;
}
// If the const method returns a smart pointer, handle it separately.
// Smart pointers are represented as RecordStorangeLocations, so their
// treatment is different from booleans or raw pointers, which are
// represented as Values.
if (isSupportedSmartPointerType(CE->getType())) {
const FunctionDecl* DirectCallee = CE->getDirectCallee();
if (DirectCallee == nullptr) {
// Perform default handling
transferCallExpr(CE, Result, State);
return;
}
StorageLocation& Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env,
initCallbackForStorageLocationIfSmartPointer(CE, State.Env));
if (CE->isGLValue()) {
// If the call to the const method returns a reference to a smart pointer,
// we can use link the call expression to the smart pointer via
// setStorageLocation.
State.Env.setStorageLocation(*CE, Loc);
} else {
// If the call to the const method returns a smart pointer by value, we
// need to use CopyRecord to link the smart pointer to the result object
// of the call expression.
copyRecord(cast<RecordStorageLocation>(Loc),
State.Env.getResultObjectLocation(*CE), State.Env);
}
return;
}
// If the const method returns a raw pointer or boolean (represented as
// Values) handle them appropriately.
if (CE->isPRValue() && (isSupportedRawPointerType(CE->getType()) ||
CE->getType()->isBooleanType())) {
Value* Val = State.Lattice.getOrCreateConstMethodReturnValue(*RecordLoc, CE,
State.Env);
if (Val == nullptr) {
// Perform default handling
transferCallExpr(CE, Result, State);
return;
}
State.Env.setValue(*CE, *Val);
if (auto* PointerVal = dyn_cast<PointerValue>(Val))
initPointerFromTypeNullability(*PointerVal, CE, State);
return;
}
// If the const method returns a reference, handle it separately.
const FunctionDecl* DirectCallee = CE->getDirectCallee();
if (DirectCallee != nullptr &&
DirectCallee->getReturnType()->isReferenceType()) {
StorageLocation& Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [](StorageLocation& Loc) {});
State.Env.setStorageLocation(*CE, Loc);
return;
}
// Perform default handling for remaining return types
transferCallExpr(CE, Result, State);
}
static void transferConstMemberCall(
const CXXMemberCallExpr* absl_nonnull MCE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
handleConstMemberCall(
MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);
}
static void transferConstMemberOperatorCall(
const CXXOperatorCallExpr* OCE, const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
State.Env.getStorageLocation(*OCE->getArg(0)));
handleConstMemberCall(OCE, RecordLoc, Result, State);
}
static void handleNonConstMemberCall(
const CallExpr* absl_nonnull CE, dataflow::RecordStorageLocation* RecordLoc,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
// When a non-const member function is called, clear all (non-const)
// pointer-type fields of the receiver. Const-qualified fields can't be
// changed (at least, not without UB).
if (RecordLoc != nullptr) {
for (const auto [Field, FieldLoc] : RecordLoc->children()) {
QualType FieldType = Field->getType();
if (FieldType.isConstQualified() || !isSupportedRawPointerType(FieldType))
continue;
const auto* FieldStronglyTyped = dyn_cast<FieldDecl>(Field);
if (FieldStronglyTyped == nullptr) continue;
if (isa<ClassTemplateSpecializationDecl>(
FieldStronglyTyped->getParent())) {
// We can't produce a new `PointerValue` here because we don't
// know what to initialize its nullability properties with: A
// `ClassTemplateSpecializationDecl` uses canonical types for
// its type arguments (there is only one specialization for the same
// canonical type arguments), so the `FieldDecl` doesn't contain
// nullability annotations. The best thing we can do, therefore, is to
// clear the value.
// TODO{mboehme): We should resugar the type of the field, similar to
// the way this is done in `transferType_DeclRefExpr()`.
State.Env.clearValue(*FieldLoc);
} else {
auto* Val = cast<PointerValue>(
State.Env.createValue(FieldStronglyTyped->getType()));
State.Env.setValue(*FieldLoc, *Val);
TypeNullability N =
State.Lattice.getTypeNullabilityWithOverrides(*FieldStronglyTyped);
if (N.empty()) {
// The field has pointer type, so it should have nullability.
// In a release build, just ignore and move on.
assert(false);
continue;
}
initPointerNullState(*Val, State.Env.getDataflowAnalysisContext(),
N.front());
}
}
State.Lattice.clearConstMethodReturnValues(*RecordLoc);
State.Lattice.clearConstMethodReturnStorageLocations(*RecordLoc);
}
// Perform default handling.
transferCallExpr(CE, Result, State);
}
static void transferNonConstMemberCall(
const CXXMemberCallExpr* absl_nonnull MCE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
handleNonConstMemberCall(
MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);
}
static void transferNonConstMemberOperatorCall(
const CXXOperatorCallExpr* absl_nonnull OCE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
State.Env.getStorageLocation(*OCE->getArg(0)));
handleNonConstMemberCall(OCE, RecordLoc, Result, State);
}
dataflow::CFGMatchSwitch<dataflow::TransferState<PointerNullabilityLattice>>
buildValueTransferer() {
// The value transfer functions must establish:
// - if we're transferring over an Expr
// - and the Expr has a supported pointer type
// - and the Expr's value is modeled by the framework (or this analysis)
// - then the PointerValue has nullability properties (is_null/from_nullable)
return dataflow::CFGMatchSwitchBuilder<
TransferState<PointerNullabilityLattice>>()
// Handles initialization of the null states of pointers.
.CaseOfCFGStmt<Expr>(isAddrOf(), transferNotNullPointer)
// TODO(mboehme): I believe we should be able to move handling of null
// pointers to the non-flow-sensitive part of the analysis.
.CaseOfCFGStmt<Expr>(isNullPointerLiteral(), transferNullPointer)
.CaseOfCFGStmt<CXXScalarValueInitExpr>(isRawPointerValueInit(),
transferNullPointer)
.CaseOfCFGStmt<ImplicitValueInitExpr>(isRawPointerImplicitValueInit(),
transferNullPointer)
.CaseOfCFGStmt<CXXDefaultInitExpr>(isNullPointerDefaultInit(),
transferNullPointer)
.CaseOfCFGStmt<UnaryOperator>(isPointerIncOrDec(),
transferPointerIncOrDec)
.CaseOfCFGStmt<BinaryOperator>(isPointerAddOrSubAssign(),
transferPointerAddOrSubAssign)
.CaseOfCFGStmt<CXXConstructExpr>(isSmartPointerConstructor(),
transferSmartPointerConstructor)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("=", 2),
transferSmartPointerAssignment)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerMethodCall("release", "Release"),
transferSmartPointerReleaseCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerMethodCall("reset", "Reset"),
transferSmartPointerResetCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerMethodCall("swap", "Swap"),
transferSmartPointerMemberSwapCall)
.CaseOfCFGStmt<CallExpr>(isSmartPointerFreeSwapCall(),
transferSmartPointerFreeSwapCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isSmartPointerMethodCall("get"),
transferSmartPointerGetCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isSmartPointerBoolConversionCall(),
transferSmartPointerBoolConversionCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("*", 1),
transferSmartPointerOperatorStar)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("->", 1),
transferSmartPointerOperatorArrow)
.CaseOfCFGStmt<CallExpr>(isSmartPointerFactoryCall(),
transferSmartPointerFactoryCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerComparisonOpCall(),
transferSmartPointerComparisonOpCall)
.CaseOfCFGStmt<CallExpr>(isSharedPtrCastCall(), transferSharedPtrCastCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isWeakPtrLockCall(),
transferWeakPtrLockCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isSupportedPointerAccessorCall(),
transferAccessorCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
dataflow::isSmartPointerLikeOperatorStar(),
[](const CXXOperatorCallExpr* CE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
State.Env.getStorageLocation(*CE->getArg(0)));
dataflow::transferSmartPointerLikeCachedDeref(
CE, RecordLoc, State,
initCallbackForStorageLocationIfSmartPointer(CE, State.Env));
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
dataflow::isSmartPointerLikeOperatorArrow(),
[](const CXXOperatorCallExpr* CE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
auto* RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
State.Env.getStorageLocation(*CE->getArg(0)));
dataflow::transferSmartPointerLikeCachedGet(
CE, RecordLoc, State,
initCallbackForStorageLocationIfSmartPointer(CE, State.Env));
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
dataflow::isSmartPointerLikeValueMethodCall(),
[](const CXXMemberCallExpr* CE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
dataflow::transferSmartPointerLikeCachedDeref(
CE, getImplicitObjectLocation(*CE, State.Env), State,
initCallbackForStorageLocationIfSmartPointer(CE, State.Env));
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
dataflow::isSmartPointerLikeGetMethodCall(),
[](const CXXMemberCallExpr* CE,
const MatchFinder::MatchResult& Result,
TransferState<PointerNullabilityLattice>& State) {
dataflow::transferSmartPointerLikeCachedGet(
CE, getImplicitObjectLocation(*CE, State.Env), State,
initCallbackForStorageLocationIfSmartPointer(CE, State.Env));
})
.CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(),
transferConstMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isZeroParamConstMemberOperatorCall(),
transferConstMemberOperatorCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),
transferNonConstMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isNonConstMemberOperatorCall(),
transferNonConstMemberOperatorCall)
.CaseOfCFGStmt<CallExpr>(ast_matchers::callExpr(), transferCallExpr)
.CaseOfCFGStmt<MemberExpr>(isSmartPointerArrowMemberExpr(),
transferSmartPointerArrowMemberExpr)
.CaseOfCFGStmt<Expr>(isPointerExpr(), transferPointer)
// Handles comparison between 2 pointers.
.CaseOfCFGStmt<BinaryOperator>(isPointerCheckBinOp(),
transferNullCheckComparison)
// Handles checking of pointer as boolean.
.CaseOfCFGStmt<Expr>(isImplicitCastPointerToBool(),
transferNullCheckImplicitCastPtrToBool)
.Build();
}
void ensureSmartPointerInitialized(
const CFGElement& Elt, TransferState<PointerNullabilityLattice>& State) {
auto S = Elt.getAs<CFGStmt>();
if (!S) return;
auto* E = dyn_cast<Expr>(S->getStmt());
if (E == nullptr || !isSupportedSmartPointerType(E->getType())) return;
initSmartPointerForExpr(E, State);
auto* SmartPtrLoc = E->isGLValue() ? State.Env.get<RecordStorageLocation>(*E)
: &State.Env.getResultObjectLocation(*E);
if (SmartPtrLoc == nullptr) return;
StorageLocation& PtrLoc = SmartPtrLoc->getSyntheticField(PtrField);
unpackPointerValue(PtrLoc, State.Env);
}
} // namespace clang::tidy::nullability