blob: 7a725b169c148225cbdbe3bc12c1b899b50b4c75 [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.BA_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.BRDA_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.BRF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.BRH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.DA_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.DELIMITER;
import static com.google.devtools.coverageoutputgenerator.Constants.END_OF_RECORD_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.FNDA_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.FNF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.FNH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.FN_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.LF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.LH_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.SF_MARKER;
import static com.google.devtools.coverageoutputgenerator.Constants.TAKEN;
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 parser for the lcov tracefile format used by geninfo. See <a
* href="http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php">lcov documentation</a>
*/
class LcovParser {
private static final Logger logger = Logger.getLogger(LcovParser.class.getName());
private final InputStream inputStream;
private SourceFileCoverage currentSourceFileCoverage;
private LcovParser(InputStream inputStream) {
this.inputStream = inputStream;
}
public static List<SourceFileCoverage> parse(InputStream inputStream) throws IOException {
return new LcovParser(inputStream).parse();
}
/**
* Reads the tracefile line by line and creates a SourceFileCoverage object for each section of
* the file between a SF:<source file> line and an end_of_record line.
*
* @return a list of each source file path found in the tracefile
*/
private List<SourceFileCoverage> parse() throws IOException {
List<SourceFileCoverage> allSourceFiles = new ArrayList<>();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, UTF_8))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
parseLine(line, allSourceFiles);
}
bufferedReader.close();
}
return allSourceFiles;
}
/**
* Merges {@code currentSourceFileCoverage} into {@code allSourceFilesCoverageData} and resets
* {@code currentSourceFileCoverage} to null.
*/
private void reset(List<SourceFileCoverage> allSourceFiles) {
allSourceFiles.add(currentSourceFileCoverage);
currentSourceFileCoverage = null;
}
/*
* Reads the line and redirects the parsing to the corresponding {@code parseXLine} method. Every
* {@code parseXLine} methods fills in data to {@code currentSourceFileCoverage} accordingly.
*/
private boolean parseLine(String line, List<SourceFileCoverage> allSourceFiles) {
if (line.startsWith(SF_MARKER)) {
return parseSFLine(line);
}
// currentSourceFileCoverage should be null only before calling an SF line, otherwise
// the object should have been created in parseSFLine. If currentSourceFileCoverage is null
// here it means the parser arrived in an invalid state.
if (currentSourceFileCoverage == null) {
return false;
}
if (line.startsWith(FN_MARKER)) {
return parseFNLine(line);
}
if (line.startsWith(FNDA_MARKER)) {
return parseFNDALine(line);
}
if (line.startsWith(FNF_MARKER)) {
return parseFNFLine(line);
}
if (line.startsWith(FNH_MARKER)) {
return parseFNHLine(line);
}
if (line.startsWith(BRDA_MARKER)) {
return parseBRDALine(line);
}
if (line.startsWith(BA_MARKER)) {
return parseBALine(line);
}
if (line.startsWith(BRF_MARKER)) {
return parseBRFLine(line);
}
if (line.startsWith(BRH_MARKER)) {
return parseBRHLine(line);
}
if (line.startsWith(DA_MARKER)) {
return parseDALine(line);
}
if (line.startsWith(LH_MARKER)) {
return parseLHLine(line);
}
if (line.startsWith(LF_MARKER)) {
return parseLFLine(line);
}
if (line.equals(END_OF_RECORD_MARKER)) {
reset(allSourceFiles);
return true;
}
logger.log(Level.WARNING, "Tracefile includes invalid line: " + line);
return false;
}
// SF:<path to source file name>
private boolean parseSFLine(String line) {
if (currentSourceFileCoverage != null) {
logger.log(Level.WARNING, "Tracefile doesn't have SF:<source file> line before" + line);
return false;
}
String sourcefile = line.substring(SF_MARKER.length());
if (sourcefile.isEmpty()) {
logger.log(Level.WARNING, "Tracefile doesn't contain source file name on line: " + line);
return false;
}
currentSourceFileCoverage = new SourceFileCoverage(sourcefile);
return true;
}
// FN:<line number of function start>,<function name>
private boolean parseFNLine(String line) {
String lineContent = line.substring(FN_MARKER.length());
String[] funcData = lineContent.split(DELIMITER, -1);
if (funcData.length != 2 || funcData[0].isEmpty() || funcData[1].isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid FN line " + line);
return false;
}
try {
int lineNrFunctionStart = Integer.parseInt(funcData[0]);
String functionName = funcData[1];
currentSourceFileCoverage.addLineNumber(functionName, lineNrFunctionStart);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains invalid line number on FN line " + line);
return false;
}
return true;
}
// FNDA:<execution count>,<function name>
private boolean parseFNDALine(String line) {
String lineContent = line.substring(FNDA_MARKER.length());
String[] funcData = lineContent.split(DELIMITER, -1);
if (funcData.length != 2 || funcData[0].isEmpty() || funcData[1].isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid FNDA line " + line);
return false;
}
try {
int executionCount = Integer.parseInt(funcData[0]);
String functionName = funcData[1];
currentSourceFileCoverage.addFunctionExecution(functionName, executionCount);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains invalid execution count on FN line " + line);
return false;
}
return true;
}
// FNF:<number of functions found>
private boolean parseFNFLine(String line) {
String lineContent = line.substring(FNF_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid FNF line " + line);
return false;
}
try {
int nrFunctionsFound = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrFunctionsFound() == nrFunctionsFound;
} catch (NumberFormatException e) {
logger.log(
Level.WARNING, "Tracefile contains invalid number of functions on FNF line " + line);
return false;
}
return true;
}
// FNH:<number of function hit>
private boolean parseFNHLine(String line) {
String lineContent = line.substring(FNH_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid FNH line " + line);
return false;
}
try {
int nrFunctionsHit = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrFunctionsHit() == nrFunctionsHit;
} catch (NumberFormatException e) {
logger.log(
Level.WARNING, "Tracefile contains invalid number of functions hit on FNH line " + line);
return false;
}
return true;
}
// BA:<line number>,<taken>
private boolean parseBALine(String line) {
String lineContent = line.substring(BA_MARKER.length());
String[] lineData = lineContent.split(DELIMITER, -1);
if (lineData.length != 2) {
logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
return false;
}
for (String data : lineData) {
if (data.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
return false;
}
}
try {
int lineNumber = Integer.parseInt(lineData[0]);
int taken = Integer.parseInt(lineData[1]);
BranchCoverage branchCoverage = BranchCoverage.create(lineNumber, taken);
currentSourceFileCoverage.addBranch(lineNumber, branchCoverage);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number BA line " + line);
return false;
}
return true;
}
// BRDA:<line number>,<block number>,<branch number>,<taken>
private boolean parseBRDALine(String line) {
String lineContent = line.substring(BRDA_MARKER.length());
String[] lineData = lineContent.split(DELIMITER, -1);
if (lineData.length != 4) {
logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
return false;
}
for (String data : lineData) {
if (data.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid BRDA line " + line);
return false;
}
}
try {
int lineNumber = Integer.parseInt(lineData[0]);
String blockNumber = lineData[1];
String branchNumber = lineData[2];
String taken = lineData[3];
boolean wasExecuted = false;
int executionCount = 0;
if (taken.equals(TAKEN)) {
executionCount = Integer.parseInt(taken);
wasExecuted = true;
}
BranchCoverage branchCoverage =
BranchCoverage.createWithBlockAndBranch(
lineNumber, blockNumber, branchNumber, executionCount);
currentSourceFileCoverage.addBranch(lineNumber, branchCoverage);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number BRDA line " + line);
return false;
}
return true;
}
// BRF:<number of branches found>
private boolean parseBRFLine(String line) {
String lineContent = line.substring(BRF_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid BRF line " + line);
return false;
}
try {
int nrBranchesFound = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrBranchesFound() == nrBranchesFound;
} catch (NumberFormatException e) {
logger.log(
Level.WARNING, "Tracefile contains invalid number of branches in BRDA line " + line);
return false;
}
return true;
}
// BRH:<number of branches hit>
private boolean parseBRHLine(String line) {
String lineContent = line.substring(BRH_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid BRH line " + line);
return false;
}
try {
int nrBranchesHit = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrBranchesHit() == nrBranchesHit;
} catch (NumberFormatException e) {
logger.log(
Level.WARNING, "Tracefile contains invalid number of branches hit in BRH line " + line);
return false;
}
return true;
}
// DA:<line number>,<execution count>,[,<checksum>]
private boolean parseDALine(String line) {
String lineContent = line.substring(DA_MARKER.length());
String[] lineData = lineContent.split(DELIMITER, -1);
if (lineData.length != 2 && lineData.length != 3) {
logger.log(Level.WARNING, "Tracefile contains invalid DA line " + line);
return false;
}
for (String data : lineData) {
if (data.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid DA line " + line);
return false;
}
}
try {
int lineNumber = Integer.parseInt(lineData[0]);
int executionCount = Integer.parseInt(lineData[1]);
String checkSum = null;
if (lineData.length == 3) {
checkSum = lineData[2];
}
LineCoverage lineCoverage = LineCoverage.create(lineNumber, executionCount, checkSum);
currentSourceFileCoverage.addLine(lineNumber, lineCoverage);
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number on DA line " + line);
return false;
}
return true;
}
// LH:<nr of lines with non-zero exec count>
private boolean parseLHLine(String line) {
String lineContent = line.substring(LH_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid LHL line " + line);
return false;
}
try {
int nrLines = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrOfLinesWithNonZeroExecution() == nrLines;
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number on LHL line " + line);
return false;
}
return true;
}
// LF:<number of instrumented lines>
private boolean parseLFLine(String line) {
String lineContent = line.substring(LF_MARKER.length());
if (lineContent.isEmpty()) {
logger.log(Level.WARNING, "Tracefile contains invalid LF line " + line);
return false;
}
try {
int nrLines = Integer.parseInt(lineContent);
assert currentSourceFileCoverage.nrOfInstrumentedLines() == nrLines;
} catch (NumberFormatException e) {
logger.log(Level.WARNING, "Tracefile contains an invalid number on LF line " + line);
return false;
}
return true;
}
}