| // 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. |
| // We return true to keep the `MergedVal` produced by the framework. When |
| // the merged value appears in an expression, `tranferValue_Pointer` 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 |