diff --git a/include/clang/Basic/DiagnosticFrontendKinds.td b/include/clang/Basic/DiagnosticFrontendKinds.td index 64a1777924..2c5dfcca09 100644 --- a/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/include/clang/Basic/DiagnosticFrontendKinds.td @@ -110,13 +110,18 @@ def warn_pch_gc_mode : Warning< "the PCH file was built with %select{no||hybrid}0 garbage collection but " "the current translation unit will compiled with %select{no||hybrid}1 " "garbage collection">; -def warn_pch_preprocessor : Warning< - "the PCH file was built with different preprocessor definitions than the " - "current translation unit">; -def note_predef_in_pch : Note< - "preprocessor definitions in PCH file">; def warn_pch_version_too_old : Warning< "PCH file uses an older PCH format that is no longer supported">; def warn_pch_version_too_new : Warning< "PCH file uses a newer PCH format that cannot be read">; +def warn_cmdline_conflicting_macro_def : Warning< + "definition of the macro '%0' conflicts with the definition used to " + "build the precompiled header">; +def note_pch_macro_defined_as : Note< + "definition of macro '%0' in the precompiled header">; +def warn_cmdline_missing_macro_defs : Warning< + "macro definitions used to build the precompiled header are missing">; +def note_using_macro_def_from_pch : Note< + "using this macro definition from precompiled header">; + } diff --git a/include/clang/Frontend/PCHReader.h b/include/clang/Frontend/PCHReader.h index c0e869fc1f..907ab7e0da 100644 --- a/include/clang/Frontend/PCHReader.h +++ b/include/clang/Frontend/PCHReader.h @@ -280,6 +280,16 @@ private: /// Objective-C protocols. llvm::SmallVector InterestingDecls; + /// \brief Suggested contents of the predefines buffer, after this + /// PCH file has been processed. + /// + /// In most cases, this string will be empty, because the predefines + /// buffer computed to build the PCH file will be identical to the + /// predefines buffer computed from the command line. However, when + /// there are differences that the PCH reader can work around, this + /// predefines buffer may contain additional definitions. + std::string SuggestedPredefines; + PCHReadResult ReadPCHBlock(); bool CheckPredefinesBuffer(const char *PCHPredef, unsigned PCHPredefLen, @@ -303,6 +313,11 @@ public: PCHReadResult ReadPCH(const std::string &FileName); + /// \brief Returns the suggested contents of the predefines buffer, + /// which contains a (typically-empty) subset of the predefines + /// build prior to including the precompiled header. + const std::string &getSuggestedPredefines() { return SuggestedPredefines; } + /// \brief Resolve a type ID into a type, potentially building a new /// type. virtual QualType GetType(pch::TypeID ID); diff --git a/lib/Frontend/PCHReader.cpp b/lib/Frontend/PCHReader.cpp index 6dd7806d0a..df9f301f79 100644 --- a/lib/Frontend/PCHReader.cpp +++ b/lib/Frontend/PCHReader.cpp @@ -30,6 +30,7 @@ #include "llvm/Support/Compiler.h" #include "llvm/Support/MemoryBuffer.h" #include +#include #include #include using namespace clang; @@ -306,6 +307,47 @@ static bool Error(const char *Str) { return true; } +/// \brief Split the given string into a vector of lines, eliminating +/// any empty lines in the process. +/// +/// \param Str the string to split. +/// \param Len the length of Str. +/// \param KeepEmptyLines true if empty lines should be included +/// \returns a vector of lines, with the line endings removed +std::vector splitLines(const char *Str, unsigned Len, + bool KeepEmptyLines = false) { + std::vector Lines; + for (unsigned LineStart = 0; LineStart < Len; ++LineStart) { + unsigned LineEnd = LineStart; + while (LineEnd < Len && Str[LineEnd] != '\n') + ++LineEnd; + if (LineStart != LineEnd || KeepEmptyLines) + Lines.push_back(std::string(&Str[LineStart], &Str[LineEnd])); + LineStart = LineEnd; + } + return Lines; +} + +/// \brief Determine whether the string Haystack starts with the +/// substring Needle. +static bool startsWith(const std::string &Haystack, const char *Needle) { + for (unsigned I = 0, N = Haystack.size(); Needle[I] != 0; ++I) { + if (I == N) + return false; + if (Haystack[I] != Needle[I]) + return false; + } + + return true; +} + +/// \brief Determine whether the string Haystack starts with the +/// substring Needle. +static inline bool startsWith(const std::string &Haystack, + const std::string &Needle) { + return startsWith(Haystack, Needle.c_str()); +} + /// \brief Check the contents of the predefines buffer against the /// contents of the predefines buffer used to build the PCH file. /// @@ -329,56 +371,154 @@ bool PCHReader::CheckPredefinesBuffer(const char *PCHPredef, const char *Predef = PP.getPredefines().c_str(); unsigned PredefLen = PP.getPredefines().size(); - // If the two predefines buffers compare equal, we're done!. + // If the two predefines buffers compare equal, we're done! if (PredefLen == PCHPredefLen && strncmp(Predef, PCHPredef, PCHPredefLen) == 0) return false; - - // The predefines buffers are different. Produce a reasonable - // diagnostic showing where they are different. - // The source locations (potentially in the two different predefines - // buffers) - SourceLocation Loc1, Loc2; SourceManager &SourceMgr = PP.getSourceManager(); + + // The predefines buffers are different. Determine what the + // differences are, and whether they require us to reject the PCH + // file. + std::vector CmdLineLines = splitLines(Predef, PredefLen); + std::vector PCHLines = splitLines(PCHPredef, PCHPredefLen); - // Create a source buffer for our predefines string, so - // that we can build a diagnostic that points into that - // source buffer. - FileID BufferID; - if (Predef && Predef[0]) { - llvm::MemoryBuffer *Buffer - = llvm::MemoryBuffer::getMemBuffer(Predef, Predef + PredefLen, - ""); - BufferID = SourceMgr.createFileIDForMemBuffer(Buffer); - } + // Sort both sets of predefined buffer lines, since + std::sort(CmdLineLines.begin(), CmdLineLines.end()); + std::sort(PCHLines.begin(), PCHLines.end()); - unsigned MinLen = std::min(PredefLen, PCHPredefLen); - std::pair Locations - = std::mismatch(Predef, Predef + MinLen, PCHPredef); - - if (Locations.first != Predef + MinLen) { - // We found the location in the two buffers where there is a - // difference. Form source locations to point there (in both - // buffers). - unsigned Offset = Locations.first - Predef; - Loc1 = SourceMgr.getLocForStartOfFile(BufferID) - .getFileLocWithOffset(Offset); - Loc2 = SourceMgr.getLocForStartOfFile(PCHBufferID) - .getFileLocWithOffset(Offset); - } else if (PredefLen > PCHPredefLen) { - Loc1 = SourceMgr.getLocForStartOfFile(BufferID) - .getFileLocWithOffset(MinLen); - } else { - Loc1 = SourceMgr.getLocForStartOfFile(PCHBufferID) - .getFileLocWithOffset(MinLen); + // Determine which predefines that where used to build the PCH file + // are missing from the command line. + std::vector MissingPredefines; + std::set_difference(PCHLines.begin(), PCHLines.end(), + CmdLineLines.begin(), CmdLineLines.end(), + std::back_inserter(MissingPredefines)); + + bool MissingDefines = false; + bool ConflictingDefines = false; + for (unsigned I = 0, N = MissingPredefines.size(); I != N; ++I) { + const std::string &Missing = MissingPredefines[I]; + if (!startsWith(Missing, "#define ") != 0) { + fprintf(stderr, "FIXME: command line is missing a non-macro entry in the predefines buffer that was used to build the PCH file\n%s\n", + Missing.c_str()); + return true; + } + + // This is a macro definition. Determine the name of the macro + // we're defining. + std::string::size_type StartOfMacroName = strlen("#define "); + std::string::size_type EndOfMacroName + = Missing.find_first_of("( \n\r", StartOfMacroName); + assert(EndOfMacroName != std::string::npos && + "Couldn't find the end of the macro name"); + std::string MacroName = Missing.substr(StartOfMacroName, + EndOfMacroName - StartOfMacroName); + + // Determine whether this macro was given a different definition + // on the command line. + std::string MacroDefStart = "#define " + MacroName; + std::string::size_type MacroDefLen = MacroDefStart.size(); + std::vector::iterator ConflictPos + = std::lower_bound(CmdLineLines.begin(), CmdLineLines.end(), + MacroDefStart); + for (; ConflictPos != CmdLineLines.end(); ++ConflictPos) { + if (!startsWith(*ConflictPos, MacroDefStart)) { + // Different macro; we're done. + ConflictPos = CmdLineLines.end(); + break; + } + + assert(ConflictPos->size() > MacroDefLen && + "Invalid #define in predefines buffer?"); + if ((*ConflictPos)[MacroDefLen] != ' ' && + (*ConflictPos)[MacroDefLen] != '(') + continue; // Longer macro name; keep trying. + + // We found a conflicting macro definition. + break; + } + + if (ConflictPos != CmdLineLines.end()) { + Diag(diag::warn_cmdline_conflicting_macro_def) + << MacroName; + + // Show the definition of this macro within the PCH file. + const char *MissingDef = strstr(PCHPredef, Missing.c_str()); + unsigned Offset = MissingDef - PCHPredef; + SourceLocation PCHMissingLoc + = SourceMgr.getLocForStartOfFile(PCHBufferID) + .getFileLocWithOffset(Offset); + Diag(PCHMissingLoc, diag::note_pch_macro_defined_as) + << MacroName; + + ConflictingDefines = true; + continue; + } + + // If the macro doesn't conflict, then we'll just pick up the + // macro definition from the PCH file. Warn the user that they + // made a mistake. + if (ConflictingDefines) + continue; // Don't complain if there are already conflicting defs + + if (!MissingDefines) { + Diag(diag::warn_cmdline_missing_macro_defs); + MissingDefines = true; + } + + // Show the definition of this macro within the PCH file. + const char *MissingDef = strstr(PCHPredef, Missing.c_str()); + unsigned Offset = MissingDef - PCHPredef; + SourceLocation PCHMissingLoc + = SourceMgr.getLocForStartOfFile(PCHBufferID) + .getFileLocWithOffset(Offset); + Diag(PCHMissingLoc, diag::note_using_macro_def_from_pch); } - Diag(Loc1, diag::warn_pch_preprocessor); - if (Loc2.isValid()) - Diag(Loc2, diag::note_predef_in_pch); - Diag(diag::note_ignoring_pch) << FileName; - return true; + if (ConflictingDefines) { + Diag(diag::note_ignoring_pch) << FileName; + return true; + } + + // Determine what predefines were introduced based on command-line + // parameters that were not present when building the PCH + // file. Extra #defines are okay, so long as the identifiers being + // defined were not used within the precompiled header. + std::vector ExtraPredefines; + std::set_difference(CmdLineLines.begin(), CmdLineLines.end(), + PCHLines.begin(), PCHLines.end(), + std::back_inserter(ExtraPredefines)); + for (unsigned I = 0, N = ExtraPredefines.size(); I != N; ++I) { + const std::string &Extra = ExtraPredefines[I]; + if (!startsWith(Extra, "#define ") != 0) { + fprintf(stderr, "FIXME: command line has extra predefines not used to build the PCH file.%s\n", + Extra.c_str()); + return true; + } + + // This is an extra macro definition. Determine the name of the + // macro we're defining. + std::string::size_type StartOfMacroName = strlen("#define "); + std::string::size_type EndOfMacroName + = Extra.find_first_of("( \n\r", StartOfMacroName); + assert(EndOfMacroName != std::string::npos && + "Couldn't find the end of the macro name"); + std::string MacroName = Extra.substr(StartOfMacroName, + EndOfMacroName - StartOfMacroName); + + // FIXME: Perform this check! + fprintf(stderr, "FIXME: check whether '%s' was used in the PCH file\n", + MacroName.c_str()); + + // Add this definition to the suggested predefines buffer. + SuggestedPredefines += Extra; + SuggestedPredefines += '\n'; + } + + // If we get here, it's because the predefines buffer had compatible + // contents. Accept the PCH file. + return false; } //===----------------------------------------------------------------------===// diff --git a/test/PCH/fuzzy-pch.c b/test/PCH/fuzzy-pch.c new file mode 100644 index 0000000000..ce4634d756 --- /dev/null +++ b/test/PCH/fuzzy-pch.c @@ -0,0 +1,17 @@ +// Test with pch. +// RUN: clang-cc -emit-pch -DFOO -o %t %S/variables.h && +// RUN: clang-cc -DBAR=int -include-pch %t -fsyntax-only -pedantic %s + +BAR bar = 17; + +#ifndef FOO +# error FOO was not defined +#endif + +#if FOO != 1 +# error FOO has the wrong definition +#endif + +#ifndef BAR +# error BAR was not defined +#endif diff --git a/test/PCH/fuzzy-pch.h b/test/PCH/fuzzy-pch.h new file mode 100644 index 0000000000..9eb1005ce4 --- /dev/null +++ b/test/PCH/fuzzy-pch.h @@ -0,0 +1,2 @@ +// Header for PCH test fuzzy-pch.c +void f(int X); diff --git a/tools/clang-cc/clang-cc.cpp b/tools/clang-cc/clang-cc.cpp index 92452ec5be..ad00664af5 100644 --- a/tools/clang-cc/clang-cc.cpp +++ b/tools/clang-cc/clang-cc.cpp @@ -1755,6 +1755,10 @@ static void ProcessInputFile(Preprocessor &PP, PreprocessorFactory &PPF, // the precompiled header into the AST context. switch (Reader->ReadPCH(ImplicitIncludePCH)) { case PCHReader::Success: { + // Set the predefines buffer as suggested by the PCH + // reader. Typically, the predefines buffer will be empty. + PP.setPredefines(Reader->getSuggestedPredefines()); + // Attach the PCH reader to the AST context as an external AST // source, so that declarations will be deserialized from the // PCH file as needed. @@ -1762,10 +1766,6 @@ static void ProcessInputFile(Preprocessor &PP, PreprocessorFactory &PPF, Source.reset(Reader.take()); ContextOwner->setExternalSource(Source); } - - // Clear out the predefines buffer, because all of the - // predefines are already in the PCH file. - PP.setPredefines(""); break; }