This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Copy ScriptedInterface object instance to a new StructuredData object
AbandonedPublic

Authored by mib on Jan 12 2022, 11:59 AM.

Details

Summary

This patch adds the ability to pass a pointer of a script object instance
to initialize a ScriptedInterface instead of having the call the
script object initializer in the ScriptedInterface constructor.

rdar://87425859

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>

Diff Detail

Event Timeline

mib requested review of this revision.Jan 12 2022, 11:59 AM
mib created this revision.
labath added a comment.EditedJan 13 2022, 5:49 AM

I'd like to remind everyone of a not-very-widely-known, but incredibly nifty feature of std::shared_ptr, called "aliasing". It allows one to create a shared_ptr which points to one object, but deletes a completely different object when it goes out of scope. It works like so:

std::shared_ptr<A> a = std::make_shared<A>(...); // or whatever
std::shared_ptr<B> b(a, getB(a)); // will point to a B, but call `delete A` when it goes out of scope

The model use case is when the first object is a subobject (field) of the second one (so we'd have struct A { B b; ... }; and getB(a) would be &a->b), but that is not a a requirement. B can be a completely arbitrary object -- the only requirement is that B remains live for as long as A is around.

Now, I don't understand this code well enough to say whether that could/should be used here, but the (few) parts which I did understand led me to think that there are some subobjects (in the broadest sense of the word) being passed around, and so I have a feeling we should at least consider this option -- it seems like it would be better to pass around objects with explicit ownership semantics instead of raw pointers.

mib added a comment.Jan 13 2022, 6:15 AM

I'd like to remind everyone of a not-very-widely-known, but incredibly nifty feature of std::shared_ptr, called "aliasing". It allows one to create a shared_ptr which points to one object, but deletes a completely different object when it goes out of scope. It works like so:

std::shared_ptr<A> a = std::make_shared<A>(...); // or whatever
std::shared_ptr<B> b(a, getB(a)); // will point to a B, but call `delete A` when it goes out of scope

The model use case is when the first object is a subobject (field) of the second one (so we'd have struct A { B b; ... }; and getB(a) would be &a->b), but that is not a a requirement. B can be a completely arbitrary object -- the only requirement is that B remains live for as long as A is around.

Very neat indeed, but I don't think it will be of any use here unfortunately: IIUC in your example, b is the first to go out-of-scope which calls a destructor. In my case, a when out of scope but it still referenced by b.

Now, I don't understand this code well enough to say whether that could/should be used here, but the (few) parts which I did understand led me to think that there are some subobjects (in the broadest sense of the word) being passed around, and so I have a feeling we should at least consider this option -- it seems like it would be better to pass around objects with explicit ownership semantics instead of raw pointers.

I agree with you on theory but here, the ScriptedThreadInterface tries to be language-agnostic that's why I pass StructuredData::GenericSP around.

mib abandoned this revision.Jan 13 2022, 6:24 AM

Following @JDevlieghere's request, the patch will be merged with D117071 since it makes more sense to have both changes together.

I'd like to remind everyone of a not-very-widely-known, but incredibly nifty feature of std::shared_ptr, called "aliasing". It allows one to create a shared_ptr which points to one object, but deletes a completely different object when it goes out of scope. It works like so:

std::shared_ptr<A> a = std::make_shared<A>(...); // or whatever
std::shared_ptr<B> b(a, getB(a)); // will point to a B, but call `delete A` when it goes out of scope

The model use case is when the first object is a subobject (field) of the second one (so we'd have struct A { B b; ... }; and getB(a) would be &a->b), but that is not a a requirement. B can be a completely arbitrary object -- the only requirement is that B remains live for as long as A is around.

Very neat indeed, but I don't think it will be of any use here unfortunately: IIUC in your example, b is the first to go out-of-scope which calls a destructor. In my case, a when out of scope but it still referenced by b.

That is perfectly fine. All of the shared pointers created in this way share their reference counts. It doesn't matter which one goes out of scope first, but when the last one (whichever it is) does, it will call delete on the original object (a). (It is then expected that the deletion of a will somehow ensure that b gets deleted as well -- that is trivially true if b is a member, but may need additional logic in other cases.)
So, if the following holds:

  • a is necessary for the proper usage of b (e.g. because b holds a pointer back to a)
  • deletion of a will (should) result in deletion of b

then you can still use this pattern.

Now, I don't understand this code well enough to say whether that could/should be used here, but the (few) parts which I did understand led me to think that there are some subobjects (in the broadest sense of the word) being passed around, and so I have a feeling we should at least consider this option -- it seems like it would be better to pass around objects with explicit ownership semantics instead of raw pointers.

I agree with you on theory but here, the ScriptedThreadInterface tries to be language-agnostic that's why I pass StructuredData::GenericSP around.

GenericSP is fine(ish), but in this patch i see a lot of void*'s floating around (which (for an uninitiated reader) is even worse than a raw pointer, since he doesn't even know what kind of an object is that referring to, let alone the ownership semantics). btw, std::shared_ptr<void> is also a thing.

mib added a comment.Jan 13 2022, 6:56 AM

I'd like to remind everyone of a not-very-widely-known, but incredibly nifty feature of std::shared_ptr, called "aliasing". It allows one to create a shared_ptr which points to one object, but deletes a completely different object when it goes out of scope. It works like so:

std::shared_ptr<A> a = std::make_shared<A>(...); // or whatever
std::shared_ptr<B> b(a, getB(a)); // will point to a B, but call `delete A` when it goes out of scope

The model use case is when the first object is a subobject (field) of the second one (so we'd have struct A { B b; ... }; and getB(a) would be &a->b), but that is not a a requirement. B can be a completely arbitrary object -- the only requirement is that B remains live for as long as A is around.

Very neat indeed, but I don't think it will be of any use here unfortunately: IIUC in your example, b is the first to go out-of-scope which calls a destructor. In my case, a when out of scope but it still referenced by b.

That is perfectly fine. All of the shared pointers created in this way share their reference counts. It doesn't matter which one goes out of scope first, but when the last one (whichever it is) does, it will call delete on the original object (a). (It is then expected that the deletion of a will somehow ensure that b gets deleted as well -- that is trivially true if b is a member, but may need additional logic in other cases.)
So, if the following holds:

  • a is necessary for the proper usage of b (e.g. because b holds a pointer back to a)
  • deletion of a will (should) result in deletion of b

then you can still use this pattern.

Very cool stuff! I'll see if/how I can use this on my patch. May be I can make the StructuredData::GenericSP "alias" the underlying smart pointer, so we don't leak memory when deleting it.

Now, I don't understand this code well enough to say whether that could/should be used here, but the (few) parts which I did understand led me to think that there are some subobjects (in the broadest sense of the word) being passed around, and so I have a feeling we should at least consider this option -- it seems like it would be better to pass around objects with explicit ownership semantics instead of raw pointers.

I agree with you on theory but here, the ScriptedThreadInterface tries to be language-agnostic that's why I pass StructuredData::GenericSP around.

GenericSP is fine(ish), but in this patch i see a lot of void*'s floating around (which (for an uninitiated reader) is even worse than a raw pointer, since he doesn't even know what kind of an object is that referring to, let alone the ownership semantics). btw, std::shared_ptr<void> is also a thing.

I've never considered that 🤯 I'll update D117071 to turn the void* into std::shared_ptr<void>.

Thanks!

mib added a comment.Jan 13 2022, 9:55 AM

I've never considered that 🤯 I'll update D117071 to turn the void* into std::shared_ptr<void>.

I tried doing that (see attached patch below) but it doesn't work because even though the StructuredData::Generic is created with a PyObject*, it's stored as a void* and is retrieved in the ScriptedThread constructor using void * StructuredData::Generic::GetValue() const.

Making a std::shared_ptr<void> out of a void * doesn't work because it's an incomplete type.

@labath If you have any other suggestion, please let me know :-)

Yes, you can't "turn" a void* into a shared_ptr<void> because the shared pointer would not know how to delete the pointed object. However, you can create a pointer which is /aliased/ to a void* and use that for something.

That said, I am (still) not sure if that is what you really want to do. And the more I look at this patch, the more it seems to me that there is still something wrong with the ownerships. Maybe we could start by clarifying what the ownership of various object really is, then seeing if we have some tools that would make that explicit. I'll continue this on the merged patch.