aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Meumertzheim <meumertzheim@code-intelligence.com>2021-04-20 16:18:23 +0200
committerFabian Meumertzheim <fabian@meumertzhe.im>2021-04-26 15:51:38 +0200
commit17d3abf666c87b66036abcee7554d6418cabcfe2 (patch)
treef845afeb860e41c42e9a75931d310000ce22f199
parentf1cbb0056b3c9076e2bd285bb5e707a26a514d9c (diff)
downloadjazzer-api-17d3abf666c87b66036abcee7554d6418cabcfe2.tar.gz
Add option to generate coverage report
The new --coverage_report option triggers a coverage report to be written on fuzzer exit. The report is generated with the JaCoCo analyzer. The information about observed coverage IDs is obtained from libFuzzer and combined with the coverage obtained during fuzzerInitialize as well as the current run.
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt2
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt177
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt14
-rw-r--r--driver/coverage_tracker.cpp54
-rw-r--r--driver/coverage_tracker.h5
-rw-r--r--driver/fuzz_target_runner.cpp19
-rw-r--r--driver/libfuzzer_driver.cpp8
-rw-r--r--driver/libfuzzer_driver.h4
-rw-r--r--repositories.bzl3
-rw-r--r--third_party/BUILD.bazel1
-rw-r--r--third_party/jacoco-make-probe-adapter-subclassable.patch84
-rw-r--r--third_party/libFuzzer-get-covered-pcs.patch21
12 files changed, 386 insertions, 6 deletions
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
index ad31db7c..91b796a3 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
@@ -15,6 +15,7 @@
package com.code_intelligence.jazzer.agent
import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor
+import com.code_intelligence.jazzer.instrumentor.CoverageRecorder
import com.code_intelligence.jazzer.instrumentor.Hook
import com.code_intelligence.jazzer.instrumentor.InstrumentationType
import com.code_intelligence.jazzer.instrumentor.loadHooks
@@ -159,6 +160,7 @@ internal class RuntimeInstrumentor(
} finally {
coverageIdSynchronizer.commitIdCount(actualNumEdgeIds)
}
+ CoverageRecorder.recordInstrumentedClass(internalClassName, bytecode, firstId, firstId + actualNumEdgeIds)
} else {
hooks(customHooks)
}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt
new file mode 100644
index 00000000..a6d5e406
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt
@@ -0,0 +1,177 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.instrumentor
+
+import com.code_intelligence.jazzer.runtime.CoverageMap
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.CoverageBuilder
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionData
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataReader
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataWriter
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfo
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfoStore
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.data.CRC64
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.time.Instant
+import java.util.UUID
+
+private data class InstrumentedClassInfo(
+ val classId: Long,
+ val initialEdgeId: Int,
+ val nextEdgeId: Int,
+ val bytecode: ByteArray
+)
+
+object CoverageRecorder {
+ private val instrumentedClassInfo = mutableMapOf<String, InstrumentedClassInfo>()
+ private var startTimestamp: Instant? = null
+ private val additionalCoverage = mutableSetOf<Int>()
+
+ fun recordInstrumentedClass(internalClassName: String, bytecode: ByteArray, firstId: Int, numIds: Int) {
+ if (startTimestamp == null)
+ startTimestamp = Instant.now()
+ instrumentedClassInfo[internalClassName] = InstrumentedClassInfo(
+ CRC64.classId(bytecode), firstId, firstId + numIds, bytecode
+ )
+ }
+
+ /**
+ * Manually records coverage IDs based on the current state of [CoverageMap.mem].
+ * Should be called after static initializers have run.
+ */
+ @JvmStatic
+ fun updateCoveredIdsWithCoverageMap() {
+ val mem = CoverageMap.mem
+ val size = mem.capacity()
+ additionalCoverage.addAll((0 until size).filter { mem[it] > 0 })
+ }
+
+ @JvmStatic
+ fun computeFileCoverage(coveredIds: IntArray): String {
+ val coverage = analyzeCoverage(coveredIds.toSet()) ?: return "No classes were instrumented"
+ return coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Branch coverage:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ val counter = fileCoverage.branchCounter
+ val percentage = 100 * counter.coveredRatio
+ "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)"
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Line coverage:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ val counter = fileCoverage.lineCounter
+ val percentage = 100 * counter.coveredRatio
+ "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)"
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Incompletely covered lines:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter {
+ val instructions = fileCoverage.getLine(it).instructionCounter
+ instructions.coveredCount in 1 until instructions.totalCount
+ }.toString()
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Missed lines:\n",
+ ) { fileCoverage ->
+ "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter {
+ val instructions = fileCoverage.getLine(it).instructionCounter
+ instructions.coveredCount == 0 && instructions.totalCount > 0
+ }.toString()
+ }
+ }
+
+ private fun Double.format(digits: Int) = "%.${digits}f".format(this)
+
+ private fun dumpJacocoCoverage(coveredIds: Set<Int>): ByteArray? {
+ // Update the list of covered IDs with the coverage information for the current run.
+ updateCoveredIdsWithCoverageMap()
+
+ val dumpTimestamp = Instant.now()
+ val outStream = ByteArrayOutputStream()
+ val outWriter = ExecutionDataWriter(outStream)
+ // Return null if no class has been instrumented.
+ val startTimestamp = startTimestamp ?: return null
+ outWriter.visitSessionInfo(
+ SessionInfo(UUID.randomUUID().toString(), startTimestamp.epochSecond, dumpTimestamp.epochSecond)
+ )
+
+ val sortedCoveredIds = (additionalCoverage + coveredIds).sorted().toIntArray()
+ for ((internalClassName, info) in instrumentedClassInfo) {
+ // Determine the subarray of coverage IDs in sortedCoveredIds that contains the IDs generated while
+ // instrumenting the current class. Since the ID array is sorted, use binary search.
+ var coveredIdsStart = sortedCoveredIds.binarySearch(info.initialEdgeId)
+ if (coveredIdsStart < 0) {
+ coveredIdsStart = -(coveredIdsStart + 1)
+ }
+ var coveredIdsEnd = sortedCoveredIds.binarySearch(info.nextEdgeId)
+ if (coveredIdsEnd < 0) {
+ coveredIdsEnd = -(coveredIdsEnd + 1)
+ }
+ if (coveredIdsStart == coveredIdsEnd) {
+ // No coverage data for the class.
+ continue
+ }
+ check(coveredIdsStart in 0 until coveredIdsEnd && coveredIdsEnd <= sortedCoveredIds.size) {
+ "Invalid range [$coveredIdsStart, $coveredIdsEnd) with coveredIds.size=${sortedCoveredIds.size}"
+ }
+ // Generate a probes array for the current class only, i.e., mapping info.initialEdgeId to 0.
+ val probes = BooleanArray(info.nextEdgeId - info.initialEdgeId)
+ (coveredIdsStart until coveredIdsEnd).asSequence()
+ .map {
+ val globalEdgeId = sortedCoveredIds[it]
+ globalEdgeId - info.initialEdgeId
+ }
+ .forEach { classLocalEdgeId ->
+ probes[classLocalEdgeId] = true
+ }
+ outWriter.visitClassExecution(ExecutionData(info.classId, internalClassName, probes))
+ }
+ return outStream.toByteArray()
+ }
+
+ private fun analyzeCoverage(coveredIds: Set<Int>): CoverageBuilder? {
+ return try {
+ val coverage = CoverageBuilder()
+ val rawExecutionData = dumpJacocoCoverage(coveredIds) ?: return null
+ val executionDataStore = ExecutionDataStore()
+ val sessionInfoStore = SessionInfoStore()
+ ByteArrayInputStream(rawExecutionData).use { stream ->
+ ExecutionDataReader(stream).run {
+ setExecutionDataVisitor(executionDataStore)
+ setSessionInfoVisitor(sessionInfoStore)
+ read()
+ }
+ }
+ for ((internalClassName, info) in instrumentedClassInfo) {
+ EdgeCoverageInstrumentor(0).analyze(
+ executionDataStore,
+ coverage,
+ info.bytecode,
+ internalClassName
+ )
+ }
+ coverage
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt
index e22cd2c0..2e2a0b11 100644
--- a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt
@@ -16,8 +16,12 @@ package com.code_intelligence.jazzer.instrumentor
import com.code_intelligence.jazzer.generated.JAVA_NO_THROW_METHODS
import com.code_intelligence.jazzer.runtime.CoverageMap
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.Analyzer
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.ICoverageVisitor
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore
import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesAdapter
import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesVisitor
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IClassProbesAdapterFactory
import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IMethodProbesAdapterFactory
import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IProbeIdGenerator
import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.MethodProbesAdapter
@@ -53,6 +57,12 @@ class EdgeCoverageInstrumentor(
return writer.toByteArray()
}
+ fun analyze(executionData: ExecutionDataStore, coverageVisitor: ICoverageVisitor, bytecode: ByteArray, internalClassName: String) {
+ Analyzer(executionData, coverageVisitor, edgeCoverageClassProbesAdapterFactory).run {
+ analyzeClass(bytecode, internalClassName)
+ }
+ }
+
val numEdges
get() = nextEdgeId - initialEdgeId
@@ -232,6 +242,10 @@ class EdgeCoverageInstrumentor(
override fun nextId(): Int = nextEdgeId()
}
+ private val edgeCoverageClassProbesAdapterFactory = IClassProbesAdapterFactory { probesVisitor, trackFrames ->
+ EdgeCoverageClassProbesAdapter(probesVisitor, trackFrames)
+ }
+
private val edgeCoverageProbeArrayStrategy = object : IProbeArrayStrategy {
override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int {
loadCoverageMap(mv, variable)
diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp
index 9c85b6dc..cda352c8 100644
--- a/driver/coverage_tracker.cpp
+++ b/driver/coverage_tracker.cpp
@@ -25,10 +25,13 @@ extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start,
uint8_t *end);
extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
const uintptr_t *pcs_end);
+extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries);
constexpr auto kCoverageMapClass =
"com/code_intelligence/jazzer/runtime/CoverageMap";
constexpr auto kByteBufferClass = "java/nio/ByteBuffer";
+constexpr auto kCoverageRecorderClass =
+ "com/code_intelligence/jazzer/instrumentor/CoverageRecorder";
// The initial size of the Java coverage map (512 counters).
constexpr std::size_t kInitialCoverageCountersBufferSize = 1u << 9u;
@@ -45,7 +48,7 @@ void AssertNoException(JNIEnv &env) {
if (env.ExceptionCheck()) {
env.ExceptionDescribe();
throw std::runtime_error(
- "Java exception occured in CoverageTracker JNI code");
+ "Java exception occurred in CoverageTracker JNI code");
}
}
} // namespace
@@ -147,4 +150,53 @@ void CoverageTracker::Clear() {
}
uint8_t *CoverageTracker::GetCoverageCounters() { return counters_; }
+
+void CoverageTracker::RecordInitialCoverage(JNIEnv &env) {
+ jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
+ AssertNoException(env);
+ jmethodID coverage_recorder_update_covered_ids_with_coverage_map =
+ env.GetStaticMethodID(coverage_recorder,
+ "updateCoveredIdsWithCoverageMap", "()V");
+ AssertNoException(env);
+ env.CallStaticVoidMethod(
+ coverage_recorder,
+ coverage_recorder_update_covered_ids_with_coverage_map);
+ AssertNoException(env);
+}
+
+std::string CoverageTracker::ComputeCoverage(JNIEnv &env) {
+ uintptr_t *covered_pcs;
+ size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs);
+ std::vector<jint> covered_edge_ids{};
+ covered_edge_ids.reserve(num_covered_pcs);
+ const uintptr_t first_pc = pc_entries_[0].PC;
+ std::for_each(covered_pcs, covered_pcs + num_covered_pcs,
+ [&covered_edge_ids, first_pc](const uintptr_t pc) {
+ jint edge_id =
+ (pc - first_pc) / sizeof(fake_instructions_[0]);
+ covered_edge_ids.push_back(edge_id);
+ });
+ delete[] covered_pcs;
+
+ jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
+ AssertNoException(env);
+ jmethodID coverage_recorder_compute_file_coverage = env.GetStaticMethodID(
+ coverage_recorder, "computeFileCoverage", "([I)Ljava/lang/String;");
+ AssertNoException(env);
+ jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs);
+ AssertNoException(env);
+ env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs,
+ covered_edge_ids.data());
+ AssertNoException(env);
+ auto file_coverage_jni = (jstring)(env.CallStaticObjectMethod(
+ coverage_recorder, coverage_recorder_compute_file_coverage,
+ covered_edge_ids_jni));
+ AssertNoException(env);
+ auto file_coverage_cstr = env.GetStringUTFChars(file_coverage_jni, nullptr);
+ AssertNoException(env);
+ std::string file_coverage(file_coverage_cstr);
+ env.ReleaseStringUTFChars(file_coverage_jni, file_coverage_cstr);
+ AssertNoException(env);
+ return file_coverage;
+}
} // namespace jazzer
diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h
index 79d8112d..55d9db60 100644
--- a/driver/coverage_tracker.h
+++ b/driver/coverage_tracker.h
@@ -16,6 +16,8 @@
#pragma once
+#include <string>
+
#include "jvm_tooling.h"
#include "third_party/jni/jni.h"
@@ -47,5 +49,8 @@ class CoverageTracker : public ExceptionPrinter {
// Returns the address of the coverage counters array.
static uint8_t *GetCoverageCounters();
+
+ static void RecordInitialCoverage(JNIEnv &env);
+ static std::string ComputeCoverage(JNIEnv &env);
};
} // namespace jazzer
diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp
index 66405380..fd87d0e0 100644
--- a/driver/fuzz_target_runner.cpp
+++ b/driver/fuzz_target_runner.cpp
@@ -25,6 +25,7 @@
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/substitute.h"
+#include "coverage_tracker.h"
#include "fuzzed_data_provider.h"
#include "gflags/gflags.h"
#include "glog/logging.h"
@@ -53,6 +54,11 @@ DEFINE_string(
DEFINE_string(reproducer_path, ".",
"Path at which fuzzing reproducers are stored. Defaults to the "
"current directory.");
+DEFINE_string(coverage_report, "",
+ "Path at which a coverage report is stored when the fuzzer "
+ "exits. If left empty, no report is generated (default)");
+
+DECLARE_bool(hooks);
constexpr auto kManifestUtilsClass =
"com/code_intelligence/jazzer/runtime/ManifestUtils";
@@ -165,6 +171,9 @@ FuzzTargetRunner::FuzzTargetRunner(
std::exit(1);
}
+ if (FLAGS_hooks && !FLAGS_coverage_report.empty()) {
+ CoverageTracker::RecordInitialCoverage(env);
+ }
SetUpFuzzedDataProvider(jvm_);
// Parse a comma-separated list of hex dedup tokens.
@@ -184,6 +193,16 @@ FuzzTargetRunner::FuzzTargetRunner(
}
FuzzTargetRunner::~FuzzTargetRunner() {
+ if (FLAGS_hooks && !FLAGS_coverage_report.empty()) {
+ std::string report = CoverageTracker::ComputeCoverage(jvm_.GetEnv());
+ std::ofstream report_file(FLAGS_coverage_report);
+ if (report_file) {
+ report_file << report << std::flush;
+ } else {
+ LOG(ERROR) << "Failed to write coverage report to "
+ << FLAGS_coverage_report;
+ }
+ }
if (fuzzer_tear_down_ != nullptr) {
std::cerr << "calling fuzzer teardown function" << std::endl;
jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_);
diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp
index d69ca3d4..4dfb53cd 100644
--- a/driver/libfuzzer_driver.cpp
+++ b/driver/libfuzzer_driver.cpp
@@ -50,6 +50,9 @@ DECLARE_bool(fake_pcs);
// Defined in jvm_tooling.cpp
DECLARE_string(id_sync_file);
+// Defined in fuzz_target_runner.cpp
+DECLARE_string(coverage_report);
+
// We apply a patch to libFuzzer to make it call this function instead of
// __sanitizer_set_death_callback to pass us the death callback.
extern "C" [[maybe_unused]] void __jazzer_set_death_callback(
@@ -128,6 +131,11 @@ AbstractLibfuzzerDriver::AbstractLibfuzzerDriver(
absl::StartsWith(arg, "-jobs=") ||
absl::StartsWith(arg, "-merge=");
})) {
+ if (!FLAGS_coverage_report.empty()) {
+ LOG(WARNING) << "WARN: --coverage_report does not support parallel "
+ "fuzzing and has been disabled";
+ FLAGS_coverage_report = "";
+ }
if (FLAGS_id_sync_file.empty()) {
// Create an empty temporary file used for coverage ID synchronization and
// pass its path to the agent in every child process. This requires adding
diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h
index 22625c56..f3dd9a49 100644
--- a/driver/libfuzzer_driver.h
+++ b/driver/libfuzzer_driver.h
@@ -63,9 +63,9 @@ class LibfuzzerDriver : public AbstractLibfuzzerDriver {
public:
LibfuzzerDriver(int *argc, char ***argv);
- virtual RunResult TestOneInput(const uint8_t *data, std::size_t size);
+ RunResult TestOneInput(const uint8_t *data, std::size_t size) override;
- virtual ~LibfuzzerDriver() = default;
+ ~LibfuzzerDriver() override = default;
void DumpReproducer(const uint8_t *data, std::size_t size);
diff --git a/repositories.bzl b/repositories.bzl
index 46f03a4f..36ba3e59 100644
--- a/repositories.bzl
+++ b/repositories.bzl
@@ -131,6 +131,9 @@ def jazzer_dependencies():
http_archive,
name = "jazzer_libfuzzer",
build_file = "@jazzer//third_party:libFuzzer.BUILD",
+ patches = [
+ "//third_party:libFuzzer-get-covered-pcs.patch",
+ ],
sha256 = "4aba69716993cc5d5361ddec99bdf53a7a20de91511d2b04e430650d5a2a8b4b",
strip_prefix = "llvm-project-jazzer-69c3fe6175b0bc8f8a5a6be3a9e66a5502d98915",
url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/archive/69c3fe6175b0bc8f8a5a6be3a9e66a5502d98915.tar.gz",
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
index d4c527f8..d9651b60 100644
--- a/third_party/BUILD.bazel
+++ b/third_party/BUILD.bazel
@@ -1,4 +1,5 @@
exports_files([
"gflags-use-double-dash-args.patch",
"jacoco-make-probe-inserter-subclassable.patch",
+ "libFuzzer-get-covered-pcs.patch",
])
diff --git a/third_party/jacoco-make-probe-adapter-subclassable.patch b/third_party/jacoco-make-probe-adapter-subclassable.patch
index 2d78a900..2946fa8f 100644
--- a/third_party/jacoco-make-probe-adapter-subclassable.patch
+++ b/third_party/jacoco-make-probe-adapter-subclassable.patch
@@ -1,9 +1,76 @@
-commit 93d0421590e59026c595090c468be0caa58a7948
+commit 96d079e68e8897c425d85c7a5ff0afc755b246eb
Author: Fabian Meumertzheim <meumertzheim@code-intelligence.com>
Date: Tue Apr 20 08:06:09 2021 +0200
Updated Jazzer patch
+diff --git org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
+index 0cc06ada..b65efb03 100644
+--- org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
++++ org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
+@@ -31,6 +31,8 @@ import org.jacoco.core.internal.analysis.ClassCoverageImpl;
+ import org.jacoco.core.internal.analysis.StringPool;
+ import org.jacoco.core.internal.data.CRC64;
+ import org.jacoco.core.internal.flow.ClassProbesAdapter;
++import org.jacoco.core.internal.flow.ClassProbesVisitor;
++import org.jacoco.core.internal.flow.IClassProbesAdapterFactory;
+ import org.jacoco.core.internal.instr.InstrSupport;
+ import org.objectweb.asm.ClassReader;
+ import org.objectweb.asm.ClassVisitor;
+@@ -52,6 +54,8 @@ public class Analyzer {
+
+ private final StringPool stringPool;
+
++ private final IClassProbesAdapterFactory classProbesAdapterFactory;
++
+ /**
+ * Creates a new analyzer reporting to the given output.
+ *
+@@ -63,9 +67,21 @@ public class Analyzer {
+ */
+ public Analyzer(final ExecutionDataStore executionData,
+ final ICoverageVisitor coverageVisitor) {
++ this(executionData, coverageVisitor, new IClassProbesAdapterFactory() {
++ @Override
++ public ClassProbesAdapter makeClassProbesAdapter(ClassProbesVisitor cv, boolean trackFrames) {
++ return new ClassProbesAdapter(cv, trackFrames);
++ };
++ });
++ }
++
++ public Analyzer(final ExecutionDataStore executionData,
++ final ICoverageVisitor coverageVisitor,
++ final IClassProbesAdapterFactory classProbesAdapterFactory) {
+ this.executionData = executionData;
+ this.coverageVisitor = coverageVisitor;
+ this.stringPool = new StringPool();
++ this.classProbesAdapterFactory = classProbesAdapterFactory;
+ }
+
+ /**
+@@ -99,7 +115,7 @@ public class Analyzer {
+ coverageVisitor.visitCoverage(coverage);
+ }
+ };
+- return new ClassProbesAdapter(analyzer, false);
++ return classProbesAdapterFactory.makeClassProbesAdapter(analyzer, false);
+ }
+
+ private void analyzeClass(final byte[] source) {
+diff --git org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java
+index a69f34c8..ea1f0352 100644
+--- org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java
++++ org.jacoco.core/src/org/jacoco/core/internal/analysis/InstructionsBuilder.java
+@@ -147,7 +147,8 @@ class InstructionsBuilder {
+ */
+ void addProbe(final int probeId, final int branch) {
+ final boolean executed = probes != null && probes[probeId];
+- currentInsn.addBranch(executed, branch);
++ if (currentInsn != null)
++ currentInsn.addBranch(executed, branch);
+ }
+
+ /**
diff --git org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java
index 3ed19a88..f2b4f5d3 100644
--- org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java
@@ -58,9 +125,21 @@ index 3ed19a88..f2b4f5d3 100644
if (trackFrames) {
final AnalyzerAdapter analyzer = new AnalyzerAdapter(
ClassProbesAdapter.this.name, access, name, desc,
+diff --git org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java
+new file mode 100644
+index 00000000..45fc2709
+--- /dev/null
++++ org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java
+@@ -0,0 +1,6 @@
++package org.jacoco.core.internal.flow;
++
++public interface IClassProbesAdapterFactory {
++ ClassProbesAdapter makeClassProbesAdapter(ClassProbesVisitor cv,
++ boolean trackFrames);
++}
diff --git org.jacoco.core/src/org/jacoco/core/internal/flow/IMethodProbesAdapterFactory.java org.jacoco.core/src/org/jacoco/core/internal/flow/IMethodProbesAdapterFactory.java
new file mode 100644
-index 00000000..e939fa31
+index 00000000..4e5460f1
--- /dev/null
+++ org.jacoco.core/src/org/jacoco/core/internal/flow/IMethodProbesAdapterFactory.java
@@ -0,0 +1,8 @@
@@ -72,7 +151,6 @@ index 00000000..e939fa31
+ MethodProbesAdapter makeMethodProbesAdapter(MethodProbesVisitor probesVisitor,
+ IProbeIdGenerator idGenerator);
+}
-\ No newline at end of file
diff --git org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java
index 30253d02..e019a290 100644
--- org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java
diff --git a/third_party/libFuzzer-get-covered-pcs.patch b/third_party/libFuzzer-get-covered-pcs.patch
new file mode 100644
index 00000000..a1591b3d
--- /dev/null
+++ b/third_party/libFuzzer-get-covered-pcs.patch
@@ -0,0 +1,21 @@
+diff --git compiler-rt/lib/fuzzer/FuzzerTracePC.cpp compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
+index 208f56af0b84..fdd714d2cb31 100644
+--- compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
++++ compiler-rt/lib/fuzzer/FuzzerTracePC.cpp
+@@ -27,6 +27,16 @@
+ // Used by -fsanitize-coverage=stack-depth to track stack depth
+ ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC uintptr_t __sancov_lowest_stack;
+
++extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **covered_pcs_out) {
++ size_t size = fuzzer::TPC.GetTotalPCCoverage();
++ auto covered_pcs = new uintptr_t[size];
++ *covered_pcs_out = covered_pcs;
++ fuzzer::TPC.ForEachObservedPC([&](const fuzzer::TracePC::PCTableEntry* entry) {
++ *covered_pcs++ = entry->PC;
++ });
++ return size;
++}
++
+ namespace fuzzer {
+
+ TracePC TPC;