Skip to content

Commit 6d50cff

Browse files
committed
MAJOR refactor for call dispatch + capturing closures support + latest rustc support + lots more
1 parent b9fdf04 commit 6d50cff

37 files changed

Lines changed: 3345 additions & 1976 deletions

File tree

‎Investigate.py‎

Lines changed: 115 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -78,72 +78,136 @@ def investigate_test(test_name: str, release_mode: bool):
7878

7979
# Handle the case where no custom target JSON is used ('use_target_json.flag' doesn't exist)
8080
if not os.path.exists(use_target_json):
81-
print("|---- Found 'use_target_json.flag', searching for JAR in standard deps folder...")
82-
deps_dir = os.path.join(test_dir, "target", target_dir, "deps")
81+
print("|---- No 'use_target_json.flag' found, searching for JAR in standard deps folder...")
82+
deps_dir_temp = os.path.join(test_dir, "target", target_dir, "deps")
8383
jar_file_name = None
84-
if os.path.isdir(deps_dir):
85-
for file in os.listdir(deps_dir):
84+
if os.path.isdir(deps_dir_temp):
85+
for file in os.listdir(deps_dir_temp):
8686
if file.startswith(test_name) and file.endswith(".jar"):
8787
jar_file_name = file
8888
break
8989

90-
if jar_file_name is None:
91-
print(f"|---- ❌ No JAR file found in {deps_dir}. Cannot continue.")
92-
return
93-
94-
# Move the jar to the location expected by the rest of the script for consistency
95-
src_path = os.path.join(deps_dir, jar_file_name)
96-
dest_dir = os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir)
97-
os.makedirs(dest_dir, exist_ok=True)
98-
dest_path = os.path.join(dest_dir, f"{test_name}.jar")
99-
shutil.move(src_path, dest_path)
100-
print(f"|---- Moved JAR to {dest_path}")
90+
if jar_file_name is not None:
91+
# Move the jar to the location expected by the rest of the script for consistency
92+
src_path = os.path.join(deps_dir_temp, jar_file_name)
93+
dest_dir = os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir)
94+
os.makedirs(dest_dir, exist_ok=True)
95+
dest_path = os.path.join(dest_dir, f"{test_name}.jar")
96+
shutil.move(src_path, dest_path)
97+
print(f"|---- Moved JAR to {dest_path}")
98+
else:
99+
print(f"|---- ⚠️ No JAR file found in {deps_dir_temp}. Will attempt to use raw classfiles later.")
101100

102101
jar_path = os.path.join(test_dir, "target", "jvm-unknown-unknown", target_dir, f"{test_name}.jar")
103102

103+
# Check if JAR exists, if not fall back to using raw classfiles from deps
104+
use_raw_classfiles = False
105+
deps_dir = None
106+
104107
if not os.path.exists(jar_path):
105-
print(f"|---- ❌ JAR file not found at expected path: {jar_path}")
106-
print("|---- Investigation cannot proceed without a JAR file. Exiting.")
107-
return
108+
print(f"|---- ⚠️ JAR file not found at expected path: {jar_path}")
109+
print("|---- Attempting to use raw .class files from deps directory...")
108110

