diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -338,6 +338,10 @@ is the target triple and `driver` first tries the canonical name for the driver (respecting ``--driver-mode=``), and then the name found in the executable. +- If the environment variable ``SOURCE_DATE_EPOCH`` is set, it specifies a UNIX + timestamp to be used in replacement of the current date and time in + the ``__DATE__``, ``__TIME__``, and ``__TIMESTAMP__`` macros. See + ``_. New Compiler Flags ------------------ diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -130,6 +130,8 @@ def warn_fe_concepts_ts_flag : Warning< "-fconcepts-ts is deprecated - use '-std=c++20' for Concepts support">, InGroup; +def err_fe_invalid_source_date_epoch : Error< + "environment variable 'SOURCE_DATE_EPOCH' ('%0') must be a non-negative decimal integer <= %1">; def err_fe_unable_to_load_basic_block_sections_file : Error< "unable to load basic block sections function list: '%0'">; diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h --- a/clang/include/clang/Lex/PreprocessorOptions.h +++ b/clang/include/clang/Lex/PreprocessorOptions.h @@ -220,6 +220,9 @@ /// Prevents intended crashes when using #pragma clang __debug. For testing. bool DisablePragmaDebugCrash = false; + /// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH. + Optional SourceDateEpoch; + public: PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {} diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -94,7 +94,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -4307,6 +4309,21 @@ Opts.addRemappedFile(Split.first, Split.second); } + if (const char *Epoch = std::getenv("SOURCE_DATE_EPOCH")) { + // SOURCE_DATE_EPOCH, if specified, must be a non-negative decimal integer. + // On time64 systems, pick 253402300799 (the UNIX timestamp of + // 9999-12-31T23:59:59Z) as the upper bound. + const uint64_t MaxTimestamp = + std::min(std::numeric_limits::max(), 253402300799); + uint64_t V; + if (StringRef(Epoch).getAsInteger(10, V) || V > MaxTimestamp) { + Diags.Report(diag::err_fe_invalid_source_date_epoch) + << Epoch << MaxTimestamp; + } else { + Opts.SourceDateEpoch = V; + } + } + // Always avoid lexing editor placeholders when we're just running the // preprocessor as we never want to emit the // "editor placeholder in source file" error in PP only mode. diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -1085,8 +1085,15 @@ /// the identifier tokens inserted. static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc, Preprocessor &PP) { - time_t TT = time(nullptr); - struct tm *TM = localtime(&TT); + time_t TT; + std::tm *TM; + if (PP.getPreprocessorOpts().SourceDateEpoch) { + TT = *PP.getPreprocessorOpts().SourceDateEpoch; + TM = std::gmtime(&TT); + } else { + 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" @@ -1095,8 +1102,11 @@ { SmallString<32> TmpBuffer; llvm::raw_svector_ostream TmpStream(TmpBuffer); - TmpStream << llvm::format("\"%s %2d %4d\"", Months[TM->tm_mon], - TM->tm_mday, TM->tm_year + 1900); + if (TM) + TmpStream << llvm::format("\"%s %2d %4d\"", Months[TM->tm_mon], + TM->tm_mday, TM->tm_year + 1900); + else + TmpStream << "??? ?? ????"; Token TmpTok; TmpTok.startToken(); PP.CreateString(TmpStream.str(), TmpTok); @@ -1106,8 +1116,11 @@ { SmallString<32> TmpBuffer; llvm::raw_svector_ostream TmpStream(TmpBuffer); - TmpStream << llvm::format("\"%02d:%02d:%02d\"", - TM->tm_hour, TM->tm_min, TM->tm_sec); + if (TM) + TmpStream << llvm::format("\"%02d:%02d:%02d\"", TM->tm_hour, TM->tm_min, + TM->tm_sec); + else + TmpStream << "??:??:??"; Token TmpTok; TmpTok.startToken(); PP.CreateString(TmpStream.str(), TmpTok); @@ -1593,22 +1606,24 @@ Diag(Tok.getLocation(), diag::warn_pp_date_time); // 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); + if (getPreprocessorOpts().SourceDateEpoch) { + time_t TT = *getPreprocessorOpts().SourceDateEpoch; + std::tm *TM = std::gmtime(&TT); 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; + if (PreprocessorLexer *TheLexer = getCurrentFileLexer()) + CurFile = SourceMgr.getFileEntryForID(TheLexer->getFileID()); + if (CurFile) { + time_t TT = CurFile->getModificationTime(); + struct tm *TM = localtime(&TT); + Result = asctime(TM); + } else { + Result = "??? ??? ?? ??:??:?? ????\n"; + } } // Surround the string with " and strip the trailing newline. OS << '"' << StringRef(Result).drop_back() << '"'; diff --git a/clang/test/Preprocessor/SOURCE_DATE_EPOCH.c b/clang/test/Preprocessor/SOURCE_DATE_EPOCH.c new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/SOURCE_DATE_EPOCH.c @@ -0,0 +1,32 @@ +// RUN: env SOURCE_DATE_EPOCH=0 %clang_cc1 -E %s | FileCheck %s --check-prefix=19700101 + +// 19700101: const char date[] = "Jan 1 1970"; +// 19700101-NEXT: const char time[] = "00:00:00"; +// 19700101-NEXT: const char timestamp[] = "Thu Jan 1 00:00:00 1970"; + +// RUN: env SOURCE_DATE_EPOCH=2147483647 %clang_cc1 -E -Wdate-time %s 2>&1 | FileCheck %s --check-prefix=Y2038 + +// Y2038: warning: expansion of date or time macro is not reproducible [-Wdate-time] +// Y2038: const char date[] = "Jan 19 2038"; +// Y2038-NEXT: const char time[] = "03:14:07"; +// Y2038-NEXT: const char timestamp[] = "Tue Jan 19 03:14:07 2038"; + +/// Test a large timestamp if the system uses 64-bit time_t and known to support large timestamps. +// RUN: %if !system-windows && clang-target-64-bits %{ env SOURCE_DATE_EPOCH=253402300799 %clang_cc1 -E -Wdate-time %s 2>&1 | FileCheck %s --check-prefix=99991231 %} + +// 99991231: warning: expansion of date or time macro is not reproducible [-Wdate-time] +// 99991231: const char date[] = "Dec 31 9999"; +// 99991231-NEXT: const char time[] = "23:59:59"; +// 99991231-NEXT: const char timestamp[] = "Fri Dec 31 23:59:59 9999"; + +// RUN: env SOURCE_DATE_EPOCH=253402300800 not %clang_cc1 -E %s 2>&1 | FileCheck %s --check-prefix=TOOBIG + +// TOOBIG: error: environment variable 'SOURCE_DATE_EPOCH' ('253402300800') must be a non-negative decimal integer <= {{(2147483647|253402300799)}} + +// RUN: env SOURCE_DATE_EPOCH=0x0 not %clang_cc1 -E %s 2>&1 | FileCheck %s --check-prefix=NOTDECIMAL + +// NOTDECIMAL: error: environment variable 'SOURCE_DATE_EPOCH' ('0x0') must be a non-negative decimal integer <= {{(2147483647|253402300799)}} + +const char date[] = __DATE__; +const char time[] = __TIME__; +const char timestamp[] = __TIMESTAMP__;