Skip to content

Commit

Permalink
Serialize PragmaAssumeNonNullLoc to support preambles
Browse files Browse the repository at this point in the history
Previously, if a `#pragma clang assume_nonnull begin` was at the
end of a premable with a `#pragma clang assume_nonnull end` at the
end of the main file, clang would diagnose an unterminated begin in
the preamble and an unbalanced end in the main file.

With this change, those errors no longer occur and the case above is
now properly handled. I've added a corresponding test to clangd,
which makes use of preambles, in order to verify this works as
expected.

Differential Revision: https://reviews.llvm.org/D122179
  • Loading branch information
DavidGoldman committed Mar 31, 2022
1 parent 1711020 commit d9739f2
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 6 deletions.
34 changes: 34 additions & 0 deletions clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
Expand Up @@ -747,6 +747,40 @@ TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
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<FunctionDecl>(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<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) ==
NullabilityKind::NonNull);
const auto *Y = cast<FunctionDecl>(findDecl(AST, "bar")).getParamDecl(0);
ASSERT_FALSE(
Y->getOriginalType()->getNullability(X->getASTContext()).hasValue());
}

TEST(DiagnosticsTest, InsideMacros) {
Annotations Test(R"cpp(
#define TEN 10
Expand Down
23 changes: 23 additions & 0 deletions clang/include/clang/Lex/Preprocessor.h
Expand Up @@ -409,6 +409,14 @@ class Preprocessor {
/// \#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;

Expand Down Expand Up @@ -1762,6 +1770,21 @@ class Preprocessor {
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) {
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Lex/PreprocessorOptions.h
Expand Up @@ -128,7 +128,8 @@ class PreprocessorOptions {
///
/// 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.
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Expand Up @@ -698,6 +698,10 @@ enum ASTRecordTypes {

/// 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.
Expand Down
19 changes: 14 additions & 5 deletions clang/lib/Lex/PPLexerChange.cpp
Expand Up @@ -432,8 +432,13 @@ bool Preprocessor::HandleEndOfFile(Token &Result, SourceLocation EndLoc,
// 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();
}
Expand Down Expand Up @@ -514,10 +519,14 @@ bool Preprocessor::HandleEndOfFile(Token &Result, SourceLocation EndLoc,
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() ||
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Expand Up @@ -3109,6 +3109,7 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
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:
Expand Down Expand Up @@ -3370,6 +3371,14 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
}
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;
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Expand Up @@ -872,6 +872,7 @@ void ASTWriter::WriteBlockInfoBlock() {
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);
Expand Down Expand Up @@ -2299,6 +2300,17 @@ void ASTWriter::WritePreprocessor(const Preprocessor &PP, bool IsModule) {
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();
Expand Down
6 changes: 6 additions & 0 deletions 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

0 comments on commit d9739f2

Please sign in to comment.