Skip to content
Open
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
12 changes: 7 additions & 5 deletions src/smda/common/labelprovider/rust_demangler/rust_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def insert(self, i: int, c: str) -> bool:
self.out[i] = c
return True

def punycode_decode(self) -> Optional[None]:
def punycode_decode(self) -> Optional[bool]:
count = 0
punycode_bytes = self.punycode
try:
Expand Down Expand Up @@ -151,10 +151,12 @@ def punycode_decode(self) -> Optional[None]:
n += i // lent
i %= lent

try:
c = chr(n)
except (ValueError, OverflowError):
# mirror char::from_u32: a scalar value, so no surrogate and nothing past the
# last code point. chr() accepts surrogates, and a name carrying one cannot be
# encoded as UTF-8 by whoever consumes the report.
if not 0 <= n <= 0x10FFFF or 0xD800 <= n <= 0xDFFF:
return None
c = chr(n)

if not self.insert(i, c):
return None
Expand All @@ -163,7 +165,7 @@ def punycode_decode(self) -> Optional[None]:
try:
punycode_bytes[count]
except IndexError:
return
return True

delta = delta // damp
damp = 2
Expand Down
23 changes: 23 additions & 0 deletions tests/testRustSymbolProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from smda.common.labelprovider.rust_demangler.rust import TypeNotFoundError
from smda.common.labelprovider.rust_demangler.rust_legacy import LegacyDemangler, UnableToLegacyDemangle
from smda.common.labelprovider.rust_demangler.rust_v0 import (
Ident,
Parser,
Printer,
UnableTov0Demangle,
Expand Down Expand Up @@ -113,6 +114,28 @@ def test_v0_unnamed_closure_has_no_stray_colon(self):
# unnamed closures/shims must render as {closure#N}, not {closure:#N}
self.assertEqual(demangle("_RNCNvC8rustc_v01fs_0"), "rustc_v0::f::{closure#1}")

def test_v0_non_ascii_identifier_is_decoded(self):
# the decode itself always worked; its success was reported as None,
# which is what every failure path returns, so the caller fell back
name = "_RNqCs4fqI2P2rA04_11utf8_identsu30____7hkackfecea1cbdathfdh9hlq6y"
self.assertEqual(demangle(name), "utf8_idents::საჭმელად_გემრიელი_სადილი")
self.assertNotIn("punycode{", demangle(name))

def test_v0_punycode_never_decodes_to_a_surrogate(self):
# chr() accepts 0xD800-0xDFFF where Rust's char::from_u32 refuses; a name carrying
# one cannot be encoded as UTF-8 by whoever consumes the report
name = "_RNvCu4_ib9b4main"

demangled = demangle(name)

self.assertNotIn("\ud800", demangled)
demangled.encode("utf-8")

def test_v0_undecodable_punycode_still_falls_back_to_the_raw_form(self):
ident = Ident("", "!not-punycode!")
ident.display()
self.assertEqual(ident.disp, "punycode{!not-punycode!}")

def test_v0_empty_const_hex_nibbles_raise_demangler_error(self):
# `Kh_` (u8 const with zero hex nibbles) previously escaped as a bare
# ValueError from int("", 16) instead of the demangler's own error type
Expand Down
3 changes: 3 additions & 0 deletions tests/test_fuzz_rust_demangler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def test_rust_demangler_never_hangs(s):
"""
try:
result = demangle(s)
# a demangled name is handed to consumers as text; a lone surrogate would make it
# unencodable, so the demangler must never produce one
result.encode("utf-8")
except (TypeNotFoundError, UnableTov0Demangle, UnableToLegacyDemangle):
return
except RecursionError:
Expand Down