1 // Copyright 2022 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.code_intelligence.jazzer.instrumentor; 16 17 import java.io.ByteArrayOutputStream; 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.lang.invoke.MethodHandle; 21 import java.lang.invoke.MethodHandles; 22 import java.lang.invoke.MethodType; 23 import org.openjdk.jmh.annotations.Benchmark; 24 import org.openjdk.jmh.annotations.Scope; 25 import org.openjdk.jmh.annotations.Setup; 26 import org.openjdk.jmh.annotations.State; 27 28 /** 29 * This benchmark compares the throughput of a typical fuzz target when instrumented with different 30 * edge coverage instrumentation strategies and coverage map implementations. 31 * 32 * The benchmark currently uses the OWASP json-sanitizer as its target, which has the following 33 * desirable properties for a benchmark: 34 * - It is a reasonably sized project that does not consist of many different classes. 35 * - It is very heavy on computation with a high density of branching. 36 * - It is entirely CPU bound with no IO and does not call expensive methods from the standard 37 * library. 38 * With these properties, results obtained from this benchmark should provide reasonable lower 39 * bounds on the relative slowdown introduced by the various approaches to instrumentations. 40 */ 41 @State(Scope.Benchmark) 42 public class CoverageInstrumentationBenchmark { 43 private static final String TARGET_CLASSNAME = "com.google.json.JsonSanitizer"; 44 private static final String TARGET_PACKAGE = 45 TARGET_CLASSNAME.substring(0, TARGET_CLASSNAME.lastIndexOf('.')); 46 private static final String TARGET_METHOD = "sanitize"; 47 private static final MethodType TARGET_TYPE = MethodType.methodType(String.class, String.class); 48 49 // This is part of the benchmark's state and not a constant to prevent constant folding. 50 String TARGET_ARG = 51 "{\"foo\":1123987,\"bar\":[true, false],\"baz\":{\"foo\":\"132ä3\",\"bar\":1.123e-005}}"; 52 53 MethodHandle uninstrumented_sanitize; 54 MethodHandle local_DirectByteBuffer_NeverZero_sanitize; 55 MethodHandle staticMethod_DirectByteBuffer_NeverZero_sanitize; 56 MethodHandle staticMethod_DirectByteBuffer2_NeverZero_sanitize; 57 MethodHandle staticMethod_Unsafe_NeverZero_sanitize; 58 MethodHandle staticMethod_Unsafe_NeverZero2_sanitize; 59 MethodHandle staticMethod_Unsafe_NeverZeroBranchfree_sanitize; 60 MethodHandle staticMethod_Unsafe_SimpleIncrement_sanitize; 61 handleForTargetMethod(ClassLoader classLoader)62 public static MethodHandle handleForTargetMethod(ClassLoader classLoader) 63 throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { 64 Class<?> targetClass = classLoader.loadClass(TARGET_CLASSNAME); 65 return MethodHandles.lookup().findStatic(targetClass, TARGET_METHOD, TARGET_TYPE); 66 } 67 instrumentWithStrategy( EdgeCoverageStrategy strategy, Class<?> coverageMapClass)68 public static MethodHandle instrumentWithStrategy( 69 EdgeCoverageStrategy strategy, Class<?> coverageMapClass) 70 throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { 71 if (strategy == null) { 72 // Do not instrument the code by using the benchmark class' ClassLoader. 73 return handleForTargetMethod(CoverageInstrumentationBenchmark.class.getClassLoader()); 74 } 75 // It's fine to reuse a single instrumentor here as we don't want to know which class received 76 // how many counters. 77 Instrumentor instrumentor = new EdgeCoverageInstrumentor(strategy, coverageMapClass, 0); 78 return handleForTargetMethod(new InstrumentingClassLoader(instrumentor, TARGET_PACKAGE)); 79 } 80 81 @Setup instrumentWithStrategies()82 public void instrumentWithStrategies() 83 throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException { 84 uninstrumented_sanitize = instrumentWithStrategy(null, null); 85 local_DirectByteBuffer_NeverZero_sanitize = instrumentWithStrategy( 86 DirectByteBufferStrategy.INSTANCE, DirectByteBufferCoverageMap.class); 87 staticMethod_DirectByteBuffer_NeverZero_sanitize = 88 instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBufferCoverageMap.class); 89 staticMethod_DirectByteBuffer2_NeverZero_sanitize = 90 instrumentWithStrategy(new StaticMethodStrategy(), DirectByteBuffer2CoverageMap.class); 91 staticMethod_Unsafe_NeverZero_sanitize = 92 instrumentWithStrategy(new StaticMethodStrategy(), UnsafeCoverageMap.class); 93 staticMethod_Unsafe_NeverZero2_sanitize = 94 instrumentWithStrategy(new StaticMethodStrategy(), Unsafe2CoverageMap.class); 95 staticMethod_Unsafe_SimpleIncrement_sanitize = 96 instrumentWithStrategy(new StaticMethodStrategy(), UnsafeSimpleIncrementCoverageMap.class); 97 staticMethod_Unsafe_NeverZeroBranchfree_sanitize = 98 instrumentWithStrategy(new StaticMethodStrategy(), UnsafeBranchfreeCoverageMap.class); 99 } 100 101 @Benchmark uninstrumented()102 public String uninstrumented() throws Throwable { 103 return (String) uninstrumented_sanitize.invokeExact(TARGET_ARG); 104 } 105 106 @Benchmark local_DirectByteBuffer_NeverZero()107 public String local_DirectByteBuffer_NeverZero() throws Throwable { 108 return (String) local_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); 109 } 110 111 @Benchmark staticMethod_DirectByteBuffer_NeverZero()112 public String staticMethod_DirectByteBuffer_NeverZero() throws Throwable { 113 return (String) staticMethod_DirectByteBuffer_NeverZero_sanitize.invokeExact(TARGET_ARG); 114 } 115 116 @Benchmark staticMethod_DirectByteBuffer2_NeverZero()117 public String staticMethod_DirectByteBuffer2_NeverZero() throws Throwable { 118 return (String) staticMethod_DirectByteBuffer2_NeverZero_sanitize.invokeExact(TARGET_ARG); 119 } 120 121 @Benchmark staticMethod_Unsafe_NeverZero()122 public String staticMethod_Unsafe_NeverZero() throws Throwable { 123 return (String) staticMethod_Unsafe_NeverZero_sanitize.invokeExact(TARGET_ARG); 124 } 125 126 @Benchmark staticMethod_Unsafe_NeverZero2()127 public String staticMethod_Unsafe_NeverZero2() throws Throwable { 128 return (String) staticMethod_Unsafe_NeverZero2_sanitize.invokeExact(TARGET_ARG); 129 } 130 131 @Benchmark staticMethod_Unsafe_SimpleIncrement()132 public String staticMethod_Unsafe_SimpleIncrement() throws Throwable { 133 return (String) staticMethod_Unsafe_SimpleIncrement_sanitize.invokeExact(TARGET_ARG); 134 } 135 136 @Benchmark staticMethod_Unsafe_NeverZeroBranchfree()137 public String staticMethod_Unsafe_NeverZeroBranchfree() throws Throwable { 138 return (String) staticMethod_Unsafe_NeverZeroBranchfree_sanitize.invokeExact(TARGET_ARG); 139 } 140 } 141 142 class InstrumentingClassLoader extends ClassLoader { 143 private final Instrumentor instrumentor; 144 private final String classNamePrefix; 145 InstrumentingClassLoader(Instrumentor instrumentor, String packageToInstrument)146 InstrumentingClassLoader(Instrumentor instrumentor, String packageToInstrument) { 147 super(InstrumentingClassLoader.class.getClassLoader()); 148 this.instrumentor = instrumentor; 149 this.classNamePrefix = packageToInstrument + "."; 150 } 151 152 @Override loadClass(String name)153 public Class<?> loadClass(String name) throws ClassNotFoundException { 154 if (!name.startsWith(classNamePrefix)) { 155 return super.loadClass(name); 156 } 157 try (InputStream stream = super.getResourceAsStream(name.replace('.', '/') + ".class")) { 158 if (stream == null) { 159 throw new ClassNotFoundException(String.format("Failed to find class file for %s", name)); 160 } 161 byte[] bytecode = readAllBytes(stream); 162 byte[] instrumentedBytecode = instrumentor.instrument(name.replace('.', '/'), bytecode); 163 return defineClass(name, instrumentedBytecode, 0, instrumentedBytecode.length); 164 } catch (IOException e) { 165 throw new ClassNotFoundException(String.format("Failed to read class file for %s", name), e); 166 } 167 } 168 readAllBytes(InputStream in)169 private static byte[] readAllBytes(InputStream in) throws IOException { 170 ByteArrayOutputStream out = new ByteArrayOutputStream(); 171 byte[] buffer = new byte[64 * 104 * 1024]; 172 int read; 173 while ((read = in.read(buffer)) != -1) { 174 out.write(buffer, 0, read); 175 } 176 return out.toByteArray(); 177 } 178 } 179