diff options
Diffstat (limited to 'tools/test/LcovMerger/java/com/google/devtools/lcovmerger/GcovParser.java')
-rw-r--r-- | tools/test/LcovMerger/java/com/google/devtools/lcovmerger/GcovParser.java | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/GcovParser.java b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/GcovParser.java new file mode 100644 index 0000000000..25592192b2 --- /dev/null +++ b/tools/test/LcovMerger/java/com/google/devtools/lcovmerger/GcovParser.java @@ -0,0 +1,196 @@ +// 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; + } + + // function:start_line_number,end_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) { + 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 = Integer.parseInt(items[2]); + String functionName = items[3]; + 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; + } + + // lcount:line number,execution_count,has_unexecuted_block + private boolean parseLCount(String line) { + String lineContent = line.substring(GCOV_LINE_MARKER.length()); + String[] items = lineContent.split(DELIMITER, -1); + if (items.length != 3) { + 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; + } +} |