Skip to content

Commit 2c09016

Browse files
committed
[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
1 parent e18b7c7 commit 2c09016

File tree

Image for: File tree

6 files changed

Image for: 6 files changed
+92
-19
lines changed

6 files changed

Image for: 6 files changed
+92
-19
lines changed

‎clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ Non-comprehensive list of changes in this release
338338
is the target triple and `driver` first tries the canonical name
339339
for the driver (respecting ``--driver-mode=``), and then the name found
340340
in the executable.
341+
- If the environment variable ``SOURCE_DATE_EPOCH`` is set, it specifies a UNIX
342+
timestamp to be used in replacement of the current date and time in
343+
the ``__DATE__``, ``__TIME__``, and ``__TIMESTAMP__`` macros. See
344+
`<https://reproducible-builds.org/docs/source-date-epoch/>`_.
341345

342346
New Compiler Flags
343347
------------------

‎clang/include/clang/Basic/DiagnosticFrontendKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ def err_fe_invalid_exception_model
130130
def warn_fe_concepts_ts_flag : Warning<
131131
"-fconcepts-ts is deprecated - use '-std=c++20' for Concepts support">,
132132
InGroup<Deprecated>;
133+
def err_fe_invalid_source_date_epoch : Error<
134+
"environment variable 'SOURCE_DATE_EPOCH' ('%0') must be a non-negative decimal integer <= %1">;
133135

134136
def err_fe_unable_to_load_basic_block_sections_file : Error<
135137
"unable to load basic block sections function list: '%0'">;

‎clang/include/clang/Lex/PreprocessorOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ class PreprocessorOptions {
220220
/// Prevents intended crashes when using #pragma clang __debug. For testing.
221221
bool DisablePragmaDebugCrash = false;
222222

223+
/// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH.
224+
Optional<uint64_t> SourceDateEpoch;
225+
223226
public:
224227
PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {}
225228

‎clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@
9494
#include <cassert>
9595
#include <cstddef>
9696
#include <cstring>
97+
#include <ctime>
9798
#include <fstream>
99+
#include <limits>
98100
#include <memory>
99101
#include <string>
100102
#include <tuple>
@@ -4307,6 +4309,21 @@ static bool ParsePreprocessorArgs(PreprocessorOptions &Opts, ArgList &Args,
43074309
Opts.addRemappedFile(Split.first, Split.second);
43084310
}
43094311

4312+
if (const char *Epoch = std::getenv("SOURCE_DATE_EPOCH")) {
4313+
// SOURCE_DATE_EPOCH, if specified, must be a non-negative decimal integer.
4314+
// On time64 systems, pick 253402300799 (the UNIX timestamp of
4315+
// 9999-12-31T23:59:59Z) as the upper bound.
4316+
const uint64_t MaxTimestamp =
4317+
std::min<uint64_t>(std::numeric_limits<time_t>::max(), 253402300799);
4318+
uint64_t V;
4319+
if (StringRef(Epoch).getAsInteger(10, V) || V > MaxTimestamp) {
4320+
Diags.Report(diag::err_fe_invalid_source_date_epoch)
4321+
<< Epoch << MaxTimestamp;
4322+
} else {
4323+
Opts.SourceDateEpoch = V;
4324+
}
4325+
}
4326+
43104327
// Always avoid lexing editor placeholders when we're just running the
43114328
// preprocessor as we never want to emit the
43124329
// "editor placeholder in source file" error in PP only mode.

‎clang/lib/Lex/PPMacroExpansion.cpp

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,15 @@ void Preprocessor::removeCachedMacroExpandedTokensOfLastLexer() {
10851085
/// the identifier tokens inserted.
10861086
static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
10871087
Preprocessor &PP) {
1088-
time_t TT = time(nullptr);
1089-
struct tm *TM = localtime(&TT);
1088+
time_t TT;
1089+
std::tm *TM;
1090+
if (PP.getPreprocessorOpts().SourceDateEpoch) {
1091+
TT = *PP.getPreprocessorOpts().SourceDateEpoch;
1092+
TM = std::gmtime(&TT);
1093+
} else {
1094+
TT = std::time(nullptr);
1095+
TM = std::localtime(&TT);
1096+
}
10901097

10911098
static const char * const Months[] = {
10921099
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
@@ -1095,8 +1102,11 @@ static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
10951102
{
10961103
SmallString<32> TmpBuffer;
10971104
llvm::raw_svector_ostream TmpStream(TmpBuffer);
1098-
TmpStream << llvm::format("\"%s %2d %4d\"", Months[TM->tm_mon],
1099-
TM->tm_mday, TM->tm_year + 1900);
1105+
if (TM)
1106+
TmpStream << llvm::format("\"%s %2d %4d\"", Months[TM->tm_mon],
1107+
TM->tm_mday, TM->tm_year + 1900);
1108+
else
1109+
TmpStream << "??? ?? ????";
11001110
Token TmpTok;
11011111
TmpTok.startToken();
11021112
PP.CreateString(TmpStream.str(), TmpTok);
@@ -1106,8 +1116,11 @@ static void ComputeDATE_TIME(SourceLocation &DATELoc, SourceLocation &TIMELoc,
11061116
{
11071117
SmallString<32> TmpBuffer;
11081118
llvm::raw_svector_ostream TmpStream(TmpBuffer);
1109-
TmpStream << llvm::format("\"%02d:%02d:%02d\"",
1110-
TM->tm_hour, TM->tm_min, TM->tm_sec);
1119+
if (TM)
1120+
TmpStream << llvm::format("\"%02d:%02d:%02d\"", TM->tm_hour, TM->tm_min,
1121+
TM->tm_sec);
1122+
else
1123+
TmpStream << "??:??:??";
11111124
Token TmpTok;
11121125
TmpTok.startToken();
11131126
PP.CreateString(TmpStream.str(), TmpTok);
@@ -1593,22 +1606,24 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
15931606
Diag(Tok.getLocation(), diag::warn_pp_date_time);
15941607
// MSVC, ICC, GCC, VisualAge C++ extension. The generated string should be
15951608
// of the form "Ddd Mmm dd hh::mm::ss yyyy", which is returned by asctime.
1596-
1597-
// Get the file that we are lexing out of. If we're currently lexing from
1598-
// a macro, dig into the include stack.
1599-
const FileEntry *CurFile = nullptr;
1600-
PreprocessorLexer *TheLexer = getCurrentFileLexer();
1601-
1602-
if (TheLexer)
1603-
CurFile = SourceMgr.getFileEntryForID(TheLexer->getFileID());
1604-
16051609
const char *Result;
1606-
if (CurFile) {
1607-
time_t TT = CurFile->getModificationTime();
1608-
struct tm *TM = localtime(&TT);
1610+
if (getPreprocessorOpts().SourceDateEpoch) {
1611+
time_t TT = *getPreprocessorOpts().SourceDateEpoch;
1612+
std::tm *TM = std::gmtime(&TT);
16091613
Result = asctime(TM);
16101614
} else {
1611-
Result = "??? ??? ?? ??:??:?? ????\n";
1615+
// Get the file that we are lexing out of. If we're currently lexing from
1616+
// a macro, dig into the include stack.
1617+
const FileEntry *CurFile = nullptr;
1618+
if (PreprocessorLexer *TheLexer = getCurrentFileLexer())
1619+
CurFile = SourceMgr.getFileEntryForID(TheLexer->getFileID());
1620+
if (CurFile) {
1621+
time_t TT = CurFile->getModificationTime();
1622+
struct tm *TM = localtime(&TT);
1623+
Result = asctime(TM);
1624+
} else {
1625+
Result = "??? ??? ?? ??:??:?? ????\n";
1626+
}
16121627
}
16131628
// Surround the string with " and strip the trailing newline.
16141629
OS << '"' << StringRef(Result).drop_back() << '"';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// RUN: env SOURCE_DATE_EPOCH=0 %clang_cc1 -E %s | FileCheck %s --check-prefix=19700101
2+
3+
// 19700101: const char date[] = "Jan 1 1970";
4+
// 19700101-NEXT: const char time[] = "00:00:00";
5+
// 19700101-NEXT: const char timestamp[] = "Thu Jan 1 00:00:00 1970";
6+
7+
// RUN: env SOURCE_DATE_EPOCH=2147483647 %clang_cc1 -E -Wdate-time %s 2>&1 | FileCheck %s --check-prefix=Y2038
8+
9+
// Y2038: warning: expansion of date or time macro is not reproducible [-Wdate-time]
10+
// Y2038: const char date[] = "Jan 19 2038";
11+
// Y2038-NEXT: const char time[] = "03:14:07";
12+
// Y2038-NEXT: const char timestamp[] = "Tue Jan 19 03:14:07 2038";
13+
14+
/// Test a large timestamp if the system uses 64-bit time_t and known to support large timestamps.
15+
// 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 %}
16+
17+
// 99991231: warning: expansion of date or time macro is not reproducible [-Wdate-time]
18+
// 99991231: const char date[] = "Dec 31 9999";
19+
// 99991231-NEXT: const char time[] = "23:59:59";
20+
// 99991231-NEXT: const char timestamp[] = "Fri Dec 31 23:59:59 9999";
21+
22+
// RUN: env SOURCE_DATE_EPOCH=253402300800 not %clang_cc1 -E %s 2>&1 | FileCheck %s --check-prefix=TOOBIG
23+
24+
// TOOBIG: error: environment variable 'SOURCE_DATE_EPOCH' ('253402300800') must be a non-negative decimal integer <= {{(2147483647|253402300799)}}
25+
26+
// RUN: env SOURCE_DATE_EPOCH=0x0 not %clang_cc1 -E %s 2>&1 | FileCheck %s --check-prefix=NOTDECIMAL
27+
28+
// NOTDECIMAL: error: environment variable 'SOURCE_DATE_EPOCH' ('0x0') must be a non-negative decimal integer <= {{(2147483647|253402300799)}}
29+
30+
const char date[] = __DATE__;
31+
const char time[] = __TIME__;
32+
const char timestamp[] = __TIMESTAMP__;

0 commit comments

Image for: 0 commit comments
Comments
 (0)