This is an archive of the discontinued LLVM Phabricator instance.

Add clang-format stability check with FormatTests
Needs ReviewPublic

Authored by vglavnyy on Oct 21 2018, 10:54 AM.

Details

Reviewers
djasper
krasimir
Summary

Twice running clang-format may give unstable result for some code samples, for example: https://bugs.llvm.org/show_bug.cgi?id=23728
This commit adds stability check to clang-format unit-tests.
After apply this patch 10 tests from the FormatTests will fail.

[==========] 647 tests from 19 test cases ran. (31449 ms total)
[ PASSED ] 637 tests.
[ FAILED ] 10 tests, listed below:
[ FAILED ] FormatTest.OnlyGeneratesNecessaryReplacements
[ FAILED ] FormatTest.BreaksStringLiterals
[ FAILED ] FormatTest.BreaksStringLiteralsWithTabs
[ FAILED ] FormatTest.BreaksWideAndNSStringLiterals
[ FAILED ] FormatTest.BreaksStringLiteralsWithin_TMacro
[ FAILED ] FormatTest.DoNotBreakStringLiteralsInEscapeSequence
[ FAILED ] FormatTest.OptimizeBreakPenaltyVsExcess
[ FAILED ] FormatTest.WorksFor8bitEncodings
[ FAILED ] FormatTest.SplitsUTF8Strings
[ FAILED ] FormatTest.SupportsCRLF

Diff Detail

Event Timeline

vglavnyy created this revision.Oct 21 2018, 10:54 AM
krasimir requested changes to this revision.Oct 22 2018, 2:06 PM

Sadly I tried this out a year ago and hit the same thing. A root cause is that tabs have variable column length depending on their start column, and in clang-format tokens are modeled as entities having a fixed column width. There are FIXMEs about this in the codebase (https://github.com/llvm-mirror/clang/blob/41e165b659ec7d8262153a6e0ed9130431c886de/lib/Format/FormatTokenLexer.cpp#L641).
A patch that adds stability to tests would have to address the underlying root cause which requires quite a lot of stuff to be threaded over clang-format and for practical purposes folks using clang-format are not complaining about this enough to be worth spending the effort and added complexity fixing this. I know, it sucks, but that's the situation right now.

This revision now requires changes to proceed.Oct 22 2018, 2:06 PM
vglavnyy updated this revision to Diff 170663.Oct 23 2018, 9:10 AM

A twice-format problem: the format of format isn't format.
This commit surface an instability problem in clang-format at unit-tests level.
The patch adds double-checking format method for all test and adds a stub for tests with the detected TF-problem.

@krasimir thank you for review.
The patch code has been updated.
I hope this patch will help to start to fix this issue.
Probably will be helpful to add an optional debug flag will enable a twice-run checking for every run of clang-format.

The problem with this is that I believe fixing this issue is not worth it, hence this patch is unnecessary. Feel free to reach out to djasper@ for a second opinion. For completeness, was the unstability a problem just in FormatTest.cpp, how about the other FormatTest*.cpp in the same directory?

@krasimir first you say that fixing it is hard, then when someone wants to attempt, that it is not necessary?

Plenty of people run clang-format in automated fashion as part of some pipeline, seems to me results ping-ponging between two formattings and cluttering commits would be pretty bad.

I just ran into this bug (https://bugs.llvm.org/show_bug.cgi?id=39280) and now instructions to my contributors are going to have to be: please run clang-format twice :)

MyDeveloperDay added a project: Restricted Project.
MyDeveloperDay added a comment.EditedNov 5 2019, 12:38 AM

As @krasimir said we wouldn't normally commit tests which just break as they'd fail on the buildbot and someone would revert your change within 24 hours.. however, what you have here raises an interesting conversation

This bug of instability is often raised, I've even seen it myself with my own code and I normally see new bugs coming in about this on a regular basis

https://bugs.llvm.org/show_bug.cgi?id=43845
https://bugs.llvm.org/show_bug.cgi?id=42509

Firstly those developers who have clang-formated integrated into say vim,emac,vs,vscode with format on save likely never see this because they often format multiple times during their development and its likely those instabilities iron themselves out pretty quickly, but this does show up for those people who perhaps only do a final git clang-format before committing or who are reformatting a largely unformatted code base, then the CI system likely complains on commit if you have a check.

It would be good to investigate the root causes, but my experience seems to be that it often feels related to the alignment rules and the positioning of trailing comments. (at least that my experience)

It would also be good to understand if this is always solved with a second run or does it sometimes require more than two runs, or has anyone seen a case where the format flip-flops between 2 styles.

Ultimately fixing the alignment routines to try and make them completely stable with regard to future downstream rules, I believe would simply make them individually more complex and unstable if new rules were added.

It might be worth simply rerunning the specific alignXXX routine again on the subsequent changes.

I'd also be interested to understand if any real thought was put into the order of these routines? Or if just conceptually if you are trying to align declarations, assignments and trailing comments something has to go first and you don't know where the best place is until someone takes a first stab.

llvm::sort(Changes, Change::IsBeforeInFile(SourceMgr));
calculateLineBreakInformation();
alignConsecutiveMacros();
alignConsecutiveDeclarations();
alignConsecutiveAssignments();
alignTrailingComments();
alignEscapedNewlines();
generateChanges();

I'd would like to think there was a more elegant approach than just running clang-format twice! one solution might be to simply rerun the whole of reformat() function more than once. Of course, we have to keep one eye on performance because for large files clang-format can already be a little slow.

But running reformat() wouldn't be 2x because we definitely don't need to rerun the sort includes twice and we don't need to reannotate the tokens, detemine the spaces between or apply the replacements twice (+ no startup costs, dll loading, code rewriting), so I actually think a second pass of reformat might not be terrible. (maybe undesirable but not terrible)

What if we experiment with a -stable command line switch which simply reruns those parts of reformat that are causing the issues? and then measure the impact to performance?

@MyDeveloperDay thanks for your thoughts.. while -stable would be helpful once you know you have the issue (or to permanently turn on in a git pre-submit), it does not help developers that don't even know this is a thing, and waste a bunch of time figuring out what is going on. In fact, it took me longer to figure out simply because I assumed it is unlikely that clang-format has such a bug :)

It might almost make sense in reverse: -stable by default, and have a -un-stable for those users where clang-format becomes a performance bottleneck :) But once you admit that, might as well fix the core problem instead.

@MyDeveloperDay thanks for your thoughts.. while -stable would be helpful once you know you have the issue (or to permanently turn on in a git pre-submit), it does not help developers that don't even know this is a thing, and waste a bunch of time figuring out what is going on. In fact, it took me longer to figure out simply because I assumed it is unlikely that clang-format has such a bug :)

It might almost make sense in reverse: -stable by default, and have a -un-stable for those users where clang-format becomes a performance bottleneck :) But once you admit that, might as well fix the core problem instead.

Sorry I didn't make myself clear,I wasn't thinking of a final customer facing solution, I was only really thinking of adding the -stable purely so we could pass a flag down and try different levels of repeating so we could do some performance tests. I feel it might be related to these 3 below, but was trying to think about how we could easily experiment.. I think ultimately we'd want a stable solution.

alignConsecutiveDeclarations();
alignConsecutiveAssignments();
alignTrailingComments();