blob: 86b691b27b6c6865e448fe9deff0fffe0ec943c4 [file] [log] [blame]
// 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
// This file defines an extension of C++'s type system to cover nullability:
// Each pointer "slot" within a compound type is marked with a nullability kind.
//
// e.g. vector<int *> *
// Nullable^ ^Nonnull
// This type describes non-null pointers to vectors of possibly-null pointers.
//
// This model interacts with clang's nullability attributes: the type
// above can be written `vector<int * _Nullable> _Nonnull`.
// The two are not quite the same thing:
// - we may infer nullability or use defaults where no attributes are written
// - we generally pass nullability around as a separate data structure rather
// than materializing the sugared types
// - we do not use _Nullable_result
//
// This is separate from our model of pointer values as part of the Value graph
// (see pointer_nullability.h). The analysis makes use of both: generally type
// nullability is useful with compound types like templates and functions where
// the concrete pointer values are not visible to analysis.
#ifndef CRUBIT_NULLABILITY_TYPE_NULLABILITY_H_
#define CRUBIT_NULLABILITY_TYPE_NULLABILITY_H_
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/log/check.h"
#include "nullability/pragma.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Analysis/FlowSensitive/Arena.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/STLFunctionalExtras.h"
namespace clang::tidy::nullability {
namespace test {
/// Instantiating a global instance of this class turns on support for smart
/// pointers in the type_nullability library. This class is primarily intended
/// for use in tests.
class EnableSmartPointers {
public:
EnableSmartPointers();
};
} // namespace test
// Enables or disables support for smart pointers in the type_nullability
// library. (Disabled by default.)
// This should only be called once before the first analysis is started.
// If smart pointer support is not enabled, `isSupportedSmartPointerType()`
// always returns false, `countPointers()` does not count smart pointers, etc.
void enableSmartPointers(bool Enabled);
/// Returns whether support for smart pointers has been turned on.
bool smartPointersEnabled();
/// Is this exactly a pointer type that we track outer nullability for?
/// This unwraps sugar, i.e. it looks at the canonical type.
///
/// (For now, only regular `PointerType`s and smart pointers, in future we
/// should consider supporting pointer-to-member, ObjC pointers, etc).
bool isSupportedPointerType(QualType);
/// Is this exactly a raw (non-smart) pointer type that we track outer
/// nullability for?
/// This unwraps sugar, i.e. it looks at the canonical type.
bool isSupportedRawPointerType(QualType);
/// Is this exactly a smart pointer type that we track outer nullability for?
/// This unwraps sugar, i.e. it looks at the canonical type.
bool isSupportedSmartPointerType(QualType);
/// The Unknown annotation should only be applied directly to pointer types.
/// Typedefs are not valid, except for certain "transparent aliases" of smart
/// pointer templates (conceptually, those that just give them a new name).
/// When applied to other types, Unknown is ignored.
bool isUnknownValidOn(QualType);
/// Returns the raw pointer type underlying a smart pointer type.
/// If this isn't a supported smart pointer type, returns a null type.
/// If the smart pointer type is not instantiated, falls back to determining
/// the raw pointer type from the first template argument, rather than from the
/// `pointer` or `element_type` type aliases.
/// `BaseAccess` is the most restrictive base class access specifier to accept
/// when checking whether the type is derived from a smart pointer type. We
/// need to make a distinction here as follows:
/// - A type derived from a smart pointer type is only itself considerd to be a
/// supported smart pointer type if the inheritance is public.
/// - However, if the inheritance is protected or private, we still need to
/// model the underlying pointer field because the implementation may perform
/// a copy or move from a supported smart pointer type.
QualType underlyingRawPointerType(QualType,
AccessSpecifier BaseAccess = AS_public);
/// Describes the nullability contract of a pointer "slot" within a type.
///
/// This may be concrete: nullable/non-null/unknown nullability.
/// Or may be symbolic: this nullability is being inferred, and the presence
/// of a "nullable" annotation is bound to a SAT variable
class PointerTypeNullability {
// If concrete: NK is set, others are default.
// If symbolic: NK=Unspecified, Symbolic=true, Nonnull/Nullable are set.
NullabilityKind NK = NullabilityKind::Unspecified;
bool Symbolic = false;
dataflow::Atom Nonnull{0};
dataflow::Atom Nullable{0};
public:
PointerTypeNullability(NullabilityKind NK = NullabilityKind::Unspecified)
: NK(NK) {}
// Creates a symbolic nullability variable.
// A owns the underlying SAT variables nonnullAtom() and nullableAtom().
static PointerTypeNullability createSymbolic(dataflow::Arena &A);
// Returns the concrete nullability, or Unspecified if symbolic.
NullabilityKind concrete() const { return NK; }
// Returns symbolic nullability atoms.
// Requires: isSymbolic().
dataflow::Atom nonnullAtom() const {
CHECK(isSymbolic());
return Nonnull;
}
dataflow::Atom nullableAtom() const {
CHECK(isSymbolic());
return Nullable;
}
bool isSymbolic() const { return Symbolic; }
// Returns the condition under which this slot is non-null.
const dataflow::Formula &isNonnull(dataflow::Arena &A) const {
return Symbolic ? A.makeAtomRef(Nonnull)
: A.makeLiteral(NK == NullabilityKind::NonNull);
}
// Returns the condition under which this slot is nullable.
const dataflow::Formula &isNullable(dataflow::Arena &A) const {
return Symbolic ? A.makeAtomRef(Nullable)
: A.makeLiteral(NK == NullabilityKind::Nullable);
}
friend bool operator==(const PointerTypeNullability &L,
const PointerTypeNullability &R) {
return std::tie(L.NK, L.Nonnull, L.Nullable) ==
std::tie(R.NK, R.Nonnull, R.Nullable);
}
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &,
const PointerTypeNullability &);
};
/// Externalized nullability of a clang::Type.
///
/// Each pointer type nested inside is mapped to a nullability.
/// This describes the nullability for pointer values used with the type.
///
/// For example, in: pair<int*, double*>
/// We can map: int* => Unspecified, double* => Nonnull
/// And given such a pair p, p.second is considered Nonnull.
///
/// We could represent this as the Type pair<int *, double *_Nonnull>.
/// However clang frequently drops type sugar such as _Nonnull, and Types are
/// inconvenient to manipulate. We pass nullability explicitly instead.
///
/// The concrete representation is currently the nullability of each nested
/// PointerType encountered in a preorder traversal of the canonical type.
using TypeNullability = std::vector<PointerTypeNullability>;
/// Returns a human-readable debug representation of a nullability vector.
std::string nullabilityToString(const TypeNullability &Nullability);
/// A function that may provide enhanced nullability information for a
/// substituted template parameter (which has no sugar of its own).
using GetTypeParamNullability =
std::optional<TypeNullability>(const SubstTemplateTypeParmType *ST);
/// Describes how we should interpret unannotated pointer types (like `int*`).
/// Typically these are treated as Unknown, and this behavior can be overridden
/// by per-file pragmas.
struct TypeNullabilityDefaults {
// TODO(sammccall): remove this legacy constructor that ignores pragmas
TypeNullabilityDefaults() : Ctx(nullptr), FileNullability(nullptr) {}
TypeNullabilityDefaults(ASTContext &Ctx, const NullabilityPragmas &Pragmas)
: Ctx(&Ctx), FileNullability(&Pragmas) {}
// Get the effective default nullability for a particular file.
NullabilityKind get(FileID) const;
// The AST context is needed to resolve the associated file in some cases.
// TODO(sammccall): this should always be provided, clean up callers.
absl::Nullable<ASTContext *> Ctx;
// The nullability of pointer types in this translation unit, where no
// nullability annotations or pragmas apply.
NullabilityKind DefaultNullability = NullabilityKind::Unspecified;
// Files where per-file pragmas have changed the default nullability.
// TODO(sammccall)): this should always be provided, clean up callers.
absl::Nullable<const NullabilityPragmas *> FileNullability;
};
/// Traverse over a type to get its nullability. For example, if T is the type
/// Struct3Arg<int * _Nonnull, int, pair<int * _Nullable, int *>> * _Nonnull,
/// the resulting nullability annotations will be {_Nonnull, _Nonnull,
/// _Nullable, _Unknown}. Note that non-pointer elements (e.g., the second
/// argument of Struct3Arg) do not get a nullability annotation.
/// Extract nullability of a clang type written somewhere in the code.
///
/// The file where it is written affects the interpretation of unannotated
/// pointer types.
/// Where possible, prefer the foolproof TypeLoc or Decl overloads.
TypeNullability getTypeNullability(
QualType, FileID, const TypeNullabilityDefaults &,
llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr);
TypeNullability getTypeNullability(
TypeLoc, const TypeNullabilityDefaults &,
llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr);
TypeNullability getTypeNullability(
const ValueDecl &, const TypeNullabilityDefaults &,
llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr);
TypeNullability getTypeNullability(
const TypeDecl &, const TypeNullabilityDefaults &,
llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr);
/// Returns the `FileID` of the file that governs the nullability of `D`.
FileID getGoverningFile(absl::Nullable<const Decl *> D);
/// Legacy getTypeNullability variant; treats unannotated pointers as Unknown.
/// Per-file pragmas are ignored.
/// TODO(sammccall): clean up all callers and remove this.
inline TypeNullability getNullabilityAnnotationsFromType(
QualType T,
llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr) {
TypeNullabilityDefaults LegacyDefaults;
return getTypeNullability(T, FileID(), LegacyDefaults, SubstituteTypeParam);
}
/// Prints QualType's underlying canonical type, annotated with nullability.
/// See rebuildWithNullability().
std::string printWithNullability(QualType, const TypeNullability &,
ASTContext &);
/// Returns an equivalent type annotated with the provided nullability.
/// Any existing sugar (including nullability) is discarded.
/// Symbolic nullability is not annotated.
/// Smart pointers are not annotated.
/// rebuildWithNullability(int *, {Nullable}) ==> int * _Nullable.
QualType rebuildWithNullability(QualType, const TypeNullability &,
ASTContext &);
/// Computes the number of pointer slots within a type.
/// Each of these could conceptually be nullable, so this is the length of
/// the nullability vector computed by getTypeNullability().
unsigned countPointersInType(QualType T);
unsigned countPointersInType(absl::Nonnull<const Expr *> E);
unsigned countPointersInType(const TemplateArgument &TA);
unsigned countPointersInType(absl::Nonnull<const DeclContext *> DC);
/// Returns the type of an expression for the purposes of nullability.
/// This handles wrinkles in the type system like BoundMember.
QualType exprType(absl::Nonnull<const Expr *> E);
TypeNullability unspecifiedNullability(absl::Nonnull<const Expr *> E);
// Type and optionally location and nullability information for a single pointer
// type seen within a potentially more complex type. `Slot` indicates the
// position of this type within the nullability vector (see TypeNullability
// documentation) of the type within which `Type` was seen.
struct TypeNullabilityLoc {
unsigned Slot = 0;
const Type *Type = nullptr;
std::optional<TypeLoc> Loc;
// If either explicitly annotated (with any supported syntax) or subject to a
// file-level pragma, this is the existing nullability annotation governing
// the TypeLoc. Otherwise, this is empty and
// TypeNullabilityDefaults.DefaultNullability should be used if the
// nullability is needed.
std::optional<NullabilityKind> ExistingAnnotation;
};
// Assembles TypeNullabilityLocs for each pointer type in the canonical type for
// `Loc`, corresponding directly with the TypeNullability that would be
// assembled for `Loc`'s type, except that the ExistingAnnotations in the
// results do not fall back to Defaults.DefaultNullability and instead report an
// empty ExistingAnnotation in those cases.
std::vector<TypeNullabilityLoc> getTypeNullabilityLocs(
TypeLoc Loc, const TypeNullabilityDefaults &Defaults);
} // namespace clang::tidy::nullability
#endif