Skip to content

Commit 59f189a

Browse files
authored
Merge pull request #1472 from WebFuzzing/first-neo4j
First PR for neo4j
2 parents 05c5684 + 75a90c5 commit 59f189a

9 files changed

Lines changed: 555 additions & 1 deletion

File tree

client-java/instrumentation-shared/src/main/java/org/evomaster/client/java/instrumentation/shared/ReplacementCategory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public enum ReplacementCategory {
4545
/**
4646
* Replacements to handle REDIS command interceptions
4747
*/
48-
REDIS
48+
REDIS,
49+
50+
/**
51+
* Replacements to handle NEO4J command interceptions
52+
*/
53+
NEO4J
4954
}
5055

client-java/instrumentation/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,18 @@
188188
<artifactId>mongodb-driver-sync</artifactId>
189189
<scope>test</scope>
190190
</dependency>
191+
<dependency>
192+
<groupId>org.neo4j.driver</groupId>
193+
<artifactId>neo4j-java-driver</artifactId>
194+
<version>4.4.12</version>
195+
<scope>test</scope>
196+
</dependency>
197+
<dependency>
198+
<groupId>org.springframework.boot</groupId>
199+
<artifactId>spring-boot-starter-data-neo4j</artifactId>
200+
<version>${springboot.version}</version>
201+
<scope>test</scope>
202+
</dependency>
191203

