This is an archive of the discontinued LLVM Phabricator instance.

[clang][Interp] Track existing InitMaps in InterpState
ClosedPublic

Authored by tbaeder on Jul 6 2023, 1:53 AM.

Details

Summary

InitMaps are created for primitive arrays when the first element of the arrays is initialized, and they are free'd when the last element is initialized, leaving the array in a fully initialized state.

The initmap is also free'd in the Descriptor's destructor function, which is called at the end of a scope.

This works fine when the execution of the program is not interrupted. However, when it is, we never destroy the scopes, leaving the initmaps behind.

To fix this, track the initmaps in InterpState and free them manually in ~InterpState(), so we don't leak the memory and fail on LSan enabled builders.

(Side note: There is a similar problem with the current handling of floating point numbers, i.e. the APFloat might heap allocate some memory and we will leak this when the execution is interrupted).

Diff Detail

Event Timeline

tbaeder created this revision.Jul 6 2023, 1:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 6 2023, 1:53 AM
tbaeder requested review of this revision.Jul 6 2023, 1:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 6 2023, 1:53 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
tbaeder added a comment.EditedJul 10 2023, 1:34 AM

One problem this does not fix (but rather introduce) is that the InterpState will also free initmaps of global variables which haven't been fully initialized. When the initialization fails midway through, the InterpState will free the InitMap, leaving the global variable with a dangling pointer to an InitMap.

One problem this does not fix (but rather introduce) is that the InterpState will also free initmaps of global variables which haven't been fully initialized. When the initialization fails midway throught, the InterpState will free the InitMap, leaving the global variable with a dangling pointer to an InitMap.

That seems like a pretty dangerous thing to leave in the interpreter; should we be using a shared_ptr for InitMap? Or perhaps it should be moved into Program rather than left in InterpState?

tbaeder updated this revision to Diff 546872.Aug 3 2023, 7:48 AM
  1. Use a shared_ptr for the initmaps.
  2. However, we allocate them into the Block, therefore can't forget to call the Block's dtor.
  3. To distinguish between Blocks we've already deallocated (via a destroy op), add an IsInitialized flag that we set in invokeCtor and unset in invokeDtor.

Ping

clang/test/AST/Interp/literals.cpp
1047 ↗(On Diff #546872)

Ignore this block, that's just from rebasing.

tbaeder updated this revision to Diff 551162.Aug 17 2023, 8:56 AM
tbaeder abandoned this revision.Sep 5 2023, 2:03 AM
tbaeder reclaimed this revision.

Ping

aaron.ballman added inline comments.
clang/lib/AST/Interp/Descriptor.cpp
43

This worries me a little bit for a few reasons, but it might be okay:

  • What validates that the bytes pointed to by Ptr are aligned properly for an InitMapPtr object? Perhaps we need an alignas in the function signature to ensure correct alignment of those bytes?
  • InitMapPtr is std::optional<std::pair<bool, std::shared_ptr<InitMap>>> and I *think* using placement new will ensure we have correct objects in all the expected places, but I'm not 100% sure because we're calling the nullopt constructor here.
  • I *think* it is correct that you are not assigning the result of the placement new expression into anything; and I think we need this placement new because of the reinterpret_cast happening in dtorArrayTy(). But it is a bit strange to see the placement new hanging off on its own like this. Might be worth some comments explaining.

CC @hubert.reinterpretcast @rsmith in case my assessment is incorrect.

clang/lib/AST/Interp/Descriptor.h
205

This interface is now more confusing as to how to use because it now has a public non-default constructor *and* an initialize method. Perhaps we should rename initialize to initializeMapElement so it's clear that the initialize method isn't for the map itself?

223
tbaeder updated this revision to Diff 557670.Oct 10 2023, 5:22 AM
tbaeder marked an inline comment as done.
tbaeder added inline comments.Oct 10 2023, 5:24 AM
clang/lib/AST/Interp/Descriptor.cpp
43

I thought using placement new would just call the normal constructors anyway?

BTW, does using a shared_ptr here even make sense? Since this is allocated in the Block, we need to call the constructor and destructors manually anyway.

aaron.ballman added inline comments.Oct 11 2023, 6:58 AM
clang/lib/AST/Interp/Descriptor.cpp
43

I thought using placement new would just call the normal constructors anyway?

It should, so I think all the lifetime issues are accounted for, but this area of C++ is poorly understood at the best of times.

BTW, does using a shared_ptr here even make sense? Since this is allocated in the Block, we need to call the constructor and destructors manually anyway.

I think it makes sense as we're intentionally sharing the pointer, aren't we? So we'll call the ctors/dtors manually in this case, but other objects holding the shared pointer don't have to worry about the pointer being yanked out from under them.

tbaeder updated this revision to Diff 557773.Oct 19 2023, 5:47 AM
tbaeder added inline comments.
clang/lib/AST/Interp/Descriptor.h
205

Made more things private and renamed initialize() and isInitialized().

aaron.ballman added inline comments.Oct 19 2023, 10:06 AM
clang/lib/AST/Interp/Descriptor.cpp
327–328

Inline these into the header now?

Even though these are private methods, I'm a bit worried about handing out a naked pointer that comes from a unique_ptr as that's pretty easy to run into lifetime issues with, but I think it's okay as-is (I don't have a vastly better approach.)

clang/lib/AST/Interp/Descriptor.h
207–210

Now that this is public, can we make it explicit so we don't get accidental conversions from unsigned?

tbaeder updated this revision to Diff 557801.Oct 19 2023, 11:43 PM
tbaeder marked an inline comment as done.Oct 19 2023, 11:48 PM
tbaeder added inline comments.
clang/lib/AST/Interp/Descriptor.h
207–210

Sure.

tbaeder marked an inline comment as done.Oct 20 2023, 2:52 AM
This revision is now accepted and ready to land.Oct 23 2023, 6:43 AM
This revision was landed with ongoing or failed builds.Oct 23 2023, 9:27 PM
This revision was automatically updated to reflect the committed changes.