| // 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 <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) { | |
| 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; | |
| } | |
| } |