blob: ca943fb8518a308881107f183c57c617b2bcd6fd [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.coverageoutputgenerator;
import static com.google.devtools.coverageoutputgenerator.Constants.DELIMITER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_BRANCH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_BRANCH_NOTEXEC;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_BRANCH_NOTTAKEN;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_BRANCH_TAKEN;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_CWD_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_FILE_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_FUNCTION_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_LINE_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.GCOV_VERSION_MARKER;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A {@link Parser} for gcov intermediate format. See the flag {@code --intermediate-format} in <a
* href="https://gcc.gnu.org/onlinedocs/gcc/Invoking-gcov.html">gcov documentation</a>.
*/
public class GcovParser {
private static final Logger logger = Logger.getLogger(GcovParser.class.getName());
private List<SourceFileCoverage> allSourceFiles;
private final InputStream inputStream;
private SourceFileCoverage currentSourceFileCoverage;
private GcovParser(InputStream inputStream) {
this.inputStream = inputStream;
}
public static List<SourceFileCoverage> parse(InputStream inputStream) throws IOException {
return new GcovParser(inputStream).parse();
}
private List<SourceFileCoverage> parse() throws IOException {
allSourceFiles = new ArrayList<>();
boolean malformedInput = false;
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, UTF_8))) {
String line;
// TODO(bazel-team): This is susceptible to OOM if the input file is too large and doesn't
// contain any newlines.
while ((line = bufferedReader.readLine()) != null) {
if (!parseLine(line)) {
malformedInput = true;
}
}
bufferedReader.close();
}
endSourceFile();
if (malformedInput) {
logger.log(
Level.WARNING,
"gcov intermediate input was malformed, some lines might not have been parsed. "
+ "Check the previous log entries for more information.");
}
return allSourceFiles;
}
/**
* Merges {@code currentSourceFileCoverage} into {@code allSourceFilesCoverageData} and resets
* {@code currentSourceFileCoverage} to null.
*/
private void endSourceFile() {
if (currentSourceFileCoverage == null) {
return;
}
allSourceFiles.add(currentSourceFileCoverage);
currentSourceFileCoverage = null;
}
private boolean parseLine(String line) {
if (line.isEmpty()) {
return true;
}
if (line.startsWith(GCOV_FILE_MARKER)) {
endSourceFile();
return parseSource(line);
}
if (line.startsWith(GCOV_FUNCTION_MARKER)) {
return parseFunction(line);
}
if (line.startsWith(GCOV_LINE_MARKER)) {
return parseLCount(line);
}
if (line.startsWith(GCOV_BRANCH_MARKER)) {
return parseBranch(line);
}
if (line.startsWith(GCOV_VERSION_MARKER) || line.startsWith(GCOV_CWD_MARKER)) {
// Ignore these fields for now as they are not necessary.
return true;
}
logger.log(
Level.WARNING,
"Line <" + line + "> does not respect the gcov intermediate format and was ignored.");
return false;
}
private boolean parseSource(String line) {
String sourcefile = line.substring(GCOV_FILE_MARKER.length());
if (sourcefile.isEmpty()) {
logger.log(Level.WARNING, "gcov info doesn't contain source file name on line: " + line);
return false;
}
currentSourceFileCoverage = new SourceFileCoverage(sourcefile);
return true;
}
/**
* Valid lines: function:start_line_number,end_line_number,execution_count,function_name
* function:start_line_number,execution_count,function_name
*/
private boolean parseFunction(String line) {
String lineContent = line.substring(GCOV_FUNCTION_MARKER.length());
String[] items = lineContent.split(DELIMITER, -1);
if (items.length != 4 && items.length != 3) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
try {
// Ignore end_line_number since it's redundant information.
int startLine = Integer.parseInt(items[0]);
int execCount = items.length == 4 ? Integer.parseInt(items[2]) : Integer.parseInt(items[1]);
String functionName = items.length == 4 ? items[3] : items[2];
currentSourceFileCoverage.addLineNumber(functionName, startLine);
currentSourceFileCoverage.addFunctionExecution(functionName, execCount);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
return true;
}
/**
* Valid lines: lcount:line number,execution_count,has_unexecuted_block lcount:line
* number,execution_count
*/
private boolean parseLCount(String line) {
String lineContent = line.substring(GCOV_LINE_MARKER.length());
String[] items = lineContent.split(DELIMITER, -1);
if (items.length != 3 && items.length != 2) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
try {
// Ignore has_unexecuted_block since it's not used.
int lineNr = Integer.parseInt(items[0]);
int execCount = Integer.parseInt(items[1]);
currentSourceFileCoverage.addLine(lineNr, LineCoverage.create(lineNr, execCount, null));
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
return true;
}
// branch:line_number,branch_coverage_type
private boolean parseBranch(String line) {
String lineContent = line.substring(GCOV_BRANCH_MARKER.length());
String[] items = lineContent.split(DELIMITER, -1);
if (items.length != 2) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
try {
// Ignore has_unexecuted_block since it's not used.
int lineNr = Integer.parseInt(items[0]);
String type = items[1];
int execCount;
switch (type) {
case GCOV_BRANCH_NOTEXEC:
execCount = 0;
break;
case GCOV_BRANCH_NOTTAKEN:
case GCOV_BRANCH_TAKEN:
execCount = 1;
break;
default:
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
currentSourceFileCoverage.addBranch(lineNr, BranchCoverage.create(lineNr, execCount));
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "gcov info contains invalid line " + line);
return false;
}
return true;
}
}