-
Notifications
You must be signed in to change notification settings - Fork 11.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[clang] replaced the usage of asctime
with std::put_time
#99075
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-clang Author: Yi-Chi Lee (yichi170) ChangesIn This is my first time contributing to an open-source project. If there is anything that I can improve (such as code quality), please let me know! Fixes: #98724 Full diff: https://github.com/llvm/llvm-project/pull/99075.diff 1 Files Affected:
diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp
index 3913ff08c2eb5..f7e657be87932 100644
--- a/clang/lib/Lex/PPMacroExpansion.cpp
+++ b/clang/lib/Lex/PPMacroExpansion.cpp
@@ -1722,10 +1722,12 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
// 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.
const char *Result;
+ char TimeString[std::size("Ddd Mmm dd hh:mm:ss yyyy")];
if (getPreprocessorOpts().SourceDateEpoch) {
time_t TT = *getPreprocessorOpts().SourceDateEpoch;
std::tm *TM = std::gmtime(&TT);
- Result = asctime(TM);
+ strftime(TimeString, std::size(TimeString), "%c", TM);
+ Result = TimeString;
} else {
// Get the file that we are lexing out of. If we're currently lexing from
// a macro, dig into the include stack.
@@ -1735,13 +1737,13 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
if (CurFile) {
time_t TT = CurFile->getModificationTime();
struct tm *TM = localtime(&TT);
- Result = asctime(TM);
+ strftime(TimeString, std::size(TimeString), "%c", TM);
+ Result = TimeString;
} else {
- Result = "??? ??? ?? ??:??:?? ????\n";
+ Result = "??? ??? ?? ??:??:?? ????";
}
}
- // Surround the string with " and strip the trailing newline.
- OS << '"' << StringRef(Result).drop_back() << '"';
+ OS << '"' << Result << '"';
Tok.setKind(tok::string_literal);
} else if (II == Ident__FLT_EVAL_METHOD__) {
// __FLT_EVAL_METHOD__ is set to the default value.
|
clang/lib/Lex/PPMacroExpansion.cpp
Outdated
if (getPreprocessorOpts().SourceDateEpoch) { | ||
time_t TT = *getPreprocessorOpts().SourceDateEpoch; | ||
std::tm *TM = std::gmtime(&TT); | ||
Result = asctime(TM); | ||
strftime(TimeString, std::size(TimeString), "%c", TM); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we need to check the result of strftime
is not 0
which would indicate that it ran out of space and we need to recover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Do you mean if the result of strftime
is zero, Result
have to be set as "???...?"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's three problems here:
- We need to look at the return value from
strftime()
and if it's0
, fallback to the failure mode. - Different locales may require a different buffer size.
%c
isn't specified to return the same thing asasctime()
though it frequently will.
So I think TimeString
should probably have a hard-coded buffer size that's a bit bigger than we expect is necessary, like char TimeString[64];
, and the format string to strftime()
should be %a %b %d %T %Y
, and we should check the return value of the call to strftime()
and fall back to ?? .. ?
if the value is 0.
using (an alternative would be to use the C++ interface https://compiler-explorer.com/z/WEqTW341b ) Either way, we need to make sure sure the produced string are identical, I'm sure people rely on that. Edit: |
I submitted a commit that used the original way with error handling and fixed-sized TimeString.
Since I'm not familiar with the issue related to locale, I'm not sure which method is better. If the C++ interface implementation mentioned by @cor3ntin is preferable, I can modify the current implementation. |
clang/lib/Lex/PPMacroExpansion.cpp
Outdated
if (getPreprocessorOpts().SourceDateEpoch) { | ||
time_t TT = *getPreprocessorOpts().SourceDateEpoch; | ||
std::tm *TM = std::gmtime(&TT); | ||
Result = asctime(TM); | ||
if (strftime(TimeString, sizeof TimeString, "%a %b %e %T %Y", TM) != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AaronBallman I adapted your advice and here I use %e
instead of %d
since asctime
returns the space-padded Day of the month.
clang/lib/Lex/PPMacroExpansion.cpp
Outdated
if (getPreprocessorOpts().SourceDateEpoch) { | ||
time_t TT = *getPreprocessorOpts().SourceDateEpoch; | ||
std::tm *TM = std::gmtime(&TT); | ||
Result = asctime(TM); | ||
if (strftime(TimeString, sizeof TimeString, "%a %b %e %T %Y", TM) != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so the last remaining problem is that of locale. I didn't realize that asctime()
is locale independent, which means that switching to strftime()
will change the output for some users. :-(
We could use setlocale()
before the call to strftime()
and then reset the locale after the call, but another approach would be to refactor ComputeDATE_TIME
which does all the formatting manually and in a locale-independent way. Given what a pain locales are in practice, I sort of think the refactoring is actually a better approach. WDYT @cor3ntin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why i suggested the C++ approach here #99075 (comment)
(wouldn't it be nice if the C standard provided overloads of everything that takes explicit local parameters to avoid having to do these sort of dances!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that using %b
in put_time
is also locale-dependent. Is it expected?
ref: https://en.cppreference.com/w/cpp/io/manip/put_time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SHOOT, yes, that is expected, same for %a
.
This is why i suggested the C++ approach here #99075 (comment)
We can't use that approach because we need to compile in C++17 mode: https://compiler-explorer.com/z/xosc68WMn
Okay, so how's this for horrible:
bool Failed = false;
if (const char *OldLocale = ::setlocale(LC_TIME, "C")) {
if (strftime(...)) {
} else {
Failed = true;
}
::setlocale(LC_TIME, OldLocale);
} else {
Failed = true;
}
if (Failed) {
// Emit question marks instead
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AaronBallman the std::print
thing was illustrative, this works in C++17 (well, 11)
std::stringstream buf;
// this is the line that ensure we are not affected by locales
buf.imbue(std::locale("C"));
std::time_t t = std::time(nullptr);
std::tm tm = *std::localtime(&t);
buf << std::put_time(&tm, "%c");
std::string str = buf.str(); // do whatever with that string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, okay, thanks! Hmm, if we have to mess with locales either way, this seems reasonable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asctime_r
might be the best replacement, but it seems unavailable on MSVC.
MSVC has asctime_s
.
I implemented with |
clang/lib/Lex/PPMacroExpansion.cpp
Outdated
std::string Result = "??? ??? ?? ??:??:?? ????"; | ||
std::stringstream TmpStream; | ||
TmpStream.imbue(std::locale("C")); | ||
TmpStream.setstate(std::ios_base::badbit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is not needed afaik (the bad bit would be set automatically on failure) ... oh wait are you trying to check that one of the two code path was taken? I think it would be cleaner to either use a boolean for that, or materialize TmpStream.str(); and check whether it's empty (that way you can get rid of the setstate/clear calls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it. Thanks for the advice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, the changes LGTM assuming precommit CI doesn't discover anything. @cor3ntin are you happy with the changes as well?
asctime
with strftime
asctime
with std::put_time
I don't have write access, so if the PR is ready, please help me land it! |
5a8c840
to
60a8a36
Compare
LGTM too! |
@yichi170 Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested Please check whether problems have been caused by your change specifically, as How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
This seems to have broken the Solaris/amd64 and Solaris/sparcv9 buildbots. |
Confirmed: reverting the change locally restores the builds, although I don't yet see why. |
The fact that there's a missing symbol suggests the STL on the machine is not conforming... the symbol that's missing ( |
Aha! https://bugs.llvm.org/show_bug.cgi?id=33767 We have a mixture of both |
Ah, old sins (from the GCC side no less) keep haunting me.
Unfortunately not: I always get the undefined reference to the |
Drat! If you dump the symbols from the STL shared library on the system, is there one for (As a perhaps terrible idea, could we use the |
While the source requires
i.e.
A hack along the lines of
at least allows |
One last thing to try -- what happens if you replace |
Unfortunately not. |
Blech, thank you for trying! I guess that unless there's some way to hide this ugliness in the cmake scripts or someone else has better ideas to try, the |
At least in the short term. ISTM that |
Agreed; long-term we need a better solution. Hopefully someone maintaining Solaris support can step in and help there, because this is quite surprising behavior. |
Summary: In `clang/lib/Lex/PPMacroExpansion.cpp`, replaced the usage of the obsolete `asctime` function with `std::put_time` for generating timestamp strings. Fixes: #98724 Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60250729
Thanks for identifying the problem. @AaronBallman @rorth |
The cleaned-up hack is in the final rounds of testing. Wlll create a PR once that's done. There's no real Solaris maintainer AFAIK: while I do run the buildbots and try to keep the port in shape as time allows, I can only do so much. The Solaris GCC maintainership takes enough of my time and splitting it would only be to the detriment of both projects. TBH, I wouldn't even know where to look: I've mostly dealt with |
The introduction of `std::put_time` in fad17b4 broke the Solaris build: ``` Undefined first referenced symbol in file _ZNKSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecPKSt2tmPKcSB_ lib/libclangLex.a(PPMacroExpansion.cpp.o) ld: fatal: symbol referencing errors ``` As discussed in PR llvm#99075, the problem is that GCC mangles `std::tm` as `tm` on Solaris for binary compatility, while `clang` doesn't (Issue As a stop-gap measure, this patch introduces an `asm` level alias to the same effect. Tested on `sparcv9-sun-solaris2.11`, `amd64-pc-solaris2.11`, and `x86_64-pc-linux-gnu`.
The introduction of `std::put_time` in fad17b4 broke the Solaris build: ``` Undefined first referenced symbol in file _ZNKSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecPKSt2tmPKcSB_ lib/libclangLex.a(PPMacroExpansion.cpp.o) ld: fatal: symbol referencing errors ``` As discussed in PR #99075, the problem is that GCC mangles `std::tm` as `tm` on Solaris for binary compatility, while `clang` doesn't (Issue #33114). As a stop-gap measure, this patch introduces an `asm` level alias to the same effect. Tested on `sparcv9-sun-solaris2.11`, `amd64-pc-solaris2.11`, and `x86_64-pc-linux-gnu`.
) In `clang/lib/Lex/PPMacroExpansion.cpp`, replaced the usage of the obsolete `asctime` function with `std::put_time` for generating timestamp strings. Fixes: llvm#98724
In
clang/lib/Lex/PPMacroExpansion.cpp
, replaced the usage of the obsoleteasctime
function withstd::put_time
for generating timestamp strings.Fixes: #98724