Skip to content

Add _mangledTypeName to allow round trips T->mangledName->T #30318

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
merged 3 commits into from
Mar 31, 2020
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
9 changes: 8 additions & 1 deletion include/swift/Runtime/HeapObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,14 @@ struct TypeNamePair {
/// -> (UnsafePointer<UInt8>, Int)
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
TypeNamePair
swift_getTypeName(const Metadata *type, bool qualified);
swift_getTypeName(const Metadata *type, bool qualified);

/// Return the mangled name of a Swift type represented by a metadata object.
/// func _getMangledTypeName(_ type: Any.Type)
/// -> (UnsafePointer<UInt8>, Int)
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API
TypeNamePair
swift_getMangledTypeName(const Metadata *type);

} // end namespace swift

Expand Down
22 changes: 22 additions & 0 deletions stdlib/public/core/Misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ func _typeName(_ type: Any.Type, qualified: Bool = true) -> String {
UnsafeBufferPointer(start: stringPtr, count: count)).0
}

@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
@_silgen_name("swift_getMangledTypeName")
public func _getMangledTypeName(_ type: Any.Type)
-> (UnsafePointer<UInt8>, Int)

/// Returns the mangled name for a given type.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
public // SPI
func _mangledTypeName<T>(_ type: T.Type) -> String? {
let (stringPtr, count) = _getMangledTypeName(type)
guard count > 0 else {
return nil
}

let (result, repairsMade) = String._fromUTF8Repairing(
UnsafeBufferPointer(start: stringPtr, count: count))

precondition(!repairsMade, "repairs made to _mangledTypeName, this is not expected since names should always valid UTF-8")

return result
}
Copy link
Member

Choose a reason for hiding this comment

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

What do we want to do here if the input contains invalid UTF-8? It seems like our mangling should only produce ASCII, so you can use String._fromASCIIValidating which returns nil of non-ASCII, and trap in that scenario.

Copy link
Member

Choose a reason for hiding this comment

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

CC @eeckstein @DougGregor @jckarter though to verify the ASCII-ness of mangled names for types with non-ASCII names

Copy link
Member

Choose a reason for hiding this comment

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

CC @eeckstein @DougGregor @jckarter though to verify the ASCII-ness of mangled names for types with non-ASCII names

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's the case. Non-ASCII characters in identifiers are encoded to ASCII with the Punycode algorithm.

Copy link
Contributor

Choose a reason for hiding this comment

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

Punycode encoding is not used in all contexts; we also support disabling it for runtime mangled names, since we aren't constrained by linker limitations there. The mangled string should always be valid UTF-8.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So... sounds like nothing to be done here then?

Anything else I can help to get this merged?

I would be happy to backport as well if possible (?), which branches should I target for "next" 5.1 / 5.2?

Copy link
Member

Choose a reason for hiding this comment

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

This LGTM, but I would suggest adding a precondition that the repairsMade in the return value was false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 34b6e4f


/// Lookup a class given a name. Until the demangled encoding of type
/// names is stabilized, this is limited to top-level class names (Foo.bar).
public // SPI(Foundation)
Expand Down
83 changes: 74 additions & 9 deletions stdlib/public/runtime/Casting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,28 @@ std::string swift::nameForMetadata(const Metadata *type,
return result;
}


/// Used as part of cache key for `TypeNameCache`.
enum class TypeNameKind {
NotQualified,
Qualified,
Mangled,
};

using TypeNameCacheKey = llvm::PointerIntPair<const Metadata *, 2, TypeNameKind>;

#if SWIFT_CASTING_SUPPORTS_MUTEX
static StaticReadWriteLock TypeNameCacheLock;
#endif

/// Cache containing rendered names for Metadata.
/// Access MUST be protected using `TypeNameCacheLock`.
static Lazy<llvm::DenseMap<TypeNameCacheKey, std::pair<const char *, size_t>>>
TypeNameCache;

