// 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.lcovmerger; import static com.google.devtools.lcovmerger.Constants.DELIMITER; import static com.google.devtools.lcovmerger.Constants.GCOV_BRANCH_MARKER; import static com.google.devtools.lcovmerger.Constants.GCOV_BRANCH_NOTEXEC; import static com.google.devtools.lcovmerger.Constants.GCOV_BRANCH_NOTTAKEN; import static com.google.devtools.lcovmerger.Constants.GCOV_BRANCH_TAKEN; import static com.google.devtools.lcovmerger.Constants.GCOV_CWD_MARKER; import static com.google.devtools.lcovmerger.Constants.GCOV_FILE_MARKER; import static com.google.devtools.lcovmerger.Constants.GCOV_FUNCTION_MARKER; import static com.google.devtools.lcovmerger.Constants.GCOV_LINE_MARKER; import static com.google.devtools.lcovmerger.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 gcov documentation. */ public class GcovParser { private static final Logger logger = Logger.getLogger(GcovParser.class.getName()); private List allSourceFiles; private final InputStream inputStream; private SourceFileCoverage currentSourceFileCoverage; private GcovParser(InputStream inputStream) { this.inputStream = inputStream; } public static List parse(InputStream inputStream) throws IOException { return new GcovParser(inputStream).parse(); } private List 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) { throw new IOException("gcov intermediate input is malformed."); } 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.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; } 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; } }