diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -745,6 +745,40 @@ EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1)); } +TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) { + auto TU = TestTU::withCode(R"cpp( +#pragma clang assume_nonnull begin +void foo(int *x); +#pragma clang assume_nonnull end +)cpp"); + auto AST = TU.build(); + EXPECT_THAT(*AST.getDiagnostics(), IsEmpty()); + const auto *X = cast(findDecl(AST, "foo")).getParamDecl(0); + ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) == + NullabilityKind::NonNull); +} + +TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) { + Annotations Header(R"cpp( +#pragma clang assume_nonnull begin // error-ok +void foo(int *X); +)cpp"); + auto TU = TestTU::withCode(R"cpp( +#include "foo.h" // unterminated assume_nonnull should not affect bar. +void bar(int *Y); +)cpp"); + TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}}; + auto AST = TU.build(); + EXPECT_THAT(*AST.getDiagnostics(), + ElementsAre(diagName("pp_eof_in_assume_nonnull"))); + const auto *X = cast(findDecl(AST, "foo")).getParamDecl(0); + ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) == + NullabilityKind::NonNull); + const auto *Y = cast(findDecl(AST, "bar")).getParamDecl(0); + ASSERT_FALSE( + Y->getOriginalType()->getNullability(X->getASTContext()).hasValue()); +} + TEST(DiagnosticsTest, InsideMacros) { Annotations Test(R"cpp( #define TEN 10 diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -409,6 +409,14 @@ /// \#pragma clang assume_nonnull begin. SourceLocation PragmaAssumeNonNullLoc; + /// Set only for preambles which end with an active + /// \#pragma clang assume_nonnull begin. + /// + /// When the preamble is loaded into the main file, + /// `PragmaAssumeNonNullLoc` will be set to this to + /// replay the unterminated assume_nonnull. + SourceLocation PreambleRecordedPragmaAssumeNonNullLoc; + /// True if we hit the code-completion point. bool CodeCompletionReached = false; @@ -1762,6 +1770,21 @@ PragmaAssumeNonNullLoc = Loc; } + /// Get the location of the recorded unterminated \#pragma clang + /// assume_nonnull begin in the preamble, if one exists. + /// + /// Returns an invalid location if the premable did not end with + /// such a pragma active or if there is no recorded preamble. + SourceLocation getPreambleRecordedPragmaAssumeNonNullLoc() const { + return PreambleRecordedPragmaAssumeNonNullLoc; + } + + /// Record the location of the unterminated \#pragma clang + /// assume_nonnull begin in the preamble. + void setPreambleRecordedPragmaAssumeNonNullLoc(SourceLocation Loc) { + PreambleRecordedPragmaAssumeNonNullLoc = Loc; + } + /// Set the directory in which the main file should be considered /// to have been found, if it is not a real file. void setMainFileDir(const DirectoryEntry *Dir) { diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h --- a/clang/include/clang/Lex/PreprocessorOptions.h +++ b/clang/include/clang/Lex/PreprocessorOptions.h @@ -128,7 +128,8 @@ /// /// When the lexer is done, one of the things that need to be preserved is the /// conditional #if stack, so the ASTWriter/ASTReader can save/restore it when - /// processing the rest of the file. + /// processing the rest of the file. Similarly, we track an unterminated + /// #pragma assume_nonnull. bool GeneratePreamble = false; /// Whether to write comment locations into the PCH when building it. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -698,6 +698,10 @@ /// Record code for included files. PP_INCLUDED_FILES = 66, + + /// Record code for an unterminated \#pragma clang assume_nonnull begin + /// recorded in a preamble. + PP_ASSUME_NONNULL_LOC = 67, }; /// Record types used within a source manager block. diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -427,13 +427,19 @@ PragmaARCCFCodeAuditedInfo = {nullptr, SourceLocation()}; } - // Complain about reaching a true EOF within assume_nonnull. + // Complain about reaching a true EOF. + // // We don't want to complain about reaching the end of a macro // instantiation or a _Pragma. if (PragmaAssumeNonNullLoc.isValid() && !isEndOfMacro && !(CurLexer && CurLexer->Is_PragmaLexer)) { - Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull); - + // If we're at the end of generating a preamble, we should record the + // unterminated \#pragma clang assume_nonnull so we can restore it later + // when the preamble is loaded into the main file. + if (isRecordingPreamble() && isInPrimaryFile()) + PreambleRecordedPragmaAssumeNonNullLoc = PragmaAssumeNonNullLoc; + else + Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull); // Recover by leaving immediately. PragmaAssumeNonNullLoc = SourceLocation(); } @@ -514,10 +520,14 @@ PPCallbacks::ExitFile, FileType, ExitedFID); } - // Restore conditional stack from the preamble right after exiting from the - // predefines file. - if (ExitedFromPredefinesFile) + // Restore conditional stack as well as the recorded + // \#pragma clang assume_nonnull from the preamble right after exiting + // from the predefines file. + if (ExitedFromPredefinesFile) { replayPreambleConditionalStack(); + if (PreambleRecordedPragmaAssumeNonNullLoc.isValid()) + PragmaAssumeNonNullLoc = PreambleRecordedPragmaAssumeNonNullLoc; + } if (!isEndOfMacro && CurPPLexer && FoundPCHThroughHeader && (isInPrimaryFile() || diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3109,6 +3109,7 @@ case IDENTIFIER_OFFSET: case INTERESTING_IDENTIFIERS: case STATISTICS: + case PP_ASSUME_NONNULL_LOC: case PP_CONDITIONAL_STACK: case PP_COUNTER_VALUE: case SOURCE_LOCATION_OFFSETS: @@ -3371,6 +3372,14 @@ } break; + case PP_ASSUME_NONNULL_LOC: { + unsigned Idx = 0; + if (!Record.empty()) + PP.setPreambleRecordedPragmaAssumeNonNullLoc( + ReadSourceLocation(F, Record, Idx)); + break; + } + case PP_CONDITIONAL_STACK: if (!Record.empty()) { unsigned Idx = 0, End = Record.size() - 1; diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -872,6 +872,7 @@ RECORD(PP_CONDITIONAL_STACK); RECORD(DECLS_TO_CHECK_FOR_DEFERRED_DIAGS); RECORD(PP_INCLUDED_FILES); + RECORD(PP_ASSUME_NONNULL_LOC); // SourceManager Block. BLOCK(SOURCE_MANAGER_BLOCK); @@ -2299,6 +2300,17 @@ Stream.EmitRecord(PP_COUNTER_VALUE, Record); } + // If we have a recorded #pragma assume_nonnull, remember it so it can be + // replayed when the preamble terminates into the main file. + SourceLocation AssumeNonNullLoc = + PP.getPreambleRecordedPragmaAssumeNonNullLoc(); + if (AssumeNonNullLoc.isValid()) { + assert(PP.isRecordingPreamble()); + AddSourceLocation(AssumeNonNullLoc, Record); + Stream.EmitRecord(PP_ASSUME_NONNULL_LOC, Record); + Record.clear(); + } + if (PP.isRecordingPreamble() && PP.hasRecordedPreamble()) { assert(!IsModule); auto SkipInfo = PP.getPreambleSkipInfo(); diff --git a/clang/test/Index/preamble-assume-nonnull.c b/clang/test/Index/preamble-assume-nonnull.c new file mode 100644 --- /dev/null +++ b/clang/test/Index/preamble-assume-nonnull.c @@ -0,0 +1,6 @@ +// RUN: env CINDEXTEST_EDITING=1 c-index-test -test-load-source local %s 2>&1 \ +// RUN: | FileCheck %s --implicit-check-not "error:" + +#pragma clang assume_nonnull begin +void foo(int *x); +#pragma clang assume_nonnull end