Repository: getsentry/symbolic
Pull request #261 (2020-09-10 10:31:10)

fix(debuginfo): Detect mangled anonymous namespaces in PDB inlinees
ID records specify the mangled format for anonymous namespaces: `?A0x<id>`, where `id` is a hex
identifier of the namespace. Demanglers usually resolve this as "anonymous namespace".

This also fixes an issue with demangling: If the anonymous namespace is in the
beginning of the composed name, it leads the demangler to believe that this is a
mangled MSVC name. However, since this is a composed demangled name, this leads
to a demangle error.




Patch:
diff --git a/debuginfo/src/pdb.rs b/debuginfo/src/pdb.rs
index 2b57cd4e..282f7b8d 100644
--- a/debuginfo/src/pdb.rs
+++ b/debuginfo/src/pdb.rs
@@ -634,6 +634,15 @@ impl DebugSession for PdbDebugSession<'_> {
     }
 }
 
+/// Checks whether the given name declares an anonymous namespace.
+///
+/// ID records specify the mangled format for anonymous namespaces: `?A0x<id>`, where `id` is a hex
+/// identifier of the namespace. Demanglers usually resolve this as "anonymous namespace".
+fn is_anonymous_namespace(name: &str) -> bool {
+    name.strip_prefix("?A0x")
+        .map_or(false, |rest| u32::from_str_radix(rest, 16).is_ok())
+}
+
 /// Formatter for function types.
 ///
 /// This formatter currently only contains the minimum implementation requried to format inline
@@ -693,7 +702,13 @@ impl<'u, 'd> TypeFormatter<'u, 'd> {
                 write!(target, "\"")?;
             }
             Ok(pdb::IdData::String(data)) => {
-                write!(target, "{}", data.name.to_string())?;
+                let mut string = data.name.to_string();
+
+                if is_anonymous_namespace(&string) {
+                    string = Cow::Borrowed("`anonymous namespace'");
+                }
+
+                write!(target, "{}", string)?;
             }
             Ok(pdb::IdData::UserDefinedTypeSource(_)) => {
                 // nothing to do.
diff --git a/py/symbolic/debuginfo.py b/py/symbolic/debuginfo.py
index 11a1a06f..322e7aa2 100644
--- a/py/symbolic/debuginfo.py
+++ b/py/symbolic/debuginfo.py
@@ -138,7 +138,10 @@ class Object(RustObject):
         )
 
     def __repr__(self):
-        return "<Object %s %r>" % (self.debug_id, self.arch,)
+        return "<Object %s %r>" % (
+            self.debug_id,
+            self.arch,
+        )
 
 
 class ObjectRef(object):
@@ -165,7 +168,10 @@ class ObjectRef(object):
         self.name = self.code_file
 
     def __repr__(self):
-        return "<ObjectRef %s %r>" % (self.debug_id, self.arch,)
+        return "<ObjectRef %s %r>" % (
+            self.debug_id,
+            self.arch,
+        )
 
 
 class ObjectLookup(object):
diff --git a/py/symbolic/sourcemap.py b/py/symbolic/sourcemap.py
index 4524125e..69a0205d 100644
--- a/py/symbolic/sourcemap.py
+++ b/py/symbolic/sourcemap.py
@@ -51,7 +51,10 @@ class SourceMapTokenMatch(object):
         return not self.__eq__(other)
 
     def __repr__(self):
-        return "<SourceMapTokenMatch %s:%d>" % (self.src, self.src_line,)
+        return "<SourceMapTokenMatch %s:%d>" % (
+            self.src,
+            self.src_line,
+        )
 
 
 class SourceView(RustObject):
diff --git a/py/symbolic/symcache.py b/py/symbolic/symcache.py
index cf4416e4..35231a10 100644
--- a/py/symbolic/symcache.py
+++ b/py/symbolic/symcache.py
@@ -73,7 +73,11 @@ class LineInfo(object):
         return strip_common_path_prefix(self.abs_path, self.comp_dir)
 
     def __str__(self):
-        return "%s:%s (%s)" % (self.function_name, self.line, self.rel_path,)
+        return "%s:%s (%s)" % (
+            self.function_name,
+            self.line,
+            self.rel_path,
+        )
 
     def __repr__(self):
         return "LineInfo(%s)" % (


Test patch:
diff --git a/debuginfo/tests/test_objects.rs b/debuginfo/tests/test_objects.rs
index 1d40338f..8df844fc 100644
--- a/debuginfo/tests/test_objects.rs
+++ b/debuginfo/tests/test_objects.rs
@@ -445,3 +445,29 @@ fn test_pdb_functions() -> Result<(), Error> {
 
     Ok(())
 }
+
+#[test]
+fn test_pdb_anonymous_namespace() -> Result<(), Error> {
+    // Regression test for ?A0x<hash> namespaces
+
+    let view = ByteView::open(fixture("windows/crash.pdb"))?;
+    let object = Object::parse(&view)?;
+
+    let session = object.debug_session()?;
+    let main_function = session
+        .functions()
+        .filter_map(|f| f.ok())
+        .find(|f| f.address == 0x2910)
+        .expect("main function at 0x2910");
+
+    let start_function = main_function
+        .inlinees
+        .iter()
+        .find(|f| f.address == 0x2a3d)
+        .expect("start function at 0x2a3d");
+
+    // was: "?A0xc3a0617d::start"
+    assert_eq!(start_function.name, "`anonymous namespace'::start");
+
+    Ok(())
+}