Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1964,6 +1964,12 @@ HelpText<"Enable cf-protection in 'full' mode">; def mibt_seal : Flag<["-"], "mibt-seal">, Group, Flags<[CoreOption, CC1Option]>, HelpText<"Optimize fcf-protection=branch/full (requires LTO).">; +// Note: No MarshallingInfo for ffixed_date_time_EQ as the value needs to be +// manually validated to be conforming to RFC3339 (without fractional seconds +// and timezone), although, it is stored as an std::string +def ffixed_date_time_EQ : Joined<["-"], "ffixed-date-time=">, Group, + Flags<[CC1Option, CoreOption]>, + HelpText<"Set a fixed value for __DATE__, __TIME__, __TIMESTAMP__, formatted as 1969-12-31T23:59:59 (RFC3339 without fractional seconds and timezone)">; defm xray_instrument : BoolFOption<"xray-instrument", LangOpts<"XRayInstrument">, DefaultFalse, Index: clang/include/clang/Lex/PreprocessorOptions.h =================================================================== --- clang/include/clang/Lex/PreprocessorOptions.h +++ clang/include/clang/Lex/PreprocessorOptions.h @@ -14,6 +14,7 @@ #include "clang/Lex/PreprocessorExcludedConditionalDirectiveSkipMapping.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" +#include #include #include #include @@ -213,6 +214,10 @@ /// Prevents intended crashes when using #pragma clang __debug. For testing. bool DisablePragmaDebugCrash = false; + /// If set, a fixed time that __DATE__, __TIME__, __TIMESTAMP__ expand to. + /// Note: Manually validated, not automatically marshalled. + std::string FixedDateTime; + public: PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {} Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -6774,6 +6774,11 @@ D.Diag(diag::err_drv_clang_unsupported) << A->getAsString(Args); } + if (Arg *A = Args.getLastArg(options::OPT_ffixed_date_time_EQ)) { + StringRef DateTime = A->getValue(); + CmdArgs.push_back(Args.MakeArgString("-ffixed-date-time=" + DateTime)); + } + Args.AddLastArg(CmdArgs, options::OPT_dM); Args.AddLastArg(CmdArgs, options::OPT_dD); Args.AddLastArg(CmdArgs, options::OPT_dI); Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -94,7 +94,11 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include @@ -4328,6 +4332,13 @@ for (const auto &RF : Opts.RemappedFiles) GenerateArg(Args, OPT_remap_file, RF.first + ";" + RF.second, SA); + // We manually forward -ffixed-date-time= even though we keep + // in std::string form (which could be automatically marshalled), as we need + // to ascertain that conforms to the RFC3339 format (without + // fractional seconds and timezone). + if (!Opts.FixedDateTime.empty()) + GenerateArg(Args, OPT_ffixed_date_time_EQ, Twine(Opts.FixedDateTime), SA); + // Don't handle LexEditorPlaceholders. It is implied by the action that is // generated elsewhere. } @@ -4416,6 +4427,25 @@ if (isStrictlyPreprocessorAction(Action)) Opts.LexEditorPlaceholders = false; + // Validate -ffixed-date-time= + if (Arg *A = Args.getLastArg(OPT_ffixed_date_time_EQ)) { + StringRef DateTime = A->getValue(); + std::tm FixedDateTime = {}; + // RFC3339Format: RFC3339 format without fractional seconds and timezone. + const char *RFC3339Format = "%Y-%m-%dT%T"; + // We use std::get_time() only to validate if the DateTime value conforms to + // RFC3339Format, reporting an error here itself, if not; if validation is + // successful, we pass it forward as-is to the preprocessor for possible + // conversion into std::tm later on, when needed. + std::istringstream ss(DateTime.str()); + ss >> std::get_time(&FixedDateTime, RFC3339Format); + if (ss.fail() || !ss.eof()) + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << DateTime; + else + Opts.FixedDateTime = DateTime.str(); + } + return Diags.getNumErrors() == NumErrorsBefore; } Index: clang/lib/Lex/PPMacroExpansion.cpp =================================================================== --- clang/lib/Lex/PPMacroExpansion.cpp +++ clang/lib/Lex/PPMacroExpansion.cpp @@ -53,6 +53,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -1075,13 +1078,45 @@ MacroExpandingLexersStack.pop_back(); } +/// MakeTime - Convert the value in FDTV (assumed to be valid) into a std::tm +/// Make no assumption about daylight saving time. +static void MakeTime(const std::string &FDTV, std::tm &FDT) { + // RFC3339Format: RFC3339 Format without fractional seconds and timezone. + const char *RFC3339Format = "%Y-%m-%dT%T"; + std::istringstream ss(FDTV); + ss >> std::get_time(&FDT, RFC3339Format); + // FDTV should already be validated as being well-formed; but, ascertain that. + assert(!ss.fail() && ss.eof() && "DateTime invalid value!"); + // Per ctime(3), tm.tm_isdst "indicates whether daylight saving time is in + // effect ... and is negative if the information is not available." + FDT.tm_isdst = -1; + // Per mktime(3), (for tm.tm_isdst) "a negative value means that mktime() + // should (use timezone information and system databases to) attempt to + // determine whether DST is in effect at the specified time." Hence, we + // call mktime(3): "Since the return value of mktime() is ignored here, its + // dependence on the local timezone should not matter. The main effect of this + // call is to fill in tm_isdst flag with the best guess. This may result in + // unexpected results if fixed-date-time is set inside the "spring forward" + // hour for the local timezone, but this issue is inherent in the ISO 8601 + // format." (Note: The foregoing comment is from one of the reviewers of + // the original D23934 work, and is included because it serves to clarify). + mktime(&FDT); +} + /// ComputeDATE_TIME - Compute the current time, enter it into the specified /// scratch buffer, then return DATELoc/TIMELoc locations with the position of /// the identifier tokens inserted. static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc, Preprocessor &PP) { - time_t TT = time(nullptr); - struct tm *TM = localtime(&TT); + PreprocessorOptions &PO = PP.getPreprocessorOpts(); + std::tm FDT = {}; + std::tm *TM = &FDT; + if (!PO.FixedDateTime.empty()) + MakeTime(PO.FixedDateTime, FDT); + else { + std::time_t TT = std::time(nullptr); + TM = std::localtime(&TT); + } static const char * const Months[] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" @@ -1556,21 +1591,29 @@ // MSVC, ICC, GCC, VisualAge C++ extension. The generated string should be // of the form "Ddd Mmm dd hh::mm::ss yyyy", which is returned by asctime. - // Get the file that we are lexing out of. If we're currently lexing from - // a macro, dig into the include stack. - const FileEntry *CurFile = nullptr; - PreprocessorLexer *TheLexer = getCurrentFileLexer(); - - if (TheLexer) - CurFile = SourceMgr.getFileEntryForID(TheLexer->getFileID()); - const char *Result; - if (CurFile) { - time_t TT = CurFile->getModificationTime(); - struct tm *TM = localtime(&TT); + PreprocessorOptions &PO = getPreprocessorOpts(); + if (!PO.FixedDateTime.empty()) { + std::tm FDT; + std::tm *TM = &FDT; + MakeTime(PO.FixedDateTime, FDT); Result = asctime(TM); } else { - Result = "??? ??? ?? ??:??:?? ????\n"; + // Get the file that we are lexing out of. If we're currently lexing from + // a macro, dig into the include stack. + const FileEntry *CurFile = nullptr; + PreprocessorLexer *TheLexer = getCurrentFileLexer(); + + if (TheLexer) + CurFile = SourceMgr.getFileEntryForID(TheLexer->getFileID()); + + if (CurFile) { + std::time_t TT = CurFile->getModificationTime(); + std::tm *TM = std::localtime(&TT); + Result = asctime(TM); + } else { + Result = "??? ??? ?? ??:??:?? ????\n"; + } } // Surround the string with " and strip the trailing newline. OS << '"' << StringRef(Result).drop_back() << '"'; Index: clang/test/Preprocessor/fixed-date-time.cpp =================================================================== --- /dev/null +++ clang/test/Preprocessor/fixed-date-time.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -ffixed-date-time=2008-09-02T14:30:27 -std=c++11 %s -verify +// RUN: not %clang_cc1 -ffixed-date-time=2008-09-02T14:30:27asdf -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-SFX_ERR +// RUN: not %clang_cc1 -ffixed-date-time=qwer2008-09-02T14:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-PFX_ERR +// RUN: not %clang_cc1 -ffixed-date-time=qwer2008-09-02T14:30:27asdf -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-CMB_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008-09-02T14:30:2X -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-SEC_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008-09-02T14:?0:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-MIN_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008-09-02T^:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-HOU_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008-09-LT14:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-DAY_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008-+-02T14:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-MON_ERR +// RUN: not %clang_cc1 -ffixed-date-time=-09-02T14:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-YEA_ERR +// RUN: not %clang_cc1 -ffixed-date-time=2008:09:02T14-30-27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-SWP_ERR +// RUN: not %clang_cc1 -ffixed-date-time=junk -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-JNK_ERR +// RUN: not %clang_cc1 -ffixed-date-time="2008 09 02T14 30 27" -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-BSP_ERR +// RUN: not %clang_cc1 -ffixed-date-time=02-09-2008T14:30:27 -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-JMB_ERR +// RUN: not %clang_cc1 -ffixed-date-time= -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=CHECK-MIS_ERR +// expected-no-diagnostics + +// CHECK-SFX_ERR: error: invalid value '2008-09-02T14:30:27asdf' in '-ffixed-date-time=2008-09-02T14:30:27asdf' +// CHECK-PFX_ERR: error: invalid value 'qwer2008-09-02T14:30:27' in '-ffixed-date-time=qwer2008-09-02T14:30:27' +// CHECK-CMB_ERR: error: invalid value 'qwer2008-09-02T14:30:27asdf' in '-ffixed-date-time=qwer2008-09-02T14:30:27asdf' +// CHECK-SEC_ERR: error: invalid value '2008-09-02T14:30:2X' in '-ffixed-date-time=2008-09-02T14:30:2X' +// CHECK-MIN_ERR: error: invalid value '2008-09-02T14:?0:27' in '-ffixed-date-time=2008-09-02T14:?0:27' +// CHECK-HOU_ERR: error: invalid value '2008-09-02T^:30:27' in '-ffixed-date-time=2008-09-02T^:30:27' +// CHECK-DAY_ERR: error: invalid value '2008-09-LT14:30:27' in '-ffixed-date-time=2008-09-LT14:30:27' +// CHECK-MON_ERR: error: invalid value '2008-+-02T14:30:27' in '-ffixed-date-time=2008-+-02T14:30:27' +// CHECK-YEA_ERR: error: invalid value '-09-02T14:30:27' in '-ffixed-date-time=-09-02T14:30:27' +// CHECK-SWP_ERR: error: invalid value '2008:09:02T14-30-27' in '-ffixed-date-time=2008:09:02T14-30-27' +// CHECK-JNK_ERR: error: invalid value 'junk' in '-ffixed-date-time=junk' +// CHECK-BSP_ERR: error: invalid value '2008 09 02T14 30 27' in '-ffixed-date-time=2008 09 02T14 30 27' +// CHECK-JMB_ERR: error: invalid value '02-09-2008T14:30:27' in '-ffixed-date-time=02-09-2008T14:30:27' +// CHECK-MIS_ERR: error: invalid value '' in '-ffixed-date-time=' + +constexpr int constexpr_strcmp(const char *p, const char *q) { + return *p != *q ? *p - *q : !*p ? 0 + : constexpr_strcmp(p + 1, q + 1); +} + +static_assert(!constexpr_strcmp(__DATE__, "Sep 2 2008"), ""); +static_assert(!constexpr_strcmp(__TIME__, "14:30:27"), ""); +static_assert(!constexpr_strcmp(__TIMESTAMP__, "Tue Sep 2 14:30:27 2008"), "");