Skip to content

Commit

Permalink
[preprocessor] When preprocessor option 'SingleFileParseMode' is enab…
Browse files Browse the repository at this point in the history
…led, parse all directive blocks if the condition uses undefined macros

This is useful for being able to parse the preprocessor directive blocks even if the header, that defined the macro that is checked, hasn't been included.

Differential Revision: https://reviews.llvm.org/D34263

llvm-svn: 305797
  • Loading branch information
akyrtzi committed Jun 20, 2017
1 parent 364a116 commit ad870f8
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 27 deletions.
13 changes: 11 additions & 2 deletions clang/include/clang/Lex/Preprocessor.h
Expand Up @@ -1835,11 +1835,20 @@ class Preprocessor {
/// \brief A fast PTH version of SkipExcludedConditionalBlock.
void PTHSkipExcludedConditionalBlock();

/// Information about the result for evaluating an expression for a
/// preprocessor directive.
struct DirectiveEvalResult {
/// Whether the expression was evaluated as true or not.
bool Conditional;
/// True if the expression contained identifiers that were undefined.
bool IncludedUndefinedIds;
};

/// \brief Evaluate an integer constant expression that may occur after a
/// \#if or \#elif directive and return it as a bool.
/// \#if or \#elif directive and return a \p DirectiveEvalResult object.
///
/// If the expression is equivalent to "!defined(X)" return X in IfNDefMacro.
bool EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro);
DirectiveEvalResult EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro);

