Skip to content
Merged
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
8 changes: 4 additions & 4 deletions scripts/deps
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ DEPS = [
),
Dep(
"cwl-demangle",
"https://github.com/getsentry/CwlDemangle/releases/download/v1.1.0/cwl-demangle-linux-x86_64",
"e15449f24a73c184c05640100ec4e0c1e9c05c3542b497e9243eb4c3c1d607dd",
"https://github.com/getsentry/CwlDemangle/releases/download/v1.2.0/cwl-demangle-linux-x86_64",
"f3c543536c1c35d3c61e8f88cd96daa0f45a1145f123b6d6227fa3598464a262",
architecture="x86_64",
system="linux",
binaries=[
Expand All @@ -173,8 +173,8 @@ DEPS = [
),
Dep(
"cwl-demangle",
"https://github.com/getsentry/CwlDemangle/releases/download/v1.1.0/cwl-demangle-macos-arm64",
"a91dee08c3794fbea2009bfe52262b25df6d4c486539e85fd906aca4fa9e9d7f",
"https://github.com/getsentry/CwlDemangle/releases/download/v1.2.0/cwl-demangle-macos-arm64",
"8de11faa6582db54f7a8c2c72c63252e4f3b6b59fdbba6f785efe97f3ddcba71",
architecture="aarch64",
system="darwin",
binaries=[
Expand Down
5 changes: 5 additions & 0 deletions sentry-options/schemas/launchpad/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"type": "integer",
"default": 4,
"description": "Number of worker processes for Apple per-binary analysis. 0 runs binaries in-process, one at a time."
},
"size.swift_demangling.json_summary.enabled": {
"type": "boolean",
"default": false,
"description": "Use the compact JSON summary output from cwl-demangle."
}
}
}
5 changes: 4 additions & 1 deletion src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,10 @@ def _create_analyzer(self, artifact: AndroidArtifact | AppleArtifact) -> Android
if isinstance(artifact, AndroidArtifact):
return AndroidAnalyzer()
elif isinstance(artifact, AppleArtifact):
return AppleAppAnalyzer(binary_analysis_workers=get_option("size.binary_analysis.workers", 4))
return AppleAppAnalyzer(
binary_analysis_workers=get_option("size.binary_analysis.workers", 4),
use_json_summary=get_option("size.swift_demangling.json_summary.enabled", False),
)
else:
raise ValueError(f"Unknown artifact kind {artifact}")

Expand Down
8 changes: 7 additions & 1 deletion src/launchpad/size/analyzers/apple.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def __init__(
skip_image_analysis: bool = False,
skip_insights: bool = False,
binary_analysis_workers: int = _DEFAULT_BINARY_ANALYSIS_WORKERS,
use_json_summary: bool = False,
) -> None:
"""Initialize the Apple analyzer.

Expand All @@ -140,9 +141,11 @@ def __init__(
skip_image_analysis: Skip image analysis for faster processing
skip_insights: Skip insights generation for faster analysis
binary_analysis_workers: Processes for per-binary analysis; 0 runs binaries in-process
use_json_summary: Use compact JSON output when demangling Swift symbols
"""
self.working_dir = working_dir
self.binary_analysis_workers = binary_analysis_workers
self.use_json_summary = use_json_summary
self.skip_swift_metadata = skip_swift_metadata
self.skip_symbols = skip_symbols
self.skip_component_analysis = skip_component_analysis
Expand Down Expand Up @@ -630,7 +633,10 @@ def _analyze_binary(
dsym_index = dsym_arch_map[arch_slice.arch_name]
dsym_binary = dwarf_fat_binary.at(dsym_index)
slice_symbol_sizes = MachOSymbolSizes(dsym_binary).get_symbol_sizes()
arch_slice.symbol_info = SymbolInfo.from_symbol_sizes(symbol_sizes=slice_symbol_sizes)
arch_slice.symbol_info = SymbolInfo.from_symbol_sizes(
symbol_sizes=slice_symbol_sizes,
use_json_summary=self.use_json_summary,
)
logger.debug(
f"Symbolicated {arch_slice.arch_name} slice with {len(slice_symbol_sizes)} symbols"
)
Expand Down
6 changes: 4 additions & 2 deletions src/launchpad/size/symbols/partitioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def _is_compiler_generated(mangled_name: str) -> bool:

@classmethod
@sentry_sdk.trace
def from_symbol_sizes(cls, symbol_sizes: List[SymbolSize]) -> "SymbolInfo":
def from_symbol_sizes(cls, symbol_sizes: List[SymbolSize], use_json_summary: bool = False) -> "SymbolInfo":
with sentry_sdk.start_span(op="partition_symbols", description="Partition symbols by language"):
swift_symbols = SwiftSymbolList()
objc_symbols = ObjCSymbolList()
Expand All @@ -108,7 +108,9 @@ def from_symbol_sizes(cls, symbol_sizes: List[SymbolSize]) -> "SymbolInfo":
other_symbols.append(symbol)

# Aggregate each partition
swift_type_groups = SwiftSymbolTypeAggregator().aggregate_symbols(swift_symbols)
swift_type_groups = SwiftSymbolTypeAggregator(use_json_summary=use_json_summary).aggregate_symbols(
swift_symbols
)
objc_type_groups = ObjCSymbolTypeAggregator().aggregate_symbols(objc_symbols)
cpp_type_groups = CppSymbolTypeAggregator().aggregate_symbols(cpp_symbols)

Expand Down
6 changes: 3 additions & 3 deletions src/launchpad/size/symbols/swift_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class SwiftModuleType(NamedTuple):
class SwiftSymbolTypeAggregator:
"""Aggregates symbols by their module/type after demangling."""

def __init__(self) -> None:
self.demangler = CwlDemangler()
def __init__(self, use_json_summary: bool = False) -> None:
self.demangler = CwlDemangler(use_json_summary=use_json_summary)

@staticmethod
def is_swift_symbol(mangled_name: str) -> bool:
Expand Down Expand Up @@ -56,7 +56,7 @@ def aggregate_symbols(self, symbol_sizes: SwiftSymbolList) -> list[SwiftSymbolTy
if demangled_result:
# Use module and type from demangled result
module = demangled_result.module or "Unattributed"
type_name = demangled_result.typeName or demangled_result.type or "Unattributed"
type_name = demangled_result.typeName or "Unattributed"
else:
# Fallback for symbols that couldn't be demangled
module = "Unattributed"
Expand Down
13 changes: 4 additions & 9 deletions src/launchpad/utils/apple/cwl_demangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@
class CwlDemangleResult:
"""Result from cwl-demangle tool parsing."""

name: str
type: str
identifier: str
module: str
testName: List[str]
typeName: str
description: str
mangled: str


Expand All @@ -42,18 +38,21 @@ def __init__(
self,
is_type: bool = False,
continue_on_error: bool = True,
use_json_summary: bool = False,
):
"""
Initialize the CwlDemangler.

Args:
is_type: Whether to treat inputs as types rather than symbols
continue_on_error: Whether to continue processing on errors
use_json_summary: Whether to request compact JSON output
"""
self.is_type = is_type
self.queue: List[str] = []
self.continue_on_error = continue_on_error
self.uuid = str(uuid.uuid4())
self.json_output_flag = "--json-summary" if use_json_summary else "--json"

# Disable parallel processing if LAUNCHPAD_NO_PARALLEL_DEMANGLE=true
env_disable = os.environ.get("LAUNCHPAD_NO_PARALLEL_DEMANGLE", "").lower() == "true"
Expand Down Expand Up @@ -153,7 +152,7 @@ def _demangle_chunk(self, chunk: List[str], chunk_idx: int) -> Dict[str, CwlDema
"batch",
"--input",
temp_file.name,
"--json",
self.json_output_flag,
]

if self.is_type:
Expand Down Expand Up @@ -183,13 +182,9 @@ def _demangle_chunk(self, chunk: List[str], chunk_idx: int) -> Dict[str, CwlDema
mangled = symbol_result.get("mangled", "")
if mangled in chunk_set:
demangle_result = CwlDemangleResult(
name=symbol_result["name"],
type=symbol_result["type"],
identifier=symbol_result["identifier"],
module=symbol_result["module"],
testName=symbol_result["testName"],
typeName=symbol_result["typeName"],
description=symbol_result["description"],
mangled=mangled,
)
results[mangled] = demangle_result
Expand Down
10 changes: 8 additions & 2 deletions tests/integration/test_cwl_demangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from unittest import mock

import pytest

from launchpad.utils.apple.cwl_demangle import CwlDemangler, CwlDemangleResult


Expand All @@ -28,9 +30,10 @@ def test_demangle_all_empty_queue(self):
result = demangler.demangle_all()
assert result == {}

def test_demangle_all_success(self):
@pytest.mark.parametrize("use_json_summary", [False, True])
def test_demangle_all_success(self, use_json_summary: bool):
"""Test successful demangling with real cwl-demangle."""
demangler = CwlDemangler()
demangler = CwlDemangler(use_json_summary=use_json_summary)
demangler.add_name(
"_$s6Sentry0A14OnDemandReplayC8addFrame33_70FE3B80E922CEF5576FF378226AFAE1LL5image9forScreenySo7UIImageC_SSSgtF"
)
Expand Down Expand Up @@ -59,6 +62,9 @@ def test_demangle_all_success(self):
first_result.mangled
== "_$s6Sentry0A14OnDemandReplayC8addFrame33_70FE3B80E922CEF5576FF378226AFAE1LL5image9forScreenySo7UIImageC_SSSgtF"
)
assert first_result.module == "Sentry"
assert first_result.typeName == "SentryOnDemandReplay"
assert first_result.testName == ["Sentry", "SentryOnDemandReplay", "addFrame(image,forScreen)"]

second_result = result[
"_$s6Sentry0A18UserFeedbackWidgetC18RootViewControllerC6config6buttonAeA0abC13ConfigurationC_AA0abcd6ButtonF0Ctcfc"
Expand Down
13 changes: 12 additions & 1 deletion tests/unit/artifacts/test_artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ def test_env_and_option_are_unioned_and_deduped(self, monkeypatch):
assert get_service_config().projects_to_skip == ["shared", "env-only", "opt-only"]


class TestCreateAnalyzerBinaryAnalysisWorkers:
class TestCreateAppleAnalyzerOptions:
def test_apple_analyzer_uses_schema_default(self):
processor = ArtifactProcessor(Mock(), Mock(), Mock())
analyzer = processor._create_analyzer(Mock(spec=AppleArtifact))
Expand All @@ -666,3 +666,14 @@ def test_apple_analyzer_uses_option_value(self):
with override_options("launchpad", {"size.binary_analysis.workers": 0}):
analyzer = processor._create_analyzer(Mock(spec=AppleArtifact))
assert analyzer.binary_analysis_workers == 0

def test_json_summary_uses_schema_default(self):
processor = ArtifactProcessor(Mock(), Mock(), Mock())
analyzer = processor._create_analyzer(Mock(spec=AppleArtifact))
assert analyzer.use_json_summary is False

def test_json_summary_uses_option_value(self):
processor = ArtifactProcessor(Mock(), Mock(), Mock())
with override_options("launchpad", {"size.swift_demangling.json_summary.enabled": True}):
analyzer = processor._create_analyzer(Mock(spec=AppleArtifact))
assert analyzer.use_json_summary is True
Loading