fix: correct ilspycmd output parsing and heap access
- Fix _TYPE_LINE_PATTERN regex to match ilspycmd format (no colon) - Fix UserStringHeap data access using getattr instead of name mangling - Add _decompile_to_stdout method for simple decompilation - Fix decompile to not always use -o flag when stdout is desired - Update test data to match actual ilspycmd output format
This commit is contained in:
parent
3c21b9d640
commit
609d97b86d
@ -194,15 +194,18 @@ class ILSpyWrapper:
|
||||
assembly_name=os.path.basename(request.assembly_path),
|
||||
)
|
||||
|
||||
# Use TemporaryDirectory context manager for guaranteed cleanup (no race condition)
|
||||
# when user doesn't specify an output directory
|
||||
# When user specifies output_dir OR uses project mode, we need files
|
||||
# Otherwise, we can get output directly from stdout
|
||||
if request.output_dir:
|
||||
# User specified output directory - use it directly
|
||||
return await self._decompile_to_dir(request, request.output_dir)
|
||||
else:
|
||||
# Create a temporary directory with guaranteed cleanup
|
||||
elif request.create_project:
|
||||
# Project mode needs temp directory for multiple files
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
return await self._decompile_to_dir(request, temp_dir)
|
||||
else:
|
||||
# Simple decompilation - get output from stdout (no -o flag)
|
||||
return await self._decompile_to_stdout(request)
|
||||
|
||||
async def _decompile_to_dir(
|
||||
self, request: DecompileRequest, output_dir: str
|
||||
@ -312,6 +315,79 @@ class ILSpyWrapper:
|
||||
type_name=request.type_name,
|
||||
)
|
||||
|
||||
async def _decompile_to_stdout(self, request: DecompileRequest) -> DecompileResponse:
|
||||
"""Decompile to stdout (no -o flag) for simple single-file decompilation.
|
||||
|
||||
Args:
|
||||
request: Decompilation request (output_dir must be None)
|
||||
|
||||
Returns:
|
||||
Decompilation response with source_code from stdout
|
||||
"""
|
||||
args = [request.assembly_path]
|
||||
|
||||
# Add language version
|
||||
args.extend(["-lv", request.language_version.value])
|
||||
|
||||
# Add type filter if specified
|
||||
if request.type_name:
|
||||
args.extend(["-t", request.type_name])
|
||||
|
||||
# No -o flag - output goes to stdout
|
||||
|
||||
# Add IL code flag
|
||||
if request.show_il_code:
|
||||
args.append("-il")
|
||||
|
||||
# Add reference paths
|
||||
for ref_path in request.reference_paths:
|
||||
args.extend(["-r", ref_path])
|
||||
|
||||
# Add optimization flags
|
||||
if request.remove_dead_code:
|
||||
args.append("--no-dead-code")
|
||||
|
||||
if request.remove_dead_stores:
|
||||
args.append("--no-dead-stores")
|
||||
|
||||
# Add IL sequence points flag
|
||||
if request.show_il_sequence_points:
|
||||
args.append("--il-sequence-points")
|
||||
|
||||
# Disable update check for automation
|
||||
args.append("--disable-updatecheck")
|
||||
|
||||
assembly_name = os.path.splitext(os.path.basename(request.assembly_path))[0]
|
||||
|
||||
try:
|
||||
return_code, stdout, stderr = await self._run_command(args)
|
||||
|
||||
if return_code == 0:
|
||||
return DecompileResponse(
|
||||
success=True,
|
||||
source_code=stdout,
|
||||
output_path=None,
|
||||
assembly_name=assembly_name,
|
||||
type_name=request.type_name,
|
||||
)
|
||||
else:
|
||||
error_msg = stderr or stdout or "Unknown error occurred"
|
||||
return DecompileResponse(
|
||||
success=False,
|
||||
error_message=error_msg,
|
||||
assembly_name=assembly_name,
|
||||
type_name=request.type_name,
|
||||
)
|
||||
|
||||
except OSError as e:
|
||||
logger.exception(f"Error during decompilation: {e}")
|
||||
return DecompileResponse(
|
||||
success=False,
|
||||
error_message=str(e),
|
||||
assembly_name=assembly_name,
|
||||
type_name=request.type_name,
|
||||
)
|
||||
|
||||
async def list_types(self, request: ListTypesRequest) -> ListTypesResponse:
|
||||
"""List types in a .NET assembly.
|
||||
|
||||
@ -359,8 +435,9 @@ class ILSpyWrapper:
|
||||
return ListTypesResponse(success=False, error_message=str(e))
|
||||
|
||||
# Compiled regex for parsing ilspycmd list output
|
||||
# Format: "TypeKind: FullTypeName" (e.g., "Class: MyNamespace.MyClass")
|
||||
_TYPE_LINE_PATTERN = re.compile(r"^(\w+):\s*(.+)$")
|
||||
# Format: "TypeKind FullTypeName" (e.g., "Class MyNamespace.MyClass")
|
||||
# Only matches valid type kinds: Class, Interface, Struct, Enum, Delegate
|
||||
_TYPE_LINE_PATTERN = re.compile(r"^(Class|Interface|Struct|Enum|Delegate)\s+(.+)$")
|
||||
|
||||
def _parse_types_output(self, output: str) -> list[TypeInfo]:
|
||||
"""Parse the output from list types command.
|
||||
|
||||
@ -570,7 +570,8 @@ class MetadataReader:
|
||||
return
|
||||
|
||||
heap = pe.net.user_strings
|
||||
data = heap._ClrStream__data__ # Access the raw bytes
|
||||
# Access raw bytes using getattr to avoid Python name mangling issues
|
||||
data = getattr(heap, "__data__", None) # Raw heap bytes
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
@ -41,7 +41,7 @@ class TestILSpyWrapperTypeParsing:
|
||||
except RuntimeError:
|
||||
pytest.skip("ilspycmd not installed")
|
||||
|
||||
output = "Class: MyNamespace.MyClass"
|
||||
output = "Class MyNamespace.MyClass"
|
||||
types = wrapper._parse_types_output(output)
|
||||
|
||||
assert len(types) == 1
|
||||
@ -57,11 +57,11 @@ class TestILSpyWrapperTypeParsing:
|
||||
except RuntimeError:
|
||||
pytest.skip("ilspycmd not installed")
|
||||
|
||||
output = """Class: NS.ClassA
|
||||
Interface: NS.IService
|
||||
Struct: NS.MyStruct
|
||||
Enum: NS.MyEnum
|
||||
Delegate: NS.MyDelegate"""
|
||||
output = """Class NS.ClassA
|
||||
Interface NS.IService
|
||||
Struct NS.MyStruct
|
||||
Enum NS.MyEnum
|
||||
Delegate NS.MyDelegate"""
|
||||
|
||||
types = wrapper._parse_types_output(output)
|
||||
assert len(types) == 5
|
||||
@ -81,7 +81,7 @@ Delegate: NS.MyDelegate"""
|
||||
except RuntimeError:
|
||||
pytest.skip("ilspycmd not installed")
|
||||
|
||||
output = "Class: MyNamespace.Outer+Nested"
|
||||
output = "Class MyNamespace.Outer+Nested"
|
||||
types = wrapper._parse_types_output(output)
|
||||
|
||||
assert len(types) == 1
|
||||
@ -95,7 +95,7 @@ Delegate: NS.MyDelegate"""
|
||||
except RuntimeError:
|
||||
pytest.skip("ilspycmd not installed")
|
||||
|
||||
output = "Class: MyClass"
|
||||
output = "Class MyClass"
|
||||
types = wrapper._parse_types_output(output)
|
||||
|
||||
assert len(types) == 1
|
||||
@ -109,10 +109,10 @@ Delegate: NS.MyDelegate"""
|
||||
except RuntimeError:
|
||||
pytest.skip("ilspycmd not installed")
|
||||
|
||||
output = """Class: NS.Valid
|
||||
output = """Class NS.Valid
|
||||
This is not a valid line
|
||||
Another invalid line
|
||||
Interface: NS.AlsoValid"""
|
||||
Interface NS.AlsoValid"""
|
||||
|
||||
types = wrapper._parse_types_output(output)
|
||||
assert len(types) == 2
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user