TypeNamePair
swift::swift_getTypeName(const Metadata *type, bool qualified) {
using Key = llvm::PointerIntPair<const Metadata *, 1, bool>;

#if SWIFT_CASTING_SUPPORTS_MUTEX
static StaticReadWriteLock TypeNameCacheLock;
#endif
static Lazy<llvm::DenseMap<Key, std::pair<const char *, size_t>>>
TypeNameCache;

Key key(type, qualified);
TypeNameCacheKey key = TypeNameCacheKey(type, qualified ? TypeNameKind::Qualified: TypeNameKind::NotQualified);
auto &cache = TypeNameCache.get();

// Attempt read-only lookup of cache entry.
Expand Down Expand Up @@ -179,6 +190,60 @@ swift::swift_getTypeName(const Metadata *type, bool qualified) {
}
}

/// Return mangled name for the given type.
TypeNamePair
swift::swift_getMangledTypeName(const Metadata *type) {
TypeNameCacheKey key(type, TypeNameKind::Mangled);
auto &cache = TypeNameCache.get();

// Attempt read-only lookup of cache entry.
{
#if SWIFT_CASTING_SUPPORTS_MUTEX
StaticScopedReadLock guard(TypeNameCacheLock);
#endif

auto found = cache.find(key);
if (found != cache.end()) {
auto result = found->second;
return TypeNamePair{result.first, result.second};
}
}

// Read-only cache lookup failed, we may need to create it.
{
#if SWIFT_CASTING_SUPPORTS_MUTEX
StaticScopedWriteLock guard(TypeNameCacheLock);
#endif

// Do lookup again just to make sure it wasn't created by another
// thread before we acquired the write lock.
auto found = cache.find(key);
if (found != cache.end()) {
auto result = found->second;
return TypeNamePair{result.first, result.second};
}

// Build the mangled name.
Demangle::Demangler Dem;
auto demangling = _swift_buildDemanglingForMetadata(type, Dem);

if (demangling == nullptr) {
return TypeNamePair{NULL, 0};
}
auto name = Demangle::mangleNode(demangling);

// Copy it to memory we can reference forever.
auto size = name.size();
auto result = (char *)malloc(size + 1);
memcpy(result, name.data(), size);
result[size] = 0;

cache.insert({key, {result, size}});

return TypeNamePair{result, size};
}
}

/// Report a dynamic cast failure.
// This is noinline to preserve this frame in stack traces.
// We want "dynamicCastFailure" to appear in crash logs even we crash
Expand Down
29 changes: 29 additions & 0 deletions test/Runtime/demangleToMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -441,5 +441,34 @@ DemangleToMetadataTests.test("Nested types in same-type-constrained extensions")
// V !: P3 in InnerTEqualsConformsToP1
}

if #available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *) {
DemangleToMetadataTests.test("Round-trip with _mangledTypeName and _typeByName") {
func roundTrip<T>(_ type: T.Type) {
let mangledName: String? = _mangledTypeName(type)
let recoveredType: Any.Type? = _typeByName(mangledName!)
expectEqual(String(reflecting: type), String(reflecting: recoveredType!))
expectEqual(type, recoveredType! as! T.Type)
}

roundTrip(Int.self)
roundTrip(String.self)
roundTrip(ConformsToP2AndP3.self)
roundTrip(SG12<ConformsToP1AndP2, ConformsToP1AndP2>.self)
roundTrip(SG12<ConformsToP1AndP2, ConformsToP1AndP2>.InnerTEqualsU<ConformsToP3>.self)
roundTrip(SG12<ConformsToP1, ConformsToP2>.InnerTEqualsConformsToP1<ConformsToP3>.self)
roundTrip(SG12<ConformsToP1, ConformsToP2>.InnerUEqualsConformsToP2<ConformsToP3>.self)
}

DemangleToMetadataTests.test("Check _mangledTypeName, _typeName use appropriate cache keys") {
// sanity check that name functions use the right keys to store cached names:
for _ in 1...2 {
expectEqual("Si", _mangledTypeName(Int.self)!)
expectEqual("Swift.Int", _typeName(Int.self, qualified: true))
expectEqual("Int", _typeName(Int.self, qualified: false))
}
}
}


runAllTests()