Skip to content

CXX-3198 modernize ClangFormat configuration options #1306

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

Merged
merged 11 commits into from
Jan 6, 2025

Conversation

eramongodb
Copy link
Contributor

@eramongodb eramongodb commented Dec 30, 2024

Summary

Followup to #1299.

This PR proposes modifying several format options in the .clang-format configuration file to modernize and (hopefully) better fit our development practices/preferences. See ClangFormatStyleOptions (for 19.1.0, the version we currently use).

This PR is organized such that each option update is accompanied by corresponding changes to source code for comparison/reference. Particularly notable changes are to ColumnLimit, AlignAfterOpenBracket, and QualifierAlignment.

ColumnLimit: 100 -> 120

For consistency with the C Driver config and for arguably greater benefit to our C++ codebase by reducing instances of awkward line-wrapping (particularly for templates).

AlignAfterOpenBracket: Align -> AlwaysBreak

One of the major proposed changes in this PR. This is primarily motivated by the desire to remove (imo) excessive indentation of parameters and arguments, e.g.:

// Before:
bsoncxx::document::value owner =
    bsoncxx::builder::basic::make_document(kvp("a", std::int32_t{1}),  // "$numberInt": "1"
                                           kvp("b", std::int64_t{2}),  // "$numberLong": "2"
                                           kvp("c", binary)  // "$binary": { "$base64": "dGhyZWU=", "subType": 00 }
    );
bsoncxx::document::view doc = owner.view();

// After:
bsoncxx::document::value owner = bsoncxx::builder::basic::make_document(
    kvp("a", std::int32_t{1}),  // "$numberInt": "1"
    kvp("b", std::int64_t{2}),  // "$numberLong": "2"
    kvp("c", binary)            // "$binary": { "$base64": "dGhyZWU=", "subType": 00 }
);
bsoncxx::document::view doc = owner.view();
// Before:
constexpr friend bsoncxx::detail::strong_ordering tag_invoke(bsoncxx::detail::compare_three_way cmp,
                                                             basic_string_view left,
                                                             basic_string_view right) noexcept {
    return cmp(left.compare(right), 0);
}

// After:
constexpr friend bsoncxx::detail::strong_ordering tag_invoke(
    bsoncxx::detail::compare_three_way cmp, basic_string_view left, basic_string_view right) noexcept {
    return cmp(left.compare(right), 0);
}

There is a concern with this setting that a parameter list may be confused with the function body given the same indentation level, e.g.:

bsoncxx::v_noabi::document::value database::run_command(
    client_session const& session,
    bsoncxx::v_noabi::document::view_or_value command) {
    return _run_command(&session, command);
}

Although BlockIndent rather than AlwaysBreak would be preferable to avoid this issue (by forcing the closing brace to be on its own line at the base indentation level), this setting does not behave consistently due to only applying to braced initializer lists and parenthesis per the associated note in documentation (e.g. does not apply to template parameters). If sufficiently confusing, we can consider adjusting ContinuationIndentWidth to either 2 (half-indentation) or 8 (double-indentation):

// ContinuationIndentWidth: 2
bsoncxx::v_noabi::document::value database::run_command(
  client_session const& session,
  bsoncxx::v_noabi::document::view_or_value command) {
    return _run_command(&session, command);
}

// ContinuationIndentWidth: 8
bsoncxx::v_noabi::document::value database::run_command(
        client_session const& session,
        bsoncxx::v_noabi::document::view_or_value command) {
    return _run_command(&session, command);
}

AllowAllParametersOfDeclarationOnNextLine: true -> false

This change ensures whenever a parameter/argument list is broken after the opening brace, each parameter/argument is given its own line. This is to encourage better wrapping and slightly reduce the parameter list vs. function body confusion described above.

// Before:
constexpr friend bsoncxx::detail::strong_ordering tag_invoke(bsoncxx::detail::compare_three_way cmp,
                                                             basic_string_view left,
                                                             basic_string_view right) noexcept {
    return cmp(left.compare(right), 0);
}

// After:
constexpr friend bsoncxx::detail::strong_ordering
tag_invoke(bsoncxx::detail::compare_three_way cmp, basic_string_view left, basic_string_view right) noexcept {
    return cmp(left.compare(right), 0);
}

QualifierAlignment: Left -> Right

