| // 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 | |
| #ifndef CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_ | |
| #define CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_ | |
| #include <optional> | |
| #include <utility> | |
| #include "absl/base/nullability.h" | |
| #include "nullability/pointer_nullability_lattice.h" | |
| #include "nullability/pragma.h" | |
| #include "nullability/type_nullability.h" | |
| #include "clang/AST/ASTContext.h" | |
| #include "clang/AST/Decl.h" | |
| #include "clang/AST/DeclBase.h" | |
| #include "clang/AST/Type.h" | |
| #include "clang/Analysis/FlowSensitive/Arena.h" | |
| #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" | |
| #include "clang/Analysis/FlowSensitive/Value.h" | |
| #include "llvm/ADT/FunctionExtras.h" | |
| namespace clang { | |
| namespace tidy { | |
| namespace nullability { | |
| /// Factory function for creating a solver implementation. | |
| using SolverFactory = std::function<std::unique_ptr<dataflow::Solver>()>; | |
| /// Analyzes constructs in the source code to collect nullability information | |
| /// about pointers at each program point. This analysis and the corresponding | |
| /// lattice were based on the gradual analysis in 'Estep, Sam, Jenna Wise, | |
| /// Jonathan Aldrich, Éric Tanter, Johannes Bader, and Joshua Sunshine. "Gradual | |
| /// Program Analysis for Null Pointers." In 35th European Conference on | |
| /// Object-Oriented Programming (ECOOP). 2021.' | |
| class PointerNullabilityAnalysis | |
| : public dataflow::DataflowAnalysis<PointerNullabilityAnalysis, | |
| PointerNullabilityLattice> { | |
| private: | |
| PointerNullabilityLattice::NonFlowSensitiveState NFS; | |
| public: | |
| explicit PointerNullabilityAnalysis(ASTContext &Context, | |
| dataflow::Environment &Env, | |
| const NullabilityPragmas &Pragmas); | |
| PointerNullabilityLattice initialElement() { | |
| return PointerNullabilityLattice(NFS); | |
| } | |
| // Instead of fixing D's nullability invariants from its annotations, | |
| // bind them to symbolic variables, and return those variables. | |
| // This is useful to infer the annotations that should be present on D. | |
| // | |
| // For example, given the following program: | |
| // void target(int* p) { | |
| // int* q = p; | |
| // *q; | |
| // } | |
| // | |
| // By default, p is treated as having unspecified nullability. | |
| // When we reach the dereference, our flow condition will say: | |
| // from_nullable = false | |
| // | |
| // However, if we bind p's nullability to a variable: | |
| // pn = assignNullabilityVariable(p) | |
| // Then the flow condition at dereference includes: | |
| // from_nullable = pn.Nullable | |
| // pn.Nonnull => !is_null | |
| // Logically connecting dereferenced values and possible invariants on p | |
| // allows us to infer p's proper annotations (here: Nonnull). | |
| // | |
| // For now, only the top-level nullability is assigned, and the returned | |
| // variables are only associated with direct reads of pointer values from D. | |
| // | |
| // The returned nullability is guaranteed to be symbolic. | |
| PointerTypeNullability assignNullabilityVariable( | |
| absl::Nonnull<const ValueDecl *> D, dataflow::Arena &); | |
| void assignNullabilityOverride( | |
| llvm::unique_function< | |
| std::optional<const PointerTypeNullability *>(const Decl &) const> | |
| Override) { | |
| NFS.ConcreteNullabilityOverride = std::move(Override); | |
| } | |
| void transfer(const CFGElement &Elt, PointerNullabilityLattice &Lattice, | |
| dataflow::Environment &Env); | |
| void join(QualType Type, const dataflow::Value &Val1, | |
| const dataflow::Environment &Env1, const dataflow::Value &Val2, | |
| const dataflow::Environment &Env2, dataflow::Value &MergedVal, | |
| dataflow::Environment &MergedEnv) override; | |
| dataflow::ComparisonResult compare( | |
| QualType Type, const dataflow::Value &Val1, | |
| const dataflow::Environment &Env1, const dataflow::Value &Val2, | |
| const dataflow::Environment &Env2) override; | |
| std::optional<dataflow::WidenResult> widen( | |
| QualType Type, dataflow::Value &Prev, | |
| const dataflow::Environment &PrevEnv, dataflow::Value &Current, | |
| dataflow::Environment &CurrentEnv) override; | |
| private: | |
| // Returns a storage location representing "top", i.e. a storage location of | |
| // type `Ty` about which nothing else is known. | |
| // Known limitation: We can't prevent a "top" storage location from being | |
| // associated with a value. This is somewhat strange but does not appear to | |
| // have any ill effects in practice. To disallow this, we may at some point | |
| // want to move the concept of "top" storage locations to the framework. | |
| dataflow::StorageLocation &getTopStorageLocation( | |
| dataflow::DataflowAnalysisContext &DACtx, QualType Ty); | |
| // Transfers (non-flow-sensitive) type properties through statements. | |
| dataflow::CFGMatchSwitch<dataflow::TransferState<PointerNullabilityLattice>> | |
| TypeTransferer; | |
| // Transfers (flow-sensitive) value properties through statements. | |
| dataflow::CFGMatchSwitch<dataflow::TransferState<PointerNullabilityLattice>> | |
| ValueTransferer; | |
| // Storage locations that represent "top" for each given type. | |
| llvm::DenseMap<QualType, dataflow::StorageLocation *> TopStorageLocations; | |
| }; | |
| } // namespace nullability | |
| } // namespace tidy | |
| } // namespace clang | |
| #endif // CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_ |