This is an archive of the discontinued LLVM Phabricator instance.

ExprConstant: Propagate correct return values from constant evaluation.
Needs ReviewPublic

Authored by jyknight on Oct 5 2018, 11:07 AM.

Details

Reviewers
rsmith
Summary

The constant evaluation now returns false whenever indicating failure
would be appropriate for the requested mode, instead of returning
"true" for success, and depending on the caller examining the various
status variables after the fact, in some circumstances.

In EM_ConstantExpression mode, evaluation is aborted as soon as a
diagnostic note is generated, and therefore, a "true" return value now
actually indicates that the expression was a constexpr. (You no longer
have to additionally check for a lack of diagnostic messages.)

In EM_ConstantFold, evaluation is now aborted upon running into a
side-effect -- so a "true" return value now actually indicates that
there were no side-effects

Also -- update and clarify documentation for the evaluation modes.

This is a cleanup, not intending to change the functionality, as
callers had been checking those properties manually before.

Event Timeline

jyknight created this revision.Oct 5 2018, 11:07 AM
rsmith added inline comments.Oct 9 2018, 1:02 PM
clang/lib/AST/ExprConstant.cpp
12–32

Is this comment still correct?

682–719

Nit: LLVM style avoids /*...*/ comments even for long comment blocks like this (https://llvm.org/docs/CodingStandards.html#comment-formatting).

686–687

I think talking about diagnostic notes here distracts from the purpose, which is that this column indicates that we accept constructs that are not permitted by the language mode's constant expression rules.

688–690

"is okay" here is misleading, I think. The point of keepEvaluatingAfter[...] is that it's safe to stop evaluating if they return false, because later evaluations can't affect the overall result.

692

defer -> be deferred?

706–707

Allowing EM_ConstantExpression to evaluate expressions that EM_ConstantFold cannot evaluate would violate our invariants. We rely on being able to check up front that an expression is a constant expression and then later just ask for its value, and for the later query to always succeed and return the same value as the earlier one. (This is one of the reasons why I suggested in D52854 that we add an explicit AST representation for constant expressions.)

711

This is not the intent. The evaluator uses the rules of the current language mode. However, it doesn't enforce the C and C++98 syntactic restrictions on what can appear in a constant expression, because it turns out to be cleaner to check those separately rather than to interleave them with evaluation.

We should probably document this as evaluating following the evaluation (but not syntactic) constant expression rules of the current language mode.

726–732

This sentence is a little hard to read.

897–898

Nit: delete line

958

"Note" is beside the point here. We should be talking about non-constant expressions; that's what matters.

1283–1286

I would strongly prefer to keep the emission of the diagnostic and the corresponding return false adjacent whenever possible, so I'd prefer that we add a bool return value to diagnosePointerArithmetic.

(Eventually, we should remove these bool return values entirely and replace them with an enum, where the failure value is only ever created by the ...Diag functions and from places where we can prove that a diagnostic has already been emitted, so that we can easily ensure that every failure path produces a diagnostic first.)

1445–1449

checkSubobject should return false if it produced a diagnostic that should cause evaluation to halt; this call to keepEvaluatingAfterNote should not be necessary. (And likewise below.)

11489

Nit: there's no such thing as "a constexpr". I think you mean "a constant expression".

11492

How confident are you that all return false;s produce an accompanying note? We have had a long tail of situations where such notes are missing from some cases where evaluation fails. (Those were previously QoI bugs but will now result in an assertion failure.)

11605–11606

I fear that a "Same as above" comment will become stale during refactorings.

void added a subscriber: void.Oct 10 2018, 4:25 PM
jyknight updated this revision to Diff 169641.Oct 14 2018, 7:54 PM
jyknight marked 11 inline comments as done.

Address most comments.

jyknight added inline comments.Oct 14 2018, 7:54 PM
clang/lib/AST/ExprConstant.cpp
12–32

Yes -- the "potential" constant expression mode still has this semantic.

682–719

Done.

686–687

Reworded.

688–690

The overall return value of the evaluation indicates whether the expression was evaluable under a specified set of conditions. The different modes cause failure under different conditions -- so I think "is okay" really _is_ what is meant here. In this particular case, "failure to evaluate" can still return success!

Not sure if some rewording would communicate that better?

692

Done.

706–707

I agree, it would right now. But I do think it should be fixed not to in the future. Let me reword the TODO, noting that it's not trivial.

711

I'm not really sure what you mean to distinguish between C/C++98 "evaluation constant expression rules" and the definition of a C/C++98 ICE?

Perhaps you mean that if evaluation succeeds, it will have produced a value consistent with the rules of the current language, but that it may not produce the diagnostics in all cases it ought to?

AFAICT, the evaluator is designed to emit diagnostics only for the C++11 (and later) constant expression rules, as well as for inability to evaluate. The diagnostic notes are usually dropped on the floor in C++98 and C modes, as only "fold" is typically used there -- along with the separate CheckICE to produce warnings.

I believe the only place the diagnostics were being actually emitted in pre-c++11 modes is for the diagnose_if and enable_if attributes' "potential constant expression" check. And there it really seems to me to be using the c++11 evaluation semantics.

The existence of isCXX11ConstantExpr suggests an intent for the function to provide the C++11 rules, as well, no?

726–732

Reworded.

897–898

Done.

958

Renamed to keepEvaluatingAfterNonConstexpr, updated comment.

1283–1286

Done, for diagnosePointerArithmetic.

1445–1449

Are you sure?

Today, checkSubobject returns false if it sees something "invalid" -- and the callers seem to depend on that -- but invalid subobject designators are still allowed during evaluation in fold mode.

E.g., this is valid in C:
struct Foo { int x[]; }; struct Foo f; int *g = f.x;

But, in C++, constexpr int *g = f.x; is an error, diagnosing note_constexpr_unsupported_unsized_array.

All the LValue code, with its mutable "Result" state, and carrying of a couple different "Invalid" bits off on the side, is really rather confusing!

11492

I attempted to ensure they were all covered. But I cannot be completely 100% sure.

I care more about the inverse really, though: that everywhere evaluation _should_ fail actually returns false, instead of only emitting a note.

11605–11606

Good point; copied the comment instead.

rsmith added inline comments.Oct 19 2018, 3:46 PM
clang/lib/AST/ExprConstant.cpp
710

LLVM -> Clang (or "we" or similar)

711

Perhaps you mean that if evaluation succeeds, it will have produced a value consistent with the rules of the current language, but that it may not produce the diagnostics in all cases it ought to?

The concern I have with this comment is that it describes a happenstance of implementation rather than the intent. The intent is certainly *not* that we will use the C++11 rules when calling this in C++98 mode, but it just so happens that there are no interesting differences there right now.

Looking at this another way, the fact that we only guarantee to produce "not a constant expression because" notes in C++11 onwards is unrelated to the evaluation mode. Rather, the reason for that is instead because all the validity / ICE checking in other language modes is based on the syntactic form of the expression (and is checked separately as a result).

As a concrete suggestion, how about something like:

"Evaluate as a constant expression. Stop if we find that the expression is not a constant expression. Note that this doesn't enforce the syntactic ICE requirements of C and C++98."

723

Do we now guarantee to stop on UB in this mode? We didn't appear to do so previously. (Particularly, we'd evaluate past signed overflow and take the 2's complement value.)

956

only no -> only if no

958

Capitalize as "...NonConstExpr" instead, since this is just an abbreviation of the term "constant expression", not C++11's notion of "constexpr". (I'd also lengthen this to "...NonConstantExpr" or "...NonConstantExpression" to further clarify.)

1198–1200

Simplify to return Info.keepEvaluating...();

1445–1449

What I mean is that you should change the code to make what I said be true, if necessary. It's the callee's responsibility to say whether we hit something we can't evaluate past. The caller should not be guessing that the only reason that checkSubobject can fail is due to a CCEDiag -- if checkSubobject fails with an FFDiag, this will sometimes return true, which would be wrong.

Here's how I'd look at it: a false value is a token that indicates that someone has produced a diagnostic whose severity is such that we don't need to keep evaluating. Once that happens, we don't need to re-check whether to keep evaluating, because we've already been told that we shouldn't, and we just need to propagate that fact to our caller.

2527–2529

Simplify to return LVal.adjust[...]

7438

Nit: drop the braces here.

11490–11492

I don't believe the parenthetical is true: in C++11 or later, we should always get a note if and only if the expression is not a constant expression, regardless of evaluation mode. Instead, I think the thing you're highlighting here is that the return value from EvaluateAsRValue does not indicate whether the expression is a constant expression unless the evaluation mode is EM_ConstantExpression.

11495–11496

Combine these two ifs.