mirror of
https://gitlab.freedesktop.org/xorg/xserver.git
synced 2026-06-07 06:28:19 +02:00
This test suite is primarily aimed at reproducing the various CVE issues we've had over the years that require custom crafted protocol requests. It may also be useful for other testing. Wrapped in python because pytest is a powerful test suite runner and writing custom buffers is easy. The architecture is so that we fork off an X server (one or more of Xvfb, Xwayland, Xorg) and then run our test clients against that to check whether we get the right reply, or crash the server, or whether valgrind complains about something (valgrind is started automatically for tests that are marked as such). Tests can be run manually via pytest or via meson test. Assisted-by: Claude:claude-claude-opus-4-6 Part-of: <https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2187>
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
# SPDX-License-Identifier: MIT
|
|
#
|
|
# Valgrind XML output parser for extracting memory errors and suppressions.
|
|
|
|
from __future__ import annotations
|
|
|
|
import xml.etree.ElementTree as ET
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
def _suppression_from_element(elem: ET.Element) -> str:
|
|
"""Format a ``<suppression>`` XML element as a suppression file entry."""
|
|
name = elem.findtext("sname", "")
|
|
kind = elem.findtext("skind", "")
|
|
auxiliary = elem.findtext("skaux")
|
|
|
|
lines = ["{", f" {name}", f" {kind}"]
|
|
if auxiliary:
|
|
lines.append(f" {auxiliary}")
|
|
for sframe in elem.iter("sframe"):
|
|
fun = sframe.findtext("fun")
|
|
obj = sframe.findtext("obj")
|
|
if fun is not None:
|
|
lines.append(f" fun:{fun}")
|
|
elif obj is not None:
|
|
lines.append(f" obj:{obj}")
|
|
else:
|
|
lines.append(" ...")
|
|
lines.append("}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
@dataclass
|
|
class ValgrindError:
|
|
"""Represents a single valgrind error extracted from XML output."""
|
|
|
|
kind: str # e.g. "InvalidRead", "InvalidWrite"
|
|
what: str # human-readable description
|
|
stack_frames: list[tuple[str, str | None, str | None]] # (func, file, line)
|
|
suppression: str | None = None # ready-to-paste suppression file entry
|
|
|
|
def __str__(self):
|
|
lines = [f"{self.kind}: {self.what}"]
|
|
for func, srcfile, line in self.stack_frames[:8]:
|
|
loc = f" ({srcfile}:{line})" if srcfile else ""
|
|
lines.append(f" at {func}{loc}")
|
|
if self.suppression:
|
|
lines.append("")
|
|
lines.append(self.suppression)
|
|
return "\n".join(lines)
|
|
|
|
@classmethod
|
|
def from_xml(cls, xml_path: Path) -> list[ValgrindError]:
|
|
"""Parse valgrind XML output and return a list of ValgrindError."""
|
|
if not xml_path.is_file():
|
|
return []
|
|
|
|
try:
|
|
tree = ET.parse(xml_path)
|
|
except ET.ParseError:
|
|
return []
|
|
|
|
errors = []
|
|
root = tree.getroot()
|
|
|
|
for error_elem in root.iter("error"):
|
|
kind_elem = error_elem.find("kind")
|
|
kind = kind_elem.text if kind_elem is not None else "Unknown"
|
|
assert kind is not None
|
|
|
|
what_elem = error_elem.find("what")
|
|
if what_elem is None:
|
|
what_elem = error_elem.find("xwhat/text")
|
|
what = what_elem.text if what_elem is not None else "Unknown error"
|
|
assert what is not None
|
|
|
|
frames = []
|
|
stack_elem = error_elem.find("stack")
|
|
if stack_elem is not None:
|
|
for frame in stack_elem.iter("frame"):
|
|
fn = frame.findtext("fn", "???")
|
|
srcfile = frame.findtext("file", "")
|
|
line = frame.findtext("line", "")
|
|
frames.append((fn, srcfile, line))
|
|
|
|
suppression = None
|
|
supp_elem = error_elem.find("suppression")
|
|
if supp_elem is not None:
|
|
suppression = _suppression_from_element(supp_elem)
|
|
|
|
errors.append(ValgrindError(kind, what, frames, suppression))
|
|
|
|
return errors
|