192204
<dependency>
193205
<groupId>org.testcontainers</groupId>

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/AdditionalInfo.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public StatementDescription(String line, String method) {
106106

107107
private final Set<MongoFindCommand> mongoFindCommandData = new CopyOnWriteArraySet<>();
108108

109+
private final Set<Neo4JRunCommand> neo4JRunCommandData = new CopyOnWriteArraySet<>();
110+
109111
private final Set<OpenSearchCommand> openSearchCommandData = new CopyOnWriteArraySet<>();
110112

111113
private final Set<RedisCommand> redisCommandData = new CopyOnWriteArraySet<>();
@@ -120,6 +122,10 @@ public Set<MongoFindCommand> getMongoInfoData(){
120122
return Collections.unmodifiableSet(mongoFindCommandData);
121123
}
122124

125+
public Set<Neo4JRunCommand> getNeo4JInfoData(){
126+
return Collections.unmodifiableSet(neo4JRunCommandData);
127+
}
128+
123129
public Set<OpenSearchCommand> getOpenSearchInfoData() {
124130
return Collections.unmodifiableSet(openSearchCommandData);
125131
}
@@ -140,6 +146,10 @@ public void addMongoInfo(MongoFindCommand info){
140146
mongoFindCommandData.add(info);
141147
}
142148

149+
public void addNeo4JInfo(Neo4JRunCommand info){
150+
neo4JRunCommandData.add(info);
151+
}
152+
143153
public void addOpenSearchInfo(OpenSearchCommand info){
144154
openSearchCommandData.add(info);
145155
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.evomaster.client.java.instrumentation;
2+
3+
import java.io.Serializable;
4+
5+
/**
6+
* Info related to Neo4J RUN command execution.
7+
*/
8+
public class Neo4JRunCommand implements Serializable {
9+
/**
10+
* Executed RUN query (Cypher query string)
11+
*/
12+
private final String query;
13+
14+
/**
15+
* Query parameters (can be Map, Value, Record, or null)
16+
*/
17+
private final Object parameters;
18+
19+
/**
20+
* If the operation was successfully executed
21+
*/
22+
private final boolean successfullyExecuted;
23+
24+
/**
25+
* Elapsed execution time
26+
*/
27+
private final long executionTime;
28+
29+
public Neo4JRunCommand(String query, Object parameters, boolean successfullyExecuted, long executionTime) {
30+
this.query = query;
31+
this.parameters = parameters;
32+
this.successfullyExecuted = successfullyExecuted;
33+
this.executionTime = executionTime;
34+
}
35+
36+
public String getQuery() {
37+
return query;
38+
}
39+
40+
public Object getParameters() {
41+
return parameters;
42+
}
43+
44+
public long getExecutionTime() {
45+
return executionTime;
46+
}
47+
48+
public boolean getSuccessfullyExecuted() {
49+
return successfullyExecuted;
50+
}
51+
52+
}

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static List<MethodReplacementClass> getList() {
5757
new MongoCollectionClassReplacement(),
5858
new MongoTemplateClassReplacement(),
5959
new OpenSearchClientClassReplacement(),
60+
new Neo4JSessionClassReplacement(),
6061
new MappingMongoEntityInformationClassReplacement(),
6162
new OkHttpClient3BuilderClassReplacement(),
6263
new OkHttpClient3ClassReplacement(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;
2+
3+
import org.evomaster.client.java.instrumentation.Neo4JRunCommand;
4+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass;
5+
import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer;
6+
7+
/**
8+
* Base class for Neo4j driver method replacements.
9+
* Provides common functionality for tracking Neo4j database operations.
10+
*/
11+
public abstract class Neo4JOperationClassReplacement extends ThirdPartyMethodReplacementClass {
12+
13+
/**
14+
* Tracks a Neo4j database operation by recording the query, parameters, execution time, and success status.
15+
*
16+
* @param query the Cypher query string
17+
* @param parameters the query parameters
18+
* @param successfullyExecuted whether the query executed successfully
19+
* @param executionTime the execution time in milliseconds
20+
*/
21+
protected static void handleNeo4J(String query, Object parameters, boolean successfullyExecuted, long executionTime) {
22+
Neo4JRunCommand info = new Neo4JRunCommand(query, parameters, successfullyExecuted, executionTime);
23+
ExecutionTracer.addNeo4JInfo(info);
24+
}
25+
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;
2+
3+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement;
4+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast;
5+
import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter;
6+
import org.evomaster.client.java.instrumentation.shared.ReplacementCategory;
7+
import org.evomaster.client.java.instrumentation.shared.ReplacementType;
8+
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
/**
17+
* Replacement class for instrumenting {@code org.neo4j.driver.Session.run()} methods.
18+
* Tracks Cypher queries, parameters, and execution time.
19+
*/
20+
public class Neo4JSessionClassReplacement extends Neo4JOperationClassReplacement {
21+
private static final Neo4JSessionClassReplacement singleton = new Neo4JSessionClassReplacement();
22+
23+
private static final String ID_RUN_STRING = "runString";
24+
private static final String ID_RUN_STRING_MAP = "runStringMap";
25+
private static final String ID_RUN_STRING_VALUE = "runStringValue";
26+
private static final String ID_RUN_STRING_RECORD = "runStringRecord";
27+
private static final String ID_RUN_QUERY = "runQuery";
28+
29+
@Override
30+
protected String getNameOfThirdPartyTargetClass() {
31+
return "org.neo4j.driver.Session";
32+
}
33+
34+
/**
35+
* Replacement for {@code Session.run(String query)}.
36+
*/
37+
@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = ID_RUN_STRING, usageFilter = UsageFilter.ANY, category = ReplacementCategory.NEO4J, castTo = "org.neo4j.driver.Result")
38+
public static Object run(Object session, String query) {
39+
return handleRun(ID_RUN_STRING, session, query, null, Collections.singletonList(query));
40+
}
41+
42+
/**
43+
* Replacement for {@code Session.run(String query, Map<String, Object> parameters)}.
44+
*/
45+
@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = ID_RUN_STRING_MAP, usageFilter = UsageFilter.ANY, category = ReplacementCategory.NEO4J, castTo = "org.neo4j.driver.Result")
46+
public static Object run(Object session, String query, Map<String, Object> parameters) {
47+
return handleRun(ID_RUN_STRING_MAP, session, query, parameters, Arrays.asList(query, parameters));
48+
}
49+
50+
/**
51+
* Replacement for {@code Session.run(String query, Value parameters)}.
52+
* Uses _EM_0 suffix to avoid signature conflicts.
53+
*/
54+
@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = ID_RUN_STRING_VALUE, usageFilter = UsageFilter.ANY, category = ReplacementCategory.NEO4J, castTo = "org.neo4j.driver.Result")
55+
public static Object run_EM_0(Object session, String query, @ThirdPartyCast(actualType = "org.neo4j.driver.Value") Object parameters) {
56+
return handleRun(ID_RUN_STRING_VALUE, session, query, parameters, Arrays.asList(query, parameters));
57+
}
58+
59+
/**
60+
* Replacement for {@code Session.run(String query, Record parameters)}.
61+
* Uses _EM_1 suffix to avoid signature conflicts.
62+
*/
63+
@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = ID_RUN_STRING_RECORD, usageFilter = UsageFilter.ANY, category = ReplacementCategory.NEO4J, castTo = "org.neo4j.driver.Result")
64+
public static Object run_EM_1(Object session, String query, @ThirdPartyCast(actualType = "org.neo4j.driver.Record") Object parameters) {
65+
return handleRun(ID_RUN_STRING_RECORD, session, query, parameters, Arrays.asList(query, parameters));
66+
}
67+
68+
/**
69+
* Replacement for {@code Session.run(Query query)}.
70+
*/
71+
@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = ID_RUN_QUERY, usageFilter = UsageFilter.ANY, category = ReplacementCategory.NEO4J, castTo = "org.neo4j.driver.Result")
72+
public static Object run(Object session, @ThirdPartyCast(actualType = "org.neo4j.driver.Query") Object query) {
73+
String queryText = extractQueryText(query);
74+
Object parameters = extractQueryParameters(query);
75+
return handleRun(ID_RUN_QUERY, session, queryText, parameters, Collections.singletonList(query));
76+
}
77+
78+
/**
79+
* Extracts the Cypher query text from a Query object using reflection.
80+
*
81+
* @param queryObject the Query object
82+
* @return the query text
83+
* @throws RuntimeException if extraction fails
84+
*/
85+
private static String extractQueryText(Object queryObject) {
86+
try {
87+
Method textMethod = queryObject.getClass().getMethod("text");
88+
return (String) textMethod.invoke(queryObject);
89+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
90+
throw new RuntimeException("Failed to extract query text from Neo4j Query object", e);
91+
}
92+
}
93+
94+
/**
95+
* Extracts the parameters from a Query object using reflection.
96+
*
97+
* @param queryObject the Query object
98+
* @return the parameters
99+
* @throws RuntimeException if extraction fails
100+
*/
101+
private static Object extractQueryParameters(Object queryObject) {
102+
try {
103+
Method parametersMethod = queryObject.getClass().getMethod("parameters");
104+
return parametersMethod.invoke(queryObject);
105+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
106+
throw new RuntimeException("Failed to extract parameters from Neo4j Query object", e);
107+
}
108+
}
109+
110+
/**
111+
* Handles the execution of a run method and tracks the query execution.
112+
*
113+
* @param id the method identifier
114+
* @param session the Session object
115+
* @param query the Cypher query string
116+
* @param parameters the query parameters
117+
* @param args the method arguments
118+
* @return the Result object
119+
*/
120+
private static Object handleRun(String id, Object session, String query, Object parameters, List<Object> args) {
121+
long start = System.currentTimeMillis();
122+
try {
123+
Method runMethod = retrieveRunMethod(id, session);
124+
Object result = runMethod.invoke(session, args.toArray());
125+
long end = System.currentTimeMillis();
126+
handleNeo4J(query, parameters, true, end - start);
127+
return result;
128+
} catch (IllegalAccessException e) {
129+
throw new RuntimeException(e);
130+
} catch (InvocationTargetException e) {
131+
Throwable cause = e.getCause();
132+
if (cause instanceof RuntimeException) {
133+
throw (RuntimeException) cause;
134+
}
135+
throw new RuntimeException(cause);
136+
}
137+
}
138+
139+
/**
140+
* Retrieves the original run method from the Session object.
141+
*
142+
* @param id the method identifier
143+
* @param session the Session object
144+
* @return the original Method
145+
*/
146+
private static Method retrieveRunMethod(String id, Object session) {
147+
return getOriginal(singleton, id, session);
148+
}
149+
}

