-
Notifications
You must be signed in to change notification settings - Fork 215
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
Fix clang-tidy warnings to make it actually useful #1048
base: main
Are you sure you want to change the base?
Conversation
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.
Thanks @azrogers! I've so far reviewed only the Cesium3DTiles, Cesium3DTilesContent, Cesium3DTilesReader, and Cesium3DTilesSelection namespaces. Lots of really good changes here! And a few things for me to nitpick (see below).
- name: Install nasm | ||
uses: ilammy/setup-nasm@v1 | ||
- name: Install latest ninja and cmake | ||
uses: lukka/get-cmake@latest |
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.
Looks like you've removed the nasm
install here.
It's unclear if nasm is actually working even before this change (see #1025), but we do want to continue installing it.
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.
The motivation here is to speed up clang-tidy a bit by not installing nasm, since it shouldn't affect anything clang-tidy is checking. I can add it back if we still need it for linting though.
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.
Oh right, of course, this is only for linting. That's fine.
Cesium3DTiles/src/MetadataQuery.cpp
Outdated
#include "Cesium3DTiles/Class.h" | ||
#include "Cesium3DTiles/ClassProperty.h" | ||
#include "Cesium3DTiles/MetadataEntity.h" | ||
#include "Cesium3DTiles/Schema.h" | ||
#include "CesiumUtility/JsonValue.h" |
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.
These should all use <>
instead of ""
. Looks like this same change needs to be done throughout. Sorry!
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.
Wrote a Node script (when all you have is a Node.js runtime...) to conform the includes, so local files (private headers included by cpp files in the same folder) use quotes and everything else uses <>
.
const auto& rapidjsonStr = | ||
it->IsNull() ? noDataValue.value_or("null") : it->GetString(); |
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 change shouldn't be needed. We'd go into the if
above instead of this else
if noDataValue
were std::nullopt
. I guess clang-tidy isn't able to see that, eh?
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.
We only check if noDataValue
is a nullopt if it
is null. If it
were a number or something other than a string or null, we would enter this else
with a possibly nullopt
noDataValue.
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.
We only check if noDataValue is a nullopt if it is null.
That's true, but in the else
, we only evaluate *noDataValue
if it->isNull()
returns true. Otherwise we go to the other side of the ternary operator and evaluate it->GetString()
instead.
In the case that it->isNull()
is true and also noDataValue.has_value()
is false, we would go into the if
, so the else
doesn't matter.
But even if I'm right, just the fact that we're having this conversation might be a sign the compiler can't be expected to figure this out.
@@ -791,6 +816,7 @@ void updateExtensionWithJsonScalarProperty( | |||
|
|||
for (int64_t i = 0; i < propertyTable.count; ++i, ++p, ++it) { | |||
if (it->IsNull()) { | |||
CESIUM_ASSERT(noDataValue.has_value()); |
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.
@j9liu can you comment on this change? I think it's dangerous to assert here, because this might represent "bad data" (which should never assert) rather than "developer errors" (which is the only time we should assert). But it's not obvious what the right behavior should be.
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 the intention is to guarantee that *noDataValue
is a valid value before dereferencing the optional
. And it's been a while since I've looked at this code, so I wasn't 100% sure that that will be the case. But after a lookover I'm fairly confident this won't happen.
TL;DR if noDataValue
were nullopt
, this would be a developer error, and I don't think it hurts to keep the assert.
Rationale below if you would like it....
First we have to execute findCompatibleTypes
to narrow down how the property could be represented. For scalar properties, we try to settle on a reasonable sentinel value (-1 or 0) if it contains null
entries. At the end, we verify whether any numeric sentinel values are left. And if there are none, we actually default the type to STRING
so that we can use the empty string sentinel.
cesium-native/Cesium3DTilesContent/src/BatchTableToGltfStructuralMetadata.cpp
Lines 654 to 659 in 80cb0d9
// If no sentinel value is available, then it's not possible to accurately | |
// represent the null value of this property. Make it a string property | |
// instead. | |
if (compatibleTypes.hasNullValue() && !compatibleTypes.getSentinelValue()) { | |
compatibleTypes.makeIncompatible(); | |
} |
Looking at where noData
is set.... the scalar property should have a sentinel value to fall back on. Because otherwise, it would have been turned into a string property. But this is definitely not obvious from a glance.
cesium-native/Cesium3DTilesContent/src/BatchTableToGltfStructuralMetadata.cpp
Lines 1420 to 1436 in 80cb0d9
auto maybeSentinel = compatibleTypes.getSentinelValue(); | |
// Try to set the "noData" value before copying the property (to avoid copying | |
// nulls). | |
if (compatibleTypes.hasNullValue() && maybeSentinel) { | |
JsonValue sentinelValue = *maybeSentinel; | |
// If -1 is the only available sentinel, modify the masked type to only use | |
// signed integer types (if possible). | |
if (sentinelValue.getInt64OrDefault(0) == -1) { | |
type.isUint8 = false; | |
type.isUint16 = false; | |
type.isUint32 = false; | |
type.isUint64 = false; | |
} | |
classProperty.noData = sentinelValue; | |
} |
And in the way we handle JSON string property, we do check whether the sentinel value is present. We don't attempt to write the sentinel value if noDataValue
is std::nullopt
. I think we just give up and try to write null
to the output. 😆
cesium-native/Cesium3DTilesContent/src/BatchTableToGltfStructuralMetadata.cpp
Lines 698 to 716 in 80cb0d9
for (int64_t i = 0; i < propertyTable.count; ++i) { | |
if (it == propertyValue.end()) { | |
rapidjsonOffsets.emplace_back(rapidjsonStrBuffer.GetLength()); | |
continue; | |
} | |
if (!it->IsString() || (it->IsNull() && !noDataValue)) { | |
// Everything else that is not string will be serialized by json | |
rapidjson::Writer<rapidjson::StringBuffer> writer(rapidjsonStrBuffer); | |
it->Accept(writer); | |
} else { | |
// Because serialized string json will add double quotations in the | |
// buffer which is not needed by us, we will manually add the string to | |
// the buffer | |
const auto& rapidjsonStr = it->IsNull() ? *noDataValue : it->GetString(); | |
rapidjsonStrBuffer.Reserve(it->GetStringLength()); | |
for (rapidjson::SizeType j = 0; j < it->GetStringLength(); ++j) { | |
rapidjsonStrBuffer.PutUnsafe(rapidjsonStr[j]); | |
} | |
} |
Disabled |
When we first added clang-tidy to Cesium Native, we left the warnings as merely informational, because there were too many of them to fix at the moment. Unfortunately, this leaves clang-tidy as an afterthought, because I don't imagine many of us are poking through the CI logs to see if our changes produced any new clang-tidy warnings when there's already hundreds of existing warnings. This change attempts to fix this: resolve the existing warnings, enable any useful checks that weren't already enabled, and treat the warnings as errors so we'll notice them.
Changes in this PR:
ExcludeHeaderFilterRegex
config option we're using to removeezvcpkg
headers from the build.bugprone
clang-tidy check has been enabled, with the following exceptions:bugprone-exception-escape
, as fixing this is already a pending issue (Functions are declared noexcept, even though they may throw #348).bugprone-misplaced-widening-cast
, as it results in extremely verbose lines (likesize_t pixels = static_cast<size_t>(image.width) * static_cast<size_t>(image.height) * static_cast<size_t>(image.channels)
instead of justsize_t pixels = static_cast<size_t>(image.width * image.height * image.channels)
.bugprone-unhandled-exception-at-new
, as this is both part of Functions are declared noexcept, even though they may throw #348, and also extremely nitpicky - I don't think we need to wrap every call tonew
in atry...catch
.modernize
andmisc
checks have been enabled, where they didn't conflict with the style guide or produce way too many changes for one PR. The following I decided were too much for just this PR, but they can be automatically applied at a later date usingclang-tidy-fix
:misc-const-correctness
detects every variable that could be declaredconst
.modernize-loop-convert
uses range-based for loops instead of manually advancing the iterator.modernize-type-traits
changestraits<...>::type
andtraits<..>::value
totraits_t<...>
andtraits_v<...>
modernize-use-constraints
replacesstd::enable_if
with C++20requires
clauses.modernize-use-using
replacestypedef
withusing
.modernize-return-braced-init-list
replaces statements likereturn glm::dvec2(0.5, 0.5);
withreturn {0.5, 0.5};
modernize-use-designated-initializers
would mean adding.field=
to initializer statements, like{.x = 0.5, .y = 0.5}
, which would prevent bugs when changing the order of fields.performance
checks have been enabled, with the exception ofperformance-enum-size
, as I felt that saving three bytes by defining an enum withint8_t
instead of the defaultint32_t
didn't seem like it was worth the change.readability
checks are mostly style guide-related, but might be worth looking into.readability-else-after-return
andreadability-braces-around-statements
seem like they could be good to add.