diff --git a/llvm/include/llvm/Demangle/Demangle.h b/llvm/include/llvm/Demangle/Demangle.h --- a/llvm/include/llvm/Demangle/Demangle.h +++ b/llvm/include/llvm/Demangle/Demangle.h @@ -57,6 +57,9 @@ char *buf, size_t *n_buf, int *status, MSDemangleFlags Flags = MSDF_None); +// Demangles a Rust v0 mangled symbol. The API follows that of __cxa_demangle. +char *rustDemangle(const char *MangledName, char *Buf, size_t *N, int *Status); + /// Attempt to demangle a string using different demangling schemes. /// The function uses heuristics to determine which demangling scheme to use. /// \param MangledName - reference to string to demangle. diff --git a/llvm/include/llvm/Demangle/RustDemangle.h b/llvm/include/llvm/Demangle/RustDemangle.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Demangle/RustDemangle.h @@ -0,0 +1,156 @@ +//===--- RustDemangle.h -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DEMANGLE_RUSTDEMANGLE_H +#define LLVM_DEMANGLE_RUSTDEMANGLE_H + +#include "llvm/Demangle/DemangleConfig.h" +#include "llvm/Demangle/StringView.h" +#include "llvm/Demangle/Utility.h" + +namespace llvm { +namespace rust_demangle { + +using llvm::itanium_demangle::OutputStream; +using llvm::itanium_demangle::StringView; + +struct Identifier { + StringView Name; + bool Punycode; + + bool empty() const { return Name.empty(); } +}; + +class Demangler { + // Enables more verbose output that includes crate hashes and const types. + const bool Verbose; + + // Maximum recursion level, used to avoid stack overflow. + const size_t MaxRecursionLevel; + size_t RecursionLevel; + + // Input string that is being demangled (without "_R" prefix if any). + StringView Input; + // Position in the input string. + size_t Position; + + size_t BoundLifetimes; + + // When true print methods append the output to the Demangled string. + // When false the output is suppressed. + bool Print; + + // True if an error occurred. + bool Error; + +public: + // Demangled output. + OutputStream Output; + + Demangler(bool Verbose_ = false, size_t MaxRecursionLevel_ = 500); + + bool demangle(const char *Mangled, size_t MangledLen); + +private: + bool demanglePath(bool InValue, bool LeaveOpen = false); + void demangleImplPath(bool InValue); + void demangleType(); + void demangleFnSig(); + void demangleBinder(); + void demangleDynTrait(); + void demangleGenericArg(); + void demangleConst(); + void demangleConstInt(const bool IsSigned); + void demangleConstBool(); + void demangleConstChar(); + + template void demangleBackRef(Callable Demangler) { + const uint64_t BackRef = parseBase62Number(); + if (Error || BackRef >= Position) { + Error = true; + return; + } + + if (!Print) + return; + + const size_t SavedPosition = Position; + Position = BackRef; + Demangler(); + Position = SavedPosition; + } + + template void withBinder(Callable Demangler) { + const auto SavedBoundLifetimes = BoundLifetimes; + demangleBinder(); + Demangler(); + BoundLifetimes = SavedBoundLifetimes; + } + + template + void withPrint(const bool NewPrint, Callable Demangler) { + const auto SavedPrint = Print; + Print = NewPrint; + Demangler(); + Print = SavedPrint; + } + + Identifier parseIdentifier(); + uint64_t parseHexNumber(StringView &HexDigits); + uint64_t parseOptionalBase62Number(char Tag); + uint64_t parseBase62Number(); + uint64_t parseDecimalNumber(); + + void printLifetime(const uint64_t Index); + void printDecimalNumber(uint64_t N); + void printHexNumber(uint64_t N); + void printIdentifier(Identifier Ident); + + void print(StringView S) { + if (Error || !Print) + return; + + Output += S; + } + + void printChar(char C) { + if (Error || !Print) + return; + + Output += C; + } + + bool consumeIf(char Prefix) { + if (Error || Position >= Input.size() || Input[Position] != Prefix) + return false; + + Position += 1; + return true; + } + + char consume() { + if (Error || Position >= Input.size()) { + Error = true; + return 0; + } + + return Input[Position++]; + } + + char look() const { + if (Error || Position >= Input.size()) + return 0; + + return Input[Position]; + } +}; + +} // namespace rust_demangle +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/Demangle/Utility.h b/llvm/include/llvm/Demangle/Utility.h --- a/llvm/include/llvm/Demangle/Utility.h +++ b/llvm/include/llvm/Demangle/Utility.h @@ -126,6 +126,26 @@ return this->operator<<(static_cast(N)); } + void insert(size_t Pos, const char *S, size_t N) { + assert(Pos <= CurrentPosition); + if (N > 0) { + grow(N); + std::memmove(Buffer + Pos + N, Buffer + Pos, CurrentPosition - Pos); + std::memcpy(Buffer + Pos, S, N); + CurrentPosition += N; + } + } + + char &operator[](size_t Idx) { + assert(Idx < CurrentPosition); + return Buffer[Idx]; + } + + const char &operator[](size_t Idx) const { + assert(Idx < CurrentPosition); + return Buffer[Idx]; + } + size_t getCurrentPosition() const { return CurrentPosition; } void setCurrentPosition(size_t NewPos) { CurrentPosition = NewPos; } diff --git a/llvm/lib/Demangle/CMakeLists.txt b/llvm/lib/Demangle/CMakeLists.txt --- a/llvm/lib/Demangle/CMakeLists.txt +++ b/llvm/lib/Demangle/CMakeLists.txt @@ -3,6 +3,7 @@ ItaniumDemangle.cpp MicrosoftDemangle.cpp MicrosoftDemangleNodes.cpp + RustDemangle.cpp ADDITIONAL_HEADER_DIRS "${LLVM_MAIN_INCLUDE_DIR}/llvm/Demangle" diff --git a/llvm/lib/Demangle/Demangle.cpp b/llvm/lib/Demangle/Demangle.cpp --- a/llvm/lib/Demangle/Demangle.cpp +++ b/llvm/lib/Demangle/Demangle.cpp @@ -19,10 +19,17 @@ return Pos > 0 && Pos <= 4 && MangledName[Pos] == 'Z'; } +static bool isRustEncoding(const std::string &MangledName) { + return MangledName.size() >= 2 && MangledName[0] == '_' && + MangledName[1] == 'R'; +} + std::string llvm::demangle(const std::string &MangledName) { char *Demangled; if (isItaniumEncoding(MangledName)) Demangled = itaniumDemangle(MangledName.c_str(), nullptr, nullptr, nullptr); + else if (isRustEncoding(MangledName)) + Demangled = rustDemangle(MangledName.c_str(), nullptr, nullptr, nullptr); else Demangled = microsoftDemangle(MangledName.c_str(), nullptr, nullptr, nullptr, nullptr); diff --git a/llvm/lib/Demangle/RustDemangle.cpp b/llvm/lib/Demangle/RustDemangle.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Demangle/RustDemangle.cpp @@ -0,0 +1,1036 @@ +//===--- RustDemangle.cpp ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a demangler for Rust v0 mangled symbols. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Demangle/RustDemangle.h" +#include "llvm/Demangle/Demangle.h" + +#include +#include +#include + +using namespace llvm; +using namespace rust_demangle; + +char *llvm::rustDemangle(const char *MangledName, char *Buf, size_t *N, + int *Status) { + if (MangledName == nullptr || (Buf != nullptr && N == nullptr)) { + if (Status != nullptr) + *Status = demangle_invalid_args; + return nullptr; + } + + // Return early if mangled name doesn't look like a Rust symbol. + if (MangledName[0] != '_' || MangledName[1] != 'R') { + if (Status != nullptr) + *Status = demangle_invalid_mangled_name; + return nullptr; + } + + Demangler D; + // The user provided buffer cannot be used as an output stream since + // demangling could fail and in that case the user provided buffer must not + // be reallocated. + if (!initializeOutputStream(nullptr, nullptr, D.Output, 1024)) { + if (Status != nullptr) + *Status = demangle_memory_alloc_failure; + return nullptr; + } + + if (!D.demangle(MangledName, strlen(MangledName))) { + if (Status != nullptr) + *Status = demangle_invalid_mangled_name; + std::free(D.Output.getBuffer()); + return nullptr; + } + + D.Output += '\0'; + char *Demangled = D.Output.getBuffer(); + const size_t DemangledLen = D.Output.getCurrentPosition(); + + if (Buf != nullptr) { + if (DemangledLen <= *N) { + std::memcpy(Buf, Demangled, DemangledLen); + std::free(Demangled); + Demangled = Buf; + } else { + std::free(Buf); + } + } + + if (N != nullptr) + *N = DemangledLen; + + if (Status != nullptr) + *Status = demangle_success; + + return Demangled; +} + +Demangler::Demangler(bool Verbose_, size_t MaxRecursionLevel_) + : Verbose(Verbose_), MaxRecursionLevel(MaxRecursionLevel_) {} + +static inline bool isDigit(const char C) { return '0' <= C && C <= '9'; } + +static inline bool isLower(const char C) { return 'a' <= C && C <= 'z'; } + +static inline bool isUpper(const char C) { return 'A' <= C && C <= 'Z'; } + +static inline bool isBasic(const char C) { + return isDigit(C) || isLower(C) || isUpper(C) || C == '_'; +} + +/// Returns true if C starts a . +static inline bool isPath(const char C) { + switch (C) { + case 'C': + case 'M': + case 'X': + case 'Y': + case 'N': + case 'I': + case 'B': + return true; + default: + return false; + } +} + +// Parses Rust v0 mangled symbol. Returns true when successful, and false +// otherwise. The demangled symbol is stored in Output field. It is +// responsibility of the caller to free the memory behind the output stream. +// +// = "_R" [] [] +bool Demangler::demangle(const char *Mangled, size_t MangledLength) { + Input = StringView(Mangled, MangledLength); + Position = 0; + Error = false; + Print = true; + BoundLifetimes = 0; + RecursionLevel = 0; + + // Back references are relative to the end of "_R" prefix. Remove the prefix + // from the input to avoid having to adjust back references later on. + if (!Input.consumeFront("_R")) { + Error = true; + return false; + } + + // A presence of a version number indicates a mangling we do not support yet. + if (isDigit(look())) { + Error = true; + return false; + } + + demanglePath(true /* InValue */); + + if (isPath(look())) + withPrint(false, [&] { demanglePath(false /* InValue */); }); + + // An extension allowing an arbitrary dot suffix. + if (consumeIf('.')) { + const auto Suffix = Input.substr(Position); + Position += Suffix.size(); + print(" ("); + print(Suffix); + print(")"); + } + + if (Position != Input.size()) + Error = true; + + return !Error; +} + +// Demangles a path. InValue indicates that the path is inside a value +// namespace, where it may need to be formatter differently. LeaveOpen will +// omit a closing `>` after generic arguments. Return value indicates whether +// generics arguments have been left open. +// +// = "C" // crate root +// | "M" // (inherent impl) +// | "X" // (trait impl) +// | "Y" // (trait definition) +// | "N" // ...::ident (nested path) +// | "I" {} "E" // ... (generic args) +// | +// = [] +bool Demangler::demanglePath(bool InValue, bool LeaveOpen) { + if (Error || RecursionLevel >= MaxRecursionLevel) { + Error = true; + return false; + } + RecursionLevel += 1; + bool IsOpen = false; + + switch (consume()) { + case 'C': { + const uint64_t Disambiguator = parseOptionalBase62Number('s'); + Identifier Ident = parseIdentifier(); + printIdentifier(Ident); + if (Verbose) { + print("["); + printHexNumber(Disambiguator); + print("]"); + } + break; + } + case 'M': { + withPrint(false, [&] { demangleImplPath(InValue); }); + print("<"); + demangleType(); + print(">"); + break; + } + case 'X': { + withPrint(false, [&] { demangleImplPath(InValue); }); + print("<"); + demangleType(); + print(" as "); + demanglePath(false /* InValue */); + print(">"); + break; + } + case 'Y': + print("<"); + demangleType(); + print(" as "); + demanglePath(false /* InValue */); + print(">"); + break; + case 'N': { + const char NS = consume(); + if (!isLower(NS) && !isUpper(NS)) { + Error = true; + break; + } + demanglePath(InValue); + + const uint64_t Disambiguator = parseOptionalBase62Number('s'); + Identifier Ident = parseIdentifier(); + + if (isUpper(NS)) { + // Special namespaces. + print("::{"); + if (NS == 'C') + print("closure"); + else if (NS == 'S') + print("shim"); + else + printChar(NS); + + if (!Ident.empty()) { + print(":"); + printIdentifier(Ident); + } + printChar('#'); + printDecimalNumber(Disambiguator); + print("}"); + } else { + // Implementation internal disambiguation category. Namespace is + // omitted from the output. + if (!Ident.empty()) { + print("::"); + printIdentifier(Ident); + } + } + break; + } + case 'I': { + demanglePath(InValue); + + if (InValue) + print("::"); + + print("<"); + for (size_t I = 0; !Error && !consumeIf('E'); ++I) { + if (I >= 1) + print(", "); + demangleGenericArg(); + } + if (LeaveOpen) + IsOpen = true; + else + print(">"); + break; + } + case 'B': + demangleBackRef([&] { IsOpen = demanglePath(InValue, LeaveOpen); }); + break; + default: + Error = true; + break; + } + + RecursionLevel -= 1; + return IsOpen; +} + +// = [] +// = "s" +void Demangler::demangleImplPath(bool InValue) { + parseOptionalBase62Number('s'); + demanglePath(InValue); +} + +// = ["u"] ["_"] +Identifier Demangler::parseIdentifier() { + const bool Punycode = consumeIf('u'); + const uint64_t Bytes = parseDecimalNumber(); + consumeIf('_'); + + if (Error || Bytes > Input.size() - Position) { + Error = true; + return {}; + } + const StringView S(Input.begin() + Position, Bytes); + Position += Bytes; + + for (const char C : S) { + if (!isBasic(C)) { + Error = true; + return {}; + } + } + + return {S, Punycode}; +} + +static const char *parseBasicType(char C) { + switch (C) { + case 'a': + return "i8"; + case 'b': + return "bool"; + case 'c': + return "char"; + case 'd': + return "f64"; + case 'e': + return "str"; + case 'f': + return "f32"; + case 'h': + return "u8"; + case 'i': + return "isize"; + case 'j': + return "usize"; + case 'l': + return "i32"; + case 'm': + return "u32"; + case 'n': + return "i128"; + case 'o': + return "u128"; + case 's': + return "i16"; + case 't': + return "u16"; + case 'u': + return "()"; + case 'v': + return "..."; + case 'x': + return "i64"; + case 'y': + return "u64"; + case 'z': + return "!"; + case 'p': + return "_"; + default: + return nullptr; + } +} + +// = | +// | // named type +// | "A" // [T; N] +// | "S" // [T] +// | "T" {} "E" // (T1, T2, T3, ...) +// | "R" [] // &T +// | "Q" [] // &mut T +// | "P" // *const T +// | "O" // *mut T +// | "F" // fn(...) -> ... +// | "D" // dyn Trait + Send + 'a +// | // backref +void Demangler::demangleType() { + if (Error || RecursionLevel >= MaxRecursionLevel) { + Error = true; + return; + } + RecursionLevel += 1; + + const char C = consume(); + if (const char *BasicType = parseBasicType(C)) { + print(BasicType); + } else { + switch (C) { + case 'A': + print("["); + demangleType(); + print("; "); + demangleConst(); + print("]"); + break; + case 'S': + print("["); + demangleType(); + print("]"); + break; + case 'T': { + print("("); + size_t I = 0; + for (; !Error && !consumeIf('E'); ++I) { + if (I > 0) + print(", "); + demangleType(); + } + if (I == 1) + print(","); + print(")"); + break; + } + case 'R': + print("&"); + if (consumeIf('L')) { + if (const auto Lifetime = parseBase62Number()) { + printLifetime(Lifetime); + print(" "); + } + } + demangleType(); + break; + case 'Q': + print("&"); + if (consumeIf('L')) { + if (const auto Lifetime = parseBase62Number()) { + printLifetime(Lifetime); + print(" "); + } + } + print("mut "); + demangleType(); + break; + case 'P': + print("*const "); + demangleType(); + break; + case 'O': + print("*mut "); + demangleType(); + break; + case 'F': + demangleFnSig(); + break; + case 'D': { + print("dyn "); + withBinder([&] { + for (size_t I = 0; !Error && !consumeIf('E'); ++I) { + if (I > 0) + print(" + "); + demangleDynTrait(); + } + }); + if (consumeIf('L')) { + if (const auto Lifetime = parseBase62Number()) { + print(" + "); + printLifetime(Lifetime); + } + } else { + Error = true; + } + break; + } + case 'B': + demangleBackRef([&] { demangleType(); }); + break; + case 'C': + case 'M': + case 'X': + case 'N': + case 'I': + Position -= 1; + demanglePath(false /* InValue */); + break; + default: + Error = true; + break; + } + } + + RecursionLevel -= 1; +} + +// := [] ["U"] ["K" ] {} "E" +// = "C" +// | +void Demangler::demangleFnSig() { + withBinder([&] { + const bool IsUnsafe = consumeIf('U'); + if (IsUnsafe) + print("unsafe "); + + if (consumeIf('K')) { + print("extern \""); + if (consumeIf('C')) { + print("C"); + } else { + const size_t Start = Output.getCurrentPosition(); + printIdentifier(parseIdentifier()); + const size_t End = Output.getCurrentPosition(); + + // When mangling ABI "-" is replaced by "_". + for (size_t I = Start; I != End; ++I) + if (Output[I] == '_') + Output[I] = '-'; + } + print("\" "); + } + + print("fn("); + for (size_t I = 0; !Error && !consumeIf('E'); ++I) { + if (I > 0) + print(", "); + demangleType(); + } + print(")"); + + if (consumeIf('u')) { + // Omit the unit type from the output. + } else { + print(" -> "); + demangleType(); + } + }); +} + +// Demangles optional binder and updates the number of bound lifetimes. +// +// = ["G" ] +void Demangler::demangleBinder() { + const uint64_t Binder = parseOptionalBase62Number('G'); + if (Error || Binder == 0) + return; + + // Use the input size as a weak upper bound on the total number of bound + // lifetimes. Inputs exceeding the bound are invalid while at the same time + // generating excessive amounts of demangled output. + if (Binder >= Input.size() - BoundLifetimes) { + Error = true; + return; + } + + print("for<"); + for (size_t I = 0; I != Binder; ++I) { + BoundLifetimes += 1; + if (I > 0) + print(", "); + printLifetime(1); + } + print("> "); +} + +// = {} +// = "p" +void Demangler::demangleDynTrait() { + bool IsOpen = demanglePath(false /* InValue */, true /* LeaveOpen */); + while (!Error && consumeIf('p')) { + if (!IsOpen) { + IsOpen = true; + print("<"); + } else { + print(", "); + } + printIdentifier(parseIdentifier()); + print(" = "); + demangleType(); + } + if (IsOpen) + print(">"); +} + +// = +// | +// | "K" +// = "L" +void Demangler::demangleGenericArg() { + if (consumeIf('L')) + printLifetime(parseBase62Number()); + else if (consumeIf('K')) + demangleConst(); + else + demangleType(); +} + +// = +// | "p" // placeholder +// | +// = ["n"] {} "_" +void Demangler::demangleConst() { + if (Error || RecursionLevel >= MaxRecursionLevel) { + Error = true; + return; + } + RecursionLevel += 1; + + const char Type = consume(); + switch (Type) { + case 'a': // i8 + case 's': // i16 + case 'l': // i32 + case 'x': // i64 + case 'n': // i128 + case 'i': // isize + demangleConstInt(true /*IsSigned*/); + break; + case 'h': // u8 + case 't': // u16 + case 'm': // u32 + case 'y': // u64 + case 'o': // u128 + case 'j': // usize + demangleConstInt(false /*IsSigned*/); + break; + case 'b': // bool + demangleConstBool(); + break; + case 'c': // char + demangleConstChar(); + break; + case 'p': // placeholder + print("_"); + break; + case 'B': + demangleBackRef([&] { demangleConst(); }); + break; + default: + Error = true; + break; + } + + const char *BasicType = parseBasicType(Type); + if (Verbose && BasicType != nullptr && Type != 'p') { + print(": "); + print(BasicType); + } + + RecursionLevel -= 1; +} + +void Demangler::demangleConstInt(const bool IsSigned) { + const bool IsNegative = IsSigned && consumeIf('n'); + StringView HexDigits; + const uint64_t Value = parseHexNumber(HexDigits); + + if (IsNegative) + print("-"); + + if (HexDigits.size() < 16) { + printDecimalNumber(Value); + } else { + print("0x"); + print(HexDigits); + } +} + +void Demangler::demangleConstBool() { + StringView HexDigits; + parseHexNumber(HexDigits); + + if (HexDigits == "0") + print("false"); + else if (HexDigits == "1") + print("true"); + else + Error = true; +} + +void Demangler::demangleConstChar() { + StringView HexDigits; + const uint64_t Value = parseHexNumber(HexDigits); + + if (Error || HexDigits.size() > 8) { + Error = true; + return; + } + + print("'"); + switch (Value) { + case '\t': + print(R"(\t)"); + break; + case '\r': + print(R"(\r)"); + break; + case '\n': + print(R"(\n)"); + break; + case '\\': + print(R"(\\)"); + break; + case '"': + print(R"(")"); + break; + case '\'': + print(R"(\')"); + break; + default: + if (' ' <= Value && Value <= '~') { + printChar(Value); + } else { + print(R"(\u{)"); + printHexNumber(Value); + print("}"); + } + break; + } + print("'"); +} + +// Parses hexadecimal number with 0-9 a-f as a digits. Returns value and +// stores hex digits in HexDigits. Overflow is not considered to be an +// error, so check the length of hex digits (<= 16), before using the +// returned value. +// +// = {<0-9a-f>} "_" +uint64_t Demangler::parseHexNumber(StringView &HexDigits) { + const size_t Start = Position; + uint64_t Value = 0; + + while (!Error && !consumeIf('_')) { + const char C = consume(); + Value *= 16; + if (isDigit(C)) + Value += C - '0'; + else if ('a' <= C && C <= 'f') + Value += 10 + (C - 'a'); + else + Error = true; + } + + if (Error) { + Value = 0; + HexDigits = StringView(); + return Value; + } + + assert(Start < Position); + const size_t N = Position - 1 - Start; + HexDigits = StringView(Input.begin() + Start, +N); + return Value; +} + +// Parses optional base 62 number. The presence of a number is determined +// using Tag. Returns 0 when tag is absent and value + 1 otherwise. +uint64_t Demangler::parseOptionalBase62Number(char Tag) { + if (!consumeIf(Tag)) + return 0; + + const uint64_t N = parseBase62Number(); + if (Error || N == std::numeric_limits::max()) { + Error = true; + return 0; + } + return N + 1; +} + +// Parses base 62 number with 0-9 a-z A-Z as digits. Number is terminated by +// "_". All values are offset by 1, so that "_" encodes 0, "0_" encodes 1, +// "1_" encodes 2, etc. +// +// = {<0-9a-zA-Z>} "_" +uint64_t Demangler::parseBase62Number() { + uint64_t Value = 0; + if (consumeIf('_')) { + return Value; + } + + const uint64_t Max = std::numeric_limits::max(); + while (true) { + uint64_t Digit; + const char C = consume(); + + if (C == '_') { + break; + } else if (isDigit(C)) { + Digit = C - '0'; + } else if (isLower(C)) { + Digit = 10 + (C - 'a'); + } else if (isUpper(C)) { + Digit = 10 + 26 + (C - 'A'); + } else { + Error = true; + return 0; + } + + if (Value > (Max - Digit) / 62) { + Error = true; + return 0; + } + Value = Value * 62 + Digit; + } + + if (Value > Max - 1) { + Error = true; + return 0; + } + Value += 1; + + return Value; +} + +// Parses a decimal number that had been encoded without any leading zeros. +// +// = "0" +// | <1-9> {<0-9>} +uint64_t Demangler::parseDecimalNumber() { + const char C = look(); + if (!isDigit(C)) { + Error = true; + return 0; + } + + if (C == '0') { + consume(); + return 0; + } + + const uint64_t Max = std::numeric_limits::max(); + uint64_t Value = 0u; + + while (isDigit(look())) { + if (Value > Max / 10) { + Error = true; + return 0; + } + Value *= 10; + + const uint64_t D = consume() - '0'; + if (Value > Max - D) { + Error = true; + return 0; + } + Value += D; + } + + return Value; +} + +// Prints the lifetime. An index 0 always represents an erased lifetime. +// Indices starting from 1, are De Bruijn indices, referring to higher-ranked +// lifetimes bound by one of the enclosing binders. +void Demangler::printLifetime(const uint64_t Index) { + if (Index == 0) { + print("'_"); + return; + } + + if (Index - 1 >= BoundLifetimes) { + Error = true; + return; + } + + const uint64_t Depth = BoundLifetimes - Index; + printChar('\''); + if (Depth < 26) { + printChar('a' + Depth); + } else { + printChar('t'); + printDecimalNumber(Depth - 26); + } +} + +void Demangler::printDecimalNumber(uint64_t N) { + if (Error || !Print) + return; + + Output << N; +} + +void Demangler::printHexNumber(uint64_t N) { + if (Error || !Print) + return; + + const size_t Start = Output.getCurrentPosition(); + do { + const uint64_t D = N % 16; + N /= 16; + printChar(D < 10 ? '0' + D : 'a' + (D - 10)); + } while (N != 0); + + const size_t End = Output.getCurrentPosition(); + std::reverse(Output.getBuffer() + Start, Output.getBuffer() + End); +} + +static inline bool decodePunycodeDigit(const char C, size_t &Value) { + if (isLower(C)) { + Value = C - 'a'; + return true; + } else if (isDigit(C)) { + Value = 26 + (C - '0'); + return true; + } else { + return false; + } +} + +static void removeNullBytes(OutputStream &S, size_t StartIdx) { + size_t I = StartIdx, J = StartIdx, N = S.getCurrentPosition(); + for (; I != N; ++I) + if (S[I]) + S[J++] = S[I]; + S.setCurrentPosition(J); +} + +// Encodes code point as UTF-8 and stores results in Output. Returns false if +// CodePoint is not a valid unicode scalar value. +static inline bool encodeUTF8(const size_t CodePoint, char *Output) { + if (0xD800 <= CodePoint && CodePoint <= 0xDFFF) + return false; + + if (CodePoint <= 0x7F) { + Output[0] = CodePoint; + return true; + } else if (CodePoint <= 0x7FF) { + Output[0] = 0xC0 | ((CodePoint >> 6) & 0x3F); + Output[1] = 0x80 | (CodePoint & 0x3F); + return true; + } else if (CodePoint <= 0xFFFF) { + Output[0] = 0xE0 | (CodePoint >> 12); + Output[1] = 0x80 | ((CodePoint >> 6) & 0x3F); + Output[2] = 0x80 | (CodePoint & 0x3F); + return true; + } else if (CodePoint <= 0x10FFFF) { + Output[0] = 0xF0 | (CodePoint >> 18); + Output[1] = 0x80 | ((CodePoint >> 12) & 0x3F); + Output[2] = 0x80 | ((CodePoint >> 6) & 0x3F); + Output[3] = 0x80 | (CodePoint & 0x3F); + return true; + } else { + return false; + } +} + +// Decodes string encoded using punycode and appends results to Output. +// Returns true if decoding was successful. +static bool decodePunycode(const StringView Input, OutputStream &Output) { + const size_t OutputSize = Output.getCurrentPosition(); + size_t InputIdx = 0; + + // Rust punycode uses an underscore as a delimiter. + size_t DelimiterPos = StringView::npos; + for (size_t I = 0; I != Input.size(); ++I) + if (Input[I] == '_') + DelimiterPos = I; + + if (DelimiterPos != StringView::npos) { + // Copy basic code points before the delimiter to the output. + for (; InputIdx != DelimiterPos; ++InputIdx) { + const char C = Input[InputIdx]; + if (!isBasic(C)) + return false; + + char UTF8[4] = {C}; + Output += StringView(UTF8, UTF8 + 4); + } + // Skip over the delimiter. + ++InputIdx; + } + + const size_t Base = 36; + const size_t Skew = 38; + size_t Bias = 72; + size_t N = 0x80; + size_t TMin = 1; + size_t TMax = 26; + size_t Damp = 700; + + auto Adapt = [&](size_t Delta, const size_t NumPoints) { + Delta /= Damp; + Delta += Delta / NumPoints; + Damp = 2; + + size_t K = 0; + while (Delta > (Base - TMin) * TMax / 2) { + Delta /= Base - TMin; + K += Base; + } + return K + (((Base - TMin + 1) * Delta) / (Delta + Skew)); + }; + + // Main decoding loop. + for (size_t I = 0; InputIdx != Input.size(); I += 1) { + size_t OldI = I; + size_t W = 1; + const size_t Max = std::numeric_limits::max(); + for (size_t K = Base; true; K += Base) { + if (InputIdx == Input.size()) + return false; + const char C = Input[InputIdx++]; + size_t Digit = 0; + if (!decodePunycodeDigit(C, Digit)) + return false; + + if (Digit > (Max - I) / W) + return false; + I += Digit * W; + + size_t T; + if (K <= Bias) + T = TMin; + else if (K >= Bias + TMax) + T = TMax; + else + T = K - Bias; + + if (Digit < T) + break; + + if (W > Max / (Base - T)) + return false; + W *= (Base - T); + } + const size_t NumPoints = (Output.getCurrentPosition() - OutputSize) / 4 + 1; + Bias = Adapt(I - OldI, NumPoints); + + if (I / NumPoints > Max - N) + return false; + N += I / NumPoints; + I = I % NumPoints; + + // Insert N at position I in the output. + char UTF8[4] = {}; + if (!encodeUTF8(N, UTF8)) + return false; + Output.insert(OutputSize + I * 4, UTF8, 4); + } + + removeNullBytes(Output, OutputSize); + + return true; +} + +void Demangler::printIdentifier(Identifier Ident) { + if (Error || !Print) + return; + + if (Ident.Punycode) { + if (!decodePunycode(Ident.Name, Output)) { + Error = true; + } + } else { + print(Ident.Name); + } +} diff --git a/llvm/test/tools/llvm-cxxfilt/rust.test b/llvm/test/tools/llvm-cxxfilt/rust.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-cxxfilt/rust.test @@ -0,0 +1,383 @@ +RUN: llvm-cxxfilt -n < %s | FileCheck --match-full-lines %s + +CHECK: a::main + _RNvC1a4main + +CHECK: xyz::main + _RNvC3xyz4mainCsabc_3xyz + +CHECK: foo::bar + _RNvC3foo3barB2_ + +; Inherent impl + +CHECK: ::new + _RNvMC1pNtB2_1X3new + +; Trait impl + +CHECK: ::default + _RNvXC1pNtB2_1XNtNtC4core7default7Default7default + +; Trait definition + +CHECK: ::display + _RNvYmNtC1p7Display7display + +; Closure namespace + +CHECK: ns::x::{closure#0}::f + _RNvNCNvC2ns1x01f + +CHECK: ns::y::{closure#1}::f + _RNvNCNvC2ns1ys_01f + +; Shim namespace + +CHECK: >::call_once::{shim:vtable#0} + _RNSNvYNCNvC2ns4shim0INtC4core6FnOnceuE9call_once6vtable + +; Internal namespace (not show) + +CHECK: ns::z::f + _RNvNaNvC2ns1z01f + +; Invalid namespace + +CHECK: _RNvN_NvC2ns1i01f + _RNvN_NvC2ns1i01f + +; Basic types + +CHECK: b::basic:: + _RINvC1b5basicaE + +CHECK: b::basic:: + _RINvC1b5basicbE + +CHECK: b::basic:: + _RINvC1b5basiccE + +CHECK: b::basic:: + _RINvC1b5basicdE + +CHECK: b::basic:: + _RINvC1b5basiceE + +CHECK: b::basic:: + _RINvC1b5basicfE + +CHECK: b::basic:: + _RINvC1b5basichE + +CHECK: b::basic:: + _RINvC1b5basiciE + +CHECK: b::basic:: + _RINvC1b5basicjE + +CHECK: b::basic:: + _RINvC1b5basiclE + +CHECK: b::basic:: + _RINvC1b5basicmE + +CHECK: b::basic:: + _RINvC1b5basicnE + +CHECK: b::basic:: + _RINvC1b5basicoE + +CHECK: b::basic:: + _RINvC1b5basicsE + +CHECK: b::basic:: + _RINvC1b5basictE + +CHECK: b::basic::<()> + _RINvC1b5basicuE + +CHECK: b::basic::<...> + _RINvC1b5basicvE + +CHECK: b::basic:: + _RINvC1b5basicxE + +CHECK: b::basic:: + _RINvC1b5basicyE + +CHECK: b::basic:: + _RINvC1b5basiczE + +CHECK: b::basic::<_> + _RINvC1b5basicpE + +; Complex types + +CHECK: c::types::<[u8; _]> + _RINvC1c5typesAhpE + +CHECK: c::types::<[u8; 10]> + _RINvC1c5typesAhha_E + +CHECK: c::types::<[u8]> + _RINvC1c5typesShE + +CHECK: c::types::<()> + _RINvC1c5typesTEE + +CHECK: c::types::<(u8,)> + _RINvC1c5typesThEE + +CHECK: c::types::<(u8, u8)> + _RINvC1c5typesThhEE + +CHECK: c::types::<(u8, u8, u8)> + _RINvC1c5typesThhhEE + +CHECK: c::types::<&u8> + _RINvC1c5typesRhE + +CHECK: c::types::<&u8> + _RINvC1c5typesRL_hE + +CHECK: c::types::<&mut u8> + _RINvC1c5typesQhE + +CHECK: c::types::<&mut u8> + _RINvC1c5typesQL_hE + +CHECK: c::types::<*const u8> + _RINvC1c5typesPhE + +CHECK: c::types::<*mut u8> + _RINvC1c5typesOhE + +; Function signatures + +CHECK: f::fns:: i64> + _RINvC1f3fnsFExE + +CHECK: f::fns:: + _RINvC1f3fnsFEuE + +CHECK: f::fns:: + _RINvC1f3fnsFUEuE + +CHECK: f::fns:: + _RINvC1f3fnsFUKCEuE + +CHECK: f::fns:: + _RINvC1f3fnsFKCEuE + +CHECK: f::fns:: + _RINvC1f3fnsFK5cdeclEuE + +CHECK: f::fns:: + _RINvC1f3fnsFK21C_cmse_nonsecure_callEuE + +CHECK: f::fns:: + _RINvC1f3fnsFKu3n3hEuE + +; Dyn traits + +CHECK: d::trait:: + _RINvC1d5traitDNtNtC4core6marker4SendEL_E + +CHECK: d::trait:: + _RINvC1d5traitDNtB2_1ANtNtC4core6marker4SendEL_E + 01234 + +CHECK: d::trait::<&dyn core::ops::function::Fn<(u32,), Output = i32>> + _RINvC1d5traitRDINtNtNtCsa_4core3ops8function2FnTmEEp6OutputlEL_E + +CHECK: d::trait::<&dyn for<'a, 'b> core::ops::function::Fn<(&'a [u8], &'b ()), Output = ()>> + _RINvC1d5traitRDG0_INtNtNtC4core3ops8function2FnTRL1_ShRL0_uEEp6OutputuEL_E + +CHECK: d::trait:: fn(&'a ()) -> &'a dyn d::Parse<'a, 'a> + 'a> + _RINvC1d5traitFG_RL0_uERL0_DINtB2_5ParseL0_L0_EEL0_E + +CHECK: d::trait:: fn(&'a dyn for<'b> core::ops::function::FnOnce<(&'b u8,), Output = &'b u8> + 'a) -> &'a u8> + _RINvCsbDqzXfLQacH_1d5traitFG_RL0_DG_INtNtNtCs3p84KrD9aKt_4core3ops8function6FnOnceTRL0_hEEp6OutputRL0_hEL0_ERL0_hEB2_ + +; Bool constants + +CHECK: k::Bool:: + _RINvC1k4BoolKb0_E + +CHECK: k::Bool:: + _RINvC1k4BoolKb1_E + +; Integer constants + +CHECK: k::xy::<0> + _RINvC1k2xyKa0_E + +CHECK: k::xy::<1> + _RINvC1k2xyKa1_E + +CHECK: k::xy::<-2> + _RINvC1k2xyKan2_E + +CHECK: k::xy::<-10> + _RINvC1k2xyKana_E + +CHECK: k::xy::<127> + _RINvC1k2xyKa7f_E + +CHECK: k::xy::<0x123456789abcdef00> + _RINvC1k2xyKo123456789abcdef00_E + +; Char constants + +CHECK: k::a::<'a'> + _RINvC1k1aKc61_E + +CHECK: k::a::<'"'> + _RINvC1k1aKc22_E + +CHECK: k::escape::<'\t'> + _RINvC1k6escapeKc9_E + +CHECK: k::escape::<'\r'> + _RINvC1k6escapeKcd_E + +CHECK: k::escape::<'\n'> + _RINvC1k6escapeKca_E + +CHECK: k::escape::<'\\'> + _RINvC1k6escapeKc5c_E + +CHECK: k::escape::<'\''> + _RINvC1k6escapeKc27_E + +CHECK: k::snake::<'\u{1f40d}'> + _RINvC1k5snakeKc1f40d_E + +; Binders + +CHECK: e::bind:: fn(&'a u8)> + _RINvC1e4bindFG_RL0_hEuE + +CHECK: e::bind:: fn(&'a u8) -> for<'b> fn(&'a u16, &'b mut u32)> + _RINvC1e4bindFG_RL0_hEFG_RL1_tQL0_mEuE + +CHECK: e::bind:: fn(&'a &'b &'c &'d &'e &'f &'g &'h &'i &'j &'k &'l &'m &'n &'o &'p &'q &'r &'s &'t &'u &'v &'w &'x &'y &'z &'t0 &'t1 &'t2 &'t3 ())> + _RINvCs21hi0yVfW1J_1e4bindFGs_RLt_RLs_RLr_RLq_RLp_RLo_RLn_RLm_RLl_RLk_RLj_RLi_RLh_RLg_RLf_RLe_RLd_RLc_RLb_RLa_RL9_RL8_RL7_RL6_RL5_RL4_RL3_RL2_RL1_RL0_uEuEB2_ + +; Backreferences + +CHECK: abc::f::<(i32, i32, i32), (i32, i32, i32)> + _RINvC3abc1fTlllEB9_E + 01234567890 + +CHECK: abc::f::<((i32, i32, i32), (i32, i32, i32)), ((i32, i32, i32), (i32, i32, i32))> + _RINvC3abc1fTTlllEBa_EB9_E + 01234567890 + +; Punycode + +CHECK: punycode::東京 + _RNvC8punycodeu7_1lqs71d + +CHECK: punycode::zażółć_gęślą_jaźń + _RNvC8punycodeu29za_gl_ja_w3a7psa2tqtgb10airva + +CHECK: punycode::საჭმელად_გემრიელი_სადილი + _RNvC8punycodeu30____7hkackfecea1cbdathfdh9hlq6y + +CHECK: Gödel::Escher::Bach + _RNtNvCu8Gdel_5qa6Escher4Bach + +CHECK: punycode::🦁🐅 + _RNvC8punycodeu7wn8hx1g + +; Punycode - invalid code point + +CHECK: _RCu5r731r + _RCu5r731r + +CHECK: _RCu8b44444yy + _RCu8b44444yy + +CHECK: _RNvC1au25zzzzzzzzzzzzzzzzzzzzzzzzz + _RNvC1au25zzzzzzzzzzzzzzzzzzzzzzzzz + +; Punycode - early EOF + +CHECK: _RCu8_CCCAR_u4 + _RCu8_CCCAR_u4 + +; Punycode - overflow + +CHECK: _RNvC1au21p18888888888888888888 + _RNvC1au21p18888888888888888888 + +; Dot-suffix + +CHECK: dot::convert (llvm.10622374130885708379) + _RNvC3dot7convertB3_.llvm.10622374130885708379 + +CHECK: dot::test (0.0) + _RNvC3dot4testB3_.0.0 + +; Unsupported / invalid version + +CHECK: _R0NvC1a4main + _R0NvC1a4main + +CHECK: _R1NvC1a4main + _R1NvC1a4main + +CHECK: _R199999999999999999999999999NvC1a4main + _R199999999999999999999999999NvC1a4main + +; Early EOF + +CHECK: _RNvC + _RNvC + +CHECK: _RNvC1a5main + _RNvC1a5main + +CHECK: _RNvC1a20abc + _RNvC1a20abc + +; Unbound lifetimes + +CHECK: _RINvC1z1fRL0_hE + _RINvC1z1fRL0_hE + +CHECK: _RINvC1z1fRL1_hE + _RINvC1z1fRL1_hE + +CHECK: _RINvC1z1fRL10000_hE + _RINvC1z1fRL10000_hE + +CHECK: _RINvC1z1fRL99999_hE + _RINvC1z1fRL99999_hE + +; Invalid backreferences + +CHECK: _RB__B0 + _RB__B0 + +CHECK: _RB_RMRX + _RB_RMRX + +CHECK: _RNvNvB_1a1b1c + _RNvNvB_1a1b1c + +CHECK: _RINvCshGpAVYOtgW1_1a1fTlllEB_EB2_ + _RINvCshGpAVYOtgW1_1a1fTlllEB_EB2_ + +; Overabundance of bound lifetimes + +CHECK: _RYSSFGFFF_EuC1a + _RYSSFGFFF_EuC1a + +CHECK: _RYSSFFGF0FFFF_RIIIIIIIIIIIIIR + _RYSSFFGF0FFFF_RIIIIIIIIIIIIIR + +CHECK: _RYSSFFFG0FFFF_RIIIIIIIIIIIIIIIIIIIIIIIIIIIIISIII3MYAmR + _RYSSFFFG0FFFF_RIIIIIIIIIIIIIIIIIIIIIIIIIIIIISIII3MYAmR diff --git a/llvm/tools/llvm-cxxfilt/llvm-cxxfilt.cpp b/llvm/tools/llvm-cxxfilt/llvm-cxxfilt.cpp --- a/llvm/tools/llvm-cxxfilt/llvm-cxxfilt.cpp +++ b/llvm/tools/llvm-cxxfilt/llvm-cxxfilt.cpp @@ -97,6 +97,11 @@ Undecorated = itaniumDemangle(DecoratedStr + 6, nullptr, nullptr, &Status); } + if (!Undecorated && + (DecoratedLength >= 2 && strncmp(DecoratedStr, "_R", 2) == 0)) { + Undecorated = rustDemangle(DecoratedStr, nullptr, nullptr, &Status); + } + std::string Result(Undecorated ? Prefix + Undecorated : Mangled); free(Undecorated); return Result; diff --git a/llvm/tools/llvm-rust-demangle-fuzzer/CMakeLists.txt b/llvm/tools/llvm-rust-demangle-fuzzer/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-rust-demangle-fuzzer/CMakeLists.txt @@ -0,0 +1,10 @@ +set(LLVM_LINK_COMPONENTS + Demangle + FuzzMutate + Support +) + +add_llvm_fuzzer(llvm-rust-demangle-fuzzer + llvm-rust-demangle-fuzzer.cpp + DUMMY_MAIN DummyDemanglerFuzzer.cpp + ) diff --git a/llvm/tools/llvm-rust-demangle-fuzzer/DummyDemanglerFuzzer.cpp b/llvm/tools/llvm-rust-demangle-fuzzer/DummyDemanglerFuzzer.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-rust-demangle-fuzzer/DummyDemanglerFuzzer.cpp @@ -0,0 +1,18 @@ +//===-- DummyDemanglerFuzzer.cpp - Entry point to sanity check the fuzzer -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implementation of main so we can build and test without linking libFuzzer. +// +//===----------------------------------------------------------------------===// + +#include "llvm/FuzzMutate/FuzzerCLI.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); +int main(int argc, char *argv[]) { + return llvm::runFuzzerOnInputs(argc, argv, LLVMFuzzerTestOneInput); +} diff --git a/llvm/tools/llvm-rust-demangle-fuzzer/llvm-rust-demangle-fuzzer.cpp b/llvm/tools/llvm-rust-demangle-fuzzer/llvm-rust-demangle-fuzzer.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-rust-demangle-fuzzer/llvm-rust-demangle-fuzzer.cpp @@ -0,0 +1,21 @@ +//===--- llvm-demangle-fuzzer.cpp - Fuzzer for the Rust Demangler ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Demangle/Demangle.h" + +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + std::string NullTerminatedString((const char *)Data, Size); + int Status = 0; + if (char *Demangled = llvm::rustDemangle(NullTerminatedString.c_str(), + nullptr, nullptr, &Status)) + std::free(Demangled); + return 0; +} diff --git a/llvm/unittests/Demangle/CMakeLists.txt b/llvm/unittests/Demangle/CMakeLists.txt --- a/llvm/unittests/Demangle/CMakeLists.txt +++ b/llvm/unittests/Demangle/CMakeLists.txt @@ -7,4 +7,5 @@ DemangleTest.cpp ItaniumDemangleTest.cpp PartialDemangleTest.cpp + RustDemangleTest.cpp ) diff --git a/llvm/unittests/Demangle/DemangleTest.cpp b/llvm/unittests/Demangle/DemangleTest.cpp --- a/llvm/unittests/Demangle/DemangleTest.cpp +++ b/llvm/unittests/Demangle/DemangleTest.cpp @@ -21,6 +21,7 @@ "invocation function for block in foo(int)"); EXPECT_EQ(demangle("?foo@@YAXH@Z"), "void __cdecl foo(int)"); EXPECT_EQ(demangle("foo"), "foo"); + EXPECT_EQ(demangle("_RNvCshGpAVYOtgW1_5hello4rust"), "hello::rust"); // Regression test for demangling of optional template-args for vendor // extended type qualifier (https://bugs.llvm.org/show_bug.cgi?id=48009) diff --git a/llvm/unittests/Demangle/RustDemangleTest.cpp b/llvm/unittests/Demangle/RustDemangleTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Demangle/RustDemangleTest.cpp @@ -0,0 +1,90 @@ +//===------------------ RustDemangleTest.cpp ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Demangle/Demangle.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + +TEST(RustDemangle, Success) { + char *Demangled = + llvm::rustDemangle("_RNvC1a4main", nullptr, nullptr, nullptr); + EXPECT_STREQ(Demangled, "a::main"); + std::free(Demangled); + + // With status. + int Status = 0; + Demangled = llvm::rustDemangle("_RNvC1a4main", nullptr, nullptr, &Status); + EXPECT_EQ(Status, llvm::demangle_success); + EXPECT_STREQ(Demangled, "a::main"); + std::free(Demangled); + + // With status and length. + size_t N = 0; + Demangled = llvm::rustDemangle("_RNvC1a4main", nullptr, &N, &Status); + EXPECT_EQ(Status, llvm::demangle_success); + EXPECT_EQ(N, 8u); + EXPECT_STREQ(Demangled, "a::main"); + std::free(Demangled); +} + +TEST(RustDemangle, Invalid) { + int Status = 0; + char *Demangled = nullptr; + + // Invalid prefix. + Demangled = llvm::rustDemangle("_ABCDEF", nullptr, nullptr, &Status); + EXPECT_EQ(Status, llvm::demangle_invalid_mangled_name); + EXPECT_EQ(Demangled, nullptr); + + // Correct prefix but still invalid. + Demangled = llvm::rustDemangle("_RRR", nullptr, nullptr, &Status); + EXPECT_EQ(Status, llvm::demangle_invalid_mangled_name); + EXPECT_EQ(Demangled, nullptr); +} + +TEST(RustDemangle, OutputBufferWithoutLength) { + char *Buffer = static_cast(std::malloc(1024)); + ASSERT_NE(Buffer, nullptr); + + int Status = 0; + char *Demangled = + llvm::rustDemangle("_RNvC1a4main", Buffer, nullptr, &Status); + + EXPECT_EQ(Status, llvm::demangle_invalid_args); + EXPECT_EQ(Demangled, nullptr); + std::free(Buffer); +} + +TEST(RustDemangle, OutputBuffer) { + size_t N = 1024; + char *Buffer = static_cast(std::malloc(N)); + ASSERT_NE(Buffer, nullptr); + + int Status = 0; + char *Demangled = llvm::rustDemangle("_RNvC1a4main", Buffer, &N, &Status); + + EXPECT_EQ(Status, llvm::demangle_success); + EXPECT_EQ(Demangled, Buffer); + EXPECT_STREQ(Demangled, "a::main"); + std::free(Demangled); +} + +TEST(RustDemangle, SmallOutputBuffer) { + size_t N = 1; + char *Buffer = static_cast(std::malloc(N)); + ASSERT_NE(Buffer, nullptr); + + int Status = 0; + char *Demangled = llvm::rustDemangle("_RNvC1a4main", Buffer, &N, &Status); + + EXPECT_EQ(Status, llvm::demangle_success); + EXPECT_STREQ(Demangled, "a::main"); + std::free(Demangled); +}