blob: 23ab044aa5ad56c54eecea5ea84c3711799f9e03 [file]
#!/usr/bin/env python3
# Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
import os
import subprocess
import argparse
import tempfile
import zipfile
from multiprocessing import Pool, cpu_count
def decompile_file(args):
"""Decompiles a single class file using javap -v -p."""
class_file, output_file = args
# Ensure the output directory exists. os.makedirs(exist_ok=True) is process-safe
# in Python 3.2+, so multiple workers can call this without a race condition.
os.makedirs(os.path.dirname(output_file), exist_ok=True)
try:
with open(output_file, 'w') as f:
subprocess.run(['javap', '-v', '-p', class_file],
stdout=f,
stderr=subprocess.PIPE,
check=True)
return True
except subprocess.CalledProcessError as e:
print(f"Error decompiling {class_file}: {e.stderr.decode().strip()}")
return False
except Exception as e:
print(f"Unexpected error with {class_file}: {e}")
return False
def process_classes(classes_dir, output_dir, jobs):
"""Processes all class files in the given directory."""
tasks = []
for root, _, files in os.walk(classes_dir):
for file in files:
if file.endswith(".class"):
class_path = os.path.join(root, file)
rel_path = os.path.relpath(class_path, classes_dir)
output_path = os.path.join(
output_dir,
os.path.splitext(rel_path)[0] + ".javap")
tasks.append((class_path, output_path))
if not tasks:
print("No .class files found.")
return
print(
f"Found {len(tasks)} files. Decompiling using {jobs} parallel jobs...")
with Pool(jobs) as pool:
results = pool.map(decompile_file, tasks)
success_count = sum(results)
print(
f"Finished. Successfully decompiled {success_count} / {len(tasks)} files."
)
def main():
parser = argparse.ArgumentParser(
description="Decompile Java class files using javap -v -p.")
parser.add_argument(
"classes", help="Directory containing .class files or a .jar file.")
parser.add_argument("output",
help="Directory to save decompiled .javap files.")
parser.add_argument("-j",
"--jobs",
type=int,
default=cpu_count(),
help="Number of parallel jobs (default: all CPUs).")
args = parser.parse_args()
input_path = os.path.abspath(args.classes)
output_dir = os.path.abspath(args.output)
if not os.path.exists(input_path):
print(f"Error: Input path '{input_path}' does not exist.")
return
if os.path.isfile(input_path) and input_path.endswith(".jar"):
print(f"Input is a JAR file. Extracting {input_path}...")
with tempfile.TemporaryDirectory() as tmp_dir:
try:
with zipfile.ZipFile(input_path, 'r') as jar:
jar.extractall(tmp_dir)
process_classes(tmp_dir, output_dir, args.jobs)
except zipfile.BadZipFile:
print(f"Error: '{input_path}' is not a valid JAR/ZIP file.")
elif os.path.isdir(input_path):
process_classes(input_path, output_dir, args.jobs)
else:
print(f"Error: '{input_path}' is neither a directory nor a .jar file.")
if __name__ == "__main__":
main()