Skip to content

Conversation

l0rinc
Copy link
Contributor

@l0rinc l0rinc commented Sep 29, 2024

Split out of #30928 (comment)

The current string formatter couldn't validate every string format template that we were using.
Extended it with dynamic widths, fixed a number parsing bug that could go over the string's content and added a %n validation.

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 29, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

No conflicts as of last run.

@DrahtBot DrahtBot added the Tests label Sep 29, 2024
@l0rinc l0rinc changed the title test: streamline CheckFormatSpecifiers testability Cover remaining tinyformat usages in CheckFormatSpecifiers Sep 29, 2024
@l0rinc l0rinc marked this pull request as ready for review September 29, 2024 20:59
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d49dfaf looks correct, but I am not sure the others.

Comment on lines -14 to -17
// Helper to allow compile-time sanity checks while providing the number of
// args directly. Normally PassFmt<sizeof...(Args)> would be used.
template <unsigned NumArgs>
inline void PassFmt(util::ConstevalFormatString<NumArgs> fmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in cba51f2: Not sure about turning the passing one into a runtime check from a compile-time check. Previously it was trivial to compile a single unit (this test) to sanity check the parser, as well as check the compile failures for various errors, by simply looking at the compile output. Also, the code was as close as possible to the real code, serving as documentation of how to use it.

Now, checking the passing cases requires not only compiling, but also linking and executing the test. Also, triggering a compile error to see that it works and to see how it looks is harder.

I understand you want better error messages on failure, but changing the passing cases isn't required for that.

Copy link
Contributor Author

@l0rinc l0rinc Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid complaint, I've added a consteval ValidFormatSpecifiers local delegate and split the valid tests from the BOOST_CHECK_EXCEPTIONs - this way failures still show the correct lines both for the valid and invalid tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit of ValidFormatSpecifiers over the existing PassFmt, other than dropping the code coverage stats?

Seems fine to update the comment below to say // Execute compile-time check again at run-time to get code coverage stats., but not sure about dropping it.

Copy link
Contributor Author

@l0rinc l0rinc Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the comment back, that's indeed important context.
Compared to PassFmt I found the ValidFormatSpecifiers to be more specific (I'm not a fan of abbrvs and // comments).
Don't have strong preference here, I can be convinced to rename it back, if you do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to PassFmt I found the ValidFormatSpecifiers to be more specific (I'm not a fan of abbrvs and // comments).

I don't care about naming, so if you want to rename PassFmt to something else, this is fine. However, the // comment isn't useless: It explains that the goal of this helper function is to be close as possible to the real code (and document the only difference). I found that useful as a single compilation unit that serves as a close proxy to the real code, with almost the same compile-time error messages (and behavior).

I understand you want better error messages on failure, but changing the behavior of the passing cases isn't required for that.

Generally, if there isn't a reason to change something, it is better to leave the code as-is, because it was most likely intentionally written in that way.

Comment on lines 28 to 33
*
* @note Counting of `*` dynamic width and precision fields (such as `%*c`,
* `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as
* they are not used in the codebase. Usage of these fields is not counted and
* can lead to run-time exceptions. Code wanting to use the `*` specifier can
* side-step this struct and call tinyformat directly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this comment? format("'%1$*3$s %2$-*3$s'", "hi", "w", 12) is still unsupported and parsed incorrectly at compile time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back the part that I think is relevant, let me know if you'd like me to rewrite it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are dynamic width fields, so I still don't understand why you remove that from the comment.

Copy link
Contributor Author

@l0rinc l0rinc Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we do have some dynamic width support for the values that were used in the codebase.
But I've reverted the original comment (but deleted %*c which is a compile-time failure now).

* Renamed `Detail_CheckNumFormatSpecifiers` to `CheckFormatSpecifiers` since we're checking more than the number of parameters
* Moved it out of `ConstevalFormatString` to make it easier to test
* Inline `FailFmtWithError` (and rename `PassFmt` to `ValidFormatSpecifiers`) in tests to provide better errors messages on failure (e.g. line number)
@l0rinc l0rinc force-pushed the l0rinc/ConstevalFOrmatString branch from 44aa62e to 6e4935d Compare September 30, 2024 12:11
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned previously, it looks like there is one correct commit. However, I have a hard time seeing how the others are useful in a great picture, given that some of them are incomplete anyway.

Comment on lines -14 to -17
// Helper to allow compile-time sanity checks while providing the number of
// args directly. Normally PassFmt<sizeof...(Args)> would be used.
template <unsigned NumArgs>
inline void PassFmt(util::ConstevalFormatString<NumArgs> fmt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to PassFmt I found the ValidFormatSpecifiers to be more specific (I'm not a fan of abbrvs and // comments).

I don't care about naming, so if you want to rename PassFmt to something else, this is fine. However, the // comment isn't useless: It explains that the goal of this helper function is to be close as possible to the real code (and document the only difference). I found that useful as a single compilation unit that serves as a close proxy to the real code, with almost the same compile-time error messages (and behavior).

I understand you want better error messages on failure, but changing the behavior of the passing cases isn't required for that.

Generally, if there isn't a reason to change something, it is better to leave the code as-is, because it was most likely intentionally written in that way.

Comment on lines +29 to 31
* @note Counting of `*` dynamic width and precision fields (such as
* `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as
* they are not used in the codebase. Usage of these fields is not counted and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about implementing a random and specific subset of * in specifiers. I think it is easier to either fully support them, or not at all. But having developers read the parser to understand which subset they are allowed to use may be causing more frustration than solving any real issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not implemented to minimize code complexity as long as they are not used in the codebase

I've implemented the part that was used, not a "random subset"

* `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as
* they are not used in the codebase. Usage of these fields is not counted and
* can lead to run-time exceptions. Code wanting to use the `*` specifier can
* side-step this struct and call tinyformat directly.
*/
constexpr static void CheckFormatSpecifiers(std::string_view str, unsigned num_params)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about moving this out. This will break the doxygen comment above. Also, it drops the "detail-namespace".

Generally, I think that test-only code should follow the real code, not the other way round. As long as real code is testable, optimizing other parts of the unit tests doesn't seem too useful, especially if it breaks the existing construct and documentation.

@l0rinc
Copy link
Contributor Author

l0rinc commented Oct 1, 2024

I'm closing it for lack of interest, feel free to cherry-pick changes to other PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants