This is an archive of the discontinued LLVM Phabricator instance.

[Sema] Fix crash when evaluating nested call with value-dependent arg
ClosedPublic

Authored by Pierre-vh on Dec 9 2022, 6:31 AM.

Details

Summary

Fix an edge case ExprConstant.cpp's EvaluateWithSubstitution when called by CheckEnableIf

The assertion in CallStackFrame::getTemporary
could fail during evaluation of nested calls to a function
using enable_if when the second argument was a
value-dependent expression.

This caused a temporary to be created for the second
argument with a given version during the
evaluation of the inner call, but we bailed out
when evaluating the second argument of the
outer call due to the expression being value-dependent.
After bailing out, we tried to clean up the argument's value slot but it
caused an assertion to trigger in getTemporary as
a temporary for the second argument existed, but only for the inner call and not the outer call.

See the test case for a more complete description of the issue.

Diff Detail

Event Timeline

Pierre-vh created this revision.Dec 9 2022, 6:31 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 9 2022, 6:31 AM
Pierre-vh requested review of this revision.Dec 9 2022, 6:31 AM
Herald added a project: Restricted Project. · View Herald TranscriptDec 9 2022, 6:31 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
Pierre-vh updated this revision to Diff 481626.Dec 9 2022, 6:31 AM

Missing newline at EOF

I think the right choice is to remove the assert as it's an invariant that can be broken in some cases, so I don't feel like the assert is worth it anymore.
Alternatively I could add something to bypass the assert in that specific call to getParamSlot for Value-Dependent expressions, it's a more targeted fix in case you think the assertion must be left in.

shafik added a subscriber: shafik.Dec 12 2022, 9:30 AM

If I am reading the code correctly it looks like if the call to (*I)->isValueDependent() is true then the temporary will not be created and therefore we should not be attempting to access the slot.

If this is the case then maybe the checking in EvaluateWithSubstitution(...) needs to be more carefully done?

I am not familiar with this code but I don't know if you analysis provides a convincing case the assert should be removed.

shafik added a reviewer: Restricted Project.Dec 12 2022, 9:31 AM

If I am reading the code correctly it looks like if the call to (*I)->isValueDependent() is true then the temporary will not be created and therefore we should not be attempting to access the slot.

If this is the case then maybe the checking in EvaluateWithSubstitution(...) needs to be more carefully done?

I am not familiar with this code but I don't know if you analysis provides a convincing case the assert should be removed.

Indeed, this only happens when isValueDependent returns true.

I am also not familiar with the code, so I just decided to propose a quick fix to get the discussion started; I certainly don't mind changing the nature of the fix if we agree it should be fixed differently.
For instance, we could also make the "getParam" call faillible by adding some "tryGetParam" variant that doesn't have the assert, or by passing some optional boolean to indicate it's acceptable to have the key present in the map with a different version.

My initial reasoning was that if the assert can be broken by legitimate code, then it shouldn't be there

If I am reading the code correctly it looks like if the call to (*I)->isValueDependent() is true then the temporary will not be created and therefore we should not be attempting to access the slot.

If this is the case then maybe the checking in EvaluateWithSubstitution(...) needs to be more carefully done?

I am not familiar with this code but I don't know if you analysis provides a convincing case the assert should be removed.

Indeed, this only happens when isValueDependent returns true.

I am also not familiar with the code, so I just decided to propose a quick fix to get the discussion started; I certainly don't mind changing the nature of the fix if we agree it should be fixed differently.
For instance, we could also make the "getParam" call faillible by adding some "tryGetParam" variant that doesn't have the assert, or by passing some optional boolean to indicate it's acceptable to have the key present in the map with a different version.

My initial reasoning was that if the assert can be broken by legitimate code, then it shouldn't be there

It looks like the original commit that added getTemporary(...) which was 4e2698ca9e49e that this invariant held but the later commit which added getParamSlot(...) which is f7f2e4261a98b broke the invariant.

So I think that looks like an error and any fix should maintain the invariant unless strongly motivated reasoning can be put forward to remove it but I am not totally sure.

CC @rsmith @ahatanak who are the authors of the two commits mentioned above.

Put the assert back in, use alternative fix

I read the patches and review comments in https://reviews.llvm.org/D42776, but I don't remember why I added that assert. Maybe I was just trying to ensure the version number passed to getTemporary was the one that was used to create the temporary. For example, if a temporary was created using version X and later retrieved using version Y because of some bug in constant evaluation, the assertion would fail.

But the assertion is wrong, so maybe it should just be removed.

Pierre-vh updated this revision to Diff 486183.Jan 4 2023, 12:34 AM

Remove assert
Please review @ahatanak

ahatanak accepted this revision.Jan 5 2023, 4:45 PM
LGTM
This revision is now accepted and ready to land.Jan 5 2023, 4:45 PM
This revision was landed with ongoing or failed builds.Jan 5 2023, 11:57 PM
This revision was automatically updated to reflect the committed changes.