Files
Laszlo NagyandClaude Fable 5 9ff05ddd3c fix: tolerate non-UTF-8 output from executed commands
run_command decoded subprocess output as strict UTF-8, so localized
clang diagnostics or source lines echoed in another encoding raised
UnicodeDecodeError and crashed the analysis worker. The decode in
the CalledProcessError handler was worse: a decode failure there
replaced the real error. Decode with errors="replace" in both
places, like safe_readlines already does for report files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 21:51:47 +10:00

61 lines
1.9 KiB
Python

# SPDX-License-Identifier: MIT
"""This module is a collection of methods commonly used in this project."""
import logging
import os
import os.path
import re
import shlex
import subprocess
def _unescape_shell_arg(arg: str) -> str:
"""Remove escaping characters from a shell argument.
Handles both quoted strings and escaped characters in shell arguments.
"""
if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] == '"':
return re.sub(r'\\(["\\])', r"\1", arg[1:-1])
return re.sub(r"\\([\\ $%&\(\)\[\]\{\}\*|<>@?!])", r"\1", arg)
def shell_split(string: str) -> list[str]:
"""Split a command string into arguments and unescape them.
Args:
string: Command string to split
Returns:
List of unescaped command arguments
"""
return [_unescape_shell_arg(token) for token in shlex.split(string)]
def run_command(command: list[str], cwd: str | None = None) -> list[str]:
"""Run a command and return its output as lines.
Args:
command: Command as a list of arguments
cwd: Working directory for command execution (defaults to current directory)
Returns:
Command output split into lines
Raises:
subprocess.CalledProcessError: If command execution fails
"""
try:
directory = os.path.abspath(cwd) if cwd else os.getcwd()
logging.debug("exec command %s in %s", command, directory)
output: bytes = subprocess.check_output(
command,
cwd=directory,
stderr=subprocess.STDOUT,
)
# the output is not necessarily valid UTF-8 (localized diagnostics,
# source lines echoed in another encoding); never fail on decoding
return output.decode("utf-8", errors="replace").splitlines()
except subprocess.CalledProcessError as ex:
ex.output = ex.output.decode("utf-8", errors="replace").splitlines()
raise