109-
print(f"|---- ✅ Found JAR: {jar_path}")
110-
111-
# 6. Analyze the JAR file
112-
# 6a. `jar tf` - List JAR contents
113-
print("|-- 🔍 Listing JAR contents (jar tf)...")
114-
proc = run_command(["jar", "tf", jar_path])
115-
write_to_file(os.path.join(investigation_dir, "jar_contents.txt"), proc.stdout)
116-
117-
# 6b. `jar xf` - Extract JAR
118-
print("|-- 📦 Extracting JAR contents for analysis...")
119-
run_command(["jar", "xf", os.path.abspath(jar_path)], cwd=extracted_jar_dir)
120-
121-
# 6c. `javap -v -p` on all .class files
122-
print("|-- 뜯 Decompiling .class files (javap -v -p)...")
123-
class_count = 0
124-
for root, _, files in os.walk(extracted_jar_dir):
125-
for file in files:
111+
# Fall back to using the deps directory
112+
deps_dir = os.path.join(test_dir, "target", target_dir, "deps")
113+
if os.path.exists(deps_dir):
114+
main_class = os.path.join(deps_dir, f"{test_name}.class")
115+
if os.path.exists(main_class):
116+
print(f"|---- ✅ Found main class file: {main_class}")
117+
print(f"|---- Continuing investigation with raw classfiles from: {deps_dir}")
118+
use_raw_classfiles = True
119+
else:
120+
print(f"|---- ❌ Main class file not found: {main_class}")
121+
print("|---- Investigation cannot proceed. Exiting.")
122+
return
123+
else:
124+
print(f"|---- ❌ Deps directory not found: {deps_dir}")
125+
print("|---- Investigation cannot proceed. Exiting.")
126+
return
127+
else:
128+
print(f"|---- ✅ Found JAR: {jar_path}")
129+
130+
# 6. Analyze the class files
131+
if use_raw_classfiles and deps_dir is not None:
132+
# Using raw classfiles from deps directory
133+
print("|-- 📋 Listing class files from deps directory...")
134+
class_files = []
135+
for file in os.listdir(deps_dir):
126136
if file.endswith(".class"):
127-
class_count += 1
128-
class_file_path = os.path.join(root, file)
129-
relative_path = os.path.relpath(class_file_path, extracted_jar_dir)
130-
131-
# Create a mirrored directory structure for the output
132-
output_path_dir = os.path.join(javap_dir, os.path.dirname(relative_path))
133-
output_file_path = os.path.join(output_path_dir, os.path.basename(relative_path).replace(".class", ".javap.txt"))
134-
135-
# Run javap on the .class file
136-
proc = run_command(["javap", "-v", "-p", class_file_path])
137-
output_content = f"--- COMMAND ---\njavap -v -p {class_file_path}\n\n--- STDOUT ---\n{proc.stdout}\n\n--- STDERR ---\n{proc.stderr}"
138-
write_to_file(output_file_path, output_content)
139-
140-
print(f"|---- ✅ Decompiled {class_count} class file(s).")
137+
class_files.append(file)
138+
139+
contents_txt = "\n".join(sorted(class_files))
140+
write_to_file(os.path.join(investigation_dir, "class_files_list.txt"), contents_txt)
141+
print(f"|---- Found {len(class_files)} class file(s)")
142+
143+
# Copy class files to extracted_jar_dir for consistency
144+
print("|-- 📦 Copying class files for analysis...")
145+
for file in class_files:
146+
src = os.path.join(deps_dir, file)
147+
dst = os.path.join(extracted_jar_dir, file)
148+
shutil.copy(src, dst)
149+
150+
# 6c. `javap -v -p` on all .class files
151+
print("|-- 뜯 Decompiling .class files (javap -v -p)...")
152+
class_count = 0
153+
for file in class_files:
154+
class_count += 1
155+
class_file_path = os.path.join(extracted_jar_dir, file)
156+
output_file_path = os.path.join(javap_dir, file.replace(".class", ".javap.txt"))
157+
158+
# Run javap on the .class file
159+
proc = run_command(["javap", "-v", "-p", class_file_path])
160+
output_content = f"--- COMMAND ---\njavap -v -p {class_file_path}\n\n--- STDOUT ---\n{proc.stdout}\n\n--- STDERR ---\n{proc.stderr}"
161+
write_to_file(output_file_path, output_content)
162+
163+
print(f"|---- ✅ Decompiled {class_count} class file(s).")
164+
else:
165+
# Using JAR file
166+
# 6a. `jar tf` - List JAR contents
167+
print("|-- 🔍 Listing JAR contents (jar tf)...")
168+
proc = run_command(["jar", "tf", jar_path])
169+
write_to_file(os.path.join(investigation_dir, "jar_contents.txt"), proc.stdout)
170+
171+
# 6b. `jar xf` - Extract JAR
172+
print("|-- 📦 Extracting JAR contents for analysis...")
173+
run_command(["jar", "xf", os.path.abspath(jar_path)], cwd=extracted_jar_dir)
174+
175+
# 6c. `javap -v -p` on all .class files
176+
print("|-- 뜯 Decompiling .class files (javap -v -p)...")
177+
class_count = 0
178+
for root, _, files in os.walk(extracted_jar_dir):
179+
for file in files:
180+
if file.endswith(".class"):
181+
class_count += 1
182+
class_file_path = os.path.join(root, file)
183+
relative_path = os.path.relpath(class_file_path, extracted_jar_dir)
184+
185+
# Create a mirrored directory structure for the output
186+
output_path_dir = os.path.join(javap_dir, os.path.dirname(relative_path))
187+
output_file_path = os.path.join(output_path_dir, os.path.basename(relative_path).replace(".class", ".javap.txt"))
188+
189+
# Run javap on the .class file
190+
proc = run_command(["javap", "-v", "-p", class_file_path])
191+
output_content = f"--- COMMAND ---\njavap -v -p {class_file_path}\n\n--- STDOUT ---\n{proc.stdout}\n\n--- STDERR ---\n{proc.stderr}"
192+
write_to_file(output_file_path, output_content)
193+
194+
print(f"|---- ✅ Decompiled {class_count} class file(s).")
141195

142196
# 7. Run with Java and capture output
143197
print("|-- 🤖 Running with Java and capturing logs...")
144198
# This classpath is copied from the original script. It assumes the script is run from the project root.
145-
classpath = f"library/build/distributions/library-0.1.0/lib/library-0.1.0.jar:library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar:{jar_path}"
146-
java_cmd = ["java", "-cp", classpath, test_name]
199+
runtime_libs = "library/build/distributions/library-0.1.0/lib/library-0.1.0.jar:library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar"
200+
201+
if use_raw_classfiles and deps_dir is not None:
202+
# Use deps directory in classpath instead of JAR
203+
# Use -noverify because stackmap frames aren't generated yet
204+
classpath = f"{runtime_libs}:{deps_dir}"
205+
print(f"|---- Using deps directory in classpath: {deps_dir}")
206+
java_cmd = ["java", "-noverify", "-cp", classpath, test_name]
207+
else:
208+
# Use JAR file
209+
classpath = f"{runtime_libs}:{jar_path}"
210+
java_cmd = ["java", "-cp", classpath, test_name]
147211

148212
proc = run_command(java_cmd)
149213

‎Readme.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ All examples live in `tests/binary` and are compiled to JVM bytecode & run/teste
5353
- Mutable borrowing, references, and dereferencing
5454
- Implementations for ADTs, including using and returning `self`, `&self`, `&mut self`
5555
- Traits, including dynamic dispatch (`&dyn Trait`)
56+
- Closures, including capturing closures
5657
- **Integration tests** for all features, in debug and release modes
5758

5859
🚧 **Next Milestone:** Full support for the Rust `core` crate.

0 commit comments

Comments
 (0)