Skip to content

Commit 8eb43dc

Browse files
committed
chore: add test to validate hook method descriptors
1 parent fe4532e commit 8eb43dc

5 files changed

Lines changed: 199 additions & 3 deletions

File tree

MODULE.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ TEST_MAVEN_ARTIFACTS = [
107107
"com.google.truth.extensions:truth-liteproto-extension:1.4.2",
108108
"com.google.truth.extensions:truth-proto-extension:1.4.2",
109109
"com.google.truth:truth:1.4.2",
110+
"jakarta.el:jakarta.el-api:6.0.1",
111+
"javax.persistence:javax.persistence-api:2.2",
110112
"junit:junit:4.13.2",
111113
"org.assertj:assertj-core:3.25.3",
112114
"org.jacoco:org.jacoco.core:0.8.12",

maven_install.json

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
3-
"__INPUT_ARTIFACTS_HASH": 285652305,
4-
"__RESOLVED_ARTIFACTS_HASH": -2054117877,
3+
"__INPUT_ARTIFACTS_HASH": -1935863112,
4+
"__RESOLVED_ARTIFACTS_HASH": 1203654031,
55
"conflict_resolution": {
66
"com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.9",
77
"com.google.errorprone:error_prone_annotations:2.3.2": "com.google.errorprone:error_prone_annotations:2.26.1",
@@ -197,6 +197,12 @@
197197
},
198198
"version": "1.12.3"
199199
},
200+
"jakarta.el:jakarta.el-api": {
201+
"shasums": {
202+
"jar": "7e84b5bed49de32b79cc5e85d90b6f5adb1a953ac67283adbb41c1e297f9c605"
203+
},
204+
"version": "6.0.1"
205+
},
200206
"jakarta.servlet:jakarta.servlet-api": {
201207
"shasums": {
202208
"jar": "c034eb1afb158987dbb53a5fea0cadf611c8dae8daadd59c44d9d5ab70129cef"
@@ -215,6 +221,12 @@
215221
},
216222
"version": "3.0.1-b06"
217223
},
224+
"javax.persistence:javax.persistence-api": {
225+
"shasums": {
226+
"jar": "5578b71b37999a5eaed3fea0d14aa61c60c6ec6328256f2b63472f336318baf4"
227+
},
228+
"version": "2.2"
229+
},
218230
"javax.validation:validation-api": {
219231
"shasums": {
220232
"jar": "9873b46df1833c9ee8f5bc1ff6853375115dadd8897bcb5a0dffb5848835ee6c"
@@ -1285,6 +1297,9 @@
12851297
"io.micrometer.observation.docs",
12861298
"io.micrometer.observation.transport"
12871299
],
1300+
"jakarta.el:jakarta.el-api": [
1301+
"jakarta.el"
1302+
],
12881303
"jakarta.servlet:jakarta.servlet-api": [
12891304
"jakarta.servlet",
12901305
"jakarta.servlet.annotation",
@@ -1297,6 +1312,12 @@
12971312
"javax.el:javax.el-api": [
12981313
"javax.el"
12991314
],
1315+
"javax.persistence:javax.persistence-api": [
1316+
"javax.persistence",
1317+
"javax.persistence.criteria",
1318+
"javax.persistence.metamodel",
1319+
"javax.persistence.spi"
1320+
],
13001321
"javax.validation:validation-api": [
13011322
"javax.validation",
13021323
"javax.validation.bootstrap",
@@ -2611,9 +2632,11 @@
26112632
"io.github.classgraph:classgraph",
26122633
"io.micrometer:micrometer-commons",
26132634
"io.micrometer:micrometer-observation",
2635+
"jakarta.el:jakarta.el-api",
26142636
"jakarta.servlet:jakarta.servlet-api",
26152637
"javax.activation:javax.activation-api",
26162638
"javax.el:javax.el-api",
2639+
"javax.persistence:javax.persistence-api",
26172640
"javax.validation:validation-api",
26182641
"javax.xml.bind:jaxb-api",
26192642
"junit:junit",

sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ kt_jvm_library(
7474
"Utils.kt",
7575
"XPathInjection.kt",
7676
],
77-
visibility = ["//sanitizers:__pkg__"],
77+
visibility = [
78+
"//sanitizers:__pkg__",
79+
"//sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers:__pkg__",
80+
],
7881
runtime_deps = [
7982
":clojure_lang_hooks",
8083
":file_path_traversal",

sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,20 @@ java_junit5_test(
1010
"@maven//:org_junit_jupiter_junit_jupiter_params",
1111
],
1212
)
13+
14+
java_junit5_test(
15+
name = "HookBindingSanityTest",
16+
srcs = ["HookBindingSanityTest.java"],
17+
deps = JUNIT5_DEPS + [
18+
"//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers",
19+
"//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:constants",
20+
"//src/main/java/com/code_intelligence/jazzer/api:hooks",
21+
"@clojure_jar//jar",
22+
"@maven//:jakarta_el_jakarta_el_api",
23+
"@maven//:javax_el_javax_el_api",
24+
"@maven//:javax_persistence_javax_persistence_api",
25+
"@maven//:javax_validation_validation_api",
26+
"@maven//:org_junit_jupiter_junit_jupiter_api",
27+
"@maven//:org_junit_jupiter_junit_jupiter_params",
28+
],
29+
)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 2025 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.code_intelligence.jazzer.sanitizers;
17+
18+
import static org.junit.jupiter.api.Assertions.*;
19+
20+
import com.code_intelligence.jazzer.api.MethodHook;
21+
import com.code_intelligence.jazzer.api.MethodHooks;
22+
import java.lang.invoke.MethodType;
23+
import java.util.Arrays;
24+
import java.util.Objects;
25+
import java.util.Optional;
26+
import java.util.Set;
27+
import java.util.stream.Stream;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.MethodSource;
30+
31+
/**
32+
* Verifies that for every declared @MethodHook in built-in sanitizers, a corresponding target
33+
* method (or constructor) with the configured descriptor exists. This guards against typos and
34+
* wrong descriptors.
35+
*/
36+
public class HookBindingSanityTest {
37+
38+
final Set<String> SKIPPED_CLASSES =
39+
Set.of(
40+
// Only Java 8
41+
"java.util.regex.Pattern$Single",
42+
"java.util.regex.Pattern$SingleI",
43+
"java.util.regex.Pattern$SingleS",
44+
"java.util.regex.Pattern$SingleU");
45+
46+
final Set<String> SKIPPED_METHODS =
47+
Set.of(
48+
// Only Java 8
49+
"java.util.regex.Pattern.caseInsensitiveRangeFor",
50+
"java.util.regex.Pattern.rangeFor",
51+
"java.util.regex.Pattern.union",
52+
"sun.misc.Unsafe.getByte",
53+
"sun.misc.Unsafe.getChar",
54+
"sun.misc.Unsafe.getDouble",
55+
"sun.misc.Unsafe.getFloat",
56+
"sun.misc.Unsafe.getInt",
57+
"sun.misc.Unsafe.getLong",
58+
"sun.misc.Unsafe.getShort",
59+
"sun.misc.Unsafe.putByte",
60+
"sun.misc.Unsafe.putChar",
61+
"sun.misc.Unsafe.putDouble",
62+
"sun.misc.Unsafe.putFloat",
63+
"sun.misc.Unsafe.putInt",
64+
"sun.misc.Unsafe.putLong",
65+
"sun.misc.Unsafe.putShort");
66+
67+
@ParameterizedTest
68+
@MethodSource("getMethodHooks")
69+
public void allHooksResolveIfClassPresent(MethodHook hook) {
70+
String targetClassName = hook.targetClassName();
71+
assertNotNull(targetClassName, "Hook has no target class");
72+
if (SKIPPED_CLASSES.contains(targetClassName)) {
73+
return;
74+
}
75+
ClassLoader loader = HookBindingSanityTest.class.getClassLoader();
76+
Class<?> targetClass =
77+
assertDoesNotThrow(
78+
() -> Class.forName(targetClassName, false, loader),
79+
() -> "Expected class to exist for hook: " + targetClassName);
80+
String methodName = hook.targetMethod();
81+
String methodDesc = hook.targetMethodDescriptor();
82+
83+
if (SKIPPED_METHODS.contains(String.format("%s.%s", targetClassName, methodName))) {
84+
return;
85+
}
86+
87+
if ("<init>".equals(methodName)) {
88+
if (methodDesc.isEmpty()) {
89+
// Any constructor is acceptable.
90+
assertNotEquals(
91+
0,
92+
targetClass.getDeclaredConstructors().length,
93+
String.format("no constructor for class %s found", targetClassName));
94+
} else {
95+
// Match specific constructor by descriptor
96+
MethodType mt = MethodType.fromMethodDescriptorString(methodDesc, loader);
97+
Class<?>[] descriptorParams = mt.parameterArray();
98+
assertTrue(
99+
Arrays.stream(targetClass.getDeclaredConstructors())
100+
.anyMatch(c -> Arrays.equals(c.getParameterTypes(), descriptorParams)),
101+
String.format("no matching constructor for class %s found", targetClassName));
102+
}
103+
} else {
104+
if (methodDesc.isEmpty()) {
105+
// Require at least one declared method with that name
106+
assertTrue(
107+
Arrays.stream(targetClass.getDeclaredMethods())
108+
.anyMatch(md -> md.getName().equals(methodName)),
109+
String.format("method name %s not found in class %s", methodName, targetClassName));
110+
} else {
111+
MethodType mt = MethodType.fromMethodDescriptorString(methodDesc, loader);
112+
Class<?> descriptorReturnType = mt.returnType();
113+
Class<?>[] descriptorParams = mt.parameterArray();
114+
assertTrue(
115+
Arrays.stream(targetClass.getDeclaredMethods())
116+
.anyMatch(
117+
md ->
118+
md.getName().equals(methodName)
119+
&& md.getReturnType().equals(descriptorReturnType)
120+
&& Arrays.equals(md.getParameterTypes(), descriptorParams)),
121+
String.format(
122+
"method %s with descriptor %s not found in class %s",
123+
methodName, methodDesc, targetClassName));
124+
}
125+
}
126+
}
127+
128+
static Class<?> getHookClass(String className) {
129+
try {
130+
return Class.forName(className, false, HookBindingSanityTest.class.getClassLoader());
131+
} catch (ClassNotFoundException e) {
132+
throw new RuntimeException("Could not find hook class " + className, e);
133+
}
134+
}
135+
136+
static MethodHook[] getMethodHooks() throws ClassNotFoundException, NoSuchMethodException {
137+
return Constants.SANITIZER_HOOK_NAMES.stream()
138+
.map(HookBindingSanityTest::getHookClass)
139+
.flatMap(clazz -> Stream.of(clazz.getMethods()))
140+
.flatMap(
141+
m ->
142+
Stream.concat(
143+
Stream.of(m.getAnnotation(MethodHook.class)),
144+
Optional.ofNullable(m.getAnnotation(MethodHooks.class))
145+
.map(MethodHooks::value)
146+
.map(Stream::of)
147+
.orElseGet(Stream::empty)))
148+
.filter(Objects::nonNull)
149+
.toArray(MethodHook[]::new);
150+
}
151+
}

0 commit comments

Comments
 (0)