This change "reverts" the fixes applied in #1299 by switching from "west const" style (const T&) to "east const" style (T const&). This is motivated by better consistency between syntax and semantics across C++ code, e.g.:

using T = int*;

using A = T const; // int* const (OK: syntax matches semantics)
using B = const T; // int* const (!?: syntax does not match semantics)

Further motivations for this style is discussed in greater detail in C++ Templates: The Complete Guide, 2nd Edition, section xxxi: "Some Remarks About Programming Style". Note we've already let several instances of east const leak into new code, which were (temporarly) reverted by #1299.

SpacesBeforeTrailingComments: 2 -> 1

This change reduces the spaces count prior to // in closing namespace braces and similar cases (it is unclear why it was set to 2 to begin with).

#if !defined(MACRO_GUARD)
#define MACRO_GUARD

namespace example {

...

} // namespace example
 ^

#endif // !defined(MACRO_GUARD)
      ^

ShortNamespaceLines: 1 -> 0

This ensures the closing namespace brace is always accompanied by a trailing comment denoting the namespace being closed.

KeepEmptyLines.AtStartOfFile: true -> false

This trims extra newlines at the beginning of a file.

@eramongodb eramongodb requested a review from kevinAlbs December 30, 2024 16:26
@eramongodb eramongodb self-assigned this Dec 30, 2024
@kevinAlbs kevinAlbs requested review from vector-of-bool, a user, adriandole and kevinAlbs and removed request for kevinAlbs and vector-of-bool December 30, 2024 16:43
Copy link
Collaborator

@kevinAlbs kevinAlbs left a comment

Choose a reason for hiding this comment

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

LGTM. I support the ColumnLimit increase.

@eramongodb
Copy link
Contributor Author

eramongodb commented Jan 2, 2025

error: declaration of ‘bsoncxx::v_noabi::types::bson_value::view bsoncxx::v_noabi::types::bson_value::value::view() const’ [-fpermissive]
     BSONCXX_ABI_EXPORT_CDECL(bson_value::view) view() const noexcept;
                                                             ^~~~~~~~
In file included from ../../../src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types/bson_value/value.hpp:27,
                 from ../../../src/bsoncxx/lib/bsoncxx/v_noabi/bsoncxx/document/element.cpp:24:
