def Nullability : DiagGroup<"nullability">;
def NullabilityDeclSpec : DiagGroup<"nullability-declspec">;
def NullableToNonNullConversion : DiagGroup<"nullable-to-nonnull-conversion">;
+def NullabilityCompleteness : DiagGroup<"nullability-completeness">;
def NullArithmetic : DiagGroup<"null-arithmetic">;
def NullCharacter : DiagGroup<"null-character">;
def NullDereference : DiagGroup<"null-dereference">;
"synthesized setter %0 for null_resettable property %1 does not handle nil">,
InGroup<Nullability>;
+def warn_nullability_missing : Warning<
+ "%select{pointer|block pointer|member pointer}0 is missing a nullability "
+ "type specifier (__nonnull, __nullable, or __null_unspecified)">,
+ InGroup<NullabilityCompleteness>;
+
}
} // end of sema component.
AS_Declspec,
/// __ptr16, alignas(...), etc.
AS_Keyword,
+ /// Context-sensitive version of a keyword attribute.
+ AS_ContextSensitiveKeyword,
/// #pragma ...
AS_Pragma
};
/// True if this has a ParsedType
unsigned HasParsedType : 1;
- /// True when this keyword attribute is a context-sensitive keyword.
- unsigned IsContextSensitiveKeyword : 1;
-
unsigned AttrKind : 8;
/// \brief The location of the 'unavailable' keyword in an
ScopeLoc(scopeLoc), EllipsisLoc(ellipsisLoc), NumArgs(numArgs),
SyntaxUsed(syntaxUsed), Invalid(false), UsedAsTypeAttr(false),
IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false),
- HasParsedType(false), IsContextSensitiveKeyword(false),
- NextInPosition(nullptr), NextInPool(nullptr) {
+ HasParsedType(false), NextInPosition(nullptr), NextInPool(nullptr) {
if (numArgs) memcpy(getArgsBuffer(), args, numArgs * sizeof(ArgsUnion));
AttrKind = getKind(getName(), getScopeName(), syntaxUsed);
}
ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(1), SyntaxUsed(syntaxUsed),
Invalid(false), UsedAsTypeAttr(false), IsAvailability(true),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
- IsContextSensitiveKeyword(false), UnavailableLoc(unavailable),
- MessageExpr(messageExpr), NextInPosition(nullptr), NextInPool(nullptr) {
+ UnavailableLoc(unavailable), MessageExpr(messageExpr),
+ NextInPosition(nullptr), NextInPool(nullptr) {
ArgsUnion PVal(Parm);
memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
new (&getAvailabilitySlot(IntroducedSlot)) AvailabilityChange(introduced);
ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(3), SyntaxUsed(syntaxUsed),
Invalid(false), UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
- IsContextSensitiveKeyword(false), NextInPosition(nullptr),
- NextInPool(nullptr) {
+ NextInPosition(nullptr), NextInPool(nullptr) {
ArgsVector Args;
Args.push_back(Parm1);
Args.push_back(Parm2);
ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(1), SyntaxUsed(syntaxUsed),
Invalid(false), UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(true), IsProperty(false), HasParsedType(false),
- IsContextSensitiveKeyword(false), NextInPosition(nullptr),
- NextInPool(nullptr) {
+ NextInPosition(nullptr), NextInPool(nullptr) {
ArgsUnion PVal(ArgKind);
memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
TypeTagForDatatypeData &ExtraData = getTypeTagForDatatypeDataSlot();
ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(0), SyntaxUsed(syntaxUsed),
Invalid(false), UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(true),
- IsContextSensitiveKeyword(false), NextInPosition(nullptr),
- NextInPool(nullptr) {
+ NextInPosition(nullptr), NextInPool(nullptr) {
new (&getTypeBuffer()) ParsedType(typeArg);
AttrKind = getKind(getName(), getScopeName(), syntaxUsed);
}
ScopeLoc(scopeLoc), EllipsisLoc(), NumArgs(0), SyntaxUsed(syntaxUsed),
Invalid(false), UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(false), IsProperty(true), HasParsedType(false),
- IsContextSensitiveKeyword(false), NextInPosition(nullptr),
- NextInPool(nullptr) {
+ NextInPosition(nullptr), NextInPool(nullptr) {
new (&getPropertyDataBuffer()) PropertyData(getterId, setterId);
AttrKind = getKind(getName(), getScopeName(), syntaxUsed);
}
bool isAlignasAttribute() const {
// FIXME: Use a better mechanism to determine this.
- return getKind() == AT_Aligned && SyntaxUsed == AS_Keyword;
+ return getKind() == AT_Aligned && isKeywordAttribute();
}
bool isDeclspecAttribute() const { return SyntaxUsed == AS_Declspec; }
bool isCXX11Attribute() const {
return SyntaxUsed == AS_CXX11 || isAlignasAttribute();
}
- bool isKeywordAttribute() const { return SyntaxUsed == AS_Keyword; }
-
- bool isContextSensitiveKeywordAttribute() const {
- return IsContextSensitiveKeyword;
+ bool isKeywordAttribute() const {
+ return SyntaxUsed == AS_Keyword || SyntaxUsed == AS_ContextSensitiveKeyword;
}
- void setContextSensitiveKeywordAttribute() {
- assert(SyntaxUsed == AS_Keyword);
- IsContextSensitiveKeyword = true;
+ bool isContextSensitiveKeywordAttribute() const {
+ return SyntaxUsed == AS_ContextSensitiveKeyword;
}
bool isInvalid() const { return Invalid; }
typedef std::pair<llvm::PointerUnion<const TemplateTypeParmType*, NamedDecl*>,
SourceLocation> UnexpandedParameterPack;
+/// Describes whether we've seen any nullability information for the given
+/// file.
+struct FileNullability {
+ /// The first pointer declarator (of any pointer kind) in the file that does
+ /// not have a corresponding nullability annotation.
+ SourceLocation PointerLoc;
+
+ /// Which kind of pointer declarator we saw.
+ uint8_t PointerKind;
+
+ /// Whether we saw any type nullability annotations in the given file.
+ bool SawTypeNullability = false;
+};
+
+/// A mapping from file IDs to a record of whether we've seen nullability
+/// information in that file.
+class FileNullabilityMap {
+ /// A mapping from file IDs to the nullability information for each file ID.
+ llvm::DenseMap<FileID, FileNullability> Map;
+
+ /// A single-element cache based on the file ID.
+ struct {
+ FileID File;
+ FileNullability Nullability;
+ } Cache;
+
+public:
+ FileNullability &operator[](FileID file) {
+ // Check the single-element cache.
+ if (file == Cache.File)
+ return Cache.Nullability;
+
+ // It's not in the single-element cache; flush the cache if we have one.
+ if (!Cache.File.isInvalid()) {
+ Map[Cache.File] = Cache.Nullability;
+ }
+
+ // Pull this entry into the cache.
+ Cache.File = file;
+ Cache.Nullability = Map[file];
+ return Cache.Nullability;
+ }
+};
+
/// Sema - This implements semantic analysis and AST building for C.
class Sema {
Sema(const Sema &) = delete;
PragmaStack<StringLiteral *> ConstSegStack;
PragmaStack<StringLiteral *> CodeSegStack;
+ /// A mapping that describes the nullability we've seen in each header file.
+ FileNullabilityMap NullabilityMap;
+
/// Last section used with #pragma init_seg.
StringLiteral *CurInitSeg;
SourceLocation CurInitSegLoc;
bool &addedToDeclSpec) {
// Create the attribute.
auto getNullabilityAttr = [&]() -> AttributeList * {
- auto attr = D.getAttributePool().create(
- P.getNullabilityKeyword(nullability),
- SourceRange(nullabilityLoc),
- nullptr, SourceLocation(),
- nullptr, 0,
- AttributeList::AS_Keyword);
- attr->setContextSensitiveKeywordAttribute();
- return attr;
+ return D.getAttributePool().create(
+ P.getNullabilityKeyword(nullability),
+ SourceRange(nullabilityLoc),
+ nullptr, SourceLocation(),
+ nullptr, 0,
+ AttributeList::AS_ContextSensitiveKeyword);
};
if (D.getNumTypeObjects() > 0) {
}
}
+static FileID getNullabilityCompletenessCheckFileID(Sema &S,
+ SourceLocation loc) {
+ // If we're anywhere in a function, method, or closure context, don't perform
+ // completeness checks.
+ for (DeclContext *ctx = S.CurContext; ctx; ctx = ctx->getParent()) {
+ if (ctx->isFunctionOrMethod())
+ return FileID();
+
+ if (ctx->isFileContext())
+ break;
+ }
+
+ // We only care about the expansion location.
+ loc = S.SourceMgr.getExpansionLoc(loc);
+ FileID file = S.SourceMgr.getFileID(loc);
+ if (file.isInvalid())
+ return FileID();
+
+ // Retrieve file information.
+ bool invalid = false;
+ const SrcMgr::SLocEntry &sloc = S.SourceMgr.getSLocEntry(file, &invalid);
+ if (invalid || !sloc.isFile())
+ return FileID();
+
+ // We don't want to perform completeness checks on the main file or in
+ // system headers.
+ const SrcMgr::FileInfo &fileInfo = sloc.getFile();
+ if (fileInfo.getIncludeLoc().isInvalid() ||
+ fileInfo.getFileCharacteristic() != SrcMgr::C_User)
+ return FileID();
+
+ return file;
+}
+
+/// Check for consistent use of nullability.
+static void checkNullabilityConsistency(TypeProcessingState &state,
+ SimplePointerKind pointerKind,
+ SourceLocation pointerLoc) {
+ Sema &S = state.getSema();
+
+ // Determine which file we're performing consistency checking for.
+ FileID file = getNullabilityCompletenessCheckFileID(S, pointerLoc);
+ if (file.isInvalid())
+ return;
+
+ // If we haven't seen any type nullability in this file, we won't warn now
+ // about anything.
+ FileNullability &fileNullability = S.NullabilityMap[file];
+ if (!fileNullability.SawTypeNullability) {
+ // If this is the first pointer declarator in the file, record it.
+ if (fileNullability.PointerLoc.isInvalid() &&
+ !S.Context.getDiagnostics().isIgnored(diag::warn_nullability_missing,
+ pointerLoc)) {
+ fileNullability.PointerLoc = pointerLoc;
+ fileNullability.PointerKind = static_cast<unsigned>(pointerKind);
+ }
+
+ return;
+ }
+
+ // Complain about missing nullability.
+ S.Diag(pointerLoc, diag::warn_nullability_missing)
+ << static_cast<unsigned>(pointerKind);
+}
+
static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
QualType declSpecType,
TypeSourceInfo *TInfo) {
!state.getDeclarator().isObjCWeakProperty() &&
!S.deduceWeakPropertyFromType(T)) {
inAssumeNonNullRegion = true;
+ // Determine which file we saw the assume-nonnull region in.
+ FileID file = getNullabilityCompletenessCheckFileID(
+ S, S.PP.getPragmaAssumeNonNullLoc());
+ if (!file.isInvalid()) {
+ FileNullability &fileNullability = S.NullabilityMap[file];
+
+ // If we haven't seen any type nullability before, now we have.
+ if (!fileNullability.SawTypeNullability) {
+ if (fileNullability.PointerLoc.isValid()) {
+ S.Diag(fileNullability.PointerLoc, diag::warn_nullability_missing)
+ << fileNullability.PointerKind;
+ }
+
+ fileNullability.SawTypeNullability = true;
+ }
+ }
}
// Whether to complain about missing nullability specifiers or not.
// inner pointers.
complainAboutMissingNullability = CAMN_InnerPointers;
- if (T->canHaveNullability()) {
+ if (T->canHaveNullability() && !T->getNullability(S.Context)) {
++NumPointersRemaining;
}
// If we're supposed to infer nullability, do so now.
if (inferNullability) {
+ auto syntax = inferNullabilityCS ? AttributeList::AS_ContextSensitiveKeyword
+ : AttributeList::AS_Keyword;
AttributeList *nullabilityAttr = state.getDeclarator().getAttributePool()
.create(
S.getNullabilityKeyword(
*inferNullability),
SourceRange(pointerLoc),
nullptr, SourceLocation(),
- nullptr, 0,
- AttributeList::AS_Keyword);
- if (inferNullabilityCS)
- nullabilityAttr->setContextSensitiveKeywordAttribute();
+ nullptr, 0, syntax);
spliceAttrIntoList(*nullabilityAttr, attrs);
return nullabilityAttr;
}
+ // If we're supposed to complain about missing nullability, do so
+ // now if it's truly missing.
+ switch (complainAboutMissingNullability) {
+ case CAMN_No:
+ break;
+
+ case CAMN_InnerPointers:
+ if (NumPointersRemaining == 0)
+ break;
+ // Fallthrough.
+
+ case CAMN_Yes:
+ checkNullabilityConsistency(state, pointerKind, pointerLoc);
+ }
+
return nullptr;
};
// If the type itself could have nullability but does not, infer pointer
- // nullability.
- if (T->canHaveNullability() && S.ActiveTemplateInstantiations.empty()) {
+ // nullability and perform consistency checking.
+ if (T->canHaveNullability() && S.ActiveTemplateInstantiations.empty() &&
+ !T->getNullability(S.Context)) {
SimplePointerKind pointerKind = SimplePointerKind::Pointer;
if (T->isBlockPointerType())
pointerKind = SimplePointerKind::BlockPointer;
return false;
}
-bool Sema::checkNullabilityTypeSpecifier(QualType &type,
+bool Sema::checkNullabilityTypeSpecifier(QualType &type,
NullabilityKind nullability,
SourceLocation nullabilityLoc,
bool isContextSensitive) {
+ // We saw a nullability type specifier. If this is the first one for
+ // this file, note that.
+ FileID file = getNullabilityCompletenessCheckFileID(*this, nullabilityLoc);
+ if (!file.isInvalid()) {
+ FileNullability &fileNullability = NullabilityMap[file];
+ if (!fileNullability.SawTypeNullability) {
+ // If we have already seen a pointer declarator without a nullability
+ // annotation, complain about it.
+ if (fileNullability.PointerLoc.isValid()) {
+ Diag(fileNullability.PointerLoc, diag::warn_nullability_missing)
+ << fileNullability.PointerKind;
+ }
+
+ fileNullability.SawTypeNullability = true;
+ }
+ }
+
// Check for existing nullability attributes on the type.
QualType desugared = type;
while (auto attributed = dyn_cast<AttributedType>(desugared.getTypePtr())) {
--- /dev/null
+void f1(int *ptr); // expected-warning{{pointer is missing a nullability type specifier}}
+
+void f2(int * __nonnull);
+
+#include "nullability-consistency-2.h"
+
+void f3(int *ptr) { // expected-warning{{pointer is missing a nullability type specifier}}
+ int *other = ptr; // shouldn't warn
+}
+
+class X {
+ void mf(int *ptr); // expected-warning{{pointer is missing a nullability type specifier}}
+ int X:: *memptr; // expected-warning{{member pointer is missing a nullability type specifier}}
+};
+
+
+
--- /dev/null
+void g1(int * __nonnull);
+
+void g2(int (^block)(int, int)); // expected-warning{{block pointer is missing a nullability type specifier}}
+
+void g3(const
+ id // expected-warning{{missing a nullability type specifier}}
+ volatile
+ * // expected-warning{{missing a nullability type specifier}}
+ );
+
+@interface SomeClass
+@property (retain,nonnull) id property1;
+@property (retain,nullable) SomeClass *property2;
+- (nullable SomeClass *)method1;
+- (void)method2:(nonnull SomeClass *)param;
+@end
--- /dev/null
+void double_declarator1(int *__nonnull *); // expected-warning{{pointer is missing a nullability type specifier (__nonnull, __nullable, or __null_unspecified)}}
--- /dev/null
+void double_declarator1(int * * __nonnull); // expected-warning{{pointer is missing a nullability type specifier (__nonnull, __nullable, or __null_unspecified)}}
--- /dev/null
+#define SUPPRESS_NULLABILITY_WARNING(Type) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wnullability-completeness\"") \
+ Type \
+ _Pragma("clang diagnostic pop")
+
+void suppress1(SUPPRESS_NULLABILITY_WARNING(int *) ptr); // no warning
+
+void shouldwarn5(int *ptr); //expected-warning{{missing a nullability type specifier}}
+
+void trigger5(int * __nonnull);
+
+void suppress2(SUPPRESS_NULLABILITY_WARNING(int *) ptr); // no warning
+
--- /dev/null
+int *ptr; // expected-warning {{missing a nullability type specifier}}
+
+#pragma clang assume_nonnull begin
+
+extern void **blah; // expected-warning 2{{missing a nullability type specifier}}
+
+#pragma clang assume_nonnull end
+
--- /dev/null
+#ifndef SOMEKIT_H
+#define SOMEKIT_H
+
+__attribute__((objc_root_class))
+#ifndef NS_ASSUME_NONNULL_BEGIN
+#if __has_feature(assume_nonnull)
+#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
+#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
+#else
+#define NS_ASSUME_NONNULL_BEGIN
+#define NS_ASSUME_NONNULL_END
+#endif
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface A
+-(null_unspecified A*)transform:(null_unspecified A*)input __attribute__((unavailable("anything but this")));
+-(A*)transform:(A*)input integer:(int)integer;
+
+@property (null_unspecified, nonatomic, readonly, retain) A* someA;
+@property (null_unspecified, nonatomic, retain) A* someOtherA;
+
+@property (nonatomic) int intValue __attribute__((unavailable("wouldn't work anyway")));
+@end
+
+NS_ASSUME_NONNULL_END
+
+
+__attribute__((unavailable("just don't")))
+@interface B : A
+@end
+
+@interface C : A
+- (instancetype)init; // expected-warning{{pointer is missing a nullability type specifier}}
+- (instancetype)initWithA:( A*)a __attribute__((objc_designated_initializer)); // expected-warning 2{{pointer is missing a nullability type specifier}}
+@end
+
+#endif
+
--- /dev/null
+typedef int* __nonnull mynonnull;
+
+__attribute__((objc_root_class))
+@interface typedefClass
+- (void) func1:(mynonnull)i;
+@end
+
+void func2(mynonnull i);
+
+void func3(int *); // expected-warning{{pointer is missing a nullability type specifier}}
+
struct X { };
-void f1(int *x);
+void f1(int *x); // expected-warning{{pointer is missing a nullability type specifier}}
typedef struct __attribute__((objc_bridge(NSError))) __CFError *CFErrorRef;
typedef NSError *NSErrorPtr;
int * __null_unspecified f15(void);
A * __null_unspecified f16(void);
void f17(CFErrorRef *error); // expected-note{{no known conversion from 'A * __nonnull' to 'CFErrorRef __nullable * __nullable' (aka '__CFError **') for 1st argument}}
-void f18(A **);
-void f19(CFErrorRefPtr error);
+void f18(A **); // expected-warning 2{{pointer is missing a nullability type specifier}}
+void f19(CFErrorRefPtr error); // expected-warning{{pointer is missing a nullability type specifier}}
void g1(int (^)(int, int));
-void g2(int (^ *bp)(int, int));
-void g3(block_ptr *bp);
+void g2(int (^ *bp)(int, int)); // expected-warning{{block pointer is missing a nullability type specifier}}
+// expected-warning@-1{{pointer is missing a nullability type specifier}}
+void g3(block_ptr *bp); // expected-warning{{block pointer is missing a nullability type specifier}}
+// expected-warning@-1{{pointer is missing a nullability type specifier}}
void g4(int (*fp)(int, int));
-void g5(int (**fp)(int, int));
+void g5(int (**fp)(int, int)); // expected-warning 2{{pointer is missing a nullability type specifier}}
@interface A(Pragmas1)
+ (instancetype)aWithA:(A *)a;
- (void)method3:(NSError **)error; // expected-note{{passing argument to parameter 'error' here}}
- (void)method4:(NSErrorPtr *)error; // expected-note{{passing argument to parameter 'error' here}}
- (void)method5:(NSErrorPtrPtr)error;
+// expected-warning@-1{{pointer is missing a nullability type specifier}}
@property A *aProp;
-@property NSError **anError;
+@property NSError **anError; // expected-warning 2{{pointer is missing a nullability type specifier}}
@end
int *global_int_ptr;
// typedefs not inferred __nonnull
typedef int *int_ptr_2;
-typedef int *
+typedef int * // expected-warning{{pointer is missing a nullability type specifier}}
*int_ptr_ptr;
static inline void f30(void) {
#pragma clang assume_nonnull end
-void f20(A *a);
-void f21(int_ptr x);
-void f22(A_ptr y);
+void f20(A *a); // expected-warning{{pointer is missing a nullability type specifier}}
+void f21(int_ptr x); // expected-warning{{pointer is missing a nullability type specifier}}
+void f22(A_ptr y); // expected-warning{{pointer is missing a nullability type specifier}}
void f23(int_ptr __nullable x);
void f24(A_ptr __nullable y);
-void f25(int_ptr_2 x);
+void f25(int_ptr_2 x); // expected-warning{{pointer is missing a nullability type specifier}}
@interface A(OutsidePragmas1)
-+ (instancetype)aWithInt:(int)value;
++ (instancetype)aWithInt:(int)value; // expected-warning{{pointer is missing a nullability type specifier}}
@end
--- /dev/null
+// RUN: %clang_cc1 -fsyntax-only -fblocks -I %S/Inputs %s -verify
+
+#include "nullability-consistency-1.h"
+#include "nullability-consistency-3.h"
+#include "nullability-consistency-4.h"
+#include "nullability-consistency-5.h"
+#include "nullability-consistency-5.h"
+#include "nullability-consistency-6.h"
+#include "nullability-consistency-7.h"
+#include "nullability-consistency-8.h"
+
+void h1(int *ptr) { } // don't warn
+
+void h2(int * __nonnull) { }
StringMatcher("Name", Declspec, OS).Emit();
OS << " } else if (AttributeList::AS_CXX11 == Syntax) {\n";
StringMatcher("Name", CXX11, OS).Emit();
- OS << " } else if (AttributeList::AS_Keyword == Syntax) {\n";
+ OS << " } else if (AttributeList::AS_Keyword == Syntax || ";
+ OS << "AttributeList::AS_ContextSensitiveKeyword == Syntax) {\n";
StringMatcher("Name", Keywords, OS).Emit();
OS << " } else if (AttributeList::AS_Pragma == Syntax) {\n";
StringMatcher("Name", Pragma, OS).Emit();