/// \brief Install the standard preprocessor pragmas:
/// \#pragma GCC poison/system_header/dependency and \#pragma once.
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Lex/PreprocessorOptions.h
Expand Up @@ -96,6 +96,10 @@ class PreprocessorOptions {
std::string TokenCache;

/// When enabled, preprocessor is in a mode for parsing a single file only.
///
/// Disables #includes of other files and if there are unresolved identifiers
/// in preprocessor directive conditions it causes all blocks to be parsed so
/// that the client can get the maximum amount of information from the parser.
bool SingleFileParseMode = false;

/// When enabled, the preprocessor will construct editor placeholder tokens.
Expand Down
38 changes: 33 additions & 5 deletions clang/lib/Lex/PPDirectives.cpp
Expand Up @@ -538,7 +538,7 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation IfTokenLoc,
assert(CurPPLexer->LexingRawMode && "We have to be skipping here!");
CurPPLexer->LexingRawMode = false;
IdentifierInfo *IfNDefMacro = nullptr;
const bool CondValue = EvaluateDirectiveExpression(IfNDefMacro);
const bool CondValue = EvaluateDirectiveExpression(IfNDefMacro).Conditional;
CurPPLexer->LexingRawMode = true;
if (Callbacks) {
const SourceLocation CondEnd = CurPPLexer->getSourceLocation();
Expand Down Expand Up @@ -635,7 +635,7 @@ void Preprocessor::PTHSkipExcludedConditionalBlock() {
// Evaluate the condition of the #elif.
IdentifierInfo *IfNDefMacro = nullptr;
CurPTHLexer->ParsingPreprocessorDirective = true;
bool ShouldEnter = EvaluateDirectiveExpression(IfNDefMacro);
bool ShouldEnter = EvaluateDirectiveExpression(IfNDefMacro).Conditional;
CurPTHLexer->ParsingPreprocessorDirective = false;

// If this condition is true, enter it!
Expand Down Expand Up @@ -2654,7 +2654,13 @@ void Preprocessor::HandleIfdefDirective(Token &Result, bool isIfndef,
}

// Should we include the stuff contained by this directive?
if (!MI == isIfndef) {
if (PPOpts->SingleFileParseMode && !MI) {
// In 'single-file-parse mode' undefined identifiers trigger parsing of all
// the directive blocks.
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
/*wasskip*/true, /*foundnonskip*/false,
/*foundelse*/false);
} else if (!MI == isIfndef) {
// Yes, remember that we are inside a conditional, then lex the next token.
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
/*wasskip*/false, /*foundnonskip*/true,
Expand All @@ -2676,7 +2682,8 @@ void Preprocessor::HandleIfDirective(Token &IfToken,
// Parse and evaluate the conditional expression.
IdentifierInfo *IfNDefMacro = nullptr;
const SourceLocation ConditionalBegin = CurPPLexer->getSourceLocation();
const bool ConditionalTrue = EvaluateDirectiveExpression(IfNDefMacro);
const DirectiveEvalResult DER = EvaluateDirectiveExpression(IfNDefMacro);
const bool ConditionalTrue = DER.Conditional;
const SourceLocation ConditionalEnd = CurPPLexer->getSourceLocation();

// If this condition is equivalent to #ifndef X, and if this is the first
Expand All @@ -2695,7 +2702,12 @@ void Preprocessor::HandleIfDirective(Token &IfToken,
(ConditionalTrue ? PPCallbacks::CVK_True : PPCallbacks::CVK_False));

// Should we include the stuff contained by this directive?
if (ConditionalTrue) {
if (PPOpts->SingleFileParseMode && DER.IncludedUndefinedIds) {
// In 'single-file-parse mode' undefined identifiers trigger parsing of all
// the directive blocks.
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/true,
/*foundnonskip*/false, /*foundelse*/false);
} else if (ConditionalTrue) {
// Yes, remember that we are inside a conditional, then lex the next token.
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/false,
/*foundnonskip*/true, /*foundelse*/false);
Expand Down Expand Up @@ -2756,6 +2768,14 @@ void Preprocessor::HandleElseDirective(Token &Result) {
if (Callbacks)
Callbacks->Else(Result.getLocation(), CI.IfLoc);

if (PPOpts->SingleFileParseMode && CI.WasSkipping) {
// In 'single-file-parse mode' undefined identifiers trigger parsing of all
// the directive blocks.
CurPPLexer->pushConditionalLevel(CI.IfLoc, /*wasskip*/false,
/*foundnonskip*/true, /*foundelse*/true);
return;
}

// Finally, skip the rest of the contents of this block.
SkipExcludedConditionalBlock(CI.IfLoc, /*Foundnonskip*/true,
/*FoundElse*/true, Result.getLocation());
Expand Down Expand Up @@ -2791,6 +2811,14 @@ void Preprocessor::HandleElifDirective(Token &ElifToken) {
SourceRange(ConditionalBegin, ConditionalEnd),
PPCallbacks::CVK_NotEvaluated, CI.IfLoc);

if (PPOpts->SingleFileParseMode && CI.WasSkipping) {
// In 'single-file-parse mode' undefined identifiers trigger parsing of all
// the directive blocks.
CurPPLexer->pushConditionalLevel(ElifToken.getLocation(), /*wasskip*/true,
/*foundnonskip*/false, /*foundelse*/false);
return;
}

// Finally, skip the rest of the contents of this block.
SkipExcludedConditionalBlock(CI.IfLoc, /*Foundnonskip*/true,
/*FoundElse*/CI.FoundElse,
Expand Down
29 changes: 20 additions & 9 deletions clang/lib/Lex/PPExpressions.cpp
Expand Up @@ -73,6 +73,7 @@ class PPValue {

static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
Token &PeekTok, bool ValueLive,
bool &IncludedUndefinedIds,
Preprocessor &PP);

/// DefinedTracker - This struct is used while parsing expressions to keep track
Expand All @@ -93,6 +94,7 @@ struct DefinedTracker {
/// TheMacro - When the state is DefinedMacro or NotDefinedMacro, this
/// indicates the macro that was checked.
IdentifierInfo *TheMacro;
bool IncludedUndefinedIds = false;
};

/// EvaluateDefined - Process a 'defined(sym)' expression.
Expand Down Expand Up @@ -128,6 +130,7 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT,
MacroDefinition Macro = PP.getMacroDefinition(II);
Result.Val = !!Macro;
Result.Val.setIsUnsigned(false); // Result is signed intmax_t.
DT.IncludedUndefinedIds = !Macro;

// If there is a macro, mark it used.
if (Result.Val != 0 && ValueLive)
Expand Down Expand Up @@ -255,6 +258,8 @@ static bool EvaluateValue(PPValue &Result, Token &PeekTok, DefinedTracker &DT,
Result.Val.setIsUnsigned(false); // "0" is signed intmax_t 0.
Result.setIdentifier(II);
Result.setRange(PeekTok.getLocation());
DT.IncludedUndefinedIds = (II->getTokenID() != tok::kw_true &&
II->getTokenID() != tok::kw_false);
PP.LexNonComment(PeekTok);
return false;
}
Expand Down Expand Up @@ -400,7 +405,8 @@ static bool EvaluateValue(PPValue &Result, Token &PeekTok, DefinedTracker &DT,
// Just use DT unmodified as our result.
} else {
// Otherwise, we have something like (x+y), and we consumed '(x'.
if (EvaluateDirectiveSubExpr(Result, 1, PeekTok, ValueLive, PP))
if (EvaluateDirectiveSubExpr(Result, 1, PeekTok, ValueLive,
DT.IncludedUndefinedIds, PP))
return true;

if (PeekTok.isNot(tok::r_paren)) {
Expand Down Expand Up @@ -532,6 +538,7 @@ static void diagnoseUnexpectedOperator(Preprocessor &PP, PPValue &LHS,
/// evaluation, such as division by zero warnings.
static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
Token &PeekTok, bool ValueLive,
bool &IncludedUndefinedIds,
Preprocessor &PP) {
unsigned PeekPrec = getPrecedence(PeekTok.getKind());
// If this token isn't valid, report the error.
Expand Down Expand Up @@ -571,6 +578,7 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
// Parse the RHS of the operator.
DefinedTracker DT;
if (EvaluateValue(RHS, PeekTok, DT, RHSIsLive, PP)) return true;
IncludedUndefinedIds = DT.IncludedUndefinedIds;

// Remember the precedence of this operator and get the precedence of the
// operator immediately to the right of the RHS.
Expand Down Expand Up @@ -601,7 +609,8 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
RHSPrec = ThisPrec+1;

if (PeekPrec >= RHSPrec) {
if (EvaluateDirectiveSubExpr(RHS, RHSPrec, PeekTok, RHSIsLive, PP))
if (EvaluateDirectiveSubExpr(RHS, RHSPrec, PeekTok, RHSIsLive,
IncludedUndefinedIds, PP))
return true;
PeekPrec = getPrecedence(PeekTok.getKind());
}
Expand Down Expand Up @@ -769,7 +778,8 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
// Parse anything after the : with the same precedence as ?. We allow
// things of equal precedence because ?: is right associative.
if (EvaluateDirectiveSubExpr(AfterColonVal, ThisPrec,
PeekTok, AfterColonLive, PP))
PeekTok, AfterColonLive,
IncludedUndefinedIds, PP))
return true;

// Now that we have the condition, the LHS and the RHS of the :, evaluate.
Expand Down Expand Up @@ -806,7 +816,8 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
/// EvaluateDirectiveExpression - Evaluate an integer constant expression that
/// may occur after a #if or #elif directive. If the expression is equivalent
/// to "!defined(X)" return X in IfNDefMacro.
bool Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro) {
Preprocessor::DirectiveEvalResult
Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro) {
SaveAndRestore<bool> PPDir(ParsingIfOrElifDirective, true);
// Save the current state of 'DisableMacroExpansion' and reset it to false. If
// 'DisableMacroExpansion' is true, then we must be in a macro argument list
Expand All @@ -833,7 +844,7 @@ bool Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro) {

// Restore 'DisableMacroExpansion'.
DisableMacroExpansion = DisableMacroExpansionAtStartOfDirective;
return false;
return {false, DT.IncludedUndefinedIds};
}

// If we are at the end of the expression after just parsing a value, there
Expand All @@ -847,20 +858,20 @@ bool Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro) {

// Restore 'DisableMacroExpansion'.
DisableMacroExpansion = DisableMacroExpansionAtStartOfDirective;
return ResVal.Val != 0;
return {ResVal.Val != 0, DT.IncludedUndefinedIds};
}

// Otherwise, we must have a binary operator (e.g. "#if 1 < 2"), so parse the
// operator and the stuff after it.
if (EvaluateDirectiveSubExpr(ResVal, getPrecedence(tok::question),
Tok, true, *this)) {
Tok, true, DT.IncludedUndefinedIds, *this)) {
// Parse error, skip the rest of the macro line.
if (Tok.isNot(tok::eod))
DiscardUntilEndOfDirective();

// Restore 'DisableMacroExpansion'.
DisableMacroExpansion = DisableMacroExpansionAtStartOfDirective;
return false;
return {false, DT.IncludedUndefinedIds};
}

// If we aren't at the tok::eod token, something bad happened, like an extra
Expand All @@ -872,5 +883,5 @@ bool Preprocessor::EvaluateDirectiveExpression(IdentifierInfo *&IfNDefMacro) {

// Restore 'DisableMacroExpansion'.
DisableMacroExpansion = DisableMacroExpansionAtStartOfDirective;
return ResVal.Val != 0;
return {ResVal.Val != 0, DT.IncludedUndefinedIds};
}
11 changes: 0 additions & 11 deletions clang/test/Index/singe-file-parse.m

This file was deleted.

111 changes: 111 additions & 0 deletions clang/test/Index/single-file-parse.m
@@ -0,0 +1,111 @@
// RUN: c-index-test -single-file-parse %s | FileCheck %s

#include <stdint.h>

// CHECK-NOT: TypedefDecl=intptr_t

// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=MyCls
@interface MyCls
// CHECK: [[@LINE+1]]:8: ObjCInstanceMethodDecl=some_meth
-(void)some_meth;
@end

#if 1
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test1
@interface Test1 @end
#else
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test2 @end
#endif

#if 0
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test3 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test4
@interface Test4 @end
#endif

#if SOMETHING_NOT_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test5
@interface Test5 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test6
@interface Test6 @end
#endif

#define SOMETHING_DEFINED 1
#if SOMETHING_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test7
@interface Test7 @end
#else
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test8 @end
#endif

#if defined(SOMETHING_NOT_DEFINED)
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test9
@interface Test9 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test10
@interface Test10 @end
#endif

#if defined(SOMETHING_DEFINED)
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test11
@interface Test11 @end
#else
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test12 @end
#endif

#if SOMETHING_NOT_DEFINED1
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test13
@interface Test13 @end
#elif SOMETHING_NOT_DEFINED2
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test14
@interface Test14 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test15
@interface Test15 @end
#endif

#ifdef SOMETHING_NOT_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test19
@interface Test19 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test20
@interface Test20 @end
#endif

#ifdef SOMETHING_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test21
@interface Test21 @end
#else
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test22 @end
#endif

#ifndef SOMETHING_NOT_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test23
@interface Test23 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test24
@interface Test24 @end
#endif

#ifndef SOMETHING_DEFINED
// CHECK-NOT: [[@LINE+1]]:12:
@interface Test25 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test26
@interface Test26 @end
#endif

#if 1 < SOMETHING_NOT_DEFINED
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test27
@interface Test27 @end
#else
// CHECK: [[@LINE+1]]:12: ObjCInterfaceDecl=Test28
@interface Test28 @end
#endif

0 comments on commit ad870f8

Please sign in to comment.