client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/staticstate/ExecutionTracer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class ExecutionTracer {
3737

3838
private static boolean executingInitRedis = false;
3939

40+
private static boolean executingInitNeo4J = false;
41+
4042
/**
4143
* indicate whether now it is to execute action during the search
4244
*/
@@ -201,6 +203,10 @@ public static void setExecutingInitRedis(boolean executingInitRedis) {
201203
ExecutionTracer.executingInitRedis = executingInitRedis;
202204
}
203205

206+
public static void setExecutingInitNeo4J(boolean executingInitNeo4J) {
207+
ExecutionTracer.executingInitNeo4J = executingInitNeo4J;
208+
}
209+
204210
public static boolean isExecutingAction() {
205211
return executingAction;
206212
}
@@ -429,6 +435,11 @@ public static void addMongoInfo(MongoFindCommand info){
429435
getCurrentAdditionalInfo().addMongoInfo(info);
430436
}
431437

438+
public static void addNeo4JInfo(Neo4JRunCommand info){
439+
if (!executingInitNeo4J)
440+
getCurrentAdditionalInfo().addNeo4JInfo(info);
441+
}
442+
432443
public static void addOpenSearchInfo(OpenSearchCommand info) {
433444
getCurrentAdditionalInfo().addOpenSearchInfo(info);
434445
}
@@ -444,6 +455,7 @@ public static void addMongoCollectionType(MongoCollectionSchema mongoCollectionS
444455
}
445456
}
446457

458+
447459
public static void markLastExecutedStatement(String lastLine, String lastMethod) {
448460
getCurrentAdditionalInfo().pushLastExecutedStatement(lastLine, lastMethod);
449461
}

0 commit comments

Comments
 (0)