Skip to content
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

ARROW-6781: [C++] Improve and consolidate CHECK macros #8735

Closed
wants to merge 11 commits into from

Conversation

bkietz
Copy link
Member

@bkietz bkietz commented Nov 21, 2020

Adds more descriptive failures for comparison DCHECKs when ARROW_EXTRA_ERROR_CONTEXT is defined.

  DCHECK(vec.size() == expected_size) << "fails and prints operands";
  // ../src/arrow/util/logging_test.cc:101:  Check failed: (vec.size() == expected_size)
  //   left:  3
  //   right: 0
  // fails and prints operands

  DCHECK(std::vector<int>{1, 2} == std::vector<int>{})
      << "vector isn't printable - no extra printing";

  DCHECK_OK(Status::OK()) << "Status Checks can be streamed into";

Note that the above destructuring evaluates operands twice. This is fine for debug checks which must be constant anyway to ensure behavior does not differ between release/debug. However ARROW_CHECK might be side-effectful (for example: ARROW_CHECK(PokeServer(url).successful())). Thus if the extra context is desired there as well we'd first need to forbid side-effectful ARROW_CHECKs; not sure it's worth the trouble.

At the same time we could simply remove DCHECK_(EQ|NE|LE|LT|GE|GT): IMHO they add no value. In any case that cleanup is very noisy and can wait until the smaller core of this patch is reviewed.

@github-actions
Copy link

@bkietz bkietz force-pushed the 6781-Improve-and-consolidate-A.2 branch from a169b65 to ab3a157 Compare November 23, 2020 02:03
@pitrou
Copy link
Member

pitrou commented Nov 23, 2020

This PR needs rebasing now.

@pitrou pitrou changed the title ARROW-6781: [C++] Improve and consoldiate CHECK macros ARROW-6781: [C++] Improve and consolidate CHECK macros Nov 23, 2020
@bkietz bkietz force-pushed the 6781-Improve-and-consolidate-A.2 branch from 1dd33a4 to 9434bf1 Compare November 23, 2020 14:40
@bkietz bkietz force-pushed the 6781-Improve-and-consolidate-A.2 branch from 9434bf1 to ecb31b2 Compare November 23, 2020 14:53
Copy link
Member

@pitrou pitrou left a comment

Choose a reason for hiding this comment

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

There are a couple oddities here.

@@ -4560,7 +4560,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state,
CheckCommaSpacing(filename, clean_lines, linenum, error)
CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error)
CheckSpacingForFunctionCall(filename, clean_lines, linenum, error)
CheckCheck(filename, clean_lines, linenum, error)
#CheckCheck(filename, clean_lines, linenum, error)
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason this is commented out?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, this is a lint check which fails for DCHECK(x == y) (consider using DCHECK_EQ(x, y))

#define DCHECK_LE(val1, val2) ARROW_UNUSED(val1 <= val2)
#define DCHECK_LT(val1, val2) ARROW_UNUSED(val1 < val2)
#define DCHECK_GE(val1, val2) ARROW_UNUSED(val1 >= val2)
#define DCHECK_GT(val1, val2) ARROW_UNUSED(val1 > val2)
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... if the comparisons are not trivial (for example they call a function that the compiler can't see whether it has side effects), then not all code will be eliminated?

Perahsp instead something like ARROW_UNUSED(val1), ARROW_UNUSED(val2)?

Copy link
Member Author

Choose a reason for hiding this comment

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

okay, but since these are debug checks anyway the giving them side effects results in differing behavior between release and debug. In general it's buggy to have non trivial code inside a debug check

Copy link
Member

Choose a reason for hiding this comment

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

I'm not saying the comparison would have a side-effect, just that the compiler wouldn't know about it and therefore couldn't entirely eliminate the comparison code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see. In that case I should prefix if (false) so the entire expr can be dropped

// If the status is bad, CHECK immediately, appending the status to the logged
// message.
#define ARROW_CHECK_OK(...) \
for (::arrow::Status _s = ::arrow::internal::GenericToStatus((__VA_ARGS__)); \
Copy link
Member

Choose a reason for hiding this comment

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

Is there a particular reason for using a for loop?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it allows us to confine _s to the scope of the loop body. The loop body is a single statement with no braces, allowing *CHECK_OK() to accept a stream.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, but I mean compared to a regular while or do...while?

Copy link
Member Author

@bkietz bkietz Nov 25, 2020

Choose a reason for hiding this comment

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

The last tokens of a do... while loop are the loop condition, which precludes providing a stream for extra error messages.

for supports a scoped decl, which while doesn't. If we wanted to avoid for, we could introduce a variable as in ARROW_ASSIGN_OR_RAISE. After some macro expansion that'd look like:

Status _s_56 = Thing();
if (ARROW_PREDICT_FALSE(!_s_56.ok()))
ARROW_LOG(FATAL) << _s.message() << "Extra messages";

It seems more desirable to keep things scoped with a for

namespace detail {
template <typename Lhs, typename Rhs>
struct BoundLhsRhs {
explicit constexpr operator bool() const { return false; }
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... why?

Copy link
Member Author

Choose a reason for hiding this comment

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

These bound expressions must be contextually convertible to bool to enable DCHECK(x >= 0 && x < 8). (It can't be destructured for better printing, but it should still be a valid assertion.) If the conversion to bool is not provided, then bound_expr && x < 8 breaks compilation

Copy link
Member

Choose a reason for hiding this comment

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

Would there be a less cryptic way to implement the whole thing?

template <typename Rhs>
BoundLhsRhs<Lhs, Rhs> operator<=(const Rhs& rhs) && {
return {lhs, rhs};
}
Copy link
Member

Choose a reason for hiding this comment

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

Why all these operators? They don't look like they perform the advertised operations.

Copy link
Member Author

Choose a reason for hiding this comment

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

The assert condition is tested without decoration. This avoids some compiler warnings and suspicion that destructuring might slow down the fast path (the check passes).

Destructuring operates on another copy of the condition expression for printing purposes only.

It relies on the slightly higher precedence of <= compared to the other comparison operators. After macro expansion, the intercepted comparison is as follows:

InterceptComparison() <= x > y
((InterceptComparison() <= x) > y)  // parentheses for clarity

The inner parenthesis captures x, then the outer parenthesis captures y

Copy link
Member

Choose a reason for hiding this comment

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

Same question then :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the approach used by the Catch2 test framework's assertion macro. I'm not aware of another way to destructure a comparison's operands

Copy link
Member

Choose a reason for hiding this comment

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

Ok, but destructuring in the first place shouldn't be required. I guess you want DCHECK(a == b) to function exactly like DCHECK_EQ(a, b), but that doesn't sound required to me.

Otherwise, please at least explain all this in comments, because it's extremely cryptic at first sight.

};

template <typename Lhs>
BoundLhs<Lhs> operator<=(const Lhs& lhs) && {
Copy link
Member

Choose a reason for hiding this comment

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

Same question here... Don't you want to used regular method names?

@github-actions github-actions bot added the needs-rebase A PR that needs to be rebased by the author label Dec 1, 2020
@bkietz
Copy link
Member Author

bkietz commented Jan 4, 2021

closing for now

@bkietz bkietz closed this Jan 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants