This is an archive of the discontinued LLVM Phabricator instance.

[AST] Fix a crash on accessing a class without definition in constexpr function context.
ClosedPublic

Authored by hokein on Jun 2 2020, 12:33 AM.

Diff Detail

Event Timeline

hokein created this revision.Jun 2 2020, 12:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 2 2020, 12:33 AM
sammccall added inline comments.Jun 2 2020, 1:48 AM
clang/lib/AST/ExprConstant.cpp
4482

This doesn't look all that safe - you're using a None value to indicate failure, but no current code path does that and none of the callers seem to check for failure.
(e.g. evaluateVarDecl returns true instead of false).
Presumably we're going to get a diagnostic somewhere (though it's not completely obvious to me) but can we be sure we won't assume this value has the right type somewhere down the line?

I get the feeling this is correct and I don't have enough context to understand why... how about you :-)

hokein marked an inline comment as done.Jun 2 2020, 6:15 AM
hokein added a subscriber: rsmith.
hokein added inline comments.
clang/lib/AST/ExprConstant.cpp
4482

I don't have a promising explanation neither.

I didn't find a better way to model failures in getDefaultInitValue. This function is used in multiple places of this file (and I'm not sure whether we should change it).

@rsmith any thoughts?

rsmith added inline comments.Jun 15 2020, 4:00 PM
clang/lib/AST/ExprConstant.cpp
4482

APValue() is a valid representation for an object of class type, representing a class object that is outside its lifetime, so I think it's OK to use this representation, if we can be sure that this only happens along error paths. (It's not ideal, though.)

If we can't be sure this happens only along error paths, then we should produce a diagnostic here. Perhaps feed an EvalInfo& and a source location into every caller of this function and produce a diagnostic if we end up querying the default-initialized value of an incomplete-but-valid class type. Or perhaps we could check that the class is complete and valid from every caller of this function instead. (I think that we guarantee that, for a valid complete class type, all transitive subobjects are of valid complete types, so checking this only once at the top level before calling into getDefaultInitValue should be enough.)

hokein marked an inline comment as done.Jun 17 2020, 2:24 AM
hokein added inline comments.
clang/lib/AST/ExprConstant.cpp
4482

Thanks for the suggestions.

oh, yeah, I missed that the Foo Decl is invalid, so checking the class decl is valid at every caller of getDefaultInitValue should work -- it would also fix other potential issues, looks like here we guarantee that the VarDecl is valid, but don't verify the decl which the VarDecl's type refers to is valid in all callers.

Given the fact that the VarDecl e is valid and class Foo Decl is invalid, another option to fix the crash is to invalidate this VarDecl. Should we invalidate the VarDecl if the type of the VarDecl refers to an invalid decl? My gut feeling is that it is worth keeping the VarDecl valid, so that more related AST nodes will be built (for better recovery and diagnostics), though it seems unsafe.

rsmith added inline comments.Jun 18 2020, 11:47 AM
clang/lib/AST/ExprConstant.cpp
4482

I think keeping the VarDecl valid is probably the better choice, to allow us to build downstream uses of it. Also, because variables can be redeclared, we could have something like struct A; extern A v; struct A { invalid; }; -- and we can't reasonably retroactively mark v as invalid in this case, so we can't guarantee that the type of every valid variable is itself valid. (We *could* guarantee that the type of every valid variable *definition* is valid, but that will lead to inconsistencies where defining the variable causes later behavior of references to the variable to change.)

It's really unfortunate that we don't have a good definition of what "valid" means for a variable, or really any listing of what invariants we maintain in the AST in the presence of invalid nodes. :( This is one of the things I would work on if I had time...

hokein updated this revision to Diff 272018.Jun 19 2020, 4:54 AM
hokein marked an inline comment as done.

Update per Richard's comment: do the error check in getDefaultInitValue, and
modify every caller of it to support the error handling.

hokein updated this revision to Diff 272019.Jun 19 2020, 4:56 AM

cleanup the debug code.

hokein added inline comments.
clang/lib/AST/ExprConstant.cpp
4482

I think keeping the VarDecl valid is probably the better choice, to allow us to build downstream uses of it. Also, because variables can be redeclared, we could have something like struct A; extern A v; struct A { invalid; }; -- and we can't reasonably retroactively mark v as invalid in this case, so we can't guarantee that the type of every valid variable is itself valid. (We *could* guarantee that the type of every valid variable *definition* is valid, but that will lead to inconsistencies where defining the variable causes later behavior of references to the variable to change.

yeah, that makes sense, thanks for the explanation.

I have updated the patch -- now the getDefaultInitValue() does error check. If fails, return APValue() which will only happen on error paths. Since it changes non-trivial amount of code, would be nice if you can take a look.

It's really unfortunate that we don't have a good definition of what "valid" means for a variable, or really any listing of what invariants we maintain in the AST in the presence of invalid nodes. :( This is one of the things I would work on if I had time...

that would be nice to have, and given that we have containsErrors, the meanings of them are subtle (sometimes I got confused by these concepts). Would you like me to help here? happy to help though I don't fully understand clang yet.

rsmith accepted this revision.Jun 24 2020, 2:23 PM
rsmith marked an inline comment as done.

Minor suggestions but this LGTM.

clang/lib/AST/ExprConstant.cpp
4478–4480

It would be useful to mention here that this only happens if we encounter something invalid (so that callers know they don't need to produce a nice diagnostic).

4482

Would you like me to help here? happy to help though I don't fully understand clang yet.

If you're motivated, this might be a good way to learn more about Clang :) You'll certainly discover things that no-one knows about Clang (and a fair few bugs) if you add something like the LLVM IR verifier for the Clang AST.

5947–5951

Nit: you're inconsistently using if (!get) Success = false; here and Success &= get; above and below. I'd prefer it if you expressed this the same way in all three cases in this function.

9086

This is the only place where we produce a diagnostic after getDefaultInitValue fails. It'd be more consistent to just return false here.

This revision is now accepted and ready to land.Jun 24 2020, 2:23 PM
hokein updated this revision to Diff 273281.Jun 25 2020, 2:56 AM
hokein marked 5 inline comments as done.

address comments.

Thanks for the review!

clang/lib/AST/ExprConstant.cpp
4482

oh, I thought it's just something low-hanging fruit like clarifying comments/documentation.

Adding verifier to clang AST looks promising and ambitious (personally I like it), but would probably require a lot of work.

5947–5951

oops, made it consistent with Success &=

This revision was automatically updated to reflect the committed changes.