| // 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_analysis.h" | |
| #include <cassert> | |
| #include <functional> | |
| #include <optional> | |
| #include <utility> | |
| #include "absl/base/nullability.h" | |
| #include "nullability/pointer_nullability.h" | |
| #include "nullability/pointer_nullability_lattice.h" | |
| #include "nullability/pragma.h" | |
| #include "nullability/type_nullability.h" | |
| #include "nullability/type_transferer.h" | |
| #include "nullability/value_transferer.h" | |
| #include "clang/AST/ASTContext.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/NestedNameSpecifierBase.h" | |
| #include "clang/AST/Stmt.h" | |
| #include "clang/AST/TemplateBase.h" | |
| #include "clang/AST/TypeBase.h" | |
| #include "clang/Analysis/CFG.h" | |
| #include "clang/Analysis/FlowSensitive/Arena.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowLattice.h" | |
| #include "clang/Analysis/FlowSensitive/Formula.h" | |
| #include "clang/Analysis/FlowSensitive/MatchSwitch.h" | |
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" | |
| #include "clang/Analysis/FlowSensitive/Value.h" | |
| #include "clang/Basic/LLVM.h" | |
| #include "clang/Basic/Specifiers.h" | |
| #include "llvm/ADT/StringMap.h" | |
| namespace clang::tidy::nullability { | |
| using dataflow::Arena; | |
| using dataflow::ComparisonResult; | |
| using dataflow::DataflowAnalysisContext; | |
| using dataflow::Environment; | |
| using dataflow::Formula; | |
| using dataflow::LatticeEffect; | |
| using dataflow::PointerValue; | |
| using dataflow::StorageLocation; | |
| using dataflow::TransferState; | |
| using dataflow::Value; | |
| using dataflow::WidenResult; | |
| // If `Elt` is an expression of raw pointer type, ensures that it has a | |
| // `PointerValue` associated with it. Also ensure that it has nullability | |
| // state. | |
| static void ensureRawPointerHasValueAndNullability( | |
| const CFGElement& Elt, Environment& Env, | |
| TransferState<PointerNullabilityLattice>& State) { | |
| auto S = Elt.getAs<CFGStmt>(); | |
| if (!S) return; | |
| const Expr* E = dyn_cast<Expr>(S->getStmt()); | |
| if (!E) return; | |
| if (auto* PointerVal = ensureRawPointerHasValue(E, Env)) { | |
| if (!hasPointerNullState(*PointerVal)) { | |
| initPointerFromTypeNullability(*PointerVal, E, State); | |
| } | |
| } | |
| } | |
| PointerNullabilityAnalysis::PointerNullabilityAnalysis( | |
| ASTContext& Context, Environment& Env, const NullabilityPragmas& Pragmas) | |
| : DataflowAnalysis<PointerNullabilityAnalysis, PointerNullabilityLattice>( | |
| Context), | |
| TypeTransferer(buildTypeTransferer()), | |
| ValueTransferer(buildValueTransferer()) { | |
| Env.getDataflowAnalysisContext().setSyntheticFieldCallback( | |
| [](QualType Ty) -> llvm::StringMap<QualType> { | |
| QualType RawPointerTy = underlyingRawPointerType(Ty, AS_private); | |
| if (RawPointerTy.isNull()) return {}; | |
| return {{PtrField, RawPointerTy}}; | |
| }); | |
| NFS.Defaults = TypeNullabilityDefaults(Context, Pragmas); | |
| } | |
| PointerTypeNullability PointerNullabilityAnalysis::assignNullabilityVariable( | |
| const ValueDecl* absl_nonnull D, dataflow::Arena& A) { | |
| auto [It, Inserted] = NFS.DeclTopLevelNullability.try_emplace( | |
| cast<ValueDecl>(D->getCanonicalDecl())); | |
| if (Inserted) It->second = PointerTypeNullability::createSymbolic(A); | |
| return It->second; | |
| } | |
| void PointerNullabilityAnalysis::transfer(const CFGElement& Elt, | |
| PointerNullabilityLattice& Lattice, | |
| Environment& Env) { | |
| TransferState<PointerNullabilityLattice> State(Lattice, Env); | |
| TypeTransferer(Elt, getASTContext(), State); | |
| ValueTransferer(Elt, getASTContext(), State); | |
| ensureRawPointerHasValueAndNullability(Elt, Env, State); | |
| ensureSmartPointerInitialized(Elt, State); | |
| } | |
| static const Formula* absl_nullable mergeFormulas( | |
| const Formula* absl_nullable Bool1, const Environment& Env1, | |
| const Formula* absl_nullable Bool2, const Environment& Env2, | |
| Environment& MergedEnv) { | |
| if (Bool1 == Bool2) { | |
| return Bool1; | |
| } | |
| if (Bool1 == nullptr || Bool2 == nullptr) return nullptr; | |
| auto& A = MergedEnv.arena(); | |
| // If `Bool1` and `Bool2` is constrained to the same true / false value, that | |
| // can serve as the return value - this simplifies the flow condition tracked | |
| // in `MergedEnv`. Otherwise, information about which path was taken is used | |
| // to associate the return value with `Bool1` and `Bool2`. | |
| if (Env1.proves(*Bool1)) { | |
| if (Env2.proves(*Bool2)) { | |
| return &A.makeLiteral(true); | |
| } | |
| } else if (Env1.proves(A.makeNot(*Bool1)) && Env2.proves(A.makeNot(*Bool2))) { | |
| return &A.makeLiteral(false); | |
| } | |
| auto& MergedBool = A.makeAtomRef(A.makeAtom()); | |
| // TODO(b/233582219): Flow conditions are not necessarily mutually | |
| // exclusive, a fix is in order: https://reviews.llvm.org/D130270. Update | |
| // this section when the patch is committed. | |
| auto FC1 = Env1.getFlowConditionToken(); | |
| auto FC2 = Env2.getFlowConditionToken(); | |
| MergedEnv.assume(A.makeOr( | |
| A.makeAnd(A.makeAtomRef(FC1), A.makeEquals(MergedBool, *Bool1)), | |
| A.makeAnd(A.makeAtomRef(FC2), A.makeEquals(MergedBool, *Bool2)))); | |
| return &MergedBool; | |
| } | |
| void PointerNullabilityAnalysis::join(QualType Type, const Value& Val1, | |
| const Environment& Env1, | |
| const Value& Val2, | |
| const Environment& Env2, Value& MergedVal, | |
| Environment& MergedEnv) { | |
| if (!isSupportedRawPointerType(Type)) return; | |
| if (!hasPointerNullState(cast<PointerValue>(Val1)) || | |
| !hasPointerNullState(cast<PointerValue>(Val2))) { | |
| // It can happen that we merge pointers without null state, if either or | |
| // both of the pointers has not appeared in an expression (and has not | |
| // otherwise been initialized with nullability properties) before the merge. | |
| // When the merged value appears in an expression, `transferPointer` will | |
| // take care of initializing it with nullability properties. | |
| return; | |
| } | |
| auto Nullability1 = getPointerNullState(cast<PointerValue>(Val1)); | |
| auto Nullability2 = getPointerNullState(cast<PointerValue>(Val2)); | |
| auto* FromNullable = | |
| mergeFormulas(Nullability1.FromNullable, Env1, Nullability2.FromNullable, | |
| Env2, MergedEnv); | |
| auto* Null = mergeFormulas(Nullability1.IsNull, Env1, Nullability2.IsNull, | |
| Env2, MergedEnv); | |
| initPointerNullState(cast<PointerValue>(MergedVal), | |
| MergedEnv.getDataflowAnalysisContext(), | |
| {FromNullable, Null}); | |
| } | |
| ComparisonResult PointerNullabilityAnalysis::compare(QualType Type, | |
| const Value& Val1, | |
| const Environment& Env1, | |
| const Value& Val2, | |
| const Environment& Env2) { | |
| if (const auto* PointerVal1 = dyn_cast<PointerValue>(&Val1)) { | |
| const auto& PointerVal2 = cast<PointerValue>(Val2); | |
| if (&PointerVal1->getPointeeLoc() != &PointerVal2.getPointeeLoc()) | |
| return ComparisonResult::Different; | |
| if (hasPointerNullState(*PointerVal1) != hasPointerNullState(PointerVal2)) | |
| return ComparisonResult::Different; | |
| if (!hasPointerNullState(*PointerVal1)) return ComparisonResult::Same; | |
| auto Nullability1 = getPointerNullState(*PointerVal1); | |
| auto Nullability2 = getPointerNullState(PointerVal2); | |
| // Ideally, we would be checking for equivalence of formulas, but that's | |
| // expensive, so we simply check for identity instead. | |
| return Nullability1.FromNullable == Nullability2.FromNullable && | |
| Nullability1.IsNull == Nullability2.IsNull | |
| ? ComparisonResult::Same | |
| : ComparisonResult::Different; | |
| } | |
| return ComparisonResult::Unknown; | |
| } | |
| // Returns the result of widening a nullability property. | |
| // `Prev` is the formula in the previous iteration, `Cur` is the formula in the | |
| // current iteration. | |
| // Returns `nullptr` (Top), if `Prev` is already Top or `Prev` and `Cur` cannot | |
| // be proven equivalent. Otherwise, (`Prev` and `Cur` are provably equivalent), | |
| // returns `Cur`. Returns `Cur`, if `Prev` is equivalent to `Cur`. Otherwise, | |
| // returns `Top`. | |
| static std::pair<const Formula* absl_nullable, LatticeEffect> | |
| widenNullabilityProperty(const Formula* absl_nullable Prev, | |
| const Environment& PrevEnv, | |
| const Formula* absl_nullable Cur, | |
| Environment& CurEnv) { | |
| if (Prev == Cur) return {Cur, LatticeEffect::Unchanged}; | |
| if (Prev == nullptr) return {nullptr, LatticeEffect::Unchanged}; | |
| if (Cur == nullptr) return {nullptr, LatticeEffect::Changed}; | |
| Arena& A = CurEnv.arena(); | |
| // Note that either of `PrevEnv` or `CurEnv` may be self-contradictory | |
| // (unsatisfiable). So, we're careful to check only that both are consistent | |
| // in their conclusions. We do not draw conclusions from them independently. | |
| // For example, if PrevEnv => Prev`, we do *not* conclude that | |
| // `PrevEnv => !Prev` is false, and use that to optimize the branches below. | |
| if ((PrevEnv.proves(*Prev) && CurEnv.proves(*Cur)) || | |
| (PrevEnv.proves(A.makeNot(*Prev)) && CurEnv.proves(A.makeNot(*Cur)))) | |
| return {Cur, LatticeEffect::Unchanged}; | |
| return {nullptr, LatticeEffect::Changed}; | |
| } | |
| std::optional<WidenResult> PointerNullabilityAnalysis::widen( | |
| QualType Type, Value& Prev, const Environment& PrevEnv, Value& Current, | |
| Environment& CurrentEnv) { | |
| auto* PrevPtr = dyn_cast<PointerValue>(&Prev); | |
| if (PrevPtr == nullptr) return std::nullopt; | |
| // Widen pointers (when different) to a pointer with a "top" storage location. | |
| auto& CurPtr = cast<PointerValue>(Current); | |
| DataflowAnalysisContext& DACtx = CurrentEnv.getDataflowAnalysisContext(); | |
| assert(&PrevEnv.getDataflowAnalysisContext() == &DACtx); | |
| bool LocUnchanged = &PrevPtr->getPointeeLoc() == &CurPtr.getPointeeLoc(); | |
| // If either `PrevPtr` or `CurPtr` lack null state, we consider the modeled | |
| // value to be outside the scope. TODO: we should consider all pointers in | |
| // scope and handle this case accordingly. We will widen the pointer location, | |
| // but (always) return a pointer value with no null state. | |
| if (!hasPointerNullState(*PrevPtr) || !hasPointerNullState(CurPtr)) | |
| return std::nullopt; | |
| auto [FromNullablePrev, NullPrev] = getPointerNullState(*PrevPtr); | |
| auto [FromNullableCur, NullCur] = getPointerNullState(CurPtr); | |
| auto [FromNullableWidened, FNWEffect] = widenNullabilityProperty( | |
| FromNullablePrev, PrevEnv, FromNullableCur, CurrentEnv); | |
| auto [NullWidened, NWEffect] = | |
| widenNullabilityProperty(NullPrev, PrevEnv, NullCur, CurrentEnv); | |
| if (LocUnchanged && FNWEffect == LatticeEffect::Unchanged && | |
| NWEffect == LatticeEffect::Unchanged) | |
| return WidenResult{&CurPtr, LatticeEffect::Unchanged}; | |
| // Widen the loc if needed. | |
| StorageLocation* WidenedLoc = | |
| LocUnchanged | |
| ? &CurPtr.getPointeeLoc() | |
| : &getTopStorageLocation(DACtx, CurPtr.getPointeeLoc().getType()); | |
| // Construct the new, widened value. | |
| auto& WidenedPtr = CurrentEnv.create<PointerValue>(*WidenedLoc); | |
| initPointerNullState(WidenedPtr, CurrentEnv.getDataflowAnalysisContext(), | |
| {FromNullableWidened, NullWidened}); | |
| LatticeEffect Effect = (WidenedLoc == &PrevPtr->getPointeeLoc() && | |
| FNWEffect == LatticeEffect::Unchanged && | |
| NWEffect == LatticeEffect::Unchanged) | |
| ? LatticeEffect::Unchanged | |
| : LatticeEffect::Changed; | |
| return WidenResult{&WidenedPtr, Effect}; | |
| } | |
| StorageLocation& PointerNullabilityAnalysis::getTopStorageLocation( | |
| DataflowAnalysisContext& DACtx, QualType Ty) { | |
| auto [It, Inserted] = TopStorageLocations.try_emplace(Ty, nullptr); | |
| if (Inserted) It->second = &DACtx.createStorageLocation(Ty); | |
| return *It->second; | |
| } | |
| } // namespace clang::tidy::nullability |