2121import java .lang .invoke .MethodType ;
2222import java .lang .reflect .InvocationTargetException ;
2323import java .security .SecureRandom ;
24+ import java .util .concurrent .ConcurrentHashMap ;
2425
2526/** Static helper methods that hooks can use to provide feedback to the fuzzer. */
2627public final class Jazzer {
@@ -33,13 +34,28 @@ public final class Jazzer {
3334 private static final MethodHandle TRACE_MEMCMP ;
3435 private static final MethodHandle TRACE_PC_INDIR ;
3536
37+ private static final MethodHandle COUNTERS_TRACKER_ALLOCATE ;
38+ private static final MethodHandle COUNTERS_TRACKER_SET_RANGE ;
39+
40+ /**
41+ * Default number of counters allocated for each call site of a method that requires registering a
42+ * range of artificial coverage counters, e.g., Jazzer maximize API. The user's value range is
43+ * linearly mapped onto this many counters.
44+ */
45+ public static final int DEFAULT_NUM_COUNTERS = 1024 ;
46+
47+ /** Tracks the registered minValue and maxValue per maximize call-site id. */
48+ private static final ConcurrentHashMap <Integer , long []> idToRange = new ConcurrentHashMap <>();
49+
3650 static {
3751 Class <?> jazzerInternal = null ;
3852 MethodHandle onFuzzTargetReady = null ;
3953 MethodHandle traceStrcmp = null ;
4054 MethodHandle traceStrstr = null ;
4155 MethodHandle traceMemcmp = null ;
4256 MethodHandle tracePcIndir = null ;
57+ MethodHandle countersTrackerAllocate = null ;
58+ MethodHandle countersTrackerSetRange = null ;
4359 try {
4460 jazzerInternal = Class .forName ("com.code_intelligence.jazzer.runtime.JazzerInternal" );
4561 MethodType onFuzzTargetReadyType = MethodType .methodType (void .class , Runnable .class );
@@ -70,6 +86,16 @@ public final class Jazzer {
7086 tracePcIndir =
7187 MethodHandles .publicLookup ()
7288 .findStatic (traceDataFlowNativeCallbacks , "tracePcIndir" , tracePcIndirType );
89+
90+ Class <?> countersTracker =
91+ Class .forName ("com.code_intelligence.jazzer.runtime.ExtraCountersTracker" );
92+ MethodType allocateType = MethodType .methodType (void .class , int .class , int .class );
93+ countersTrackerAllocate =
94+ MethodHandles .publicLookup ()
95+ .findStatic (countersTracker , "ensureCountersAllocated" , allocateType );
96+ MethodType setRangeType = MethodType .methodType (void .class , int .class , int .class );
97+ countersTrackerSetRange =
98+ MethodHandles .publicLookup ().findStatic (countersTracker , "setCounterRange" , setRangeType );
7399 } catch (ClassNotFoundException ignore ) {
74100 // Not running in the context of the agent. This is fine as long as no methods are called on
75101 // this class.
@@ -86,14 +112,16 @@ public final class Jazzer {
86112 TRACE_STRSTR = traceStrstr ;
87113 TRACE_MEMCMP = traceMemcmp ;
88114 TRACE_PC_INDIR = tracePcIndir ;
115+ COUNTERS_TRACKER_ALLOCATE = countersTrackerAllocate ;
116+ COUNTERS_TRACKER_SET_RANGE = countersTrackerSetRange ;
89117 }
90118
91119 private Jazzer () {}
92120
93121 /**
94122 * A 32-bit random number that hooks can use to make pseudo-random choices between multiple
95123 * possible mutations they could guide the fuzzer towards. Hooks <b>must not</b> base the decision
96- * whether or not to report a finding on this number as this will make findings non-reproducible.
124+ * whether to report a finding on this number as this will make findings non-reproducible.
97125 *
98126 * <p>This is the same number that libFuzzer uses as a seed internally, which makes it possible to
99127 * deterministically reproduce a previous fuzzing run by supplying the seed value printed by
@@ -119,8 +147,10 @@ public static void guideTowardsEquality(String current, String target, int id) {
119147 }
120148 try {
121149 TRACE_STRCMP .invokeExact (current , target , 1 , id );
150+ } catch (JazzerApiException e ) {
151+ throw e ;
122152 } catch (Throwable e ) {
123- e . printStackTrace ( );
153+ throw new JazzerApiException ( "guideTowardsEquality: " + e . getMessage (), e );
124154 }
125155 }
126156
@@ -142,8 +172,10 @@ public static void guideTowardsEquality(byte[] current, byte[] target, int id) {
142172 }
143173 try {
144174 TRACE_MEMCMP .invokeExact (current , target , 1 , id );
175+ } catch (JazzerApiException e ) {
176+ throw e ;
145177 } catch (Throwable e ) {
146- e . printStackTrace ( );
178+ throw new JazzerApiException ( "guideTowardsEquality: " + e . getMessage (), e );
147179 }
148180 }
149181
@@ -166,8 +198,10 @@ public static void guideTowardsContainment(String haystack, String needle, int i
166198 }
167199 try {
168200 TRACE_STRSTR .invokeExact (haystack , needle , id );
201+ } catch (JazzerApiException e ) {
202+ throw e ;
169203 } catch (Throwable e ) {
170- e . printStackTrace ( );
204+ throw new JazzerApiException ( "guideTowardsContainment: " + e . getMessage (), e );
171205 }
172206 }
173207
@@ -212,8 +246,10 @@ public static void exploreState(byte state, int id) {
212246 int upperBits = id >>> 5 ;
213247 try {
214248 TRACE_PC_INDIR .invokeExact (upperBits , lowerBits );
249+ } catch (JazzerApiException e ) {
250+ throw e ;
215251 } catch (Throwable e ) {
216- e . printStackTrace ( );
252+ throw new JazzerApiException ( "exploreState: " + e . getMessage (), e );
217253 }
218254 }
219255
@@ -230,6 +266,117 @@ public static void exploreState(byte state) {
230266 // an automatically generated call-site id. Without instrumentation, this is a no-op.
231267 }
232268
269+ /**
270+ * Core implementation of the hill-climbing maximize API. It maps {@code value} from the range
271+ * [{@code minValue}, {@code maxValue}] onto {@code numCounters} coverage counters via linear
272+ * interpolation, then sets all counters from 0 to the mapped offset.
273+ *
274+ * <p>Values below {@code minValue} produce no signal. Values above {@code maxValue} are clamped.
275+ *
276+ * @param value the value to maximize
277+ * @param minValue the minimum expected value (inclusive)
278+ * @param maxValue the maximum expected value (inclusive); must be >= {@code minValue}
279+ * @param numCounters the number of counters to allocate; must be > 0
280+ * @param id a unique identifier for this call site (must be consistent across runs)
281+ * @throws JazzerApiException if {@code maxValue < minValue} or {@code numCounters <= 0}
282+ */
283+ public static void maximize (long value , long minValue , long maxValue , int numCounters , int id ) {
284+ if (COUNTERS_TRACKER_ALLOCATE == null ) {
285+ return ;
286+ }
287+
288+ try {
289+ ensureRangeConsistent (id , minValue , maxValue );
290+ int effectiveCounters = effectiveCounters (minValue , maxValue , numCounters );
291+ COUNTERS_TRACKER_ALLOCATE .invokeExact (id , effectiveCounters );
292+
293+ if (value >= minValue ) {
294+ int toOffset ;
295+ if (minValue == maxValue ) {
296+ toOffset = 0 ;
297+ } else {
298+ double range = (double ) maxValue - (double ) minValue ;
299+ double offset = (double ) Math .min (value , maxValue ) - (double ) minValue ;
300+ toOffset = (int ) (offset / range * (effectiveCounters - 1 ));
301+ }
302+ COUNTERS_TRACKER_SET_RANGE .invokeExact (id , toOffset );
303+ }
304+ } catch (JazzerApiException e ) {
305+ throw e ;
306+ } catch (Throwable e ) {
307+ throw new JazzerApiException ("maximize: " + e .getMessage (), e );
308+ }
309+ }
310+
311+ private static void ensureRangeConsistent (int id , long minValue , long maxValue ) {
312+ long [] existing = idToRange .putIfAbsent (id , new long [] {minValue , maxValue });
313+ if (existing != null && (existing [0 ] != minValue || existing [1 ] != maxValue )) {
314+ throw new IllegalArgumentException (
315+ String .format (
316+ "ensureRangeConsistent called with different range for id %d: "
317+ + "existing=[%d, %d], requested=[%d, %d]" ,
318+ id , existing [0 ], existing [1 ], minValue , maxValue ));
319+ }
320+ }
321+
322+ private static int effectiveCounters (long minValue , long maxValue , int maxNumCounters ) {
323+ if (maxValue < minValue ) {
324+ throw new IllegalArgumentException (
325+ "maxValue (" + maxValue + ") must not be less than minValue (" + minValue + ")" );
326+ }
327+ if (maxNumCounters <= 0 ) {
328+ throw new IllegalArgumentException (
329+ "maxNumCounters (" + maxNumCounters + ") must be positive" );
330+ }
331+
332+ // Cap maxNumCounters at the actual range size to avoid wasting counters when the
333+ // range is smaller than the requested number (e.g. range [0, 10] only needs 11).
334+ double rangeSize = (double ) maxValue - (double ) minValue + 1 ;
335+ return (rangeSize < maxNumCounters ) ? (int ) rangeSize : maxNumCounters ;
336+ }
337+
338+ /**
339+ * Convenience overload of {@link #maximize(long, long, long, int, int)} that uses {@link
340+ * #DEFAULT_NUM_COUNTERS} counters and an automatically generated call-site id.
341+ *
342+ * <p>During instrumentation, calls to this method are replaced by a hook that supplies a unique
343+ * id for each call site. Without instrumentation, this is a no-op.
344+ *
345+ * <pre>{@code
346+ * // Maximize temperature in [0, 4500]
347+ * Jazzer.maximize(temperature, 0, 4500);
348+ * }</pre>
349+ *
350+ * @param value the value to maximize
351+ * @param minValue the minimum expected value (inclusive)
352+ * @param maxValue the maximum expected value (inclusive)
353+ * @see #maximize(long, long, long, int, int)
354+ */
355+ public static void maximize (long value , long minValue , long maxValue ) {
356+ // Instrumentation replaces calls to this method with the core overload using
357+ // DEFAULT_NUM_COUNTERS and an automatically generated call-site id.
358+ // Without instrumentation, this is a no-op.
359+ }
360+
361+ /**
362+ * Convenience overload of {@link #maximize(long, long, long, int, int)} that uses a custom number
363+ * of counters and an automatically generated call-site id.
364+ *
365+ * <p>During instrumentation, calls to this method are replaced by a hook that supplies a unique
366+ * id for each call site. Without instrumentation, this is a no-op.
367+ *
368+ * @param value the value to maximize
369+ * @param minValue the minimum expected value (inclusive)
370+ * @param maxValue the maximum expected value (inclusive)
371+ * @param numCounters the number of counters to allocate; must be > 0
372+ * @see #maximize(long, long, long, int, int)
373+ */
374+ public static void maximize (long value , long minValue , long maxValue , int numCounters ) {
375+ // Instrumentation replaces calls to this method with the core overload using
376+ // the given numCounters and an automatically generated call-site id.
377+ // Without instrumentation, this is a no-op.
378+ }
379+
233380 /**
234381 * Make Jazzer report the provided {@link Throwable} as a finding.
235382 *
@@ -261,8 +408,10 @@ public static void reportFindingFromHook(Throwable finding) {
261408 public static void onFuzzTargetReady (Runnable callback ) {
262409 try {
263410 ON_FUZZ_TARGET_READY .invokeExact (callback );
411+ } catch (JazzerApiException e ) {
412+ throw e ;
264413 } catch (Throwable e ) {
265- e . printStackTrace ( );
414+ throw new JazzerApiException ( "onFuzzTargetReady: " + e . getMessage (), e );
266415 }
267416 }
268417
0 commit comments