blob: 8f63639d4935fdef72c61a0ff8b578a0aa3363e4 [file] [log] [blame]
/*
* Copyright 2022 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.example;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionData;
import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataReader;
import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.ExecutionDataStore;
import com.code_intelligence.jazzer.third_party.org.jacoco.core.data.SessionInfoStore;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Test of coverage report and dump.
*
* Internally, JaCoCo is used to gather coverage information to guide the fuzzer to cover new
* branches. This information can be dumped in the JaCoCo format and used to generate reports later
* on. The dump only contains classes with at least one coverage data point. A JaCoCo report will
* also include completely uncovered files based on the available classes in the stated jar files
* in the report command.
*
* A human-readable coverage report can be generated directly by Jazzer. It contains information
* on file level about all classes that should have been instrumented according to the
* instrumentation_includes and instrumentation_exclude filters.
*/
@SuppressWarnings({"unused", "UnusedReturnValue"})
public final class CoverageFuzzer {
// Not used during fuzz run, so not included in the dump
public static class ClassNotToCover {
private final int i;
public ClassNotToCover(int i) {
this.i = i;
}
public int getI() {
return i;
}
}
// Used in the fuzz run and included in the dump
public static class ClassToCover {
private final int i;
public ClassToCover(int i) {
if (i < 0 || i > 1000) {
throw new IllegalArgumentException(String.format("Invalid repeat number \"%d\"", i));
}
this.i = i;
}
public String repeat(String str) {
if (str != null && str.length() >= 3 && str.length() <= 10) {
return IntStream.range(0, i).mapToObj(i -> str).collect(Collectors.joining());
}
throw new IllegalArgumentException(String.format("Invalid str \"%s\"", str));
}
}
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
try {
ClassToCover classToCover = new ClassToCover(data.consumeInt());
String repeated = classToCover.repeat(data.consumeRemainingAsAsciiString());
if (repeated.equals("foofoofoo")) {
throw new FuzzerSecurityIssueLow("Finished coverage fuzzer test");
}
} catch (IllegalArgumentException ignored) {
}
}
public static void fuzzerTearDown() throws IOException {
assertCoverageReport();
assertCoverageDump();
}
private static void assertCoverageReport() throws IOException {
List<String> coverage = Files.readAllLines(Paths.get(System.getenv("COVERAGE_REPORT_FILE")));
List<List<String>> sections = new ArrayList<>(4);
sections.add(new ArrayList<>());
coverage.forEach(l -> {
if (l.isEmpty()) {
sections.add(new ArrayList<>());
} else {
sections.get(sections.size() - 1).add(l);
}
});
List<String> branchCoverage = sections.get(0);
assertEquals(2, branchCoverage.size());
List<String> lineCoverage = sections.get(1);
assertEquals(2, lineCoverage.size());
List<String> incompleteCoverage = sections.get(2);
assertEquals(2, incompleteCoverage.size());
List<String> missedCoverage = sections.get(3);
assertEquals(2, missedCoverage.size());
assertNotNull(
branchCoverage.stream()
.filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find branch coverage")));
assertNotNull(
lineCoverage.stream()
.filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find line coverage")));
assertNotNull(
incompleteCoverage.stream()
.filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find incomplete coverage")));
String missed =
missedCoverage.stream()
.filter(l -> l.startsWith(CoverageFuzzer.class.getSimpleName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find missed coverage"));
List<String> missingLines = IntStream.rangeClosed(63, 79)
.mapToObj(i -> " " + i)
.filter(missed::contains)
.collect(Collectors.toList());
if (!missingLines.isEmpty()) {
throw new IllegalStateException(String.format(
"Missing coverage for ClassToCover on lines %s", String.join(", ", missingLines)));
}
}
private static void assertCoverageDump() throws IOException {
ExecutionDataStore executionDataStore = new ExecutionDataStore();
SessionInfoStore sessionInfoStore = new SessionInfoStore();
try (FileInputStream bais = new FileInputStream(System.getenv("COVERAGE_DUMP_FILE"))) {
ExecutionDataReader reader = new ExecutionDataReader(bais);
reader.setExecutionDataVisitor(executionDataStore);
reader.setSessionInfoVisitor(sessionInfoStore);
reader.read();
}
assertEquals(2, executionDataStore.getContents().size());
ExecutionData coverageFuzzerCoverage = new ExecutionData(0, "", 0);
ExecutionData classToCoverCoverage = new ExecutionData(0, "", 0);
for (ExecutionData content : executionDataStore.getContents()) {
if (content.getName().endsWith("ClassToCover")) {
classToCoverCoverage = content;
} else {
coverageFuzzerCoverage = content;
}
}
assertEquals("com/example/CoverageFuzzer", coverageFuzzerCoverage.getName());
assertEquals(7, countHits(coverageFuzzerCoverage.getProbes()));
assertEquals("com/example/CoverageFuzzer$ClassToCover", classToCoverCoverage.getName());
assertEquals(11, countHits(classToCoverCoverage.getProbes()));
}
private static int countHits(boolean[] probes) {
int count = 0;
for (boolean probe : probes) {
if (probe)
count++;
}
return count;
}
private static <T> void assertEquals(T expected, T actual) {
if (!expected.equals(actual)) {
throw new IllegalStateException(
String.format("Expected \"%s\", got \"%s\"", expected, actual));
}
}
private static <T> void assertNotNull(T actual) {
if (actual == null) {
throw new IllegalStateException("Expected none null value, got null");
}
}
}