├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── XcodeCoverage.podspec ├── XcodeCoverage.xcconfig ├── cleancov ├── envcov.sh ├── exportenv.sh ├── getcov ├── lcov-1.14 ├── .version ├── COPYING └── bin │ ├── copy_dates.sh │ ├── gendesc │ ├── genhtml │ ├── geninfo │ ├── genpng │ ├── get_changes.sh │ ├── get_version.sh │ ├── install.sh │ ├── lcov │ └── updateversion.pl ├── lcov_cobertura.py ├── llvm-cov-wrapper.sh ├── run_code_coverage_post.sh └── transform.xslt /.gitignore: -------------------------------------------------------------------------------- 1 | env.sh 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 1.4.0 2 | ------------- 3 | _01 Jan 2020_ 4 | 5 | * Updated lcov to 1.14 6 | * Updated lcov_cobertura.py to 2.0.3 7 | 8 | 9 | Version 1.3.2 10 | ------------- 11 | _01 Feb 2019_ 12 | 13 | * Fixed for projects using Xcode "New Build System" 14 | 15 | 16 | Version 1.3.1 17 | ------------- 18 | _24 Sep 2017_ 19 | 20 | * Updated lcov to 1.13 for Xcode 8/9 compatibility. _Thanks to: senmiao_ 21 | 22 | 23 | Version 1.3.0 24 | ------------- 25 | _29 Feb 2016_ 26 | 27 | * Updated lcov to 1.12 for Xcode 7 compatibility. _Thanks to: Nico Elayda_ 28 | * Instead of editing `getcov` directly to exclude certain files from coverage, specify them in an `.xcodecoverageignore` file in `SRCROOT`. _Thanks to: Ellen Shapiro_ 29 | * Add ability to generate Clover XML reports for Bamboo CI. Call getcov with `--xmlclover` or `-xc`. _Thanks to: Kamil Pyć_ 30 | 31 | 32 | Version 1.2.2 33 | ------------- 34 | _22 Mar 2015_ 35 | 36 | * Add `getcov` command line parameters, making it easier to use XcodeCoverage in continuous integration. _Thanks to: Tom Aylesworth_ 37 | * Add Cobertura XML generation. _Thanks to: Ellen Shapiro_ 38 | * Support use as CocoaPod. _Thanks to: Ellen Shapiro_ 39 | * Update to lcov 1.11. _Thanks to: Ellen Shapiro_ 40 | * Add XcodeCoverage.xcconfig for simple project setup. 41 | 42 | 43 | Version 1.1.1 44 | ------------- 45 | _13 Nov 2014_ 46 | 47 | * Exclude "Developer/SDKs" instead of "Applications/Xcode.app" so that people can use multiple versions of Xcode. _Thanks to: Cédric Luthi_ 48 | * Quote arguments to support built products directory containing spaces. _Thanks to: Cédric Luthi_ 49 | * Fix functionality optional post-test dialog in other locales by forcing buttons to be in English. 50 | 51 | 52 | Version 1.1.0 53 | ------------- 54 | _30 Mar 2014_ 55 | 56 | * Support Xcode 5.1. _Thanks to: Mike Maietta_ 57 | * Add optional post-test script `run_code_coverage_post.sh` to prompt whether to generate coverage report. _Thanks to: Matthew Purland_ 58 | * Improve function coverage by specifying `--derive-func-data`. _Thanks to: jstevenco_ 59 | * Directly include copy of lcov 1.10. 60 | 61 | 62 | Version 1.0.1 63 | ------------- 64 | _09 Mar 2014_ 65 | 66 | * Fix: Use `CURRENT_ARCH` instead of `NATIVE_ARCH`. _Thanks to: Scott Densmore_ 67 | * Improve scripts so they can be run from any working directory. 68 | * Export `OBJROOT` so that customizations can include subprojects. 69 | 70 | 71 | Version 1.0.0 72 | ------------- 73 | _01 Dec 2012_ 74 | 75 | * Initial release 76 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Quality Coding, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![XcodeCoverage](http://qualitycoding.org/jrwp/wp-content/uploads/2016/01/XcodeCoverage@2x.png) 2 | 3 | [![CocoaPods Version](https://cocoapod-badges.herokuapp.com/v/XcodeCoverage/badge.png)](http://cocoapods.org/pods/XcodeCoverage) 4 | 5 | XcodeCoverage provides a simple way to generate reports of the Objective-C code coverage of your Xcode project. Generated reports include HTML and Cobertura XML. 6 | 7 | Coverage data excludes Apple's SDKs, and the exclusion rules can be customized. 8 | 9 | Sadly, Swift coverage is not supported. 10 | 11 | 12 | Installation: Standard 13 | ====================== 14 | 15 | Use the standard installation if you want to customize XcodeCoverage to exclude certain files and directories, such as third-party libraries. Otherwise, the CocoaPods installation described below may be more convenient. 16 | 17 | 1. Fork this repository. 18 | 2. Place the XcodeCoverage folder in the same folder as your Xcode project. 19 | 3. In your main target's Build Phases, add a Run Script build phase to execute `XcodeCoverage/exportenv.sh` 20 | 21 | A few people have been tripped up by the last step: Make sure you add the script to your main target (your app or library), not your test target. 22 | 23 | 24 | Installation: CocoaPods 25 | ======================= 26 | 27 | A [CocoaPod](http://cocoapods.org/) has been added for convenient use in simple projects. There are a couple of things you should be aware of if you are using the CocoaPod instead of the standard method: 28 | 29 | - There will be no actual files added to your project. Files are only added through `preserve_paths`, so they will be available in your `Pods/XcodeCoverage` path, but you will not see them in Xcode, and they will not be compiled by Xcode. 30 | - You will not be able to modify the scripts without those modifications being potentially overwritten by CocoaPods. 31 | 32 | If those caveats are deal-breakers, please use the standard installation method above. 33 | 34 | The steps to install via CocoaPods: 35 | 36 | 1. Add `pod 'XcodeCoverage', '~>1.0'` (or whatever [version specification](http://guides.cocoapods.org/using/the-podfile.html#specifying-pod-versions) you desire) to your Podfile. 37 | 2. Run `pod install`. This will download the necessary files. 38 | 3. In your main target, add a Run Script build phase to execute 39 | `Pods/XcodeCoverage/exportenv.sh`. 40 | 41 | Again, make sure you add the script to your main target (your app or library), not your test target. 42 | 43 | 44 | Xcode Project Setup 45 | =================== 46 | 47 | XcodeCoverage comes with an xcconfig file with the build settings required to instrument your code for coverage analysis. 48 | 49 | If you already use an xcconfig, include it in the configuration you want to instrument: 50 | 51 | * Standard installation: `#include "XcodeCoverage/XcodeCoverage.xcconfig"` 52 | * CocoaPods installation: `#include "Pods/XcodeCoverage/XcodeCoverage.xcconfig"` 53 | 54 | If you don't already use an xcconfig, drag XcodeCoverage.xcconfig into your project. Where it prompts "Add to targets," deselect all targets. (Otherwise, it will be included in the bundle.) Then click on your project in Xcode's Navigator pane, and select the Info tab. For the configuration you want to instrument, select XcodeCoverage. 55 | 56 | If you'd rather specify the build settings by hand, enable these two settings at the project level: 57 | 58 | * Instrument Program Flow 59 | * Generate Legacy Test Coverage Files 60 | 61 | Make sure not to instrument your AppStore release. 62 | 63 | Execution 64 | ========= 65 | 66 | 1. Run your unit tests. 67 | 2. In Terminal, execute `getcov` in your project's XcodeCoverage folder. 68 | 69 | `getcov` has the following command-line options: 70 | 71 | * `--show` or `-s`: Show HTML report. 72 | * `--xml` or `-x`: Generate Cobertura XML. 73 | * `-o output_dir`: Specify output directory. 74 | * `-i info_file`: Specify name of generated lcov info file. 75 | * `-v`: Enable verbose output. 76 | * `-h` or `--help`: Show usage. 77 | 78 | If you make changes to your test code without changing the production code and want a clean slate, use the `cleancov` script. 79 | 80 | If you make changes to your production code, you should clear out all build artifacts before measuring code coverage again. "Clean Build Folder" by holding down the Option key in Xcode's "Product" menu, or by using the ⌥⇧⌘K key combination. 81 | 82 | **Optional:** XcodeCoverage can prompt to run code coverage after running unit tests: 83 | 84 | * Edit Xcode scheme -> Test -> Post-actions 85 | * Set "Shell" to: `/bin/bash` 86 | * Set "Provide build settings from" to your main target 87 | * Set script to `source XcodeCoverage/run_code_coverage_post.sh` for standard installation. For CocoaPods installation, use `source Pods/XcodeCoverage/run_code_coverage_post.sh` 88 | 89 | 90 | Excluding Files From Coverage 91 | ============================= 92 | 93 | If there are files or folders which you want to have the coverage generator ignore (for instance, third-party libraries not installed via CocoaPods or machine-generated files), add an `.xcodecoverageignore` file to your `SRCROOT`. 94 | 95 | Each line should be a different file or group of files which should be excluded for code coverage purposes. You can use `SRCROOT` relative paths as well as the `*` character to indicate everything below a certain directory should be excluded. 96 | 97 | Example contents of an `.xcodecoverageignore` file: 98 | 99 | ``` 100 | ${SRCROOT}/TestedProject/Machine Files/* 101 | ${SRCROOT}/TestedProject/Third-Party/SingleFile.m 102 | ${SRCROOT}/TestedProject/Categories/UIImage+IgnoreMe.{h,m} 103 | ``` 104 | 105 | Note: If you were using a version of XcodeCoverage prior to 1.3, you will need to move the list of files and folders you wish to ignore to the `.xcodecoverageignore` file. The current setup will prevent your customized list from being overwritten when there is an update to this project. 106 | 107 | 108 | Credits 109 | ======= 110 | 111 | The `lcov` -> Cobertura script is from [https://github.com/eriwen/lcov-to-cobertura-xml/](https://github.com/eriwen/lcov-to-cobertura-xml/) and is bound by [the license of that project](https://github.com/eriwen/lcov-to-cobertura-xml/blob/master/LICENSE.txt). 112 | 113 | -------------------------------------------------------------------------------- /XcodeCoverage.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'XcodeCoverage' 3 | s.version = '1.4.0' 4 | s.summary = 'Code coverage for Xcode projects' 5 | s.description = <<-DESC 6 | XcodeCoverage provides a simple way to generate reports of the Objective-C code coverage 7 | of your Xcode project. Generated reports include HTML and Cobertura XML. 8 | 9 | Coverage data excludes Apple's SDKs, and the exclusion rules can be customized. 10 | DESC 11 | s.homepage = 'https://github.com/jonreid/XcodeCoverage' 12 | s.license = 'MIT' 13 | s.author = {'Jon Reid' => 'jon@qualitycoding.org'} 14 | s.social_media_url = 'https://twitter.com/qcoding' 15 | 16 | s.ios.deployment_target = '6.0' 17 | s.osx.deployment_target = '10.8' 18 | s.source = {:git => 'https://github.com/jonreid/XcodeCoverage.git', :tag => 'v1.4.0'} 19 | 20 | # XcodeCoverage files will be brought into the filesystem, but not added to your .xcodeproj. 21 | s.preserve_paths = '*', '**' 22 | end 23 | -------------------------------------------------------------------------------- /XcodeCoverage.xcconfig: -------------------------------------------------------------------------------- 1 | // XcodeCoverage by Jon Reid, https://qualitycoding.org 2 | // Copyright 2021 Quality Coding, Inc. See LICENSE.txt 3 | 4 | GCC_GENERATE_TEST_COVERAGE_FILES = YES 5 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES 6 | -------------------------------------------------------------------------------- /cleancov: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 3 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 4 | 5 | scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | source "${scripts}/envcov.sh" 7 | 8 | LCOV --zerocounters -d "${OBJ_DIR}" 9 | -------------------------------------------------------------------------------- /envcov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 3 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 4 | 5 | scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 6 | source "${scripts}/env.sh" 7 | 8 | # For New Build System, hard-code to 64-bit simulator 9 | if [ ${CURRENT_ARCH} = "undefined_arch" ] 10 | then 11 | ARCHITECTURE="x86_64" 12 | else 13 | ARCHITECTURE=${CURRENT_ARCH} 14 | fi 15 | 16 | LCOV_PATH="${scripts}/lcov-1.14/bin" 17 | OBJ_DIR="${OBJECT_FILE_DIR_normal}/${ARCHITECTURE}" 18 | 19 | # Fix for the new LLVM-COV that requires gcov to have a -v parameter 20 | LCOV() { 21 | "${LCOV_PATH}/lcov" "$@" --gcov-tool "${scripts}/llvm-cov-wrapper.sh" 22 | } 23 | -------------------------------------------------------------------------------- /exportenv.sh: -------------------------------------------------------------------------------- 1 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 2 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 3 | 4 | scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | export | egrep '( BUILT_PRODUCTS_DIR)|(CURRENT_ARCH)|(OBJECT_FILE_DIR_normal)|(SRCROOT)|(OBJROOT)' > "${scripts}/env.sh" 6 | -------------------------------------------------------------------------------- /getcov: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 3 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 4 | 5 | usage() { 6 | echo "usage: getcov [[-s] [-x] [-xc] [-o output_dir] [-i info_file] [-v]] | [-h]]" 7 | } 8 | 9 | main() { 10 | scripts="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 11 | source "${scripts}/envcov.sh" 12 | 13 | LCOV_INFO=Coverage.info 14 | output_dir="${BUILT_PRODUCTS_DIR}" 15 | while [ "$1" != "" ]; do 16 | case $1 in 17 | -s|--show) 18 | show_html=1 19 | echo "Show HTML Report" 20 | ;; 21 | -x|--xml) 22 | generate_xml=1 23 | echo "Generate Cobertura XML" 24 | ;; 25 | -xc|--xmlclover) 26 | generate_xml=1 27 | generate_xml_clover=1 28 | echo "Generate Clover XML" 29 | ;; 30 | -o) 31 | shift 32 | output_dir=$1 33 | echo "output_dir = ${output_dir}" 34 | ;; 35 | -i) 36 | shift 37 | LCOV_INFO=$1 38 | echo "LCOV_INFO = ${LCOV_INFO}" 39 | ;; 40 | -v) 41 | verbose=1 42 | echo "Verbose" 43 | ;; 44 | -h|--help) 45 | usage 46 | echo "Show Help" 47 | exit 48 | ;; 49 | *) 50 | usage 51 | exit 1 52 | esac 53 | shift 54 | done 55 | 56 | if [ "$verbose" = "1" ]; then 57 | report_values 58 | fi 59 | 60 | remove_old_report 61 | enter_lcov_dir 62 | gather_coverage 63 | exclude_data 64 | 65 | if [ "$generate_xml" = "1" ]; then 66 | generate_cobertura_xml 67 | fi 68 | 69 | if [ "$generate_xml_clover" = "1" ]; then 70 | generate_clover_xml 71 | fi 72 | 73 | generate_html_report 74 | 75 | if [ "$show_html" = "1" ]; then 76 | show_html_report 77 | fi 78 | } 79 | 80 | report_values() { 81 | echo "XcodeCoverage: Environment" 82 | echo "scripts : ${scripts}" 83 | echo "output_dir : ${output_dir}" 84 | echo "LCOV_INFO : ${LCOV_INFO}" 85 | echo "BUILD_DIR : ${BUILT_PRODUCTS_DIR}" 86 | echo "SRCROOT : ${SRCROOT}" 87 | echo "OBJ_DIR : ${OBJ_DIR}" 88 | echo "LCOV_PATH : ${LCOV_PATH}" 89 | echo "IGNORED : ${XCODECOV_IGNORED_PATHS}" 90 | } 91 | 92 | remove_old_report() { 93 | if [ "$verbose" = "1" ]; then 94 | echo "XcodeCoverage: Removing old report" 95 | fi 96 | 97 | pushd "${output_dir}" 98 | if [ -e lcov ]; then 99 | rm -r lcov 100 | fi 101 | popd 102 | } 103 | 104 | enter_lcov_dir() { 105 | cd "${output_dir}" 106 | mkdir lcov 107 | cd lcov 108 | } 109 | 110 | gather_coverage() { 111 | if [ "$verbose" = "1" ]; then 112 | echo "XcodeCoverage: Gathering coverage" 113 | fi 114 | 115 | LCOV --capture --derive-func-data -b "${SRCROOT}" -d "${OBJ_DIR}" -o "${LCOV_INFO}" 116 | } 117 | 118 | exclude_data() { 119 | if [ "$verbose" = "1" ]; then 120 | echo "XcodeCoverage: Excluding data" 121 | fi 122 | 123 | LCOV --remove "${LCOV_INFO}" "Developer/SDKs/*" -d "${OBJ_DIR}" -o "${LCOV_INFO}" 124 | LCOV --remove "${LCOV_INFO}" "main.m" -d "${OBJ_DIR}" -o "${LCOV_INFO}" 125 | 126 | #Remove anything the .xcodecoverageignore file has specified should be ignored. 127 | (cat "${SRCROOT}/.xcodecoverageignore"; echo) | while read IGNORE_THIS; do 128 | #use eval to expand any of the variables and then pass them to the shell - this allows 129 | #use of wildcards in the variables. 130 | eval LCOV --remove "${LCOV_INFO}" "${IGNORE_THIS}" -d "${OBJ_DIR}" -o "${LCOV_INFO}" 131 | done 132 | } 133 | 134 | generate_cobertura_xml() { 135 | if [ "$verbose" = "1" ]; then 136 | echo "XcodeCoverage: Generating Cobertura XML" 137 | fi 138 | 139 | python "${scripts}/lcov_cobertura.py" ${LCOV_INFO} --base-dir "${SRCROOT}" --output "coverage.xml" 140 | } 141 | 142 | generate_html_report() { 143 | if [ "$verbose" = "1" ]; then 144 | echo "XcodeCoverage: Generating HTML report" 145 | fi 146 | 147 | "${LCOV_PATH}/genhtml" --output-directory . "${LCOV_INFO}" 148 | } 149 | 150 | generate_clover_xml () { 151 | if [ "$verbose" = "1" ]; then 152 | echo "XcodeCoverage: Generating Clover XML" 153 | fi 154 | xsltproc "${scripts}/transform.xslt" "coverage.xml" > "clover.xml" 155 | } 156 | 157 | show_html_report() { 158 | if [ "$verbose" = "1" ]; then 159 | echo "XcodeCoverage: Opening HTML report" 160 | fi 161 | 162 | open index.html 163 | } 164 | 165 | main "$@" 166 | -------------------------------------------------------------------------------- /lcov-1.14/.version: -------------------------------------------------------------------------------- 1 | VERSION=1.14 2 | RELEASE=1 3 | FULL=1.14 4 | -------------------------------------------------------------------------------- /lcov-1.14/COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /lcov-1.14/bin/copy_dates.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Usage: copy_dates.sh SOURCE TARGET 4 | # 5 | # For each file found in SOURCE, set the modification time of the copy of that 6 | # file in TARGET to either the time of the latest Git commit (if SOURCE contains 7 | # a Git repository and the file was not modified after the last commit), or the 8 | # modification time of the original file. 9 | 10 | SOURCE="$1" 11 | TARGET="$2" 12 | 13 | if [ -z "$SOURCE" -o -z "$TARGET" ] ; then 14 | echo "Usage: $0 SOURCE TARGET" >&2 15 | exit 1 16 | fi 17 | 18 | [ -d "$SOURCE/.git" ] ; NOGIT=$? 19 | 20 | echo "Copying modification/commit times from $SOURCE to $TARGET" 21 | 22 | cd "$SOURCE" || exit 1 23 | find * -type f | while read FILENAME ; do 24 | [ ! -e "$TARGET/$FILENAME" ] && continue 25 | 26 | # Copy modification time 27 | touch -m "$TARGET/$FILENAME" -r "$FILENAME" 28 | 29 | [ $NOGIT -eq 1 ] && continue # No Git 30 | git diff --quiet -- "$FILENAME" || continue # Modified 31 | git diff --quiet --cached -- "$FILENAME" || continue # Modified 32 | 33 | # Apply modification time from Git commit time 34 | TIME=$(git log --pretty=format:%cd -n 1 --date=iso -- "$FILENAME") 35 | [ -n "$TIME" ] && touch -m "$TARGET/$FILENAME" --date "$TIME" 36 | done 37 | -------------------------------------------------------------------------------- /lcov-1.14/bin/gendesc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # Copyright (c) International Business Machines Corp., 2002 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or (at 8 | # your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | # 20 | # gendesc 21 | # 22 | # This script creates a description file as understood by genhtml. 23 | # Input file format: 24 | # 25 | # For each test case: 26 | # 27 | # 28 | # 29 | # Actual description may consist of several lines. By default, output is 30 | # written to stdout. Test names consist of alphanumeric characters 31 | # including _ and -. 32 | # 33 | # 34 | # History: 35 | # 2002-09-02: created by Peter Oberparleiter 36 | # 37 | 38 | use strict; 39 | use warnings; 40 | use File::Basename; 41 | use Getopt::Long; 42 | use Cwd qw/abs_path/; 43 | 44 | 45 | # Constants 46 | our $tool_dir = abs_path(dirname($0)); 47 | our $lcov_version = "LCOV version 1.14"; 48 | our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; 49 | our $tool_name = basename($0); 50 | 51 | 52 | # Prototypes 53 | sub print_usage(*); 54 | sub gen_desc(); 55 | sub warn_handler($); 56 | sub die_handler($); 57 | 58 | 59 | # Global variables 60 | our $help; 61 | our $version; 62 | our $output_filename; 63 | our $input_filename; 64 | 65 | 66 | # 67 | # Code entry point 68 | # 69 | 70 | $SIG{__WARN__} = \&warn_handler; 71 | $SIG{__DIE__} = \&die_handler; 72 | 73 | # Parse command line options 74 | if (!GetOptions("output-filename=s" => \$output_filename, 75 | "version" =>\$version, 76 | "help|?" => \$help 77 | )) 78 | { 79 | print(STDERR "Use $tool_name --help to get usage information\n"); 80 | exit(1); 81 | } 82 | 83 | $input_filename = $ARGV[0]; 84 | 85 | # Check for help option 86 | if ($help) 87 | { 88 | print_usage(*STDOUT); 89 | exit(0); 90 | } 91 | 92 | # Check for version option 93 | if ($version) 94 | { 95 | print("$tool_name: $lcov_version\n"); 96 | exit(0); 97 | } 98 | 99 | 100 | # Check for input filename 101 | if (!$input_filename) 102 | { 103 | die("No input filename specified\n". 104 | "Use $tool_name --help to get usage information\n"); 105 | } 106 | 107 | # Do something 108 | gen_desc(); 109 | 110 | 111 | # 112 | # print_usage(handle) 113 | # 114 | # Write out command line usage information to given filehandle. 115 | # 116 | 117 | sub print_usage(*) 118 | { 119 | local *HANDLE = $_[0]; 120 | 121 | print(HANDLE < 143 | # TD: 144 | # 145 | # If defined, write output to OUTPUT_FILENAME, otherwise to stdout. 146 | # 147 | # Die on error. 148 | # 149 | 150 | sub gen_desc() 151 | { 152 | local *INPUT_HANDLE; 153 | local *OUTPUT_HANDLE; 154 | my $empty_line = "ignore"; 155 | 156 | open(INPUT_HANDLE, "<", $input_filename) 157 | or die("ERROR: cannot open $input_filename!\n"); 158 | 159 | # Open output file for writing 160 | if ($output_filename) 161 | { 162 | open(OUTPUT_HANDLE, ">", $output_filename) 163 | or die("ERROR: cannot create $output_filename!\n"); 164 | } 165 | else 166 | { 167 | *OUTPUT_HANDLE = *STDOUT; 168 | } 169 | 170 | # Process all lines in input file 171 | while () 172 | { 173 | chomp($_); 174 | 175 | if (/^(\w[\w-]*)(\s*)$/) 176 | { 177 | # Matched test name 178 | # Name starts with alphanum or _, continues with 179 | # alphanum, _ or - 180 | print(OUTPUT_HANDLE "TN: $1\n"); 181 | $empty_line = "ignore"; 182 | } 183 | elsif (/^(\s+)(\S.*?)\s*$/) 184 | { 185 | # Matched test description 186 | if ($empty_line eq "insert") 187 | { 188 | # Write preserved empty line 189 | print(OUTPUT_HANDLE "TD: \n"); 190 | } 191 | print(OUTPUT_HANDLE "TD: $2\n"); 192 | $empty_line = "observe"; 193 | } 194 | elsif (/^\s*$/) 195 | { 196 | # Matched empty line to preserve paragraph separation 197 | # inside description text 198 | if ($empty_line eq "observe") 199 | { 200 | $empty_line = "insert"; 201 | } 202 | } 203 | } 204 | 205 | # Close output file if defined 206 | if ($output_filename) 207 | { 208 | close(OUTPUT_HANDLE); 209 | } 210 | 211 | close(INPUT_HANDLE); 212 | } 213 | 214 | sub warn_handler($) 215 | { 216 | my ($msg) = @_; 217 | 218 | warn("$tool_name: $msg"); 219 | } 220 | 221 | sub die_handler($) 222 | { 223 | my ($msg) = @_; 224 | 225 | die("$tool_name: $msg"); 226 | } 227 | -------------------------------------------------------------------------------- /lcov-1.14/bin/geninfo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # Copyright (c) International Business Machines Corp., 2002,2012 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or (at 8 | # your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | # 20 | # geninfo 21 | # 22 | # This script generates .info files from data files as created by code 23 | # instrumented with gcc's built-in profiling mechanism. Call it with 24 | # --help and refer to the geninfo man page to get information on usage 25 | # and available options. 26 | # 27 | # 28 | # Authors: 29 | # 2002-08-23 created by Peter Oberparleiter 30 | # IBM Lab Boeblingen 31 | # based on code by Manoj Iyer and 32 | # Megan Bock 33 | # IBM Austin 34 | # 2002-09-05 / Peter Oberparleiter: implemented option that allows file list 35 | # 2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also 36 | # parse the new gcov format which is to be introduced in gcc 3.3 37 | # 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT 38 | # 2003-07-03 / Peter Oberparleiter: added line checksum support, added 39 | # --no-checksum 40 | # 2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV 41 | # 2003-12-11 / Laurent Deniel: added --follow option 42 | # workaround gcov (<= 3.2.x) bug with empty .da files 43 | # 2004-01-03 / Laurent Deniel: Ignore empty .bb files 44 | # 2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and 45 | # gcov versioning 46 | # 2004-08-09 / Peter Oberparleiter: added configuration file support 47 | # 2008-07-14 / Tom Zoerner: added --function-coverage command line option 48 | # 2008-08-13 / Peter Oberparleiter: modified function coverage 49 | # implementation (now enabled per default) 50 | # 51 | 52 | use strict; 53 | use warnings; 54 | use File::Basename; 55 | use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir 56 | splitpath catpath/; 57 | use Getopt::Long; 58 | use Digest::MD5 qw(md5_base64); 59 | use Cwd qw/abs_path/; 60 | if( $^O eq "msys" ) 61 | { 62 | require File::Spec::Win32; 63 | } 64 | 65 | # Constants 66 | our $tool_dir = abs_path(dirname($0)); 67 | our $lcov_version = "LCOV version 1.14"; 68 | our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; 69 | our $gcov_tool = "gcov"; 70 | our $tool_name = basename($0); 71 | 72 | our $GCOV_VERSION_8_0_0 = 0x80000; 73 | our $GCOV_VERSION_4_7_0 = 0x40700; 74 | our $GCOV_VERSION_3_4_0 = 0x30400; 75 | our $GCOV_VERSION_3_3_0 = 0x30300; 76 | our $GCNO_FUNCTION_TAG = 0x01000000; 77 | our $GCNO_LINES_TAG = 0x01450000; 78 | our $GCNO_FILE_MAGIC = 0x67636e6f; 79 | our $BBG_FILE_MAGIC = 0x67626267; 80 | 81 | # Error classes which users may specify to ignore during processing 82 | our $ERROR_GCOV = 0; 83 | our $ERROR_SOURCE = 1; 84 | our $ERROR_GRAPH = 2; 85 | our %ERROR_ID = ( 86 | "gcov" => $ERROR_GCOV, 87 | "source" => $ERROR_SOURCE, 88 | "graph" => $ERROR_GRAPH, 89 | ); 90 | 91 | our $EXCL_START = "LCOV_EXCL_START"; 92 | our $EXCL_STOP = "LCOV_EXCL_STOP"; 93 | 94 | # Marker to exclude branch coverage but keep function and line coveage 95 | our $EXCL_BR_START = "LCOV_EXCL_BR_START"; 96 | our $EXCL_BR_STOP = "LCOV_EXCL_BR_STOP"; 97 | 98 | # Compatibility mode values 99 | our $COMPAT_VALUE_OFF = 0; 100 | our $COMPAT_VALUE_ON = 1; 101 | our $COMPAT_VALUE_AUTO = 2; 102 | 103 | # Compatibility mode value names 104 | our %COMPAT_NAME_TO_VALUE = ( 105 | "off" => $COMPAT_VALUE_OFF, 106 | "on" => $COMPAT_VALUE_ON, 107 | "auto" => $COMPAT_VALUE_AUTO, 108 | ); 109 | 110 | # Compatiblity modes 111 | our $COMPAT_MODE_LIBTOOL = 1 << 0; 112 | our $COMPAT_MODE_HAMMER = 1 << 1; 113 | our $COMPAT_MODE_SPLIT_CRC = 1 << 2; 114 | 115 | # Compatibility mode names 116 | our %COMPAT_NAME_TO_MODE = ( 117 | "libtool" => $COMPAT_MODE_LIBTOOL, 118 | "hammer" => $COMPAT_MODE_HAMMER, 119 | "split_crc" => $COMPAT_MODE_SPLIT_CRC, 120 | "android_4_4_0" => $COMPAT_MODE_SPLIT_CRC, 121 | ); 122 | 123 | # Map modes to names 124 | our %COMPAT_MODE_TO_NAME = ( 125 | $COMPAT_MODE_LIBTOOL => "libtool", 126 | $COMPAT_MODE_HAMMER => "hammer", 127 | $COMPAT_MODE_SPLIT_CRC => "split_crc", 128 | ); 129 | 130 | # Compatibility mode default values 131 | our %COMPAT_MODE_DEFAULTS = ( 132 | $COMPAT_MODE_LIBTOOL => $COMPAT_VALUE_ON, 133 | $COMPAT_MODE_HAMMER => $COMPAT_VALUE_AUTO, 134 | $COMPAT_MODE_SPLIT_CRC => $COMPAT_VALUE_AUTO, 135 | ); 136 | 137 | # Compatibility mode auto-detection routines 138 | sub compat_hammer_autodetect(); 139 | our %COMPAT_MODE_AUTO = ( 140 | $COMPAT_MODE_HAMMER => \&compat_hammer_autodetect, 141 | $COMPAT_MODE_SPLIT_CRC => 1, # will be done later 142 | ); 143 | 144 | our $BR_LINE = 0; 145 | our $BR_BLOCK = 1; 146 | our $BR_BRANCH = 2; 147 | our $BR_TAKEN = 3; 148 | our $BR_VEC_ENTRIES = 4; 149 | our $BR_VEC_WIDTH = 32; 150 | our $BR_VEC_MAX = vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH); 151 | 152 | our $UNNAMED_BLOCK = -1; 153 | 154 | # Prototypes 155 | sub print_usage(*); 156 | sub transform_pattern($); 157 | sub gen_info($); 158 | sub process_dafile($$); 159 | sub match_filename($@); 160 | sub solve_ambiguous_match($$$); 161 | sub split_filename($); 162 | sub solve_relative_path($$); 163 | sub read_gcov_header($); 164 | sub read_gcov_file($); 165 | sub info(@); 166 | sub map_llvm_version($); 167 | sub version_to_str($); 168 | sub get_gcov_version(); 169 | sub system_no_output($@); 170 | sub read_config($); 171 | sub apply_config($); 172 | sub get_exclusion_data($); 173 | sub apply_exclusion_data($$); 174 | sub process_graphfile($$); 175 | sub filter_fn_name($); 176 | sub warn_handler($); 177 | sub die_handler($); 178 | sub graph_error($$); 179 | sub graph_expect($); 180 | sub graph_read(*$;$$); 181 | sub graph_skip(*$;$); 182 | sub uniq(@); 183 | sub sort_uniq(@); 184 | sub sort_uniq_lex(@); 185 | sub graph_cleanup($); 186 | sub graph_find_base($); 187 | sub graph_from_bb($$$$); 188 | sub graph_add_order($$$); 189 | sub read_bb_word(*;$); 190 | sub read_bb_value(*;$); 191 | sub read_bb_string(*$); 192 | sub read_bb($); 193 | sub read_bbg_word(*;$); 194 | sub read_bbg_value(*;$); 195 | sub read_bbg_string(*); 196 | sub read_bbg_lines_record(*$$$$$); 197 | sub read_bbg($); 198 | sub read_gcno_word(*;$$); 199 | sub read_gcno_value(*$;$$); 200 | sub read_gcno_string(*$); 201 | sub read_gcno_lines_record(*$$$$$$); 202 | sub determine_gcno_split_crc($$$$); 203 | sub read_gcno_function_record(*$$$$$); 204 | sub read_gcno($); 205 | sub get_gcov_capabilities(); 206 | sub get_overall_line($$$$); 207 | sub print_overall_rate($$$$$$$$$); 208 | sub br_gvec_len($); 209 | sub br_gvec_get($$); 210 | sub debug($); 211 | sub int_handler(); 212 | sub parse_ignore_errors(@); 213 | sub is_external($); 214 | sub compat_name($); 215 | sub parse_compat_modes($); 216 | sub is_compat($); 217 | sub is_compat_auto($); 218 | 219 | 220 | # Global variables 221 | our $gcov_version; 222 | our $gcov_version_string; 223 | our $graph_file_extension; 224 | our $data_file_extension; 225 | our @data_directory; 226 | our $test_name = ""; 227 | our $quiet; 228 | our $help; 229 | our $output_filename; 230 | our $base_directory; 231 | our $version; 232 | our $follow; 233 | our $checksum; 234 | our $no_checksum; 235 | our $opt_compat_libtool; 236 | our $opt_no_compat_libtool; 237 | our $rc_adjust_src_path;# Regexp specifying parts to remove from source path 238 | our $adjust_src_pattern; 239 | our $adjust_src_replace; 240 | our $adjust_testname; 241 | our $config; # Configuration file contents 242 | our @ignore_errors; # List of errors to ignore (parameter) 243 | our @ignore; # List of errors to ignore (array) 244 | our $initial; 245 | our @include_patterns; # List of source file patterns to include 246 | our @exclude_patterns; # List of source file patterns to exclude 247 | our %excluded_files; # Files excluded due to include/exclude options 248 | our $no_recursion = 0; 249 | our $maxdepth; 250 | our $no_markers = 0; 251 | our $opt_derive_func_data = 0; 252 | our $opt_external = 1; 253 | our $opt_no_external; 254 | our $debug = 0; 255 | our $gcov_caps; 256 | our @gcov_options; 257 | our @internal_dirs; 258 | our $opt_config_file; 259 | our $opt_gcov_all_blocks = 1; 260 | our $opt_compat; 261 | our %opt_rc; 262 | our %compat_value; 263 | our $gcno_split_crc; 264 | our $func_coverage = 1; 265 | our $br_coverage = 0; 266 | our $rc_auto_base = 1; 267 | our $excl_line = "LCOV_EXCL_LINE"; 268 | our $excl_br_line = "LCOV_EXCL_BR_LINE"; 269 | 270 | our $cwd = `pwd`; 271 | chomp($cwd); 272 | 273 | 274 | # 275 | # Code entry point 276 | # 277 | 278 | # Register handler routine to be called when interrupted 279 | $SIG{"INT"} = \&int_handler; 280 | $SIG{__WARN__} = \&warn_handler; 281 | $SIG{__DIE__} = \&die_handler; 282 | 283 | # Set LC_ALL so that gcov output will be in a unified format 284 | $ENV{"LC_ALL"} = "C"; 285 | 286 | # Check command line for a configuration file name 287 | Getopt::Long::Configure("pass_through", "no_auto_abbrev"); 288 | GetOptions("config-file=s" => \$opt_config_file, 289 | "rc=s%" => \%opt_rc); 290 | Getopt::Long::Configure("default"); 291 | 292 | { 293 | # Remove spaces around rc options 294 | my %new_opt_rc; 295 | 296 | while (my ($key, $value) = each(%opt_rc)) { 297 | $key =~ s/^\s+|\s+$//g; 298 | $value =~ s/^\s+|\s+$//g; 299 | 300 | $new_opt_rc{$key} = $value; 301 | } 302 | %opt_rc = %new_opt_rc; 303 | } 304 | 305 | # Read configuration file if available 306 | if (defined($opt_config_file)) { 307 | $config = read_config($opt_config_file); 308 | } elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) 309 | { 310 | $config = read_config($ENV{"HOME"}."/.lcovrc"); 311 | } 312 | elsif (-r "/etc/lcovrc") 313 | { 314 | $config = read_config("/etc/lcovrc"); 315 | } elsif (-r "/usr/local/etc/lcovrc") 316 | { 317 | $config = read_config("/usr/local/etc/lcovrc"); 318 | } 319 | 320 | if ($config || %opt_rc) 321 | { 322 | # Copy configuration file and --rc values to variables 323 | apply_config({ 324 | "geninfo_gcov_tool" => \$gcov_tool, 325 | "geninfo_adjust_testname" => \$adjust_testname, 326 | "geninfo_checksum" => \$checksum, 327 | "geninfo_no_checksum" => \$no_checksum, # deprecated 328 | "geninfo_compat_libtool" => \$opt_compat_libtool, 329 | "geninfo_external" => \$opt_external, 330 | "geninfo_gcov_all_blocks" => \$opt_gcov_all_blocks, 331 | "geninfo_compat" => \$opt_compat, 332 | "geninfo_adjust_src_path" => \$rc_adjust_src_path, 333 | "geninfo_auto_base" => \$rc_auto_base, 334 | "lcov_function_coverage" => \$func_coverage, 335 | "lcov_branch_coverage" => \$br_coverage, 336 | "lcov_excl_line" => \$excl_line, 337 | "lcov_excl_br_line" => \$excl_br_line, 338 | }); 339 | 340 | # Merge options 341 | if (defined($no_checksum)) 342 | { 343 | $checksum = ($no_checksum ? 0 : 1); 344 | $no_checksum = undef; 345 | } 346 | 347 | # Check regexp 348 | if (defined($rc_adjust_src_path)) { 349 | my ($pattern, $replace) = split(/\s*=>\s*/, 350 | $rc_adjust_src_path); 351 | local $SIG{__DIE__}; 352 | eval '$adjust_src_pattern = qr>'.$pattern.'>;'; 353 | if (!defined($adjust_src_pattern)) { 354 | my $msg = $@; 355 | 356 | chomp($msg); 357 | $msg =~ s/at \(eval.*$//; 358 | warn("WARNING: invalid pattern in ". 359 | "geninfo_adjust_src_path: $msg\n"); 360 | } elsif (!defined($replace)) { 361 | # If no replacement is specified, simply remove pattern 362 | $adjust_src_replace = ""; 363 | } else { 364 | $adjust_src_replace = $replace; 365 | } 366 | } 367 | for my $regexp (($excl_line, $excl_br_line)) { 368 | eval 'qr/'.$regexp.'/'; 369 | my $error = $@; 370 | chomp($error); 371 | $error =~ s/at \(eval.*$//; 372 | die("ERROR: invalid exclude pattern: $error") if $error; 373 | } 374 | } 375 | 376 | # Parse command line options 377 | if (!GetOptions("test-name|t=s" => \$test_name, 378 | "output-filename|o=s" => \$output_filename, 379 | "checksum" => \$checksum, 380 | "no-checksum" => \$no_checksum, 381 | "base-directory|b=s" => \$base_directory, 382 | "version|v" =>\$version, 383 | "quiet|q" => \$quiet, 384 | "help|h|?" => \$help, 385 | "follow|f" => \$follow, 386 | "compat-libtool" => \$opt_compat_libtool, 387 | "no-compat-libtool" => \$opt_no_compat_libtool, 388 | "gcov-tool=s" => \$gcov_tool, 389 | "ignore-errors=s" => \@ignore_errors, 390 | "initial|i" => \$initial, 391 | "include=s" => \@include_patterns, 392 | "exclude=s" => \@exclude_patterns, 393 | "no-recursion" => \$no_recursion, 394 | "no-markers" => \$no_markers, 395 | "derive-func-data" => \$opt_derive_func_data, 396 | "debug" => \$debug, 397 | "external|e" => \$opt_external, 398 | "no-external" => \$opt_no_external, 399 | "compat=s" => \$opt_compat, 400 | "config-file=s" => \$opt_config_file, 401 | "rc=s%" => \%opt_rc, 402 | )) 403 | { 404 | print(STDERR "Use $tool_name --help to get usage information\n"); 405 | exit(1); 406 | } 407 | else 408 | { 409 | # Merge options 410 | if (defined($no_checksum)) 411 | { 412 | $checksum = ($no_checksum ? 0 : 1); 413 | $no_checksum = undef; 414 | } 415 | 416 | if (defined($opt_no_compat_libtool)) 417 | { 418 | $opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1); 419 | $opt_no_compat_libtool = undef; 420 | } 421 | 422 | if (defined($opt_no_external)) { 423 | $opt_external = 0; 424 | $opt_no_external = undef; 425 | } 426 | 427 | if(@include_patterns) { 428 | # Need perlreg expressions instead of shell pattern 429 | @include_patterns = map({ transform_pattern($_); } @include_patterns); 430 | } 431 | 432 | if(@exclude_patterns) { 433 | # Need perlreg expressions instead of shell pattern 434 | @exclude_patterns = map({ transform_pattern($_); } @exclude_patterns); 435 | } 436 | } 437 | 438 | @data_directory = @ARGV; 439 | 440 | debug("$lcov_version\n"); 441 | 442 | # Check for help option 443 | if ($help) 444 | { 445 | print_usage(*STDOUT); 446 | exit(0); 447 | } 448 | 449 | # Check for version option 450 | if ($version) 451 | { 452 | print("$tool_name: $lcov_version\n"); 453 | exit(0); 454 | } 455 | 456 | # Check gcov tool 457 | if (system_no_output(3, $gcov_tool, "--help") == -1) 458 | { 459 | die("ERROR: need tool $gcov_tool!\n"); 460 | } 461 | 462 | ($gcov_version, $gcov_version_string) = get_gcov_version(); 463 | 464 | # Determine gcov options 465 | $gcov_caps = get_gcov_capabilities(); 466 | push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} && 467 | ($br_coverage || $func_coverage)); 468 | push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} && 469 | $br_coverage); 470 | push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} && 471 | $opt_gcov_all_blocks && $br_coverage); 472 | if ($gcov_caps->{'hash-filenames'}) 473 | { 474 | push(@gcov_options, "-x"); 475 | } else { 476 | push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); 477 | } 478 | 479 | # Determine compatibility modes 480 | parse_compat_modes($opt_compat); 481 | 482 | # Determine which errors the user wants us to ignore 483 | parse_ignore_errors(@ignore_errors); 484 | 485 | # Make sure test names only contain valid characters 486 | if ($test_name =~ s/\W/_/g) 487 | { 488 | warn("WARNING: invalid characters removed from testname!\n"); 489 | } 490 | 491 | # Adjust test name to include uname output if requested 492 | if ($adjust_testname) 493 | { 494 | $test_name .= "__".`uname -a`; 495 | $test_name =~ s/\W/_/g; 496 | } 497 | 498 | # Make sure base_directory contains an absolute path specification 499 | if ($base_directory) 500 | { 501 | $base_directory = solve_relative_path($cwd, $base_directory); 502 | } 503 | 504 | # Check for follow option 505 | if ($follow) 506 | { 507 | $follow = "-follow" 508 | } 509 | else 510 | { 511 | $follow = ""; 512 | } 513 | 514 | # Determine checksum mode 515 | if (defined($checksum)) 516 | { 517 | # Normalize to boolean 518 | $checksum = ($checksum ? 1 : 0); 519 | } 520 | else 521 | { 522 | # Default is off 523 | $checksum = 0; 524 | } 525 | 526 | # Determine max depth for recursion 527 | if ($no_recursion) 528 | { 529 | $maxdepth = "-maxdepth 1"; 530 | } 531 | else 532 | { 533 | $maxdepth = ""; 534 | } 535 | 536 | # Check for directory name 537 | if (!@data_directory) 538 | { 539 | die("No directory specified\n". 540 | "Use $tool_name --help to get usage information\n"); 541 | } 542 | else 543 | { 544 | foreach (@data_directory) 545 | { 546 | stat($_); 547 | if (!-r _) 548 | { 549 | die("ERROR: cannot read $_!\n"); 550 | } 551 | } 552 | } 553 | 554 | if ($gcov_version < $GCOV_VERSION_3_4_0) 555 | { 556 | if (is_compat($COMPAT_MODE_HAMMER)) 557 | { 558 | $data_file_extension = ".da"; 559 | $graph_file_extension = ".bbg"; 560 | } 561 | else 562 | { 563 | $data_file_extension = ".da"; 564 | $graph_file_extension = ".bb"; 565 | } 566 | } 567 | else 568 | { 569 | $data_file_extension = ".gcda"; 570 | $graph_file_extension = ".gcno"; 571 | } 572 | 573 | # Check output filename 574 | if (defined($output_filename) && ($output_filename ne "-")) 575 | { 576 | # Initially create output filename, data is appended 577 | # for each data file processed 578 | local *DUMMY_HANDLE; 579 | open(DUMMY_HANDLE, ">", $output_filename) 580 | or die("ERROR: cannot create $output_filename!\n"); 581 | close(DUMMY_HANDLE); 582 | 583 | # Make $output_filename an absolute path because we're going 584 | # to change directories while processing files 585 | if (!($output_filename =~ /^\/(.*)$/)) 586 | { 587 | $output_filename = $cwd."/".$output_filename; 588 | } 589 | } 590 | 591 | # Build list of directories to identify external files 592 | foreach my $entry(@data_directory, $base_directory) { 593 | next if (!defined($entry)); 594 | push(@internal_dirs, solve_relative_path($cwd, $entry)); 595 | } 596 | 597 | # Do something 598 | foreach my $entry (@data_directory) { 599 | gen_info($entry); 600 | } 601 | 602 | if ($initial && $br_coverage) { 603 | warn("Note: --initial does not generate branch coverage ". 604 | "data\n"); 605 | } 606 | info("Finished .info-file creation\n"); 607 | 608 | exit(0); 609 | 610 | 611 | 612 | # 613 | # print_usage(handle) 614 | # 615 | # Print usage information. 616 | # 617 | 618 | sub print_usage(*) 619 | { 620 | local *HANDLE = $_[0]; 621 | 622 | print(HANDLE < (.) and * => (.*) 687 | 688 | $pattern =~ s/\*/\(\.\*\)/g; 689 | $pattern =~ s/\?/\(\.\)/g; 690 | 691 | return $pattern; 692 | } 693 | 694 | 695 | # 696 | # get_common_prefix(min_dir, filenames) 697 | # 698 | # Return the longest path prefix shared by all filenames. MIN_DIR specifies 699 | # the minimum number of directories that a filename may have after removing 700 | # the prefix. 701 | # 702 | 703 | sub get_common_prefix($@) 704 | { 705 | my ($min_dir, @files) = @_; 706 | my $file; 707 | my @prefix; 708 | my $i; 709 | 710 | foreach $file (@files) { 711 | my ($v, $d, $f) = splitpath($file); 712 | my @comp = splitdir($d); 713 | 714 | if (!@prefix) { 715 | @prefix = @comp; 716 | next; 717 | } 718 | for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) { 719 | if ($comp[$i] ne $prefix[$i] || 720 | ((scalar(@comp) - ($i + 1)) <= $min_dir)) { 721 | delete(@prefix[$i..scalar(@prefix)]); 722 | last; 723 | } 724 | } 725 | } 726 | 727 | return catdir(@prefix); 728 | } 729 | 730 | # 731 | # gen_info(directory) 732 | # 733 | # Traverse DIRECTORY and create a .info file for each data file found. 734 | # The .info file contains TEST_NAME in the following format: 735 | # 736 | # TN: 737 | # 738 | # For each source file name referenced in the data file, there is a section 739 | # containing source code and coverage data: 740 | # 741 | # SF: 742 | # FN:, for each function 743 | # DA:, for each instrumented line 744 | # LH: greater than 0 745 | # LF: 746 | # 747 | # Sections are separated by: 748 | # 749 | # end_of_record 750 | # 751 | # In addition to the main source code file there are sections for each 752 | # #included file containing executable code. Note that the absolute path 753 | # of a source file is generated by interpreting the contents of the respective 754 | # graph file. Relative filenames are prefixed with the directory in which the 755 | # graph file is found. Note also that symbolic links to the graph file will be 756 | # resolved so that the actual file path is used instead of the path to a link. 757 | # This approach is necessary for the mechanism to work with the /proc/gcov 758 | # files. 759 | # 760 | # Die on error. 761 | # 762 | 763 | sub gen_info($) 764 | { 765 | my $directory = $_[0]; 766 | my @file_list; 767 | my $file; 768 | my $prefix; 769 | my $type; 770 | my $ext; 771 | 772 | if ($initial) { 773 | $type = "graph"; 774 | $ext = $graph_file_extension; 775 | } else { 776 | $type = "data"; 777 | $ext = $data_file_extension; 778 | } 779 | 780 | if (-d $directory) 781 | { 782 | info("Scanning $directory for $ext files ...\n"); 783 | 784 | @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f -o -name \\*$ext -type l 2>/dev/null`; 785 | chomp(@file_list); 786 | if (!@file_list) { 787 | warn("WARNING: no $ext files found in $directory - ". 788 | "skipping!\n"); 789 | return; 790 | } 791 | $prefix = get_common_prefix(1, @file_list); 792 | info("Found %d %s files in %s\n", $#file_list+1, $type, 793 | $directory); 794 | } 795 | else 796 | { 797 | @file_list = ($directory); 798 | $prefix = ""; 799 | } 800 | 801 | # Process all files in list 802 | foreach $file (@file_list) { 803 | # Process file 804 | if ($initial) { 805 | process_graphfile($file, $prefix); 806 | } else { 807 | process_dafile($file, $prefix); 808 | } 809 | } 810 | 811 | # Report whether files were excluded. 812 | if (%excluded_files) { 813 | info("Excluded data for %d files due to include/exclude options\n", 814 | scalar keys %excluded_files); 815 | } 816 | } 817 | 818 | 819 | # 820 | # derive_data(contentdata, funcdata, bbdata) 821 | # 822 | # Calculate function coverage data by combining line coverage data and the 823 | # list of lines belonging to a function. 824 | # 825 | # contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ] 826 | # instr: Instrumentation flag for line n 827 | # count: Execution count for line n 828 | # source: Source code for line n 829 | # 830 | # funcdata: [ count1, func1, count2, func2, ... ] 831 | # count: Execution count for function number n 832 | # func: Function name for function number n 833 | # 834 | # bbdata: function_name -> [ line1, line2, ... ] 835 | # line: Line number belonging to the corresponding function 836 | # 837 | 838 | sub derive_data($$$) 839 | { 840 | my ($contentdata, $funcdata, $bbdata) = @_; 841 | my @gcov_content = @{$contentdata}; 842 | my @gcov_functions = @{$funcdata}; 843 | my %fn_count; 844 | my %ln_fn; 845 | my $line; 846 | my $maxline; 847 | my %fn_name; 848 | my $fn; 849 | my $count; 850 | 851 | if (!defined($bbdata)) { 852 | return @gcov_functions; 853 | } 854 | 855 | # First add existing function data 856 | while (@gcov_functions) { 857 | $count = shift(@gcov_functions); 858 | $fn = shift(@gcov_functions); 859 | 860 | $fn_count{$fn} = $count; 861 | } 862 | 863 | # Convert line coverage data to function data 864 | foreach $fn (keys(%{$bbdata})) { 865 | my $line_data = $bbdata->{$fn}; 866 | my $line; 867 | my $fninstr = 0; 868 | 869 | if ($fn eq "") { 870 | next; 871 | } 872 | # Find the lowest line count for this function 873 | $count = 0; 874 | foreach $line (@$line_data) { 875 | my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ]; 876 | my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ]; 877 | 878 | next if (!$linstr); 879 | $fninstr = 1; 880 | if (($lcount > 0) && 881 | (($count == 0) || ($lcount < $count))) { 882 | $count = $lcount; 883 | } 884 | } 885 | next if (!$fninstr); 886 | $fn_count{$fn} = $count; 887 | } 888 | 889 | 890 | # Check if we got data for all functions 891 | foreach $fn (keys(%fn_name)) { 892 | if ($fn eq "") { 893 | next; 894 | } 895 | if (defined($fn_count{$fn})) { 896 | next; 897 | } 898 | warn("WARNING: no derived data found for function $fn\n"); 899 | } 900 | 901 | # Convert hash to list in @gcov_functions format 902 | foreach $fn (sort(keys(%fn_count))) { 903 | push(@gcov_functions, $fn_count{$fn}, $fn); 904 | } 905 | 906 | return @gcov_functions; 907 | } 908 | 909 | # 910 | # get_filenames(directory, pattern) 911 | # 912 | # Return a list of filenames found in directory which match the specified 913 | # pattern. 914 | # 915 | # Die on error. 916 | # 917 | 918 | sub get_filenames($$) 919 | { 920 | my ($dirname, $pattern) = @_; 921 | my @result; 922 | my $directory; 923 | local *DIR; 924 | 925 | opendir(DIR, $dirname) or 926 | die("ERROR: cannot read directory $dirname\n"); 927 | while ($directory = readdir(DIR)) { 928 | push(@result, $directory) if ($directory =~ /$pattern/); 929 | } 930 | closedir(DIR); 931 | 932 | return @result; 933 | } 934 | 935 | # 936 | # process_dafile(da_filename, dir) 937 | # 938 | # Create a .info file for a single data file. 939 | # 940 | # Die on error. 941 | # 942 | 943 | sub process_dafile($$) 944 | { 945 | my ($file, $dir) = @_; 946 | my $da_filename; # Name of data file to process 947 | my $da_dir; # Directory of data file 948 | my $source_dir; # Directory of source file 949 | my $da_basename; # data filename without ".da/.gcda" extension 950 | my $bb_filename; # Name of respective graph file 951 | my $bb_basename; # Basename of the original graph file 952 | my $graph; # Contents of graph file 953 | my $instr; # Contents of graph file part 2 954 | my $gcov_error; # Error code of gcov tool 955 | my $object_dir; # Directory containing all object files 956 | my $source_filename; # Name of a source code file 957 | my $gcov_file; # Name of a .gcov file 958 | my @gcov_content; # Content of a .gcov file 959 | my $gcov_branches; # Branch content of a .gcov file 960 | my @gcov_functions; # Function calls of a .gcov file 961 | my @gcov_list; # List of generated .gcov files 962 | my $line_number; # Line number count 963 | my $lines_hit; # Number of instrumented lines hit 964 | my $lines_found; # Number of instrumented lines found 965 | my $funcs_hit; # Number of instrumented functions hit 966 | my $funcs_found; # Number of instrumented functions found 967 | my $br_hit; 968 | my $br_found; 969 | my $source; # gcov source header information 970 | my $object; # gcov object header information 971 | my @matches; # List of absolute paths matching filename 972 | my $base_dir; # Base directory for current file 973 | my @tmp_links; # Temporary links to be cleaned up 974 | my @result; 975 | my $index; 976 | my $da_renamed; # If data file is to be renamed 977 | local *INFO_HANDLE; 978 | 979 | info("Processing %s\n", abs2rel($file, $dir)); 980 | # Get path to data file in absolute and normalized form (begins with /, 981 | # contains no more ../ or ./) 982 | $da_filename = solve_relative_path($cwd, $file); 983 | 984 | # Get directory and basename of data file 985 | ($da_dir, $da_basename) = split_filename($da_filename); 986 | 987 | $source_dir = $da_dir; 988 | if (is_compat($COMPAT_MODE_LIBTOOL)) { 989 | # Avoid files from .libs dirs 990 | $source_dir =~ s/\.libs$//; 991 | } 992 | 993 | if (-z $da_filename) 994 | { 995 | $da_renamed = 1; 996 | } 997 | else 998 | { 999 | $da_renamed = 0; 1000 | } 1001 | 1002 | # Construct base_dir for current file 1003 | if ($base_directory) 1004 | { 1005 | $base_dir = $base_directory; 1006 | } 1007 | else 1008 | { 1009 | $base_dir = $source_dir; 1010 | } 1011 | 1012 | # Check for writable $base_dir (gcov will try to write files there) 1013 | stat($base_dir); 1014 | if (!-w _) 1015 | { 1016 | die("ERROR: cannot write to directory $base_dir!\n"); 1017 | } 1018 | 1019 | # Construct name of graph file 1020 | $bb_basename = $da_basename.$graph_file_extension; 1021 | $bb_filename = "$da_dir/$bb_basename"; 1022 | 1023 | # Find out the real location of graph file in case we're just looking at 1024 | # a link 1025 | while (readlink($bb_filename)) 1026 | { 1027 | my $last_dir = dirname($bb_filename); 1028 | 1029 | $bb_filename = readlink($bb_filename); 1030 | $bb_filename = solve_relative_path($last_dir, $bb_filename); 1031 | } 1032 | 1033 | # Ignore empty graph file (e.g. source file with no statement) 1034 | if (-z $bb_filename) 1035 | { 1036 | warn("WARNING: empty $bb_filename (skipped)\n"); 1037 | return; 1038 | } 1039 | 1040 | # Read contents of graph file into hash. We need it later to find out 1041 | # the absolute path to each .gcov file created as well as for 1042 | # information about functions and their source code positions. 1043 | if ($gcov_version < $GCOV_VERSION_3_4_0) 1044 | { 1045 | if (is_compat($COMPAT_MODE_HAMMER)) 1046 | { 1047 | ($instr, $graph) = read_bbg($bb_filename); 1048 | } 1049 | else 1050 | { 1051 | ($instr, $graph) = read_bb($bb_filename); 1052 | } 1053 | } 1054 | else 1055 | { 1056 | ($instr, $graph) = read_gcno($bb_filename); 1057 | } 1058 | 1059 | # Try to find base directory automatically if requested by user 1060 | if ($rc_auto_base) { 1061 | $base_dir = find_base_from_graph($base_dir, $instr, $graph); 1062 | } 1063 | 1064 | ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); 1065 | 1066 | # Set $object_dir to real location of object files. This may differ 1067 | # from $da_dir if the graph file is just a link to the "real" object 1068 | # file location. 1069 | $object_dir = dirname($bb_filename); 1070 | 1071 | # Is the data file in a different directory? (this happens e.g. with 1072 | # the gcov-kernel patch) 1073 | if ($object_dir ne $da_dir) 1074 | { 1075 | # Need to create link to data file in $object_dir 1076 | system("ln", "-s", $da_filename, 1077 | "$object_dir/$da_basename$data_file_extension") 1078 | and die ("ERROR: cannot create link $object_dir/". 1079 | "$da_basename$data_file_extension!\n"); 1080 | push(@tmp_links, 1081 | "$object_dir/$da_basename$data_file_extension"); 1082 | # Need to create link to graph file if basename of link 1083 | # and file are different (CONFIG_MODVERSION compat) 1084 | if ((basename($bb_filename) ne $bb_basename) && 1085 | (! -e "$object_dir/$bb_basename")) { 1086 | symlink($bb_filename, "$object_dir/$bb_basename") or 1087 | warn("WARNING: cannot create link ". 1088 | "$object_dir/$bb_basename\n"); 1089 | push(@tmp_links, "$object_dir/$bb_basename"); 1090 | } 1091 | } 1092 | 1093 | # Change to directory containing data files and apply GCOV 1094 | debug("chdir($base_dir)\n"); 1095 | chdir($base_dir); 1096 | 1097 | if ($da_renamed) 1098 | { 1099 | # Need to rename empty data file to workaround 1100 | # gcov <= 3.2.x bug (Abort) 1101 | system_no_output(3, "mv", "$da_filename", "$da_filename.ori") 1102 | and die ("ERROR: cannot rename $da_filename\n"); 1103 | } 1104 | 1105 | # Execute gcov command and suppress standard output 1106 | $gcov_error = system_no_output(1, $gcov_tool, $da_filename, 1107 | "-o", $object_dir, @gcov_options); 1108 | 1109 | if ($da_renamed) 1110 | { 1111 | system_no_output(3, "mv", "$da_filename.ori", "$da_filename") 1112 | and die ("ERROR: cannot rename $da_filename.ori"); 1113 | } 1114 | 1115 | # Clean up temporary links 1116 | foreach (@tmp_links) { 1117 | unlink($_); 1118 | } 1119 | 1120 | if ($gcov_error) 1121 | { 1122 | if ($ignore[$ERROR_GCOV]) 1123 | { 1124 | warn("WARNING: GCOV failed for $da_filename!\n"); 1125 | return; 1126 | } 1127 | die("ERROR: GCOV failed for $da_filename!\n"); 1128 | } 1129 | 1130 | # Collect data from resulting .gcov files and create .info file 1131 | @gcov_list = get_filenames('.', '\.gcov$'); 1132 | 1133 | # Check for files 1134 | if (!@gcov_list) 1135 | { 1136 | warn("WARNING: gcov did not create any files for ". 1137 | "$da_filename!\n"); 1138 | } 1139 | 1140 | # Check whether we're writing to a single file 1141 | if ($output_filename) 1142 | { 1143 | if ($output_filename eq "-") 1144 | { 1145 | *INFO_HANDLE = *STDOUT; 1146 | } 1147 | else 1148 | { 1149 | # Append to output file 1150 | open(INFO_HANDLE, ">>", $output_filename) 1151 | or die("ERROR: cannot write to ". 1152 | "$output_filename!\n"); 1153 | } 1154 | } 1155 | else 1156 | { 1157 | # Open .info file for output 1158 | open(INFO_HANDLE, ">", "$da_filename.info") 1159 | or die("ERROR: cannot create $da_filename.info!\n"); 1160 | } 1161 | 1162 | # Write test name 1163 | printf(INFO_HANDLE "TN:%s\n", $test_name); 1164 | 1165 | # Traverse the list of generated .gcov files and combine them into a 1166 | # single .info file 1167 | foreach $gcov_file (sort(@gcov_list)) 1168 | { 1169 | my $i; 1170 | my $num; 1171 | 1172 | # Skip gcov file for gcc built-in code 1173 | next if ($gcov_file eq ".gcov"); 1174 | 1175 | ($source, $object) = read_gcov_header($gcov_file); 1176 | 1177 | if (!defined($source)) { 1178 | # Derive source file name from gcov file name if 1179 | # header format could not be parsed 1180 | $source = $gcov_file; 1181 | $source =~ s/\.gcov$//; 1182 | } 1183 | 1184 | $source = solve_relative_path($base_dir, $source); 1185 | 1186 | if (defined($adjust_src_pattern)) { 1187 | # Apply transformation as specified by user 1188 | $source =~ s/$adjust_src_pattern/$adjust_src_replace/g; 1189 | } 1190 | 1191 | # gcov will happily create output even if there's no source code 1192 | # available - this interferes with checksum creation so we need 1193 | # to pull the emergency brake here. 1194 | if (! -r $source && $checksum) 1195 | { 1196 | if ($ignore[$ERROR_SOURCE]) 1197 | { 1198 | warn("WARNING: could not read source file ". 1199 | "$source\n"); 1200 | next; 1201 | } 1202 | die("ERROR: could not read source file $source\n"); 1203 | } 1204 | 1205 | @matches = match_filename($source, keys(%{$instr})); 1206 | 1207 | # Skip files that are not mentioned in the graph file 1208 | if (!@matches) 1209 | { 1210 | warn("WARNING: cannot find an entry for ".$gcov_file. 1211 | " in $graph_file_extension file, skipping ". 1212 | "file!\n"); 1213 | unlink($gcov_file); 1214 | next; 1215 | } 1216 | 1217 | # Read in contents of gcov file 1218 | @result = read_gcov_file($gcov_file); 1219 | if (!defined($result[0])) { 1220 | warn("WARNING: skipping unreadable file ". 1221 | $gcov_file."\n"); 1222 | unlink($gcov_file); 1223 | next; 1224 | } 1225 | @gcov_content = @{$result[0]}; 1226 | $gcov_branches = $result[1]; 1227 | @gcov_functions = @{$result[2]}; 1228 | 1229 | # Skip empty files 1230 | if (!@gcov_content) 1231 | { 1232 | warn("WARNING: skipping empty file ".$gcov_file."\n"); 1233 | unlink($gcov_file); 1234 | next; 1235 | } 1236 | 1237 | if (scalar(@matches) == 1) 1238 | { 1239 | # Just one match 1240 | $source_filename = $matches[0]; 1241 | } 1242 | else 1243 | { 1244 | # Try to solve the ambiguity 1245 | $source_filename = solve_ambiguous_match($gcov_file, 1246 | \@matches, \@gcov_content); 1247 | } 1248 | 1249 | if (@include_patterns) 1250 | { 1251 | my $keep = 0; 1252 | 1253 | foreach my $pattern (@include_patterns) 1254 | { 1255 | $keep ||= ($source_filename =~ (/^$pattern$/)); 1256 | } 1257 | 1258 | if (!$keep) 1259 | { 1260 | $excluded_files{$source_filename} = (); 1261 | unlink($gcov_file); 1262 | next; 1263 | } 1264 | } 1265 | 1266 | if (@exclude_patterns) 1267 | { 1268 | my $exclude = 0; 1269 | 1270 | foreach my $pattern (@exclude_patterns) 1271 | { 1272 | $exclude ||= ($source_filename =~ (/^$pattern$/)); 1273 | } 1274 | 1275 | if ($exclude) 1276 | { 1277 | $excluded_files{$source_filename} = (); 1278 | unlink($gcov_file); 1279 | next; 1280 | } 1281 | } 1282 | 1283 | # Skip external files if requested 1284 | if (!$opt_external) { 1285 | if (is_external($source_filename)) { 1286 | info(" ignoring data for external file ". 1287 | "$source_filename\n"); 1288 | unlink($gcov_file); 1289 | next; 1290 | } 1291 | } 1292 | 1293 | # Write absolute path of source file 1294 | printf(INFO_HANDLE "SF:%s\n", $source_filename); 1295 | 1296 | # If requested, derive function coverage data from 1297 | # line coverage data of the first line of a function 1298 | if ($opt_derive_func_data) { 1299 | @gcov_functions = 1300 | derive_data(\@gcov_content, \@gcov_functions, 1301 | $graph->{$source_filename}); 1302 | } 1303 | 1304 | # Write function-related information 1305 | if (defined($graph->{$source_filename})) 1306 | { 1307 | my $fn_data = $graph->{$source_filename}; 1308 | my $fn; 1309 | 1310 | foreach $fn (sort 1311 | {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} 1312 | keys(%{$fn_data})) { 1313 | my $ln_data = $fn_data->{$fn}; 1314 | my $line = $ln_data->[0]; 1315 | 1316 | # Skip empty function 1317 | if ($fn eq "") { 1318 | next; 1319 | } 1320 | # Remove excluded functions 1321 | if (!$no_markers) { 1322 | my $gfn; 1323 | my $found = 0; 1324 | 1325 | foreach $gfn (@gcov_functions) { 1326 | if ($gfn eq $fn) { 1327 | $found = 1; 1328 | last; 1329 | } 1330 | } 1331 | if (!$found) { 1332 | next; 1333 | } 1334 | } 1335 | 1336 | # Normalize function name 1337 | $fn = filter_fn_name($fn); 1338 | 1339 | print(INFO_HANDLE "FN:$line,$fn\n"); 1340 | } 1341 | } 1342 | 1343 | #-- 1344 | #-- FNDA: , 1345 | #-- FNF: overall count of functions 1346 | #-- FNH: overall count of functions with non-zero call count 1347 | #-- 1348 | $funcs_found = 0; 1349 | $funcs_hit = 0; 1350 | while (@gcov_functions) 1351 | { 1352 | my $count = shift(@gcov_functions); 1353 | my $fn = shift(@gcov_functions); 1354 | 1355 | $fn = filter_fn_name($fn); 1356 | printf(INFO_HANDLE "FNDA:$count,$fn\n"); 1357 | $funcs_found++; 1358 | $funcs_hit++ if ($count > 0); 1359 | } 1360 | if ($funcs_found > 0) { 1361 | printf(INFO_HANDLE "FNF:%s\n", $funcs_found); 1362 | printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); 1363 | } 1364 | 1365 | # Write coverage information for each instrumented branch: 1366 | # 1367 | # BRDA:,,, 1368 | # 1369 | # where 'taken' is the number of times the branch was taken 1370 | # or '-' if the block to which the branch belongs was never 1371 | # executed 1372 | $br_found = 0; 1373 | $br_hit = 0; 1374 | $num = br_gvec_len($gcov_branches); 1375 | for ($i = 0; $i < $num; $i++) { 1376 | my ($line, $block, $branch, $taken) = 1377 | br_gvec_get($gcov_branches, $i); 1378 | 1379 | $block = $BR_VEC_MAX if ($block < 0); 1380 | print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); 1381 | $br_found++; 1382 | $br_hit++ if ($taken ne '-' && $taken > 0); 1383 | } 1384 | if ($br_found > 0) { 1385 | printf(INFO_HANDLE "BRF:%s\n", $br_found); 1386 | printf(INFO_HANDLE "BRH:%s\n", $br_hit); 1387 | } 1388 | 1389 | # Reset line counters 1390 | $line_number = 0; 1391 | $lines_found = 0; 1392 | $lines_hit = 0; 1393 | 1394 | # Write coverage information for each instrumented line 1395 | # Note: @gcov_content contains a list of (flag, count, source) 1396 | # tuple for each source code line 1397 | while (@gcov_content) 1398 | { 1399 | $line_number++; 1400 | 1401 | # Check for instrumented line 1402 | if ($gcov_content[0]) 1403 | { 1404 | $lines_found++; 1405 | printf(INFO_HANDLE "DA:".$line_number.",". 1406 | $gcov_content[1].($checksum ? 1407 | ",". md5_base64($gcov_content[2]) : ""). 1408 | "\n"); 1409 | 1410 | # Increase $lines_hit in case of an execution 1411 | # count>0 1412 | if ($gcov_content[1] > 0) { $lines_hit++; } 1413 | } 1414 | 1415 | # Remove already processed data from array 1416 | splice(@gcov_content,0,3); 1417 | } 1418 | 1419 | # Write line statistics and section separator 1420 | printf(INFO_HANDLE "LF:%s\n", $lines_found); 1421 | printf(INFO_HANDLE "LH:%s\n", $lines_hit); 1422 | print(INFO_HANDLE "end_of_record\n"); 1423 | 1424 | # Remove .gcov file after processing 1425 | unlink($gcov_file); 1426 | } 1427 | 1428 | if (!($output_filename && ($output_filename eq "-"))) 1429 | { 1430 | close(INFO_HANDLE); 1431 | } 1432 | 1433 | # Change back to initial directory 1434 | chdir($cwd); 1435 | } 1436 | 1437 | 1438 | # 1439 | # solve_relative_path(path, dir) 1440 | # 1441 | # Solve relative path components of DIR which, if not absolute, resides in PATH. 1442 | # 1443 | 1444 | sub solve_relative_path($$) 1445 | { 1446 | my $path = $_[0]; 1447 | my $dir = $_[1]; 1448 | my $volume; 1449 | my $directories; 1450 | my $filename; 1451 | my @dirs; # holds path elements 1452 | my $result; 1453 | 1454 | # Convert from Windows path to msys path 1455 | if( $^O eq "msys" ) 1456 | { 1457 | # search for a windows drive letter at the beginning 1458 | ($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir ); 1459 | if( $volume ne '' ) 1460 | { 1461 | my $uppercase_volume; 1462 | # transform c/d\../e/f\g to Windows style c\d\..\e\f\g 1463 | $dir = File::Spec::Win32->canonpath( $dir ); 1464 | # use Win32 module to retrieve path components 1465 | # $uppercase_volume is not used any further 1466 | ( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir ); 1467 | @dirs = File::Spec::Win32->splitdir( $directories ); 1468 | 1469 | # prepend volume, since in msys C: is always mounted to /c 1470 | $volume =~ s|^([a-zA-Z]+):|/\L$1\E|; 1471 | unshift( @dirs, $volume ); 1472 | 1473 | # transform to Unix style '/' path 1474 | $directories = File::Spec->catdir( @dirs ); 1475 | $dir = File::Spec->catpath( '', $directories, $filename ); 1476 | } else { 1477 | # eliminate '\' path separators 1478 | $dir = File::Spec->canonpath( $dir ); 1479 | } 1480 | } 1481 | 1482 | $result = $dir; 1483 | # Prepend path if not absolute 1484 | if ($dir =~ /^[^\/]/) 1485 | { 1486 | $result = "$path/$result"; 1487 | } 1488 | 1489 | # Remove // 1490 | $result =~ s/\/\//\//g; 1491 | 1492 | # Remove . 1493 | while ($result =~ s/\/\.\//\//g) 1494 | { 1495 | } 1496 | $result =~ s/\/\.$/\//g; 1497 | 1498 | # Remove trailing / 1499 | $result =~ s/\/$//g; 1500 | 1501 | # Solve .. 1502 | while ($result =~ s/\/[^\/]+\/\.\.\//\//) 1503 | { 1504 | } 1505 | 1506 | # Remove preceding .. 1507 | $result =~ s/^\/\.\.\//\//g; 1508 | 1509 | return $result; 1510 | } 1511 | 1512 | 1513 | # 1514 | # match_filename(gcov_filename, list) 1515 | # 1516 | # Return a list of those entries of LIST which match the relative filename 1517 | # GCOV_FILENAME. 1518 | # 1519 | 1520 | sub match_filename($@) 1521 | { 1522 | my ($filename, @list) = @_; 1523 | my ($vol, $dir, $file) = splitpath($filename); 1524 | my @comp = splitdir($dir); 1525 | my $comps = scalar(@comp); 1526 | my $entry; 1527 | my @result; 1528 | 1529 | entry: 1530 | foreach $entry (@list) { 1531 | my ($evol, $edir, $efile) = splitpath($entry); 1532 | my @ecomp; 1533 | my $ecomps; 1534 | my $i; 1535 | 1536 | # Filename component must match 1537 | if ($efile ne $file) { 1538 | next; 1539 | } 1540 | # Check directory components last to first for match 1541 | @ecomp = splitdir($edir); 1542 | $ecomps = scalar(@ecomp); 1543 | if ($ecomps < $comps) { 1544 | next; 1545 | } 1546 | for ($i = 0; $i < $comps; $i++) { 1547 | if ($comp[$comps - $i - 1] ne 1548 | $ecomp[$ecomps - $i - 1]) { 1549 | next entry; 1550 | } 1551 | } 1552 | push(@result, $entry), 1553 | } 1554 | 1555 | return @result; 1556 | } 1557 | 1558 | # 1559 | # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) 1560 | # 1561 | # Try to solve ambiguous matches of mapping (gcov file) -> (source code) file 1562 | # by comparing source code provided in the GCOV file with that of the files 1563 | # in MATCHES. REL_FILENAME identifies the relative filename of the gcov 1564 | # file. 1565 | # 1566 | # Return the one real match or die if there is none. 1567 | # 1568 | 1569 | sub solve_ambiguous_match($$$) 1570 | { 1571 | my $rel_name = $_[0]; 1572 | my $matches = $_[1]; 1573 | my $content = $_[2]; 1574 | my $filename; 1575 | my $index; 1576 | my $no_match; 1577 | local *SOURCE; 1578 | 1579 | # Check the list of matches 1580 | foreach $filename (@$matches) 1581 | { 1582 | 1583 | # Compare file contents 1584 | open(SOURCE, "<", $filename) 1585 | or die("ERROR: cannot read $filename!\n"); 1586 | 1587 | $no_match = 0; 1588 | for ($index = 2; ; $index += 3) 1589 | { 1590 | chomp; 1591 | 1592 | # Also remove CR from line-end 1593 | s/\015$//; 1594 | 1595 | if ($_ ne @$content[$index]) 1596 | { 1597 | $no_match = 1; 1598 | last; 1599 | } 1600 | } 1601 | 1602 | close(SOURCE); 1603 | 1604 | if (!$no_match) 1605 | { 1606 | info("Solved source file ambiguity for $rel_name\n"); 1607 | return $filename; 1608 | } 1609 | } 1610 | 1611 | die("ERROR: could not match gcov data for $rel_name!\n"); 1612 | } 1613 | 1614 | 1615 | # 1616 | # split_filename(filename) 1617 | # 1618 | # Return (path, filename, extension) for a given FILENAME. 1619 | # 1620 | 1621 | sub split_filename($) 1622 | { 1623 | my @path_components = split('/', $_[0]); 1624 | my @file_components = split('\.', pop(@path_components)); 1625 | my $extension = pop(@file_components); 1626 | 1627 | return (join("/",@path_components), join(".",@file_components), 1628 | $extension); 1629 | } 1630 | 1631 | 1632 | # 1633 | # read_gcov_header(gcov_filename) 1634 | # 1635 | # Parse file GCOV_FILENAME and return a list containing the following 1636 | # information: 1637 | # 1638 | # (source, object) 1639 | # 1640 | # where: 1641 | # 1642 | # source: complete relative path of the source code file (gcc >= 3.3 only) 1643 | # object: name of associated graph file 1644 | # 1645 | # Die on error. 1646 | # 1647 | 1648 | sub read_gcov_header($) 1649 | { 1650 | my $source; 1651 | my $object; 1652 | local *INPUT; 1653 | 1654 | if (!open(INPUT, "<", $_[0])) 1655 | { 1656 | if ($ignore_errors[$ERROR_GCOV]) 1657 | { 1658 | warn("WARNING: cannot read $_[0]!\n"); 1659 | return (undef,undef); 1660 | } 1661 | die("ERROR: cannot read $_[0]!\n"); 1662 | } 1663 | 1664 | while () 1665 | { 1666 | chomp($_); 1667 | 1668 | # Also remove CR from line-end 1669 | s/\015$//; 1670 | 1671 | if (/^\s+-:\s+0:Source:(.*)$/) 1672 | { 1673 | # Source: header entry 1674 | $source = $1; 1675 | } 1676 | elsif (/^\s+-:\s+0:Object:(.*)$/) 1677 | { 1678 | # Object: header entry 1679 | $object = $1; 1680 | } 1681 | else 1682 | { 1683 | last; 1684 | } 1685 | } 1686 | 1687 | close(INPUT); 1688 | 1689 | return ($source, $object); 1690 | } 1691 | 1692 | 1693 | # 1694 | # br_gvec_len(vector) 1695 | # 1696 | # Return the number of entries in the branch coverage vector. 1697 | # 1698 | 1699 | sub br_gvec_len($) 1700 | { 1701 | my ($vec) = @_; 1702 | 1703 | return 0 if (!defined($vec)); 1704 | return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; 1705 | } 1706 | 1707 | 1708 | # 1709 | # br_gvec_get(vector, number) 1710 | # 1711 | # Return an entry from the branch coverage vector. 1712 | # 1713 | 1714 | sub br_gvec_get($$) 1715 | { 1716 | my ($vec, $num) = @_; 1717 | my $line; 1718 | my $block; 1719 | my $branch; 1720 | my $taken; 1721 | my $offset = $num * $BR_VEC_ENTRIES; 1722 | 1723 | # Retrieve data from vector 1724 | $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); 1725 | $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); 1726 | $block = -1 if ($block == $BR_VEC_MAX); 1727 | $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); 1728 | $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); 1729 | 1730 | # Decode taken value from an integer 1731 | if ($taken == 0) { 1732 | $taken = "-"; 1733 | } else { 1734 | $taken--; 1735 | } 1736 | 1737 | return ($line, $block, $branch, $taken); 1738 | } 1739 | 1740 | 1741 | # 1742 | # br_gvec_push(vector, line, block, branch, taken) 1743 | # 1744 | # Add an entry to the branch coverage vector. 1745 | # 1746 | 1747 | sub br_gvec_push($$$$$) 1748 | { 1749 | my ($vec, $line, $block, $branch, $taken) = @_; 1750 | my $offset; 1751 | 1752 | $vec = "" if (!defined($vec)); 1753 | $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; 1754 | $block = $BR_VEC_MAX if $block < 0; 1755 | 1756 | # Encode taken value into an integer 1757 | if ($taken eq "-") { 1758 | $taken = 0; 1759 | } else { 1760 | $taken++; 1761 | } 1762 | 1763 | # Add to vector 1764 | vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; 1765 | vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; 1766 | vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; 1767 | vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; 1768 | 1769 | return $vec; 1770 | } 1771 | 1772 | 1773 | # 1774 | # read_gcov_file(gcov_filename) 1775 | # 1776 | # Parse file GCOV_FILENAME (.gcov file format) and return the list: 1777 | # (reference to gcov_content, reference to gcov_branch, reference to gcov_func) 1778 | # 1779 | # gcov_content is a list of 3 elements 1780 | # (flag, count, source) for each source code line: 1781 | # 1782 | # $result[($line_number-1)*3+0] = instrumentation flag for line $line_number 1783 | # $result[($line_number-1)*3+1] = execution count for line $line_number 1784 | # $result[($line_number-1)*3+2] = source code text for line $line_number 1785 | # 1786 | # gcov_branch is a vector of 4 4-byte long elements for each branch: 1787 | # line number, block number, branch number, count + 1 or 0 1788 | # 1789 | # gcov_func is a list of 2 elements 1790 | # (number of calls, function name) for each function 1791 | # 1792 | # Die on error. 1793 | # 1794 | 1795 | sub read_gcov_file($) 1796 | { 1797 | my $filename = $_[0]; 1798 | my @result = (); 1799 | my $branches = ""; 1800 | my @functions = (); 1801 | my $number; 1802 | my $exclude_flag = 0; 1803 | my $exclude_line = 0; 1804 | my $exclude_br_flag = 0; 1805 | my $exclude_branch = 0; 1806 | my $last_block = $UNNAMED_BLOCK; 1807 | my $last_line = 0; 1808 | local *INPUT; 1809 | 1810 | if (!open(INPUT, "<", $filename)) { 1811 | if ($ignore_errors[$ERROR_GCOV]) 1812 | { 1813 | warn("WARNING: cannot read $filename!\n"); 1814 | return (undef, undef, undef); 1815 | } 1816 | die("ERROR: cannot read $filename!\n"); 1817 | } 1818 | 1819 | if ($gcov_version < $GCOV_VERSION_3_3_0) 1820 | { 1821 | # Expect gcov format as used in gcc < 3.3 1822 | while () 1823 | { 1824 | chomp($_); 1825 | 1826 | # Also remove CR from line-end 1827 | s/\015$//; 1828 | 1829 | if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { 1830 | next if (!$br_coverage); 1831 | next if ($exclude_line); 1832 | next if ($exclude_branch); 1833 | $branches = br_gvec_push($branches, $last_line, 1834 | $last_block, $1, $2); 1835 | } elsif (/^branch\s+(\d+)\s+never\s+executed/) { 1836 | next if (!$br_coverage); 1837 | next if ($exclude_line); 1838 | next if ($exclude_branch); 1839 | $branches = br_gvec_push($branches, $last_line, 1840 | $last_block, $1, '-'); 1841 | } 1842 | elsif (/^call/ || /^function/) 1843 | { 1844 | # Function call return data 1845 | } 1846 | else 1847 | { 1848 | $last_line++; 1849 | # Check for exclusion markers 1850 | if (!$no_markers) { 1851 | if (/$EXCL_STOP/) { 1852 | $exclude_flag = 0; 1853 | } elsif (/$EXCL_START/) { 1854 | $exclude_flag = 1; 1855 | } 1856 | if (/$excl_line/ || $exclude_flag) { 1857 | $exclude_line = 1; 1858 | } else { 1859 | $exclude_line = 0; 1860 | } 1861 | } 1862 | # Check for exclusion markers (branch exclude) 1863 | if (!$no_markers) { 1864 | if (/$EXCL_BR_STOP/) { 1865 | $exclude_br_flag = 0; 1866 | } elsif (/$EXCL_BR_START/) { 1867 | $exclude_br_flag = 1; 1868 | } 1869 | if (/$excl_br_line/ || $exclude_br_flag) { 1870 | $exclude_branch = 1; 1871 | } else { 1872 | $exclude_branch = 0; 1873 | } 1874 | } 1875 | # Source code execution data 1876 | if (/^\t\t(.*)$/) 1877 | { 1878 | # Uninstrumented line 1879 | push(@result, 0); 1880 | push(@result, 0); 1881 | push(@result, $1); 1882 | next; 1883 | } 1884 | $number = (split(" ",substr($_, 0, 16)))[0]; 1885 | 1886 | # Check for zero count which is indicated 1887 | # by ###### 1888 | if ($number eq "######") { $number = 0; } 1889 | 1890 | if ($exclude_line) { 1891 | # Register uninstrumented line instead 1892 | push(@result, 0); 1893 | push(@result, 0); 1894 | } else { 1895 | push(@result, 1); 1896 | push(@result, $number); 1897 | } 1898 | push(@result, substr($_, 16)); 1899 | } 1900 | } 1901 | } 1902 | else 1903 | { 1904 | # Expect gcov format as used in gcc >= 3.3 1905 | while () 1906 | { 1907 | chomp($_); 1908 | 1909 | # Also remove CR from line-end 1910 | s/\015$//; 1911 | 1912 | if (/^\s*(\d+|\$+|\%+):\s*(\d+)-block\s+(\d+)\s*$/) { 1913 | # Block information - used to group related 1914 | # branches 1915 | $last_line = $2; 1916 | $last_block = $3; 1917 | } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { 1918 | next if (!$br_coverage); 1919 | next if ($exclude_line); 1920 | next if ($exclude_branch); 1921 | $branches = br_gvec_push($branches, $last_line, 1922 | $last_block, $1, $2); 1923 | } elsif (/^branch\s+(\d+)\s+never\s+executed/) { 1924 | next if (!$br_coverage); 1925 | next if ($exclude_line); 1926 | next if ($exclude_branch); 1927 | $branches = br_gvec_push($branches, $last_line, 1928 | $last_block, $1, '-'); 1929 | } 1930 | elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/) 1931 | { 1932 | next if (!$func_coverage); 1933 | if ($exclude_line) { 1934 | next; 1935 | } 1936 | push(@functions, $2, $1); 1937 | } 1938 | elsif (/^call/) 1939 | { 1940 | # Function call return data 1941 | } 1942 | elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) 1943 | { 1944 | my ($count, $line, $code) = ($1, $2, $3); 1945 | 1946 | # Skip instance-specific counts 1947 | next if ($line <= (scalar(@result) / 3)); 1948 | 1949 | $last_line = $line; 1950 | $last_block = $UNNAMED_BLOCK; 1951 | # Check for exclusion markers 1952 | if (!$no_markers) { 1953 | if (/$EXCL_STOP/) { 1954 | $exclude_flag = 0; 1955 | } elsif (/$EXCL_START/) { 1956 | $exclude_flag = 1; 1957 | } 1958 | if (/$excl_line/ || $exclude_flag) { 1959 | $exclude_line = 1; 1960 | } else { 1961 | $exclude_line = 0; 1962 | } 1963 | } 1964 | # Check for exclusion markers (branch exclude) 1965 | if (!$no_markers) { 1966 | if (/$EXCL_BR_STOP/) { 1967 | $exclude_br_flag = 0; 1968 | } elsif (/$EXCL_BR_START/) { 1969 | $exclude_br_flag = 1; 1970 | } 1971 | if (/$excl_br_line/ || $exclude_br_flag) { 1972 | $exclude_branch = 1; 1973 | } else { 1974 | $exclude_branch = 0; 1975 | } 1976 | } 1977 | 1978 | # Strip unexecuted basic block marker 1979 | $count =~ s/\*$//; 1980 | 1981 | # :: 1982 | if ($line eq "0") 1983 | { 1984 | # Extra data 1985 | } 1986 | elsif ($count eq "-") 1987 | { 1988 | # Uninstrumented line 1989 | push(@result, 0); 1990 | push(@result, 0); 1991 | push(@result, $code); 1992 | } 1993 | else 1994 | { 1995 | if ($exclude_line) { 1996 | push(@result, 0); 1997 | push(@result, 0); 1998 | } else { 1999 | # Check for zero count 2000 | if ($count =~ /^[#=]/) { 2001 | $count = 0; 2002 | } 2003 | push(@result, 1); 2004 | push(@result, $count); 2005 | } 2006 | push(@result, $code); 2007 | } 2008 | } 2009 | } 2010 | } 2011 | 2012 | close(INPUT); 2013 | if ($exclude_flag || $exclude_br_flag) { 2014 | warn("WARNING: unterminated exclusion section in $filename\n"); 2015 | } 2016 | return(\@result, $branches, \@functions); 2017 | } 2018 | 2019 | 2020 | # Map LLVM versions to the version of GCC gcov which they emulate. 2021 | 2022 | sub map_llvm_version($) 2023 | { 2024 | my ($ver) = @_; 2025 | 2026 | return 0x040200 if ($ver >= 0x030400); 2027 | 2028 | warn("WARNING: This version of LLVM's gcov is unknown. ". 2029 | "Assuming it emulates GCC gcov version 4.2.\n"); 2030 | 2031 | return 0x040200; 2032 | } 2033 | 2034 | 2035 | # Return a readable version of encoded gcov version. 2036 | 2037 | sub version_to_str($) 2038 | { 2039 | my ($ver) = @_; 2040 | my ($a, $b, $c); 2041 | 2042 | $a = $ver >> 16 & 0xff; 2043 | $b = $ver >> 8 & 0xff; 2044 | $c = $ver & 0xff; 2045 | 2046 | return "$a.$b.$c"; 2047 | } 2048 | 2049 | 2050 | # 2051 | # Get the GCOV tool version. Return an integer number which represents the 2052 | # GCOV version. Version numbers can be compared using standard integer 2053 | # operations. 2054 | # 2055 | 2056 | sub get_gcov_version() 2057 | { 2058 | local *HANDLE; 2059 | my $version_string; 2060 | my $result; 2061 | my ($a, $b, $c) = (4, 2, 0); # Fallback version 2062 | 2063 | # Examples for gcov version output: 2064 | # 2065 | # gcov (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3) 2066 | # 2067 | # gcov (crosstool-NG 1.18.0) 4.7.2 2068 | # 2069 | # LLVM (http://llvm.org/): 2070 | # LLVM version 3.4svn 2071 | # 2072 | # Apple LLVM version 8.0.0 (clang-800.0.38) 2073 | # Optimized build. 2074 | # Default target: x86_64-apple-darwin16.0.0 2075 | # Host CPU: haswell 2076 | 2077 | open(GCOV_PIPE, "-|", "$gcov_tool --version") 2078 | or die("ERROR: cannot retrieve gcov version!\n"); 2079 | local $/; 2080 | $version_string = ; 2081 | close(GCOV_PIPE); 2082 | 2083 | # Remove all bracketed information 2084 | $version_string =~ s/\([^\)]*\)//g; 2085 | 2086 | if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/) { 2087 | ($a, $b, $c) = ($1, $2, $4); 2088 | $c = 0 if (!defined($c)); 2089 | } else { 2090 | warn("WARNING: cannot determine gcov version - ". 2091 | "assuming $a.$b.$c\n"); 2092 | } 2093 | $result = $a << 16 | $b << 8 | $c; 2094 | 2095 | if ($version_string =~ /LLVM/) { 2096 | $result = map_llvm_version($result); 2097 | info("Found LLVM gcov version $a.$b.$c, which emulates gcov ". 2098 | "version ".version_to_str($result)."\n"); 2099 | } else { 2100 | info("Found gcov version: ".version_to_str($result)."\n"); 2101 | } 2102 | 2103 | return ($result, $version_string); 2104 | } 2105 | 2106 | 2107 | # 2108 | # info(printf_parameter) 2109 | # 2110 | # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag 2111 | # is not set. 2112 | # 2113 | 2114 | sub info(@) 2115 | { 2116 | if (!$quiet) 2117 | { 2118 | # Print info string 2119 | if (defined($output_filename) && ($output_filename eq "-")) 2120 | { 2121 | # Don't interfere with the .info output to STDOUT 2122 | printf(STDERR @_); 2123 | } 2124 | else 2125 | { 2126 | printf(@_); 2127 | } 2128 | } 2129 | } 2130 | 2131 | 2132 | # 2133 | # int_handler() 2134 | # 2135 | # Called when the script was interrupted by an INT signal (e.g. CTRl-C) 2136 | # 2137 | 2138 | sub int_handler() 2139 | { 2140 | if ($cwd) { chdir($cwd); } 2141 | info("Aborted.\n"); 2142 | exit(1); 2143 | } 2144 | 2145 | 2146 | # 2147 | # system_no_output(mode, parameters) 2148 | # 2149 | # Call an external program using PARAMETERS while suppressing depending on 2150 | # the value of MODE: 2151 | # 2152 | # MODE & 1: suppress STDOUT 2153 | # MODE & 2: suppress STDERR 2154 | # 2155 | # Return 0 on success, non-zero otherwise. 2156 | # 2157 | 2158 | sub system_no_output($@) 2159 | { 2160 | my $mode = shift; 2161 | my $result; 2162 | local *OLD_STDERR; 2163 | local *OLD_STDOUT; 2164 | 2165 | # Save old stdout and stderr handles 2166 | ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); 2167 | ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); 2168 | 2169 | # Redirect to /dev/null 2170 | ($mode & 1) && open(STDOUT, ">", "/dev/null"); 2171 | ($mode & 2) && open(STDERR, ">", "/dev/null"); 2172 | 2173 | debug("system(".join(' ', @_).")\n"); 2174 | system(@_); 2175 | $result = $?; 2176 | 2177 | # Close redirected handles 2178 | ($mode & 1) && close(STDOUT); 2179 | ($mode & 2) && close(STDERR); 2180 | 2181 | # Restore old handles 2182 | ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); 2183 | ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); 2184 | 2185 | return $result; 2186 | } 2187 | 2188 | 2189 | # 2190 | # read_config(filename) 2191 | # 2192 | # Read configuration file FILENAME and return a reference to a hash containing 2193 | # all valid key=value pairs found. 2194 | # 2195 | 2196 | sub read_config($) 2197 | { 2198 | my $filename = $_[0]; 2199 | my %result; 2200 | my $key; 2201 | my $value; 2202 | local *HANDLE; 2203 | 2204 | if (!open(HANDLE, "<", $filename)) 2205 | { 2206 | warn("WARNING: cannot read configuration file $filename\n"); 2207 | return undef; 2208 | } 2209 | while () 2210 | { 2211 | chomp; 2212 | # Skip comments 2213 | s/#.*//; 2214 | # Remove leading blanks 2215 | s/^\s+//; 2216 | # Remove trailing blanks 2217 | s/\s+$//; 2218 | next unless length; 2219 | ($key, $value) = split(/\s*=\s*/, $_, 2); 2220 | if (defined($key) && defined($value)) 2221 | { 2222 | $result{$key} = $value; 2223 | } 2224 | else 2225 | { 2226 | warn("WARNING: malformed statement in line $. ". 2227 | "of configuration file $filename\n"); 2228 | } 2229 | } 2230 | close(HANDLE); 2231 | return \%result; 2232 | } 2233 | 2234 | 2235 | # 2236 | # apply_config(REF) 2237 | # 2238 | # REF is a reference to a hash containing the following mapping: 2239 | # 2240 | # key_string => var_ref 2241 | # 2242 | # where KEY_STRING is a keyword and VAR_REF is a reference to an associated 2243 | # variable. If the global configuration hashes CONFIG or OPT_RC contain a value 2244 | # for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 2245 | # 2246 | 2247 | sub apply_config($) 2248 | { 2249 | my $ref = $_[0]; 2250 | 2251 | foreach (keys(%{$ref})) 2252 | { 2253 | if (defined($opt_rc{$_})) { 2254 | ${$ref->{$_}} = $opt_rc{$_}; 2255 | } elsif (defined($config->{$_})) { 2256 | ${$ref->{$_}} = $config->{$_}; 2257 | } 2258 | } 2259 | } 2260 | 2261 | 2262 | # 2263 | # get_exclusion_data(filename) 2264 | # 2265 | # Scan specified source code file for exclusion markers and return 2266 | # linenumber -> 1 2267 | # for all lines which should be excluded. 2268 | # 2269 | 2270 | sub get_exclusion_data($) 2271 | { 2272 | my ($filename) = @_; 2273 | my %list; 2274 | my $flag = 0; 2275 | local *HANDLE; 2276 | 2277 | if (!open(HANDLE, "<", $filename)) { 2278 | warn("WARNING: could not open $filename\n"); 2279 | return undef; 2280 | } 2281 | while () { 2282 | if (/$EXCL_STOP/) { 2283 | $flag = 0; 2284 | } elsif (/$EXCL_START/) { 2285 | $flag = 1; 2286 | } 2287 | if (/$excl_line/ || $flag) { 2288 | $list{$.} = 1; 2289 | } 2290 | } 2291 | close(HANDLE); 2292 | 2293 | if ($flag) { 2294 | warn("WARNING: unterminated exclusion section in $filename\n"); 2295 | } 2296 | 2297 | return \%list; 2298 | } 2299 | 2300 | 2301 | # 2302 | # apply_exclusion_data(instr, graph) 2303 | # 2304 | # Remove lines from instr and graph data structures which are marked 2305 | # for exclusion in the source code file. 2306 | # 2307 | # Return adjusted (instr, graph). 2308 | # 2309 | # graph : file name -> function data 2310 | # function data : function name -> line data 2311 | # line data : [ line1, line2, ... ] 2312 | # 2313 | # instr : filename -> line data 2314 | # line data : [ line1, line2, ... ] 2315 | # 2316 | 2317 | sub apply_exclusion_data($$) 2318 | { 2319 | my ($instr, $graph) = @_; 2320 | my $filename; 2321 | my %excl_data; 2322 | my $excl_read_failed = 0; 2323 | 2324 | # Collect exclusion marker data 2325 | foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { 2326 | my $excl = get_exclusion_data($filename); 2327 | 2328 | # Skip and note if file could not be read 2329 | if (!defined($excl)) { 2330 | $excl_read_failed = 1; 2331 | next; 2332 | } 2333 | 2334 | # Add to collection if there are markers 2335 | $excl_data{$filename} = $excl if (keys(%{$excl}) > 0); 2336 | } 2337 | 2338 | # Warn if not all source files could be read 2339 | if ($excl_read_failed) { 2340 | warn("WARNING: some exclusion markers may be ignored\n"); 2341 | } 2342 | 2343 | # Skip if no markers were found 2344 | return ($instr, $graph) if (keys(%excl_data) == 0); 2345 | 2346 | # Apply exclusion marker data to graph 2347 | foreach $filename (keys(%excl_data)) { 2348 | my $function_data = $graph->{$filename}; 2349 | my $excl = $excl_data{$filename}; 2350 | my $function; 2351 | 2352 | next if (!defined($function_data)); 2353 | 2354 | foreach $function (keys(%{$function_data})) { 2355 | my $line_data = $function_data->{$function}; 2356 | my $line; 2357 | my @new_data; 2358 | 2359 | # To be consistent with exclusion parser in non-initial 2360 | # case we need to remove a function if the first line 2361 | # was excluded 2362 | if ($excl->{$line_data->[0]}) { 2363 | delete($function_data->{$function}); 2364 | next; 2365 | } 2366 | # Copy only lines which are not excluded 2367 | foreach $line (@{$line_data}) { 2368 | push(@new_data, $line) if (!$excl->{$line}); 2369 | } 2370 | 2371 | # Store modified list 2372 | if (scalar(@new_data) > 0) { 2373 | $function_data->{$function} = \@new_data; 2374 | } else { 2375 | # All of this function was excluded 2376 | delete($function_data->{$function}); 2377 | } 2378 | } 2379 | 2380 | # Check if all functions of this file were excluded 2381 | if (keys(%{$function_data}) == 0) { 2382 | delete($graph->{$filename}); 2383 | } 2384 | } 2385 | 2386 | # Apply exclusion marker data to instr 2387 | foreach $filename (keys(%excl_data)) { 2388 | my $line_data = $instr->{$filename}; 2389 | my $excl = $excl_data{$filename}; 2390 | my $line; 2391 | my @new_data; 2392 | 2393 | next if (!defined($line_data)); 2394 | 2395 | # Copy only lines which are not excluded 2396 | foreach $line (@{$line_data}) { 2397 | push(@new_data, $line) if (!$excl->{$line}); 2398 | } 2399 | 2400 | # Store modified list 2401 | $instr->{$filename} = \@new_data; 2402 | } 2403 | 2404 | return ($instr, $graph); 2405 | } 2406 | 2407 | 2408 | sub process_graphfile($$) 2409 | { 2410 | my ($file, $dir) = @_; 2411 | my $graph_filename = $file; 2412 | my $graph_dir; 2413 | my $graph_basename; 2414 | my $source_dir; 2415 | my $base_dir; 2416 | my $graph; 2417 | my $instr; 2418 | my $filename; 2419 | local *INFO_HANDLE; 2420 | 2421 | info("Processing %s\n", abs2rel($file, $dir)); 2422 | 2423 | # Get path to data file in absolute and normalized form (begins with /, 2424 | # contains no more ../ or ./) 2425 | $graph_filename = solve_relative_path($cwd, $graph_filename); 2426 | 2427 | # Get directory and basename of data file 2428 | ($graph_dir, $graph_basename) = split_filename($graph_filename); 2429 | 2430 | $source_dir = $graph_dir; 2431 | if (is_compat($COMPAT_MODE_LIBTOOL)) { 2432 | # Avoid files from .libs dirs 2433 | $source_dir =~ s/\.libs$//; 2434 | } 2435 | 2436 | # Construct base_dir for current file 2437 | if ($base_directory) 2438 | { 2439 | $base_dir = $base_directory; 2440 | } 2441 | else 2442 | { 2443 | $base_dir = $source_dir; 2444 | } 2445 | 2446 | # Ignore empty graph file (e.g. source file with no statement) 2447 | if (-z $graph_filename) 2448 | { 2449 | warn("WARNING: empty $graph_filename (skipped)\n"); 2450 | return; 2451 | } 2452 | 2453 | if ($gcov_version < $GCOV_VERSION_3_4_0) 2454 | { 2455 | if (is_compat($COMPAT_MODE_HAMMER)) 2456 | { 2457 | ($instr, $graph) = read_bbg($graph_filename); 2458 | } 2459 | else 2460 | { 2461 | ($instr, $graph) = read_bb($graph_filename); 2462 | } 2463 | } 2464 | else 2465 | { 2466 | ($instr, $graph) = read_gcno($graph_filename); 2467 | } 2468 | 2469 | # Try to find base directory automatically if requested by user 2470 | if ($rc_auto_base) { 2471 | $base_dir = find_base_from_graph($base_dir, $instr, $graph); 2472 | } 2473 | 2474 | ($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph); 2475 | 2476 | if (!$no_markers) { 2477 | # Apply exclusion marker data to graph file data 2478 | ($instr, $graph) = apply_exclusion_data($instr, $graph); 2479 | } 2480 | 2481 | # Check whether we're writing to a single file 2482 | if ($output_filename) 2483 | { 2484 | if ($output_filename eq "-") 2485 | { 2486 | *INFO_HANDLE = *STDOUT; 2487 | } 2488 | else 2489 | { 2490 | # Append to output file 2491 | open(INFO_HANDLE, ">>", $output_filename) 2492 | or die("ERROR: cannot write to ". 2493 | "$output_filename!\n"); 2494 | } 2495 | } 2496 | else 2497 | { 2498 | # Open .info file for output 2499 | open(INFO_HANDLE, ">", "$graph_filename.info") 2500 | or die("ERROR: cannot create $graph_filename.info!\n"); 2501 | } 2502 | 2503 | # Write test name 2504 | printf(INFO_HANDLE "TN:%s\n", $test_name); 2505 | foreach $filename (sort(keys(%{$instr}))) 2506 | { 2507 | my $funcdata = $graph->{$filename}; 2508 | my $line; 2509 | my $linedata; 2510 | 2511 | # Skip external files if requested 2512 | if (!$opt_external) { 2513 | if (is_external($filename)) { 2514 | info(" ignoring data for external file ". 2515 | "$filename\n"); 2516 | next; 2517 | } 2518 | } 2519 | 2520 | print(INFO_HANDLE "SF:$filename\n"); 2521 | 2522 | if (defined($funcdata) && $func_coverage) { 2523 | my @functions = sort {$funcdata->{$a}->[0] <=> 2524 | $funcdata->{$b}->[0]} 2525 | keys(%{$funcdata}); 2526 | my $func; 2527 | 2528 | # Gather list of instrumented lines and functions 2529 | foreach $func (@functions) { 2530 | $linedata = $funcdata->{$func}; 2531 | 2532 | # Print function name and starting line 2533 | print(INFO_HANDLE "FN:".$linedata->[0]. 2534 | ",".filter_fn_name($func)."\n"); 2535 | } 2536 | # Print zero function coverage data 2537 | foreach $func (@functions) { 2538 | print(INFO_HANDLE "FNDA:0,". 2539 | filter_fn_name($func)."\n"); 2540 | } 2541 | # Print function summary 2542 | print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); 2543 | print(INFO_HANDLE "FNH:0\n"); 2544 | } 2545 | # Print zero line coverage data 2546 | foreach $line (@{$instr->{$filename}}) { 2547 | print(INFO_HANDLE "DA:$line,0\n"); 2548 | } 2549 | # Print line summary 2550 | print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); 2551 | print(INFO_HANDLE "LH:0\n"); 2552 | 2553 | print(INFO_HANDLE "end_of_record\n"); 2554 | } 2555 | if (!($output_filename && ($output_filename eq "-"))) 2556 | { 2557 | close(INFO_HANDLE); 2558 | } 2559 | } 2560 | 2561 | sub filter_fn_name($) 2562 | { 2563 | my ($fn) = @_; 2564 | 2565 | # Remove characters used internally as function name delimiters 2566 | $fn =~ s/[,=]/_/g; 2567 | 2568 | return $fn; 2569 | } 2570 | 2571 | sub warn_handler($) 2572 | { 2573 | my ($msg) = @_; 2574 | 2575 | warn("$tool_name: $msg"); 2576 | } 2577 | 2578 | sub die_handler($) 2579 | { 2580 | my ($msg) = @_; 2581 | 2582 | die("$tool_name: $msg"); 2583 | } 2584 | 2585 | 2586 | # 2587 | # graph_error(filename, message) 2588 | # 2589 | # Print message about error in graph file. If ignore_graph_error is set, return. 2590 | # Otherwise abort. 2591 | # 2592 | 2593 | sub graph_error($$) 2594 | { 2595 | my ($filename, $msg) = @_; 2596 | 2597 | if ($ignore[$ERROR_GRAPH]) { 2598 | warn("WARNING: $filename: $msg - skipping\n"); 2599 | return; 2600 | } 2601 | die("ERROR: $filename: $msg\n"); 2602 | } 2603 | 2604 | # 2605 | # graph_expect(description) 2606 | # 2607 | # If debug is set to a non-zero value, print the specified description of what 2608 | # is expected to be read next from the graph file. 2609 | # 2610 | 2611 | sub graph_expect($) 2612 | { 2613 | my ($msg) = @_; 2614 | 2615 | if (!$debug || !defined($msg)) { 2616 | return; 2617 | } 2618 | 2619 | print(STDERR "DEBUG: expecting $msg\n"); 2620 | } 2621 | 2622 | # 2623 | # graph_read(handle, bytes[, description, peek]) 2624 | # 2625 | # Read and return the specified number of bytes from handle. Return undef 2626 | # if the number of bytes could not be read. If PEEK is non-zero, reset 2627 | # file position after read. 2628 | # 2629 | 2630 | sub graph_read(*$;$$) 2631 | { 2632 | my ($handle, $length, $desc, $peek) = @_; 2633 | my $data; 2634 | my $result; 2635 | my $pos; 2636 | 2637 | graph_expect($desc); 2638 | if ($peek) { 2639 | $pos = tell($handle); 2640 | if ($pos == -1) { 2641 | warn("Could not get current file position: $!\n"); 2642 | return undef; 2643 | } 2644 | } 2645 | $result = read($handle, $data, $length); 2646 | if ($debug) { 2647 | my $op = $peek ? "peek" : "read"; 2648 | my $ascii = ""; 2649 | my $hex = ""; 2650 | my $i; 2651 | 2652 | print(STDERR "DEBUG: $op($length)=$result: "); 2653 | for ($i = 0; $i < length($data); $i++) { 2654 | my $c = substr($data, $i, 1);; 2655 | my $n = ord($c); 2656 | 2657 | $hex .= sprintf("%02x ", $n); 2658 | if ($n >= 32 && $n <= 127) { 2659 | $ascii .= $c; 2660 | } else { 2661 | $ascii .= "."; 2662 | } 2663 | } 2664 | print(STDERR "$hex |$ascii|"); 2665 | print(STDERR "\n"); 2666 | } 2667 | if ($peek) { 2668 | if (!seek($handle, $pos, 0)) { 2669 | warn("Could not set file position: $!\n"); 2670 | return undef; 2671 | } 2672 | } 2673 | if ($result != $length) { 2674 | return undef; 2675 | } 2676 | return $data; 2677 | } 2678 | 2679 | # 2680 | # graph_skip(handle, bytes[, description]) 2681 | # 2682 | # Read and discard the specified number of bytes from handle. Return non-zero 2683 | # if bytes could be read, zero otherwise. 2684 | # 2685 | 2686 | sub graph_skip(*$;$) 2687 | { 2688 | my ($handle, $length, $desc) = @_; 2689 | 2690 | if (defined(graph_read($handle, $length, $desc))) { 2691 | return 1; 2692 | } 2693 | return 0; 2694 | } 2695 | 2696 | # 2697 | # uniq(list) 2698 | # 2699 | # Return list without duplicate entries. 2700 | # 2701 | 2702 | sub uniq(@) 2703 | { 2704 | my (@list) = @_; 2705 | my @new_list; 2706 | my %known; 2707 | 2708 | foreach my $item (@list) { 2709 | next if ($known{$item}); 2710 | $known{$item} = 1; 2711 | push(@new_list, $item); 2712 | } 2713 | 2714 | return @new_list; 2715 | } 2716 | 2717 | # 2718 | # sort_uniq(list) 2719 | # 2720 | # Return list in numerically ascending order and without duplicate entries. 2721 | # 2722 | 2723 | sub sort_uniq(@) 2724 | { 2725 | my (@list) = @_; 2726 | my %hash; 2727 | 2728 | foreach (@list) { 2729 | $hash{$_} = 1; 2730 | } 2731 | return sort { $a <=> $b } keys(%hash); 2732 | } 2733 | 2734 | # 2735 | # sort_uniq_lex(list) 2736 | # 2737 | # Return list in lexically ascending order and without duplicate entries. 2738 | # 2739 | 2740 | sub sort_uniq_lex(@) 2741 | { 2742 | my (@list) = @_; 2743 | my %hash; 2744 | 2745 | foreach (@list) { 2746 | $hash{$_} = 1; 2747 | } 2748 | return sort keys(%hash); 2749 | } 2750 | 2751 | # 2752 | # parent_dir(dir) 2753 | # 2754 | # Return parent directory for DIR. DIR must not contain relative path 2755 | # components. 2756 | # 2757 | 2758 | sub parent_dir($) 2759 | { 2760 | my ($dir) = @_; 2761 | my ($v, $d, $f) = splitpath($dir, 1); 2762 | my @dirs = splitdir($d); 2763 | 2764 | pop(@dirs); 2765 | 2766 | return catpath($v, catdir(@dirs), $f); 2767 | } 2768 | 2769 | # 2770 | # find_base_from_graph(base_dir, instr, graph) 2771 | # 2772 | # Try to determine the base directory of the graph file specified by INSTR 2773 | # and GRAPH. The base directory is the base for all relative filenames in 2774 | # the graph file. It is defined by the current working directory at time 2775 | # of compiling the source file. 2776 | # 2777 | # This function implements a heuristic which relies on the following 2778 | # assumptions: 2779 | # - all files used for compilation are still present at their location 2780 | # - the base directory is either BASE_DIR or one of its parent directories 2781 | # - files by the same name are not present in multiple parent directories 2782 | # 2783 | 2784 | sub find_base_from_graph($$$) 2785 | { 2786 | my ($base_dir, $instr, $graph) = @_; 2787 | my $old_base; 2788 | my $best_miss; 2789 | my $best_base; 2790 | my %rel_files; 2791 | 2792 | # Determine list of relative paths 2793 | foreach my $filename (keys(%{$instr}), keys(%{$graph})) { 2794 | next if (file_name_is_absolute($filename)); 2795 | 2796 | $rel_files{$filename} = 1; 2797 | } 2798 | 2799 | # Early exit if there are no relative paths 2800 | return $base_dir if (!%rel_files); 2801 | 2802 | do { 2803 | my $miss = 0; 2804 | 2805 | foreach my $filename (keys(%rel_files)) { 2806 | if (!-e solve_relative_path($base_dir, $filename)) { 2807 | $miss++; 2808 | } 2809 | } 2810 | 2811 | debug("base_dir=$base_dir miss=$miss\n"); 2812 | 2813 | # Exit if we find an exact match with no misses 2814 | return $base_dir if ($miss == 0); 2815 | 2816 | # No exact match, aim for the one with the least source file 2817 | # misses 2818 | if (!defined($best_base) || $miss < $best_miss) { 2819 | $best_base = $base_dir; 2820 | $best_miss = $miss; 2821 | } 2822 | 2823 | # Repeat until there's no more parent directory 2824 | $old_base = $base_dir; 2825 | $base_dir = parent_dir($base_dir); 2826 | } while ($old_base ne $base_dir); 2827 | 2828 | return $best_base; 2829 | } 2830 | 2831 | # 2832 | # adjust_graph_filenames(base_dir, instr, graph) 2833 | # 2834 | # Make relative paths in INSTR and GRAPH absolute and apply 2835 | # geninfo_adjust_src_path setting to graph file data. 2836 | # 2837 | 2838 | sub adjust_graph_filenames($$$) 2839 | { 2840 | my ($base_dir, $instr, $graph) = @_; 2841 | 2842 | foreach my $filename (keys(%{$instr})) { 2843 | my $old_filename = $filename; 2844 | 2845 | # Convert to absolute canonical form 2846 | $filename = solve_relative_path($base_dir, $filename); 2847 | 2848 | # Apply adjustment 2849 | if (defined($adjust_src_pattern)) { 2850 | $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; 2851 | } 2852 | 2853 | if ($filename ne $old_filename) { 2854 | $instr->{$filename} = delete($instr->{$old_filename}); 2855 | } 2856 | } 2857 | 2858 | foreach my $filename (keys(%{$graph})) { 2859 | my $old_filename = $filename; 2860 | 2861 | # Make absolute 2862 | # Convert to absolute canonical form 2863 | $filename = solve_relative_path($base_dir, $filename); 2864 | 2865 | # Apply adjustment 2866 | if (defined($adjust_src_pattern)) { 2867 | $filename =~ s/$adjust_src_pattern/$adjust_src_replace/g; 2868 | } 2869 | 2870 | if ($filename ne $old_filename) { 2871 | $graph->{$filename} = delete($graph->{$old_filename}); 2872 | } 2873 | } 2874 | 2875 | return ($instr, $graph); 2876 | } 2877 | 2878 | # 2879 | # graph_cleanup(graph) 2880 | # 2881 | # Remove entries for functions with no lines. Remove duplicate line numbers. 2882 | # Sort list of line numbers numerically ascending. 2883 | # 2884 | 2885 | sub graph_cleanup($) 2886 | { 2887 | my ($graph) = @_; 2888 | my $filename; 2889 | 2890 | foreach $filename (keys(%{$graph})) { 2891 | my $per_file = $graph->{$filename}; 2892 | my $function; 2893 | 2894 | foreach $function (keys(%{$per_file})) { 2895 | my $lines = $per_file->{$function}; 2896 | 2897 | if (scalar(@$lines) == 0) { 2898 | # Remove empty function 2899 | delete($per_file->{$function}); 2900 | next; 2901 | } 2902 | # Normalize list 2903 | $per_file->{$function} = [ uniq(@$lines) ]; 2904 | } 2905 | if (scalar(keys(%{$per_file})) == 0) { 2906 | # Remove empty file 2907 | delete($graph->{$filename}); 2908 | } 2909 | } 2910 | } 2911 | 2912 | # 2913 | # graph_find_base(bb) 2914 | # 2915 | # Try to identify the filename which is the base source file for the 2916 | # specified bb data. 2917 | # 2918 | 2919 | sub graph_find_base($) 2920 | { 2921 | my ($bb) = @_; 2922 | my %file_count; 2923 | my $basefile; 2924 | my $file; 2925 | my $func; 2926 | my $filedata; 2927 | my $count; 2928 | my $num; 2929 | 2930 | # Identify base name for this bb data. 2931 | foreach $func (keys(%{$bb})) { 2932 | $filedata = $bb->{$func}; 2933 | 2934 | foreach $file (keys(%{$filedata})) { 2935 | $count = $file_count{$file}; 2936 | 2937 | # Count file occurrence 2938 | $file_count{$file} = defined($count) ? $count + 1 : 1; 2939 | } 2940 | } 2941 | $count = 0; 2942 | $num = 0; 2943 | foreach $file (keys(%file_count)) { 2944 | if ($file_count{$file} > $count) { 2945 | # The file that contains code for the most functions 2946 | # is likely the base file 2947 | $count = $file_count{$file}; 2948 | $num = 1; 2949 | $basefile = $file; 2950 | } elsif ($file_count{$file} == $count) { 2951 | # If more than one file could be the basefile, we 2952 | # don't have a basefile 2953 | $basefile = undef; 2954 | } 2955 | } 2956 | 2957 | return $basefile; 2958 | } 2959 | 2960 | # 2961 | # graph_from_bb(bb, fileorder, bb_filename, fileorder_first) 2962 | # 2963 | # Convert data from bb to the graph format and list of instrumented lines. 2964 | # 2965 | # If FILEORDER_FIRST is set, use fileorder data to determine a functions 2966 | # base source file. 2967 | # 2968 | # Returns (instr, graph). 2969 | # 2970 | # bb : function name -> file data 2971 | # : undef -> file order 2972 | # file data : filename -> line data 2973 | # line data : [ line1, line2, ... ] 2974 | # 2975 | # file order : function name -> [ filename1, filename2, ... ] 2976 | # 2977 | # graph : file name -> function data 2978 | # function data : function name -> line data 2979 | # line data : [ line1, line2, ... ] 2980 | # 2981 | # instr : filename -> line data 2982 | # line data : [ line1, line2, ... ] 2983 | # 2984 | 2985 | sub graph_from_bb($$$$) 2986 | { 2987 | my ($bb, $fileorder, $bb_filename, $fileorder_first) = @_; 2988 | my $graph = {}; 2989 | my $instr = {}; 2990 | my $basefile; 2991 | my $file; 2992 | my $func; 2993 | my $filedata; 2994 | my $linedata; 2995 | my $order; 2996 | 2997 | $basefile = graph_find_base($bb); 2998 | # Create graph structure 2999 | foreach $func (keys(%{$bb})) { 3000 | $filedata = $bb->{$func}; 3001 | $order = $fileorder->{$func}; 3002 | 3003 | # Account for lines in functions 3004 | if (defined($basefile) && defined($filedata->{$basefile}) && 3005 | !$fileorder_first) { 3006 | # If the basefile contributes to this function, 3007 | # account this function to the basefile. 3008 | $graph->{$basefile}->{$func} = $filedata->{$basefile}; 3009 | } else { 3010 | # If the basefile does not contribute to this function, 3011 | # account this function to the first file contributing 3012 | # lines. 3013 | $graph->{$order->[0]}->{$func} = 3014 | $filedata->{$order->[0]}; 3015 | } 3016 | 3017 | foreach $file (keys(%{$filedata})) { 3018 | # Account for instrumented lines 3019 | $linedata = $filedata->{$file}; 3020 | push(@{$instr->{$file}}, @$linedata); 3021 | } 3022 | } 3023 | # Clean up array of instrumented lines 3024 | foreach $file (keys(%{$instr})) { 3025 | $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; 3026 | } 3027 | 3028 | return ($instr, $graph); 3029 | } 3030 | 3031 | # 3032 | # graph_add_order(fileorder, function, filename) 3033 | # 3034 | # Add an entry for filename to the fileorder data set for function. 3035 | # 3036 | 3037 | sub graph_add_order($$$) 3038 | { 3039 | my ($fileorder, $function, $filename) = @_; 3040 | my $item; 3041 | my $list; 3042 | 3043 | $list = $fileorder->{$function}; 3044 | foreach $item (@$list) { 3045 | if ($item eq $filename) { 3046 | return; 3047 | } 3048 | } 3049 | push(@$list, $filename); 3050 | $fileorder->{$function} = $list; 3051 | } 3052 | 3053 | # 3054 | # read_bb_word(handle[, description]) 3055 | # 3056 | # Read and return a word in .bb format from handle. 3057 | # 3058 | 3059 | sub read_bb_word(*;$) 3060 | { 3061 | my ($handle, $desc) = @_; 3062 | 3063 | return graph_read($handle, 4, $desc); 3064 | } 3065 | 3066 | # 3067 | # read_bb_value(handle[, description]) 3068 | # 3069 | # Read a word in .bb format from handle and return the word and its integer 3070 | # value. 3071 | # 3072 | 3073 | sub read_bb_value(*;$) 3074 | { 3075 | my ($handle, $desc) = @_; 3076 | my $word; 3077 | 3078 | $word = read_bb_word($handle, $desc); 3079 | return undef if (!defined($word)); 3080 | 3081 | return ($word, unpack("V", $word)); 3082 | } 3083 | 3084 | # 3085 | # read_bb_string(handle, delimiter) 3086 | # 3087 | # Read and return a string in .bb format from handle up to the specified 3088 | # delimiter value. 3089 | # 3090 | 3091 | sub read_bb_string(*$) 3092 | { 3093 | my ($handle, $delimiter) = @_; 3094 | my $word; 3095 | my $value; 3096 | my $string = ""; 3097 | 3098 | graph_expect("string"); 3099 | do { 3100 | ($word, $value) = read_bb_value($handle, "string or delimiter"); 3101 | return undef if (!defined($value)); 3102 | if ($value != $delimiter) { 3103 | $string .= $word; 3104 | } 3105 | } while ($value != $delimiter); 3106 | $string =~ s/\0//g; 3107 | 3108 | return $string; 3109 | } 3110 | 3111 | # 3112 | # read_bb(filename) 3113 | # 3114 | # Read the contents of the specified .bb file and return (instr, graph), where: 3115 | # 3116 | # instr : filename -> line data 3117 | # line data : [ line1, line2, ... ] 3118 | # 3119 | # graph : filename -> file_data 3120 | # file_data : function name -> line_data 3121 | # line_data : [ line1, line2, ... ] 3122 | # 3123 | # See the gcov info pages of gcc 2.95 for a description of the .bb file format. 3124 | # 3125 | 3126 | sub read_bb($) 3127 | { 3128 | my ($bb_filename) = @_; 3129 | my $minus_one = 0x80000001; 3130 | my $minus_two = 0x80000002; 3131 | my $value; 3132 | my $filename; 3133 | my $function; 3134 | my $bb = {}; 3135 | my $fileorder = {}; 3136 | my $instr; 3137 | my $graph; 3138 | local *HANDLE; 3139 | 3140 | open(HANDLE, "<", $bb_filename) or goto open_error; 3141 | binmode(HANDLE); 3142 | while (!eof(HANDLE)) { 3143 | $value = read_bb_value(*HANDLE, "data word"); 3144 | goto incomplete if (!defined($value)); 3145 | if ($value == $minus_one) { 3146 | # Source file name 3147 | graph_expect("filename"); 3148 | $filename = read_bb_string(*HANDLE, $minus_one); 3149 | goto incomplete if (!defined($filename)); 3150 | } elsif ($value == $minus_two) { 3151 | # Function name 3152 | graph_expect("function name"); 3153 | $function = read_bb_string(*HANDLE, $minus_two); 3154 | goto incomplete if (!defined($function)); 3155 | } elsif ($value > 0) { 3156 | # Line number 3157 | if (!defined($filename) || !defined($function)) { 3158 | warn("WARNING: unassigned line number ". 3159 | "$value\n"); 3160 | next; 3161 | } 3162 | push(@{$bb->{$function}->{$filename}}, $value); 3163 | graph_add_order($fileorder, $function, $filename); 3164 | } 3165 | } 3166 | close(HANDLE); 3167 | 3168 | ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename, 0); 3169 | graph_cleanup($graph); 3170 | 3171 | return ($instr, $graph); 3172 | 3173 | open_error: 3174 | graph_error($bb_filename, "could not open file"); 3175 | return undef; 3176 | incomplete: 3177 | graph_error($bb_filename, "reached unexpected end of file"); 3178 | return undef; 3179 | } 3180 | 3181 | # 3182 | # read_bbg_word(handle[, description]) 3183 | # 3184 | # Read and return a word in .bbg format. 3185 | # 3186 | 3187 | sub read_bbg_word(*;$) 3188 | { 3189 | my ($handle, $desc) = @_; 3190 | 3191 | return graph_read($handle, 4, $desc); 3192 | } 3193 | 3194 | # 3195 | # read_bbg_value(handle[, description]) 3196 | # 3197 | # Read a word in .bbg format from handle and return its integer value. 3198 | # 3199 | 3200 | sub read_bbg_value(*;$) 3201 | { 3202 | my ($handle, $desc) = @_; 3203 | my $word; 3204 | 3205 | $word = read_bbg_word($handle, $desc); 3206 | return undef if (!defined($word)); 3207 | 3208 | return unpack("N", $word); 3209 | } 3210 | 3211 | # 3212 | # read_bbg_string(handle) 3213 | # 3214 | # Read and return a string in .bbg format. 3215 | # 3216 | 3217 | sub read_bbg_string(*) 3218 | { 3219 | my ($handle, $desc) = @_; 3220 | my $length; 3221 | my $string; 3222 | 3223 | graph_expect("string"); 3224 | # Read string length 3225 | $length = read_bbg_value($handle, "string length"); 3226 | return undef if (!defined($length)); 3227 | if ($length == 0) { 3228 | return ""; 3229 | } 3230 | # Read string 3231 | $string = graph_read($handle, $length, "string"); 3232 | return undef if (!defined($string)); 3233 | # Skip padding 3234 | graph_skip($handle, 4 - $length % 4, "string padding") or return undef; 3235 | 3236 | return $string; 3237 | } 3238 | 3239 | # 3240 | # read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, 3241 | # function) 3242 | # 3243 | # Read a bbg format lines record from handle and add the relevant data to 3244 | # bb and fileorder. Return filename on success, undef on error. 3245 | # 3246 | 3247 | sub read_bbg_lines_record(*$$$$$) 3248 | { 3249 | my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_; 3250 | my $string; 3251 | my $lineno; 3252 | 3253 | graph_expect("lines record"); 3254 | # Skip basic block index 3255 | graph_skip($handle, 4, "basic block index") or return undef; 3256 | while (1) { 3257 | # Read line number 3258 | $lineno = read_bbg_value($handle, "line number"); 3259 | return undef if (!defined($lineno)); 3260 | if ($lineno == 0) { 3261 | # Got a marker for a new filename 3262 | graph_expect("filename"); 3263 | $string = read_bbg_string($handle); 3264 | return undef if (!defined($string)); 3265 | # Check for end of record 3266 | if ($string eq "") { 3267 | return $filename; 3268 | } 3269 | $filename = $string; 3270 | if (!exists($bb->{$function}->{$filename})) { 3271 | $bb->{$function}->{$filename} = []; 3272 | } 3273 | next; 3274 | } 3275 | # Got an actual line number 3276 | if (!defined($filename)) { 3277 | warn("WARNING: unassigned line number in ". 3278 | "$bbg_filename\n"); 3279 | next; 3280 | } 3281 | push(@{$bb->{$function}->{$filename}}, $lineno); 3282 | graph_add_order($fileorder, $function, $filename); 3283 | } 3284 | } 3285 | 3286 | # 3287 | # read_bbg(filename) 3288 | # 3289 | # Read the contents of the specified .bbg file and return the following mapping: 3290 | # graph: filename -> file_data 3291 | # file_data: function name -> line_data 3292 | # line_data: [ line1, line2, ... ] 3293 | # 3294 | # See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description 3295 | # of the .bbg format. 3296 | # 3297 | 3298 | sub read_bbg($) 3299 | { 3300 | my ($bbg_filename) = @_; 3301 | my $file_magic = 0x67626267; 3302 | my $tag_function = 0x01000000; 3303 | my $tag_lines = 0x01450000; 3304 | my $word; 3305 | my $tag; 3306 | my $length; 3307 | my $function; 3308 | my $filename; 3309 | my $bb = {}; 3310 | my $fileorder = {}; 3311 | my $instr; 3312 | my $graph; 3313 | local *HANDLE; 3314 | 3315 | open(HANDLE, "<", $bbg_filename) or goto open_error; 3316 | binmode(HANDLE); 3317 | # Read magic 3318 | $word = read_bbg_value(*HANDLE, "file magic"); 3319 | goto incomplete if (!defined($word)); 3320 | # Check magic 3321 | if ($word != $file_magic) { 3322 | goto magic_error; 3323 | } 3324 | # Skip version 3325 | graph_skip(*HANDLE, 4, "version") or goto incomplete; 3326 | while (!eof(HANDLE)) { 3327 | # Read record tag 3328 | $tag = read_bbg_value(*HANDLE, "record tag"); 3329 | goto incomplete if (!defined($tag)); 3330 | # Read record length 3331 | $length = read_bbg_value(*HANDLE, "record length"); 3332 | goto incomplete if (!defined($tag)); 3333 | if ($tag == $tag_function) { 3334 | graph_expect("function record"); 3335 | # Read function name 3336 | graph_expect("function name"); 3337 | $function = read_bbg_string(*HANDLE); 3338 | goto incomplete if (!defined($function)); 3339 | $filename = undef; 3340 | # Skip function checksum 3341 | graph_skip(*HANDLE, 4, "function checksum") 3342 | or goto incomplete; 3343 | } elsif ($tag == $tag_lines) { 3344 | # Read lines record 3345 | $filename = read_bbg_lines_record(HANDLE, $bbg_filename, 3346 | $bb, $fileorder, $filename, 3347 | $function); 3348 | goto incomplete if (!defined($filename)); 3349 | } else { 3350 | # Skip record contents 3351 | graph_skip(*HANDLE, $length, "unhandled record") 3352 | or goto incomplete; 3353 | } 3354 | } 3355 | close(HANDLE); 3356 | ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename, 0); 3357 | 3358 | graph_cleanup($graph); 3359 | 3360 | return ($instr, $graph); 3361 | 3362 | open_error: 3363 | graph_error($bbg_filename, "could not open file"); 3364 | return undef; 3365 | incomplete: 3366 | graph_error($bbg_filename, "reached unexpected end of file"); 3367 | return undef; 3368 | magic_error: 3369 | graph_error($bbg_filename, "found unrecognized bbg file magic"); 3370 | return undef; 3371 | } 3372 | 3373 | # 3374 | # read_gcno_word(handle[, description, peek]) 3375 | # 3376 | # Read and return a word in .gcno format. 3377 | # 3378 | 3379 | sub read_gcno_word(*;$$) 3380 | { 3381 | my ($handle, $desc, $peek) = @_; 3382 | 3383 | return graph_read($handle, 4, $desc, $peek); 3384 | } 3385 | 3386 | # 3387 | # read_gcno_value(handle, big_endian[, description, peek]) 3388 | # 3389 | # Read a word in .gcno format from handle and return its integer value 3390 | # according to the specified endianness. If PEEK is non-zero, reset file 3391 | # position after read. 3392 | # 3393 | 3394 | sub read_gcno_value(*$;$$) 3395 | { 3396 | my ($handle, $big_endian, $desc, $peek) = @_; 3397 | my $word; 3398 | my $pos; 3399 | 3400 | $word = read_gcno_word($handle, $desc, $peek); 3401 | return undef if (!defined($word)); 3402 | if ($big_endian) { 3403 | return unpack("N", $word); 3404 | } else { 3405 | return unpack("V", $word); 3406 | } 3407 | } 3408 | 3409 | # 3410 | # read_gcno_string(handle, big_endian) 3411 | # 3412 | # Read and return a string in .gcno format. 3413 | # 3414 | 3415 | sub read_gcno_string(*$) 3416 | { 3417 | my ($handle, $big_endian) = @_; 3418 | my $length; 3419 | my $string; 3420 | 3421 | graph_expect("string"); 3422 | # Read string length 3423 | $length = read_gcno_value($handle, $big_endian, "string length"); 3424 | return undef if (!defined($length)); 3425 | if ($length == 0) { 3426 | return ""; 3427 | } 3428 | $length *= 4; 3429 | # Read string 3430 | $string = graph_read($handle, $length, "string and padding"); 3431 | return undef if (!defined($string)); 3432 | $string =~ s/\0//g; 3433 | 3434 | return $string; 3435 | } 3436 | 3437 | # 3438 | # read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, 3439 | # function, big_endian) 3440 | # 3441 | # Read a gcno format lines record from handle and add the relevant data to 3442 | # bb and fileorder. Return filename on success, undef on error. 3443 | # 3444 | 3445 | sub read_gcno_lines_record(*$$$$$$) 3446 | { 3447 | my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, 3448 | $big_endian) = @_; 3449 | my $string; 3450 | my $lineno; 3451 | 3452 | graph_expect("lines record"); 3453 | # Skip basic block index 3454 | graph_skip($handle, 4, "basic block index") or return undef; 3455 | while (1) { 3456 | # Read line number 3457 | $lineno = read_gcno_value($handle, $big_endian, "line number"); 3458 | return undef if (!defined($lineno)); 3459 | if ($lineno == 0) { 3460 | # Got a marker for a new filename 3461 | graph_expect("filename"); 3462 | $string = read_gcno_string($handle, $big_endian); 3463 | return undef if (!defined($string)); 3464 | # Check for end of record 3465 | if ($string eq "") { 3466 | return $filename; 3467 | } 3468 | $filename = $string; 3469 | if (!exists($bb->{$function}->{$filename})) { 3470 | $bb->{$function}->{$filename} = []; 3471 | } 3472 | next; 3473 | } 3474 | # Got an actual line number 3475 | if (!defined($filename)) { 3476 | warn("WARNING: unassigned line number in ". 3477 | "$gcno_filename\n"); 3478 | next; 3479 | } 3480 | # Add to list 3481 | push(@{$bb->{$function}->{$filename}}, $lineno); 3482 | graph_add_order($fileorder, $function, $filename); 3483 | } 3484 | } 3485 | 3486 | # 3487 | # determine_gcno_split_crc(handle, big_endian, rec_length, version) 3488 | # 3489 | # Determine if HANDLE refers to a .gcno file with a split checksum function 3490 | # record format. Return non-zero in case of split checksum format, zero 3491 | # otherwise, undef in case of read error. 3492 | # 3493 | 3494 | sub determine_gcno_split_crc($$$$) 3495 | { 3496 | my ($handle, $big_endian, $rec_length, $version) = @_; 3497 | my $strlen; 3498 | my $overlong_string; 3499 | 3500 | return 1 if ($version >= $GCOV_VERSION_4_7_0); 3501 | return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC)); 3502 | 3503 | # Heuristic: 3504 | # Decide format based on contents of next word in record: 3505 | # - pre-gcc 4.7 3506 | # This is the function name length / 4 which should be 3507 | # less than the remaining record length 3508 | # - gcc 4.7 3509 | # This is a checksum, likely with high-order bits set, 3510 | # resulting in a large number 3511 | $strlen = read_gcno_value($handle, $big_endian, undef, 1); 3512 | return undef if (!defined($strlen)); 3513 | $overlong_string = 1 if ($strlen * 4 >= $rec_length - 12); 3514 | 3515 | if ($overlong_string) { 3516 | if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) { 3517 | info("Auto-detected compatibility mode for split ". 3518 | "checksum .gcno file format\n"); 3519 | 3520 | return 1; 3521 | } else { 3522 | # Sanity check 3523 | warn("Found overlong string in function record: ". 3524 | "try '--compat split_crc'\n"); 3525 | } 3526 | } 3527 | 3528 | return 0; 3529 | } 3530 | 3531 | # 3532 | # read_gcno_function_record(handle, graph, big_endian, rec_length, version) 3533 | # 3534 | # Read a gcno format function record from handle and add the relevant data 3535 | # to graph. Return (filename, function, artificial) on success, undef on error. 3536 | # 3537 | 3538 | sub read_gcno_function_record(*$$$$$) 3539 | { 3540 | my ($handle, $bb, $fileorder, $big_endian, $rec_length, $version) = @_; 3541 | my $filename; 3542 | my $function; 3543 | my $lineno; 3544 | my $lines; 3545 | my $artificial; 3546 | 3547 | graph_expect("function record"); 3548 | # Skip ident and checksum 3549 | graph_skip($handle, 8, "function ident and checksum") or return undef; 3550 | # Determine if this is a function record with split checksums 3551 | if (!defined($gcno_split_crc)) { 3552 | $gcno_split_crc = determine_gcno_split_crc($handle, $big_endian, 3553 | $rec_length, 3554 | $version); 3555 | return undef if (!defined($gcno_split_crc)); 3556 | } 3557 | # Skip cfg checksum word in case of split checksums 3558 | graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc); 3559 | # Read function name 3560 | graph_expect("function name"); 3561 | $function = read_gcno_string($handle, $big_endian); 3562 | return undef if (!defined($function)); 3563 | if ($version >= $GCOV_VERSION_8_0_0) { 3564 | $artificial = read_gcno_value($handle, $big_endian, 3565 | "compiler-generated entity flag"); 3566 | return undef if (!defined($artificial)); 3567 | } 3568 | # Read filename 3569 | graph_expect("filename"); 3570 | $filename = read_gcno_string($handle, $big_endian); 3571 | return undef if (!defined($filename)); 3572 | # Read first line number 3573 | $lineno = read_gcno_value($handle, $big_endian, "initial line number"); 3574 | return undef if (!defined($lineno)); 3575 | # Skip column and ending line number 3576 | if ($version >= $GCOV_VERSION_8_0_0) { 3577 | graph_skip($handle, 4, "column number") or return undef; 3578 | graph_skip($handle, 4, "ending line number") or return undef; 3579 | } 3580 | # Add to list 3581 | push(@{$bb->{$function}->{$filename}}, $lineno); 3582 | graph_add_order($fileorder, $function, $filename); 3583 | 3584 | return ($filename, $function, $artificial); 3585 | } 3586 | 3587 | # 3588 | # map_gcno_version 3589 | # 3590 | # Map version number as found in .gcno files to the format used in geninfo. 3591 | # 3592 | 3593 | sub map_gcno_version($) 3594 | { 3595 | my ($version) = @_; 3596 | my ($a, $b, $c); 3597 | my ($major, $minor); 3598 | 3599 | $a = $version >> 24; 3600 | $b = $version >> 16 & 0xff; 3601 | $c = $version >> 8 & 0xff; 3602 | 3603 | if ($a < ord('A')) { 3604 | $major = $a - ord('0'); 3605 | $minor = ($b - ord('0')) * 10 + $c - ord('0'); 3606 | } else { 3607 | $major = ($a - ord('A')) * 10 + $b - ord('0'); 3608 | $minor = $c - ord('0'); 3609 | } 3610 | 3611 | return $major << 16 | $minor << 8; 3612 | } 3613 | 3614 | sub remove_fn_from_hash($$) 3615 | { 3616 | my ($hash, $fns) = @_; 3617 | 3618 | foreach my $fn (@$fns) { 3619 | delete($hash->{$fn}); 3620 | } 3621 | } 3622 | 3623 | # 3624 | # read_gcno(filename) 3625 | # 3626 | # Read the contents of the specified .gcno file and return the following 3627 | # mapping: 3628 | # graph: filename -> file_data 3629 | # file_data: function name -> line_data 3630 | # line_data: [ line1, line2, ... ] 3631 | # 3632 | # See the gcov-io.h file in the gcc 3.3 source code for a description of 3633 | # the .gcno format. 3634 | # 3635 | 3636 | sub read_gcno($) 3637 | { 3638 | my ($gcno_filename) = @_; 3639 | my $file_magic = 0x67636e6f; 3640 | my $tag_function = 0x01000000; 3641 | my $tag_lines = 0x01450000; 3642 | my $big_endian; 3643 | my $word; 3644 | my $tag; 3645 | my $length; 3646 | my $filename; 3647 | my $function; 3648 | my $bb = {}; 3649 | my $fileorder = {}; 3650 | my $instr; 3651 | my $graph; 3652 | my $filelength; 3653 | my $version; 3654 | my $artificial; 3655 | my @artificial_fns; 3656 | local *HANDLE; 3657 | 3658 | open(HANDLE, "<", $gcno_filename) or goto open_error; 3659 | $filelength = (stat(HANDLE))[7]; 3660 | binmode(HANDLE); 3661 | # Read magic 3662 | $word = read_gcno_word(*HANDLE, "file magic"); 3663 | goto incomplete if (!defined($word)); 3664 | # Determine file endianness 3665 | if (unpack("N", $word) == $file_magic) { 3666 | $big_endian = 1; 3667 | } elsif (unpack("V", $word) == $file_magic) { 3668 | $big_endian = 0; 3669 | } else { 3670 | goto magic_error; 3671 | } 3672 | # Read version 3673 | $version = read_gcno_value(*HANDLE, $big_endian, "compiler version"); 3674 | $version = map_gcno_version($version); 3675 | debug(sprintf("found version 0x%08x\n", $version)); 3676 | # Skip stamp 3677 | graph_skip(*HANDLE, 4, "file timestamp") or goto incomplete; 3678 | if ($version >= $GCOV_VERSION_8_0_0) { 3679 | graph_skip(*HANDLE, 4, "support unexecuted blocks flag") 3680 | or goto incomplete; 3681 | } 3682 | while (!eof(HANDLE)) { 3683 | my $next_pos; 3684 | my $curr_pos; 3685 | 3686 | # Read record tag 3687 | $tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); 3688 | goto incomplete if (!defined($tag)); 3689 | # Read record length 3690 | $length = read_gcno_value(*HANDLE, $big_endian, 3691 | "record length"); 3692 | goto incomplete if (!defined($length)); 3693 | # Convert length to bytes 3694 | $length *= 4; 3695 | # Calculate start of next record 3696 | $next_pos = tell(HANDLE); 3697 | goto tell_error if ($next_pos == -1); 3698 | $next_pos += $length; 3699 | # Catch garbage at the end of a gcno file 3700 | if ($next_pos > $filelength) { 3701 | debug("Overlong record: file_length=$filelength ". 3702 | "rec_length=$length\n"); 3703 | warn("WARNING: $gcno_filename: Overlong record at end ". 3704 | "of file!\n"); 3705 | last; 3706 | } 3707 | # Process record 3708 | if ($tag == $tag_function) { 3709 | ($filename, $function, $artificial) = 3710 | read_gcno_function_record( 3711 | *HANDLE, $bb, $fileorder, $big_endian, 3712 | $length, $version); 3713 | goto incomplete if (!defined($function)); 3714 | push(@artificial_fns, $function) if ($artificial); 3715 | } elsif ($tag == $tag_lines) { 3716 | # Read lines record 3717 | $filename = read_gcno_lines_record(*HANDLE, 3718 | $gcno_filename, $bb, $fileorder, 3719 | $filename, $function, $big_endian); 3720 | goto incomplete if (!defined($filename)); 3721 | } else { 3722 | # Skip record contents 3723 | graph_skip(*HANDLE, $length, "unhandled record") 3724 | or goto incomplete; 3725 | } 3726 | # Ensure that we are at the start of the next record 3727 | $curr_pos = tell(HANDLE); 3728 | goto tell_error if ($curr_pos == -1); 3729 | next if ($curr_pos == $next_pos); 3730 | goto record_error if ($curr_pos > $next_pos); 3731 | graph_skip(*HANDLE, $next_pos - $curr_pos, 3732 | "unhandled record content") 3733 | or goto incomplete; 3734 | } 3735 | close(HANDLE); 3736 | 3737 | # Remove artificial functions from result data 3738 | remove_fn_from_hash($bb, \@artificial_fns); 3739 | remove_fn_from_hash($fileorder, \@artificial_fns); 3740 | 3741 | ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename, 1); 3742 | graph_cleanup($graph); 3743 | 3744 | return ($instr, $graph); 3745 | 3746 | open_error: 3747 | graph_error($gcno_filename, "could not open file"); 3748 | return undef; 3749 | incomplete: 3750 | graph_error($gcno_filename, "reached unexpected end of file"); 3751 | return undef; 3752 | magic_error: 3753 | graph_error($gcno_filename, "found unrecognized gcno file magic"); 3754 | return undef; 3755 | tell_error: 3756 | graph_error($gcno_filename, "could not determine file position"); 3757 | return undef; 3758 | record_error: 3759 | graph_error($gcno_filename, "found unrecognized record format"); 3760 | return undef; 3761 | } 3762 | 3763 | sub debug($) 3764 | { 3765 | my ($msg) = @_; 3766 | 3767 | return if (!$debug); 3768 | print(STDERR "DEBUG: $msg"); 3769 | } 3770 | 3771 | # 3772 | # get_gcov_capabilities 3773 | # 3774 | # Determine the list of available gcov options. 3775 | # 3776 | 3777 | sub get_gcov_capabilities() 3778 | { 3779 | my $help = `$gcov_tool --help`; 3780 | my %capabilities; 3781 | my %short_option_translations = ( 3782 | 'a' => 'all-blocks', 3783 | 'b' => 'branch-probabilities', 3784 | 'c' => 'branch-counts', 3785 | 'f' => 'function-summaries', 3786 | 'h' => 'help', 3787 | 'l' => 'long-file-names', 3788 | 'n' => 'no-output', 3789 | 'o' => 'object-directory', 3790 | 'p' => 'preserve-paths', 3791 | 'u' => 'unconditional-branches', 3792 | 'v' => 'version', 3793 | 'x' => 'hash-filenames', 3794 | ); 3795 | 3796 | foreach (split(/\n/, $help)) { 3797 | my $capability; 3798 | if (/--(\S+)/) { 3799 | $capability = $1; 3800 | } else { 3801 | # If the line provides a short option, translate it. 3802 | next if (!/^\s*-(\S)\s/); 3803 | $capability = $short_option_translations{$1}; 3804 | next if not defined($capability); 3805 | } 3806 | next if ($capability eq 'help'); 3807 | next if ($capability eq 'version'); 3808 | next if ($capability eq 'object-directory'); 3809 | 3810 | $capabilities{$capability} = 1; 3811 | debug("gcov has capability '$capability'\n"); 3812 | } 3813 | 3814 | return \%capabilities; 3815 | } 3816 | 3817 | # 3818 | # parse_ignore_errors(@ignore_errors) 3819 | # 3820 | # Parse user input about which errors to ignore. 3821 | # 3822 | 3823 | sub parse_ignore_errors(@) 3824 | { 3825 | my (@ignore_errors) = @_; 3826 | my @items; 3827 | my $item; 3828 | 3829 | return if (!@ignore_errors); 3830 | 3831 | foreach $item (@ignore_errors) { 3832 | $item =~ s/\s//g; 3833 | if ($item =~ /,/) { 3834 | # Split and add comma-separated parameters 3835 | push(@items, split(/,/, $item)); 3836 | } else { 3837 | # Add single parameter 3838 | push(@items, $item); 3839 | } 3840 | } 3841 | foreach $item (@items) { 3842 | my $item_id = $ERROR_ID{lc($item)}; 3843 | 3844 | if (!defined($item_id)) { 3845 | die("ERROR: unknown argument for --ignore-errors: ". 3846 | "$item\n"); 3847 | } 3848 | $ignore[$item_id] = 1; 3849 | } 3850 | } 3851 | 3852 | # 3853 | # is_external(filename) 3854 | # 3855 | # Determine if a file is located outside of the specified data directories. 3856 | # 3857 | 3858 | sub is_external($) 3859 | { 3860 | my ($filename) = @_; 3861 | my $dir; 3862 | 3863 | foreach $dir (@internal_dirs) { 3864 | return 0 if ($filename =~ /^\Q$dir\/\E/); 3865 | } 3866 | return 1; 3867 | } 3868 | 3869 | # 3870 | # compat_name(mode) 3871 | # 3872 | # Return the name of compatibility mode MODE. 3873 | # 3874 | 3875 | sub compat_name($) 3876 | { 3877 | my ($mode) = @_; 3878 | my $name = $COMPAT_MODE_TO_NAME{$mode}; 3879 | 3880 | return $name if (defined($name)); 3881 | 3882 | return ""; 3883 | } 3884 | 3885 | # 3886 | # parse_compat_modes(opt) 3887 | # 3888 | # Determine compatibility mode settings. 3889 | # 3890 | 3891 | sub parse_compat_modes($) 3892 | { 3893 | my ($opt) = @_; 3894 | my @opt_list; 3895 | my %specified; 3896 | 3897 | # Initialize with defaults 3898 | %compat_value = %COMPAT_MODE_DEFAULTS; 3899 | 3900 | # Add old style specifications 3901 | if (defined($opt_compat_libtool)) { 3902 | $compat_value{$COMPAT_MODE_LIBTOOL} = 3903 | $opt_compat_libtool ? $COMPAT_VALUE_ON 3904 | : $COMPAT_VALUE_OFF; 3905 | } 3906 | 3907 | # Parse settings 3908 | if (defined($opt)) { 3909 | @opt_list = split(/\s*,\s*/, $opt); 3910 | } 3911 | foreach my $directive (@opt_list) { 3912 | my ($mode, $value); 3913 | 3914 | # Either 3915 | # mode=off|on|auto or 3916 | # mode (implies on) 3917 | if ($directive !~ /^(\w+)=(\w+)$/ && 3918 | $directive !~ /^(\w+)$/) { 3919 | die("ERROR: Unknown compatibility mode specification: ". 3920 | "$directive!\n"); 3921 | } 3922 | # Determine mode 3923 | $mode = $COMPAT_NAME_TO_MODE{lc($1)}; 3924 | if (!defined($mode)) { 3925 | die("ERROR: Unknown compatibility mode '$1'!\n"); 3926 | } 3927 | $specified{$mode} = 1; 3928 | # Determine value 3929 | if (defined($2)) { 3930 | $value = $COMPAT_NAME_TO_VALUE{lc($2)}; 3931 | if (!defined($value)) { 3932 | die("ERROR: Unknown compatibility mode ". 3933 | "value '$2'!\n"); 3934 | } 3935 | } else { 3936 | $value = $COMPAT_VALUE_ON; 3937 | } 3938 | $compat_value{$mode} = $value; 3939 | } 3940 | # Perform auto-detection 3941 | foreach my $mode (sort(keys(%compat_value))) { 3942 | my $value = $compat_value{$mode}; 3943 | my $is_autodetect = ""; 3944 | my $name = compat_name($mode); 3945 | 3946 | if ($value == $COMPAT_VALUE_AUTO) { 3947 | my $autodetect = $COMPAT_MODE_AUTO{$mode}; 3948 | 3949 | if (!defined($autodetect)) { 3950 | die("ERROR: No auto-detection for ". 3951 | "mode '$name' available!\n"); 3952 | } 3953 | 3954 | if (ref($autodetect) eq "CODE") { 3955 | $value = &$autodetect(); 3956 | $compat_value{$mode} = $value; 3957 | $is_autodetect = " (auto-detected)"; 3958 | } 3959 | } 3960 | 3961 | if ($specified{$mode}) { 3962 | if ($value == $COMPAT_VALUE_ON) { 3963 | info("Enabling compatibility mode ". 3964 | "'$name'$is_autodetect\n"); 3965 | } elsif ($value == $COMPAT_VALUE_OFF) { 3966 | info("Disabling compatibility mode ". 3967 | "'$name'$is_autodetect\n"); 3968 | } else { 3969 | info("Using delayed auto-detection for ". 3970 | "compatibility mode ". 3971 | "'$name'\n"); 3972 | } 3973 | } 3974 | } 3975 | } 3976 | 3977 | sub compat_hammer_autodetect() 3978 | { 3979 | if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 || 3980 | $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302) 3981 | { 3982 | info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n"); 3983 | return $COMPAT_VALUE_ON; 3984 | } 3985 | return $COMPAT_VALUE_OFF; 3986 | } 3987 | 3988 | # 3989 | # is_compat(mode) 3990 | # 3991 | # Return non-zero if compatibility mode MODE is enabled. 3992 | # 3993 | 3994 | sub is_compat($) 3995 | { 3996 | my ($mode) = @_; 3997 | 3998 | return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON); 3999 | return 0; 4000 | } 4001 | 4002 | # 4003 | # is_compat_auto(mode) 4004 | # 4005 | # Return non-zero if compatibility mode MODE is set to auto-detect. 4006 | # 4007 | 4008 | sub is_compat_auto($) 4009 | { 4010 | my ($mode) = @_; 4011 | 4012 | return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO); 4013 | return 0; 4014 | } 4015 | -------------------------------------------------------------------------------- /lcov-1.14/bin/genpng: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # Copyright (c) International Business Machines Corp., 2002 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or (at 8 | # your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 | # 19 | # 20 | # genpng 21 | # 22 | # This script creates an overview PNG image of a source code file by 23 | # representing each source code character by a single pixel. 24 | # 25 | # Note that the Perl module GD.pm is required for this script to work. 26 | # It may be obtained from http://www.cpan.org 27 | # 28 | # History: 29 | # 2002-08-26: created by Peter Oberparleiter 30 | # 31 | 32 | use strict; 33 | use warnings; 34 | use File::Basename; 35 | use Getopt::Long; 36 | use Cwd qw/abs_path/; 37 | 38 | 39 | # Constants 40 | our $tool_dir = abs_path(dirname($0)); 41 | our $lcov_version = "LCOV version 1.14"; 42 | our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; 43 | our $tool_name = basename($0); 44 | 45 | 46 | # Prototypes 47 | sub gen_png($$$@); 48 | sub check_and_load_module($); 49 | sub genpng_print_usage(*); 50 | sub genpng_process_file($$$$); 51 | sub genpng_warn_handler($); 52 | sub genpng_die_handler($); 53 | 54 | 55 | # 56 | # Code entry point 57 | # 58 | 59 | # Check whether required module GD.pm is installed 60 | if (check_and_load_module("GD")) 61 | { 62 | # Note: cannot use die() to print this message because inserting this 63 | # code into another script via do() would not fail as required! 64 | print(STDERR < \$tab_size, 86 | "width=i" => \$width, 87 | "output-filename=s" => \$out_filename, 88 | "help" => \$help, 89 | "version" => \$version)) 90 | { 91 | print(STDERR "Use $tool_name --help to get usage ". 92 | "information\n"); 93 | exit(1); 94 | } 95 | 96 | $filename = $ARGV[0]; 97 | 98 | # Check for help flag 99 | if ($help) 100 | { 101 | genpng_print_usage(*STDOUT); 102 | exit(0); 103 | } 104 | 105 | # Check for version flag 106 | if ($version) 107 | { 108 | print("$tool_name: $lcov_version\n"); 109 | exit(0); 110 | } 111 | 112 | # Check options 113 | if (!$filename) 114 | { 115 | die("No filename specified\n"); 116 | } 117 | 118 | # Check for output filename 119 | if (!$out_filename) 120 | { 121 | $out_filename = "$filename.png"; 122 | } 123 | 124 | genpng_process_file($filename, $out_filename, $width, $tab_size); 125 | exit(0); 126 | } 127 | 128 | 129 | # 130 | # genpng_print_usage(handle) 131 | # 132 | # Write out command line usage information to given filehandle. 133 | # 134 | 135 | sub genpng_print_usage(*) 136 | { 137 | local *HANDLE = $_[0]; 138 | 139 | print(HANDLE <) 193 | { 194 | if (/^\t\t(.*)$/) 195 | { 196 | # Uninstrumented line 197 | push(@source, ":$1"); 198 | } 199 | elsif (/^ ###### (.*)$/) 200 | { 201 | # Line with zero execution count 202 | push(@source, "0:$1"); 203 | } 204 | elsif (/^( *)(\d*) (.*)$/) 205 | { 206 | # Line with positive execution count 207 | push(@source, "$2:$3"); 208 | } 209 | } 210 | } 211 | else 212 | { 213 | # Plain text file 214 | while () { push(@source, ":$_"); } 215 | } 216 | close(HANDLE); 217 | 218 | gen_png($out_filename, $width, $tab_size, @source); 219 | } 220 | 221 | 222 | # 223 | # gen_png(filename, width, tab_size, source) 224 | # 225 | # Write an overview PNG file to FILENAME. Source code is defined by SOURCE 226 | # which is a list of lines : per source code line. 227 | # The output image will be made up of one pixel per character of source, 228 | # coloring will be done according to execution counts. WIDTH defines the 229 | # image width. TAB_SIZE specifies the number of spaces to use as replacement 230 | # string for tabulator signs in source code text. 231 | # 232 | # Die on error. 233 | # 234 | 235 | sub gen_png($$$@) 236 | { 237 | my $filename = shift(@_); # Filename for PNG file 238 | my $overview_width = shift(@_); # Imagewidth for image 239 | my $tab_size = shift(@_); # Replacement string for tab signs 240 | my @source = @_; # Source code as passed via argument 2 241 | my $height; # Height as define by source size 242 | my $overview; # Source code overview image data 243 | my $col_plain_back; # Color for overview background 244 | my $col_plain_text; # Color for uninstrumented text 245 | my $col_cov_back; # Color for background of covered lines 246 | my $col_cov_text; # Color for text of covered lines 247 | my $col_nocov_back; # Color for background of lines which 248 | # were not covered (count == 0) 249 | my $col_nocov_text; # Color for test of lines which were not 250 | # covered (count == 0) 251 | my $col_hi_back; # Color for background of highlighted lines 252 | my $col_hi_text; # Color for text of highlighted lines 253 | my $line; # Current line during iteration 254 | my $row = 0; # Current row number during iteration 255 | my $column; # Current column number during iteration 256 | my $color_text; # Current text color during iteration 257 | my $color_back; # Current background color during iteration 258 | my $last_count; # Count of last processed line 259 | my $count; # Count of current line 260 | my $source; # Source code of current line 261 | my $replacement; # Replacement string for tabulator chars 262 | local *PNG_HANDLE; # Handle for output PNG file 263 | 264 | # Handle empty source files 265 | if (!@source) { 266 | @source = ( "" ); 267 | } 268 | $height = scalar(@source); 269 | # Create image 270 | $overview = new GD::Image($overview_width, $height) 271 | or die("ERROR: cannot allocate overview image!\n"); 272 | 273 | # Define colors 274 | $col_plain_back = $overview->colorAllocate(0xff, 0xff, 0xff); 275 | $col_plain_text = $overview->colorAllocate(0xaa, 0xaa, 0xaa); 276 | $col_cov_back = $overview->colorAllocate(0xaa, 0xa7, 0xef); 277 | $col_cov_text = $overview->colorAllocate(0x5d, 0x5d, 0xea); 278 | $col_nocov_back = $overview->colorAllocate(0xff, 0x00, 0x00); 279 | $col_nocov_text = $overview->colorAllocate(0xaa, 0x00, 0x00); 280 | $col_hi_back = $overview->colorAllocate(0x00, 0xff, 0x00); 281 | $col_hi_text = $overview->colorAllocate(0x00, 0xaa, 0x00); 282 | 283 | # Visualize each line 284 | foreach $line (@source) 285 | { 286 | # Replace tabs with spaces to keep consistent with source 287 | # code view 288 | while ($line =~ /^([^\t]*)(\t)/) 289 | { 290 | $replacement = " "x($tab_size - ((length($1) - 1) % 291 | $tab_size)); 292 | $line =~ s/^([^\t]*)(\t)/$1$replacement/; 293 | } 294 | 295 | # Skip lines which do not follow the : 296 | # specification, otherwise $1 = count, $2 = source code 297 | if (!($line =~ /(\*?)(\d*):(.*)$/)) { next; } 298 | $count = $2; 299 | $source = $3; 300 | 301 | # Decide which color pair to use 302 | 303 | # If this line was not instrumented but the one before was, 304 | # take the color of that line to widen color areas in 305 | # resulting image 306 | if (($count eq "") && defined($last_count) && 307 | ($last_count ne "")) 308 | { 309 | $count = $last_count; 310 | } 311 | 312 | if ($count eq "") 313 | { 314 | # Line was not instrumented 315 | $color_text = $col_plain_text; 316 | $color_back = $col_plain_back; 317 | } 318 | elsif ($count == 0) 319 | { 320 | # Line was instrumented but not executed 321 | $color_text = $col_nocov_text; 322 | $color_back = $col_nocov_back; 323 | } 324 | elsif ($1 eq "*") 325 | { 326 | # Line was highlighted 327 | $color_text = $col_hi_text; 328 | $color_back = $col_hi_back; 329 | } 330 | else 331 | { 332 | # Line was instrumented and executed 333 | $color_text = $col_cov_text; 334 | $color_back = $col_cov_back; 335 | } 336 | 337 | # Write one pixel for each source character 338 | $column = 0; 339 | foreach (split("", $source)) 340 | { 341 | # Check for width 342 | if ($column >= $overview_width) { last; } 343 | 344 | if ($_ eq " ") 345 | { 346 | # Space 347 | $overview->setPixel($column++, $row, 348 | $color_back); 349 | } 350 | else 351 | { 352 | # Text 353 | $overview->setPixel($column++, $row, 354 | $color_text); 355 | } 356 | } 357 | 358 | # Fill rest of line 359 | while ($column < $overview_width) 360 | { 361 | $overview->setPixel($column++, $row, $color_back); 362 | } 363 | 364 | $last_count = $2; 365 | 366 | $row++; 367 | } 368 | 369 | # Write PNG file 370 | open (PNG_HANDLE, ">", $filename) 371 | or die("ERROR: cannot write png file $filename!\n"); 372 | binmode(*PNG_HANDLE); 373 | print(PNG_HANDLE $overview->png()); 374 | close(PNG_HANDLE); 375 | } 376 | 377 | sub genpng_warn_handler($) 378 | { 379 | my ($msg) = @_; 380 | 381 | warn("$tool_name: $msg"); 382 | } 383 | 384 | sub genpng_die_handler($) 385 | { 386 | my ($msg) = @_; 387 | 388 | die("$tool_name: $msg"); 389 | } 390 | -------------------------------------------------------------------------------- /lcov-1.14/bin/get_changes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Usage: get_changes.sh 4 | # 5 | # Print lcov change log information as provided by Git 6 | 7 | TOOLDIR=$(cd $(dirname $0) >/dev/null ; pwd) 8 | 9 | cd $TOOLDIR 10 | 11 | if ! git --no-pager log --no-merges --decorate=short --color=never 2>/dev/null ; then 12 | cat "$TOOLDIR/../CHANGES" 2>/dev/null 13 | fi 14 | -------------------------------------------------------------------------------- /lcov-1.14/bin/get_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Usage: get_version.sh --version|--release|--full 4 | # 5 | # Print lcov version or release information as provided by Git, .version 6 | # or a fallback. 7 | 8 | TOOLDIR=$(cd $(dirname $0) >/dev/null ; pwd) 9 | GITVER=$(cd $TOOLDIR ; git describe --tags 2>/dev/null) 10 | 11 | if [ -z "$GITVER" ] ; then 12 | # Get version information from file 13 | if [ -e "$TOOLDIR/../.version" ] ; then 14 | source "$TOOLDIR/../.version" 15 | fi 16 | else 17 | # Get version information from git 18 | FULL=${GITVER:1} 19 | VERSION=${GITVER%%-*} 20 | VERSION=${VERSION:1} 21 | if [ "${GITVER#*-}" != "$GITVER" ] ; then 22 | RELEASE=${GITVER#*-} 23 | RELEASE=${RELEASE/-/.} 24 | fi 25 | fi 26 | 27 | # Fallback 28 | [ -z "$VERSION" ] && VERSION="1.0" 29 | [ -z "$RELEASE" ] && RELEASE="1" 30 | [ -z "$FULL" ] && FULL="$VERSION" 31 | 32 | [ "$1" == "--version" ] && echo -n "$VERSION" 33 | [ "$1" == "--release" ] && echo -n "$RELEASE" 34 | [ "$1" == "--full" ] && echo -n "$FULL" 35 | -------------------------------------------------------------------------------- /lcov-1.14/bin/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # install.sh [--uninstall] sourcefile targetfile [install options] 4 | # 5 | 6 | 7 | # Check for uninstall option 8 | if test "x$1" == "x--uninstall" ; then 9 | UNINSTALL=true 10 | SOURCE=$2 11 | TARGET=$3 12 | shift 3 13 | else 14 | UNINSTALL=false 15 | SOURCE=$1 16 | TARGET=$2 17 | shift 2 18 | fi 19 | 20 | # Check usage 21 | if test -z "$SOURCE" || test -z "$TARGET" ; then 22 | echo Usage: install.sh [--uninstall] source target [install options] >&2 23 | exit 1 24 | fi 25 | 26 | 27 | # 28 | # do_install(SOURCE_FILE, TARGET_FILE) 29 | # 30 | 31 | do_install() 32 | { 33 | local SOURCE=$1 34 | local TARGET=$2 35 | local PARAMS=$3 36 | 37 | install -d $(dirname $TARGET) 38 | install -p $PARAMS $SOURCE $TARGET 39 | if [ -n "$LCOV_PERL_PATH" ] ; then 40 | # Replace Perl interpreter specification 41 | sed -e "1 s%^#\!.*perl.*$%#\!$LCOV_PERL_PATH%" -i $TARGET 42 | fi 43 | } 44 | 45 | 46 | # 47 | # do_uninstall(SOURCE_FILE, TARGET_FILE) 48 | # 49 | 50 | do_uninstall() 51 | { 52 | local SOURCE=$1 53 | local TARGET=$2 54 | 55 | # Does target exist? 56 | if test -r $TARGET ; then 57 | # Is target of the same version as this package? 58 | if diff -I '^our \$lcov_version' -I '^\.TH ' -I '^#!' $SOURCE $TARGET >/dev/null; then 59 | rm -f $TARGET 60 | else 61 | echo WARNING: Skipping uninstall for $TARGET - versions differ! >&2 62 | fi 63 | else 64 | echo WARNING: Skipping uninstall for $TARGET - not installed! >&2 65 | fi 66 | } 67 | 68 | 69 | # Call sub routine 70 | if $UNINSTALL ; then 71 | do_uninstall $SOURCE $TARGET 72 | else 73 | do_install $SOURCE $TARGET "$*" 74 | fi 75 | 76 | exit 0 77 | -------------------------------------------------------------------------------- /lcov-1.14/bin/updateversion.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use File::Basename; 7 | 8 | sub update_man_page($); 9 | sub update_bin_tool($); 10 | sub update_txt_file($); 11 | sub update_spec_file($); 12 | sub write_version_file($); 13 | sub get_file_info($); 14 | 15 | our $directory = $ARGV[0]; 16 | our $version = $ARGV[1]; 17 | our $release = $ARGV[2]; 18 | our $full = $ARGV[3]; 19 | 20 | our @man_pages = ("man/gendesc.1", "man/genhtml.1", "man/geninfo.1", 21 | "man/genpng.1", "man/lcov.1", "man/lcovrc.5"); 22 | our @bin_tools = ("bin/gendesc", "bin/genhtml", "bin/geninfo", 23 | "bin/genpng", "bin/lcov"); 24 | our @txt_files = ("README"); 25 | our @spec_files = ("rpm/lcov.spec"); 26 | 27 | if (!defined($directory) || !defined($version) || !defined($release)) { 28 | die("Usage: $0 DIRECTORY|FILE VERSION RELEASE FULL_VERSION\n"); 29 | } 30 | 31 | # Determine mode of operation 32 | if (-f $directory) { 33 | my $file = $directory; 34 | my $base = basename($file); 35 | 36 | if (grep(/^$base$/, map({ basename($_) } @man_pages))) { 37 | print("Updating man page $file\n"); 38 | update_man_page($file); 39 | } elsif (grep(/^$base$/, map({ basename($_) } @bin_tools))) { 40 | print("Updating bin tool $file\n"); 41 | update_bin_tool($file); 42 | } elsif (grep(/^$base$/, map({ basename($_) } @txt_files))) { 43 | print("Updating text file $file\n"); 44 | update_txt_file($file); 45 | } elsif (grep(/^$base$/, map({ basename($_) } @spec_files))) { 46 | print("Updating spec file $file\n"); 47 | update_spec_file($file); 48 | } elsif ($base eq ".version") { 49 | print("Updating version file $file\n"); 50 | write_version_file($file); 51 | } else { 52 | print("WARNING: Skipping unknown file $file\n"); 53 | } 54 | print("Done.\n"); 55 | exit(0); 56 | } 57 | 58 | foreach (@man_pages) { 59 | print("Updating man page $_\n"); 60 | update_man_page($directory."/".$_); 61 | } 62 | foreach (@bin_tools) { 63 | print("Updating bin tool $_\n"); 64 | update_bin_tool($directory."/".$_); 65 | } 66 | foreach (@txt_files) { 67 | print("Updating text file $_\n"); 68 | update_txt_file($directory."/".$_); 69 | } 70 | foreach (@spec_files) { 71 | print("Updating spec file $_\n"); 72 | update_spec_file($directory."/".$_); 73 | } 74 | print("Updating version file $directory/.version\n"); 75 | write_version_file("$directory/.version"); 76 | print("Done.\n"); 77 | 78 | sub get_file_info($) 79 | { 80 | my ($filename) = @_; 81 | my ($sec, $min, $hour, $year, $month, $day); 82 | my @stat; 83 | my $gittime; 84 | 85 | return (0, 0, 0) if (!-e $filename); 86 | @stat = stat($filename); 87 | ($sec, $min, $hour, $day, $month, $year) = gmtime($stat[9]); 88 | $year += 1900; 89 | $month += 1; 90 | 91 | return (sprintf("%04d-%02d-%02d", $year, $month, $day), 92 | sprintf("%04d%02d%02d%02d%02d.%02d", $year, $month, $day, 93 | $hour, $min, $sec), 94 | sprintf("%o", $stat[2] & 07777)); 95 | } 96 | 97 | sub update_man_page($) 98 | { 99 | my ($filename) = @_; 100 | my @date = get_file_info($filename); 101 | my $date_string = $date[0]; 102 | local *IN; 103 | local *OUT; 104 | 105 | $date_string =~ s/-/\\-/g; 106 | open(IN, "<$filename") || die ("Error: cannot open $filename\n"); 107 | open(OUT, ">$filename.new") || 108 | die("Error: cannot create $filename.new\n"); 109 | while () { 110 | s/\"LCOV\s+\d+\.\d+\"/\"LCOV $version\"/g; 111 | s/\d\d\d\d\\\-\d\d\\\-\d\d/$date_string/g; 112 | print(OUT $_); 113 | } 114 | close(OUT); 115 | close(IN); 116 | chmod(oct($date[2]), "$filename.new"); 117 | system("mv", "-f", "$filename.new", "$filename"); 118 | system("touch", "$filename", "-t", $date[1]); 119 | } 120 | 121 | sub update_bin_tool($) 122 | { 123 | my ($filename) = @_; 124 | my @date = get_file_info($filename); 125 | local *IN; 126 | local *OUT; 127 | 128 | open(IN, "<$filename") || die ("Error: cannot open $filename\n"); 129 | open(OUT, ">$filename.new") || 130 | die("Error: cannot create $filename.new\n"); 131 | while () { 132 | s/^(our\s+\$lcov_version\s*=).*$/$1 "LCOV version $full";/g; 133 | print(OUT $_); 134 | } 135 | close(OUT); 136 | close(IN); 137 | chmod(oct($date[2]), "$filename.new"); 138 | system("mv", "-f", "$filename.new", "$filename"); 139 | system("touch", "$filename", "-t", $date[1]); 140 | } 141 | 142 | sub update_txt_file($) 143 | { 144 | my ($filename) = @_; 145 | my @date = get_file_info($filename); 146 | local *IN; 147 | local *OUT; 148 | 149 | open(IN, "<$filename") || die ("Error: cannot open $filename\n"); 150 | open(OUT, ">$filename.new") || 151 | die("Error: cannot create $filename.new\n"); 152 | while () { 153 | s/(Last\s+changes:\s+)\d\d\d\d-\d\d-\d\d/$1$date[0]/g; 154 | print(OUT $_); 155 | } 156 | close(OUT); 157 | close(IN); 158 | chmod(oct($date[2]), "$filename.new"); 159 | system("mv", "-f", "$filename.new", "$filename"); 160 | system("touch", "$filename", "-t", $date[1]); 161 | } 162 | 163 | sub update_spec_file($) 164 | { 165 | my ($filename) = @_; 166 | my @date = get_file_info($filename); 167 | local *IN; 168 | local *OUT; 169 | 170 | open(IN, "<$filename") || die ("Error: cannot open $filename\n"); 171 | open(OUT, ">$filename.new") || 172 | die("Error: cannot create $filename.new\n"); 173 | while () { 174 | s/^(Version:\s*)\d+\.\d+.*$/$1$version/; 175 | s/^(Release:\s*).*$/$1$release/; 176 | print(OUT $_); 177 | } 178 | close(OUT); 179 | close(IN); 180 | system("mv", "-f", "$filename.new", "$filename"); 181 | system("touch", "$filename", "-t", $date[1]); 182 | } 183 | 184 | sub write_version_file($) 185 | { 186 | my ($filename) = @_; 187 | my $fd; 188 | 189 | open($fd, ">", $filename) or die("Error: cannot write $filename: $!\n"); 190 | print($fd "VERSION=$version\n"); 191 | print($fd "RELEASE=$release\n"); 192 | print($fd "FULL=$full\n"); 193 | close($fd); 194 | } 195 | -------------------------------------------------------------------------------- /lcov_cobertura.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2011-2012 Eric Wendelin 4 | # 5 | # This is free software, licensed under the Apache License, Version 2.0, 6 | # available in the accompanying LICENSE.txt file. 7 | 8 | """ 9 | Converts lcov line coverage output to Cobertura-compatible XML for CI 10 | """ 11 | 12 | import re 13 | import sys 14 | import os 15 | import time 16 | import subprocess 17 | from xml.dom import minidom 18 | from optparse import OptionParser 19 | 20 | from distutils.spawn import find_executable 21 | 22 | CPPFILT = "c++filt" 23 | HAVE_CPPFILT = False 24 | 25 | if find_executable(CPPFILT) is not None: 26 | HAVE_CPPFILT = True 27 | 28 | VERSION = '1.6' 29 | __all__ = ['LcovCobertura'] 30 | 31 | 32 | class Demangler(object): 33 | def __init__(self): 34 | self.pipe = subprocess.Popen( 35 | CPPFILT, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 36 | 37 | def demangle(self, name): 38 | self.pipe.stdin.write(name + "\n") 39 | return self.pipe.stdout.readline().rstrip() 40 | 41 | 42 | class LcovCobertura(object): 43 | """ 44 | Converts code coverage report files in lcov format to Cobertura's XML 45 | report format so that CI servers like Jenkins can aggregate results and 46 | determine build stability etc. 47 | 48 | >>> from lcov_cobertura import LcovCobertura 49 | >>> LCOV_INPUT = 'your lcov input' 50 | >>> converter = LcovCobertura(LCOV_INPUT) 51 | >>> cobertura_xml = converter.convert() 52 | >>> print(cobertura_xml) 53 | """ 54 | 55 | def __init__(self, lcov_data, base_dir='.', excludes=None, demangle=False): 56 | """ 57 | Create a new :class:`LcovCobertura` object using the given `lcov_data` 58 | and `options`. 59 | 60 | :param lcov_data: Path to LCOV data file 61 | :type lcov_data: string 62 | :param base_dir: Path upon which to base all sources 63 | :type base_dir: string 64 | :param excludes: list of regexes to packages as excluded 65 | :type excludes: [string] 66 | :param demangle: whether to demangle function names using c++filt 67 | :type demangle: bool 68 | """ 69 | 70 | if not excludes: 71 | excludes = [] 72 | self.lcov_data = lcov_data 73 | self.base_dir = base_dir 74 | self.excludes = excludes 75 | if demangle: 76 | demangler = Demangler() 77 | self.format = demangler.demangle 78 | else: 79 | self.format = lambda x: x 80 | 81 | def convert(self): 82 | """ 83 | Convert lcov file to cobertura XML using options from this instance. 84 | """ 85 | coverage_data = self.parse() 86 | return self.generate_cobertura_xml(coverage_data) 87 | 88 | def parse(self): 89 | """ 90 | Generate a data structure representing it that can be serialized in any 91 | logical format. 92 | """ 93 | 94 | coverage_data = { 95 | 'packages': {}, 96 | 'summary': {'lines-total': 0, 'lines-covered': 0, 97 | 'branches-total': 0, 'branches-covered': 0}, 98 | 'timestamp': str(int(time.time())) 99 | } 100 | package = None 101 | current_file = None 102 | file_lines_total = 0 103 | file_lines_covered = 0 104 | file_lines = {} 105 | file_methods = {} 106 | file_branches_total = 0 107 | file_branches_covered = 0 108 | 109 | for line in self.lcov_data.split('\n'): 110 | if line.strip() == 'end_of_record': 111 | if current_file is not None: 112 | package_dict = coverage_data['packages'][package] 113 | package_dict['lines-total'] += file_lines_total 114 | package_dict['lines-covered'] += file_lines_covered 115 | package_dict['branches-total'] += file_branches_total 116 | package_dict['branches-covered'] += file_branches_covered 117 | file_dict = package_dict['classes'][current_file] 118 | file_dict['lines-total'] = file_lines_total 119 | file_dict['lines-covered'] = file_lines_covered 120 | file_dict['lines'] = dict(file_lines) 121 | file_dict['methods'] = dict(file_methods) 122 | file_dict['branches-total'] = file_branches_total 123 | file_dict['branches-covered'] = file_branches_covered 124 | coverage_data['summary']['lines-total'] += file_lines_total 125 | coverage_data['summary']['lines-covered'] += file_lines_covered 126 | coverage_data['summary']['branches-total'] += file_branches_total 127 | coverage_data['summary']['branches-covered'] += file_branches_covered 128 | 129 | line_parts = line.split(':', 1) 130 | input_type = line_parts[0] 131 | 132 | if input_type == 'SF': 133 | # Get file name 134 | file_name = line_parts[-1].strip() 135 | relative_file_name = os.path.relpath(file_name, self.base_dir) 136 | package = '.'.join(relative_file_name.split(os.path.sep)[0:-1]) 137 | class_name = '.'.join(relative_file_name.split(os.path.sep)) 138 | if package not in coverage_data['packages']: 139 | coverage_data['packages'][package] = { 140 | 'classes': {}, 'lines-total': 0, 'lines-covered': 0, 141 | 'branches-total': 0, 'branches-covered': 0 142 | } 143 | coverage_data['packages'][package]['classes'][ 144 | relative_file_name] = { 145 | 'name': class_name, 'lines': {}, 'lines-total': 0, 146 | 'lines-covered': 0, 'branches-total': 0, 147 | 'branches-covered': 0 148 | } 149 | package = package 150 | current_file = relative_file_name 151 | file_lines_total = 0 152 | file_lines_covered = 0 153 | file_lines.clear() 154 | file_methods.clear() 155 | file_branches_total = 0 156 | file_branches_covered = 0 157 | elif input_type == 'DA': 158 | # DA:2,0 159 | (line_number, line_hits) = line_parts[-1].strip().split(',') 160 | line_number = int(line_number) 161 | if line_number not in file_lines: 162 | file_lines[line_number] = { 163 | 'branch': 'false', 'branches-total': 0, 164 | 'branches-covered': 0 165 | } 166 | file_lines[line_number]['hits'] = line_hits 167 | # Increment lines total/covered for class and package 168 | try: 169 | if int(line_hits) > 0: 170 | file_lines_covered += 1 171 | except: 172 | pass 173 | file_lines_total += 1 174 | elif input_type == 'BRDA': 175 | # BRDA:1,1,2,0 176 | (line_number, block_number, branch_number, branch_hits) = line_parts[-1].strip().split(',') 177 | line_number = int(line_number) 178 | if line_number not in file_lines: 179 | file_lines[line_number] = { 180 | 'branch': 'true', 'branches-total': 0, 181 | 'branches-covered': 0, 'hits': 0 182 | } 183 | file_lines[line_number]['branch'] = 'true' 184 | file_lines[line_number]['branches-total'] += 1 185 | file_branches_total += 1 186 | if branch_hits != '-' and int(branch_hits) > 0: 187 | file_lines[line_number]['branches-covered'] += 1 188 | file_branches_covered += 1 189 | elif input_type == 'BRF': 190 | file_branches_total = int(line_parts[1]) 191 | elif input_type == 'BRH': 192 | file_branches_covered = int(line_parts[1]) 193 | elif input_type == 'FN': 194 | # FN:5,(anonymous_1) 195 | function_line, function_name = line_parts[-1].strip().split(',') 196 | file_methods[function_name] = [function_line, '0'] 197 | elif input_type == 'FNDA': 198 | # FNDA:0,(anonymous_1) 199 | (function_hits, function_name) = line_parts[-1].strip().split(',') 200 | if function_name not in file_methods: 201 | file_methods[function_name] = ['0', '0'] 202 | file_methods[function_name][-1] = function_hits 203 | 204 | # Exclude packages 205 | excluded = [x for x in coverage_data['packages'] for e in self.excludes 206 | if re.match(e, x)] 207 | for package in excluded: 208 | del coverage_data['packages'][package] 209 | 210 | # Compute line coverage rates 211 | for package_data in list(coverage_data['packages'].values()): 212 | package_data['line-rate'] = self._percent( 213 | package_data['lines-total'], 214 | package_data['lines-covered']) 215 | package_data['branch-rate'] = self._percent( 216 | package_data['branches-total'], 217 | package_data['branches-covered']) 218 | 219 | return coverage_data 220 | 221 | def generate_cobertura_xml(self, coverage_data): 222 | """ 223 | Given parsed coverage data, return a String cobertura XML representation. 224 | 225 | :param coverage_data: Nested dict representing coverage information. 226 | :type coverage_data: dict 227 | """ 228 | 229 | dom_impl = minidom.getDOMImplementation() 230 | doctype = dom_impl.createDocumentType("coverage", None, 231 | "http://cobertura.sourceforge.net/xml/coverage-04.dtd") 232 | document = dom_impl.createDocument(None, "coverage", doctype) 233 | root = document.documentElement 234 | summary = coverage_data['summary'] 235 | self._attrs(root, { 236 | 'branch-rate': self._percent(summary['branches-total'], 237 | summary['branches-covered']), 238 | 'branches-covered': str(summary['branches-covered']), 239 | 'branches-valid': str(summary['branches-total']), 240 | 'complexity': '0', 241 | 'line-rate': self._percent(summary['lines-total'], 242 | summary['lines-covered']), 243 | 'lines-covered': str(summary['lines-covered']), 244 | 'lines-valid': str(summary['lines-total']), 245 | 'timestamp': coverage_data['timestamp'], 246 | 'version': '2.0.3' 247 | }) 248 | 249 | sources = self._el(document, 'sources', {}) 250 | source = self._el(document, 'source', {}) 251 | source.appendChild(document.createTextNode(self.base_dir)) 252 | sources.appendChild(source) 253 | 254 | root.appendChild(sources) 255 | 256 | packages_el = self._el(document, 'packages', {}) 257 | 258 | packages = coverage_data['packages'] 259 | for package_name, package_data in list(packages.items()): 260 | package_el = self._el(document, 'package', { 261 | 'line-rate': package_data['line-rate'], 262 | 'branch-rate': package_data['branch-rate'], 263 | 'name': package_name, 264 | 'complexity': '0', 265 | }) 266 | classes_el = self._el(document, 'classes', {}) 267 | for class_name, class_data in list(package_data['classes'].items()): 268 | class_el = self._el(document, 'class', { 269 | 'branch-rate': self._percent(class_data['branches-total'], 270 | class_data['branches-covered']), 271 | 'complexity': '0', 272 | 'filename': class_name, 273 | 'line-rate': self._percent(class_data['lines-total'], 274 | class_data['lines-covered']), 275 | 'name': class_data['name'] 276 | }) 277 | 278 | # Process methods 279 | methods_el = self._el(document, 'methods', {}) 280 | for method_name, (line, hits) in list(class_data['methods'].items()): 281 | method_el = self._el(document, 'method', { 282 | 'name': self.format(method_name), 283 | 'signature': '', 284 | 'line-rate': '1.0' if int(hits) > 0 else '0.0', 285 | 'branch-rate': '1.0' if int(hits) > 0 else '0.0', 286 | }) 287 | method_lines_el = self._el(document, 'lines', {}) 288 | method_line_el = self._el(document, 'line', { 289 | 'hits': hits, 290 | 'number': line, 291 | 'branch': 'false', 292 | }) 293 | method_lines_el.appendChild(method_line_el) 294 | method_el.appendChild(method_lines_el) 295 | methods_el.appendChild(method_el) 296 | 297 | # Process lines 298 | lines_el = self._el(document, 'lines', {}) 299 | lines = list(class_data['lines'].keys()) 300 | lines.sort() 301 | for line_number in lines: 302 | line_el = self._el(document, 'line', { 303 | 'branch': class_data['lines'][line_number]['branch'], 304 | 'hits': str(class_data['lines'][line_number]['hits']), 305 | 'number': str(line_number) 306 | }) 307 | if class_data['lines'][line_number]['branch'] == 'true': 308 | total = int(class_data['lines'][line_number]['branches-total']) 309 | covered = int(class_data['lines'][line_number]['branches-covered']) 310 | percentage = int((covered * 100.0) / total) 311 | line_el.setAttribute('condition-coverage', 312 | '{0}% ({1}/{2})'.format( 313 | percentage, covered, total)) 314 | lines_el.appendChild(line_el) 315 | 316 | class_el.appendChild(methods_el) 317 | class_el.appendChild(lines_el) 318 | classes_el.appendChild(class_el) 319 | package_el.appendChild(classes_el) 320 | packages_el.appendChild(package_el) 321 | root.appendChild(packages_el) 322 | 323 | return document.toprettyxml() 324 | 325 | def _el(self, document, name, attrs): 326 | """ 327 | Create an element within document with given name and attributes. 328 | 329 | :param document: Document element 330 | :type document: Document 331 | :param name: Element name 332 | :type name: string 333 | :param attrs: Attributes for element 334 | :type attrs: dict 335 | """ 336 | return self._attrs(document.createElement(name), attrs) 337 | 338 | def _attrs(self, element, attrs): 339 | """ 340 | Set attributes on given element. 341 | 342 | :param element: DOM Element 343 | :type element: Element 344 | :param attrs: Attributes for element 345 | :type attrs: dict 346 | """ 347 | for attr, val in list(attrs.items()): 348 | element.setAttribute(attr, val) 349 | return element 350 | 351 | def _percent(self, lines_total, lines_covered): 352 | """ 353 | Get the percentage of lines covered in the total, with formatting. 354 | 355 | :param lines_total: Total number of lines in given module 356 | :type lines_total: number 357 | :param lines_covered: Number of lines covered by tests in module 358 | :type lines_covered: number 359 | """ 360 | 361 | if lines_total == 0: 362 | return '0.0' 363 | return str(float(float(lines_covered) / float(lines_total))) 364 | 365 | 366 | def main(argv=None): 367 | """ 368 | Converts LCOV coverage data to Cobertura-compatible XML for reporting. 369 | 370 | Usage: 371 | lcov_cobertura.py lcov-file.dat 372 | lcov_cobertura.py lcov-file.dat -b src/dir -e test.lib -o path/out.xml 373 | 374 | By default, XML output will be written to ./coverage.xml 375 | """ 376 | if argv is None: 377 | argv = sys.argv 378 | parser = OptionParser() 379 | parser.usage = ('lcov_cobertura.py lcov-file.dat [-b source/dir] ' 380 | '[-e ] [-o output.xml] [-d]') 381 | parser.description = 'Converts lcov output to cobertura-compatible XML' 382 | parser.add_option('-b', '--base-dir', action='store', 383 | help='Directory where source files are located', 384 | dest='base_dir', default='.') 385 | parser.add_option('-e', '--excludes', 386 | help='Comma-separated list of regexes of packages to exclude', 387 | action='append', dest='excludes', default=[]) 388 | parser.add_option('-o', '--output', 389 | help='Path to store cobertura xml file', 390 | action='store', dest='output', default='coverage.xml') 391 | parser.add_option('-d', '--demangle', 392 | help='Demangle C++ function names using %s' % CPPFILT, 393 | action='store_true', dest='demangle', default=False) 394 | (options, args) = parser.parse_args(args=argv) 395 | 396 | if options.demangle and not HAVE_CPPFILT: 397 | raise RuntimeError("C++ filter executable (%s) not found!" % CPPFILT) 398 | 399 | if len(args) != 2: 400 | print(main.__doc__) 401 | sys.exit(1) 402 | 403 | try: 404 | with open(args[1], 'r') as lcov_file: 405 | lcov_data = lcov_file.read() 406 | lcov_cobertura = LcovCobertura(lcov_data, options.base_dir, options.excludes, options.demangle) 407 | cobertura_xml = lcov_cobertura.convert() 408 | with open(options.output, mode='wt') as output_file: 409 | output_file.write(cobertura_xml) 410 | except IOError: 411 | sys.stderr.write("Unable to convert %s to Cobertura XML" % args[1]) 412 | 413 | if __name__ == '__main__': 414 | main() -------------------------------------------------------------------------------- /llvm-cov-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 3 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 4 | 5 | if [ "$1" = "-v" ]; then 6 | echo "llvm-cov-wrapper 4.2.1" 7 | exit 0 8 | else 9 | /usr/bin/gcov "$@" 10 | fi 11 | -------------------------------------------------------------------------------- /run_code_coverage_post.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # XcodeCoverage by Jon Reid, https://qualitycoding.org 3 | # Copyright 2021 Quality Coding, Inc. See LICENSE.txt 4 | 5 | button=`/usr/bin/osascript < 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 22 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 56 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | --------------------------------------------------------------------------------