This is an archive of the discontinued LLVM Phabricator instance.

Don't allow ValueObject::Cast from a smaller type to a larger
ClosedPublic

Authored by jingham on Jun 23 2023, 11:53 AM.

Details

Summary

SBValue::Cast actually allows casting from a struct type to another struct type. That's a little odd, C-family languages don't allow this, but we have done forever, so I don't want to remove the ability altogether. However, we can't support casting from a small structure to a larger one because in some cases - e.g. all the ConstResult types used both for expression results and for many synthetic children - we don't know where we should fetch the extra bits from. Just zero-filling them seems odd, and error seems like a better response.

This fixes a bug where when casting an expression result from a smaller type to a larger, lldb would present the memory in lldb after the ValueObject's data buffer as the value of the cast type. Again, I could have fixed that bug by expanding the data buffer to match the larger size, but I wouldn't know what to fill it with.

There were two places in the C++ formatters that were using this cast from struct to struct type to change a C++ std typedef (e.g. std::shared_ptr<Foo>::element type *) to a type that is more useful to the user (pointer to the first template argument's type). The cast from struct to struct in that case wasn't necessary, and looked weird since this really isn't an allowed C++ operation. So I also changed those to case the pointer first, then dereference the cast value.

Diff Detail

Event Timeline

jingham created this revision.Jun 23 2023, 11:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 23 2023, 11:54 AM
jingham requested review of this revision.Jun 23 2023, 11:54 AM
Herald added a project: Restricted Project. · View Herald TranscriptJun 23 2023, 11:54 AM
This revision is now accepted and ready to land.Jun 23 2023, 1:49 PM

I think this patch is probably okay to do, but it does break a supported use case that I'm aware of: One way you can do "object oriented programming" in C is to do exactly what you're trying to prevent. Using the test, you could say that "MyStruct" is the base class and "MyBiggerStruct" would be a derived class. You might have a pointer to a "MyStruct" object, even though you called malloc(sizeof(MyBiggerStruct)), and maybe you can perform that cast if you're sure that you actually have a MyBiggerStruct object.

I know there's not necessarily a good way to support that without also supporting the bug you're trying to fix though. :(

lldb/include/lldb/Core/ValueObject.h
617–620

I'm a little concerned about the API surface here. Maybe this is just my misunderstandings about ValueObject, but...

Given a ValueObjectSP (which may contain a ValueObject, ValueObjectConstResult, etc), which one of these are you supposed to call? If you call Cast, you'll get what you want. If you call DoCast, you might circumvent the safety checks that Cast is providing... but if you have something like a ValueObjectConstResult, this actually calls ValueObject::Cast which does the right thing. Am I understanding this correctly?

I also have a little confusion about which one I would call based on the names... Maybe it would make more sense to call DoCast something like CastImpl?

lldb/source/Core/ValueObject.cpp
2792

What is the purpose of this bool unused?

I think this patch is probably okay to do, but it does break a supported use case that I'm aware of: One way you can do "object oriented programming" in C is to do exactly what you're trying to prevent. Using the test, you could say that "MyStruct" is the base class and "MyBiggerStruct" would be a derived class. You might have a pointer to a "MyStruct" object, even though you called malloc(sizeof(MyBiggerStruct)), and maybe you can perform that cast if you're sure that you actually have a MyBiggerStruct object.

I know there's not necessarily a good way to support that without also supporting the bug you're trying to fix though. :(

You can't actually cast structures to structures in C. You can only cast pointers. So in your underlying code you always have to have pointers around if you are playing these games, and you can cast the pointer to the derived type and then dereference it, which is what you would have had to do in the source language anyway.

lldb/include/lldb/Core/ValueObject.h
617–620

We use this naming convention pretty consistently in lldb when there's an overridden method that we need to do some generic preparation for. We use Action for the public call, and DoAction as the overridden one. I could put in some words in the header, but this is a pretty well-established convention: in lldb, you should never call the DoAction version unless you are the Action wrapper.

lldb/source/Core/ValueObject.cpp
2792

That's leftover from a previous overly-complex solution.

I think this patch is probably okay to do, but it does break a supported use case that I'm aware of: One way you can do "object oriented programming" in C is to do exactly what you're trying to prevent. Using the test, you could say that "MyStruct" is the base class and "MyBiggerStruct" would be a derived class. You might have a pointer to a "MyStruct" object, even though you called malloc(sizeof(MyBiggerStruct)), and maybe you can perform that cast if you're sure that you actually have a MyBiggerStruct object.

I know there's not necessarily a good way to support that without also supporting the bug you're trying to fix though. :(

You can't actually cast structures to structures in C. You can only cast pointers. So in your underlying code you always have to have pointers around if you are playing these games, and you can cast the pointer to the derived type and then dereference it, which is what you would have had to do in the source language anyway.

Right, this makes sense, that was my disconnect. Thanks for clarifying and helping me understand!