This was showing up in our internal crash collector. I have no idea how
to test it out though, open for suggestions if there are easy paths but
otherwise I'd move forward with the patch.
Details
- Reviewers
usaxena95 ilya-biryukov aaron.ballman shafik - Group Reviewers
Restricted Project - Commits
- rGa5ab650714d0: [clang] Fix a crash in constant evaluation
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
Thanks. This looks reasonable.
As discussed offline, this verifiably fixes the crash but it is hard to come up with a reduced reproducer.
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
4797 | It seems to me that we shouldn't GET to this function with an incomplete type. I suspect whoever calls this is doing so incorrectly. |
Also, without a reduced example lit-test, we shouldn't be making changes. Use creduce or something, but PLEASE come back with a lit test.
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
4797 | Also note we only check in ExprConstant.cpp for hasDefinition() in one other place in findCompleteObject and that is around extern see: https://github.com/llvm/llvm-project/commit/c0d04a2567c22631595bed8092bc042bb91ea4ee#diff-255a21a02a8966766225831836d482547787baf9a770fbf67178ebb7d7347e27 |
sorry, i missed the other comments around having a lit test. reverting the change.
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
4797 |
Agreed, that's also my assumption. but we've been unable to get a minimal crasher. i am not a fan of landing these changes without reproducers but this was clearly fixing the issue we had (moreover, it's happening on invalid code). moreover we're checking for recorddecl being invalid up above, so it felt quite likely to hit this code path with incomplete types as well (or there were some changes up the callstack that forgot to update the implementation here). |
clang/lib/AST/ExprConstant.cpp | ||
---|---|---|
4797 |
It's hard to say that it actually is fixing the issue instead of papering over the root cause elsewhere in the project. Having test coverage helps us to determine whether the fix is correct or incorrect.
If the type is incomplete, why is the record not invalid? This smells like we're possibly missing a call to RequireCompleteType() somewhere else. |
Add reproducer.
I think the issue is about keeping constexpr functions valid even when their
bodies contain invalid decls under certain instantiations, which I believe is
the right behaviour. As the function body might be invalid for a certain
instantiation at constexpr time, but might be valid for others (or even for the
same instantiation later on in the TU).
I was just passing by, but wanted to add more context from our investigation with @kadircet.
If variables with incomplete types appear inside non-template constexpr function this gets detected by a call to CheckConstexprFunctionDefinition inside ActOnFinishFunctionBody:
if (!IsInstantiation && FD && FD->isConstexpr() && !FD->isInvalidDecl() && !CheckConstexprFunctionDefinition(FD, CheckConstexprKind::Diagnose)) FD->setInvalidDecl();
So the resulting constexpr function is marked as invalid and Clang does not attempt to evaluate its body.
However, CheckConstexprFunctionDefinition does not run for template function instantiation, so the VarDecl marked invalid ends up inside a valid constexpr function and Clang does run the evaluation.
This is helpful information but I am not sure this convinces me that EvaluateVarDecl is the correct place to check. Why not in EvaluateDecl or EvaluateStmt? Both locations we could also check.
This is helpful information but I am not sure this convinces me that EvaluateVarDecl is the correct place to check. Why not in EvaluateDecl or EvaluateStmt? Both locations we could also check.
I have done the check inside EvaluateVarDecl because the invalid evaluation happens when we try to analyze type of an invalid var decl.
I don't know if it would be safe to say we shouldn't evaluate any invalid decl. e.g. EvaluateDecl returns true for any non-vardecl today, without checking for anything at all. I don't know if changing the contract there, let alone at the statement level, would be correct. but doing it inside the vardecl evaluation is at least correct, but might be redundant.
So the options are:
- assert(Decl->isValid()) in EvaluateVarDecl and checks at call sites in EvaluateDecl and EvaluateStmt.
- a single check in EvaluateVarDecl.
The actual observable behavior of the compiler seems equivalent (EvaluateDecl can only fail inside EvaluateVarDecl anyway).
I think either is fine, neither of the approaches seems to be a big win over the other.
I will stamp this as I'm already in the reviewers list, think this change is ok and believe that Aaron's comments were addressed.
However, I still suggest to give time for @shafik to respond before landing this.
It seems to me that we shouldn't GET to this function with an incomplete type. I suspect whoever calls this is doing so incorrectly.