../../../src/bsoncxx/include/bsoncxx/v_noabi/bsoncxx/types/bson_value/view.hpp:54:7: error: changes meaning of ‘view’ from ‘class bsoncxx::v_noabi::types::bson_value::view’ [-fpermissive]
 class view {
       ^~~~

It is unclear why this GCC error was triggered merely by a change from const view& to view const& in the parameter list for bsoncxx::v_noabi::types::bson_value::value's ctor, but qualifying the type with bson_value:: to disambiguate it from the member function suffices as a workaround (note: it should have been qualified to begin with). 🤔

Copy link
Contributor

@adriandole adriandole left a comment

Choose a reason for hiding this comment

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

Looks good. I never liked the look of the highly indented arguments.

Suggestion: once merged, can you create a .git-blame-ignore-revs file containing this commit? Usage for GitLens and CLI described here.

@eramongodb
Copy link
Contributor Author

eramongodb commented Jan 3, 2025

@adriandole Due to GitHub not supporting fast-forward merges of PRs, the commit hash of the formatting commit can only be known after the PR has been merged. A followup PR is therefore necessary to add the .git-blame-ignore-revs file (e.g. as done for the C Driver with mongodb/mongo-c-driver#1546). I can add refs for both this and the prior PR after this PR is merged. 👍

(Edit: whoops, sorry for the unnecessary elaboration, overlooked the "once merged".)

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

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

I can't speak to the aesthetics of this change. It's subjective. I would make different choices, but I'm not aware of any stakeholder requirements that would support or refute these changes.

The 'const' movement seems especially risky; there's one fixup in here already, and I may have found another inconsistency in the interpretation of 'view' during review. The overall size of this diff is daunting: 789 files, 58kloc. I did my best to review it thoroughly for correctness.

I noticed some existing problems during the review: an unnecessary strlen and min, and more worrying a flawed RNG in the examples.

Using rand() like this even in a didactic context is troubling. (Maximum available entropy is going to be less than the key size even if rand() happens to have a secure entropy source) Additionally, change 0d649bf introduced a bug. The "% UINT8_MAX" pattern is not correct unless it's important to avoid 0xFF bytes for some reason I'm unaware of.


int EXAMPLES_CDECL main() {
instance inst{};

// This must be the same master key that was used to create
// the encryption key; here, we use a random key as a placeholder.
std::uint8_t key_storage[kKeyLength];
std::generate_n(key_storage, kKeyLength, []() {
Copy link

Choose a reason for hiding this comment

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

Is this supposed to be excluding 0xFF bytes? Using rand() like this even as a didactic placeholder for a cryptographic rng seems problematic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it shouldn't exclude 0xff. Good catch. Will address in a followup PR.

That being said, I do not believe the use of rand() is too concerning. Although it is demonstrating cryptography-related code, these generated bytes are not being used for any cryptographically secure purpose or application: it is just to satisfy the interface for sake of the example below (users do not ever manually generate their own key material like this; they use the key material returned from the server by a createKey operation).

These examples (which are entirely self-contained) have been largely left unchanged from their initial implementation in Sep 2020. 0d649bf introduced the current lambda expression + cast to remove a cast of std::rand to a function pointer (not permitted by stdlib specification) and implicit conversion from int -> std::uint8_t. Examples under examples/ (excluding the new examples/api/ examples) used to be embedded or referenced (in their entirety) in C++ Driver manual/documentation pages (not to be confused with Doxygen API documentation) but these have been removed over time. I expect these source files will also eventually be removed in favor of the new API doc examples, which take greater care to hide "placeholder" code and behavior which may distract from the purpose of the example, such as these random byte generators.

msg,
std::min(strlen(msg) + 1, static_cast<size_t>(BSON_ERROR_BUFFER_SIZE)));
inline void set_bson_error_message(bson_error_t* error, char const* msg) {
bson_strncpy(error->message, msg, std::min(strlen(msg) + 1, static_cast<size_t>(BSON_ERROR_BUFFER_SIZE)));
Copy link

Choose a reason for hiding this comment

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

This extra strlen and std::min isn't helping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. This code seems to have been untouched (excluding recent formatting changes) since its introduction in Feb 2020. Will address in a followup PR.

Copy link

Choose a reason for hiding this comment

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

This "view::view(const view& rhs)" on line 55 didn't change. Might be another instance of the same confusion issue that commit 9a1f489 fixed one instance of.

Copy link
Contributor Author

@eramongodb eramongodb Jan 6, 2025

Choose a reason for hiding this comment

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

The preprocessor directives preceeding the function definition seems to confuse ClangFormat. Grepping for const ([a-zA-Z_:]+)[&\*] reveal several more instances that appear to be missed due to preprocessor directives or being within strings and comments. These have been manually updated accordingly. It does not seem related to 9a1f489.

});
bsoncxx::types::b_binary local_master_key{
bsoncxx::binary_sub_type::k_binary, kKeyLength, key_storage};
std::generate_n(key_storage, kKeyLength, []() { return static_cast<std::uint8_t>(std::rand() % UINT8_MAX); });
Copy link

Choose a reason for hiding this comment

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

Another std::rand() % UINT8_MAX

});
bsoncxx::types::b_binary local_master_key{
bsoncxx::binary_sub_type::k_binary, kKeyLength, key_storage};
std::generate_n(key_storage, kKeyLength, []() { return static_cast<std::uint8_t>(std::rand() % UINT8_MAX); });
Copy link

Choose a reason for hiding this comment

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

Another std::rand() % UINT8_MAX

});
bsoncxx::types::b_binary local_master_key{
bsoncxx::binary_sub_type::k_binary, kKeyLength, key_storage};
std::generate_n(key_storage, kKeyLength, []() { return static_cast<std::uint8_t>(std::rand() % UINT8_MAX); });
Copy link

Choose a reason for hiding this comment

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

And this one

@eramongodb eramongodb merged commit 2f53a56 into mongodb:master Jan 6, 2025
11 of 14 checks passed
@eramongodb eramongodb deleted the cxx-3198 branch January 6, 2025 16:30
eramongodb added a commit that referenced this pull request Jan 7, 2025
* Remove unnecessary strlen+min on call to bson_strncpy
* Use pre-generated key material for examples instead of rand()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants