Skip to content

Commit

Permalink
Allow for unfinished #if blocks in preambles
Browse files Browse the repository at this point in the history
Previously, a preamble only included #if blocks (and friends like
ifdef) if there was a corresponding #endif before any declaration or
definition. The problem is that any header file that uses include guards
will not have a preamble generated, which can make code-completion very
slow.

To prevent errors about unbalanced preprocessor conditionals in the
preamble, and unbalanced preprocessor conditionals after a preamble
containing unfinished conditionals, the conditional stack is stored
in the pch file.

This fixes PR26045.

Differential Revision: http://reviews.llvm.org/D15994

llvm-svn: 304207
  • Loading branch information
erikjv committed May 30, 2017
1 parent 626c997 commit b34c79f
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 35 deletions.
64 changes: 64 additions & 0 deletions clang/include/clang/Lex/Preprocessor.h
Expand Up @@ -283,6 +283,44 @@ class Preprocessor {
/// This is used when loading a precompiled preamble.
std::pair<int, bool> SkipMainFilePreamble;

class PreambleConditionalStackStore {
enum State {
Off = 0,
Recording = 1,
Replaying = 2,
};

public:
PreambleConditionalStackStore() : ConditionalStackState(Off) {}

void startRecording() { ConditionalStackState = Recording; }
void startReplaying() { ConditionalStackState = Replaying; }
bool isRecording() const { return ConditionalStackState == Recording; }
bool isReplaying() const { return ConditionalStackState == Replaying; }

ArrayRef<PPConditionalInfo> getStack() const {
return ConditionalStack;
}

void doneReplaying() {
ConditionalStack.clear();
ConditionalStackState = Off;
}

void setStack(ArrayRef<PPConditionalInfo> s) {
if (!isRecording() && !isReplaying())
return;
ConditionalStack.clear();
ConditionalStack.append(s.begin(), s.end());
}

bool hasRecordedPreamble() const { return !ConditionalStack.empty(); }

private:
SmallVector<PPConditionalInfo, 4> ConditionalStack;
State ConditionalStackState;
} PreambleConditionalStack;

/// \brief The current top of the stack that we're lexing from if
/// not expanding a macro and we are lexing directly from source code.
///
Expand Down Expand Up @@ -1695,6 +1733,11 @@ class Preprocessor {
/// \brief Return true if we're in the top-level file, not in a \#include.
bool isInPrimaryFile() const;

/// \brief Return true if we're in the main file (specifically, if we are 0
/// (zero) levels deep \#include. This is used by the lexer to determine if
/// it needs to generate errors about unterminated \#if directives.
bool isInMainFile() const;

/// \brief Handle cases where the \#include name is expanded
/// from a macro as multiple tokens, which need to be glued together.
///
Expand Down Expand Up @@ -1932,6 +1975,27 @@ class Preprocessor {
Module *M,
SourceLocation MLoc);

bool isRecordingPreamble() const {
return PreambleConditionalStack.isRecording();
}

bool hasRecordedPreamble() const {
return PreambleConditionalStack.hasRecordedPreamble();
}

ArrayRef<PPConditionalInfo> getPreambleConditionalStack() const {
return PreambleConditionalStack.getStack();
}

void setRecordedPreambleConditionalStack(ArrayRef<PPConditionalInfo> s) {
PreambleConditionalStack.setStack(s);
}

void setReplayablePreambleConditionalStack(ArrayRef<PPConditionalInfo> s) {
PreambleConditionalStack.startReplaying();
PreambleConditionalStack.setStack(s);
}

private:
// Macro handling.
void HandleDefineDirective(Token &Tok, bool ImmediatelyAfterTopLevelIfndef);
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Lex/PreprocessorLexer.h
Expand Up @@ -17,6 +17,7 @@

#include "clang/Lex/MultipleIncludeOpt.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"

namespace clang {
Expand Down Expand Up @@ -176,6 +177,11 @@ class PreprocessorLexer {
conditional_iterator conditional_end() const {
return ConditionalStack.end();
}

void setConditionalLevels(ArrayRef<PPConditionalInfo> CL) {
ConditionalStack.clear();
ConditionalStack.append(CL.begin(), CL.end());
}
};

} // end namespace clang
Expand Down
10 changes: 9 additions & 1 deletion clang/include/clang/Lex/PreprocessorOptions.h
Expand Up @@ -80,7 +80,14 @@ class PreprocessorOptions {
/// The boolean indicates whether the preamble ends at the start of a new
/// line.
std::pair<unsigned, bool> PrecompiledPreambleBytes;


/// \brief True indicates that a preamble is being generated.
///
/// 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.
bool GeneratePreamble;

/// The implicit PTH input included at the start of the translation unit, or
/// empty.
std::string ImplicitPTHInclude;
Expand Down Expand Up @@ -144,6 +151,7 @@ class PreprocessorOptions {
AllowPCHWithCompilerErrors(false),
DumpDeserializedPCHDecls(false),
PrecompiledPreambleBytes(0, true),
GeneratePreamble(false),
RemappedFilesKeepOriginalName(true),
RetainRemappedFileBuffers(false),
ObjCXXARCStandardLibrary(ARCXX_nolib) { }
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Expand Up @@ -607,6 +607,9 @@ namespace clang {

/// \brief Record code for \#pragma pack options.
PACK_PRAGMA_OPTIONS = 61,

/// \brief The stack of open #ifs/#ifdefs recorded in a preamble.
PP_CONDITIONAL_STACK = 62,
};

/// \brief Record types used within a source manager block.
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Frontend/ASTUnit.cpp
Expand Up @@ -1999,6 +1999,7 @@ ASTUnit *ASTUnit::LoadFromCommandLine(
PreprocessorOptions &PPOpts = CI->getPreprocessorOpts();
PPOpts.RemappedFilesKeepOriginalName = RemappedFilesKeepOriginalName;
PPOpts.AllowPCHWithCompilerErrors = AllowPCHWithCompilerErrors;
PPOpts.GeneratePreamble = PrecompilePreambleAfterNParses != 0;

// Override the resources path.
CI->getHeaderSearchOpts().ResourceDir = ResourceFilesPath;
Expand Down
39 changes: 11 additions & 28 deletions clang/lib/Lex/Lexer.cpp
Expand Up @@ -550,8 +550,6 @@ namespace {

enum PreambleDirectiveKind {
PDK_Skipped,
PDK_StartIf,
PDK_EndIf,
PDK_Unknown
};

Expand All @@ -574,8 +572,6 @@ std::pair<unsigned, bool> Lexer::ComputePreamble(StringRef Buffer,

bool InPreprocessorDirective = false;
Token TheTok;
Token IfStartTok;
unsigned IfCount = 0;
SourceLocation ActiveCommentLoc;

unsigned MaxLineOffset = 0;
Expand Down Expand Up @@ -658,33 +654,18 @@ std::pair<unsigned, bool> Lexer::ComputePreamble(StringRef Buffer,
.Case("sccs", PDK_Skipped)
.Case("assert", PDK_Skipped)
.Case("unassert", PDK_Skipped)
.Case("if", PDK_StartIf)
.Case("ifdef", PDK_StartIf)
.Case("ifndef", PDK_StartIf)
.Case("if", PDK_Skipped)
.Case("ifdef", PDK_Skipped)
.Case("ifndef", PDK_Skipped)
.Case("elif", PDK_Skipped)
.Case("else", PDK_Skipped)
.Case("endif", PDK_EndIf)
.Case("endif", PDK_Skipped)
.Default(PDK_Unknown);

switch (PDK) {
case PDK_Skipped:
continue;

case PDK_StartIf:
if (IfCount == 0)
IfStartTok = HashTok;

++IfCount;
continue;

case PDK_EndIf:
// Mismatched #endif. The preamble ends here.
if (IfCount == 0)
break;

--IfCount;
continue;

case PDK_Unknown:
// We don't know what this directive is; stop at the '#'.
break;
Expand All @@ -705,16 +686,13 @@ std::pair<unsigned, bool> Lexer::ComputePreamble(StringRef Buffer,
} while (true);

SourceLocation End;
if (IfCount)
End = IfStartTok.getLocation();
else if (ActiveCommentLoc.isValid())
if (ActiveCommentLoc.isValid())
End = ActiveCommentLoc; // don't truncate a decl comment.
else
End = TheTok.getLocation();

return std::make_pair(End.getRawEncoding() - StartLoc.getRawEncoding(),
IfCount? IfStartTok.isAtStartOfLine()
: TheTok.isAtStartOfLine());
TheTok.isAtStartOfLine());
}

/// AdvanceToTokenCharacter - Given a location that specifies the start of a
Expand Down Expand Up @@ -2570,6 +2548,11 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) {
return true;
}

if (PP->isRecordingPreamble() && !PP->isInMainFile()) {
PP->setRecordedPreambleConditionalStack(ConditionalStack);
ConditionalStack.clear();
}

// Issue diagnostics for unterminated #if and missing newline.

// If we are in a #if directive, emit an error.
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Lex/PPLexerChange.cpp
Expand Up @@ -46,6 +46,12 @@ bool Preprocessor::isInPrimaryFile() const {
});
}

bool Preprocessor::isInMainFile() const {
if (IsFileLexer())
return IncludeMacroStack.size() == 0;
return true;
}

/// getCurrentLexer - Return the current file lexer being lexed from. Note
/// that this ignores any potentially active macro expansions and _Pragma
/// expansions going on at the time.
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Lex/Preprocessor.cpp
Expand Up @@ -150,6 +150,9 @@ Preprocessor::Preprocessor(std::shared_ptr<PreprocessorOptions> PPOpts,
Ident_GetExceptionInfo = Ident_GetExceptionCode = nullptr;
Ident_AbnormalTermination = nullptr;
}

if (this->PPOpts->GeneratePreamble)
PreambleConditionalStack.startRecording();
}

Preprocessor::~Preprocessor() {
Expand Down Expand Up @@ -532,6 +535,12 @@ void Preprocessor::EnterMainSourceFile() {

// Start parsing the predefines.
EnterSourceFile(FID, nullptr, SourceLocation());

// Restore the conditional stack from the preamble, if there is one.
if (PreambleConditionalStack.isReplaying()) {
CurPPLexer->setConditionalLevels(PreambleConditionalStack.getStack());
PreambleConditionalStack.doneReplaying();
}
}

void Preprocessor::EndSourceFile() {
Expand Down
15 changes: 15 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Expand Up @@ -2925,6 +2925,21 @@ ASTReader::ReadASTBlock(ModuleFile &F, unsigned ClientLoadCapabilities) {
}
break;

case PP_CONDITIONAL_STACK:
if (!Record.empty()) {
SmallVector<PPConditionalInfo, 4> ConditionalStack;
for (unsigned Idx = 0, N = Record.size() - 1; Idx < N; /* in loop */) {
auto Loc = ReadSourceLocation(F, Record, Idx);
bool WasSkipping = Record[Idx++];
bool FoundNonSkip = Record[Idx++];
bool FoundElse = Record[Idx++];
ConditionalStack.push_back(
{Loc, WasSkipping, FoundNonSkip, FoundElse});
}
PP.setReplayablePreambleConditionalStack(ConditionalStack);
}
break;

case PP_COUNTER_VALUE:
if (!Record.empty() && Listener)
Listener->ReadCounter(F, Record[0]);
Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Expand Up @@ -1093,6 +1093,7 @@ void ASTWriter::WriteBlockInfoBlock() {
RECORD(UNUSED_LOCAL_TYPEDEF_NAME_CANDIDATES);
RECORD(DELETE_EXPRS_TO_ANALYZE);
RECORD(CUDA_PRAGMA_FORCE_HOST_DEVICE_DEPTH);
RECORD(PP_CONDITIONAL_STACK);

// SourceManager Block.
BLOCK(SOURCE_MANAGER_BLOCK);
Expand Down Expand Up @@ -2302,6 +2303,18 @@ void ASTWriter::WritePreprocessor(const Preprocessor &PP, bool IsModule) {
Stream.EmitRecord(PP_COUNTER_VALUE, Record);
}

if (PP.isRecordingPreamble() && PP.hasRecordedPreamble()) {
assert(!IsModule);
for (const auto &Cond : PP.getPreambleConditionalStack()) {
AddSourceLocation(Cond.IfLoc, Record);
Record.push_back(Cond.WasSkipping);
Record.push_back(Cond.FoundNonSkip);
Record.push_back(Cond.FoundElse);
}
Stream.EmitRecord(PP_CONDITIONAL_STACK, Record);
Record.clear();
}

// Enter the preprocessor block.
Stream.EnterSubblock(PREPROCESSOR_BLOCK_ID, 3);

Expand Down
11 changes: 5 additions & 6 deletions clang/test/Lexer/preamble.c
Expand Up @@ -9,15 +9,12 @@
#pragma unknown
#endif
#ifdef WIBBLE
#include "honk"
#else
int foo();
#include "foo"
int bar;
#endif

// This test checks for detection of the preamble of a file, which
// includes all of the starting comments and #includes. Note that any
// changes to the preamble part of this file must be mirrored in
// Inputs/preamble.txt, since we diff against it.
// includes all of the starting comments and #includes.

// RUN: %clang_cc1 -print-preamble %s > %t
// RUN: echo END. >> %t
Expand All @@ -33,4 +30,6 @@ int foo();
// CHECK-NEXT: #endif
// CHECK-NEXT: #pragma unknown
// CHECK-NEXT: #endif
// CHECK-NEXT: #ifdef WIBBLE
// CHECK-NEXT: #include "foo"
// CHECK-NEXT: END.
19 changes: 19 additions & 0 deletions clang/test/Lexer/preamble2.c
@@ -0,0 +1,19 @@
// Preamble detection test: header with an include guard.
#ifndef HEADER_H
#define HEADER_H
#include "foo"
int bar;
#endif

// This test checks for detection of the preamble of a file, which
// includes all of the starting comments and #includes.

// RUN: %clang_cc1 -print-preamble %s > %t
// RUN: echo END. >> %t
// RUN: FileCheck < %t %s

// CHECK: // Preamble detection test: header with an include guard.
// CHECK-NEXT: #ifndef HEADER_H
// CHECK-NEXT: #define HEADER_H
// CHECK-NEXT: #include "foo"
// CHECK-NEXT: END.

0 comments on commit b34c79f

Please sign in to comment.