Skip to content

[lldb] Add Foundation._NSSwiftTimeZone support #7074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lldb/include/lldb/Target/LanguageRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class LanguageRuntime : public Runtime, public PluginInterface {

virtual lldb::LanguageType GetLanguageType() const = 0;

/// Return the preferred language runtime instance, which in most cases will
/// be the current instance.
virtual LanguageRuntime *GetPreferredLanguageRuntime(ValueObject &in_value) {
return nullptr;
}

virtual bool GetObjectDescription(Stream &str, ValueObject &object) = 0;

virtual bool GetObjectDescription(Stream &str, Value &value,
Expand Down
13 changes: 12 additions & 1 deletion lldb/source/Core/ValueObjectDynamicValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,18 @@ bool ValueObjectDynamicValue::UpdateValue() {
known_type != lldb::eLanguageTypeUnknown &&
known_type != lldb::eLanguageTypeC) {
runtime = process->GetLanguageRuntime(known_type);
if (runtime)
if (auto *preferred_runtime =
runtime->GetPreferredLanguageRuntime(*m_parent)) {
// Try the preferred runtime first.
found_dynamic_type = preferred_runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
if (found_dynamic_type)
// Set the operative `runtime` for later use in this function.
runtime = preferred_runtime;
}
if (!found_dynamic_type)
// Fallback to the runtime for `known_type`.
found_dynamic_type = runtime->GetDynamicTypeAndAddress(
*m_parent, m_use_dynamic, class_type_or_name, dynamic_address,
value_type);
Expand Down
14 changes: 14 additions & 0 deletions lldb/source/Plugins/Language/ObjC/Cocoa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ bool lldb_private::formatters::NSTimeZoneSummaryProvider(
stream.Printf("%s", summary_stream.GetData());
return true;
}
} else if (class_name == "_NSSwiftTimeZone") {
llvm::ArrayRef<ConstString> identifier_path = {
ConstString("timeZone"),
ConstString("_timeZone"),
ConstString("some"),
ConstString("identifier"),
};
if (auto identifier_sp = valobj.GetChildAtNamePath(identifier_path)) {
std::string desc;
if (identifier_sp->GetSummaryAsCString(desc, options)) {
stream.PutCString(desc);
return true;
}
}
}

return false;
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,11 @@ LoadFoundationValueTypesFormatters(lldb::TypeCategoryImplSP swift_category_sp) {
"Decimal summary provider", ConstString("Foundation.Decimal"),
TypeSummaryImpl::Flags(summary_flags).SetDontShowChildren(true));

lldb_private::formatters::AddCXXSummary(
swift_category_sp, lldb_private::formatters::NSTimeZoneSummaryProvider,
"NSTimeZone summary provider", ConstString("Foundation._NSSwiftTimeZone"),
TypeSummaryImpl::Flags(summary_flags).SetDontShowChildren(true));

lldb_private::formatters::AddCXXSynthetic(
swift_category_sp,
lldb_private::formatters::swift::URLComponentsSyntheticFrontEndCreator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

#include "lldb/Expression/FunctionCaller.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/Language.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/lldb-enumerations.h"

using namespace lldb;
using namespace lldb_private;
Expand Down Expand Up @@ -668,6 +670,19 @@ uint64_t ClassDescriptorV2::GetInstanceSize() {
return 0;
}

// From the ObjC runtime.
static uint8_t IS_SWIFT_STABLE = 1U << 1;

LanguageType ClassDescriptorV2::GetImplementationLanguage() const {
std::unique_ptr<objc_class_t> objc_class;
if (auto *process = m_runtime.GetProcess())
if (Read_objc_class(process, objc_class))
if (objc_class->m_flags & IS_SWIFT_STABLE)
return lldb::eLanguageTypeSwift;

return lldb::eLanguageTypeObjC;
}

ClassDescriptorV2::iVarsStorage::iVarsStorage() : m_ivars(), m_mutex() {}

size_t ClassDescriptorV2::iVarsStorage::size() { return m_ivars.size(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <mutex>

#include "AppleObjCRuntimeV2.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-private.h"

#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
Expand All @@ -34,6 +35,8 @@ class ClassDescriptorV2 : public ObjCLanguageRuntime::ClassDescriptor {
return true; // any Objective-C v2 runtime class descriptor we vend is valid
}

lldb::LanguageType GetImplementationLanguage() const override;

// a custom descriptor is used for tagged pointers
bool GetTaggedPointerInfo(uint64_t *info_bits = nullptr,
uint64_t *value_bits = nullptr,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "lldb/Target/ABI.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/LanguageRuntime.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
Expand Down Expand Up @@ -758,6 +759,19 @@ AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process,
RegisterObjCExceptionRecognizer(process);
}

LanguageRuntime *
AppleObjCRuntimeV2::GetPreferredLanguageRuntime(ValueObject &in_value) {
if (auto process_sp = in_value.GetProcessSP()) {
assert(process_sp.get() == m_process);
if (auto descriptor_sp = GetNonKVOClassDescriptor(in_value)) {
LanguageType impl_lang = descriptor_sp->GetImplementationLanguage();
if (impl_lang != eLanguageTypeUnknown)
return process_sp->GetLanguageRuntime(impl_lang);
}
}
return nullptr;
}

bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class AppleObjCRuntimeV2 : public AppleObjCRuntime {

static llvm::StringRef GetPluginNameStatic() { return "apple-objc-v2"; }

LanguageRuntime *GetPreferredLanguageRuntime(ValueObject &in_value) override;

static char ID;

bool isA(const void *ClassID) const override {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "lldb/Symbol/Type.h"
#include "lldb/Target/LanguageRuntime.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-private.h"

class CommandObjectObjC_ClassTable_Dump;
Expand Down Expand Up @@ -84,6 +85,11 @@ class ObjCLanguageRuntime : public LanguageRuntime {
return (m_is_cf == eLazyBoolYes);
}

/// Determine whether this class is implemented in Swift.
virtual lldb::LanguageType GetImplementationLanguage() const {
return lldb::eLanguageTypeObjC;
}

virtual bool IsValid() = 0;

/// There are two routines in the ObjC runtime that tagged pointer clients
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "lldb/Symbol/Variable.h"
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/ProcessStructReader.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
Expand All @@ -39,6 +40,7 @@
#include "swift/Remote/MemoryReader.h"
#include "swift/RemoteAST/RemoteAST.h"
#include "swift/Runtime/Metadata.h"
#include "swift/Strings.h"

#include <sstream>

Expand Down Expand Up @@ -2941,6 +2943,80 @@ SwiftLanguageRuntimeImpl::GetValueType(ValueObject &in_value,
return Value::ValueType::Scalar;
}

namespace {
struct SwiftNominalType {
std::string module;
std::string identifier;
};

// Find the Swift class that backs an ObjC type.
//
// A Swift class that uses the @objc(<ClassName>) attribute will emit ObjC
// metadata into the binary. Typically, ObjC classes have a symbol in the form
// of OBJC_CLASS_$_<ClassName>, however for Swift classes, there are two symbols
// that both point to the ObjC class metadata, where the second symbol is a
// Swift mangled name.
std::optional<SwiftNominalType> GetSwiftClass(ValueObject &valobj,
AppleObjCRuntime &objc_runtime) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be one ore two opportunities for comments in this function to explain the algorithm :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// To find the Swift symbol, the following preparation steps are taken:
// 1. Get the value's ISA pointer
// 2. Resolve the ISA load address into an Address instance
// 3. Get the Module that contains the Address
auto descriptor_sp = objc_runtime.GetClassDescriptor(valobj);
if (!descriptor_sp)
return {};

auto isa_load_addr = descriptor_sp->GetISA();
Address isa;
const auto &sections = objc_runtime.GetTargetRef().GetSectionLoadList();
if (!sections.ResolveLoadAddress(isa_load_addr, isa))
return {};

// Next, iterate over the Module's symbol table, looking for a symbol with
// following criteria:
// 1. The symbol address is the ISA address
// 2. The symbol name is a Swift mangled name
std::optional<StringRef> swift_symbol;
auto find_swift_symbol_for_isa = [&](Symbol *symbol) {
if (symbol->GetAddress() == isa) {
StringRef symbol_name =
symbol->GetMangled().GetMangledName().GetStringRef();
if (SwiftLanguageRuntime::IsSwiftMangledName(symbol_name)) {
swift_symbol = symbol_name;
return false;
}
}
return true;
};

isa.GetModule()->GetSymtab()->ForEachSymbolContainingFileAddress(
isa.GetFileAddress(), find_swift_symbol_for_isa);
if (!swift_symbol)
return {};

// Once the Swift symbol is found, demangle it into a node tree. The node tree
// provides the final data, the name of the class and the name of its module.
swift::Demangle::Context ctx;
auto *global = ctx.demangleSymbolAsNode(*swift_symbol);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do this right away — but for readability this could be a helper in SwiftDemangle.h?

using Kind = Node::Kind;
auto *class_node = swift_demangle::nodeAtPath(
global, {Kind::TypeMetadata, Kind::Type, Kind::Class});
if (class_node && class_node->getNumChildren() == 2) {
auto module_node = class_node->getFirstChild();
auto ident_node = class_node->getLastChild();
if (module_node->getKind() == Kind::Module && module_node->hasText() &&
ident_node->getKind() == Kind::Identifier && ident_node->hasText()) {
auto module_name = module_node->getText();
auto class_name = ident_node->getText();
return SwiftNominalType{module_name.str(), class_name.str()};
}
}

return {};
}

} // namespace

bool SwiftLanguageRuntimeImpl::GetDynamicTypeAndAddress_ClangType(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &address,
Expand All @@ -2967,9 +3043,22 @@ bool SwiftLanguageRuntimeImpl::GetDynamicTypeAndAddress_ClangType(
dyn_name.startswith("__NS"))
return false;

SwiftNominalType swift_class;

if (auto maybe_swift_class = GetSwiftClass(in_value, *objc_runtime)) {
swift_class = *maybe_swift_class;
std::string type_name =
(llvm::Twine(swift_class.module) + "." + swift_class.identifier).str();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this shorter?

llvm::raw_string_ostream(type_name)<<swift_class.module<<"."<<swift_class.identifier;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately it's not

dyn_class_type_or_name.SetName(type_name.data());
address.SetRawAddress(in_value.GetPointerValue());
} else {
swift_class.module = swift::MANGLING_MODULE_OBJC;
swift_class.identifier = dyn_name;
}

std::string remangled;
{
// Create a mangle tree for __C.dyn_name?.
// Create a mangle tree for Swift.Optional<$module.$class>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be a few helper functions (in a follow-up commit).

using namespace swift::Demangle;
NodeFactory factory;
NodePointer global = factory.createNode(Node::Kind::Global);
Expand All @@ -2980,7 +3069,8 @@ bool SwiftLanguageRuntimeImpl::GetDynamicTypeAndAddress_ClangType(
NodePointer ety = factory.createNode(Node::Kind::Type);
bge->addChild(ety, factory);
NodePointer e = factory.createNode(Node::Kind::Enum);
e->addChild(factory.createNode(Node::Kind::Module, "Swift"), factory);
e->addChild(factory.createNode(Node::Kind::Module, swift::STDLIB_NAME),
factory);
e->addChild(factory.createNode(Node::Kind::Identifier, "Optional"),
factory);
ety->addChild(e, factory);
Expand All @@ -2989,8 +3079,11 @@ bool SwiftLanguageRuntimeImpl::GetDynamicTypeAndAddress_ClangType(
NodePointer cty = factory.createNode(Node::Kind::Type);
list->addChild(cty, factory);
NodePointer c = factory.createNode(Node::Kind::Class);
c->addChild(factory.createNode(Node::Kind::Module, "__C"), factory);
c->addChild(factory.createNode(Node::Kind::Identifier, dyn_name), factory);
c->addChild(factory.createNode(Node::Kind::Module, swift_class.module),
factory);
c->addChild(
factory.createNode(Node::Kind::Identifier, swift_class.identifier),
factory);
cty->addChild(c, factory);

auto mangling = mangleNode(global);
Expand Down
20 changes: 20 additions & 0 deletions lldb/test/API/lang/swift/objc/dynamic-swift-type/App.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation

struct Document {
var kind: Kind
var path: String

enum Kind {
case binary
case text
}
}

@objc(App)
class App : NSObject {
var name: String = "Debugger"
var version: (Int, Int) = (1, 0)
var recentDocuments: [Document]? = [
Document(kind: .binary, path: "/path/to/something"),
]
}
5 changes: 5 additions & 0 deletions lldb/test/API/lang/swift/objc/dynamic-swift-type/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OBJC_SOURCES := main.m
SWIFT_SOURCES := App.swift
SWIFTFLAGS_EXTRAS := -parse-as-library

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil


class TestCase(TestBase):
@skipUnlessFoundation
@swiftTest
def test(self):
"""Verify printing of Swift implemented ObjC objects."""
self.build()
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))

# For Swift implemented objects, it's assumed the ObjC runtime prints
# only a pointer, and cannot generate any child values.
self.expect("v -d no app", startstr="(App *) app = 0x")
self.expect(
"v -d no -P1 app",
matching=False,
substrs=["name", "version", "recentDocuments"],
)

# With dynamic typing, the Swift runtime produces Swift child values.
self.expect("v app", substrs=["App?) app = 0x"])
self.expect("v app.name", startstr='(String) app.name = "Debugger"')
self.expect(
"v app.version", startstr="((Int, Int)) app.version = (0 = 1, 1 = 0)"
)

documents = """\
([a.Document]?) app.recentDocuments = 1 value {
[0] = {
kind = binary
path = "/path/to/something"
}
}
"""
self.expect("v app.recentDocuments", startstr=documents)
10 changes: 10 additions & 0 deletions lldb/test/API/lang/swift/objc/dynamic-swift-type/main.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>

@interface App : NSObject
@end

int main() {
App *app = [App new];
printf("break here\n");
return 0;
}