Skip to content

Commit 602cb68

Browse files
authored
Merge pull request #1980 from codeflash-ai/cf-java-void-optimization
feat: support void method optimization in Java pipeline
2 parents cf59523 + f577794 commit 602cb68

13 files changed

Lines changed: 966 additions & 34 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
name: E2E - Java Void Optimization (No Git)
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'codeflash/languages/java/**'
7+
- 'codeflash/languages/base.py'
8+
- 'codeflash/languages/registry.py'
9+
- 'codeflash/optimization/**'
10+
- 'codeflash/verification/**'
11+
- 'code_to_optimize/java/**'
12+
- 'codeflash-java-runtime/**'
13+
- 'tests/scripts/end_to_end_test_java_void_optimization.py'
14+
- '.github/workflows/e2e-java-void-optimization.yaml'
15+
16+
workflow_dispatch:
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.ref_name }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
java-void-optimization-no-git:
24+
environment: ${{ (github.event_name == 'workflow_dispatch' || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}
25+
26+
runs-on: ubuntu-latest
27+
env:
28+
CODEFLASH_AIS_SERVER: prod
29+
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
30+
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
31+
COLUMNS: 110
32+
MAX_RETRIES: 3
33+
RETRY_DELAY: 5
34+
EXPECTED_IMPROVEMENT_PCT: 70
35+
CODEFLASH_END_TO_END: 1
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v4
39+
with:
40+
ref: ${{ github.event.pull_request.head.ref }}
41+
repository: ${{ github.event.pull_request.head.repo.full_name }}
42+
fetch-depth: 0
43+
token: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Validate PR
46+
env:
47+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
48+
PR_STATE: ${{ github.event.pull_request.state }}
49+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
50+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
51+
run: |
52+
if git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep -q "^.github/workflows/"; then
53+
echo "⚠️ Workflow changes detected."
54+
echo "PR Author: $PR_AUTHOR"
55+
if [[ "$PR_AUTHOR" == "misrasaurabh1" || "$PR_AUTHOR" == "KRRT7" ]]; then
56+
echo "✅ Authorized user ($PR_AUTHOR). Proceeding."
57+
elif [[ "$PR_STATE" == "open" ]]; then
58+
echo "✅ PR is open. Proceeding."
59+
else
60+
echo "⛔ Unauthorized user ($PR_AUTHOR) attempting to modify workflows. Exiting."
61+
exit 1
62+
fi
63+
else
64+
echo "✅ No workflow file changes detected. Proceeding."
65+
fi
66+
67+
- name: Set up JDK 11
68+
uses: actions/setup-java@v4
69+
with:
70+
java-version: '11'
71+
distribution: 'temurin'
72+
cache: maven
73+
74+
- name: Set up Python 3.11 for CLI
75+
uses: astral-sh/setup-uv@v6
76+
with:
77+
python-version: 3.11.6
78+
79+
- name: Install dependencies (CLI)
80+
run: uv sync
81+
82+
- name: Build codeflash-runtime JAR
83+
run: |
84+
cd codeflash-java-runtime
85+
mvn clean package -q -DskipTests
86+
mvn install -q -DskipTests
87+
88+
- name: Verify Java installation
89+
run: |
90+
java -version
91+
mvn --version
92+
93+
- name: Remove .git
94+
run: |
95+
if [ -d ".git" ]; then
96+
sudo rm -rf .git
97+
echo ".git directory removed."
98+
else
99+
echo ".git directory does not exist."
100+
exit 1
101+
fi
102+
103+
- name: Run Codeflash to optimize void function
104+
run: |
105+
uv run python tests/scripts/end_to_end_test_java_void_optimization.py
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example;
2+
3+
public class InPlaceSorter {
4+
5+
public static void bubbleSortInPlace(int[] arr) {
6+
if (arr == null || arr.length <= 1) {
7+
return;
8+
}
9+
10+
int n = arr.length;
11+
for (int i = 0; i < n; i++) {
12+
for (int j = 0; j < n - 1; j++) {
13+
if (arr[j] > arr[j + 1]) {
14+
int temp = arr[j];
15+
arr[j] = arr[j + 1];
16+
arr[j + 1] = temp;
17+
}
18+
}
19+
}
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example;
2+
3+
public class InstanceSorter {
4+
5+
public void bubbleSortInPlace(int[] arr) {
6+
if (arr == null || arr.length <= 1) {
7+
return;
8+
}
9+
10+
int n = arr.length;
11+
for (int i = 0; i < n; i++) {
12+
for (int j = 0; j < n - 1; j++) {
13+
if (arr[j] > arr[j + 1]) {
14+
int temp = arr[j];
15+
arr[j] = arr[j + 1];
16+
arr[j + 1] = temp;
17+
}
18+
}
19+
}
20+
}
21+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.example;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
class InPlaceSorterTest {
7+
8+
@Test
9+
void testBubbleSortInPlace() {
10+
int[] arr = {5, 3, 1, 4, 2};
11+
InPlaceSorter.bubbleSortInPlace(arr);
12+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
13+
}
14+
15+
@Test
16+
void testBubbleSortInPlaceAlreadySorted() {
17+
int[] arr = {1, 2, 3, 4, 5};
18+
InPlaceSorter.bubbleSortInPlace(arr);
19+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
20+
}
21+
22+
@Test
23+
void testBubbleSortInPlaceReversed() {
24+
int[] arr = {5, 4, 3, 2, 1};
25+
InPlaceSorter.bubbleSortInPlace(arr);
26+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
27+
}
28+
29+
@Test
30+
void testBubbleSortInPlaceWithDuplicates() {
31+
int[] arr = {3, 2, 4, 1, 3, 2};
32+
InPlaceSorter.bubbleSortInPlace(arr);
33+
assertArrayEquals(new int[]{1, 2, 2, 3, 3, 4}, arr);
34+
}
35+
36+
@Test
37+
void testBubbleSortInPlaceWithNegatives() {
38+
int[] arr = {3, -2, 7, 0, -5};
39+
InPlaceSorter.bubbleSortInPlace(arr);
40+
assertArrayEquals(new int[]{-5, -2, 0, 3, 7}, arr);
41+
}
42+
43+
@Test
44+
void testBubbleSortInPlaceSingleElement() {
45+
int[] arr = {42};
46+
InPlaceSorter.bubbleSortInPlace(arr);
47+
assertArrayEquals(new int[]{42}, arr);
48+
}
49+
50+
@Test
51+
void testBubbleSortInPlaceEmpty() {
52+
int[] arr = {};
53+
InPlaceSorter.bubbleSortInPlace(arr);
54+
assertArrayEquals(new int[]{}, arr);
55+
}
56+
57+
@Test
58+
void testBubbleSortInPlaceNull() {
59+
InPlaceSorter.bubbleSortInPlace(null);
60+
}
61+
62+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.example;
2+
3+
import org.junit.jupiter.api.Test;
4+
import static org.junit.jupiter.api.Assertions.*;
5+
6+
class InstanceSorterTest {
7+
8+
@Test
9+
void testBubbleSortInPlace() {
10+
InstanceSorter sorter = new InstanceSorter();
11+
int[] arr = {5, 3, 1, 4, 2};
12+
sorter.bubbleSortInPlace(arr);
13+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
14+
}
15+
16+
@Test
17+
void testBubbleSortInPlaceAlreadySorted() {
18+
InstanceSorter sorter = new InstanceSorter();
19+
int[] arr = {1, 2, 3, 4, 5};
20+
sorter.bubbleSortInPlace(arr);
21+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
22+
}
23+
24+
@Test
25+
void testBubbleSortInPlaceReversed() {
26+
InstanceSorter sorter = new InstanceSorter();
27+
int[] arr = {5, 4, 3, 2, 1};
28+
sorter.bubbleSortInPlace(arr);
29+
assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr);
30+
}
31+
32+
@Test
33+
void testBubbleSortInPlaceWithDuplicates() {
34+
InstanceSorter sorter = new InstanceSorter();
35+
int[] arr = {3, 2, 4, 1, 3, 2};
36+
sorter.bubbleSortInPlace(arr);
37+
assertArrayEquals(new int[]{1, 2, 2, 3, 3, 4}, arr);
38+
}
39+
40+
@Test
41+
void testBubbleSortInPlaceWithNegatives() {
42+
InstanceSorter sorter = new InstanceSorter();
43+
int[] arr = {3, -2, 7, 0, -5};
44+
sorter.bubbleSortInPlace(arr);
45+
assertArrayEquals(new int[]{-5, -2, 0, 3, 7}, arr);
46+
}
47+
48+
@Test
49+
void testBubbleSortInPlaceSingleElement() {
50+
InstanceSorter sorter = new InstanceSorter();
51+
int[] arr = {42};
52+
sorter.bubbleSortInPlace(arr);
53+
assertArrayEquals(new int[]{42}, arr);
54+
}
55+
56+
@Test
57+
void testBubbleSortInPlaceEmpty() {
58+
InstanceSorter sorter = new InstanceSorter();
59+
int[] arr = {};
60+
sorter.bubbleSortInPlace(arr);
61+
assertArrayEquals(new int[]{}, arr);
62+
}
63+
64+
@Test
65+
void testBubbleSortInPlaceNull() {
66+
InstanceSorter sorter = new InstanceSorter();
67+
sorter.bubbleSortInPlace(null);
68+
}
69+
}

codeflash-java-runtime/src/test/java/com/codeflash/ComparatorCorrectnessTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,87 @@ void testIsDeserializationError() {
209209
assertFalse(Comparator.isDeserializationError(42));
210210
}
211211

212+
// ============================================================
213+
// VOID METHOD STATE COMPARISON — proves we actually compare
214+
// post-call state for void methods, not just skip them
215+
// ============================================================
216+
217+
@Test
218+
@DisplayName("void state: both sides sorted identically → equivalent")
219+
void testVoidState_identicalMutation_equivalent() throws Exception {
220+
createTestDb(originalDb);
221+
createTestDb(candidateDb);
222+
223+
// Simulate: bubbleSortInPlace(arr) — both original and candidate sort correctly
224+
// Post-call state: Object[]{sorted_array}
225+
int[] sortedArr = {1, 2, 3, 4, 5};
226+
byte[] origState = Serializer.serialize(new Object[]{sortedArr});
227+
byte[] candState = Serializer.serialize(new Object[]{new int[]{1, 2, 3, 4, 5}});
228+
229+
insertRow(originalDb, "L1_1", 1, origState);
230+
insertRow(candidateDb, "L1_1", 1, candState);
231+
232+
String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
233+
Map<String, Object> result = parseJson(json);
234+
235+
assertTrue((Boolean) result.get("equivalent"),
236+
"Both sides produce same sorted array — should be equivalent");
237+
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
238+
}
239+
240+
@Test
241+
@DisplayName("void state: candidate mutates array differently → NOT equivalent")
242+
void testVoidState_differentMutation_rejected() throws Exception {
243+
createTestDb(originalDb);
244+
createTestDb(candidateDb);
245+
246+
// Simulate: original sorts [3,1,2] → [1,2,3]
247+
// Bad optimization doesn't sort correctly → [3,1,2] unchanged
248+
byte[] origState = Serializer.serialize(new Object[]{new int[]{1, 2, 3}});
249+
byte[] candState = Serializer.serialize(new Object[]{new int[]{3, 1, 2}});
250+
251+
insertRow(originalDb, "L1_1", 1, origState);
252+
insertRow(candidateDb, "L1_1", 1, candState);
253+
254+
String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
255+
Map<String, Object> result = parseJson(json);
256+
257+
assertFalse((Boolean) result.get("equivalent"),
258+
"Candidate produced wrong array — must be rejected");
259+
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
260+
}
261+
262+
@Test
263+
@DisplayName("void state: receiver + args both compared — wrong receiver state rejected")
264+
void testVoidState_receiverAndArgs_wrongReceiverRejected() throws Exception {
265+
createTestDb(originalDb);
266+
createTestDb(candidateDb);
267+
268+
// Simulate: instance method sorter.sort(data)
269+
// Post-call state is Object[]{receiver_fields_map, mutated_data}
270+
// Original: receiver has size=3, data is [1,2,3]
271+
// Candidate: receiver has size=0 (wrong), data is [1,2,3]
272+
Map<String, Object> origReceiver = new HashMap<>();
273+
origReceiver.put("size", 3);
274+
origReceiver.put("sorted", true);
275+
Map<String, Object> candReceiver = new HashMap<>();
276+
candReceiver.put("size", 0);
277+
candReceiver.put("sorted", true);
278+
279+
byte[] origState = Serializer.serialize(new Object[]{origReceiver, new int[]{1, 2, 3}});
280+
byte[] candState = Serializer.serialize(new Object[]{candReceiver, new int[]{1, 2, 3}});
281+
282+
insertRow(originalDb, "L1_1", 1, origState);
283+
insertRow(candidateDb, "L1_1", 1, candState);
284+
285+
String json = Comparator.compareDatabases(originalDb.toString(), candidateDb.toString());
286+
Map<String, Object> result = parseJson(json);
287+
288+
assertFalse((Boolean) result.get("equivalent"),
289+
"Receiver state differs (size 3 vs 0) — must be rejected even though args match");
290+
assertEquals(1, ((Number) result.get("actualComparisons")).intValue());
291+
}
292+
212293
// --- Helpers ---
213294

214295
private void createTestDb(Path dbPath) throws Exception {

codeflash/discovery/functions_to_optimize.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ def _find_all_functions_via_language_support(file_path: Path) -> dict[Path, list
195195

196196
try:
197197
lang_support = get_language_support(file_path)
198-
criteria = FunctionFilterCriteria(require_return=True)
198+
require_return = lang_support.language != Language.JAVA
199+
criteria = FunctionFilterCriteria(require_return=require_return)
199200
functions[file_path] = lang_support.discover_functions(file_path, criteria)
200201
except Exception as e:
201202
logger.debug(f"Failed to discover functions in {file_path}: {e}")
@@ -454,7 +455,8 @@ def find_all_functions_in_file(file_path: Path) -> dict[Path, list[FunctionToOpt
454455
from codeflash.languages.base import FunctionFilterCriteria
455456

456457
lang_support = get_language_support(file_path)
457-
criteria = FunctionFilterCriteria(require_return=True)
458+
require_return = lang_support.language != Language.JAVA
459+
criteria = FunctionFilterCriteria(require_return=require_return)
458460
source = file_path.read_text(encoding="utf-8")
459461
return {file_path: lang_support.discover_functions(source, file_path, criteria)}
460462
except Exception as e:

0 commit comments

Comments
 (0)