-
Notifications
You must be signed in to change notification settings - Fork 48
/
type_nullability.h
310 lines (264 loc) · 13.4 KB
/
type_nullability.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
// 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