[clang] 2c09016 - [Frontend] Recognize environment variable SOURCE_DATE_EPOCH

Fangrui Song via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 12 11:55:37 PDT 2022


Author: Fangrui Song
Date: 2022-10-12T11:55:27-07:00
New Revision: 2c090162746a6b901c5639562c090e4bb2b7327e

URL: https://github.com/llvm/llvm-project/commit/2c090162746a6b901c5639562c090e4bb2b7327e
DIFF: https://github.com/llvm/llvm-project/commit/2c090162746a6b901c5639562c090e4bb2b7327e.diff

LOG: [Frontend] Recognize environment variable SOURCE_DATE_EPOCH

See https://reproducible-builds.org/docs/source-date-epoch/ . The environment
variable ``SOURCE_DATE_EPOCH`` been recognized by many compilers.

In GCC, if `SOURCE_DATE_EPOCH` is set, it specifies a UNIX timestamp to be used
in replacement of the current date and time in the `__DATE__` and `__TIME__`
macros. Note: GCC as of today does not update `__TIMESTAMP__` (the modification
time of the current source file) but
https://wiki.debian.org/ReproducibleBuilds/TimestampsFromCPPMacros expresses the
intention to update it.

This patches parses SOURCE_DATE_EPOCH and changes all the three macros.

In addition, in case gmtime/localtime returns null (e.g. on 64-bit Windows
gmtime returns null when the timestamp is larger than 32536850399
(3001-01-19T21:59:59Z)), use `??? ?? ????` as used by GCC.

Reviewed By: ychen

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

Added: 
    clang/test/Preprocessor/SOURCE_DATE_EPOCH.c

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/DiagnosticFrontendKinds.td
    clang/include/clang/Lex/PreprocessorOptions.h
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Lex/PPMacroExpansion.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9803243e61967..1fe4be39e7aba 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -338,6 +338,10 @@ Non-comprehensive list of changes in this release
   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
+  `<https://reproducible-builds.org/docs/source-date-epoch/>`_.
 
 New Compiler Flags
 ------------------

diff  --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 8a4b69c757aaa..3e87f6256e0aa 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -130,6 +130,8 @@ def err_fe_invalid_exception_model
 def warn_fe_concepts_ts_flag : Warning<
   "-fconcepts-ts is deprecated - use '-std=c++20' for Concepts support">,
   InGroup<Deprecated>;
+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
index 4cf18e98f051f..d51f2bcbe14ed 100644
--- a/clang/include/clang/Lex/PreprocessorOptions.h
+++ b/clang/include/clang/Lex/PreprocessorOptions.h
@@ -220,6 +220,9 @@ class PreprocessorOptions {
   /// Prevents intended crashes when using #pragma clang __debug. For testing.
   bool DisablePragmaDebugCrash = false;
 
+  /// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH.
+  Optional<uint64_t> SourceDateEpoch;
+
 public:
   PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {}
 

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 656e5950db988..bcd5359a5f3b2 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -94,7 +94,9 @@
 #include <cassert>
 #include <cstddef>
 #include <cstring>
+#include <ctime>
 #include <fstream>
+#include <limits>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -4307,6 +4309,21 @@ static bool ParsePreprocessorArgs(PreprocessorOptions &Opts, ArgList &Args,
     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<uint64_t>(std::numeric_limits<time_t>::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
index 6053f3b5d123b..3734923576381 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -1085,8 +1085,15 @@ void Preprocessor::removeCachedMacroExpandedTokensOfLastLexer() {
 /// 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 @@ static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
   {
     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 @@ static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
   {
     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 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
     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
index 0000000000000..71de3c7586a07
--- /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__;


        


More information about the cfe-commits mailing list