| // 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_ |