pax_global_header00006660000000000000000000000064147677423370014536gustar00rootroot0000000000000052 comment=14c2e0174a7196040cc54584465600fc4e1ea946 rizsotto-Bear-14c2e01/000077500000000000000000000000001476774233700146235ustar00rootroot00000000000000rizsotto-Bear-14c2e01/.github/000077500000000000000000000000001476774233700161635ustar00rootroot00000000000000rizsotto-Bear-14c2e01/.github/FUNDING.yml000066400000000000000000000000231476774233700177730ustar00rootroot00000000000000github: [rizsotto] rizsotto-Bear-14c2e01/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476774233700203465ustar00rootroot00000000000000rizsotto-Bear-14c2e01/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000024371476774233700230460ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. **Environment:** - OS name: [e.g. linux] run `uname -s` - OS version: [e.g. CentOS 7.6] - OS architecture: [e.g. `x86_64`] run `uname -m` - Bear version: [e.g. `3.15.0`] - Bear install method: [e.g. from package] **Additional context** - Can you give us a reference to the project that you are running against this tool? [e.g. No if it's a close source project, but in case of an open source project a link to the sources could be nice.] - What build tools this project is using? [e.g. GNU make wtih `configure` script] - What architecture you are trying to compile for? [e.g. cross compiling] - Could you attach build logs? If you can re-run the command which failed (e.g. `bear -- make`) with extra verbose log switches (e.g. `bear --verbose -- make`) and send the output of it, that would help a lot. **Before you send...** - Have you read the `README.md` file or `man bear`? - Have you looked what other open issues Bear has? - Are you sure that the build works without Bear? rizsotto-Bear-14c2e01/.github/workflows/000077500000000000000000000000001476774233700202205ustar00rootroot00000000000000rizsotto-Bear-14c2e01/.github/workflows/build_on_arch.yml000066400000000000000000000046701476774233700235420ustar00rootroot00000000000000name: continuous integration on arch on: push: branches: - master pull_request: branches: - master jobs: build_on_arch: runs-on: ubuntu-22.04 name: Build on ${{ matrix.distro }} for ${{ matrix.arch }} strategy: matrix: include: - arch: aarch64 distro: alpine_latest - arch: s390x distro: alpine_latest - arch: ppc64le distro: alpine_latest steps: - uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v2 name: Build id: build with: arch: ${{ matrix.arch }} distro: ${{ matrix.distro }} githubToken: ${{ github.token }} setup: | mkdir -p "${PWD}/bear_build" mkdir -p "${PWD}/bear_install" dockerRunArgs: | --volume "${PWD}:/work" env: | SOURCE_DIR: /work BUILD_DIR: /work/bear_build INSTALL_DIR: /work/bear_install C_FLAGS: -Wall -Wextra -pedantic CXX_FLAGS: -Wall -Wextra -pedantic CTEST_OUTPUT_ON_FAILURE: 1 # Install some dependencies in the container. This speeds up builds if # you are also using githubToken. Any dependencies installed here will # be part of the container image that gets cached, so subsequent # builds don't have to re-install them. The image layer is cached # publicly in your project's package repository, so it is vital that # no secrets are present in the container state or logs. install: | case "${{ matrix.distro }}" in fedora*) dnf -y update dnf -y install python cmake gcc gcc-c++ pkg-config json-devel spdlog-devel fmt-devel grpc-devel grpc-plugins gtest-devel gmock-devel ;; alpine*) apk update apk add python3 cmake gcc g++ pkgconf make fmt-dev spdlog-dev nlohmann-json protobuf-dev grpc grpc-dev c-ares-dev re2-dev gtest-dev ;; arch*) pacman -Sy --noconfirm python cmake make gcc pkg-config grpc spdlog fmt nlohmann-json gtest gmock ;; esac run: | cmake -B "$BUILD_DIR" -S "$SOURCE_DIR" -DCMAKE_INSTALL_PREFIX:PATH="$INSTALL_DIR" -DENABLE_FUNC_TESTS=OFF cmake --build "$BUILD_DIR" --parallel 2 --target install rizsotto-Bear-14c2e01/.github/workflows/build_on_push.yml000066400000000000000000000046301476774233700236000ustar00rootroot00000000000000name: continuous integration on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} name: Build on ${{ matrix.os }} with multilib ${{ matrix.multilib }} strategy: matrix: include: - os: ubuntu-latest multilib: true - os: ubuntu-latest multilib: false - os: macos-latest multilib: false steps: - name: Checkout code uses: actions/checkout@v4 - name: Install package dependencies if: startsWith( matrix.os, 'ubuntu' ) run: | sudo apt-get update -y sudo apt-get install -y cmake pkg-config gfortran nvidia-cuda-toolkit libtool-bin valgrind - name: Install package dependencies if: startsWith( matrix.os, 'macos' ) run: | brew install cmake pkg-config fmt spdlog nlohmann-json grpc echo "PKG_CONFIG_PATH=$(brew --prefix)/opt/openssl@1.1/lib/pkgconfig" >> $GITHUB_ENV - uses: actions/setup-python@v5 with: python-version: '3.x' cache: 'pip' cache-dependency-path: '**/requirements.txt' - name: Prepare run: | pip install -r test/requirements.txt mkdir "$HOME/work/bear_build" mkdir "$HOME/work/bear_install" echo "BUILD_DIR=$HOME/work/bear_build" >> $GITHUB_ENV echo "INSTALL_DIR=$HOME/work/bear_install" >> $GITHUB_ENV echo "ZLIB_SRC_DIR=$HOME/work/zlib" >> $GITHUB_ENV echo "$HOME/work/bear_install/bin:$PATH" >> $GITHUB_PATH - name: Build env: CTEST_OUTPUT_ON_FAILURE: 1 C_FLAGS: -Wall -Wextra -pedantic CXX_FLAGS: -Wall -Wextra -pedantic CMAKE_OPTIONS: -DENABLE_MULTILIB=${{ matrix.multilib }} run: | cmake -B "$BUILD_DIR" -S "$GITHUB_WORKSPACE" $CMAKE_OPTIONS -DCMAKE_INSTALL_PREFIX:PATH="$INSTALL_DIR" -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu cmake --build "$BUILD_DIR" --parallel 1 --target install - name: Run [functional test] run: | git clone https://github.com/madler/zlib.git $ZLIB_SRC_DIR -b v1.2.11 mkdir $HOME/work/zlib_compilations && cd $HOME/work/zlib_compilations bear --help bear -- $ZLIB_SRC_DIR/configure bear -- make cat compile_commands.json $GITHUB_WORKSPACE/test/bin/assert_compilation compile_commands.json count -gt 30 rizsotto-Bear-14c2e01/.github/workflows/build_rust.yml000066400000000000000000000017601476774233700231230ustar00rootroot00000000000000name: rust CI on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: rustup component add clippy && rustup update stable && rustup default stable - run: cd rust && cargo clippy compile: name: Compile runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: rustup update stable && rustup default stable - run: cd rust && cargo check --verbose test: name: Test strategy: matrix: os: - ubuntu-latest - windows-latest - macOS-latest toolchain: - stable - beta - nightly runs-on: ${{ matrix.os }} needs: [compile] steps: - uses: actions/checkout@v3 - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - run: cd rust && cargo build --verbose - run: cd rust && cargo test --verbose rizsotto-Bear-14c2e01/.gitignore000066400000000000000000000001151476774233700166100ustar00rootroot00000000000000.idea/ cmake-build*/ Output/ Cargo.lock target/ .DS_Store cmake-build-debug/ rizsotto-Bear-14c2e01/CMakeLists.txt000066400000000000000000000132111476774233700173610ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12 FATAL_ERROR) cmake_policy(VERSION 3.12) project(Bear VERSION 3.1.6 DESCRIPTION "Bear is a tool to generate compilation database for clang tooling." LANGUAGES C CXX ) # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") cmake_policy(SET CMP0135 NEW) endif() option(ENABLE_UNIT_TESTS "Build and run unit test for this project" ON) option(ENABLE_FUNC_TESTS "Build and run functional test for this project" ON) option(ENABLE_MULTILIB "Enable to build with multilib support" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(default_build_type "Release") if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif () # Superbuild variables for sub projects include(ExternalProject) set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects) set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage) set(DEPENDENCIES_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/subprojects/Install) include(GNUInstallDirs) # Verify or install dependencies add_subdirectory(third_party) unset(CMAKE_CACHE_ARGS_EXTRA) if (CMAKE_PROJECT_INCLUDE AND NOT CMAKE_PROJECT_INCLUDE STREQUAL "") set(CMAKE_CACHE_ARGS_EXTRA "-DCMAKE_PROJECT_INCLUDE:PATH=${CMAKE_PROJECT_INCLUDE}") endif() # Build the project itself ExternalProject_Add(BearSource SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/source" DEPENDS nlohmann_json_dependency fmt_dependency spdlog_dependency grpc_dependency googletest_dependency CMAKE_ARGS -DENABLE_UNIT_TESTS:BOOL=${ENABLE_UNIT_TESTS} -DENABLE_MULTILIB:BOOL=${ENABLE_MULTILIB} -DPKG_CONFIG_EXECUTABLE:PATH=${PKG_CONFIG_EXECUTABLE} CMAKE_CACHE_ARGS -DCMAKE_PROJECT_VERSION:STRING=${CMAKE_PROJECT_VERSION} -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_IGNORE_PATH:PATH=${CMAKE_IGNORE_PATH} -DCMAKE_SYSROOT:PATH=${CMAKE_SYSROOT} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX:PATH=${STAGED_INSTALL_PREFIX} -DCMAKE_INSTALL_LIBDIR:PATH=${CMAKE_INSTALL_LIBDIR} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} -DROOT_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} ${CMAKE_CACHE_ARGS_EXTRA} BUILD_ALWAYS 1 TEST_BEFORE_INSTALL 1 TEST_COMMAND ctest # or `ctest -T memcheck` ) # Run the functional tests if (ENABLE_FUNC_TESTS) ExternalProject_Add(BearTest SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test" DEPENDS BearSource CMAKE_CACHE_ARGS -DCMAKE_INSTALL_LIBDIR:PATH=${CMAKE_INSTALL_LIBDIR} -DCMAKE_INSTALL_BINDIR:PATH=${CMAKE_INSTALL_BINDIR} -DSTAGED_INSTALL_PREFIX:PATH=${STAGED_INSTALL_PREFIX} TEST_BEFORE_INSTALL 1 INSTALL_COMMAND "" TEST_COMMAND ctest --verbose ) endif () # Install the project artifacts from the staged directory include(GNUInstallDirs) install(DIRECTORY ${STAGED_INSTALL_PREFIX}/ DESTINATION . USE_SOURCE_PERMISSIONS ) install(FILES COPYING README.md INSTALL.md CONTRIBUTING.md CODE_OF_CONDUCT.md DESTINATION ${CMAKE_INSTALL_DOCDIR} ) # Set up package from this project set(CPACK_PACKAGE_NAME "bear") set(CPACK_PACKAGE_CONTACT "László Nagy") set(CPACK_PACKAGE_VENDOR ${CPACK_PACKAGE_CONTACT}) set(CPACK_PACKAGE_VERSION ${CMAKE_PROJECT_VERSION}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "BuildEAR") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) set(CPACK_RPM_PACKAGE_RELEASE "1%{?dist}") set(CPACK_RPM_PACKAGE_LICENSE "GPLv3") set(CPACK_RPM_PACKAGE_GROUP "Development/Tools") set(CPACK_RPM_PACKAGE_URL "http://github.com/rizsotto/Bear") set(CPACK_RPM_PACKAGE_DESCRIPTION "Bear is a tool to generate compilation database for clang tooling.") set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${CMAKE_INSTALL_MANDIR}" "${CMAKE_INSTALL_MANDIR}/man1") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) include(CPack) rizsotto-Bear-14c2e01/CODE_OF_CONDUCT.md000066400000000000000000000121441476774233700174240ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at *rizsotto@gmail.com*. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. rizsotto-Bear-14c2e01/CONTRIBUTING.md000066400000000000000000000025271476774233700170620ustar00rootroot00000000000000# How to contribute Thank you for taking the time to contribute! ## Reporting bug - Please read the documentation, maybe it's a limitation, a known issue of that release. This might also help to clear false expectation from the tool itself, or help you to classify you request not as a bug, but maybe an enhancement. - Ensure that the bug was not already reported by searching on GitHub under Issues. - If you have not found an open issue addressing the problem, open a new one. Be sure to include a title and clear description, with as much relevant information as possible. You might also attach the output of the tool, this case try to run it with verbose flags. - For more detailed information on submitting a bug report and creating an issue, visit our reporting guidelines. ## Suggesting enhancements - Enhancement suggestions are tracked as GitHub issues. - Use a clear and descriptive title for the issue to identify the suggestion. - Describe the current behavior and explain which behavior you expected to see instead and why. ## Pull Requests - Open a new GitHub pull request with the patch. - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. - Think about testability. Please write test(s) for the problem you have fixed. Make sure that existing test are not get broken. rizsotto-Bear-14c2e01/COPYING000066400000000000000000001045131476774233700156620ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . rizsotto-Bear-14c2e01/INSTALL.md000066400000000000000000000104171476774233700162560ustar00rootroot00000000000000How to build ============ Bear should be quite portable on UNIX operating systems. It has been tested on FreeBSD, GNU/Linux and OS X. ## Build dependencies 1. **C++ compiler**, to compile the sources. (Should support [C++17 dialect](https://en.cppreference.com/w/cpp/compiler_support#cpp17).) 2. **CMake**, to configure the build. (Minimum version is 3.12) And a build tool [supported](https://cmake.org/cmake/help/v3.5/manual/cmake-generators.7.html) by CMake. 3. **pkg-config** to look up dependencies' compiler flags. 4. **protoc** and **grpc_cpp_plugin** commands. (See gRPC dependencies.) ## Dependencies The dependencies can come from OS packages or the build will fetch the sources and build locally. - [gRPC](https://github.com/grpc/grpc) >= 1.26 - [fmt](https://github.com/fmtlib/fmt) >= 6.2 - [spdlog](https://github.com/gabime/spdlog) >= 1.5 - [json](https://github.com/nlohmann/json) >= 3.7 and != 3.10.3 Developer dependencies: - [python](https://www.python.org/) >= 3.5 - [googletest](https://github.com/google/googletest) >= 1.10 - [lit](https://pypi.org/project/lit/0.7.1/) >= 0.7 ## Build commands Ideally, you should build Bear in a separate build directory. cmake -DENABLE_UNIT_TESTS=OFF -DENABLE_FUNC_TESTS=OFF $BEAR_SOURCE_DIR make all make install You can configure the build process with passing arguments to cmake. One of the flags you might want to pay attention is the `CMAKE_INSTALL_LIBDIR` flag, which has to be the directory name for libraries. The value of this varies for different distribution: debian derivatives are using `lib/i386-linux-gnu` and `lib/x86_64-linux-gnu`, while many other distributions are simple `lib` and `lib64` directories. Check out where the system `libc.so` is. And use that directory name for the `CMAKE_INSTALL_LIBDIR`. (If your system has it as `/lib/x86_64-linux-gnu/libc.so`, then `lib/x86_64-linux-gnu` is the value you need to use.) Passing the flag looks like this: cmake -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu ... $BEAR_SOURCE_DIR To enable multilib support, an extra CMake flag is needed. `ENABLE_MULTILIB` is defined by this project and will change where Bear looks for files. To use multilib, you need to make the `CMAKE_INSTALL_LIBDIR` set right. (See the paragraph above.) Passing the flag looks like this: cmake -DENABLE_MULTILIB=ON ... $BEAR_SOURCE_DIR To run test during the build process, you will need to install the test frameworks and re-configure the build. For unit testing Bear uses googletest, which will be built from source if not already installed. # install `lit` the functional test framework into a python virtualenv mkvirtualenv bear pip install lit # it's important to re-run the configure step again cmake $BEAR_SOURCE_DIR cmake --build $build_dir --parallel 4 ## OS specific notes Install dependencies from packages on Fedora 32/33 dnf install python cmake pkg-config dnf install json-devel spdlog-devel fmt-devel grpc-devel grpc-plugins dnf install gtest-devel gmock-devel # optional for running the tests Install dependencies from packages on Arch pacman -S python cmake pkg-config pacman -S grpc spdlog fmt nlohmann-json pacman -S gtest gmock # optional for running the tests Install dependencies from packages on Ubuntu 20.04 apt-get install python cmake pkg-config apt-get install libfmt-dev libspdlog-dev nlohmann-json3-dev \ libgrpc++-dev protobuf-compiler-grpc libssl-dev Install dependencies from packages from Brew brew install fmt spdlog nlohmann-json grpc pkg-config Install dependencies from packages on Alpine edge apk add git cmake pkgconf make g++ apk add fmt-dev spdlog-dev nlohmann-json protobuf-dev grpc-dev c-ares-dev ### Platform: macOS Xcode < 11 or macOS < 10.15 users should get [LLVM Clang](https://releases.llvm.org) binaries and headers. Make sure that `clang++ -v` returns the correct `InstalledDir`. This is because `std::filesystem` is not available on Clang supplied with Xcode < 11, and `std::filesystem::path` is not available in system C++ dylib for macOS < 10.15. If OpenSSL is installed via Brew, and it's keg-only, run the following (before the build) for pkg-config to find it as grpc's dependency: export PKG_CONFIG_PATH=$(brew --prefix)/opt/openssl@1.1/lib/pkgconfig rizsotto-Bear-14c2e01/README.md000066400000000000000000000061761476774233700161140ustar00rootroot00000000000000[![Packaging status](https://repology.org/badge/tiny-repos/bear-clang.svg)](https://repology.org/project/bear-clang/versions) [![GitHub release](https://img.shields.io/github/release/rizsotto/Bear)](https://github.com/rizsotto/Bear/releases) [![GitHub Release Date](https://img.shields.io/github/release-date/rizsotto/Bear)](https://github.com/rizsotto/Bear/releases) [![Continuous Integration](https://github.com/rizsotto/Bear/workflows/continuous%20integration/badge.svg)](https://github.com/rizsotto/Bear/actions) [![Contributors](https://img.shields.io/github/contributors/rizsotto/Bear)](https://github.com/rizsotto/Bear/graphs/contributors) [![Gitter](https://img.shields.io/gitter/room/rizsotto/Bear)](https://gitter.im/rizsotto/Bear) ʕ·ᴥ·ʔ Build EAR =============== Bear is a tool that generates a compilation database for clang tooling. The [JSON compilation database][JSONCDB] is used in the clang project to provide information on how a single compilation unit is processed. With this, it is easy to re-run the compilation with alternate programs. Some build system natively supports the generation of JSON compilation database. For projects which does not use such build tool, Bear generates the JSON file during the build process. [JSONCDB]: http://clang.llvm.org/docs/JSONCompilationDatabase.html How to install -------------- Bear is [packaged](https://repology.org/project/bear-clang/versions) for many distributions. Check out your package manager. Or [build it](INSTALL.md) from source. How to use ---------- After installation the usage is like this: bear -- The output file called `compile_commands.json` is saved in the current directory. For more options you can check the man page or pass `--help` parameter. Note that if you want to pass parameter to Bear, pass those _before_ the `--` sign, everything after that will be the build command. Please be aware that some package manager still ship our old 2.4.x release. In that case please omit the extra `--` sign or consult your local documentation. For more, read the man pages or [wiki][WIKI] of the project, which talks about limitations, known issues and platform specific usage. Problem reports --------------- Before you open a new problem report, please look at the [wiki][WIKI] if your problem is a known one with documented workaround. It's also helpful to look at older (maybe closed) [issues][ISSUES] before you open a new one. If you decided to report a problem, try to give as much context as it would help me to reproduce the error you see. If you just have a question about the usage, please don't be shy, ask your question in an issue or in [chat][CHAT]. If you found a bug, but also found a fix for it, please share it with me and open a pull request. Please follow the [contribution guide][GUIDE] when you do these. [ISSUES]: https://github.com/rizsotto/Bear/issues [WIKI]: https://github.com/rizsotto/Bear/wiki [CHAT]: https://gitter.im/rizsotto/Bear [GUIDE]: https://github.com/rizsotto/Bear/blob/master/CONTRIBUTING.md --- Thanks to [JetBrains](https://www.jetbrains.com/?from=Bear) for donating product licenses to help develop Bear rizsotto-Bear-14c2e01/rust/000077500000000000000000000000001476774233700156205ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/Cargo.toml000066400000000000000000000023061476774233700175510ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later [workspace] members = [ "bear" ] resolver = "2" [workspace.dependencies] thiserror = "2.0" anyhow = "1.0" serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = ["std"] } serde_yml = "0.0" clap = { version = "4.5", default-features = false, features = ["std", "cargo", "help", "usage", "suggestions"] } chrono = { version = "0.4", default-features = false, features = ["std", "clock"] } log = { version = "0.4", default-features = false, features = ["std"] } env_logger = { version = "0.11", default-features = false, features = ["humantime"]} rand = { version = "0.9", default-features = false, features = ["std", "thread_rng"] } path-absolutize = "3.1" directories = "6.0" nom = { version = "7.1", default-features = false, features = ["std"] } regex = "1.9" shell-words = "1.1" tempfile = "3.13" signal-hook = { version = "0.3", default-features = false } [workspace.package] version = "4.0.0" authors = ["László Nagy "] repository = "https://github.com/rizsotto/Bear" homepage = "https://github.com/rizsotto/Bear" license = "GPL-3" edition = "2021" rizsotto-Bear-14c2e01/rust/README.md000066400000000000000000000024131476774233700170770ustar00rootroot00000000000000# What's this? This is a rust rewrite of the current master branch of this project. # Why? The current master branch is written in C++ and is not very well written. I want to rewrite it in rust to make it more maintainable and easier to work with. ## What's wrong with the current codebase? - The idea of disabling exception handling and using Rust-like result values is sound, but the implementation could be improved. - The use of CMake as a build tool has caused several issues, including poor handling of third-party libraries and subprojects. - Some dependencies are problematic: - Not all of them are available on all platforms. - Updating them can be challenging. ## What are the benefits of rewriting the project in Rust? - Easy porting of the project to other platforms, including Windows - Improved maintainability through the use of third-party libraries and better development tooling # How? The `3.x` version will be the last version of the C++ codebase. The `4.x` version will be the first version of the rust codebase. The `master` branch will be kept as the main release branch. And the rust codebase will be developed on the `master` branch, but it will be kept in a separate directory. # When? I will work on this project in my free time (as before). rizsotto-Bear-14c2e01/rust/bear/000077500000000000000000000000001476774233700165315ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/Cargo.toml000066400000000000000000000020221476774233700204550ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-3.0-or-later [package] name = "bear" description = "Bear is a tool that generates a compilation database for clang tooling." keywords = ["clang", "clang-tooling", "compilation-database"] version.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true license.workspace = true edition.workspace = true [lib] name = "bear" path = "src/lib.rs" [[bin]] name = "bear" path = "src/bin/bear.rs" [[bin]] name = "wrapper" path = "src/bin/wrapper.rs" [dependencies] thiserror.workspace = true anyhow.workspace = true serde.workspace = true serde_json.workspace = true serde_yml.workspace = true clap.workspace = true directories.workspace = true chrono.workspace = true log.workspace = true env_logger.workspace = true path-absolutize.workspace = true shell-words.workspace = true nom.workspace = true regex.workspace = true rand.workspace = true tempfile.workspace = true signal-hook.workspace = true [profile.release] strip = true lto = true opt-level = 3 codegen-units = 1rizsotto-Bear-14c2e01/rust/bear/build.rs000066400000000000000000000003471476774233700202020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later fn main() { println!("cargo:rustc-env=WRAPPER_EXECUTABLE_PATH=/usr/libexec/bear/wrapper"); println!("cargo:rustc-env=PRELOAD_LIBRARY_PATH=/usr/libexec/bear/$LIB/libexec.so"); } rizsotto-Bear-14c2e01/rust/bear/src/000077500000000000000000000000001476774233700173205ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/args.rs000066400000000000000000000270431476774233700206300ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! This module contains the command line interface of the application. //! //! The command line parsing is implemented using the `clap` library. //! The module is defining types to represent a structured form of the //! program invocation. The `Arguments` type is used to represent all //! possible invocations of the program. use anyhow::anyhow; use clap::{arg, command, ArgAction, ArgMatches, Command}; /// Common constants used in the module. const MODE_INTERCEPT_SUBCOMMAND: &str = "intercept"; const MODE_SEMANTIC_SUBCOMMAND: &str = "semantic"; const DEFAULT_OUTPUT_FILE: &str = "compile_commands.json"; const DEFAULT_EVENT_FILE: &str = "events.json"; /// Represents the command line arguments of the application. #[derive(Debug, PartialEq)] pub struct Arguments { // The path of the configuration file. pub config: Option, // The mode of the application. pub mode: Mode, } /// Represents the mode of the application. #[derive(Debug, PartialEq)] pub enum Mode { Intercept { input: BuildCommand, output: BuildEvents, }, Semantic { input: BuildEvents, output: BuildSemantic, }, Combined { input: BuildCommand, output: BuildSemantic, }, } /// Represents the execution of a command. #[derive(Debug, PartialEq)] pub struct BuildCommand { pub arguments: Vec, } #[derive(Debug, PartialEq)] pub struct BuildSemantic { pub file_name: String, pub append: bool, } #[derive(Debug, PartialEq)] pub struct BuildEvents { pub file_name: String, } impl TryFrom for Arguments { type Error = anyhow::Error; fn try_from(matches: ArgMatches) -> Result { let config = matches.get_one::("config").map(String::to_string); match matches.subcommand() { Some((MODE_INTERCEPT_SUBCOMMAND, intercept_matches)) => { let input = BuildCommand::try_from(intercept_matches)?; let output = intercept_matches .get_one::("output") .map(String::to_string) .expect("output is defaulted"); // let output = BuildEvents::try_from(intercept_matches)?; let mode = Mode::Intercept { input, output: BuildEvents { file_name: output }, }; let arguments = Arguments { config, mode }; Ok(arguments) } Some((MODE_SEMANTIC_SUBCOMMAND, semantic_matches)) => { let input = semantic_matches .get_one::("input") .map(String::to_string) .expect("input is defaulted"); let output = BuildSemantic::try_from(semantic_matches)?; let mode = Mode::Semantic { input: BuildEvents { file_name: input }, output, }; let arguments = Arguments { config, mode }; Ok(arguments) } None => { let input = BuildCommand::try_from(&matches)?; let output = BuildSemantic::try_from(&matches)?; let mode = Mode::Combined { input, output }; let arguments = Arguments { config, mode }; Ok(arguments) } _ => Err(anyhow!("unrecognized subcommand")), } } } impl TryFrom<&ArgMatches> for BuildCommand { type Error = anyhow::Error; fn try_from(matches: &ArgMatches) -> Result { let arguments = matches .get_many("COMMAND") .expect("missing build command") .cloned() .collect(); Ok(BuildCommand { arguments }) } } impl TryFrom<&ArgMatches> for BuildSemantic { type Error = anyhow::Error; fn try_from(matches: &ArgMatches) -> Result { let file_name = matches .get_one::("output") .map(String::to_string) .expect("output is defaulted"); let append = *matches.get_one::("append").unwrap_or(&false); Ok(BuildSemantic { file_name, append }) } } /// Represents the command line interface of the application. /// /// This describes how the user can interact with the application. /// The different modes of the application are represented as subcommands. /// The application can be run in intercept mode, semantic mode, or the /// default mode where both intercept and semantic are executed. pub fn cli() -> Command { command!() .subcommand_required(false) .subcommand_negates_reqs(true) .subcommand_precedence_over_arg(true) .arg_required_else_help(true) .args(&[ arg!(-v --verbose ... "Sets the level of verbosity").action(ArgAction::Count), arg!(-c --config "Path of the config file"), ]) .subcommand( Command::new(MODE_INTERCEPT_SUBCOMMAND) .about("intercepts command execution") .args(&[ arg!( "Build command") .action(ArgAction::Append) .value_terminator("--") .num_args(1..) .last(true) .required(true), arg!(-o --output "Path of the event file") .default_value(DEFAULT_EVENT_FILE) .hide_default_value(false), ]) .arg_required_else_help(true), ) .subcommand( Command::new(MODE_SEMANTIC_SUBCOMMAND) .about("detect semantics of command executions") .args(&[ arg!(-i --input "Path of the event file") .default_value(DEFAULT_EVENT_FILE) .hide_default_value(false), arg!(-o --output "Path of the result file") .default_value(DEFAULT_OUTPUT_FILE) .hide_default_value(false), arg!(-a --append "Append result to an existing output file") .action(ArgAction::SetTrue), ]) .arg_required_else_help(false), ) .args(&[ arg!( "Build command") .action(ArgAction::Append) .value_terminator("--") .num_args(1..) .last(true) .required(true), arg!(-o --output "Path of the result file") .default_value(DEFAULT_OUTPUT_FILE) .hide_default_value(false), arg!(-a --append "Append result to an existing output file").action(ArgAction::SetTrue), ]) } #[cfg(test)] mod test { use super::*; use crate::vec_of_strings; #[test] fn test_intercept_call() { let execution = vec![ "bear", "-c", "~/bear.yaml", "intercept", "-o", "custom.json", "--", "make", "all", ]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: Some("~/bear.yaml".to_string()), mode: Mode::Intercept { input: BuildCommand { arguments: vec_of_strings!["make", "all"] }, output: BuildEvents { file_name: "custom.json".to_string() }, }, } ); } #[test] fn test_intercept_defaults() { let execution = vec!["bear", "intercept", "--", "make", "all"]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: None, mode: Mode::Intercept { input: BuildCommand { arguments: vec_of_strings!["make", "all"] }, output: BuildEvents { file_name: "events.json".to_string() }, }, } ); } #[test] fn test_semantic_call() { let execution = vec![ "bear", "-c", "~/bear.yaml", "semantic", "-i", "custom.json", "-o", "result.json", "-a", ]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: Some("~/bear.yaml".to_string()), mode: Mode::Semantic { input: BuildEvents { file_name: "custom.json".to_string() }, output: BuildSemantic { file_name: "result.json".to_string(), append: true }, }, } ); } #[test] fn test_semantic_defaults() { let execution = vec!["bear", "semantic"]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: None, mode: Mode::Semantic { input: BuildEvents { file_name: "events.json".to_string() }, output: BuildSemantic { file_name: "compile_commands.json".to_string(), append: false }, }, } ); } #[test] fn test_all_call() { let execution = vec![ "bear", "-c", "~/bear.yaml", "-o", "result.json", "-a", "--", "make", "all", ]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: Some("~/bear.yaml".to_string()), mode: Mode::Combined { input: BuildCommand { arguments: vec_of_strings!["make", "all"] }, output: BuildSemantic { file_name: "result.json".to_string(), append: true }, }, } ); } #[test] fn test_all_defaults() { let execution = vec!["bear", "--", "make", "all"]; let matches = cli().get_matches_from(execution); let arguments = Arguments::try_from(matches).unwrap(); assert_eq!( arguments, Arguments { config: None, mode: Mode::Combined { input: BuildCommand { arguments: vec_of_strings!["make", "all"] }, output: BuildSemantic { file_name: "compile_commands.json".to_string(), append: false }, }, } ); } } rizsotto-Bear-14c2e01/rust/bear/src/bin/000077500000000000000000000000001476774233700200705ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/bin/bear.rs000066400000000000000000000052351476774233700213540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use bear::modes::{Combined, Intercept, Mode, Semantic}; use bear::{args, config}; use std::env; use std::process::ExitCode; /// Driver function of the application. fn main() -> anyhow::Result { // Initialize the logging system. env_logger::init(); // Get the package name and version from Cargo let pkg_name = env!("CARGO_PKG_NAME"); let pkg_version = env!("CARGO_PKG_VERSION"); log::debug!("{} v{}", pkg_name, pkg_version); // Parse the command line arguments. let matches = args::cli().get_matches(); let arguments = args::Arguments::try_from(matches)?; // Print the arguments. log::debug!("Arguments: {:?}", arguments); // Load the configuration. let configuration = config::Main::load(&arguments.config)?; log::debug!("Configuration: {:?}", configuration); // Run the application. let application = Application::configure(arguments, configuration)?; let result = application.run(); log::debug!("Exit code: {:?}", result); Ok(result) } /// Represent the application state. enum Application { Intercept(Intercept), Semantic(Semantic), Combined(Combined), } impl Application { /// Configure the application based on the command line arguments and the configuration. /// /// Trying to validate the configuration and the arguments, while creating the application /// state that will be used by the `run` method. Trying to catch problems early before /// the actual execution of the application. fn configure(args: args::Arguments, config: config::Main) -> anyhow::Result { match args.mode { args::Mode::Intercept { input, output } => { log::debug!("Mode: intercept"); Intercept::from(input, output, config).map(Application::Intercept) } args::Mode::Semantic { input, output } => { log::debug!("Mode: semantic analysis"); Semantic::from(input, output, config).map(Application::Semantic) } args::Mode::Combined { input, output } => { log::debug!("Mode: intercept and semantic analysis"); Combined::from(input, output, config).map(Application::Combined) } } } fn run(self) -> ExitCode { let status = match self { Application::Intercept(intercept) => intercept.run(), Application::Semantic(semantic) => semantic.run(), Application::Combined(all) => all.run(), }; status.unwrap_or_else(|error| { log::error!("Bear: {}", error); ExitCode::FAILURE }) } } rizsotto-Bear-14c2e01/rust/bear/src/bin/wrapper.rs000066400000000000000000000117361476774233700221260ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! This module implements a wrapper around an arbitrary executable. //! //! The wrapper is used to intercept the execution of the executable and //! report it to a remote server. The wrapper is named after the executable //! via a soft link (or a hard copy on platforms where soft links are not //! supported). The wrapper process is called instead of the original executable. //! This is arranged by the process that supervise the build process. //! The build supervisor creates a directory with soft links and place //! that directory at the beginning of the PATH variable. Which guarantees //! that the wrapper is called instead of the original executable. //! //! The wrapper reads the PATH variable and finds the next executable with //! the same name as the wrapper. It reports the execution of the real //! executable and then calls the real executable with the same arguments. extern crate core; use anyhow::{Context, Result}; use bear::intercept::supervise::supervise; use bear::intercept::tcp::ReporterOnTcp; use bear::intercept::Reporter; use bear::intercept::KEY_DESTINATION; use bear::intercept::{Event, Execution, ProcessId}; use std::path::{Path, PathBuf}; /// Implementation of the wrapper process. /// /// The process exit code is the same as the executed process exit code. /// Besides the functionality described in the module documentation, the /// wrapper process logs the execution and the relevant steps leading to /// the execution. fn main() -> Result<()> { env_logger::init(); // Find out what is the executable name the execution was started with let executable = file_name_from_arguments()?; log::info!("Executable as called: {:?}", executable); // Read the PATH variable and find the next executable with the same name let real_executable = next_in_path(&executable)?; log::info!("Executable to call: {:?}", real_executable); // Reporting failures shall not fail the execution. match into_execution(&real_executable).and_then(report) { Ok(_) => log::info!("Execution reported"), Err(e) => log::error!("Execution reporting failed: {}", e), } // Execute the real executable with the same arguments let mut command = std::process::Command::new(real_executable); let exit_status = supervise(command.args(std::env::args().skip(1)))?; log::info!("Execution finished with status: {:?}", exit_status); // Return the child process status code std::process::exit(exit_status.code().unwrap_or(1)); } /// Get the file name of the executable from the arguments. /// /// Since the executable will be called via soft link, the first argument /// will be the name of the soft link. This function returns the file name /// of the soft link. fn file_name_from_arguments() -> Result { std::env::args() .next() .ok_or_else(|| anyhow::anyhow!("Cannot get first argument")) .and_then(|arg| match PathBuf::from(arg).file_name() { Some(file_name) => Ok(PathBuf::from(file_name)), None => Err(anyhow::anyhow!( "Cannot get the file name from the argument" )), }) } /// Find the next executable in the PATH variable. /// /// The function reads the PATH variable and tries to find the next executable /// with the same name as the given executable. It returns the path to the /// executable. fn next_in_path(target: &Path) -> Result { let path = std::env::var("PATH")?; log::debug!("PATH: {}", path); // The `current_exe` is a canonical path to the current executable. let current_exe = std::env::current_exe()?; path.split(':') .map(|dir| Path::new(dir).join(target)) .filter(|path| path.is_file()) .find(|path| { // We need to compare it with the real path of the candidate executable to avoid // calling the same executable again. let real_path = match path.canonicalize() { Ok(path) => path, Err(_) => return false, }; real_path != current_exe }) .ok_or_else(|| anyhow::anyhow!("Cannot find the real executable")) } fn report(execution: Execution) -> Result<()> { let event = Event { pid: ProcessId(std::process::id()), execution, }; // Get the reporter address from the environment std::env::var(KEY_DESTINATION) .with_context(|| format!("${} is missing from the environment", KEY_DESTINATION)) // Create a new reporter .and_then(ReporterOnTcp::new) .with_context(|| "Cannot create TCP execution reporter") // Report the execution .and_then(|reporter| reporter.report(event)) .with_context(|| "Sending execution failed") } fn into_execution(path_buf: &Path) -> Result { Ok(Execution { executable: path_buf.to_path_buf(), arguments: std::env::args().collect(), working_dir: std::env::current_dir()?, environment: std::env::vars().collect(), }) } rizsotto-Bear-14c2e01/rust/bear/src/config.rs000066400000000000000000001317321476774233700211420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! This module defines the configuration of the application. //! //! The configuration is either loaded from a file or used with the default //! values, which are defined in the code. The configuration exposes the main //! logical steps that the application will follow. //! //! The configuration file syntax is based on the YAML format. //! The default configuration file name is `bear.yml`. //! //! The configuration file location is searched in the following order: //! - The current working directory. //! - The local configuration directory of the user. //! - The configuration directory of the user. //! - The local configuration directory of the application. //! - The configuration directory of the application. //! //! The configuration file content is validated against the schema version, //! syntax and semantic constraints. If the configuration file is invalid, //! the application will exit with an error message explaining the issue. //! //! ```yaml //! schema: 4.0 //! //! intercept: //! mode: wrapper //! directory: /tmp //! executables: //! - /usr/bin/cc //! - /usr/bin/c++ //! - /usr/bin/clang //! - /usr/bin/clang++ //! output: //! specification: clang //! compilers: //! - path: /usr/local/bin/cc //! ignore: always //! - path: /usr/bin/cc //! ignore: never //! - path: /usr/bin/c++ //! ignore: conditional //! arguments: //! match: //! - -### //! - path: /usr/bin/clang //! ignore: never //! arguments: //! add: //! - -DDEBUG //! remove: //! - -Wall //! - path: /usr/bin/clang++ //! arguments: //! remove: //! - -Wall //! sources: //! only_existing_files: true //! paths: //! - path: /opt/project/sources //! ignore: never //! - path: /opt/project/tests //! ignore: always //! duplicates: //! by_fields: //! - file //! - directory //! format: //! command_as_array: true //! drop_output_field: false //! paths_as: canonical //! ``` //! //! ```yaml //! schema: 4.0 //! //! intercept: //! mode: preload //! output: //! specification: bear //! ``` use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use crate::config::validation::Validate; use anyhow::{Context, Result}; use directories::{BaseDirs, ProjectDirs}; use log::{debug, info}; use serde::{Deserialize, Serialize}; const SUPPORTED_SCHEMA_VERSION: &str = "4.0"; const PRELOAD_LIBRARY_PATH: &str = env!("PRELOAD_LIBRARY_PATH"); const WRAPPER_EXECUTABLE_PATH: &str = env!("WRAPPER_EXECUTABLE_PATH"); /// Represents the application configuration. #[derive(Debug, PartialEq, Deserialize, Serialize)] pub struct Main { #[serde(deserialize_with = "validate_schema_version")] pub schema: String, #[serde(default)] pub intercept: Intercept, #[serde(default)] pub output: Output, } impl Main { /// Loads the configuration from the specified file or the default locations. /// /// If the configuration file is specified, it will be used. Otherwise, the default locations /// will be searched for the configuration file. If the configuration file is not found, the /// default configuration will be returned. pub fn load(file: &Option) -> Result { if let Some(path) = file { // If the configuration file is specified, use it. let config_file_path = PathBuf::from(path); Self::from_file(config_file_path.as_path()) } else { // Otherwise, try to find the configuration file in the default locations. let locations = Self::file_locations(); for location in locations { debug!("Checking configuration file: {}", location.display()); if location.exists() { return Self::from_file(location.as_path()); } } // If the configuration file is not found, return the default configuration. debug!("Configuration file not found. Using the default configuration."); Ok(Self::default()) } } /// The default locations where the configuration file can be found. /// /// The locations are searched in the following order: /// - The current working directory. /// - The local configuration directory of the user. /// - The configuration directory of the user. /// - The local configuration directory of the application. /// - The configuration directory of the application. fn file_locations() -> Vec { let mut locations = Vec::new(); if let Ok(current_dir) = std::env::current_dir() { locations.push(current_dir); } if let Some(base_dirs) = BaseDirs::new() { locations.push(base_dirs.config_local_dir().to_path_buf()); locations.push(base_dirs.config_dir().to_path_buf()); } if let Some(proj_dirs) = ProjectDirs::from("com.github", "rizsotto", "Bear") { locations.push(proj_dirs.config_local_dir().to_path_buf()); locations.push(proj_dirs.config_dir().to_path_buf()); } // filter out duplicate elements from the list locations.dedup(); // append the default configuration file name to the locations locations.iter().map(|p| p.join("bear.yml")).collect() } /// Loads the configuration from the specified file. pub fn from_file(file: &Path) -> Result { info!("Loading configuration file: {}", file.display()); let reader = OpenOptions::new() .read(true) .open(file) .with_context(|| format!("Failed to open configuration file: {:?}", file))?; let content: Self = Self::from_reader(reader) .with_context(|| format!("Failed to parse configuration from file: {:?}", file))?; content.validate() } /// Define the deserialization format of the config file. fn from_reader(rdr: R) -> serde_yml::Result where R: std::io::Read, T: serde::de::DeserializeOwned, { serde_yml::from_reader(rdr) } } impl Default for Main { fn default() -> Self { Main { schema: String::from(SUPPORTED_SCHEMA_VERSION), intercept: Intercept::default(), output: Output::default(), } } } /// Intercept configuration is either a wrapper or a preload mode. /// /// In wrapper mode, the compiler is wrapped with a script that intercepts the compiler calls. /// The configuration for that is capturing the directory where the wrapper scripts are stored /// and the list of executables to wrap. /// /// In preload mode, the compiler is intercepted by a shared library that is preloaded before /// the compiler is executed. The configuration for that is the path to the shared library. #[derive(Debug, PartialEq, Deserialize, Serialize)] #[serde(tag = "mode")] pub enum Intercept { #[serde(rename = "wrapper")] Wrapper { #[serde(default = "default_wrapper_executable")] path: PathBuf, #[serde(default = "default_wrapper_directory")] directory: PathBuf, executables: Vec, }, #[serde(rename = "preload")] Preload { #[serde(default = "default_preload_library")] path: PathBuf, }, } /// The default intercept mode is varying based on the target operating system. impl Default for Intercept { #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "dragonfly" ))] fn default() -> Self { Intercept::Preload { path: default_preload_library(), } } #[cfg(any(target_os = "macos", target_os = "ios"))] fn default() -> Self { Intercept::Wrapper { path: default_wrapper_executable(), directory: default_wrapper_directory(), executables: vec![ PathBuf::from("/usr/bin/cc"), PathBuf::from("/usr/bin/c++"), PathBuf::from("/usr/bin/clang"), PathBuf::from("/usr/bin/clang++"), ], } } #[cfg(target_os = "windows")] fn default() -> Self { Intercept::Wrapper { path: default_wrapper_executable(), directory: default_wrapper_directory(), executables: vec![ PathBuf::from("C:\\msys64\\mingw64\\bin\\gcc.exe"), PathBuf::from("C:\\msys64\\mingw64\\bin\\g++.exe"), ], } } } /// Output configuration is used to customize the output format. /// /// Allow to customize the output format of the compiler calls. /// /// - Clang: Output the compiler calls in the clang project defined "JSON compilation database" /// format. (The format is used by clang tooling and other tools based on that library.) /// - Semantic: Output the compiler calls in the semantic format. (The format is not defined yet.) #[derive(Debug, PartialEq, Deserialize, Serialize)] #[serde(tag = "specification")] pub enum Output { #[serde(rename = "clang")] Clang { #[serde(default)] compilers: Vec, #[serde(default)] sources: SourceFilter, #[serde(default)] duplicates: DuplicateFilter, #[serde(default)] format: Format, }, #[serde(rename = "bear")] Semantic {}, } /// The default output is the clang format. impl Default for Output { fn default() -> Self { Output::Clang { compilers: vec![], sources: SourceFilter::default(), duplicates: DuplicateFilter::default(), format: Format::default(), } } } /// Represents instructions to transform the compiler calls. /// /// Allow to transform the compiler calls by adding or removing arguments. /// It also can instruct to filter out the compiler call from the output. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Compiler { pub path: PathBuf, #[serde(default)] pub ignore: IgnoreOrConsider, #[serde(default)] pub arguments: Arguments, } /// Represents instructions to ignore the compiler call. /// /// The meaning of the possible values are: /// - Always: Always ignore the compiler call. /// - Never: Never ignore the compiler call. (Default) /// - Conditional: Ignore the compiler call if the arguments match. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum IgnoreOrConsider { #[serde(rename = "always", alias = "true")] Always, #[serde(rename = "never", alias = "false")] Never, #[serde(rename = "conditional")] Conditional, } /// The default ignore mode is never ignore. impl Default for IgnoreOrConsider { fn default() -> Self { IgnoreOrConsider::Never } } /// Argument lists to match, add or remove. /// /// The `match` field is used to specify the arguments to match. Can be used only with the /// conditional ignore mode. /// /// The `add` or `remove` fields are used to specify the arguments to add or remove. These can be /// used with the conditional or never ignore mode. #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct Arguments { #[serde(default, rename = "match")] pub match_: Vec, #[serde(default)] pub add: Vec, #[serde(default)] pub remove: Vec, } /// Source filter configuration is used to filter the compiler calls based on the source files. /// /// Allow to filter the compiler calls based on the source files. /// /// - Include only existing files: can be true or false. /// - List of directories to include or exclude. /// (The order of these entries will imply the order of evaluation.) #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct SourceFilter { #[serde(default = "default_disabled")] pub only_existing_files: bool, #[serde(default)] pub paths: Vec, } /// Directory filter configuration is used to filter the compiler calls based on /// the source file location. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DirectoryFilter { pub path: PathBuf, pub ignore: Ignore, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Ignore { #[serde(rename = "always", alias = "true")] Always, #[serde(rename = "never", alias = "false")] Never, } /// Duplicate filter configuration is used to filter the duplicate compiler calls. /// /// - By fields: Specify the fields of the JSON compilation database record to detect duplicates. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct DuplicateFilter { pub by_fields: Vec, } impl Default for DuplicateFilter { fn default() -> Self { DuplicateFilter { by_fields: vec![OutputFields::File, OutputFields::Arguments], } } } /// Represent the fields of the JSON compilation database record. #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum OutputFields { #[serde(rename = "directory")] Directory, #[serde(rename = "file")] File, #[serde(rename = "arguments")] Arguments, #[serde(rename = "output")] Output, } /// Format configuration of the JSON compilation database. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Format { #[serde(default = "default_enabled")] pub command_as_array: bool, #[serde(default = "default_disabled")] pub drop_output_field: bool, #[serde(default)] pub paths_as: PathFormat, } impl Default for Format { fn default() -> Self { Format { command_as_array: true, drop_output_field: false, paths_as: PathFormat::default(), } } } /// Path format configuration describes how the paths should be formatted. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum PathFormat { #[serde(rename = "original", alias = "is")] Original, #[serde(rename = "absolute")] Absolute, #[serde(rename = "canonical")] Canonical, } /// The default path format is the original path. impl Default for PathFormat { fn default() -> Self { PathFormat::Original } } fn default_disabled() -> bool { false } fn default_enabled() -> bool { true } /// The default directory where the wrapper executables will be stored. fn default_wrapper_directory() -> PathBuf { std::env::temp_dir() } /// The default path to the wrapper executable. fn default_wrapper_executable() -> PathBuf { PathBuf::from(WRAPPER_EXECUTABLE_PATH) } /// The default path to the shared library that will be preloaded. fn default_preload_library() -> PathBuf { PathBuf::from(PRELOAD_LIBRARY_PATH) } // Custom deserialization function to validate the schema version fn validate_schema_version<'de, D>(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { let schema: String = Deserialize::deserialize(deserializer)?; if schema != SUPPORTED_SCHEMA_VERSION { use serde::de::Error; Err(D::Error::custom(format!( "Unsupported schema version: {}. Expected: {}", schema, SUPPORTED_SCHEMA_VERSION ))) } else { Ok(schema) } } #[cfg(test)] mod test { use super::*; use crate::{vec_of_pathbuf, vec_of_strings}; #[test] fn test_wrapper_config() { let content: &[u8] = br#" schema: 4.0 intercept: mode: wrapper directory: /tmp executables: - /usr/bin/cc - /usr/bin/c++ - /usr/bin/clang - /usr/bin/clang++ output: specification: clang compilers: - path: /usr/local/bin/cc ignore: always - path: /usr/bin/cc ignore: never - path: /usr/bin/c++ ignore: conditional arguments: match: - -### - path: /usr/bin/clang ignore: never arguments: add: - -DDEBUG remove: - -Wall - path: /usr/bin/clang++ arguments: remove: - -Wall sources: only_existing_files: true paths: - path: /opt/project/sources ignore: never - path: /opt/project/tests ignore: always duplicates: by_fields: - file - directory format: command_as_array: true drop_output_field: false paths_as: canonical "#; let result = Main::from_reader(content).unwrap(); let expected = Main { intercept: Intercept::Wrapper { path: default_wrapper_executable(), directory: PathBuf::from("/tmp"), executables: vec_of_pathbuf![ "/usr/bin/cc", "/usr/bin/c++", "/usr/bin/clang", "/usr/bin/clang++" ], }, output: Output::Clang { compilers: vec![ Compiler { path: PathBuf::from("/usr/local/bin/cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/c++"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec_of_strings!["-###"], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/clang"), ignore: IgnoreOrConsider::Never, arguments: Arguments { add: vec_of_strings!["-DDEBUG"], remove: vec_of_strings!["-Wall"], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/clang++"), ignore: IgnoreOrConsider::Never, arguments: Arguments { remove: vec_of_strings!["-Wall"], ..Default::default() }, }, ], sources: SourceFilter { only_existing_files: true, paths: vec![ DirectoryFilter { path: PathBuf::from("/opt/project/sources"), ignore: Ignore::Never, }, DirectoryFilter { path: PathBuf::from("/opt/project/tests"), ignore: Ignore::Always, }, ], }, duplicates: DuplicateFilter { by_fields: vec![OutputFields::File, OutputFields::Directory], }, format: Format { command_as_array: true, drop_output_field: false, paths_as: PathFormat::Canonical, }, }, schema: String::from("4.0"), }; assert_eq!(expected, result); } #[test] fn test_incomplete_wrapper_config() { let content: &[u8] = br#" schema: 4.0 intercept: mode: wrapper executables: - /usr/bin/cc - /usr/bin/c++ output: specification: clang sources: only_existing_files: true duplicates: by_fields: - file - directory format: command_as_array: true "#; let result = Main::from_reader(content).unwrap(); let expected = Main { intercept: Intercept::Wrapper { path: default_wrapper_executable(), directory: default_wrapper_directory(), executables: vec_of_pathbuf!["/usr/bin/cc", "/usr/bin/c++"], }, output: Output::Clang { compilers: vec![], sources: SourceFilter { only_existing_files: true, paths: vec![], }, duplicates: DuplicateFilter { by_fields: vec![OutputFields::File, OutputFields::Directory], }, format: Format { command_as_array: true, drop_output_field: false, paths_as: PathFormat::Original, }, }, schema: String::from("4.0"), }; assert_eq!(expected, result); } #[test] fn test_preload_config() { let content: &[u8] = br#" schema: 4.0 intercept: mode: preload path: /usr/local/lib/libexec.so output: specification: bear "#; let result = Main::from_reader(content).unwrap(); let expected = Main { intercept: Intercept::Preload { path: PathBuf::from("/usr/local/lib/libexec.so"), }, output: Output::Semantic {}, schema: String::from("4.0"), }; assert_eq!(expected, result); } #[test] fn test_incomplete_preload_config() { let content: &[u8] = br#" schema: 4.0 intercept: mode: preload output: specification: clang compilers: - path: /usr/local/bin/cc - path: /usr/local/bin/c++ - path: /usr/local/bin/clang ignore: always - path: /usr/local/bin/clang++ ignore: always sources: only_existing_files: false duplicates: by_fields: - file format: command_as_array: true drop_output_field: true use_absolute_path: false "#; let result = Main::from_reader(content).unwrap(); let expected = Main { intercept: Intercept::Preload { path: default_preload_library(), }, output: Output::Clang { compilers: vec![ Compiler { path: PathBuf::from("/usr/local/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/local/bin/c++"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/local/bin/clang"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/local/bin/clang++"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, ], sources: SourceFilter { only_existing_files: false, paths: vec![], }, duplicates: DuplicateFilter { by_fields: vec![OutputFields::File], }, format: Format { command_as_array: true, drop_output_field: true, paths_as: PathFormat::Original, }, }, schema: String::from("4.0"), }; assert_eq!(expected, result); } #[test] fn test_default_config() { let result = Main::default(); let expected = Main { intercept: Intercept::default(), output: Output::Clang { compilers: vec![], sources: SourceFilter::default(), duplicates: DuplicateFilter::default(), format: Format::default(), }, schema: String::from(SUPPORTED_SCHEMA_VERSION), }; assert_eq!(expected, result); } #[test] fn test_invalid_schema_version() { let content: &[u8] = br#" schema: 3.0 intercept: mode: wrapper directory: /tmp executables: - /usr/bin/gcc - /usr/bin/g++ "#; let result: serde_yml::Result
= Main::from_reader(content); assert!(result.is_err()); let message = result.unwrap_err().to_string(); assert_eq!( "Unsupported schema version: 3.0. Expected: 4.0 at line 2 column 9", message ); } #[test] fn test_failing_config() { let content: &[u8] = br#"{ "output": { "format": { "command_as_array": false }, "content": { "duplicates": "files" } } }"#; let result: serde_yml::Result
= Main::from_reader(content); assert!(result.is_err()); } } mod validation { //! This module defines the validation logic for the configuration. use anyhow::Result; use std::collections::HashSet; use std::path::{Path, PathBuf}; use crate::config::{ Arguments, Compiler, DuplicateFilter, IgnoreOrConsider, Intercept, Main, Output, SourceFilter, }; /// A trait to validate the configuration and return a valid instance. pub trait Validate { fn validate(self) -> Result where Self: Sized; } impl Validate for Main { /// Validate the configuration of the main configuration. fn validate(self) -> Result { let intercept = self.intercept.validate()?; let output = self.output.validate()?; Ok(Main { schema: self.schema, intercept, output, }) } } impl Validate for Intercept { /// Validate the configuration of the intercept mode. fn validate(self) -> Result { match &self { Intercept::Wrapper { path, directory, executables, } => { if is_empty_path(path) { anyhow::bail!("The wrapper path cannot be empty."); } if is_empty_path(directory) { anyhow::bail!("The wrapper directory cannot be empty."); } for executable in executables { if is_empty_path(executable) { anyhow::bail!("The executable path cannot be empty."); } } Ok(self) } Intercept::Preload { path } => { if is_empty_path(path) { anyhow::bail!("The preload library path cannot be empty."); } Ok(self) } } } } impl Validate for Output { /// Validate the configuration of the output writer. fn validate(self) -> Result { match self { Output::Clang { compilers, sources, duplicates, format, } => { let compilers = compilers.validate()?; let sources = sources.validate()?; let duplicates = duplicates.validate()?; Ok(Output::Clang { compilers, sources, duplicates, format, }) } Output::Semantic {} => Ok(Output::Semantic {}), } } } impl Validate for Vec { /// Validate the configuration of the compiler list. /// /// The validation is done on the individual compiler configuration. /// Duplicate paths are allowed in the list. But the instruction to ignore the /// compiler should be the end of the list. fn validate(self) -> Result { let mut validated_compilers = Vec::new(); let mut grouped_compilers: std::collections::HashMap> = std::collections::HashMap::new(); // Group compilers by their path for compiler in self { grouped_compilers .entry(compiler.path.clone()) .or_default() .push(compiler); } // Validate each group for (path, group) in grouped_compilers { let mut has_always = false; let mut has_conditional = false; let mut has_never = false; for compiler in group { match compiler.ignore { IgnoreOrConsider::Always | IgnoreOrConsider::Conditional if has_never => { anyhow::bail!("Invalid configuration: 'Always' or 'Conditional' can't be used after 'Never' for path {:?}", path); } IgnoreOrConsider::Never | IgnoreOrConsider::Conditional if has_always => { anyhow::bail!("Invalid configuration: 'Never' or 'Conditional' can't be used after 'Always' for path {:?}", path); } IgnoreOrConsider::Never if has_conditional => { anyhow::bail!("Invalid configuration: 'Never' can't be used after 'Conditional' for path {:?}", path); } IgnoreOrConsider::Always if has_always => { anyhow::bail!("Invalid configuration: 'Always' can't be used multiple times for path {:?}", path); } IgnoreOrConsider::Conditional if has_conditional => { anyhow::bail!("Invalid configuration: 'Conditional' can't be used multiple times for path {:?}", path); } IgnoreOrConsider::Never if has_never => { anyhow::bail!("Invalid configuration: 'Never' can't be used multiple times for path {:?}", path); } IgnoreOrConsider::Conditional => { has_conditional = true; } IgnoreOrConsider::Always => { has_always = true; } IgnoreOrConsider::Never => { has_never = true; } } validated_compilers.push(compiler.validate()?); } } Ok(validated_compilers) } } impl Validate for Compiler { /// Validate the configuration of the compiler. fn validate(self) -> Result { match self.ignore { IgnoreOrConsider::Always if self.arguments != Arguments::default() => { anyhow::bail!( "All arguments must be empty in always ignore mode. {:?}", self.path ); } IgnoreOrConsider::Conditional if self.arguments.match_.is_empty() => { anyhow::bail!( "The match arguments cannot be empty in conditional ignore mode. {:?}", self.path ); } IgnoreOrConsider::Never if !self.arguments.match_.is_empty() => { anyhow::bail!( "The arguments must be empty in never ignore mode. {:?}", self.path ); } _ if is_empty_path(&self.path) => { anyhow::bail!("The compiler path cannot be empty."); } _ => Ok(self), } } } impl Validate for SourceFilter { /// Fail when the same directory is in multiple times in the list. /// Otherwise, return the received source filter. fn validate(self) -> Result { let mut already_seen = HashSet::new(); for directory in &self.paths { if !already_seen.insert(&directory.path) { anyhow::bail!("The directory {:?} is duplicated.", directory.path); } } Ok(self) } } impl Validate for DuplicateFilter { /// Deduplicate the fields of the fields vector. fn validate(self) -> Result { // error out when the fields vector is empty if self.by_fields.is_empty() { anyhow::bail!("The field list cannot be empty."); } // error out when the fields vector contains duplicates let mut already_seen = HashSet::new(); for field in &self.by_fields { if !already_seen.insert(field) { anyhow::bail!("The field {:?} is duplicated.", field); } } Ok(self) } } fn is_empty_path(path: &Path) -> bool { path.to_str().is_some_and(|p| p.is_empty()) } #[cfg(test)] mod test { use super::*; use crate::config::{DirectoryFilter, Ignore, OutputFields}; #[test] fn test_duplicate_detection_validation_pass() { let sut = DuplicateFilter { by_fields: vec![OutputFields::File, OutputFields::Arguments], }; let result = sut.validate(); assert!(result.is_ok()); } #[test] fn test_duplicate_detection_validation_fails() { let sut = DuplicateFilter { by_fields: vec![OutputFields::File, OutputFields::File], }; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_duplicate_detection_validation_fails_on_empty() { let sut = DuplicateFilter { by_fields: vec![] }; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_validate_compiler_always_with_arguments() { let sut = Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments { add: vec!["-DDEBUG".to_string()], ..Default::default() }, }; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_validate_compiler_conditional_without_match() { let compiler = Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments::default(), }; let result = compiler.validate(); assert!(result.is_err()); } #[test] fn test_validate_compiler_never_with_match() { let compiler = Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments { match_: vec!["-###".to_string()], ..Default::default() }, }; let result = compiler.validate(); assert!(result.is_err()); } #[test] fn test_validate_compiler_empty_path() { let compiler = Compiler { path: PathBuf::from(""), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }; let result = compiler.validate(); assert!(result.is_err()); } #[test] fn test_compiler_validation_pass() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec!["-###".to_string()], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/c++"), ignore: IgnoreOrConsider::Never, arguments: Arguments { add: vec!["-DDEBUG".to_string()], remove: vec!["-Wall".to_string()], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/gcc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec!["-###".to_string()], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/gcc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, ]; let result = sut.validate(); assert!(result.is_ok()); } #[test] fn test_compiler_validation_fails_conditional_after_always() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec!["-###".to_string()], ..Default::default() }, }, ]; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_compiler_validation_fails_never_after_always() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, ]; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_compiler_validation_fails_always_after_never() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }, ]; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_compiler_validation_fails_never_after_never() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments { add: vec!["-Wall".to_string()], ..Default::default() }, }, ]; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_compiler_validation_fails_never_after_conditional() { let sut: Vec = vec![ Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec!["-###".to_string()], ..Default::default() }, }, Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: IgnoreOrConsider::Never, arguments: Arguments::default(), }, ]; let result = sut.validate(); assert!(result.is_err()); } #[test] fn test_validate_intercept_wrapper_valid() { let sut = Intercept::Wrapper { path: PathBuf::from("/usr/bin/wrapper"), directory: PathBuf::from("/tmp"), executables: vec![PathBuf::from("/usr/bin/cc")], }; assert!(sut.validate().is_ok()); } #[test] fn test_validate_intercept_wrapper_empty_path() { let sut = Intercept::Wrapper { path: PathBuf::from(""), directory: PathBuf::from("/tmp"), executables: vec![PathBuf::from("/usr/bin/cc")], }; assert!(sut.validate().is_err()); } #[test] fn test_validate_intercept_wrapper_empty_directory() { let sut = Intercept::Wrapper { path: PathBuf::from("/usr/bin/wrapper"), directory: PathBuf::from(""), executables: vec![PathBuf::from("/usr/bin/cc")], }; assert!(sut.validate().is_err()); } #[test] fn test_validate_intercept_wrapper_empty_executables() { let sut = Intercept::Wrapper { path: PathBuf::from("/usr/bin/wrapper"), directory: PathBuf::from("/tmp"), executables: vec![ PathBuf::from("/usr/bin/cc"), PathBuf::from("/usr/bin/c++"), PathBuf::from(""), ], }; assert!(sut.validate().is_err()); } #[test] fn test_validate_intercept_preload_valid() { let sut = Intercept::Preload { path: PathBuf::from("/usr/local/lib/libexec.so"), }; assert!(sut.validate().is_ok()); } #[test] fn test_validate_intercept_preload_empty_path() { let sut = Intercept::Preload { path: PathBuf::from(""), }; assert!(sut.validate().is_err()); } #[test] fn test_source_filter_validation_success() { let sut = SourceFilter { only_existing_files: true, paths: vec![ DirectoryFilter { path: PathBuf::from("/opt/project/sources"), ignore: Ignore::Never, }, DirectoryFilter { path: PathBuf::from("/opt/project/tests"), ignore: Ignore::Always, }, ], }; let result = sut.validate(); assert!(result.is_ok()); } #[test] fn test_source_filter_validation_duplicates() { let sut = SourceFilter { only_existing_files: true, paths: vec![ DirectoryFilter { path: PathBuf::from("/opt/project/sources"), ignore: Ignore::Never, }, DirectoryFilter { path: PathBuf::from("/opt/project/test"), ignore: Ignore::Always, }, DirectoryFilter { path: PathBuf::from("/opt/project/sources"), ignore: Ignore::Always, }, ], }; let result = sut.validate(); assert!(result.is_err()); } } } rizsotto-Bear-14c2e01/rust/bear/src/fixtures.rs000066400000000000000000000010001476774233700215260ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later #[cfg(test)] pub mod fixtures { #[macro_export] macro_rules! vec_of_strings { ($($x:expr),*) => (vec![$($x.to_string()),*]); } #[macro_export] macro_rules! map_of_strings { ($($k:expr => $v:expr),* $(,)?) => {{ core::convert::From::from([$(($k.to_string(), $v.to_string()),)*]) }}; } #[macro_export] macro_rules! vec_of_pathbuf { ($($x:expr),*) => (vec![$(PathBuf::from($x)),*]); } } rizsotto-Bear-14c2e01/rust/bear/src/intercept/000077500000000000000000000000001476774233700213155ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/intercept/mod.rs000066400000000000000000000304601476774233700224450ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! The module contains the intercept reporting and collecting functionality. //! //! When a command execution is intercepted, the interceptor sends the event to the collector. //! This happens in two different processes, requiring a communication channel between these //! processes. //! //! The module provides abstractions for the reporter and the collector. And it also defines //! the data structures that are used to represent the events. use crate::intercept::supervise::supervise; use crate::{args, config}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::process::{Command, ExitCode}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Arc; use std::{env, fmt, thread}; pub mod persistence; pub mod supervise; pub mod tcp; /// Declare the environment variables used by the intercept mode. pub const KEY_DESTINATION: &str = "INTERCEPT_REPORTER_ADDRESS"; pub const KEY_PRELOAD_PATH: &str = "LD_PRELOAD"; /// Represents the remote sink of supervised process events. /// /// This allows the reporters to send events to a remote collector. pub trait Reporter { fn report(&self, event: Event) -> Result<(), anyhow::Error>; } /// Represents the local sink of supervised process events. /// /// The collector is responsible for collecting the events from the reporters. /// /// To share the collector between threads, we use the `Arc` type to wrap the /// collector. This way we can clone the collector and send it to other threads. pub trait Collector { /// Returns the address of the collector. /// /// The address is in the format of `ip:port`. fn address(&self) -> String; /// Collects the events from the reporters. /// /// The events are sent to the given destination channel. /// /// The function returns when the collector is stopped. The collector is stopped /// when the `stop` method invoked (from another thread). fn collect(&self, destination: Sender) -> Result<(), anyhow::Error>; /// Stops the collector. fn stop(&self) -> Result<(), anyhow::Error>; } /// Envelope is a wrapper around the event. /// /// It contains the reporter id, the timestamp of the event and the event itself. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Envelope { pub rid: ReporterId, pub timestamp: u64, pub event: Event, } impl fmt::Display for Envelope { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Envelope rid={}, timestamp={}, event={}", self.rid.0, self.timestamp, self.event ) } } /// Represent a relevant life cycle event of a process. /// /// In the current implementation, we only have one event, the `Started` event. /// This event is sent when a process is started. It contains the process id /// and the execution information. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Event { pub pid: ProcessId, pub execution: Execution, } impl fmt::Display for Event { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Event pid={}, execution={}", self.pid.0, self.execution) } } /// Execution is a representation of a process execution. /// /// It does not contain information about the outcome of the execution, /// like the exit code or the duration of the execution. It only contains /// the information that is necessary to reproduce the execution. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct Execution { pub executable: PathBuf, pub arguments: Vec, pub working_dir: PathBuf, pub environment: HashMap, } impl fmt::Display for Execution { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Execution path={}, args=[{}]", self.executable.display(), self.arguments.join(",") ) } } /// Reporter id is a unique identifier for a reporter. /// /// It is used to identify the process that sends the execution report. /// Because the OS PID is not unique across a single build (PIDs are /// recycled), we need to use a new unique identifier to identify the process. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct ReporterId(pub u64); /// Process id is a OS identifier for a process. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct ProcessId(pub u32); /// The service is responsible for collecting the events from the supervised processes. /// /// The service is implemented as TCP server that listens on a random port on the loopback /// interface. The address of the service can be obtained by the `address` method. /// /// The service is started in a separate thread to dispatch the events to the consumer. /// The consumer is a function that receives the events from the service and processes them. /// It also runs in a separate thread. The reason for having two threads is to avoid blocking /// the main thread of the application and decouple the collection from the processing. pub(crate) struct CollectorService { collector: Arc, network_thread: Option>, output_thread: Option>, } impl CollectorService { /// Creates a new intercept service. /// /// The `consumer` is a function that receives the events and processes them. /// The function is executed in a separate thread. pub fn new(consumer: F) -> anyhow::Result where F: FnOnce(Receiver) -> anyhow::Result<()>, F: Send + 'static, { let collector = tcp::CollectorOnTcp::new()?; let collector_arc = Arc::new(collector); let (sender, receiver) = channel(); let collector_in_thread = collector_arc.clone(); let collector_thread = thread::spawn(move || { let result = collector_in_thread.collect(sender); if let Err(e) = result { log::error!("Failed to collect events: {}", e); } }); let output_thread = thread::spawn(move || { let result = consumer(receiver); if let Err(e) = result { log::error!("Failed to process events: {}", e); } }); log::debug!("Collector service started at {}", collector_arc.address()); Ok(CollectorService { collector: collector_arc, network_thread: Some(collector_thread), output_thread: Some(output_thread), }) } /// Returns the address of the service. pub fn address(&self) -> String { self.collector.address() } } impl Drop for CollectorService { /// Shuts down the service. fn drop(&mut self) { // TODO: log the shutdown of the service and any errors self.collector.stop().expect("Failed to stop the collector"); if let Some(thread) = self.network_thread.take() { thread.join().expect("Failed to join the collector thread"); } if let Some(thread) = self.output_thread.take() { thread.join().expect("Failed to join the output thread"); } } } /// The environment for the intercept mode. /// /// Running the build command requires a specific environment. The environment we /// need for intercepting the child processes is different for each intercept mode. /// /// The `Wrapper` mode requires a temporary directory with the executables that will /// be used to intercept the child processes. The executables are hard linked to the /// temporary directory. /// /// The `Preload` mode requires the path to the preload library that will be used to /// intercept the child processes. pub(crate) enum InterceptEnvironment { Wrapper { bin_dir: tempfile::TempDir, address: String, collector: CollectorService, }, Preload { path: PathBuf, address: String, collector: CollectorService, }, } impl InterceptEnvironment { /// Creates a new intercept environment. /// /// The `config` is the intercept configuration that specifies the mode and the /// required parameters for the mode. The `collector` is the service to collect /// the execution events. pub fn new(config: &config::Intercept, collector: CollectorService) -> anyhow::Result { let address = collector.address(); let result = match config { config::Intercept::Wrapper { path, directory, executables, } => { // Create a temporary directory and populate it with the executables. let bin_dir = tempfile::TempDir::with_prefix_in(directory, "bear-")?; for executable in executables { std::fs::hard_link(executable, path)?; } InterceptEnvironment::Wrapper { bin_dir, address, collector, } } config::Intercept::Preload { path } => InterceptEnvironment::Preload { path: path.clone(), address, collector, }, }; Ok(result) } /// Executes the build command in the intercept environment. /// /// The method is blocking and waits for the build command to finish. /// The method returns the exit code of the build command. Result failure /// indicates that the build command failed to start. pub fn execute_build_command(&self, input: args::BuildCommand) -> anyhow::Result { // TODO: record the execution of the build command let environment = self.environment(); let process = input.arguments[0].clone(); let arguments = input.arguments[1..].to_vec(); let mut child = Command::new(process); let exit_status = supervise(child.args(arguments).envs(environment))?; log::info!("Execution finished with status: {:?}", exit_status); // The exit code is not always available. When the process is killed by a signal, // the exit code is not available. In this case, we return the `FAILURE` exit code. let exit_code = exit_status .code() .map(|code| ExitCode::from(code as u8)) .unwrap_or(ExitCode::FAILURE); Ok(exit_code) } /// Returns the environment variables for the intercept environment. /// /// The environment variables are different for each intercept mode. /// It does not change the original environment variables, but creates /// the environment variables that are required for the intercept mode. fn environment(&self) -> Vec<(String, String)> { match self { InterceptEnvironment::Wrapper { bin_dir, address, .. } => { let path_original = env::var("PATH").unwrap_or_else(|_| String::new()); let path_updated = InterceptEnvironment::insert_to_path( &path_original, Self::path_to_string(bin_dir.path()), ); vec![ ("PATH".to_string(), path_updated), (KEY_DESTINATION.to_string(), address.clone()), ] } InterceptEnvironment::Preload { path, address, .. } => { let path_original = env::var(KEY_PRELOAD_PATH).unwrap_or_else(|_| String::new()); let path_updated = InterceptEnvironment::insert_to_path( &path_original, Self::path_to_string(path), ); vec![ (KEY_PRELOAD_PATH.to_string(), path_updated), (KEY_DESTINATION.to_string(), address.clone()), ] } } } /// Manipulate a `PATH` like environment value by inserting the `first` path into /// the original value. It removes the `first` path if it already exists in the /// original value. And it inserts the `first` path at the beginning of the value. fn insert_to_path(original: &str, first: String) -> String { let mut paths: Vec<_> = original.split(':').filter(|it| it != &first).collect(); paths.insert(0, first.as_str()); paths.join(":") } fn path_to_string(path: &Path) -> String { path.to_str().unwrap_or("").to_string() } } rizsotto-Bear-14c2e01/rust/bear/src/intercept/persistence.rs000066400000000000000000000112101476774233700242020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use super::Envelope; use serde_json::de::IoRead; use serde_json::StreamDeserializer; use std::io; /// Generate the build events from the file. /// /// Returns an iterator over the build events. /// Any error will interrupt the reading process and the remaining events will be lost. pub fn read(reader: impl io::Read) -> impl Iterator { let stream = StreamDeserializer::new(IoRead::new(reader)); stream.filter_map(|result| match result { Ok(value) => Some(value), Err(error) => { log::error!("Failed to read event: {:?}", error); None } }) } /// Write the build events to the file. /// /// Can fail if the events cannot be serialized or written to the file. /// Any error will interrupt the writing process and the file will be incomplete. pub fn write( mut writer: impl io::Write, envelopes: impl IntoIterator, ) -> Result<(), anyhow::Error> { for envelope in envelopes { serde_json::to_writer(&mut writer, &envelope)?; writer.write_all(b"\n")?; } Ok(()) } #[cfg(test)] mod test { use super::*; use crate::intercept::{Event, Execution, ProcessId, ReporterId}; use crate::vec_of_strings; use serde_json::json; use std::collections::HashMap; use std::path::PathBuf; #[test] fn read_write() { let events = expected_values(); let mut buffer = Vec::new(); write(&mut buffer, events.iter().cloned()).unwrap(); let mut cursor = io::Cursor::new(buffer); let read_events: Vec<_> = read(&mut cursor).collect(); assert_eq!(events, read_events); } #[test] fn read_write_empty() { let events = Vec::::new(); let mut buffer = Vec::new(); write(&mut buffer, events.iter().cloned()).unwrap(); let mut cursor = io::Cursor::new(buffer); let read_events: Vec<_> = read(&mut cursor).collect(); assert_eq!(events, read_events); } #[test] fn read_stops_on_errors() { let line1 = json!({ "rid": 42, "timestamp": 0, "event": { "pid": 11782, "execution": { "executable": "/usr/bin/clang", "arguments": ["clang", "-c", "main.c"], "working_dir": "/home/user", "environment": { "PATH": "/usr/bin", "HOME": "/home/user" } } } }); let line2 = json!({"rid": 42 }); let line3 = json!({ "rid": 42, "timestamp": 273, "event": { "pid": 11934, "execution": { "executable": "/usr/bin/clang", "arguments": ["clang", "-c", "output.c"], "working_dir": "/home/user", "environment": {} } } }); let content = format!("{}\n{}\n{}\n", line1, line2, line3); let mut cursor = io::Cursor::new(content); let read_events: Vec<_> = read(&mut cursor).collect(); // Only the fist event is read, all other lines are ignored. assert_eq!(expected_values()[0..1], read_events); } const REPORTER_ID: ReporterId = ReporterId(42); fn expected_values() -> Vec { vec![ Envelope { rid: REPORTER_ID, timestamp: 0, event: Event { pid: ProcessId(11782), execution: Execution { executable: PathBuf::from("/usr/bin/clang"), arguments: vec_of_strings!["clang", "-c", "main.c"], working_dir: PathBuf::from("/home/user"), environment: HashMap::from([ ("PATH".to_string(), "/usr/bin".to_string()), ("HOME".to_string(), "/home/user".to_string()), ]), }, }, }, Envelope { rid: REPORTER_ID, timestamp: 273, event: Event { pid: ProcessId(11934), execution: Execution { executable: PathBuf::from("/usr/bin/clang"), arguments: vec_of_strings!["clang", "-c", "output.c"], working_dir: PathBuf::from("/home/user"), environment: HashMap::from([]), }, }, }, ] } } rizsotto-Bear-14c2e01/rust/bear/src/intercept/supervise.rs000066400000000000000000000027511476774233700237150ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use anyhow::Result; use std::process::{Command, ExitStatus}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; use std::time; /// This method supervises the execution of a command. /// /// It starts the command and waits for its completion. It also forwards /// signals to the child process. The method returns the exit status of the /// child process. pub fn supervise(command: &mut Command) -> Result { let signaled = Arc::new(AtomicUsize::new(0)); for signal in signal_hook::consts::TERM_SIGNALS { signal_hook::flag::register_usize(*signal, Arc::clone(&signaled), *signal as usize)?; } let mut child = command.spawn()?; loop { // Forward signals to the child process, but don't exit the loop while it is running if signaled.swap(0usize, Ordering::SeqCst) != 0 { log::debug!("Received signal, forwarding to child process"); child.kill()?; } // Check if the child process has exited match child.try_wait() { Ok(Some(exit_status)) => { log::debug!("Child process exited"); return Ok(exit_status); } Ok(None) => { thread::sleep(time::Duration::from_millis(100)); } Err(e) => { log::error!("Error waiting for child process: {}", e); return Err(e.into()); } } } } rizsotto-Bear-14c2e01/rust/bear/src/intercept/tcp.rs000066400000000000000000000270701476774233700224570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! The module contains the implementation of the TCP collector and reporter. use std::io::{Read, Write}; use std::net::{SocketAddr, TcpListener, TcpStream}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::Sender; use std::sync::Arc; use super::{Collector, Envelope, Event, Reporter, ReporterId}; use chrono::Utc; use rand; /// Implements convenient methods for the `Envelope` type. impl Envelope { fn new(rid: &ReporterId, event: Event) -> Self { let timestamp = Utc::now().timestamp_millis() as u64; Envelope { rid: rid.clone(), timestamp, event, } } /// Read an envelope from a reader using TLV format. /// /// The envelope is serialized using JSON and the length of the JSON /// is written as a 4 byte big-endian integer before the JSON. fn read_from(reader: &mut impl Read) -> Result { let mut length_bytes = [0; 4]; reader.read_exact(&mut length_bytes)?; let length = u32::from_be_bytes(length_bytes) as usize; let mut buffer = vec![0; length]; reader.read_exact(&mut buffer)?; let envelope = serde_json::from_slice(buffer.as_ref())?; Ok(envelope) } /// Write an envelope to a writer using TLV format. /// /// The envelope is serialized using JSON and the length of the JSON /// is written as a 4 byte big-endian integer before the JSON. fn write_into(&self, writer: &mut impl Write) -> Result { let serialized_envelope = serde_json::to_string(&self)?; let bytes = serialized_envelope.into_bytes(); let length = bytes.len() as u32; writer.write_all(&length.to_be_bytes())?; writer.write_all(&bytes)?; Ok(length) } } /// Implements convenient methods for the `ReporterId` type. impl ReporterId { pub fn generate() -> Self { let id = rand::random::(); ReporterId(id) } } /// Represents a TCP event collector. pub struct CollectorOnTcp { shutdown: Arc, listener: TcpListener, address: SocketAddr, } impl CollectorOnTcp { /// Creates a new TCP event collector. /// /// The collector listens on a random port on the loopback interface. /// The address of the collector can be obtained by the `address` method. pub fn new() -> Result { let shutdown = Arc::new(AtomicBool::new(false)); let listener = TcpListener::bind("127.0.0.1:0")?; let address = listener.local_addr()?; let result = CollectorOnTcp { shutdown, listener, address, }; Ok(result) } fn send( &self, mut socket: TcpStream, destination: Sender, ) -> Result<(), anyhow::Error> { let envelope = Envelope::read_from(&mut socket)?; destination.send(envelope)?; Ok(()) } } impl Collector for CollectorOnTcp { fn address(&self) -> String { self.address.to_string() } /// Single-threaded implementation of the collector. /// /// The collector listens on the TCP port and accepts incoming connections. /// When a connection is accepted, the collector reads the events from the /// connection and sends them to the destination channel. fn collect(&self, destination: Sender) -> Result<(), anyhow::Error> { for stream in self.listener.incoming() { // This has to be the first thing to do, in order to implement the stop method! if self.shutdown.load(Ordering::Relaxed) { break; } match stream { Ok(connection) => { // ... (process the connection in a separate thread or task) self.send(connection, destination.clone())?; } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { // No new connection available, continue checking for shutdown continue; } Err(e) => { println!("Error: {}", e); break; } } } Ok(()) } /// Stops the collector by flipping the shutdown flag and connecting to the collector. /// /// The collector is stopped when the `collect` method sees the shutdown flag. /// To signal the collector to stop, we connect to the collector to unblock the /// `accept` call to check the shutdown flag. fn stop(&self) -> Result<(), anyhow::Error> { self.shutdown.store(true, Ordering::Relaxed); let _ = TcpStream::connect(self.address)?; Ok(()) } } /// Represents a TCP event reporter. pub struct ReporterOnTcp { destination: String, reporter_id: ReporterId, } impl ReporterOnTcp { /// Creates a new TCP reporter instance. /// /// It does not open the TCP connection yet. Stores the destination /// address and creates a unique reporter id. pub fn new(destination: String) -> Result { let reporter_id = ReporterId::generate(); let result = ReporterOnTcp { destination, reporter_id, }; Ok(result) } } impl Reporter for ReporterOnTcp { /// Sends an event to the remote collector. /// /// The event is wrapped in an envelope and sent to the remote collector. /// The TCP connection is opened and closed for each event. fn report(&self, event: Event) -> Result<(), anyhow::Error> { let envelope = Envelope::new(&self.reporter_id, event); let mut socket = TcpStream::connect(self.destination.clone())?; envelope.write_into(&mut socket)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use std::io::Cursor; use std::sync::mpsc::channel; use std::sync::Arc; use std::thread; use std::time::Duration; // Test that the serialization and deserialization of the Envelope works. // We write the Envelope to a buffer and read it back to check if the // deserialized Envelope is the same as the original one. #[test] fn read_write_works() { let mut writer = Cursor::new(vec![0; 1024]); for envelope in fixtures::ENVELOPES.iter() { let result = Envelope::write_into(envelope, &mut writer); assert!(result.is_ok()); } let mut reader = Cursor::new(writer.get_ref()); for envelope in fixtures::ENVELOPES.iter() { let result = Envelope::read_from(&mut reader); assert!(result.is_ok()); assert_eq!(result.unwrap(), envelope.clone()); } } // Test that the TCP reporter and the TCP collector work together. // We create a TCP collector and a TCP reporter, then we send events // to the reporter and check if the collector receives them. // // We use a bounded channel to send the events from the reporter to the // collector. The collector reads the events from the channel and checks // if they are the same as the original events. #[test] fn tcp_reporter_and_collectors_work() { let collector = CollectorOnTcp::new().unwrap(); let reporter = ReporterOnTcp::new(collector.address()).unwrap(); // Create wrapper to share the collector across threads. let thread_collector = Arc::new(collector); let main_collector = thread_collector.clone(); // Start the collector in a separate thread. let (input, output) = channel(); let receiver_thread = thread::spawn(move || { thread_collector.collect(input).unwrap(); }); // Send events to the reporter. for event in fixtures::EVENTS.iter() { let result = reporter.report(event.clone()); assert!(result.is_ok()); } // Call the stop method to stop the collector. This will close the // channel and the collector will stop reading from it. thread::sleep(Duration::from_secs(1)); main_collector.stop().unwrap(); // Empty the channel and assert that we received all the events. let mut count = 0; for envelope in output.iter() { assert!(fixtures::EVENTS.contains(&envelope.event)); count += 1; } assert_eq!(count, fixtures::EVENTS.len()); // shutdown the receiver thread receiver_thread.join().unwrap(); } mod fixtures { use super::*; use crate::intercept::{Execution, ProcessId}; use crate::{map_of_strings, vec_of_strings}; use std::collections::HashMap; use std::path::PathBuf; pub(super) static ENVELOPES: std::sync::LazyLock> = std::sync::LazyLock::new(|| { vec![ Envelope { rid: ReporterId::generate(), timestamp: timestamp(), event: Event { pid: pid(), execution: Execution { executable: PathBuf::from("/usr/bin/ls"), arguments: vec_of_strings!["ls", "-l"], working_dir: PathBuf::from("/tmp"), environment: HashMap::new(), }, }, }, Envelope { rid: ReporterId::generate(), timestamp: timestamp(), event: Event { pid: pid(), execution: Execution { executable: PathBuf::from("/usr/bin/cc"), arguments: vec_of_strings![ "cc", "-c", "./file_a.c", "-o", "./file_a.o" ], working_dir: PathBuf::from("/home/user"), environment: map_of_strings! { "PATH" => "/usr/bin:/bin", "HOME" => "/home/user", }, }, }, }, Envelope { rid: ReporterId::generate(), timestamp: timestamp(), event: Event { pid: pid(), execution: Execution { executable: PathBuf::from("/usr/bin/ld"), arguments: vec_of_strings!["ld", "-o", "./file_a", "./file_a.o"], working_dir: PathBuf::from("/opt/project"), environment: map_of_strings! { "PATH" => "/usr/bin:/bin", "LD_LIBRARY_PATH" => "/usr/lib:/lib", }, }, }, }, ] }); pub(super) static EVENTS: std::sync::LazyLock> = std::sync::LazyLock::new(|| ENVELOPES.iter().map(|e| e.event.clone()).collect()); fn timestamp() -> u64 { rand::random::() } fn pid() -> ProcessId { ProcessId(rand::random::()) } } } rizsotto-Bear-14c2e01/rust/bear/src/lib.rs000066400000000000000000000002361476774233700204350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later pub mod args; pub mod config; mod fixtures; pub mod intercept; pub mod modes; pub mod output; pub mod semantic; rizsotto-Bear-14c2e01/rust/bear/src/modes/000077500000000000000000000000001476774233700204275ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/modes/intercept.rs000066400000000000000000000025711476774233700227770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use crate::intercept::{CollectorService, Envelope, InterceptEnvironment}; use crate::{args, config}; use anyhow::Context; use std::process::ExitCode; use std::sync::mpsc::Receiver; /// The build interceptor is responsible for capturing the build commands and /// dispatching them to the consumer. The consumer is a function that processes /// the intercepted command executions. pub(super) struct BuildInterceptor { environment: InterceptEnvironment, } impl BuildInterceptor { /// Create a new process execution interceptor. pub(super) fn new(config: config::Main, consumer: F) -> anyhow::Result where F: FnOnce(Receiver) -> anyhow::Result<()>, F: Send + 'static, { let service = CollectorService::new(consumer) .with_context(|| "Failed to create the intercept service")?; let environment = InterceptEnvironment::new(&config.intercept, service) .with_context(|| "Failed to create the intercept environment")?; Ok(Self { environment }) } /// Run the build command in the intercept environment. pub(super) fn run_build_command(self, command: args::BuildCommand) -> anyhow::Result { self.environment .execute_build_command(command) .with_context(|| "Failed to execute the build command") } } rizsotto-Bear-14c2e01/rust/bear/src/modes/mod.rs000066400000000000000000000101131476774233700215500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later pub mod intercept; pub mod semantic; use crate::intercept::persistence::{read, write}; use crate::modes::intercept::BuildInterceptor; use crate::modes::semantic::SemanticAnalysisPipeline; use crate::{args, config}; use anyhow::Context; use std::fs::{File, OpenOptions}; use std::io; use std::io::BufReader; use std::process::ExitCode; /// The mode trait is used to run the application in different modes. pub trait Mode { fn run(self) -> anyhow::Result; } /// The intercept mode we are only capturing the build commands /// and write it into the output file. pub struct Intercept { command: args::BuildCommand, interceptor: BuildInterceptor, } impl Intercept { /// Create a new intercept mode instance. pub fn from( command: args::BuildCommand, output: args::BuildEvents, config: config::Main, ) -> anyhow::Result { let file_name = output.file_name.as_str(); let output_file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(file_name) .map(io::BufWriter::new) .with_context(|| format!("Failed to open file: {:?}", file_name))?; let interceptor = BuildInterceptor::new(config, move |envelopes| write(output_file, envelopes))?; Ok(Self { command, interceptor, }) } } impl Mode for Intercept { /// Run the intercept mode by setting up the collector service and /// the intercept environment. The build command is executed in the /// intercept environment. /// /// The exit code is based on the result of the build command. fn run(self) -> anyhow::Result { self.interceptor.run_build_command(self.command) } } /// The semantic mode we are deduct the semantic meaning of the /// executed commands from the build process. pub struct Semantic { event_file: BufReader, semantic: SemanticAnalysisPipeline, } impl Semantic { pub fn from( input: args::BuildEvents, output: args::BuildSemantic, config: config::Main, ) -> anyhow::Result { let file_name = input.file_name.as_str(); let event_file = OpenOptions::new() .read(true) .open(file_name) .map(BufReader::new) .with_context(|| format!("Failed to open file: {:?}", file_name))?; let semantic = SemanticAnalysisPipeline::from(output, &config)?; Ok(Self { event_file, semantic, }) } } impl Mode for Semantic { /// Run the semantic mode by reading the event file and analyzing the events. /// /// The exit code is based on the result of the output writer. fn run(self) -> anyhow::Result { self.semantic .analyze_and_write(read(self.event_file)) .map(|_| ExitCode::SUCCESS) } } /// The all model is combining the intercept and semantic modes. pub struct Combined { command: args::BuildCommand, interceptor: BuildInterceptor, } impl Combined { /// Create a new all mode instance. pub fn from( command: args::BuildCommand, output: args::BuildSemantic, config: config::Main, ) -> anyhow::Result { let semantic = SemanticAnalysisPipeline::from(output, &config)?; let interceptor = BuildInterceptor::new(config, move |envelopes| { semantic.analyze_and_write(envelopes) })?; Ok(Self { command, interceptor, }) } } impl Mode for Combined { /// Run the all mode by setting up the collector service and the intercept environment. /// The build command is executed in the intercept environment. The collected events are /// then processed by the semantic recognition and transformation. The result is written /// to the output file. /// /// The exit code is based on the result of the build command. fn run(self) -> anyhow::Result { self.interceptor.run_build_command(self.command) } } rizsotto-Bear-14c2e01/rust/bear/src/modes/semantic.rs000066400000000000000000000211771476774233700226100ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use crate::intercept::Envelope; use crate::output::OutputWriter; use crate::semantic::interpreters::create_interpreter; use crate::semantic::transformation::Transformation; use crate::{args, config, output, semantic}; use anyhow::Context; use std::fs::{File, OpenOptions}; use std::io::{BufReader, BufWriter}; use std::path::{Path, PathBuf}; /// The semantic analysis that is independent of the event source. pub(super) struct SemanticAnalysisPipeline { interpreter: Box, transform: Box, output_writer: OutputWriterImpl, } impl SemanticAnalysisPipeline { /// Create a new semantic mode instance. pub(super) fn from(output: args::BuildSemantic, config: &config::Main) -> anyhow::Result { let interpreter = create_interpreter(config); let transform = Transformation::from(&config.output); let output_writer = OutputWriterImpl::create(&output, &config.output)?; Ok(Self { interpreter: Box::new(interpreter), transform: Box::new(transform), output_writer, }) } /// Consumer the envelopes for analysis and write the result to the output file. /// This implements the pipeline of the semantic analysis. pub(super) fn analyze_and_write( self, envelopes: impl IntoIterator, ) -> anyhow::Result<()> { // Set up the pipeline of compilation database entries. let entries = envelopes .into_iter() .inspect(|envelope| log::debug!("envelope: {}", envelope)) .map(|envelope| envelope.event.execution) .flat_map(|execution| self.interpreter.recognize(&execution)) .inspect(|semantic| log::debug!("semantic: {:?}", semantic)) .flat_map(|semantic| self.transform.apply(semantic)); // Consume the entries and write them to the output file. // The exit code is based on the result of the output writer. self.output_writer.run(entries) } } /// The output writer implementation. /// /// This is a workaround for the lack of trait object support for generic arguments. /// https://doc.rust-lang.org/reference/items/traits.html#object-safety. pub(crate) enum OutputWriterImpl { Clang(ClangOutputWriter), Semantic(SemanticOutputWriter), } impl OutputWriter for OutputWriterImpl { fn run( &self, compiler_calls: impl Iterator, ) -> anyhow::Result<()> { match self { OutputWriterImpl::Clang(writer) => writer.run(compiler_calls), OutputWriterImpl::Semantic(writer) => writer.run(compiler_calls), } } } impl OutputWriterImpl { /// Create a new instance of the output writer. pub(crate) fn create( args: &args::BuildSemantic, config: &config::Output, ) -> anyhow::Result { // TODO: This method should fail early if the output file is not writable. match config { config::Output::Clang { format, sources, duplicates, .. } => { let result = ClangOutputWriter { output: PathBuf::from(&args.file_name), append: args.append, source_filter: sources.clone(), duplicate_filter: duplicates.clone(), command_as_array: format.command_as_array, formatter: From::from(format), }; Ok(OutputWriterImpl::Clang(result)) } config::Output::Semantic { .. } => { let result = SemanticOutputWriter { output: PathBuf::from(&args.file_name), }; Ok(OutputWriterImpl::Semantic(result)) } } } } pub(crate) struct SemanticOutputWriter { output: PathBuf, } impl OutputWriter for SemanticOutputWriter { fn run(&self, entries: impl Iterator) -> anyhow::Result<()> { let file_name = &self.output; let file = File::create(file_name) .map(BufWriter::new) .with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?; semantic::serialize(file, entries)?; Ok(()) } } /// Responsible for writing the final compilation database file. /// /// Implements filtering, formatting and atomic file writing. /// (Atomic file writing implemented by writing to a temporary file and renaming it.) /// /// Filtering is implemented by the `filter` module, and the formatting is implemented by the /// `json_compilation_db` module. pub(crate) struct ClangOutputWriter { output: PathBuf, append: bool, source_filter: config::SourceFilter, duplicate_filter: config::DuplicateFilter, command_as_array: bool, formatter: output::formatter::EntryFormatter, } impl OutputWriter for ClangOutputWriter { /// Implements the main logic of the output writer. fn run( &self, compiler_calls: impl Iterator, ) -> anyhow::Result<()> { let entries = compiler_calls.flat_map(|compiler_call| self.formatter.apply(compiler_call)); if self.append && self.output.exists() { let entries_from_db = Self::read_from_compilation_db(self.output.as_path())?; let final_entries = entries.chain(entries_from_db); self.write_into_compilation_db(final_entries) } else { if self.append { log::warn!("The output file does not exist, the append option is ignored."); } self.write_into_compilation_db(entries) } } } impl ClangOutputWriter { /// Write the entries to the compilation database. /// /// The entries are written to a temporary file and then renamed to the final output. /// This guaranties that the output file is always in a consistent state. fn write_into_compilation_db( &self, entries: impl Iterator, ) -> anyhow::Result<()> { // Filter out the entries as per the configuration. let mut source_filter: output::filter::EntryPredicate = From::from(&self.source_filter); let mut duplicate_filter: output::filter::EntryPredicate = From::from(&self.duplicate_filter); let filtered_entries = entries.filter(move |entry| source_filter(entry) && duplicate_filter(entry)); // Write the entries to a temporary file. self.write_into_temporary_compilation_db(filtered_entries) .and_then(|temp| { // Rename the temporary file to the final output. std::fs::rename(temp.as_path(), self.output.as_path()).with_context(|| { format!( "Failed to rename file from '{:?}' to '{:?}'.", temp.as_path(), self.output.as_path() ) }) }) } /// Write the entries to a temporary file and returns the temporary file name. fn write_into_temporary_compilation_db( &self, entries: impl Iterator, ) -> anyhow::Result { // Generate a temporary file name. let file_name = self.output.with_extension("tmp"); // Open the file for writing. let file = File::create(&file_name) .map(BufWriter::new) .with_context(|| format!("Failed to create file: {:?}", file_name.as_path()))?; // Write the entries to the file. output::clang::write(self.command_as_array, file, entries) .with_context(|| format!("Failed to write entries: {:?}", file_name.as_path()))?; // Return the temporary file name. Ok(file_name) } /// Read the compilation database from a file. fn read_from_compilation_db( source: &Path, ) -> anyhow::Result> { let source_copy = source.to_path_buf(); let file = OpenOptions::new() .read(true) .open(source) .map(BufReader::new) .with_context(|| format!("Failed to open file: {:?}", source))?; let entries = output::clang::read(file) .map(move |candidate| { // We are here to log the error. candidate.map_err(|error| { log::error!("Failed to read file: {:?}, reason: {}", source_copy, error); error }) }) .filter_map(Result::ok); Ok(entries) } } rizsotto-Bear-14c2e01/rust/bear/src/output/000077500000000000000000000000001476774233700206605ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/output/clang/000077500000000000000000000000001476774233700217445ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/output/clang/iterator.rs000066400000000000000000000046741476774233700241560ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! Provides an iterator over a JSON array of objects. //! //! from https://github.com/serde-rs/json/issues/404#issuecomment-892957228 use std::io::{self, Read}; use serde::de::DeserializeOwned; use serde_json::{Deserializer, Error, Result}; pub fn iter_json_array(mut reader: R) -> impl Iterator> where T: DeserializeOwned, R: io::Read, { let mut at_start = State::AtStart; std::iter::from_fn(move || yield_next_obj(&mut reader, &mut at_start).transpose()) } enum State { AtStart, AtMiddle, Finished, Failed, } fn yield_next_obj(mut reader: R, state: &mut State) -> Result> where T: DeserializeOwned, R: io::Read, { match state { State::AtStart => { if read_skipping_ws(&mut reader)? == b'[' { // read the next char to see if the array is empty let peek = read_skipping_ws(&mut reader)?; if peek == b']' { *state = State::Finished; Ok(None) } else { *state = State::AtMiddle; deserialize_single(io::Cursor::new([peek]).chain(reader)).map(Some) } } else { *state = State::Failed; Err(serde::de::Error::custom("expected `[`")) } } State::AtMiddle => match read_skipping_ws(&mut reader)? { b',' => deserialize_single(reader).map(Some), b']' => { *state = State::Finished; Ok(None) } _ => { *state = State::Failed; Err(serde::de::Error::custom("expected `,` or `]`")) } }, State::Finished => Ok(None), State::Failed => Ok(None), } } fn deserialize_single(reader: R) -> Result where T: DeserializeOwned, R: io::Read, { let next_obj = Deserializer::from_reader(reader).into_iter::().next(); match next_obj { Some(result) => result, None => Err(serde::de::Error::custom("premature EOF")), } } fn read_skipping_ws(mut reader: impl io::Read) -> Result { loop { let mut byte = 0u8; if let Err(io) = reader.read_exact(std::slice::from_mut(&mut byte)) { return Err(Error::io(io)); } if !byte.is_ascii_whitespace() { return Ok(byte); } } } rizsotto-Bear-14c2e01/rust/bear/src/output/clang/mod.rs000066400000000000000000000067101476774233700230750ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! This crate provides support for reading and writing JSON compilation database files. //! //! A compilation database is a set of records which describe the compilation of the //! source files in a given project. It describes the compiler invocation command to //! compile a source module to an object file. //! //! This database can have many forms. One well known and supported format is the JSON //! compilation database, which is a simple JSON file having the list of compilation //! as an array. The definition of the JSON compilation database files is done in the //! LLVM project [documentation](https://clang.llvm.org/docs/JSONCompilationDatabase.html). use serde::ser::{SerializeSeq, Serializer}; use serde_json::Error; mod iterator; mod tests; mod type_de; mod type_ser; /// Represents an entry of the compilation database. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { /// The main translation unit source processed by this compilation step. /// This is used by tools as the key into the compilation database. /// There can be multiple command objects for the same file, for example if the same /// source file is compiled with different configurations. pub file: std::path::PathBuf, /// The compile command executed. This must be a valid command to rerun the exact /// compilation step for the translation unit in the environment the build system uses. /// Shell expansion is not supported. pub arguments: Vec, /// The working directory of the compilation. All paths specified in the command or /// file fields must be either absolute or relative to this directory. pub directory: std::path::PathBuf, /// The name of the output created by this compilation step. This field is optional. /// It can be used to distinguish different processing modes of the same input file. pub output: Option, } /// Deserialize entries from a JSON array into an iterator. pub fn read(reader: impl std::io::Read) -> impl Iterator> { iterator::iter_json_array(reader) } /// The entries are written in the format specified by the configuration. pub fn write( command_as_array: bool, writer: impl std::io::Write, entries: impl Iterator, ) -> Result<(), Error> { let method = if command_as_array { write_with_arguments } else { write_with_command }; method(writer, entries) } /// Serialize entries from an iterator into a JSON array. /// /// It uses the `arguments` field of the `Entry` struct to serialize the array of strings. pub(super) fn write_with_arguments( writer: impl std::io::Write, entries: impl Iterator, ) -> Result<(), Error> { let mut ser = serde_json::Serializer::pretty(writer); let mut seq = ser.serialize_seq(None)?; for entry in entries { seq.serialize_element(&entry)?; } seq.end() } /// Serialize entries from an iterator into a JSON array. /// /// It uses the `arguments` field of the `Entry` struct to serialize the array of strings. pub(super) fn write_with_command( writer: impl std::io::Write, entries: impl Iterator, ) -> Result<(), Error> { let mut ser = serde_json::Serializer::pretty(writer); let mut seq = ser.serialize_seq(None)?; for entry in entries { let entry = type_ser::EntryWithCommand::from(entry); seq.serialize_element(&entry)?; } seq.end() } rizsotto-Bear-14c2e01/rust/bear/src/output/clang/tests.rs000066400000000000000000000232211476774233700234540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later #[cfg(test)] mod failures { use super::super::*; use serde_json::error::Category; use serde_json::json; macro_rules! assert_semantic_error { ($x:expr) => { match $x { Some(Err(error)) => assert_eq!(error.classify(), Category::Data), _ => assert!(false, "shout be semantic error"), } }; } #[test] fn load_non_json_content() { let content = r#"this is not json"#; let mut result = read(content.as_bytes()); assert_semantic_error!(result.next()); assert!(result.next().is_none()); } #[test] fn load_not_expected_json_content() { let content = json!({ "file": "string" }).to_string(); let mut result = read(content.as_bytes()); assert_semantic_error!(result.next()); assert!(result.next().is_none()); } #[test] fn load_on_bad_value() { let content = json!([ { "directory": " ", "file": "./file_a.c", "command": "cc -Dvalue=\"this" } ]) .to_string(); let mut result = read(content.as_bytes()); assert_semantic_error!(result.next()); assert!(result.next().is_none()); } #[test] fn load_on_multiple_commands() { let content = json!([ { "directory": " ", "file": "./file_a.c", "command": "cc source.c", "arguments": ["cc", "source.c"], } ]) .to_string(); let mut result = read(content.as_bytes()); assert_semantic_error!(result.next()); assert!(result.next().is_none()); } } #[cfg(test)] mod success { use super::super::*; use serde_json::json; mod empty { use super::*; #[test] fn load_empty_array() { let content = json!([]).to_string(); let mut result = read(content.as_bytes()); assert!(result.next().is_none()); } } mod basic { use super::*; use crate::vec_of_strings; use std::io::{Cursor, Seek, SeekFrom}; fn expected_values() -> Vec { vec![ Entry { directory: std::path::PathBuf::from("/home/user"), file: std::path::PathBuf::from("./file_a.c"), arguments: vec_of_strings!("cc", "-c", "./file_a.c", "-o", "./file_a.o"), output: None, }, Entry { directory: std::path::PathBuf::from("/home/user"), file: std::path::PathBuf::from("./file_b.c"), arguments: vec_of_strings!("cc", "-c", "./file_b.c", "-o", "./file_b.o"), output: Some(std::path::PathBuf::from("./file_b.o")), }, ] } fn expected_with_array_syntax() -> serde_json::Value { json!([ { "directory": "/home/user", "file": "./file_a.c", "arguments": ["cc", "-c", "./file_a.c", "-o", "./file_a.o"] }, { "directory": "/home/user", "file": "./file_b.c", "output": "./file_b.o", "arguments": ["cc", "-c", "./file_b.c", "-o", "./file_b.o"] } ]) } fn expected_with_string_syntax() -> serde_json::Value { json!([ { "directory": "/home/user", "file": "./file_a.c", "command": "cc -c ./file_a.c -o ./file_a.o" }, { "directory": "/home/user", "file": "./file_b.c", "output": "./file_b.o", "command": "cc -c ./file_b.c -o ./file_b.o" } ]) } #[test] fn load_content_with_string_command_syntax() { let content = expected_with_string_syntax().to_string(); let result = read(content.as_bytes()); let entries: Vec = result.map(|e| e.unwrap()).collect(); assert_eq!(expected_values(), entries); } #[test] fn load_content_with_array_command_syntax() { let content = expected_with_array_syntax().to_string(); let result = read(content.as_bytes()); let entries: Vec = result.map(|e| e.unwrap()).collect(); assert_eq!(expected_values(), entries); } #[test] fn save_with_array_command_syntax() -> Result<(), Error> { let input = expected_values(); // Create fake "file" let mut buffer = Cursor::new(Vec::new()); let result = write_with_arguments(&mut buffer, input.into_iter()); assert!(result.is_ok()); // Use the fake "file" as input buffer.seek(SeekFrom::Start(0)).unwrap(); let content: serde_json::Value = serde_json::from_reader(&mut buffer)?; assert_eq!(expected_with_array_syntax(), content); Ok(()) } #[test] fn save_with_string_command_syntax() -> Result<(), Error> { let input = expected_values(); // Create fake "file" let mut buffer = Cursor::new(Vec::new()); let result = write_with_command(&mut buffer, input.into_iter()); assert!(result.is_ok()); // Use the fake "file" as input buffer.seek(SeekFrom::Start(0)).unwrap(); let content: serde_json::Value = serde_json::from_reader(&mut buffer)?; assert_eq!(expected_with_string_syntax(), content); Ok(()) } } mod quoted { use super::*; use crate::vec_of_strings; use serde_json::Value; use std::io::{Cursor, Seek, SeekFrom}; fn expected_values() -> Vec { vec![ Entry { directory: std::path::PathBuf::from("/home/user"), file: std::path::PathBuf::from("./file_a.c"), arguments: vec_of_strings!( "cc", "-c", "-D", r#"name=\"me\""#, "./file_a.c", "-o", "./file_a.o" ), output: None, }, Entry { directory: std::path::PathBuf::from("/home/user"), file: std::path::PathBuf::from("./file_b.c"), arguments: vec_of_strings!( "cc", "-c", "-D", r#"name="me""#, "./file_b.c", "-o", "./file_b.o" ), output: None, }, ] } fn expected_with_array_syntax() -> serde_json::Value { json!([ { "directory": "/home/user", "file": "./file_a.c", "arguments": ["cc", "-c", "-D", r#"name=\"me\""#, "./file_a.c", "-o", "./file_a.o"] }, { "directory": "/home/user", "file": "./file_b.c", "arguments": ["cc", "-c", "-D", r#"name="me""#, "./file_b.c", "-o", "./file_b.o"] } ]) } fn expected_with_string_syntax() -> serde_json::Value { json!([ { "directory": "/home/user", "file": "./file_a.c", "command": r#"cc -c -D 'name=\"me\"' ./file_a.c -o ./file_a.o"# }, { "directory": "/home/user", "file": "./file_b.c", "command": r#"cc -c -D 'name="me"' ./file_b.c -o ./file_b.o"# } ]) } #[test] fn load_content_with_array_command_syntax() { let content = expected_with_array_syntax().to_string(); let result = read(content.as_bytes()); let entries: Vec = result.map(|e| e.unwrap()).collect(); assert_eq!(expected_values(), entries); } #[test] fn save_with_array_command_syntax() -> Result<(), Error> { let input = expected_values(); // Create fake "file" let mut buffer = Cursor::new(Vec::new()); let result = write_with_arguments(&mut buffer, input.into_iter()); assert!(result.is_ok()); // Use the fake "file" as input buffer.seek(SeekFrom::Start(0)).unwrap(); let content: Value = serde_json::from_reader(&mut buffer)?; assert_eq!(expected_with_array_syntax(), content); Ok(()) } #[test] fn save_with_string_command_syntax() -> Result<(), Error> { let input = expected_values(); // Create fake "file" let mut buffer = Cursor::new(Vec::new()); let result = write_with_command(&mut buffer, input.into_iter()); assert!(result.is_ok()); // Use the fake "file" as input buffer.seek(SeekFrom::Start(0)).unwrap(); let content: Value = serde_json::from_reader(&mut buffer)?; assert_eq!(expected_with_string_syntax(), content); Ok(()) } } } rizsotto-Bear-14c2e01/rust/bear/src/output/clang/type_de.rs000066400000000000000000000130101476774233700237360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! Implements deserialization of the `Entry` struct. use std::fmt; use std::path; use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; use super::Entry; impl<'de> Deserialize<'de> for Entry { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { enum Field { Directory, File, Command, Arguments, Output, } const FIELDS: &[&str] = &["directory", "file", "command", "arguments", "output"]; impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct FieldVisitor; impl Visitor<'_> for FieldVisitor { type Value = Field; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter .write_str("`directory`, `file`, `command`, `arguments`, or `output`") } fn visit_str(self, value: &str) -> Result where E: de::Error, { match value { "directory" => Ok(Field::Directory), "file" => Ok(Field::File), "command" => Ok(Field::Command), "arguments" => Ok(Field::Arguments), "output" => Ok(Field::Output), _ => Err(de::Error::unknown_field(value, FIELDS)), } } } deserializer.deserialize_identifier(FieldVisitor) } } struct EntryVisitor; impl<'de> Visitor<'de> for EntryVisitor { type Value = Entry; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("struct Entry") } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut directory: Option = None; let mut file: Option = None; let mut command: Option = None; let mut arguments: Option> = None; let mut output: Option = None; while let Some(key) = map.next_key()? { match key { Field::Directory => { if directory.is_some() { return Err(de::Error::duplicate_field("directory")); } directory = Some(map.next_value()?); } Field::File => { if file.is_some() { return Err(de::Error::duplicate_field("file")); } file = Some(map.next_value()?); } Field::Command => { if command.is_some() { return Err(de::Error::duplicate_field("command")); } command = Some(map.next_value()?); } Field::Arguments => { if arguments.is_some() { return Err(de::Error::duplicate_field("arguments")); } arguments = Some(map.next_value()?); } Field::Output => { if output.is_some() { return Err(de::Error::duplicate_field("output")); } output = Some(map.next_value()?); } } } let directory = directory.ok_or_else(|| de::Error::missing_field("directory"))?; let file = file.ok_or_else(|| de::Error::missing_field("file"))?; if arguments.is_some() && command.is_some() { return Err(de::Error::custom( "Either `command` or `arguments` field need to be specified, but not both.", )); } let arguments = arguments.map_or_else( || { command .ok_or_else(|| de::Error::missing_field("`command` or `arguments`")) .and_then(|cmd| { shell_words::split(cmd.as_str()).map_err(|_| { de::Error::invalid_value( de::Unexpected::Str(cmd.as_str()), &"quotes needs to be matched", ) }) }) }, Ok, )?; Ok(Entry { directory, file, arguments, output, }) } } deserializer.deserialize_struct("Entry", FIELDS, EntryVisitor) } } rizsotto-Bear-14c2e01/rust/bear/src/output/clang/type_ser.rs000066400000000000000000000034351476774233700241510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! Implements serialization of the `Entry` struct. use serde::ser::{Serialize, SerializeStruct, Serializer}; use super::Entry; impl Serialize for Entry { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let size = if self.output.is_some() { 4 } else { 3 }; let mut state = serializer.serialize_struct("Entry", size)?; state.serialize_field("directory", &self.directory)?; state.serialize_field("file", &self.file)?; state.serialize_field("arguments", &self.arguments)?; if self.output.is_some() { state.serialize_field("output", &self.output)?; } state.end() } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct EntryWithCommand { pub file: std::path::PathBuf, pub command: String, pub directory: std::path::PathBuf, pub output: Option, } impl From for EntryWithCommand { fn from(entry: Entry) -> Self { Self { file: entry.file, command: shell_words::join(&entry.arguments), directory: entry.directory, output: entry.output, } } } impl Serialize for EntryWithCommand { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let size = if self.output.is_some() { 4 } else { 3 }; let mut state = serializer.serialize_struct("Entry", size)?; state.serialize_field("directory", &self.directory)?; state.serialize_field("file", &self.file)?; state.serialize_field("command", &self.command)?; if self.output.is_some() { state.serialize_field("output", &self.output)?; } state.end() } } rizsotto-Bear-14c2e01/rust/bear/src/output/filter.rs000066400000000000000000000401341476774233700225150ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use std::hash::Hash; use std::path::Path; use crate::config; use crate::output::clang::Entry; use builder::create_hash; use builder::EntryPredicateBuilder as Builder; /// A predicate that can be used to filter compilation database entries. /// /// If the predicate returns `true`, the entry is included in the result set. /// If the predicate returns `false`, the entry is excluded from the result set. pub type EntryPredicate = Box bool>; impl From<&config::SourceFilter> for EntryPredicate { /// Create a filter from the configuration. fn from(config: &config::SourceFilter) -> Self { let source_exist_check = Builder::filter_by_source_existence(config.only_existing_files); let mut source_path_checks = Builder::new(); for config::DirectoryFilter { path, ignore } in &config.paths { let filter = Builder::filter_by_source_path(path); match ignore { config::Ignore::Always => { source_path_checks = source_path_checks & !filter; } config::Ignore::Never => { source_path_checks = source_path_checks & filter; } } } (source_exist_check & source_path_checks).build() } } impl From<&config::DuplicateFilter> for EntryPredicate { /// Create a filter from the configuration. fn from(config: &config::DuplicateFilter) -> Self { let hash_function = create_hash(&config.by_fields); Builder::filter_duplicate_entries(hash_function).build() } } mod builder { use super::*; use std::collections::HashSet; use std::hash::{DefaultHasher, Hasher}; /// Represents a builder object that can be used to construct an entry predicate. pub(super) struct EntryPredicateBuilder { candidate: Option, } impl EntryPredicateBuilder { /// Creates an entry predicate from the builder. pub(super) fn build(self) -> EntryPredicate { match self.candidate { Some(predicate) => predicate, None => Box::new(|_: &Entry| true), } } /// Construct a predicate builder that is empty. #[inline] pub(crate) fn new() -> Self { Self { candidate: None } } /// Construct a predicate builder that implements a predicate. #[inline] fn from

(predicate: P) -> Self where P: FnMut(&Entry) -> bool + 'static, { Self { candidate: Some(Box::new(predicate)), } } /// Create a predicate that filters out entries /// that are not using any of the given source paths. pub(super) fn filter_by_source_path(path: &Path) -> Self { let owned_path = path.to_owned(); Self::from(move |entry| entry.file.starts_with(owned_path.clone())) } /// Create a predicate that filters out entries /// that source file does not exist. pub(super) fn filter_by_source_existence(only_existing: bool) -> Self { if only_existing { Self::from(|entry| entry.file.is_file()) } else { Self::new() } } /// Create a predicate that filters out entries /// that are already in the compilation database based on their hash. pub(super) fn filter_duplicate_entries( hash_function: impl Fn(&Entry) -> u64 + 'static, ) -> Self { let mut have_seen = HashSet::new(); Self::from(move |entry| { let hash = hash_function(entry); if !have_seen.contains(&hash) { have_seen.insert(hash); true } else { false } }) } } /// Implement the AND operator for combining predicates. impl std::ops::BitAnd for EntryPredicateBuilder { type Output = EntryPredicateBuilder; fn bitand(self, rhs: Self) -> Self::Output { match (self.candidate, rhs.candidate) { (None, None) => EntryPredicateBuilder::new(), (None, some) => EntryPredicateBuilder { candidate: some }, (some, None) => EntryPredicateBuilder { candidate: some }, (Some(mut lhs), Some(mut rhs)) => EntryPredicateBuilder::from(move |entry| { let result = lhs(entry); if result { rhs(entry) } else { result } }), } } } /// Implement the NOT operator for combining predicates. impl std::ops::Not for EntryPredicateBuilder { type Output = EntryPredicateBuilder; fn not(self) -> Self::Output { match self.candidate { Some(mut original) => Self::from(move |entry| { let result = original(entry); !result }), None => Self::new(), } } } /// Create a hash function that is using the given fields to calculate the hash of an entry. pub(super) fn create_hash(fields: &[config::OutputFields]) -> impl Fn(&Entry) -> u64 + 'static { let owned_fields: Vec = fields.to_vec(); move |entry: &Entry| { let mut hasher = DefaultHasher::new(); for field in &owned_fields { match field { config::OutputFields::Directory => entry.directory.hash(&mut hasher), config::OutputFields::File => entry.file.hash(&mut hasher), config::OutputFields::Arguments => entry.arguments.hash(&mut hasher), config::OutputFields::Output => entry.output.hash(&mut hasher), } } hasher.finish() } } #[cfg(test)] mod sources_test { use super::*; use crate::vec_of_strings; use std::path::PathBuf; #[test] fn test_filter_by_source_paths() { let input: Vec = vec![ Entry { file: PathBuf::from("/home/user/project/source/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c"], directory: PathBuf::from("/home/user/project"), output: None, }, Entry { file: PathBuf::from("/home/user/project/test/source.c"), arguments: vec_of_strings!["cc", "-c", "test.c"], directory: PathBuf::from("/home/user/project"), output: None, }, ]; let expected: Vec = vec![input[0].clone()]; let config = config::SourceFilter { only_existing_files: false, paths: vec![ config::DirectoryFilter { path: PathBuf::from("/home/user/project/source"), ignore: config::Ignore::Never, }, config::DirectoryFilter { path: PathBuf::from("/home/user/project/test"), ignore: config::Ignore::Always, }, ], }; let sut: EntryPredicate = From::from(&config); let result: Vec = input.into_iter().filter(sut).collect(); assert_eq!(result, expected); } } #[cfg(test)] mod existence_test { use super::*; use crate::vec_of_strings; use std::hash::{Hash, Hasher}; use std::path::PathBuf; #[test] fn test_duplicate_detection_works() { let input: Vec = vec![ Entry { file: PathBuf::from("/home/user/project/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c"], directory: PathBuf::from("/home/user/project"), output: Some(PathBuf::from("/home/user/project/source.o")), }, Entry { file: PathBuf::from("/home/user/project/source.c"), arguments: vec_of_strings!["cc", "-c", "-Wall", "source.c"], directory: PathBuf::from("/home/user/project"), output: Some(PathBuf::from("/home/user/project/source.o")), }, Entry { file: PathBuf::from("/home/user/project/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c", "-o", "test.o"], directory: PathBuf::from("/home/user/project"), output: Some(PathBuf::from("/home/user/project/test.o")), }, ]; let expected: Vec = vec![input[0].clone(), input[2].clone()]; let hash_function = |entry: &Entry| { let mut hasher = DefaultHasher::new(); entry.file.hash(&mut hasher); entry.output.hash(&mut hasher); hasher.finish() }; let sut: EntryPredicate = EntryPredicateBuilder::filter_duplicate_entries(hash_function).build(); let result: Vec = input.into_iter().filter(sut).collect(); assert_eq!(result, expected); } } #[cfg(test)] mod create_hash_tests { use super::*; use crate::vec_of_strings; use std::path::PathBuf; #[test] fn test_create_hash_with_directory_field() { let entry = create_test_entry(); let fields = vec![config::OutputFields::Directory]; let hash_function = create_hash(&fields); let hash = hash_function(&entry); let mut hasher = DefaultHasher::new(); entry.directory.hash(&mut hasher); let expected_hash = hasher.finish(); assert_eq!(hash, expected_hash); } #[test] fn test_create_hash_with_file_field() { let entry = create_test_entry(); let fields = vec![config::OutputFields::File]; let hash_function = create_hash(&fields); let hash = hash_function(&entry); let mut hasher = DefaultHasher::new(); entry.file.hash(&mut hasher); let expected_hash = hasher.finish(); assert_eq!(hash, expected_hash); } #[test] fn test_create_hash_with_arguments_field() { let entry = create_test_entry(); let fields = vec![config::OutputFields::Arguments]; let hash_function = create_hash(&fields); let hash = hash_function(&entry); let mut hasher = DefaultHasher::new(); entry.arguments.hash(&mut hasher); let expected_hash = hasher.finish(); assert_eq!(hash, expected_hash); } #[test] fn test_create_hash_with_output_field() { let entry = create_test_entry(); let fields = vec![config::OutputFields::Output]; let hash_function = create_hash(&fields); let hash = hash_function(&entry); let mut hasher = DefaultHasher::new(); entry.output.hash(&mut hasher); let expected_hash = hasher.finish(); assert_eq!(hash, expected_hash); } #[test] fn test_create_hash_with_multiple_fields() { let entry = create_test_entry(); let fields = vec![ config::OutputFields::Directory, config::OutputFields::File, config::OutputFields::Arguments, config::OutputFields::Output, ]; let hash_function = create_hash(&fields); let hash = hash_function(&entry); let mut hasher = DefaultHasher::new(); entry.directory.hash(&mut hasher); entry.file.hash(&mut hasher); entry.arguments.hash(&mut hasher); entry.output.hash(&mut hasher); let expected_hash = hasher.finish(); assert_eq!(hash, expected_hash); } fn create_test_entry() -> Entry { Entry { file: PathBuf::from("/home/user/project/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c"], directory: PathBuf::from("/home/user/project"), output: Some(PathBuf::from("/home/user/project/source.o")), } } } #[cfg(test)] mod bitand_tests { use super::*; use crate::vec_of_strings; use std::path::PathBuf; #[test] fn test_bitand_both_predicates_true() { let input = create_test_entries(); let predicate1 = EntryPredicateBuilder::from(|_: &Entry| true); let predicate2 = EntryPredicateBuilder::from(|_: &Entry| true); let combined_predicate = (predicate1 & predicate2).build(); let result: Vec = input.into_iter().filter(combined_predicate).collect(); assert_eq!(result.len(), 1); } #[test] fn test_bitand_first_predicate_false() { let input = create_test_entries(); let predicate1 = EntryPredicateBuilder::from(|_: &Entry| false); let predicate2 = EntryPredicateBuilder::from(|_: &Entry| true); let combined_predicate = (predicate1 & predicate2).build(); let result: Vec = input.into_iter().filter(combined_predicate).collect(); assert_eq!(result.len(), 0); } #[test] fn test_bitand_second_predicate_false() { let input = create_test_entries(); let predicate1 = EntryPredicateBuilder::from(|_: &Entry| true); let predicate2 = EntryPredicateBuilder::from(|_: &Entry| false); let combined_predicate = (predicate1 & predicate2).build(); let result: Vec = input.into_iter().filter(combined_predicate).collect(); assert_eq!(result.len(), 0); } #[test] fn test_bitand_both_predicates_false() { let input = create_test_entries(); let predicate1 = EntryPredicateBuilder::from(|_: &Entry| false); let predicate2 = EntryPredicateBuilder::from(|_: &Entry| false); let combined_predicate = (predicate1 & predicate2).build(); let result: Vec = input.into_iter().filter(combined_predicate).collect(); assert_eq!(result.len(), 0); } fn create_test_entries() -> Vec { vec![Entry { file: PathBuf::from("/home/user/project/source/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c"], directory: PathBuf::from("/home/user/project"), output: None, }] } } #[cfg(test)] mod not_tests { use super::*; use crate::vec_of_strings; use std::path::PathBuf; #[test] fn test_not_predicate_true() { let input = create_test_entries(); let predicate = EntryPredicateBuilder::from(|_: &Entry| true); let not_predicate = (!predicate).build(); let result: Vec = input.into_iter().filter(not_predicate).collect(); assert_eq!(result.len(), 0); } #[test] fn test_not_predicate_false() { let input = create_test_entries(); let predicate = EntryPredicateBuilder::from(|_: &Entry| false); let not_predicate = (!predicate).build(); let result: Vec = input.into_iter().filter(not_predicate).collect(); assert_eq!(result.len(), 1); } fn create_test_entries() -> Vec { vec![Entry { file: PathBuf::from("/home/user/project/source/source.c"), arguments: vec_of_strings!["cc", "-c", "source.c"], directory: PathBuf::from("/home/user/project"), output: None, }] } } } rizsotto-Bear-14c2e01/rust/bear/src/output/formatter.rs000066400000000000000000000222451476774233700232360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use crate::output::clang::Entry; use crate::{config, semantic}; use anyhow::anyhow; use path_absolutize::Absolutize; use std::borrow::Cow; use std::io; use std::path::{Path, PathBuf}; pub struct EntryFormatter { drop_output_field: bool, path_format: config::PathFormat, } impl From<&config::Format> for EntryFormatter { /// Create a formatter from the configuration. fn from(config: &config::Format) -> Self { let drop_output_field = config.drop_output_field; let path_format = config.paths_as.clone(); Self { drop_output_field, path_format, } } } impl EntryFormatter { /// Convert the compiler calls into entries. /// /// The conversion is done by converting the compiler passes into entries. /// Errors are logged and ignored. The entries format is controlled by the configuration. pub(crate) fn apply(&self, compiler_call: semantic::CompilerCall) -> Vec { let semantic::CompilerCall { compiler, working_dir, passes, } = compiler_call; passes .into_iter() .map(|pass| self.try_convert_from_pass(&working_dir, &compiler, pass)) // We are here to log the error. .map(|result| result.map_err(|error| log::info!("{}", error))) .filter_map(Result::ok) .collect() } /// Creates a single entry from a compiler pass if possible. /// /// The preprocess pass is ignored, and the compile pass is converted into an entry. /// /// The file and directory paths are converted into fully qualified paths when required. fn try_convert_from_pass( &self, working_dir: &Path, compiler: &Path, pass: semantic::CompilerPass, ) -> anyhow::Result { match pass { semantic::CompilerPass::Preprocess => { Err(anyhow!("preprocess pass should not show up in results")) } semantic::CompilerPass::Compile { source, output, flags, } => { let output_clone = output.clone(); let output_result = match output.filter(|_| !self.drop_output_field) { None => None, Some(candidate) => { let x = self.format_path(candidate.as_path(), working_dir)?; Some(PathBuf::from(x)) } }; Ok(Entry { file: PathBuf::from(self.format_path(source.as_path(), working_dir)?), directory: working_dir.to_path_buf(), output: output_result, arguments: Self::format_arguments(compiler, &source, &flags, output_clone)?, }) } } } /// Reconstruct the arguments for the compiler call. /// /// It is not the same as the command line arguments, because the compiler call is /// decomposed into a separate lists of arguments. To assemble from the parts will /// not necessarily result in the same command line arguments. One example for that /// is the multiple source files are treated as separate compiler calls. Another /// thing that can change is the order of the arguments. fn format_arguments( compiler: &Path, source: &Path, flags: &[String], output: Option, ) -> anyhow::Result, anyhow::Error> { let mut arguments: Vec = vec![]; // Assemble the arguments as it would be for a single source file. arguments.push(into_string(compiler)?); for flag in flags { arguments.push(flag.clone()); } if let Some(file) = output { arguments.push(String::from("-o")); arguments.push(into_string(file.as_path())?) } arguments.push(into_string(source)?); Ok(arguments) } fn format_path<'a>(&self, path: &'a Path, root: &Path) -> io::Result> { // Will compute the absolute path if needed. let absolute = || { if path.is_absolute() { path.absolutize() } else { path.absolutize_from(root) } }; match self.path_format { config::PathFormat::Original => Ok(Cow::from(path)), config::PathFormat::Absolute => absolute(), config::PathFormat::Canonical => absolute()?.canonicalize().map(Cow::from), } } } fn into_string(path: &Path) -> anyhow::Result { path.to_path_buf() .into_os_string() .into_string() .map_err(|_| anyhow!("Path can't be encoded to UTF")) } #[cfg(test)] mod test { use super::*; use crate::vec_of_strings; #[test] fn test_non_compilations() { let input = semantic::CompilerCall { compiler: PathBuf::from("/usr/bin/cc"), working_dir: PathBuf::from("/home/user"), passes: vec![semantic::CompilerPass::Preprocess], }; let format = config::Format { command_as_array: true, drop_output_field: false, paths_as: config::PathFormat::Original, }; let sut: EntryFormatter = (&format).into(); let result = sut.apply(input); let expected: Vec = vec![]; assert_eq!(expected, result); } #[test] fn test_single_source_compilation() { let input = semantic::CompilerCall { compiler: PathBuf::from("/usr/bin/clang"), working_dir: PathBuf::from("/home/user"), passes: vec![semantic::CompilerPass::Compile { source: PathBuf::from("source.c"), output: Some(PathBuf::from("source.o")), flags: vec_of_strings!["-Wall"], }], }; let format = config::Format { command_as_array: true, drop_output_field: false, paths_as: config::PathFormat::Original, }; let sut: EntryFormatter = (&format).into(); let result = sut.apply(input); let expected = vec![Entry { directory: PathBuf::from("/home/user"), file: PathBuf::from("source.c"), arguments: vec_of_strings!["/usr/bin/clang", "-Wall", "-o", "source.o", "source.c"], output: Some(PathBuf::from("source.o")), }]; assert_eq!(expected, result); } #[test] fn test_multiple_sources_compilation() { let input = compiler_call_with_multiple_passes(); let format = config::Format { command_as_array: true, drop_output_field: true, paths_as: config::PathFormat::Original, }; let sut: EntryFormatter = (&format).into(); let result = sut.apply(input); let expected = vec![ Entry { directory: PathBuf::from("/home/user"), file: PathBuf::from("/tmp/source1.c"), arguments: vec_of_strings!["clang", "-o", "./source1.o", "/tmp/source1.c"], output: None, }, Entry { directory: PathBuf::from("/home/user"), file: PathBuf::from("../source2.c"), arguments: vec_of_strings!["clang", "-Wall", "../source2.c"], output: None, }, ]; assert_eq!(expected, result); } #[test] fn test_multiple_sources_compilation_with_abs_paths() { let input = compiler_call_with_multiple_passes(); let format = config::Format { command_as_array: true, drop_output_field: true, paths_as: config::PathFormat::Absolute, }; let sut: EntryFormatter = (&format).into(); let result = sut.apply(input); let expected = vec![ Entry { directory: PathBuf::from("/home/user"), file: PathBuf::from("/tmp/source1.c"), arguments: vec_of_strings!["clang", "-o", "./source1.o", "/tmp/source1.c"], output: None, }, Entry { directory: PathBuf::from("/home/user"), file: PathBuf::from("/home/source2.c"), arguments: vec_of_strings!["clang", "-Wall", "../source2.c"], output: None, }, ]; assert_eq!(expected, result); } fn compiler_call_with_multiple_passes() -> semantic::CompilerCall { semantic::CompilerCall { compiler: PathBuf::from("clang"), working_dir: PathBuf::from("/home/user"), passes: vec![ semantic::CompilerPass::Preprocess, semantic::CompilerPass::Compile { source: PathBuf::from("/tmp/source1.c"), output: Some(PathBuf::from("./source1.o")), flags: vec_of_strings![], }, semantic::CompilerPass::Compile { source: PathBuf::from("../source2.c"), output: None, flags: vec_of_strings!["-Wall"], }, ], } } } rizsotto-Bear-14c2e01/rust/bear/src/output/mod.rs000066400000000000000000000006641476774233700220130ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use super::semantic; use anyhow::Result; pub mod clang; pub mod filter; pub mod formatter; /// The output writer trait is responsible for writing output file. pub(crate) trait OutputWriter { /// Running the writer means to consume the compiler calls /// and write the entries to the output file. fn run(&self, _: impl Iterator) -> Result<()>; } rizsotto-Bear-14c2e01/rust/bear/src/semantic/000077500000000000000000000000001476774233700211235ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/000077500000000000000000000000001476774233700236515ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/combinators.rs000066400000000000000000000075211476774233700265440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use super::super::{CompilerCall, Execution, Interpreter, Recognition}; /// Represents a set of interpreters, where any of them can recognize the semantic. /// The evaluation is done in the order of the interpreters. The first one which /// recognizes the semantic will be returned as result. pub(super) struct Any { interpreters: Vec>, } impl Any { pub(super) fn new(tools: Vec>) -> Self { Self { interpreters: tools, } } } impl Interpreter for Any { fn recognize(&self, x: &Execution) -> Recognition { for tool in &self.interpreters { match tool.recognize(x) { Recognition::Unknown => continue, result => return result, } } Recognition::Unknown } } #[cfg(test)] mod test { use std::collections::HashMap; use std::path::PathBuf; use super::super::super::CompilerCall; use super::*; #[test] fn test_any_when_no_match() { let sut = Any { interpreters: vec![ Box::new(MockTool::NotRecognize), Box::new(MockTool::NotRecognize), Box::new(MockTool::NotRecognize), ], }; let input = any_execution(); match sut.recognize(&input) { Recognition::Unknown => assert!(true), _ => assert!(false), } } #[test] fn test_any_when_success() { let sut = Any { interpreters: vec![ Box::new(MockTool::NotRecognize), Box::new(MockTool::Recognize), Box::new(MockTool::NotRecognize), ], }; let input = any_execution(); match sut.recognize(&input) { Recognition::Success(_) => assert!(true), _ => assert!(false), } } #[test] fn test_any_when_ignored() { let sut = Any { interpreters: vec![ Box::new(MockTool::NotRecognize), Box::new(MockTool::RecognizeIgnored), Box::new(MockTool::Recognize), ], }; let input = any_execution(); match sut.recognize(&input) { Recognition::Ignored => assert!(true), _ => assert!(false), } } #[test] fn test_any_when_match_fails() { let sut = Any { interpreters: vec![ Box::new(MockTool::NotRecognize), Box::new(MockTool::RecognizeFailed), Box::new(MockTool::Recognize), Box::new(MockTool::NotRecognize), ], }; let input = any_execution(); match sut.recognize(&input) { Recognition::Error(_) => assert!(true), _ => assert!(false), } } enum MockTool { Recognize, RecognizeIgnored, RecognizeFailed, NotRecognize, } impl Interpreter for MockTool { fn recognize(&self, _: &Execution) -> Recognition { match self { MockTool::Recognize => Recognition::Success(any_compiler_call()), MockTool::RecognizeIgnored => Recognition::Ignored, MockTool::RecognizeFailed => Recognition::Error(String::from("problem")), MockTool::NotRecognize => Recognition::Unknown, } } } fn any_execution() -> Execution { Execution { executable: PathBuf::new(), arguments: vec![], working_dir: PathBuf::new(), environment: HashMap::new(), } } fn any_compiler_call() -> CompilerCall { CompilerCall { compiler: PathBuf::new(), working_dir: PathBuf::new(), passes: vec![], } } } rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/gcc.rs000066400000000000000000000146251476774233700247630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use nom::branch::alt; use nom::multi::many1; use nom::sequence::preceded; use super::super::{CompilerCall, Execution, Interpreter, Recognition}; use internal::Argument; pub(super) struct Gcc {} impl Gcc { pub(super) fn new() -> Self { Gcc {} } } impl Interpreter for Gcc { fn recognize(&self, execution: &Execution) -> Recognition { let mut parser = preceded( internal::compiler, many1(alt((internal::flag, internal::source))), ); match parser(execution.arguments.as_slice()) { Ok(result) => { // TODO: append flags from environment let flags = result.1; let passes = Argument::passes(flags.as_slice()); Recognition::Success(CompilerCall { compiler: execution.executable.clone(), working_dir: execution.working_dir.clone(), passes, }) } Err(error) => { log::debug!("Gcc failed to parse it: {error}."); Recognition::Unknown } } } } mod internal { use nom::{error::ErrorKind, IResult}; use regex::Regex; use std::path::PathBuf; use super::super::super::CompilerPass; use super::super::matchers::source::looks_like_a_source_file; #[derive(Debug, PartialEq)] enum Language { C, Cpp, ObjectiveC, ObjectiveCpp, Ada, Fortran, Go, D, Assembler, Other, } #[derive(Debug, PartialEq)] enum Pass { Preprocessor, Compiler, Linker, } #[derive(Debug, PartialEq)] enum Meaning { Compiler, ControlKindOfOutput { stop_before: Option }, ControlLanguage(Language), ControlPass(Pass), Diagnostic, Debug, Optimize, Instrumentation, DirectorySearch(Option), Developer, Input(Pass), Output, } /// Compiler flags are varies the number of arguments, but means one thing. pub(super) struct Argument<'a> { arguments: &'a [String], meaning: Meaning, } impl Argument<'_> { pub(super) fn passes(flags: &[Argument]) -> Vec { let mut pass: Pass = Pass::Linker; let mut inputs: Vec = vec![]; let mut output: Option = None; let mut args: Vec = vec![]; for flag in flags { match flag.meaning { Meaning::ControlKindOfOutput { stop_before: Some(Pass::Compiler), } => { pass = Pass::Preprocessor; args.extend(flag.arguments.iter().map(String::to_owned)); } Meaning::ControlKindOfOutput { stop_before: Some(Pass::Linker), } => { pass = Pass::Compiler; args.extend(flag.arguments.iter().map(String::to_owned)); } Meaning::ControlKindOfOutput { .. } | Meaning::ControlLanguage(_) | Meaning::ControlPass(Pass::Preprocessor) | Meaning::ControlPass(Pass::Compiler) | Meaning::Diagnostic | Meaning::Debug | Meaning::Optimize | Meaning::Instrumentation | Meaning::DirectorySearch(None) => { args.extend(flag.arguments.iter().map(String::to_owned)); } Meaning::Input(_) => { assert_eq!(flag.arguments.len(), 1); inputs.push(flag.arguments[0].clone()) } Meaning::Output => { assert_eq!(flag.arguments.len(), 1); output = Some(flag.arguments[0].clone()) } _ => {} } } match pass { Pass::Preprocessor if inputs.is_empty() => { vec![] } Pass::Preprocessor => { vec![CompilerPass::Preprocess] } Pass::Compiler | Pass::Linker => inputs .into_iter() .map(|source| CompilerPass::Compile { source: PathBuf::from(source), output: output.as_ref().map(PathBuf::from), flags: args.clone(), }) .collect(), } } } pub(super) fn compiler(i: &[String]) -> IResult<&[String], Argument> { let candidate = &i[0]; if COMPILER_REGEX.is_match(candidate) { const MEANING: Meaning = Meaning::Compiler; Ok(( &i[1..], Argument { arguments: &i[..0], meaning: MEANING, }, )) } else { // Declare it as a non-recoverable error, so argument processing will stop after this. Err(nom::Err::Failure(nom::error::Error::new(i, ErrorKind::Tag))) } } pub(super) fn source(i: &[String]) -> IResult<&[String], Argument> { let candidate = &i[0]; if looks_like_a_source_file(candidate.as_str()) { const MEANING: Meaning = Meaning::Input(Pass::Preprocessor); Ok(( &i[1..], Argument { arguments: &i[..0], meaning: MEANING, }, )) } else { Err(nom::Err::Error(nom::error::Error::new(i, ErrorKind::Tag))) } } pub(super) fn flag(_i: &[String]) -> IResult<&[String], Argument> { todo!() } static COMPILER_REGEX: std::sync::LazyLock = std::sync::LazyLock::new(|| { // - cc // - c++ // - cxx // - CC // - mcc, gcc, m++, g++, gfortran, fortran // - with prefixes like: arm-none-eabi- // - with postfixes like: -7.0 or 6.4.0 Regex::new( r"(^(cc|c\+\+|cxx|CC|(([^-]*-)*([mg](cc|\+\+)|[g]?fortran)(-?\d+(\.\d+){0,2})?))$)", ) .unwrap() }); } rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/generic.rs000066400000000000000000000100701476774233700256310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use std::collections::HashSet; use std::path::PathBuf; use std::vec; use super::super::{CompilerCall, CompilerPass, Execution, Interpreter, Recognition}; use super::matchers::source::looks_like_a_source_file; /// A tool to recognize a compiler by executable name. pub(super) struct Generic { executables: HashSet, } impl Generic { pub(super) fn from(compilers: &[PathBuf]) -> Self { let executables = compilers.iter().cloned().collect(); Self { executables } } } impl Interpreter for Generic { /// This tool is a naive implementation only considering: /// - the executable name, /// - one of the arguments is a source file, /// - the rest of the arguments are flags. fn recognize(&self, x: &Execution) -> Recognition { if self.executables.contains(&x.executable) { let mut flags = vec![]; let mut sources = vec![]; // find sources and filter out requested flags. for argument in x.arguments.iter().skip(1) { if looks_like_a_source_file(argument.as_str()) { sources.push(PathBuf::from(argument)); } else { flags.push(argument.clone()); } } if sources.is_empty() { Recognition::Error(String::from("source file is not found")) } else { Recognition::Success(CompilerCall { compiler: x.executable.clone(), working_dir: x.working_dir.clone(), passes: sources .iter() .map(|source| CompilerPass::Compile { source: source.clone(), output: None, flags: flags.clone(), }) .collect(), }) } } else { Recognition::Unknown } } } #[cfg(test)] mod test { use std::collections::HashMap; use crate::{vec_of_pathbuf, vec_of_strings}; use super::*; #[test] fn test_matching() { let input = Execution { executable: PathBuf::from("/usr/bin/something"), arguments: vec_of_strings![ "something", "-Dthis=that", "-I.", "source.c", "-o", "source.c.o" ], working_dir: PathBuf::from("/home/user"), environment: HashMap::new(), }; let expected = CompilerCall { compiler: PathBuf::from("/usr/bin/something"), working_dir: PathBuf::from("/home/user"), passes: vec![CompilerPass::Compile { flags: vec_of_strings!["-Dthis=that", "-I.", "-o", "source.c.o"], source: PathBuf::from("source.c"), output: None, }], }; assert_eq!(Recognition::Success(expected), SUT.recognize(&input)); } #[test] fn test_matching_without_sources() { let input = Execution { executable: PathBuf::from("/usr/bin/something"), arguments: vec_of_strings!["something", "--help"], working_dir: PathBuf::from("/home/user"), environment: HashMap::new(), }; assert_eq!( Recognition::Error(String::from("source file is not found")), SUT.recognize(&input) ); } #[test] fn test_not_matching() { let input = Execution { executable: PathBuf::from("/usr/bin/cc"), arguments: vec_of_strings!["cc", "-Dthis=that", "-I.", "source.c", "-o", "source.c.o"], working_dir: PathBuf::from("/home/user"), environment: HashMap::new(), }; assert_eq!(Recognition::Unknown, SUT.recognize(&input)); } static SUT: std::sync::LazyLock = std::sync::LazyLock::new(|| Generic { executables: vec_of_pathbuf!["/usr/bin/something"].into_iter().collect(), }); } rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/ignore.rs000066400000000000000000000105001476774233700254760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use std::collections::HashSet; use std::path::PathBuf; use super::super::{CompilerCall, Execution, Interpreter, Recognition}; /// A tool to ignore a command execution by executable name. pub(super) struct IgnoreByPath { executables: HashSet, } impl IgnoreByPath { pub(super) fn new() -> Self { let executables = COREUTILS_FILES.iter().map(PathBuf::from).collect(); Self { executables } } pub(super) fn from(compilers: &[PathBuf]) -> Self { let executables = compilers.iter().cloned().collect(); Self { executables } } } impl Default for IgnoreByPath { fn default() -> Self { Self::new() } } /// A tool to ignore a command execution by arguments. impl Interpreter for IgnoreByPath { fn recognize(&self, execution: &Execution) -> Recognition { if self.executables.contains(&execution.executable) { Recognition::Ignored } else { Recognition::Unknown } } } static COREUTILS_FILES: [&str; 106] = [ "/usr/bin/[", "/usr/bin/arch", "/usr/bin/b2sum", "/usr/bin/base32", "/usr/bin/base64", "/usr/bin/basename", "/usr/bin/basenc", "/usr/bin/cat", "/usr/bin/chcon", "/usr/bin/chgrp", "/usr/bin/chmod", "/usr/bin/chown", "/usr/bin/cksum", "/usr/bin/comm", "/usr/bin/cp", "/usr/bin/csplit", "/usr/bin/cut", "/usr/bin/date", "/usr/bin/dd", "/usr/bin/df", "/usr/bin/dir", "/usr/bin/dircolors", "/usr/bin/dirname", "/usr/bin/du", "/usr/bin/echo", "/usr/bin/env", "/usr/bin/expand", "/usr/bin/expr", "/usr/bin/factor", "/usr/bin/false", "/usr/bin/fmt", "/usr/bin/fold", "/usr/bin/groups", "/usr/bin/head", "/usr/bin/hostid", "/usr/bin/id", "/usr/bin/install", "/usr/bin/join", "/usr/bin/link", "/usr/bin/ln", "/usr/bin/logname", "/usr/bin/ls", "/usr/bin/md5sum", "/usr/bin/mkdir", "/usr/bin/mkfifo", "/usr/bin/mknod", "/usr/bin/mktemp", "/usr/bin/mv", "/usr/bin/nice", "/usr/bin/nl", "/usr/bin/nohup", "/usr/bin/nproc", "/usr/bin/numfmt", "/usr/bin/od", "/usr/bin/paste", "/usr/bin/pathchk", "/usr/bin/pinky", "/usr/bin/pr", "/usr/bin/printenv", "/usr/bin/printf", "/usr/bin/ptx", "/usr/bin/pwd", "/usr/bin/readlink", "/usr/bin/realpath", "/usr/bin/rm", "/usr/bin/rmdir", "/usr/bin/runcon", "/usr/bin/seq", "/usr/bin/sha1sum", "/usr/bin/sha224sum", "/usr/bin/sha256sum", "/usr/bin/sha384sum", "/usr/bin/sha512sum", "/usr/bin/shred", "/usr/bin/shuf", "/usr/bin/sleep", "/usr/bin/sort", "/usr/bin/split", "/usr/bin/stat", "/usr/bin/stdbuf", "/usr/bin/stty", "/usr/bin/sum", "/usr/bin/sync", "/usr/bin/tac", "/usr/bin/tail", "/usr/bin/tee", "/usr/bin/test", "/usr/bin/timeout", "/usr/bin/touch", "/usr/bin/tr", "/usr/bin/true", "/usr/bin/truncate", "/usr/bin/tsort", "/usr/bin/tty", "/usr/bin/uname", "/usr/bin/unexpand", "/usr/bin/uniq", "/usr/bin/unlink", "/usr/bin/users", "/usr/bin/vdir", "/usr/bin/wc", "/usr/bin/who", "/usr/bin/whoami", "/usr/bin/yes", "/usr/bin/make", "/usr/bin/gmake", ]; #[cfg(test)] mod test { use std::collections::HashMap; use std::path::PathBuf; use crate::vec_of_strings; use super::*; #[test] fn test_executions_are_ignored_by_executable_name() { let input = Execution { executable: PathBuf::from("/usr/bin/ls"), arguments: vec_of_strings!["ls", "/home/user/build"], working_dir: PathBuf::from("/home/user"), environment: HashMap::new(), }; let sut = IgnoreByPath::new(); assert_eq!(Recognition::Ignored, sut.recognize(&input)) } #[test] fn test_not_known_executables_are_not_recognized() { let input = Execution { executable: PathBuf::from("/usr/bin/bear"), arguments: vec_of_strings!["bear", "--", "make"], working_dir: PathBuf::from("/home/user"), environment: HashMap::new(), }; let sut = IgnoreByPath::new(); assert_eq!(Recognition::Unknown, sut.recognize(&input)) } } rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/000077500000000000000000000000001476774233700254575ustar00rootroot00000000000000rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/mod.rs000066400000000000000000000001051476774233700266000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later pub(super) mod source; rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/matchers/source.rs000066400000000000000000000042301476774233700273240ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use std::collections::HashSet; #[cfg(target_family = "unix")] pub fn looks_like_a_source_file(argument: &str) -> bool { // not a command line flag if argument.starts_with('-') { return false; } if let Some((_, extension)) = argument.rsplit_once('.') { return EXTENSIONS.contains(extension); } false } #[cfg(target_family = "windows")] pub fn looks_like_a_source_file(argument: &str) -> bool { // not a command line flag if argument.starts_with('/') { return false; } if let Some((_, extension)) = argument.rsplit_once('.') { return EXTENSIONS.contains(extension); } false } #[rustfmt::skip] static EXTENSIONS: std::sync::LazyLock> = std::sync::LazyLock::new(|| { HashSet::from([ // header files "h", "hh", "H", "hp", "hxx", "hpp", "HPP", "h++", "tcc", // C "c", "C", // C++ "cc", "CC", "c++", "C++", "cxx", "cpp", "cp", // CUDA "cu", // ObjectiveC "m", "mi", "mm", "M", "mii", // Preprocessed "i", "ii", // Assembly "s", "S", "sx", "asm", // Fortran "f", "for", "ftn", "F", "FOR", "fpp", "FPP", "FTN", "f90", "f95", "f03", "f08", "F90", "F95", "F03", "F08", // go "go", // brig "brig", // D "d", "di", "dd", // Ada "ads", "abd", ]) }); #[cfg(test)] mod test { use super::*; #[test] fn test_filenames() { assert!(looks_like_a_source_file("source.c")); assert!(looks_like_a_source_file("source.cpp")); assert!(looks_like_a_source_file("source.cxx")); assert!(looks_like_a_source_file("source.cc")); assert!(looks_like_a_source_file("source.h")); assert!(looks_like_a_source_file("source.hpp")); assert!(!looks_like_a_source_file("gcc")); assert!(!looks_like_a_source_file("clang")); assert!(!looks_like_a_source_file("-o")); assert!(!looks_like_a_source_file("-Wall")); assert!(!looks_like_a_source_file("/o")); } } rizsotto-Bear-14c2e01/rust/bear/src/semantic/interpreters/mod.rs000066400000000000000000000106561476774233700250060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later use super::interpreters::combinators::Any; use super::interpreters::generic::Generic; use super::interpreters::ignore::IgnoreByPath; use super::Interpreter; use crate::config; use std::path::PathBuf; mod combinators; mod gcc; mod generic; mod ignore; mod matchers; /// Creates an interpreter to recognize the compiler calls. /// /// Using the configuration we can define which compilers to include and exclude. /// Also read the environment variables to detect the compiler to include (and /// make sure those are not excluded either). // TODO: Use the CC or CXX environment variables to detect the compiler to include. // Use the CC or CXX environment variables and make sure those are not excluded. // Make sure the environment variables are passed to the method. // TODO: Take environment variables as input. pub fn create_interpreter<'a>(config: &config::Main) -> impl Interpreter + 'a { let compilers_to_include = match &config.intercept { config::Intercept::Wrapper { executables, .. } => executables.clone(), _ => vec![], }; let compilers_to_exclude = match &config.output { config::Output::Clang { compilers, .. } => compilers .iter() .filter(|compiler| compiler.ignore == config::IgnoreOrConsider::Always) .map(|compiler| compiler.path.clone()) .collect(), _ => vec![], }; let mut interpreters: Vec> = vec![ // ignore executables which are not compilers, Box::new(IgnoreByPath::default()), // recognize default compiler Box::new(Generic::from(&[PathBuf::from("/usr/bin/cc")])), ]; if !compilers_to_include.is_empty() { let tool = Generic::from(&compilers_to_include); interpreters.push(Box::new(tool)); } if !compilers_to_exclude.is_empty() { let tool = IgnoreByPath::from(&compilers_to_exclude); interpreters.insert(0, Box::new(tool)); } Any::new(interpreters) } #[cfg(test)] mod test { use std::collections::HashMap; use std::path::PathBuf; use super::super::{CompilerCall, Execution, Recognition}; use super::*; use crate::config; use crate::config::{DuplicateFilter, Format, SourceFilter}; use crate::{vec_of_pathbuf, vec_of_strings}; fn any_execution() -> Execution { Execution { executable: PathBuf::from("/usr/bin/cc"), arguments: vec_of_strings!["cc", "-c", "-Wall", "main.c"], environment: HashMap::new(), working_dir: PathBuf::from("/home/user"), } } #[test] fn test_create_interpreter_with_default_config() { let config = config::Main::default(); let interpreter = create_interpreter(&config); let input = any_execution(); match interpreter.recognize(&input) { Recognition::Success(CompilerCall { .. }) => assert!(true), _ => assert!(false), } } #[test] fn test_create_interpreter_with_compilers_to_include() { let config = config::Main { intercept: config::Intercept::Wrapper { executables: vec_of_pathbuf!["/usr/bin/cc"], path: PathBuf::from("/usr/libexec/bear"), directory: PathBuf::from("/tmp"), }, ..Default::default() }; let interpreter = create_interpreter(&config); let input = any_execution(); match interpreter.recognize(&input) { Recognition::Success(CompilerCall { .. }) => assert!(true), _ => assert!(false), } } #[test] fn test_create_interpreter_with_compilers_to_exclude() { let config = config::Main { output: config::Output::Clang { compilers: vec![config::Compiler { path: PathBuf::from("/usr/bin/cc"), ignore: config::IgnoreOrConsider::Always, arguments: config::Arguments::default(), }], sources: SourceFilter::default(), duplicates: DuplicateFilter::default(), format: Format::default(), }, ..Default::default() }; let interpreter = create_interpreter(&config); let input = any_execution(); match interpreter.recognize(&input) { Recognition::Ignored => assert!(true), _ => assert!(false), } } } rizsotto-Bear-14c2e01/rust/bear/src/semantic/mod.rs000066400000000000000000000062671476774233700222630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! This module is defining the semantic of executed commands. //! //! The semantic identifies the intent of the execution. It not only //! recognizes the compiler calls, but also identifies the compiler //! passes that are executed. //! //! A compilation of a source file can be divided into multiple passes. //! We are interested in the compiler passes, because those are the //! ones that are relevant to build a JSON compilation database. pub mod interpreters; pub mod transformation; use super::intercept::Execution; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use std::path::PathBuf; /// Represents an executed command semantic. #[derive(Debug, PartialEq, Serialize)] pub struct CompilerCall { pub compiler: PathBuf, pub working_dir: PathBuf, pub passes: Vec, } /// Represents a compiler call pass. #[derive(Debug, PartialEq, Serialize)] pub enum CompilerPass { Preprocess, Compile { source: PathBuf, output: Option, flags: Vec, }, } /// Responsible to recognize the semantic of an executed command. /// /// The implementation can be responsible for a single compiler, /// a set of compilers, or a set of commands that are not compilers. /// /// The benefit to recognize a non-compiler command, is to not /// spend more time to try to recognize with other interpreters. /// Or classify the recognition as ignored to not be further processed /// later on. pub trait Interpreter: Send { fn recognize(&self, _: &Execution) -> Recognition; } /// Represents a semantic recognition result. /// /// The unknown recognition is used when the interpreter is not /// able to recognize the command. This can signal the search process /// to continue with the next interpreter. #[derive(Debug, PartialEq)] pub enum Recognition { /// The command was recognized and the semantic was identified. Success(T), /// The command was recognized, but the semantic was ignored. Ignored, /// The command was recognized, but the semantic was broken. Error(String), /// The command was not recognized. Unknown, } impl IntoIterator for Recognition { type Item = T; type IntoIter = std::option::IntoIter; fn into_iter(self) -> Self::IntoIter { match self { Recognition::Success(value) => Some(value).into_iter(), _ => None.into_iter(), } } } /// Responsible to transform the semantic of an executed command. /// /// It conditionally removes compiler calls based on compiler names or flags. /// It can also alter the compiler flags of the compiler calls. The actions /// are defined in the configuration this module is given. pub trait Transform: Send { fn apply(&self, _: CompilerCall) -> Option; } /// Serialize compiler calls into a JSON array. pub fn serialize( writer: impl std::io::Write, entries: impl Iterator + Sized, ) -> anyhow::Result<()> { let mut ser = serde_json::Serializer::pretty(writer); let mut seq = ser.serialize_seq(None)?; for entry in entries { seq.serialize_element(&entry)?; } seq.end()?; Ok(()) } rizsotto-Bear-14c2e01/rust/bear/src/semantic/transformation.rs000066400000000000000000000230731476774233700245440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-3.0-or-later //! Responsible for transforming the compiler calls. //! //! It conditionally removes compiler calls based on compiler names or flags. //! It can also alter the compiler flags of the compiler calls. The actions //! are defined in the configuration this module is given. use crate::{config, semantic}; use std::collections::HashMap; use std::path::PathBuf; /// Transformation contains rearranged information from the configuration. /// /// The configuration is a list of instruction on how to transform the compiler call. /// The transformation group the instructions by the compiler path, so it can be /// applied to the compiler call when it matches the path. #[derive(Debug, PartialEq)] pub struct Transformation { compilers: HashMap>, } impl From<&config::Output> for Transformation { fn from(config: &config::Output) -> Self { match config { config::Output::Clang { compilers, .. } => compilers.as_slice().into(), config::Output::Semantic { .. } => Transformation::new(), } } } impl From<&[config::Compiler]> for Transformation { fn from(config: &[config::Compiler]) -> Self { let mut compilers = HashMap::new(); for compiler in config { compilers .entry(compiler.path.clone()) .or_insert_with(Vec::new) .push(compiler.clone()); } Transformation { compilers } } } impl semantic::Transform for Transformation { fn apply(&self, input: semantic::CompilerCall) -> Option { if let Some(configs) = self.compilers.get(&input.compiler) { Self::apply_when_not_empty(configs.as_slice(), input) } else { Some(input) } } } impl Transformation { fn new() -> Self { Transformation { compilers: HashMap::new(), } } /// Apply the transformation to the compiler call. /// /// Multiple configurations can be applied to the same compiler call. /// And depending on the instruction from the configuration, the compiler call /// can be ignored, modified, or left unchanged. The conditional ignore will /// check if the compiler call matches the flags defined in the configuration. fn apply_when_not_empty( configs: &[config::Compiler], input: semantic::CompilerCall, ) -> Option { let mut current_input = Some(input); for config in configs { current_input = match config { config::Compiler { ignore: config::IgnoreOrConsider::Always, .. } => None, config::Compiler { ignore: config::IgnoreOrConsider::Conditional, arguments, .. } => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)), config::Compiler { ignore: config::IgnoreOrConsider::Never, arguments, .. } => current_input.map(|input| semantic::CompilerCall { compiler: input.compiler.clone(), working_dir: input.working_dir.clone(), passes: Transformation::apply_argument_changes( arguments, input.passes.as_slice(), ), }), }; if current_input.is_none() { break; } } current_input } /// Check if the compiler call matches the condition defined in the configuration. /// /// Any compiler pass that matches the flags defined in the configuration will cause /// the whole compiler call to be ignored. fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool { let match_flags = arguments.match_.as_slice(); passes.iter().any(|pass| match pass { semantic::CompilerPass::Compile { flags, .. } => { flags.iter().any(|flag| match_flags.contains(flag)) } _ => false, }) } /// Apply the changes defined in the configuration to the compiler call. /// /// The changes can be to remove or add flags to the compiler call. /// Only the flags will be changed, but applies to all compiler passes. fn apply_argument_changes( arguments: &config::Arguments, passes: &[semantic::CompilerPass], ) -> Vec { let arguments_to_remove = arguments.remove.as_slice(); let arguments_to_add = arguments.add.as_slice(); let mut new_passes = Vec::with_capacity(passes.len()); for pass in passes { match pass { semantic::CompilerPass::Compile { source, output, flags, } => { let mut new_flags = flags.clone(); new_flags.retain(|flag| !arguments_to_remove.contains(flag)); new_flags.extend(arguments_to_add.iter().cloned()); new_passes.push(semantic::CompilerPass::Compile { source: source.clone(), output: output.clone(), flags: new_flags, }); } semantic::CompilerPass::Preprocess => { new_passes.push(semantic::CompilerPass::Preprocess) } } } new_passes } } #[cfg(test)] mod tests { use super::*; use crate::config::{Arguments, Compiler, IgnoreOrConsider}; use crate::semantic::{CompilerCall, CompilerPass, Transform}; use std::path::PathBuf; #[test] fn test_apply_no_filter() { let input = CompilerCall { compiler: std::path::PathBuf::from("gcc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-O2".into()], }], working_dir: std::path::PathBuf::from("/project"), }; let sut = Transformation::from(&config::Output::Semantic {}); let result = sut.apply(input); let expected = CompilerCall { compiler: std::path::PathBuf::from("gcc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-O2".into()], }], working_dir: std::path::PathBuf::from("/project"), }; assert_eq!(result, Some(expected)); } #[test] fn test_apply_filter_match() { let input = CompilerCall { compiler: std::path::PathBuf::from("cc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-O2".into()], }], working_dir: std::path::PathBuf::from("/project"), }; let sut: Transformation = vec![Compiler { path: std::path::PathBuf::from("cc"), ignore: IgnoreOrConsider::Always, arguments: Arguments::default(), }] .as_slice() .into(); let result = sut.apply(input); assert!(result.is_none()); } #[test] fn test_apply_conditional_match() { let input = CompilerCall { compiler: std::path::PathBuf::from("gcc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-O2".into(), "-Wall".into()], }], working_dir: std::path::PathBuf::from("/project"), }; let sut: Transformation = vec![Compiler { path: std::path::PathBuf::from("gcc"), ignore: IgnoreOrConsider::Conditional, arguments: Arguments { match_: vec!["-O2".into()], ..Arguments::default() }, }] .as_slice() .into(); let result = sut.apply(input); assert!(result.is_none()); } #[test] fn test_apply_ignore_never_modify_arguments() { let input = CompilerCall { compiler: std::path::PathBuf::from("gcc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-O2".into()], }], working_dir: std::path::PathBuf::from("/project"), }; let sut: Transformation = vec![Compiler { path: std::path::PathBuf::from("gcc"), ignore: IgnoreOrConsider::Never, arguments: Arguments { add: vec!["-Wall".into()], remove: vec!["-O2".into()], ..Arguments::default() }, }] .as_slice() .into(); let result = sut.apply(input); let expected = CompilerCall { compiler: std::path::PathBuf::from("gcc"), passes: vec![CompilerPass::Compile { source: PathBuf::from("main.c"), output: PathBuf::from("main.o").into(), flags: vec!["-Wall".into()], }], working_dir: std::path::PathBuf::from("/project"), }; assert_eq!(result, Some(expected)); } } rizsotto-Bear-14c2e01/source/000077500000000000000000000000001476774233700161235ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/.clang-format000066400000000000000000000071201476774233700204760ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: WebKit AccessModifierOffset: -4 AlignAfterOpenBracket: DontAlign AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: false AlignTrailingComments: false AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: "Empty" AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: "false" AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: All BreakBeforeBraces: WebKit BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: All ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: "c++17" StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseTab: Never ... rizsotto-Bear-14c2e01/source/CMakeLists.txt000066400000000000000000000060621476774233700206670ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12 FATAL_ERROR) cmake_policy(VERSION 3.12) project(BearSource VERSION ${CMAKE_PROJECT_VERSION} LANGUAGES C CXX ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSPDLOG_NO_EXCEPTIONS") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGOOGLE_PROTOBUF_NO_RTTI") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") if (ENABLE_UNIT_TESTS) find_program(MEMORYCHECK_COMMAND NAMES valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes --leak-check=full") include(CTest) enable_testing() find_package(PkgConfig REQUIRED) pkg_check_modules(GTest REQUIRED IMPORTED_TARGET gtest gtest_main gmock) endif () find_package(Threads REQUIRED) find_package(nlohmann_json REQUIRED) find_package(fmt REQUIRED) find_package(spdlog REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(gRPC REQUIRED IMPORTED_TARGET protobuf grpc++) if (fmt_VERSION_MAJOR GREATER_EQUAL 9) set(FMT_NEEDS_OSTREAM_FORMATTER 1) set(HAVE_FMT_STD_H 1) # FIXME: this should be done with `check_include_file` endif () if (UNIX AND NOT APPLE) set(SUPPORT_PRELOAD 1) endif() if (ENABLE_MULTILIB) set(SUPPORT_MULTILIB 1) endif() include(CheckIncludeFile) check_include_file(spawn.h HAVE_SPAWN_H) check_include_file(unistd.h HAVE_UNISTD_H) check_include_file(dlfcn.h HAVE_DLFCN_H) check_include_file(errno.h HAVE_ERRNO_H) check_include_file(sys/utsname.h HAVE_SYS_UTSNAME_H) check_include_file(sys/wait.h HAVE_SYS_WAIT_H) check_include_file(sys/time.h HAVE_SYS_TIME_H) check_include_file(sys/stat.h HAVE_SYS_STAT_H) check_include_file(gnu/lib-names.h HAVE_GNU_LIB_NAMES_H) #check_include_file(fmt/std.h HAVE_FMT_STD_H) include(CheckSymbolExists) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) set(CMAKE_REQUIRED_FLAGS -D_GNU_SOURCE) check_symbol_exists(_NSGetEnviron "crt_externs.h" HAVE_NSGETENVIRON) check_symbol_exists(dlopen "dlfcn.h" HAVE_DLOPEN) check_symbol_exists(dlsym "dlfcn.h" HAVE_DLSYM) check_symbol_exists(dlerror "dlfcn.h" HAVE_DLERROR) check_symbol_exists(dlclose "dlfcn.h" HAVE_DLCLOSE) check_symbol_exists(RTLD_NEXT "dlfcn.h" HAVE_RTLD_NEXT) check_symbol_exists(EACCES "errno.h" HAVE_EACCES) check_symbol_exists(ENOENT "errno.h" HAVE_ENOENT) check_symbol_exists(strerror_r "string.h" HAVE_STRERROR_R) check_symbol_exists(environ "unistd.h" HAVE_ENVIRON) check_symbol_exists(uname "sys/utsname.h" HAVE_UNAME) check_symbol_exists(confstr "unistd.h" HAVE_CONFSTR) check_symbol_exists(_CS_PATH "unistd.h" HAVE_CS_PATH) check_symbol_exists(_CS_GNU_LIBC_VERSION "unistd.h" HAVE_CS_GNU_LIBC_VERSION) check_symbol_exists(_CS_GNU_LIBPTHREAD_VERSION "unistd.h" HAVE_CS_GNU_LIBPTHREAD_VERSION) include(GNUInstallDirs) # The directory names are used in the config file configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(libresult) add_subdirectory(libflags) add_subdirectory(libshell) add_subdirectory(libsys) add_subdirectory(libmain) add_subdirectory(intercept) add_subdirectory(citnames) add_subdirectory(bear) rizsotto-Bear-14c2e01/source/bear/000077500000000000000000000000001476774233700170345ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/bear/CMakeLists.txt000066400000000000000000000022361476774233700215770ustar00rootroot00000000000000# Create a static library, which is used for unit tests and the final shared library. add_library(bear_a OBJECT) target_include_directories(bear_a PUBLIC source/ ../citnames/include/ ../intercept/include/) target_sources(bear_a PRIVATE source/Application.cc INTERFACE $ ) target_link_libraries(bear_a PUBLIC main_a sys_a flags_a fmt::fmt citnames_a intercept_a spdlog::spdlog) # Create an executable from the sub projects. add_executable(bear main.cc ) target_link_libraries(bear bear_a) include(GNUInstallDirs) install(TARGETS bear RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # Markdown file is the source to the man file. Please modify that and generate # the man file from it with pandoc. # # $ pandoc -s -t man bear.1.md -o bear.1 # # This is not automated, because pandoc has big dependencies on different OS # distributions and packaging would require to install those. Which might be # too much effort to generate a single text file. install(FILES man/bear.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rizsotto-Bear-14c2e01/source/bear/main.cc000066400000000000000000000016761476774233700203010ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "source/Application.h" #include "libmain/main.h" int main(int argc, char *argv[], char *envp[]) { return ps::main(argc, argv, envp); } rizsotto-Bear-14c2e01/source/bear/man/000077500000000000000000000000001476774233700176075ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/bear/man/bear.1000066400000000000000000000076051476774233700206120ustar00rootroot00000000000000.\" Automatically generated by Pandoc 2.14.0.3 .\" .TH "BEAR" "1" "Jan 02, 2023" "Bear User Manuals" "" .hy .SH NAME .PP Bear - a tool to generate compilation database for Clang tooling. .SH SYNOPSIS .PP bear [\f[I]options\f[R]] -- [\f[I]build command\f[R]] .SH DESCRIPTION .PP The JSON compilation database is used in Clang project to provide information how a single compilation unit was processed. When that is available then it is easy to re-run the compilation with different programs. .PP Bear executes the original build command and intercept the command executions issued by the build tool. From the log of command executions it tries to identify the compiler calls and creates the final compilation database. .SH OPTIONS .TP --version Print version number. .TP --help Print help message. .TP --verbose Enable verbose logging. .TP --output \f[I]file\f[R] Specify output file. (Default file name provided.) The output is a JSON compilation database. .TP --append Use previously generated output file and append the new entries to it. This way you can run Bear continuously during work, and it keeps the compilation database up to date. File deletion and addition are both considered. But build process change (compiler flags change) might cause duplicate entries. .TP --config \f[I]file\f[R] Specify a configuration file. The configuration file captures how the output should be formatted and which entries it shall contain. .TP --force-preload Force to use the dynamic linker method of \f[C]intercept\f[R] command. .TP --force-wrapper Force to use the compiler wrapper method of \f[C]intercept\f[R] command. .SH COMMANDS .TP \f[B]\f[CB]bear-intercept(1)\f[B]\f[R] Intercepts events that happened during the execution of the build command. .TP \f[B]\f[CB]bear-citnames(1)\f[B]\f[R] Deduce the semantics of the commands captured by \f[C]bear-intercept(1)\f[R]. .SH OUTPUT .PP The JSON compilation database definition changed over time. The current version of Bear generates entries where: .TP \f[B]\f[CB]directory\f[B]\f[R] has absolute path. .TP \f[B]\f[CB]file\f[B]\f[R] has absolute path. .TP \f[B]\f[CB]output\f[B]\f[R] has absolute path. .TP \f[B]\f[CB]arguments\f[B]\f[R] used instead of \f[C]command\f[R] to avoid shell escaping problems. (Configuration can force to emit the \f[C]command\f[R] field.) The compiler as the first argument has absolute path. Some non compilation related flags are filtered out from the final output. .SH CONFIG FILE .PP Read \f[C]bear-citnames(1)\f[R] man page for the content of this file. \f[C]bear\f[R] is not reading the content of this file, but passing the file name to \f[C]bear citnames\f[R] command. .SH EXIT STATUS .PP The exit status of the program is the exit status of the build command. Except when the program itself crashes, then it sets to non-zero. .SH TROUBLESHOOTING .PP The potential problems you can face with are: the build with and without Bear behaves differently or the output is empty. .PP The most common cause for empty outputs is that the build command did not execute any commands. The reason for that could be, because incremental builds not running the compilers if everything is up-to-date. Remember, Bear does not understand the build file (eg.: makefile), but intercepts the executed commands. .PP The other common cause for empty output is that the build has a \[lq]configure\[rq] step, which captures the compiler to build the project. In case of Bear is using the \f[I]wrapper\f[R] mode (read \f[C]bear-intercept(1)\f[R] man page), it needs to run the configure step with Bear too (and discard that output), before run the build with Bear. .PP There could be many reasons for any of these failures. It\[cq]s better to consult with the project wiki page for known problems, before open a bug report. .SH COPYRIGHT .PP Copyright (C) 2012-2024 by L\['a]szl\['o] Nagy .SH AUTHORS L\['a]szl\['o] Nagy. rizsotto-Bear-14c2e01/source/bear/man/bear.1.md000066400000000000000000000071211476774233700212020ustar00rootroot00000000000000% BEAR(1) Bear User Manuals % László Nagy % Jan 02, 2023 # NAME Bear - a tool to generate compilation database for Clang tooling. # SYNOPSIS bear [*options*] \-\- [*build command*] # DESCRIPTION The JSON compilation database is used in Clang project to provide information how a single compilation unit was processed. When that is available then it is easy to re-run the compilation with different programs. Bear executes the original build command and intercept the command executions issued by the build tool. From the log of command executions it tries to identify the compiler calls and creates the final compilation database. # OPTIONS \--version : Print version number. \--help : Print help message. \--verbose : Enable verbose logging. \--output *file* : Specify output file. (Default file name provided.) The output is a JSON compilation database. \--append : Use previously generated output file and append the new entries to it. This way you can run Bear continuously during work, and it keeps the compilation database up to date. File deletion and addition are both considered. But build process change (compiler flags change) might cause duplicate entries. \--config *file* : Specify a configuration file. The configuration file captures how the output should be formatted and which entries it shall contain. \--force-preload : Force to use the dynamic linker method of `intercept` command. \--force-wrapper : Force to use the compiler wrapper method of `intercept` command. # COMMANDS `bear-intercept(1)` : Intercepts events that happened during the execution of the build command. `bear-citnames(1)` : Deduce the semantics of the commands captured by `bear-intercept(1)`. # OUTPUT The JSON compilation database definition changed over time. The current version of Bear generates entries where: `directory` : has absolute path. `file` : has absolute path. `output` : has absolute path. `arguments` : used instead of `command` to avoid shell escaping problems. (Configuration can force to emit the `command` field.) The compiler as the first argument has absolute path. Some non compilation related flags are filtered out from the final output. # CONFIG FILE Read `bear-citnames(1)` man page for the content of this file. `bear` is not reading the content of this file, but passing the file name to `bear citnames` command. # EXIT STATUS The exit status of the program is the exit status of the build command. Except when the program itself crashes, then it sets to non-zero. # TROUBLESHOOTING The potential problems you can face with are: the build with and without Bear behaves differently or the output is empty. The most common cause for empty outputs is that the build command did not execute any commands. The reason for that could be, because incremental builds not running the compilers if everything is up-to-date. Remember, Bear does not understand the build file (eg.: makefile), but intercepts the executed commands. The other common cause for empty output is that the build has a "configure" step, which captures the compiler to build the project. In case of Bear is using the _wrapper_ mode (read `bear-intercept(1)` man page), it needs to run the configure step with Bear too (and discard that output), before run the build with Bear. There could be many reasons for any of these failures. It's better to consult with the project wiki page for known problems, before open a bug report. # COPYRIGHT Copyright (C) 2012-2024 by László Nagy rizsotto-Bear-14c2e01/source/bear/source/000077500000000000000000000000001476774233700203345ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/bear/source/Application.cc000066400000000000000000000312161476774233700231110ustar00rootroot00000000000000/* (C) 2012-2022 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Application.h" #include "citnames/citnames-forward.h" #include "intercept/intercept-forward.h" namespace { constexpr std::optional ADVANCED_GROUP = {"advanced options"}; constexpr std::optional DEVELOPER_GROUP = {"developer options"}; rust::Result prepare_intercept(const flags::Arguments &arguments, const sys::env::Vars &environment, const fs::path &output) { auto program = arguments.as_string(cmd::bear::FLAG_BEAR); auto command = arguments.as_string_list(cmd::intercept::FLAG_COMMAND); auto library = arguments.as_string(cmd::intercept::FLAG_LIBRARY); auto wrapper = arguments.as_string(cmd::intercept::FLAG_WRAPPER); auto wrapper_dir = arguments.as_string(cmd::intercept::FLAG_WRAPPER_DIR); auto verbose = arguments.as_bool(flags::VERBOSE).unwrap_or(false); auto force_wrapper = arguments.as_bool(cmd::intercept::FLAG_FORCE_WRAPPER).unwrap_or(false); auto force_preload = arguments.as_bool(cmd::intercept::FLAG_FORCE_PRELOAD).unwrap_or(false); return rust::merge(program, command, rust::merge(library, wrapper, wrapper_dir)) .map( [&environment, &output, &verbose, &force_wrapper, &force_preload](auto tuple) { const auto&[program, command, pack] = tuple; const auto&[library, wrapper, wrapper_dir] = pack; auto builder = sys::Process::Builder(program) .set_environment(environment) .add_argument(program) .add_argument("intercept") .add_argument(cmd::intercept::FLAG_LIBRARY).add_argument(library) .add_argument(cmd::intercept::FLAG_WRAPPER).add_argument(wrapper) .add_argument(cmd::intercept::FLAG_WRAPPER_DIR).add_argument(wrapper_dir) .add_argument(cmd::intercept::FLAG_OUTPUT).add_argument(output); if (force_wrapper) { builder.add_argument(cmd::intercept::FLAG_FORCE_WRAPPER); } if (force_preload) { builder.add_argument(cmd::intercept::FLAG_FORCE_PRELOAD); } if (verbose) { builder.add_argument(flags::VERBOSE); } builder.add_argument(cmd::intercept::FLAG_COMMAND) .add_arguments(command.begin(), command.end()); return builder; }); } rust::Result prepare_citnames(const flags::Arguments &arguments, const sys::env::Vars &environment, const fs::path &input) { auto program = arguments.as_string(cmd::bear::FLAG_BEAR); auto output = arguments.as_string(cmd::citnames::FLAG_OUTPUT); auto config = arguments.as_string(cmd::citnames::FLAG_CONFIG); auto append = arguments.as_bool(cmd::citnames::FLAG_APPEND).unwrap_or(false); auto verbose = arguments.as_bool(flags::VERBOSE).unwrap_or(false); return rust::merge(program, output) .map([&environment, &input, &config, &append, &verbose](auto tuple) { const auto&[program, output] = tuple; auto builder = sys::Process::Builder(program) .set_environment(environment) .add_argument(program) .add_argument("citnames") .add_argument(cmd::citnames::FLAG_INPUT).add_argument(input) .add_argument(cmd::citnames::FLAG_OUTPUT).add_argument(output) // can run the file checks, because we are on the host. .add_argument(cmd::citnames::FLAG_RUN_CHECKS); if (append) { builder.add_argument(cmd::citnames::FLAG_APPEND); } if (config.is_ok()) { builder.add_argument(cmd::citnames::FLAG_CONFIG).add_argument(config.unwrap()); } if (verbose) { builder.add_argument(flags::VERBOSE); } return builder; }); } rust::Result execute(sys::Process::Builder builder, const std::string_view &name) { return builder.spawn() .and_then([](auto child) { sys::SignalForwarder guard(child); return child.wait(); }) .map([](auto status) { return status.code().value_or(EXIT_FAILURE); }) .map_err([&name](auto error) { spdlog::warn("Running {} failed: {}", name, error.what()); return error; }) .on_success([&name](auto status) { spdlog::debug("Running {} finished. [Exited with {}]", name, status); }); } } namespace bear { Command::Command(const sys::Process::Builder& intercept, const sys::Process::Builder& citnames, fs::path output) noexcept : ps::Command() , intercept_(intercept) , citnames_(citnames) , output_(std::move(output)) { } [[nodiscard]] rust::Result Command::execute() const { auto result = ::execute(intercept_, "intercept"); std::error_code error_code; if (fs::exists(output_, error_code)) { ::execute(citnames_, "citnames"); fs::remove(output_, error_code); } return result; } Application::Application() : ps::ApplicationFromArgs(ps::ApplicationLogConfig("bear", "br")) { } rust::Result Application::parse(int argc, const char **argv) const { const flags::Parser intercept_parser("intercept", cmd::VERSION, { {cmd::intercept::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::intercept::DEFAULT_OUTPUT}, std::nullopt}}, {cmd::intercept::FLAG_FORCE_PRELOAD, {0, false, "force to use library preload", std::nullopt, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_FORCE_WRAPPER, {0, false, "force to use compiler wrappers", std::nullopt, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_LIBRARY, {1, false, "path to the preload library", {cmd::library::DEFAULT_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_WRAPPER, {1, false, "path to the wrapper executable", {cmd::wrapper::DEFAULT_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_WRAPPER_DIR, {1, false, "path to the wrapper directory", {cmd::wrapper::DEFAULT_DIR_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_COMMAND, {-1, true, "command to execute", std::nullopt, std::nullopt}} }); const flags::Parser citnames_parser("citnames", cmd::VERSION, { {cmd::citnames::FLAG_INPUT, {1, false, "path of the input file", {cmd::intercept::DEFAULT_OUTPUT}, std::nullopt}}, {cmd::citnames::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::citnames::DEFAULT_OUTPUT}, std::nullopt}}, {cmd::citnames::FLAG_CONFIG, {1, false, "path of the config file", std::nullopt, std::nullopt}}, {cmd::citnames::FLAG_APPEND, {0, false, "append to output, instead of overwrite it", std::nullopt, std::nullopt}}, {cmd::citnames::FLAG_RUN_CHECKS, {0, false, "can run checks on the current host", std::nullopt, std::nullopt}} }); const flags::Parser parser("bear", cmd::VERSION, {intercept_parser, citnames_parser}, { {cmd::citnames::FLAG_OUTPUT, {1, false, "path of the result file", {cmd::citnames::DEFAULT_OUTPUT}, std::nullopt}}, {cmd::citnames::FLAG_APPEND, {0, false, "append result to an existing output file", std::nullopt, ADVANCED_GROUP}}, {cmd::citnames::FLAG_CONFIG, {1, false, "path of the config file", std::nullopt, ADVANCED_GROUP}}, {cmd::intercept::FLAG_FORCE_PRELOAD, {0, false, "force to use library preload", std::nullopt, ADVANCED_GROUP}}, {cmd::intercept::FLAG_FORCE_WRAPPER, {0, false, "force to use compiler wrappers", std::nullopt, ADVANCED_GROUP}}, {cmd::bear::FLAG_BEAR, {1, false, "path to the bear executable", {cmd::bear::DEFAULT_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_LIBRARY, {1, false, "path to the preload library", {cmd::library::DEFAULT_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_WRAPPER, {1, false, "path to the wrapper executable", {cmd::wrapper::DEFAULT_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_WRAPPER_DIR, {1, false, "path to the wrapper directory", {cmd::wrapper::DEFAULT_DIR_PATH}, DEVELOPER_GROUP}}, {cmd::intercept::FLAG_COMMAND, {-1, true, "command to execute", std::nullopt, std::nullopt}} }); return parser.parse_or_exit(argc, const_cast(argv)); } rust::Result Application::command(const flags::Arguments& args, const char** envp) const { // Check if subcommand was called. if (args.as_string(flags::COMMAND).is_ok()) { if (auto citnames = cs::Citnames(log_config_); citnames.matches(args)) { return citnames.subcommand(args, envp); } if (auto intercept = ic::Intercept(log_config_); intercept.matches(args)) { return intercept.subcommand(args, envp); } return rust::Err(std::runtime_error("Invalid subcommand")); } // If there were no subcommand, then just execute the two one after the other. // TODO: execute the two process parallel like the intercept output is the citnames input. // `bear intercept -o - | bear citnames -i - -o compile_commands.json` auto commands = args.as_string(cmd::citnames::FLAG_OUTPUT) .map([](const auto& output) { return fs::path(output).replace_extension(".events.json"); }) .unwrap_or(fs::path(cmd::citnames::DEFAULT_OUTPUT)); auto environment = sys::env::from(const_cast(envp)); auto intercept = prepare_intercept(args, environment, commands); auto citnames = prepare_citnames(args, environment, commands); return rust::merge(intercept, citnames) .map([&commands](const auto& tuple) { const auto& [intercept, citnames] = tuple; return std::make_unique(intercept, citnames, commands); }); } } rizsotto-Bear-14c2e01/source/bear/source/Application.h000066400000000000000000000035771476774233700227640ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libflags/Flags.h" #include "libresult/Result.h" #include "libsys/Environment.h" #include "libsys/Process.h" #include "libsys/Signal.h" #include "libmain/ApplicationFromArgs.h" #include #include #include #include #include #include namespace bear { struct Command : ps::Command { public: Command(const sys::Process::Builder& intercept, const sys::Process::Builder& citnames, fs::path output) noexcept; [[nodiscard]] rust::Result execute() const override; NON_DEFAULT_CONSTRUCTABLE(Command) NON_COPYABLE_NOR_MOVABLE(Command) private: sys::Process::Builder intercept_; sys::Process::Builder citnames_; fs::path output_; }; struct Application : ps::ApplicationFromArgs { Application(); rust::Result parse(int argc, const char **argv) const override; rust::Result command(const flags::Arguments &args, const char **envp) const override; }; } rizsotto-Bear-14c2e01/source/citnames/000077500000000000000000000000001476774233700177265ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/CMakeLists.txt000066400000000000000000000052151476774233700224710ustar00rootroot00000000000000# Create a static library, which is used for unit tests and the final shared library. add_library(citnames_json_a OBJECT) target_sources(citnames_json_a PRIVATE source/Configuration.cc source/Output.cc INTERFACE $ ) target_link_libraries(citnames_json_a PUBLIC result_a shell_a sys_a fmt::fmt spdlog::spdlog nlohmann_json::nlohmann_json) target_compile_options(citnames_json_a PRIVATE -fexceptions) # Create a static library, which is used for unit tests and the final shared library. add_library(citnames_a OBJECT) target_include_directories(citnames_a PUBLIC source/ include/) target_sources(citnames_a PRIVATE source/Citnames.cc source/semantic/Build.cc source/semantic/Common.cc source/semantic/Parsers.cc source/semantic/Semantic.cc source/semantic/ToolAny.cc source/semantic/ToolCrayFtnfe.cc source/semantic/ToolClang.cc source/semantic/ToolCuda.cc source/semantic/ToolGcc.cc source/semantic/ToolIntelFortran.cc source/semantic/ToolWrapper.cc source/semantic/ToolExtendingWrapper.cc INTERFACE $ ) target_link_libraries(citnames_a PUBLIC main_a citnames_json_a events_db_a domain_a result_a flags_a sys_a exec_a fmt::fmt spdlog::spdlog) include(GNUInstallDirs) # Markdown file is the source to the man file. Please modify that and generate # the man file from it with pandoc. # # $ pandoc -s -t man bear-citnames.1.md -o bear-citnames.1 # # This is not automated, because pandoc has big dependencies on different OS # distributions and packaging would require to install those. Which might be # too much effort to generate a single text file. install(FILES man/bear-citnames.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) if (ENABLE_UNIT_TESTS) add_executable(citnames_unit_test test/OutputTest.cc test/ParserTest.cc test/ToolCrayFtnfeTest.cc test/ToolClangTest.cc test/ToolGccTest.cc test/ToolIntelFortranTest.cc test/ToolWrapperTest.cc ) target_link_libraries(citnames_unit_test citnames_a) target_link_libraries(citnames_unit_test citnames_json_a) target_link_libraries(citnames_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::citnames_unit_test COMMAND $) endif () rizsotto-Bear-14c2e01/source/citnames/include/000077500000000000000000000000001476774233700213515ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/include/citnames/000077500000000000000000000000001476774233700231545ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/include/citnames/citnames-forward.h000066400000000000000000000022001476774233700265640ustar00rootroot00000000000000/* Copyright (C) 2023 by Samu698 This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libmain/SubcommandFromArgs.h" #include "libresult/Result.h" namespace cs { struct Citnames : ps::SubcommandFromArgs { Citnames(const ps::ApplicationLogConfig&) noexcept; rust::Result command(const flags::Arguments &args, const char **envp) const override; NON_DEFAULT_CONSTRUCTABLE(Citnames) }; } rizsotto-Bear-14c2e01/source/citnames/man/000077500000000000000000000000001476774233700205015ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/man/bear-citnames.1000066400000000000000000000135121476774233700232770ustar00rootroot00000000000000.\" Automatically generated by Pandoc 2.14.0.3 .\" .TH "BEAR-CITNAMES" "1" "Jan 02, 2023" "Bear User Manuals" "" .hy .SH NAME .PP bear-citnames - deduce command semantic .SH SYNOPSIS .PP bear citnames [\f[I]options\f[R]] --input --output .SH DESCRIPTION .PP The name citnames comes from to reverse the word \[lq]semantic\[rq]. .PP Because when you type a command, you know your intent. The command execution is just a thing to achieve your goal. This program takes the command which was executed, and try to find out what the intent was to run that command. It deduces the semantic of the command. .PP This is useful to generate a compilation database. Citnames get a list of commands, and it creates a JSON compilation database. (This is currently the only output of the tool.) .SH OPTIONS .TP --version Print version number. .TP --help Print help message. .TP --verbose Enable verbose logging. .TP --input \f[I]file\f[R] Specify input file. (Default file name provided.) The input is a command execution list, with some extra information. The syntax is detailed in a separate section. .TP --output \f[I]file\f[R] Specify output file. (Default file name provided.) The output is currently a JSON compilation database. .TP --append Use previously generated output file and append the new entries to it. This way you can run continuously during work, and it keeps the compilation database up to date. File deletion and addition are both considered. But build process change (compiler flags change) might cause duplicate entries. .TP --run-checks Allow the program to verify file location checks on the current machine it runs. (Default value provided. Run help to query it.) This is important if the execution list is not from the current host. .TP --config \f[I]file\f[R] Specify a configuration file. The configuration file captures how the output should be formatted and which entries it shall contain. .SH EXIT STATUS .PP Citnames exit status is non-zero in case of IO problems, otherwise it\[cq]s zero. The exit status is independent of how many command it recognized or was it recognized at all. .SH OBSERVABILITY .PP Any insight about the command recognition logic can be observed with \f[C]--verbose\f[R] flag on the standard error. Otherwise, the command is silent. .SH INPUT FILE .PP It\[cq]s a JSON file, with the command execution history. (Plus some metadata, that is useful for debugging the application which was produced it.) This file can be produced by the \f[C]bear intercept\f[R] command, which records the process executions of a build. .PP Read more about the syntax of the file in the \f[C]bear-intercept(1)\f[R] man page. .SH OUTPUT FILE .PP Currently, the only output format is the JSON compilation database. Read more about the syntax of that in the \f[C]bear(1)\f[R] man page. .SH CONFIG FILE .PP The config file influences the command recognition (by the section \[lq]compilation\[rq]) and the output format (by the section \[lq]output\[rq]). .PP The config file is optional. The program will use default values, which can be dumped with the \f[C]--verbose\f[R] flags. .PP Some parts of the file has overlap with the command line arguments. If both present the command line argument overrides the config file values. .IP .nf \f[C] { \[dq]compilation\[dq]: { \[dq]compilers_to_recognize\[dq]: [ { \[dq]executable\[dq]: \[dq]/usr/bin/mpicc\[dq], \[dq]flags_to_add\[dq]: [\[dq]-I/opt/MPI/include\[dq]], \[dq]flags_to_remove\[dq]: [\[dq]-Wall\[dq]] } ], \[dq]compilers_to_exclude\[dq]: [] }, \[dq]output\[dq]: { \[dq]content\[dq]: { \[dq]include_only_existing_source\[dq]: true, \[dq]paths_to_include\[dq]: [], \[dq]paths_to_exclude\[dq]: [], \[dq]duplicate_filter_fields\[dq]: \[dq]file_output\[dq] }, \[dq]format\[dq]: { \[dq]command_as_array\[dq]: true, \[dq]drop_output_field\[dq]: false } } } \f[R] .fi .TP \f[B]\f[CB]compilation.compilers_to_recognize\f[B]\f[R] where compiler can be specified, which are not yet recognized by default. The \f[C]executable\f[R] is an absolute path to the compiler. The \f[C]flags_to_add\f[R] is an optional attribute, which contains flags which will append to the final output. (It\[cq]s a good candidate to use this for adding OpenMPI compiler wrapper flags from the \f[C]mpicc --showme:compile\f[R] output.) The \f[C]flags_to_remove\f[R] is an optional attribute, where the given flags will be removed for the final argument list. (The flags checked for equality only, no regex match. Flags with arguments are not good candidates to put here, because the removal logic is too simple for that.) .TP \f[B]\f[CB]compilation.compilers_to_exclude\f[B]\f[R] this is an optional list of executables (with absolute path) which needs to be removed from the output. .TP \f[B]\f[CB]output.content\f[B]\f[R] The \f[C]paths_to_include\f[R] and \f[C]paths_to_exclude\f[R] are for filter out entries from these directories. (Directory names can be absolute paths or relative to the current working directory if the \f[C]--run-checks\f[R] flag passed.) The \f[C]include_only_existing_source\f[R] allows or disables file check for the output. The \f[C]--run-checks\f[R] flag overrides this config value. The \f[C]duplicate_filter_fields\f[R] select the method how duplicate entries are detected in the output. The possible values for this field are: \f[C]all\f[R], \f[C]file\f[R] and \f[C]file_output\f[R]. .TP \f[B]\f[CB]output.format\f[B]\f[R] The \f[C]command_as_array\f[R] controls which command field is emitted in the output. True produces \f[C]arguments\f[R], false produces \f[C]command\f[R] field. The \f[C]drop_output_field\f[R] will disable the \f[C]output\f[R] field from the output. .SH SEE ALSO .PP \f[C]bear(1)\f[R], \f[C]bear-intercept(1)\f[R] .SH COPYRIGHT .PP Copyright (C) 2012-2024 by L\['a]szl\['o] Nagy .SH AUTHORS L\['a]szl\['o] Nagy. rizsotto-Bear-14c2e01/source/citnames/man/bear-citnames.1.md000066400000000000000000000124721476774233700237020ustar00rootroot00000000000000% BEAR-CITNAMES(1) Bear User Manuals % László Nagy % Jan 02, 2023 # NAME bear-citnames - deduce command semantic # SYNOPSIS bear citnames [*options*] \--input \ \--output \ # DESCRIPTION The name citnames comes from to reverse the word "semantic". Because when you type a command, you know your intent. The command execution is just a thing to achieve your goal. This program takes the command which was executed, and try to find out what the intent was to run that command. It deduces the semantic of the command. This is useful to generate a compilation database. Citnames get a list of commands, and it creates a JSON compilation database. (This is currently the only output of the tool.) # OPTIONS \--version : Print version number. \--help : Print help message. \--verbose : Enable verbose logging. \--input *file* : Specify input file. (Default file name provided.) The input is a command execution list, with some extra information. The syntax is detailed in a separate section. \--output *file* : Specify output file. (Default file name provided.) The output is currently a JSON compilation database. \--append : Use previously generated output file and append the new entries to it. This way you can run continuously during work, and it keeps the compilation database up to date. File deletion and addition are both considered. But build process change (compiler flags change) might cause duplicate entries. \--run-checks : Allow the program to verify file location checks on the current machine it runs. (Default value provided. Run help to query it.) This is important if the execution list is not from the current host. \--config *file* : Specify a configuration file. The configuration file captures how the output should be formatted and which entries it shall contain. # EXIT STATUS Citnames exit status is non-zero in case of IO problems, otherwise it's zero. The exit status is independent of how many command it recognized or was it recognized at all. # OBSERVABILITY Any insight about the command recognition logic can be observed with `--verbose` flag on the standard error. Otherwise, the command is silent. # INPUT FILE It's a JSON file, with the command execution history. (Plus some metadata, that is useful for debugging the application which was produced it.) This file can be produced by the `bear intercept` command, which records the process executions of a build. Read more about the syntax of the file in the `bear-intercept(1)` man page. # OUTPUT FILE Currently, the only output format is the JSON compilation database. Read more about the syntax of that in the `bear(1)` man page. # CONFIG FILE The config file influences the command recognition (by the section "compilation") and the output format (by the section "output"). The config file is optional. The program will use default values, which can be dumped with the `--verbose` flags. Some parts of the file has overlap with the command line arguments. If both present the command line argument overrides the config file values. ```json { "compilation": { "compilers_to_recognize": [ { "executable": "/usr/bin/mpicc", "flags_to_add": ["-I/opt/MPI/include"], "flags_to_remove": ["-Wall"] } ], "compilers_to_exclude": [] }, "output": { "content": { "include_only_existing_source": true, "paths_to_include": [], "paths_to_exclude": [], "duplicate_filter_fields": "file_output" }, "format": { "command_as_array": true, "drop_output_field": false } } } ``` `compilation.compilers_to_recognize` : where compiler can be specified, which are not yet recognized by default. The `executable` is an absolute path to the compiler. The `flags_to_add` is an optional attribute, which contains flags which will append to the final output. (It's a good candidate to use this for adding OpenMPI compiler wrapper flags from the `mpicc --showme:compile` output.) The `flags_to_remove` is an optional attribute, where the given flags will be removed for the final argument list. (The flags checked for equality only, no regex match. Flags with arguments are not good candidates to put here, because the removal logic is too simple for that.) `compilation.compilers_to_exclude` : this is an optional list of executables (with absolute path) which needs to be removed from the output. `output.content` : The `paths_to_include` and `paths_to_exclude` are for filter out entries from these directories. (Directory names can be absolute paths or relative to the current working directory if the `--run-checks` flag passed.) The `include_only_existing_source` allows or disables file check for the output. The `--run-checks` flag overrides this config value. The `duplicate_filter_fields` select the method how duplicate entries are detected in the output. The possible values for this field are: `all`, `file` and `file_output`. `output.format` : The `command_as_array` controls which command field is emitted in the output. True produces `arguments`, false produces `command` field. The `drop_output_field` will disable the `output` field from the output. # SEE ALSO `bear(1)`, `bear-intercept(1)` # COPYRIGHT Copyright (C) 2012-2024 by László Nagy rizsotto-Bear-14c2e01/source/citnames/source/000077500000000000000000000000001476774233700212265ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/source/Citnames.cc000066400000000000000000000253041476774233700233040ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "Citnames.h" #include "Configuration.h" #include "Output.h" #include "semantic/Build.h" #include "semantic/Tool.h" #include "collect/db/EventsDatabaseReader.h" #include "libsys/Path.h" #include #ifdef HAVE_FMT_STD_H #include #endif #include #include namespace fs = std::filesystem; namespace db = ic::collect::db; #ifdef FMT_NEEDS_OSTREAM_FORMATTER template <> struct fmt::formatter : ostream_formatter {}; #endif namespace { std::list to_abspath(const std::list &paths, const fs::path &root) { std::list results; for (const auto &path : paths) { auto result = path.is_absolute() ? path : root / path; results.emplace_back(result); } return results; } cs::Content update_content(cs::Content content, bool run_checks) { if (run_checks) { auto cwd = sys::path::get_cwd(); if (cwd.is_ok()) { const fs::path& root = cwd.unwrap(); return cs::Content { run_checks, content.duplicate_filter_fields, to_abspath(content.paths_to_include, root), to_abspath(content.paths_to_exclude, root) }; } else { spdlog::warn("Update configuration failed: {}", cwd.unwrap_err().what()); } } return content; } std::list update_compilers_to_recognize( std::list wrappers, std::list compilers) { for (auto && compiler : compilers) { const bool already_in_wrappers = std::any_of(wrappers.begin(), wrappers.end(), [&compiler](auto wrapper) { return wrapper.executable == compiler; }); if (!already_in_wrappers) { wrappers.emplace_back(cs::CompilerWrapper { compiler, {}, {} }); } } return wrappers; } bool is_exists(const fs::path &path) { std::error_code error_code; return fs::exists(path, error_code); } bool rename_file(const fs::path &from, const fs::path &to) { std::error_code error_code; fs::rename(from, to, error_code); return error_code.value() == 0; } rust::Result into_arguments(const flags::Arguments &args) { auto input = args.as_string(cmd::citnames::FLAG_INPUT); auto output = args.as_string(cmd::citnames::FLAG_OUTPUT); auto append = args.as_bool(cmd::citnames::FLAG_APPEND) .unwrap_or(false); return rust::merge(input, output) .map([&append](auto tuple) { const auto&[input, output] = tuple; return cs::Arguments{ fs::path(input), fs::path(output), append, }; }) .and_then([](auto arguments) -> rust::Result { // validate if (!is_exists(arguments.input)) { return rust::Err(std::runtime_error( fmt::format("Missing input file: {}", arguments.input))); } return rust::Ok(cs::Arguments{ arguments.input, arguments.output, (arguments.append && is_exists(arguments.output)), }); }); } std::list compilers(const sys::env::Vars &environment) { std::list result; if (auto it = environment.find("CC"); it != environment.end()) { result.emplace_back(it->second); } if (auto it = environment.find("CXX"); it != environment.end()) { result.emplace_back(it->second); } if (auto it = environment.find("FC"); it != environment.end()) { result.emplace_back(it->second); } return result; } rust::Result into_configuration(const flags::Arguments &args, const sys::env::Vars &environment) { auto config_arg = args.as_string(cmd::citnames::FLAG_CONFIG); auto config = config_arg.is_ok() ? config_arg .and_then([](auto candidate) { return cs::ConfigurationSerializer().from_json(fs::path(candidate)); }) : rust::Ok(cs::Configuration()); return config .map([&args](auto config) { // command line arguments overrides the default values or the configuration content. const auto run_checks = args .as_bool(cmd::citnames::FLAG_RUN_CHECKS) .unwrap_or(config.output.content.include_only_existing_source); // update the content filter parameters according to the run_check outcome. config.output.content = update_content(config.output.content, run_checks); return config; }) .map([&environment](auto config) { // recognize compilers from known environment variables. const auto env_compilers = compilers(environment); config.compilation.compilers_to_recognize = update_compilers_to_recognize(config.compilation.compilers_to_recognize, env_compilers); return config; }) .on_success([](const auto &config) { spdlog::debug("Configuration: {}", config); }); } size_t transform(cs::semantic::Build &build, const db::EventsDatabaseReader::Ptr& events, std::list &output) { for (const auto &event : *events) { const auto entries = build.recognize(event) .map>([](const auto &semantic) -> std::list { const auto candidate = dynamic_cast(semantic.get()); return (candidate != nullptr) ? candidate->into_entries() : std::list(); }) .unwrap_or({}); std::copy(entries.begin(), entries.end(), std::back_inserter(output)); } return output.size(); } } namespace cs { rust::Result Command::execute() const { cs::CompilationDatabase output(configuration_.output.format, configuration_.output.content); std::list entries; // get current compilations from the input. return db::EventsDatabaseReader::from(arguments_.input) .map([this, &entries](const auto &commands) { cs::semantic::Build build(configuration_.compilation); return transform(build, commands, entries); }) .and_then([this, &output, &entries](auto new_entries_count) { spdlog::debug("compilation entries created. [size: {}]", new_entries_count); // read back the current content and extend with the new elements. return (arguments_.append) ? output.from_json(arguments_.output, entries) .template map([&new_entries_count](auto old_entries_count) { spdlog::debug("compilation entries have read. [size: {}]", old_entries_count); return new_entries_count + old_entries_count; }) : rust::Result(rust::Ok(new_entries_count)); }) .and_then([this, &output, &entries](const size_t & size) { // write the entries into the output file. spdlog::debug("compilation entries to output. [size: {}]", size); const fs::path temporary_file(arguments_.output.string() + ".tmp"); auto result = output.to_json(temporary_file, entries); return rename_file(temporary_file, arguments_.output) ? result : rust::Err(std::runtime_error(fmt::format("Failed to rename file: {}", arguments_.output))); }) .map([](auto size) { // just map to success exit code if it was successful. spdlog::debug("compilation entries written. [size: {}]", size); return EXIT_SUCCESS; }); } Command::Command(Arguments arguments, cs::Configuration configuration) noexcept : ps::Command() , arguments_(std::move(arguments)) , configuration_(std::move(configuration)) { } Citnames::Citnames(const ps::ApplicationLogConfig& log_config) noexcept : ps::SubcommandFromArgs("citnames", log_config) { } rust::Result Citnames::command(const flags::Arguments &args, const char **envp) const { auto environment = sys::env::from(const_cast(envp)); auto arguments = into_arguments(args); auto configuration = into_configuration(args, environment); return rust::merge(arguments, configuration) .map([](auto tuples) { const auto&[arguments, configuration] = tuples; // read the configuration return std::make_unique(arguments, configuration); }); } } rizsotto-Bear-14c2e01/source/citnames/source/Citnames.h000066400000000000000000000030151476774233700231410ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Output.h" #include "semantic/Tool.h" #include "libmain/SubcommandFromArgs.h" #include "libresult/Result.h" #include "libsys/Environment.h" #include #include #include "citnames/citnames-forward.h" namespace fs = std::filesystem; namespace cs { struct Arguments { fs::path input; fs::path output; bool append; }; struct Command : ps::Command { Command(Arguments arguments, cs::Configuration configuration) noexcept; [[nodiscard]] rust::Result execute() const override; NON_DEFAULT_CONSTRUCTABLE(Command) NON_COPYABLE_NOR_MOVABLE(Command) private: Arguments arguments_; cs::Configuration configuration_; }; } rizsotto-Bear-14c2e01/source/citnames/source/Configuration.cc000066400000000000000000000200341476774233700243430ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Configuration.h" #include #include #include #include namespace cs { void from_json(const nlohmann::json &j, Format &rhs) { j.at("command_as_array").get_to(rhs.command_as_array); j.at("drop_output_field").get_to(rhs.drop_output_field); } void to_json(nlohmann::json &j, const Format &rhs) { j = nlohmann::json{ {"command_as_array", rhs.command_as_array}, {"drop_output_field", rhs.drop_output_field}, }; } void from_json(const nlohmann::json &j, Content &rhs) { j.at("include_only_existing_source").get_to(rhs.include_only_existing_source); if (j.contains("paths_to_include")) { j.at("paths_to_include").get_to(rhs.paths_to_include); } if (j.contains("paths_to_exclude")) { j.at("paths_to_exclude").get_to(rhs.paths_to_exclude); } if (j.contains("duplicate_filter_fields")) { j.at("duplicate_filter_fields").get_to(rhs.duplicate_filter_fields); } } void to_json(nlohmann::json &j, const Content &rhs) { j = nlohmann::json{ {"include_only_existing_source", rhs.include_only_existing_source}, {"duplicate_filter_fields", rhs.duplicate_filter_fields}, }; if (!rhs.paths_to_include.empty()) { j["paths_to_include"] = rhs.paths_to_include; } if (!rhs.paths_to_exclude.empty()) { j["paths_to_exclude"] = rhs.paths_to_exclude; } } void from_json(const nlohmann::json &j, Output &rhs) { j.at("format").get_to(rhs.format); j.at("content").get_to(rhs.content); } void to_json(nlohmann::json &j, const Output &rhs) { j = nlohmann::json{ {"format", rhs.format}, {"content", rhs.content}, }; } void from_json(const nlohmann::json &j, CompilerWrapper &rhs) { j.at("executable").get_to(rhs.executable); if (j.contains("flags_to_add")) { j.at("flags_to_add").get_to(rhs.flags_to_add); } if (j.contains("flags_to_remove")) { j.at("flags_to_remove").get_to(rhs.flags_to_remove); } } void to_json(nlohmann::json &j, const CompilerWrapper &rhs) { j = nlohmann::json{ {"executable", rhs.executable}, }; if (!rhs.flags_to_add.empty()) { j["flags_to_add"] = rhs.flags_to_add; } if (!rhs.flags_to_remove.empty()) { j["flags_to_remove"] = rhs.flags_to_remove; } } void from_json(const nlohmann::json &j, Compilation &rhs) { if (j.contains("compilers_to_recognize")) { j.at("compilers_to_recognize").get_to(rhs.compilers_to_recognize); } if (j.contains("compilers_to_exclude")) { j.at("compilers_to_exclude").get_to(rhs.compilers_to_exclude); } } void to_json(nlohmann::json &j, const Compilation &rhs) { if (!rhs.compilers_to_recognize.empty()) { j["compilers_to_recognize"] = rhs.compilers_to_recognize; } if (!rhs.compilers_to_exclude.empty()) { j["compilers_to_exclude"] = rhs.compilers_to_exclude; } } void from_json(const nlohmann::json &j, Configuration &rhs) { if (j.contains("output")) { j.at("output").get_to(rhs.output); } if (j.contains("compilation")) { j.at("compilation").get_to(rhs.compilation); } } void to_json(nlohmann::json &j, const Configuration &rhs) { j = nlohmann::json{ {"output", rhs.output}, {"compilation", rhs.compilation}, }; } std::ostream &operator<<(std::ostream &os, const Format &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } std::ostream &operator<<(std::ostream &os, const Content &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } std::ostream &operator<<(std::ostream &os, const Output &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } std::ostream &operator<<(std::ostream &os, const CompilerWrapper &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } std::ostream &operator<<(std::ostream &os, const Compilation &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } std::ostream &operator<<(std::ostream &os, const Configuration &value) { nlohmann::json payload; to_json(payload, value); os << payload; return os; } rust::Result ConfigurationSerializer::to_json(const fs::path &file, const Configuration &rhs) const { try { std::ofstream target(file); return to_json(target, rhs) .map_err([&file](auto error) { return std::runtime_error( fmt::format("Failed to write file: {}, cause: {}", file.string(), error.what())); }); } catch (const std::exception &error) { return rust::Err(std::runtime_error( fmt::format("Failed to write file: {}, cause: {}", file.string(), error.what()))); } } rust::Result ConfigurationSerializer::to_json(std::ostream &os, const Configuration &rhs) const { try { nlohmann::json out = rhs; os << std::setw(4) << out << std::endl; return rust::Ok(size_t(1)); } catch (const std::exception &error) { return rust::Err(std::runtime_error(error.what())); } } rust::Result ConfigurationSerializer::from_json(const fs::path &file) const { try { std::ifstream source(file); return from_json(source) .map_err([&file](auto error) { return std::runtime_error( fmt::format("Failed to read file: {}, cause: {}", file.string(), error.what())); }); } catch (const std::exception &error) { return rust::Err(std::runtime_error( fmt::format("Failed to read file: {}, cause: {}", file.string(), error.what()))); } } rust::Result ConfigurationSerializer::from_json(std::istream &is) const { try { nlohmann::json in; is >> in; Configuration result; ::cs::from_json(in, result); return rust::Ok(std::move(result)); } catch (const std::exception &error) { return rust::Err(std::runtime_error(error.what())); } } } rizsotto-Bear-14c2e01/source/citnames/source/Configuration.h000066400000000000000000000077571476774233700242260ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace cs { static const std::string DUPLICATE_FILE = "file"; static const std::string DUPLICATE_FILE_OUTPUT = "file_output"; static const std::string DUPLICATE_ALL = "all"; // Controls the output format. // // The entries in the JSON compilation database can have different forms. // One format element is how the command is represented: it can be an array // of strings or a single string (shell escaping to protect white spaces). // Another format element is if the output field is emitted or not. struct Format { bool command_as_array = true; bool drop_output_field = false; }; // Controls the content of the output. // // This will act as a filter on the output elements. // These attributes can be read from the configuration file, and can be // overridden by command line arguments. struct Content { bool include_only_existing_source = false; std::string duplicate_filter_fields = DUPLICATE_FILE_OUTPUT; std::list paths_to_include = {}; std::list paths_to_exclude = {}; }; // Groups together the output related configurations. struct Output { Format format; Content content; }; // Represents a compiler wrapper that the tool will recognize. // // When executable name matches it tries to parse the flags as it would // be a known compiler, and append the additional flags to the output // entry if the compiler is recognized. struct CompilerWrapper { fs::path executable; std::list flags_to_add; std::list flags_to_remove; }; // Represents compiler related configuration. struct Compilation { std::list compilers_to_recognize; std::list compilers_to_exclude; }; // Represents the application configuration. struct Configuration { Output output; Compilation compilation; }; // Convenient methods for these types. std::ostream& operator<<(std::ostream&, const Format&); std::ostream& operator<<(std::ostream&, const Content&); std::ostream& operator<<(std::ostream&, const Output&); std::ostream& operator<<(std::ostream&, const CompilerWrapper&); std::ostream& operator<<(std::ostream&, const Compilation&); std::ostream& operator<<(std::ostream&, const Configuration&); // Utility class to persists configuration in JSON. struct ConfigurationSerializer { virtual ~ConfigurationSerializer() noexcept = default; // Serialization methods with error mapping. [[nodiscard]] virtual rust::Result to_json(const fs::path &, const Configuration &rhs) const; [[nodiscard]] virtual rust::Result to_json(std::ostream &ostream, const Configuration &rhs) const; [[nodiscard]] virtual rust::Result from_json(const fs::path &) const; [[nodiscard]] virtual rust::Result from_json(std::istream &istream) const; }; } rizsotto-Bear-14c2e01/source/citnames/source/Output.cc000066400000000000000000000263211476774233700230410ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Output.h" #include "libshell/Command.h" #include #include #include #include #include #include #include #include namespace { struct Filter { virtual ~Filter() noexcept = default; virtual bool apply(const cs::Entry &) = 0; }; struct ContentFilter : public Filter { explicit ContentFilter(cs::Content config) : config(std::move(config)) { } bool apply(const cs::Entry &entry) override { const auto &file = entry.file; return exists(file) && to_include(file) && !to_exclude(file); } private: [[nodiscard]] inline bool exists(const fs::path &file) const { const auto &to_check = config.include_only_existing_source; return (!to_check) || (to_check && does_exist(file)); } [[nodiscard]] inline bool to_include(const fs::path &file) const { const auto &include = config.paths_to_include; return include.empty() || does_contain(include, file); } [[nodiscard]] inline bool to_exclude(const fs::path &file) const { const auto &exclude = config.paths_to_exclude; return !exclude.empty() && does_contain(exclude, file); } [[nodiscard]] static bool does_exist(const fs::path &path) { std::error_code error_code; return fs::exists(path, error_code); } [[nodiscard]] static bool does_contain(const std::list &directories, const fs::path &file) { return std::any_of(directories.begin(), directories.end(), [&file](auto directory) { // check if the path elements (list of directory names) are the same. const auto [end, nothing] = std::mismatch(directory.begin(), directory.end(), file.begin()); // the file is contained in the directory if all path elements are // in the file paths too. return (end == directory.end()) || (*end == ""); }); } private: const cs::Content config; }; // Pure version of the boost::hash_combine function. static size_t hash_combine(size_t hash, size_t to_combine) { return hash ^ (to_combine + 0x9e3779b9 + (hash << 6) + (hash >> 2)); } using DuplicateFilterPtr = std::unique_ptr; struct DuplicateFilter : public Filter { static DuplicateFilterPtr from_content(const cs::Content&); bool apply(const cs::Entry &entry) override { const auto h2 = hash(entry); auto [_, new_entry] = hashes.emplace(h2); return new_entry; } private: virtual size_t hash(const cs::Entry&) const = 0; std::unordered_set hashes; }; struct FileDuplicateFilter : public DuplicateFilter { private: size_t hash(const cs::Entry &entry) const override { auto string_hasher = std::hash{}; return string_hasher(entry.file); } }; struct FileOutputDuplicateFilter : public DuplicateFilter { private: size_t hash(const cs::Entry &entry) const override { auto string_hasher = std::hash{}; auto hash = string_hasher(entry.file); if (entry.output) { hash = hash_combine(hash, string_hasher(*entry.output)); } return hash; } }; struct StrictDuplicateFilter : public DuplicateFilter { private: size_t hash(const cs::Entry &entry) const override { auto string_hasher = std::hash{}; auto hash = string_hasher(entry.file); if (entry.output) { hash = hash_combine(hash, string_hasher(*entry.output)); } for (const auto& arg : entry.arguments) { hash = hash_combine(hash, string_hasher(arg)); } return hash; } }; DuplicateFilterPtr DuplicateFilter::from_content(const cs::Content& content) { auto fields = content.duplicate_filter_fields; if (fields == cs::DUPLICATE_ALL) { return std::make_unique(); } if (fields == cs::DUPLICATE_FILE_OUTPUT) { return std::make_unique(); } if (fields == cs::DUPLICATE_FILE) { return std::make_unique(); } // If the parameter is invalid use the default filter return std::make_unique(); } } namespace cs { void validate(const Entry &entry) { if (entry.file.empty()) { throw std::runtime_error("Field 'file' is empty string."); } if (entry.directory.empty()) { throw std::runtime_error("Field 'directory' is empty string."); } if (entry.output.has_value() && entry.output.value().empty()) { throw std::runtime_error("Field 'output' is empty string."); } if (entry.arguments.empty()) { throw std::runtime_error("Field 'arguments' is empty list."); } } nlohmann::json to_json(const Entry &rhs, const Format &format) { nlohmann::json json; json["file"] = rhs.file; json["directory"] = rhs.directory; if (!format.drop_output_field && rhs.output.has_value()) { json["output"] = rhs.output.value(); } if (format.command_as_array) { json["arguments"] = rhs.arguments; } else { json["command"] = sh::join(rhs.arguments); } return json; } void from_json(const nlohmann::json &j, Entry &entry) { j.at("file").get_to(entry.file); j.at("directory").get_to(entry.directory); if (j.contains("output")) { std::string output; j.at("output").get_to(output); entry.output.emplace(output); } if (j.contains("arguments")) { std::list arguments; j.at("arguments").get_to(arguments); entry.arguments.swap(arguments); } else if (j.contains("command")) { std::string command; j.at("command").get_to(command); sh::split(command) .on_success([&entry](auto arguments) { entry.arguments = arguments; }) .on_error([](auto error) { throw error; }); } else { throw std::runtime_error("Field 'command' or 'arguments' not found"); } validate(entry); } bool operator==(const Entry &lhs, const Entry &rhs) { return (lhs.file == rhs.file) && (lhs.directory == rhs.directory) && (lhs.output == rhs.output) && (lhs.arguments == rhs.arguments); } std::ostream &operator<<(std::ostream &os, const Entry &entry) { const Format format; const nlohmann::json &json = to_json(entry, format); os << json; return os; } CompilationDatabase::CompilationDatabase(Format _format, Content _content) : format(_format) , content(std::move(_content)) { } rust::Result CompilationDatabase::to_json(const fs::path &file, const Entries &rhs) const { try { std::ofstream target(file); return to_json(target, rhs) .map_err([&file](auto error) { return std::runtime_error( fmt::format("Failed to write file: {}, cause: {}", file.string(), error.what())); }); } catch (const std::exception &error) { return rust::Err(std::runtime_error( fmt::format("Failed to write file: {}, cause: {}", file.string(), error.what()))); } } rust::Result CompilationDatabase::to_json(std::ostream &ostream, const Entries &entries) const { try { ContentFilter content_filter(content); DuplicateFilterPtr duplicate_filter = DuplicateFilter::from_content(content); size_t count = 0; nlohmann::json json = nlohmann::json::array(); for (const auto &entry : entries) { if (content_filter.apply(entry) && duplicate_filter->apply(entry)) { auto json_entry = cs::to_json(entry, format); json.emplace_back(std::move(json_entry)); ++count; } } ostream << std::setw(2) << json << std::endl; return rust::Ok(count); } catch (const std::exception &error) { return rust::Err(std::runtime_error(error.what())); } } rust::Result CompilationDatabase::from_json(const fs::path &file, std::list &entries) const { try { std::ifstream source(file); return from_json(source, entries) .map_err([&file](auto error) { return std::runtime_error( fmt::format("Failed to read file: {}, cause: {}", file.string(), error.what())); }); } catch (const std::exception &error) { return rust::Err(std::runtime_error( fmt::format("Failed to read file: {}, cause: {}", file.string(), error.what()))); } } rust::Result CompilationDatabase::from_json(std::istream &istream, std::list &entries) const { try { nlohmann::json in; istream >> in; for (const auto &e : in) { Entry entry; cs::from_json(e, entry); entries.emplace_back(entry); } return rust::Ok(in.size()); } catch (const std::exception &error) { return rust::Err(std::runtime_error(error.what())); } } } rizsotto-Bear-14c2e01/source/citnames/source/Output.h000066400000000000000000000060201476774233700226750ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Configuration.h" #include "libresult/Result.h" #include #include #include #include #include namespace fs = std::filesystem; namespace cs { // The definition of the JSON compilation database format can be // found here: // https://clang.llvm.org/docs/JSONCompilationDatabase.html // // The entry represents one element of the database. While the // database might contains multiple entries (even for the same // source file). A list of entries represents a whole compilation // database. (No other metadata is provided.) // // The only unique field in the database the output field can be, // but that is an optional field. So, in this sense this is not // really a database with keys. struct Entry { fs::path file; fs::path directory; std::optional output; std::list arguments; }; // Convenient methods for these types. bool operator==(const Entry& lhs, const Entry& rhs); std::ostream& operator<<(std::ostream&, const Entry&); // Utility class to persists JSON compilation database. // // While the JSON compilation database might have different format // (have either "command" or "arguments" fields), this util class // provides a simple interface to read any format of the file. // // It also supports to write different format with configuration // parameters. And basic content filtering is also available. struct CompilationDatabase { using Entries = std::list; CompilationDatabase(Format, Content); virtual ~CompilationDatabase() noexcept = default; // Serialization methods with error mapping. [[nodiscard]] virtual rust::Result to_json(const fs::path& file, const Entries &entries) const; [[nodiscard]] virtual rust::Result to_json(std::ostream &ostream, const Entries &entries) const; [[nodiscard]] virtual rust::Result from_json(const fs::path& file, Entries &entries) const; [[nodiscard]] virtual rust::Result from_json(std::istream &istream, Entries &entries) const; private: Format format; Content content; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/000077500000000000000000000000001476774233700230315ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/source/semantic/Build.cc000066400000000000000000000060701476774233700244020ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Build.h" #include "ToolAny.h" #include "ToolGcc.h" #include "ToolCrayFtnfe.h" #include "ToolClang.h" #include "ToolCuda.h" #include "ToolIntelFortran.h" #include "ToolWrapper.h" #include "ToolExtendingWrapper.h" #include "Convert.h" #include #include #include #include #ifdef FMT_NEEDS_OSTREAM_FORMATTER template <> struct fmt::formatter : ostream_formatter {}; #endif namespace { std::shared_ptr from(cs::Compilation cfg) { cs::semantic::ToolAny::ToolPtrs tools = { std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), }; for (auto && compiler : cfg.compilers_to_recognize) { tools.emplace_front(std::make_shared(std::move(compiler))); } return std::make_shared( std::move(tools), std::move(cfg.compilers_to_exclude) ); } } namespace cs::semantic { Build::Build(Compilation cfg) noexcept : tools_(from(std::move(cfg))) { } [[nodiscard]] rust::Result Build::recognize(const rpc::Event &event) const { if (event.has_started()) { auto execution = domain::from(event.started().execution()); auto pid = event.started().pid(); spdlog::debug("[pid: {}] execution: {}", pid, execution); auto result = tools_->recognize(execution); if (Tool::recognized_ok(result)) { spdlog::debug("[pid: {}] recognized.", pid); } else if (Tool::recognized_with_error(result)) { spdlog::debug("[pid: {}] recognition failed: {}", pid, result.unwrap_err().what()); } else if (Tool::not_recognized(result)) { spdlog::debug("[pid: {}] not recognized.", pid); } return result; } else { return rust::Err(std::runtime_error("n/a")); } } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Build.h000066400000000000000000000026721476774233700242500ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "Configuration.h" #include "semantic/Tool.h" #include "intercept.grpc.pb.h" #include namespace cs::semantic { // Represents an expert system which can recognize compilation entries from // command executions. It covers multiple tools and consider omit results // based on configuration. class Build { public: explicit Build(Compilation cfg) noexcept; [[nodiscard]] rust::Result recognize(const rpc::Event &event) const; NON_DEFAULT_CONSTRUCTABLE(Build) NON_COPYABLE_NOR_MOVABLE(Build) private: std::shared_ptr tools_; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Common.cc000066400000000000000000000105241476774233700245720ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Common.h" #include #include #include using namespace cs::semantic; namespace { std::tuple< Arguments, std::vector, std::optional> split(const CompilerFlags& flags) { Arguments arguments; std::vector sources; std::optional output; for (const auto& flag : flags) { switch (flag.type) { case CompilerFlagType::SOURCE: { auto candidate = fs::path(flag.arguments.front()); sources.emplace_back(std::move(candidate)); break; } case CompilerFlagType::KIND_OF_OUTPUT_OUTPUT: { auto candidate = fs::path(flag.arguments.back()); output = std::make_optional(std::move(candidate)); break; } case CompilerFlagType::LINKER: case CompilerFlagType::PREPROCESSOR_MAKE: case CompilerFlagType::DIRECTORY_SEARCH_LINKER: break; default: { std::copy(flag.arguments.begin(), flag.arguments.end(), std::back_inserter(arguments)); break; } } } return std::make_tuple(arguments, sources, output); } bool is_compiler_query(const CompilerFlags& flags) { // no flag is a no compilation if (flags.empty()) { return true; } // otherwise check if this was a version query of a help return std::any_of(flags.begin(), flags.end(), [](const auto& flag) { return (flag.type == CompilerFlagType::KIND_OF_OUTPUT_INFO); }); } bool linking(const CompilerFlags& flags) { return std::none_of(flags.begin(), flags.end(), [](auto flag) { return (flag.type == CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING); }); } } rust::Result cs::semantic::compilation_impl(const FlagsByName& flags, const Execution& execution, std::function create_argument_list_func, std::function is_preprocessor_func) { const auto& parser = Repeat( OneOf( FlagParser(flags), SourceMatcher(), EverythingElseFlagMatcher())); const Arguments& input_arguments = create_argument_list_func(execution); return parse(parser, input_arguments) .and_then([&execution, is_preprocessor_func](auto flags) -> rust::Result { if (is_compiler_query(flags)) { SemanticPtr result = std::make_shared(); return rust::Ok(std::move(result)); } if (is_preprocessor_func(flags)) { SemanticPtr result = std::make_shared(); return rust::Ok(std::move(result)); } auto [arguments, sources, output] = split(flags); // Validate: must have source files. if (sources.empty()) { return rust::Err(std::runtime_error("Source files not found for compilation.")); } // TODO: introduce semantic type for linking if (linking(flags)) { arguments.insert(arguments.begin(), "-c"); } SemanticPtr result = std::make_shared( execution.working_dir, execution.executable, std::move(arguments), std::move(sources), std::move(output)); return rust::Ok(std::move(result)); }); } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Common.h000066400000000000000000000021441476774233700244330ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Semantic.h" #include "Parsers.h" #include "libresult/Result.h" namespace cs::semantic { rust::Result compilation_impl(const FlagsByName& flags, const Execution& execution, std::function create_argument_list_func, std::function is_preprocessor_func); } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Parsers.cc000066400000000000000000000300361476774233700247610ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Parsers.h" #include namespace { std::string_view take_extension(const std::string_view &file) { const auto pos = file.rfind('.'); return (pos == std::string::npos) ? file : file.substr(pos); } std::optional split_extra(const std::string_view &prefix, const std::string_view &candidate) { if (prefix.empty()) { return std::make_optional(candidate); } if (candidate.empty()) { return std::nullopt; } if (prefix.size() > candidate.size()) { return std::nullopt; } const auto common = candidate.substr(0, prefix.size()); if (common == prefix) { return std::make_optional(candidate.substr(prefix.size())); } return std::nullopt; } enum class FlagMatch { SEP, GLUED, GLUED_WITH_EQ, }; FlagMatch classify_flag_matching(const std::string_view &flag) { if (flag.empty()) { return FlagMatch::SEP; } else { if (flag[0] == '=') { return FlagMatch::GLUED_WITH_EQ; } else { return FlagMatch::GLUED; } } } using namespace cs::semantic; bool is_exact_match_only(const MatchInstruction match_instruction) { switch (match_instruction) { case MatchInstruction::EXACTLY: case MatchInstruction::EXACTLY_WITH_1_OPT_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP: case MatchInstruction::EXACTLY_WITH_2_OPTS: case MatchInstruction::EXACTLY_WITH_3_OPTS: return true; default: return false; } } bool is_prefix_match(const MatchInstruction match_instruction) { switch (match_instruction) { case MatchInstruction::PREFIX: case MatchInstruction::PREFIX_WITH_1_OPT: case MatchInstruction::PREFIX_WITH_2_OPTS: case MatchInstruction::PREFIX_WITH_3_OPTS: return true; default: return false; } } bool is_glue_allowed(const MatchInstruction match_instruction) { switch (match_instruction) { case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP: case MatchInstruction::PREFIX: case MatchInstruction::PREFIX_WITH_1_OPT: case MatchInstruction::PREFIX_WITH_2_OPTS: case MatchInstruction::PREFIX_WITH_3_OPTS: return true; default: return false; } } bool is_glue_with_equal_allowed(const MatchInstruction match_instruction) { switch (match_instruction) { case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP: return true; default: return false; } } [[nodiscard]] size_t count_of_arguments(MatchInstruction match_instruction) { switch (match_instruction) { case MatchInstruction::EXACTLY: return 1; case MatchInstruction::EXACTLY_WITH_1_OPT_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP: case MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP: return 2; case MatchInstruction::EXACTLY_WITH_2_OPTS: return 3; case MatchInstruction::EXACTLY_WITH_3_OPTS: return 4; case MatchInstruction::PREFIX: return 1; case MatchInstruction::PREFIX_WITH_1_OPT: return 2; case MatchInstruction::PREFIX_WITH_2_OPTS: return 3; case MatchInstruction::PREFIX_WITH_3_OPTS: return 4; } return 0; } } namespace cs::semantic { std::tuple ArgumentsView::take(const size_t count) const { const size_t size = std::distance(begin_, end_); if (size < count) { auto arguments = ArgumentsView(begin_, begin_); auto remainder = ArgumentsView(end_, end_); return std::make_tuple(arguments, remainder); } else { const auto end = std::next(begin_, count); auto arguments = ArgumentsView(begin_, end); auto remainder = ArgumentsView(end, end_); return std::make_tuple(arguments, remainder); } } Arguments::value_type ArgumentsView::front() const { return *begin_; } Arguments::value_type ArgumentsView::back() const { return *std::prev(end_); } rust::Result, ArgumentsView> FlagParser::parse(const ArgumentsView &input) const { // early exit if there is nothing to do. if (input.empty()) { return rust::Err(input); } // early exit if the flag is an empty string. const auto key = input.front(); if (key.empty()) { return rust::Err(input); } // based on the lookup result, consume the input. if (auto match = lookup(key); match) { const auto&[count, type] = match.value(); const auto&[arguments, remainder] = input.take(count); if (arguments.empty()) { return rust::Err(input); } auto flag = CompilerFlag { arguments, type }; return rust::Ok(std::make_pair(flag, remainder)); } return rust::Err(input); } std::optional FlagParser::lookup(const std::string_view &key) const { // try to find if the key has an associated instruction if (const auto candidate = flags_.lower_bound(key); flags_.end() != candidate) { // exact matches are preferred in all cases. if (auto result = check_equal(key, *candidate); result) { return result; } } std::optional candidate_longest = std::nullopt; for (const auto &[candidate, info] : flags_) { if (const auto& extra = split_extra(candidate, key); extra) { const size_t prefix_length = key.size() - extra.value().size(); if (!candidate_longest || (prefix_length > candidate_longest.value().size())) { candidate_longest = std::make_optional(std::string(candidate)); } } } return (candidate_longest.has_value()) ? check_partial(key, *(flags_.find(candidate_longest.value()))) : std::nullopt; } std::optional FlagParser::check_equal(const std::string_view &key, const FlagsByName::value_type &candidate) { const auto &flag_definition = candidate.second; if ((is_exact_match_only(flag_definition.match) || is_prefix_match(flag_definition.match)) && key == candidate.first) { const size_t count = count_of_arguments(flag_definition.match); return std::make_optional(std::make_tuple(count, flag_definition.type)); } return std::nullopt; } std::optional FlagParser::check_partial(const std::string_view &key, const FlagsByName::value_type &candidate) { const auto &flag_definition = candidate.second; const auto flag_matching = classify_flag_matching(key.substr(candidate.first.size())); switch (flag_matching) { case FlagMatch::GLUED: if (is_glue_allowed(flag_definition.match)) { const size_t decrease = is_prefix_match(flag_definition.match) ? 0 : 1; const size_t count = count_of_arguments(flag_definition.match) - decrease; return std::make_optional(std::make_tuple(count, flag_definition.type)); } break; case FlagMatch::GLUED_WITH_EQ: if (is_glue_with_equal_allowed(flag_definition.match)) { const size_t count = count_of_arguments(flag_definition.match) - 1; return std::make_optional(std::make_tuple(count, flag_definition.type)); } break; default: // This should not happen here. Exact match is already filtered out. __builtin_unreachable(); } return std::nullopt; } rust::Result, ArgumentsView> SourceMatcher::parse(const ArgumentsView &input) { static const std::set extensions = { // header files ".h", ".hh", ".H", ".hp", ".hxx", ".hpp", ".HPP", ".h++", ".tcc", // C ".c", ".C", // C++ ".cc", ".CC", ".c++", ".C++", ".cxx", ".cpp", ".cp", // CUDA ".cu", // ObjectiveC ".m", ".mi", ".mm", ".M", ".mii", // Preprocessed ".i", ".ii", // Assembly ".s", ".S", ".sx", ".asm", // Fortran ".f", ".for", ".ftn", ".F", ".FOR", ".fpp", ".FPP", ".FTN", ".f90", ".f95", ".f03", ".f08", ".F90", ".F95", ".F03", ".F08", // go ".go", // brig ".brig", // D ".d", ".di", ".dd", // Ada ".ads", ".abd" }; if (input.empty()) { return rust::Err(input); } const auto &candidate = input.front(); const auto &extension = take_extension(candidate); if (extensions.find(extension) != extensions.end()) { const auto &[arguments, remainder] = input.take(1); if (arguments.empty()) { return rust::Err(input); } auto flag = CompilerFlag { arguments, CompilerFlagType::SOURCE }; return rust::Ok(std::make_pair(flag, remainder)); } return rust::Err(input); } rust::Result, ArgumentsView> EverythingElseFlagMatcher::parse(const ArgumentsView &input) { if (input.empty()) { return rust::Err(input); } if (const auto &front = input.front(); !front.empty()) { const auto &[arguments, remainder] = input.take(1); if (arguments.empty()) { return rust::Err(input); } auto flag = CompilerFlag { arguments, CompilerFlagType::LINKER_OBJECT_FILE }; return rust::Ok(std::make_pair(flag, remainder)); } return rust::Err(input); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Parsers.h000066400000000000000000000200551476774233700246230ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libresult/Result.h" #include "Domain.h" #include #include #include #include #include #include #include #include namespace cs::semantic { using namespace domain; // Represents command line arguments. using Arguments = std::list; // Represents a segment of command line arguments. struct ArgumentsView { explicit ArgumentsView(const Arguments &input) noexcept : begin_(std::next(input.begin())) , end_(input.end()) { } explicit ArgumentsView(Arguments::const_iterator begin, Arguments::const_iterator end) noexcept : begin_(begin) , end_(end) { } [[nodiscard]] bool empty() const { return (begin_ == end_); } [[nodiscard]] Arguments::const_iterator begin() const { return begin_; } [[nodiscard]] Arguments::const_iterator end() const { return end_; } [[nodiscard]] Arguments::value_type front() const; [[nodiscard]] Arguments::value_type back() const; [[nodiscard]] std::tuple take(size_t count) const; private: Arguments::const_iterator begin_; Arguments::const_iterator end_; }; inline bool operator==(const ArgumentsView &lhs, const ArgumentsView &rhs) { return (lhs.begin() == rhs.begin()) && (lhs.end() == rhs.end()); } // enum class Category { // CONTROL, // INPUT, // OUTPUT, // DEBUG, // OPTIMIZE, // DIAGNOSTIC, // }; // // enum class CompilerPass { // PREPROCESSOR, // COMPILER, // ANALYZER, // LINKER, // }; // // struct Meaning { // Category category; // std::optional affects; // }; enum class CompilerFlagType { KIND_OF_OUTPUT, KIND_OF_OUTPUT_NO_LINKING, KIND_OF_OUTPUT_INFO, KIND_OF_OUTPUT_OUTPUT, PREPROCESSOR, PREPROCESSOR_MAKE, LINKER, LINKER_OBJECT_FILE, DIRECTORY_SEARCH, DIRECTORY_SEARCH_LINKER, SOURCE, OTHER, STATIC_ANALYZER, }; struct CompilerFlag { ArgumentsView arguments; CompilerFlagType type; }; using CompilerFlags = std::list; enum class MatchInstruction { EXACTLY, EXACTLY_WITH_1_OPT_SEP, EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, EXACTLY_WITH_1_OPT_GLUED, EXACTLY_WITH_1_OPT_GLUED_OR_SEP, EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP, EXACTLY_WITH_2_OPTS, EXACTLY_WITH_3_OPTS, PREFIX, PREFIX_WITH_1_OPT, PREFIX_WITH_2_OPTS, PREFIX_WITH_3_OPTS, }; struct FlagDefinition { MatchInstruction match; CompilerFlagType type; }; using FlagsByName = std::map; // Parser combinator which takes a list of flag definition and tries to apply // to the received input stream. It can recognize only a single compiler flag // at the time. class FlagParser { public: explicit FlagParser(FlagsByName const& flags) noexcept : flags_(flags) { } [[nodiscard]] rust::Result, ArgumentsView> parse(const ArgumentsView &input) const; private: using Match = std::tuple; [[nodiscard]] std::optional lookup(const std::string_view &key) const; [[nodiscard]] static std::optional check_equal(const std::string_view& key, const FlagsByName::value_type &candidate); [[nodiscard]] static std::optional check_partial(const std::string_view& key, const FlagsByName::value_type &candidate); FlagsByName const& flags_; }; // Parser combinator which recognize source files as a single argument of a compiler call. struct SourceMatcher { [[nodiscard]] static rust::Result, ArgumentsView> parse(const ArgumentsView &input); }; // A parser combinator, which recognize a single compiler flag without any conditions. struct EverythingElseFlagMatcher { [[nodiscard]] static rust::Result, ArgumentsView> parse(const ArgumentsView &input); }; // A parser combinator, which takes multiple parsers and executes them // util one returns successfully and returns that as result. If none of // the parser returns success, it fails. template struct OneOf { using container_type = typename std::tuple; container_type const parsers; explicit constexpr OneOf(Parsers const& ...p) noexcept : parsers(p...) { } [[nodiscard]] rust::Result, ArgumentsView> parse(const ArgumentsView &input) const { rust::Result, ArgumentsView> result = rust::Err(input); const bool valid = std::apply([&input, &result](auto &&... parser) { return ((result = parser.parse(input), result.is_ok()) || ... ); }, parsers); return (valid) ? result : rust::Err(input); } }; // A parser combinator, which takes single parser and executes it util // returns successfully or consumes all input. If the parser fails before // the input is all consumed, it fails. template struct Repeat { using result_type = rust::Result; Parser const parser; explicit constexpr Repeat(Parser p) noexcept : parser(std::move(p)) { } [[nodiscard]] result_type parse(ArgumentsView input) const { CompilerFlags flags; while (!input.empty()) { auto result = parser.parse(input) .on_success([&flags, &input](const auto& tuple) { const auto& [flag, remainder] = tuple; flags.push_back(flag); input = remainder; }); if (result.is_err()) { break; } } return (input.empty()) ? result_type(rust::Ok(flags)) : result_type(rust::Err(input)); } }; template rust::Result parse(const Parser &parser, const Arguments &arguments) { if (arguments.empty()) { return rust::Err(std::runtime_error("Failed to recognize: no arguments found.")); } ArgumentsView input(arguments); return parser.parse(input) .template map_err([](auto remainder) { return std::runtime_error( fmt::format("Failed to recognize: {}", fmt::join(remainder.begin(), remainder.end(), ", ")) ); }); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Semantic.cc000066400000000000000000000107431476774233700251100ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "semantic/Semantic.h" #include #include #ifdef HAVE_FMT_STD_H #include #else namespace fmt { template <> struct formatter : formatter {}; } #endif namespace cs::semantic { bool QueryCompiler::operator==(const Semantic &rhs) const { if (this == &rhs) return true; if (const auto *const ptr = dynamic_cast(&rhs); ptr != nullptr) { return true; } return false; } std::ostream &QueryCompiler::operator<<(std::ostream &os) const { os << "Query"; return os; } std::list QueryCompiler::into_entries() const { return {}; } bool Preprocess::operator==(const Semantic &) const { return false; } std::ostream &Preprocess::operator<<(std::ostream &os) const { os << "Preprocess"; return os; } std::list Preprocess::into_entries() const { return {}; } Compile::Compile(fs::path working_dir, fs::path compiler, std::list flags, std::vector sources, std::optional output) : working_dir(std::move(working_dir)) , compiler(std::move(compiler)) , flags(std::move(flags)) , sources(std::move(sources)) , output(std::move(output)) { } bool Compile::operator==(const Semantic &rhs) const { if (this == &rhs) return true; if (const auto *const ptr = dynamic_cast(&rhs); ptr != nullptr) { return (working_dir == ptr->working_dir) && (compiler == ptr->compiler) && (output == ptr->output) && (sources == ptr->sources) && (flags == ptr->flags); } return false; } std::ostream &Compile::operator<<(std::ostream &os) const { os << "Compile { working_dir: " << working_dir << ", compiler: " << compiler << ", flags: " << fmt::format("[{}]", fmt::join(flags.begin(), flags.end(), ", ")) << ", sources: " << fmt::format("[{}]", fmt::join(sources.begin(), sources.end(), ", ")) << ", output: " << (output ? output.value().string() : "") << " }"; return os; } std::list Compile::into_entries() const { const auto abspath = [this](const fs::path &path) -> fs::path { auto candidate = (path.is_absolute()) ? path : working_dir / path; // Create canonical path without checking of file existence. fs::path result; for (const auto& part : candidate) { if (part == ".") continue; if (part == "..") result = result.parent_path(); else result = result / part; } return result; }; std::list results; for (const auto& source : sources) { cs::Entry result { abspath(source), working_dir, output ? std::optional(abspath(output.value())) : std::nullopt, { compiler.string() } }; std::copy(flags.begin(), flags.end(), std::back_inserter(result.arguments)); if (output) { result.arguments.emplace_back("-o"); result.arguments.push_back(output.value().string()); } result.arguments.push_back(source); results.emplace_back(std::move(result)); } return results; } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Semantic.h000066400000000000000000000064131476774233700247510ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Output.h" #include "Domain.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace cs::semantic { using namespace domain; // Represents a recognized command. Which we can find out the intent // of that command. And therefore we know the semantic of it. struct Semantic { virtual ~Semantic() noexcept = default; virtual bool operator==(Semantic const&) const = 0; virtual std::ostream& operator<<(std::ostream&) const = 0; }; inline std::ostream& operator<<(std::ostream& os, Semantic const& value) { value.operator<<(os); return os; } inline bool operator==(Semantic const &lhs, Semantic const &rhs) { return lhs.operator==(rhs); } using SemanticPtr = std::shared_ptr; // Represents a compiler call command. struct CompilerCall : public Semantic { [[nodiscard]] virtual std::list into_entries() const = 0; }; // Represents a compiler call, which does process any input, but query // something from the compiler itself. It can be a help or a version query. struct QueryCompiler : public CompilerCall { bool operator==(Semantic const&) const override; std::ostream& operator<<(std::ostream&) const override; [[nodiscard]] std::list into_entries() const override; }; // Represents a compiler call, which runs only the preprocessor. struct Preprocess : public CompilerCall { bool operator==(Semantic const&) const override; std::ostream& operator<<(std::ostream&) const override; [[nodiscard]] std::list into_entries() const override; }; // Represents a compiler call, which runs the compilation pass. struct Compile : public CompilerCall { Compile(fs::path working_dir, fs::path compiler, std::list flags, std::vector sources, std::optional output); bool operator==(Semantic const&) const override; std::ostream& operator<<(std::ostream&) const override; [[nodiscard]] std::list into_entries() const override; public: fs::path working_dir; fs::path compiler; std::list flags; std::vector sources; std::optional output; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/Tool.h000066400000000000000000000041461476774233700241240ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "semantic/Semantic.h" #include "libresult/Result.h" #include namespace cs::semantic { // Represents a program, which can recognize the intent of the execution // and return the semantic of that. It can be a compiler or any other // program participating in a build process. struct Tool { virtual ~Tool() noexcept = default; // Returns the semantic of a command execution. [[nodiscard]] virtual rust::Result recognize(const Execution &) const = 0; // Helper methods to evaluate the recognize method result. static bool recognized_ok(const rust::Result &result) noexcept; static bool recognized_with_error(const rust::Result &result) noexcept; static bool not_recognized(const rust::Result &result) noexcept; }; inline bool Tool::recognized_ok(const rust::Result &result) noexcept { return result.is_ok() && (result.unwrap().operator bool()); } inline bool Tool::recognized_with_error(const rust::Result &result) noexcept { return result.is_err(); } inline bool Tool::not_recognized(const rust::Result &result) noexcept { return result.is_ok() && !(result.unwrap().operator bool()); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolAny.cc000066400000000000000000000036011476774233700247250ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolAny.h" #include namespace cs::semantic { ToolAny::ToolAny(ToolAny::ToolPtrs &&tools, std::list &&to_exclude) noexcept : tools_(tools) , to_exclude_(to_exclude) { } rust::Result ToolAny::recognize(const domain::Execution &execution) const { // do different things if the execution is matching one of the nominated compilers. if (to_exclude_.end() != std::find(to_exclude_.begin(), to_exclude_.end(), execution.executable)) { return rust::Err(std::runtime_error("The tool is on the exclude list from configuration.")); } else { // check if any tool can recognize the execution. for (const auto &tool : tools_) { auto result = tool->recognize(execution); // return if it recognized in any way. if (Tool::recognized_ok(result) || Tool::recognized_with_error(result)) { return result; } } } return rust::Err(std::runtime_error("No tools recognize this execution.")); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolAny.h000066400000000000000000000023621476774233700245720ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Tool.h" namespace cs::semantic { class ToolAny : public Tool { public: using ToolPtr = std::shared_ptr; using ToolPtrs = std::list; ToolAny(ToolPtrs &&tools, std::list &&to_exclude) noexcept; [[nodiscard]] rust::Result recognize(const Execution &execution) const override; private: ToolPtrs tools_; std::list to_exclude_; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolClang.cc000066400000000000000000000553141476774233700252320ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolClang.h" #include "ToolGcc.h" #include using namespace cs::semantic; namespace { // https://clang.llvm.org/docs/ClangCommandLineReference.html const FlagsByName CLANG_FLAG_DEFINITION = { {"-cc1", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"--prefix", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::DIRECTORY_SEARCH}}, {"-F", {MatchInstruction::PREFIX, CompilerFlagType::DIRECTORY_SEARCH}}, {"-ObjC", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-ObjC++", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-Xarch", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-Xcuda", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-Xopenmp-target", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-Xopenmp-target=", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-Z", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::STATIC_ANALYZER}}, {"-a", {MatchInstruction::PREFIX, CompilerFlagType::STATIC_ANALYZER}}, {"--profile-blocks", {MatchInstruction::EXACTLY, CompilerFlagType::STATIC_ANALYZER}}, {"-all_load", {MatchInstruction::EXACTLY, CompilerFlagType::STATIC_ANALYZER}}, {"-allowable_client", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::STATIC_ANALYZER}}, {"--analyze", {MatchInstruction::EXACTLY, CompilerFlagType::STATIC_ANALYZER}}, {"--analyzer-no-default-checks", {MatchInstruction::EXACTLY, CompilerFlagType::STATIC_ANALYZER}}, {"--analyzer-output", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::STATIC_ANALYZER}}, {"-Xanalyzer", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::STATIC_ANALYZER}}, {"-arch", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-arch_errors_fatal", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-arch_only", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-arcmt-migrate-emit-errors", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-arcmt-migrate-report-output", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"--autocomplete", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-bind_at_load", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-bundle", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-bundle_loader", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-client_name", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-compatibility_version", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--config", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"--constant-cfstrings", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--cuda-compile-host-device", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--cuda-device-only", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--cuda-host-only", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--cuda-include-ptx", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"--no-cuda-include-ptx", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"--cuda-noopt-device-debug", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--no-cuda-noopt-device-debug", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-cuid", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-current_version", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-dead_strip", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-dependency-dot", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-dependency-file", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-dsym-dir", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-dumpmachine", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-dumpversion", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--dyld-prefix", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, CompilerFlagType::OTHER}}, {"-dylib_file", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-dylinker", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-dylinker_install_name", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-dynamic", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-dynamiclib", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-emit-ast", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-exported_symbols_list", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-faligned-new", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-force_load", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-framework", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"--gcc-toolchain", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-gcodeview", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-gcodeview-ghash", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-gno-codeview-ghash", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--gpu-instrument-lib", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"--gpu-max-threads-per-block", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-headerpad_max_install_names", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--hip-link", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--hip-version", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-ibuiltininc", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-image_base", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-index-header-map", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-init", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-install_name", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-interface-stub-version", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-keep_private_externs", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-lazy", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-EB", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--migrate", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-mllvm", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-module-dependency-dir", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-multiply_defined", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"--output", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, CompilerFlagType::OTHER}}, {"-objcmt", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-object", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--profile", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--pipe", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--print-diagnostic-categories", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-r", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--save", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-sect", {MatchInstruction::PREFIX_WITH_3_OPTS, CompilerFlagType::OTHER}}, {"-seg1addr", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-seg_", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-segaddr", {MatchInstruction::EXACTLY_WITH_2_OPTS, CompilerFlagType::OTHER}}, {"-segcreate", {MatchInstruction::EXACTLY_WITH_3_OPTS, CompilerFlagType::OTHER}}, {"-seglinkedit", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-segprot", {MatchInstruction::EXACTLY_WITH_3_OPTS, CompilerFlagType::OTHER}}, {"-serialize-diagnostics", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"--serialize-diagnostics", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-single_module", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-sub_", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--sysroot", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, CompilerFlagType::OTHER}}, {"--target", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-target", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-time", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--traditional", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-traditional", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-twolevel", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-umbrella", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-unexported_symbols_list", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-unwindlib", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"--unwindlib", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-x", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--language", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, CompilerFlagType::OTHER}}, {"-Xassembler", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-Xclang", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-Xpreprocessor", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, }; // Taken from the LLVM 20.1 at: // https://github.com/llvm/llvm-project/blob/llvmorg-20.1.0/clang/include/clang/Driver/Options.td // Only flang exclusive flags are specified here (the ones without // ClangOption visibility) const FlagsByName FLANG_FLAG_DEFINITION = { {"-J", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-Xflang", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-cpp", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-nocpp", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-falternative-parameter-statement", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fbackslash", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-backslash", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fconvert", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-fdefault-double-8", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdefault-integer-8", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdefault-real-8", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdisable-integer-16", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdisable-integer-2", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdisable-real-10", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fdisable-real-3", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-ffixed-form", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-ffixed-line-length", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-ffixed-line-length-", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-ffree-form", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-finit-global-zero", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-init-global-zero", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fhermetic-module-files", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fimplicit-none", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-implicit-none", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fintrinsic-modules-path", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-flang-deprecated-no-hlfir", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-flang-experimental-hlfir", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-flarge-sizes", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-flogical-abbreviations", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-logical-abbreviations", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-automatic", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-frealloc-lhs", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-realloc-lhs", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fsave-main-program", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-funderscoring", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-underscoring", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-funsigned", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-unsigned", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fxor-operator", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-fno-xor-operator", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-module-dir", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER}}, {"--romc-path", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::DIRECTORY_SEARCH_LINKER}}, }; FlagsByName clang_flags(const FlagsByName &base) { FlagsByName flags(base); flags.insert(CLANG_FLAG_DEFINITION.begin(), CLANG_FLAG_DEFINITION.end()); flags.insert(FLANG_FLAG_DEFINITION.begin(), FLANG_FLAG_DEFINITION.end()); return flags; } } namespace cs::semantic { ToolClang::ToolClang() noexcept : flag_definition(clang_flags(ToolGcc::FLAG_DEFINITION)) { } rust::Result ToolClang::recognize(const Execution &execution) const { if (is_compiler_call(execution.executable)) { return ToolGcc::compilation(ToolClang::flag_definition, execution); } return rust::Ok(SemanticPtr()); } bool ToolClang::is_compiler_call(const fs::path &program) const { static const auto pattern = std::regex(R"(^([^-]*-)*(clang(|\+\+)|flang(-new)?)(-?\d+(\.\d+){0,2})?$)"); std::cmatch m; return std::regex_match(program.filename().c_str(), m, pattern); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolClang.h000066400000000000000000000022541476774233700250670ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "ToolGcc.h" namespace cs::semantic { struct ToolClang : public ToolGcc { ToolClang() noexcept; [[nodiscard]] rust::Result recognize(const Execution &execution) const override; protected: [[nodiscard]] bool is_compiler_call(const fs::path &program) const override; const FlagsByName flag_definition; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolCrayFtnfe.cc000066400000000000000000000203501476774233700260570ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolCrayFtnfe.h" #include "Common.h" #include #include using namespace cs::semantic; namespace { Arguments create_argument_list(const Execution& execution) { Arguments input_arguments; std::copy(execution.arguments.begin(), execution.arguments.end(), std::back_inserter(input_arguments)); return input_arguments; } bool is_preprocessor(const CompilerFlags& flags) { return std::any_of(flags.begin(), flags.end(), [](const auto& flag) { const std::string& candidate = flag.arguments.front(); static const std::set NO_COMPILATION_FLAG = { "-E", "-eZ", "-e Z", "-eP", "-e P" }; return ((flag.type == CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING) && (NO_COMPILATION_FLAG.find(candidate) != NO_COMPILATION_FLAG.end())) || ((flag.type == CompilerFlagType::PREPROCESSOR_MAKE)); }); return false; } } namespace cs::semantic { const FlagsByName ToolCrayFtnfe::FLAG_DEFINITION = { { "-add-rpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-add-rpath-shared", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-add-runpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-as-needed", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "--as-needed", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-A", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-b", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT } }, { "-c", { MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING } }, { "--custom-ld-script=", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::LINKER } }, { "-d", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-D", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR } }, { "-e", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-E", { MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING } }, { "-f", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-F", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-g", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-gcc-rpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-G", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-h", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-I", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH } }, { "-J", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH } }, { "-K", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-l", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::LINKER } }, { "-L", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH_LINKER } }, { "-m", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-M", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-no-add-rpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-no-add-rpath-shared", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-no-add-runpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-no-as-needed", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "--no-as-needed", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "--no-custom-ld-script", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-no-gcc-rpath", { MatchInstruction::EXACTLY, CompilerFlagType::LINKER } }, { "-N", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-O", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER } }, { "-o", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT } }, { "-p", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH } }, { "-Q", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH } }, { "-r", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::KIND_OF_OUTPUT } }, { "-R", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-s", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-S", { MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING } }, { "-T", { MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO } }, { "-target-accel=", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER } }, { "-target-cpu=", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER } }, { "-target-network=", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER } }, { "-U", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR } }, { "-v", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-V", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-W", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-x", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-Y", { MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER } }, { "-openmp", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-noopenmp", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-mp", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-Mnoopenmp", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-qno-openmp", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-dynamic", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-shared", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-static", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-default64", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-VV", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-VVV", { MatchInstruction::EXACTLY, CompilerFlagType::OTHER } }, { "-cray", { MatchInstruction::PREFIX, CompilerFlagType::OTHER } }, { "--cray", { MatchInstruction::PREFIX, CompilerFlagType::OTHER } }, }; rust::Result ToolCrayFtnfe::recognize(const Execution& execution) const { if (is_compiler_call(execution.executable)) { return compilation_impl(FLAG_DEFINITION, execution, create_argument_list, is_preprocessor); } return rust::Ok(SemanticPtr()); } bool ToolCrayFtnfe::is_compiler_call(const fs::path& program) const { static const auto pattern = std::regex(R"(^([^-]*-)*(ftnfe)(-?\w+(\.\d+){0,2})?$)"); std::cmatch m; return std::regex_match(program.filename().c_str(), m, pattern); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolCrayFtnfe.h000066400000000000000000000022261476774233700257230ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Parsers.h" #include "Tool.h" namespace cs::semantic { struct ToolCrayFtnfe : public Tool { [[nodiscard]] rust::Result recognize(const Execution& execution) const override; protected: [[nodiscard]] bool is_compiler_call(const fs::path& program) const; static const FlagsByName FLAG_DEFINITION; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolCuda.cc000066400000000000000000000021331476774233700250510ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolCuda.h" #include "ToolGcc.h" #include namespace cs::semantic { bool ToolCuda::is_compiler_call(const fs::path& program) const { static const auto pattern = std::regex(R"(^(nvcc)$)"); std::cmatch m; return std::regex_match(program.filename().c_str(), m, pattern); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolCuda.h000066400000000000000000000017411476774233700247170ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "ToolGcc.h" namespace cs::semantic { struct ToolCuda : public ToolGcc { [[nodiscard]] bool is_compiler_call(const fs::path& program) const override; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolExtendingWrapper.cc000066400000000000000000000052111476774233700274630ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolExtendingWrapper.h" #include namespace cs::semantic { ToolExtendingWrapper::ToolExtendingWrapper(CompilerWrapper &&compilers_to_recognize) noexcept : compilers_to_recognize_(compilers_to_recognize) { } bool ToolExtendingWrapper::is_compiler_call(const fs::path &program) const { return compilers_to_recognize_.executable == program; } rust::Result ToolExtendingWrapper::recognize(const Execution &execution) const { return ToolGcc::recognize(execution) .map([this](auto semantic) { if (auto* ptr = dynamic_cast(semantic.get()); ptr != nullptr) { // remove flags which were asked to be removed. ptr->flags.erase( std::remove_if( ptr->flags.begin(), ptr->flags.end(), [this](auto flag) { return std::any_of( compilers_to_recognize_.flags_to_remove.begin(), compilers_to_recognize_.flags_to_remove.end(), [&flag](auto flag_to_remove) { return flag_to_remove == flag; }); }), ptr->flags.end()); // add flags which were asked to be added. std::copy(compilers_to_recognize_.flags_to_add.begin(), compilers_to_recognize_.flags_to_add.end(), std::back_inserter(ptr->flags)); } return semantic; }); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolExtendingWrapper.h000066400000000000000000000023671476774233700273360ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "ToolGcc.h" namespace cs::semantic { struct ToolExtendingWrapper : public ToolGcc { explicit ToolExtendingWrapper(CompilerWrapper &&compilers_to_recognize) noexcept; [[nodiscard]] bool is_compiler_call(const fs::path& program) const override; [[nodiscard]] rust::Result recognize(const Execution &execution) const override; private: CompilerWrapper compilers_to_recognize_; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolGcc.cc000066400000000000000000000414101476774233700246720ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolGcc.h" #include "Parsers.h" #include "Common.h" #include "libsys/Path.h" #include #include #include #include #include using namespace cs::semantic; namespace { // https://gcc.gnu.org/onlinedocs/cpp/Environment-Variables.html Arguments flags_from_environment(const std::map &environment) { Arguments flags; // define util function to append the content of a defined variable const auto &inserter = [&flags](const std::string& value, const std::string& flag) { // the variable value is a colon separated directory list for (const auto &path : sys::path::split(value)) { // If the expression was ":/opt/thing", that will split into two // entries. One which is an empty string and the path. Empty string // refers the current working directory. auto directory = (path.empty()) ? "." : path.string(); flags.push_back(flag); flags.push_back(directory); } }; // check the environment for preprocessor influencing variables for (const auto env : { "CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH" }) { if (const auto it = environment.find(env); it != environment.end()) { inserter(it->second, "-I"); } } if (const auto it = environment.find("OBJC_INCLUDE_PATH"); it != environment.end()) { inserter(it->second, "-isystem"); } return flags; } Arguments create_argument_list(const Execution &execution) { Arguments input_arguments; std::copy(execution.arguments.begin(), execution.arguments.end(), std::back_inserter(input_arguments)); Arguments extra_arguments = flags_from_environment(execution.environment); std::copy(extra_arguments.begin(), extra_arguments.end(), std::back_inserter(input_arguments)); return input_arguments; } bool is_prerpocessor(const CompilerFlags& flags) { // one of those make dependency generation also not count as compilation. // (will cause duplicate element, which is hard to detect.) static const std::set NO_COMPILATION_FLAG = { "-M", "-MM" }; return std::any_of(flags.begin(), flags.end(), [](const auto &flag) { const std::string &candidate = flag.arguments.front(); return ((flag.type == CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING) && (candidate == "-E")) || ((flag.type == CompilerFlagType::PREPROCESSOR_MAKE) && (NO_COMPILATION_FLAG.find(candidate) != NO_COMPILATION_FLAG.end())); }); } } namespace cs::semantic { const FlagsByName ToolGcc::FLAG_DEFINITION = { {"-x", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-c", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-S", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-E", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-o", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, {"-dumpbase", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-dumpbase-ext", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-dumpdir", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-v", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT}}, {"-###", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT}}, {"--help", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"--target-help", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"--version", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"-pass-exit-codes", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT}}, {"-pipe", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT}}, {"-specs", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::KIND_OF_OUTPUT}}, {"-wrapper", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-ffile-prefix-map", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::KIND_OF_OUTPUT}}, {"-fplugin", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::KIND_OF_OUTPUT}}, {"@", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT}}, {"-A", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-D", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-U", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-include", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-imacros", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR}}, {"-undef", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-pthread", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-M", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MM", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MG", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MP", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MD", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MMD", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MF", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MT", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MQ", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-C", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-CC", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-P", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-traditional", {MatchInstruction::PREFIX, CompilerFlagType::PREPROCESSOR}}, {"-trigraphs", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-remap", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-H", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-Xpreprocessor", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR}}, {"-Wp,", {MatchInstruction::PREFIX, CompilerFlagType::PREPROCESSOR}}, {"-I", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iplugindir", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iquote", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-isystem", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-idirafter", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iprefix", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iwithprefix", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iwithprefixbefore", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-isysroot", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-imultilib", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-L", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH_LINKER}}, {"-B", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"--sysroot", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::DIRECTORY_SEARCH}}, {"-flinker-output", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::LINKER}}, {"-fuse-ld", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::LINKER}}, {"-l", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::LINKER}}, {"-nostartfiles", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-nodefaultlibs", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-nolibc", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-nostdlib", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-e", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-entry", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::LINKER}}, {"-pie", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-no-pie", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-static-pie", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-r", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-rdynamic", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-s", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-symbolic", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-static", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-shared", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-T", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Xlinker", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Wl,", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-u", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-z", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Xassembler", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-Wa,", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-ansi", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-aux-info", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-std", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-O", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-g", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-f", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-m", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-p", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-W", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-no", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-tno", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-save", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-d", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-E", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-Q", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-X", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-Y", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"--", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, }; rust::Result ToolGcc::recognize(const Execution &execution) const { if (is_compiler_call(execution.executable)) { return compilation(execution); } return rust::Ok(SemanticPtr()); } bool ToolGcc::is_compiler_call(const fs::path& program) const { static const auto pattern = std::regex( // - cxx // - CC // - cc, mcc, gcc, c++, m++, g++, gfortran, fortran // - with prefixes like: arm-none-eabi- // - with postfixes like: -7.0 or 6.4.0 // - (excluding cc1) // - xgcc // - xg++ R"(^(cxx|CC|(([^-]*-)*(cc(?!1(?![\d\.]))|[mg]cc|[cmg]\+\+|[g]?fortran)(-?\d+(\.\d+){0,2})?)|xgcc|xg\+\+)$)" ); std::cmatch m; return std::regex_match(program.filename().c_str(), m, pattern); } rust::Result ToolGcc::compilation(const Execution &execution) const { return compilation(FLAG_DEFINITION, execution); } rust::Result ToolGcc::compilation(const FlagsByName& flags, const Execution& execution) { return compilation_impl(flags, execution, create_argument_list, is_prerpocessor); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolGcc.h000066400000000000000000000026231476774233700245370ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Tool.h" #include "Parsers.h" namespace cs::semantic { struct ToolGcc : public Tool { [[nodiscard]] rust::Result recognize(const Execution &execution) const override; protected: [[nodiscard]] virtual bool is_compiler_call(const fs::path& program) const; [[nodiscard]] virtual rust::Result compilation(const Execution &execution) const; [[nodiscard]] static rust::Result compilation(const FlagsByName &flags, const Execution &execution); static const FlagsByName FLAG_DEFINITION; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolIntelFortran.cc000066400000000000000000000246531476774233700266170ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolIntelFortran.h" #include "Common.h" #include "Parsers.h" #include "libsys/Path.h" #include #include #include #include #include using namespace cs::semantic; namespace { Arguments create_argument_list(const Execution& execution) { Arguments input_arguments; std::copy(execution.arguments.begin(), execution.arguments.end(), std::back_inserter(input_arguments)); return input_arguments; } bool is_preprocessor(const CompilerFlags& flags) { return std::any_of(flags.begin(), flags.end(), [](const auto& flag) { const std::string& candidate = flag.arguments.front(); static const std::set NO_COMPILATION_FLAG = { "-preprocess-only", "-P", "-E", "-Ep" }; return ((flag.type == CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING) && (NO_COMPILATION_FLAG.find(candidate) != NO_COMPILATION_FLAG.end())) || ((flag.type == CompilerFlagType::PREPROCESSOR_MAKE)); }); } } namespace cs::semantic { const FlagsByName ToolIntelFortran::FLAG_DEFINITION = { {"-c", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-S", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-E", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-Ep", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-preprocess-only", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-P", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_NO_LINKING}}, {"-o", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT_OUTPUT}}, {"-debug", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"-debug-parameters", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::KIND_OF_OUTPUT}}, {"@", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT}}, {"-Fa", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT}}, {"-FA", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT}}, {"-shared", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT}}, {"-dryrun", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"-dumpmachine", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"-v", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"-V", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"--help", {MatchInstruction::PREFIX, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"--version", {MatchInstruction::EXACTLY, CompilerFlagType::KIND_OF_OUTPUT_INFO}}, {"-D", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-U", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-include", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::PREPROCESSOR}}, {"-undef", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-pthread", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-MD", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MMD", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-MF", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-gen-dep", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::PREPROCESSOR_MAKE}}, {"-C", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-Xoption,cpp", {MatchInstruction::PREFIX, CompilerFlagType::PREPROCESSOR}}, {"-Xoption,fpp", {MatchInstruction::PREFIX, CompilerFlagType::PREPROCESSOR}}, {"-fpp", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-nofpp", {MatchInstruction::EXACTLY, CompilerFlagType::PREPROCESSOR}}, {"-Wp", {MatchInstruction::PREFIX, CompilerFlagType::PREPROCESSOR}}, {"-I", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-iquote", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-isystem", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-isysroot", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::DIRECTORY_SEARCH}}, {"-L", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::DIRECTORY_SEARCH_LINKER}}, {"--sysroot", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::DIRECTORY_SEARCH}}, {"-X", {MatchInstruction::EXACTLY, CompilerFlagType::DIRECTORY_SEARCH}}, {"-l", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::LINKER}}, {"-nostartfiles", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-nodefaultlibs", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-nostdlib", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-r", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-s", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-shared-intel", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"shared-libgcc", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-static", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-static-intel", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-static-libgcc", {MatchInstruction::EXACTLY, CompilerFlagType::LINKER}}, {"-T", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Xlinker", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Xoption,link", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-u", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::LINKER}}, {"-Wl", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-Xoption,asm", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-std", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-O", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-g", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-f", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-m", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-x", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-diag-", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-no", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-gen-interfaces", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-nogen-interfaces", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"--", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, }; rust::Result ToolIntelFortran::recognize(const Execution& execution) const { if (is_compiler_call(execution.executable)) { return compilation_impl(FLAG_DEFINITION, execution, create_argument_list, is_preprocessor); } return rust::Ok(SemanticPtr()); } bool ToolIntelFortran::is_compiler_call(const fs::path& program) const { static const auto pattern = std::regex(R"(^([^-]*-)*(ifx|ifort)(-?\w+(\.\d+){0,2})?$)"); std::cmatch m; return std::regex_match(program.filename().c_str(), m, pattern); } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolIntelFortran.h000066400000000000000000000021671476774233700264550ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Tool.h" #include "Parsers.h" namespace cs::semantic { struct ToolIntelFortran : public Tool { [[nodiscard]] rust::Result recognize(const Execution &execution) const override; protected: [[nodiscard]] bool is_compiler_call(const fs::path& program) const; static const FlagsByName FLAG_DEFINITION; }; } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolWrapper.cc000066400000000000000000000102611476774233700256160ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "ToolWrapper.h" #include "report/libexec/Resolver.h" #include "libsys/Errors.h" #include namespace { bool looks_like_ccache_parameter(const std::string &candidate) { return candidate.empty() || candidate[0] == '-'; } bool looks_like_distcc_parameter(const std::string &candidate) { static std::set flags = { "--help", "--version", "--show-hosts", "--scan-includes", "-j", "--show-principal" }; return candidate.empty() || (flags.find(candidate) != flags.end()); } } namespace cs::semantic { rust::Result ToolWrapper::recognize(const Execution &execution) const { if (is_ccache_call(execution.executable)) { return is_ccache_query(execution.arguments) ? rust::Ok(std::make_shared()) : compilation(remove_wrapper(execution)); } if (is_distcc_call(execution.executable)) { return is_distcc_query(execution.arguments) ? rust::Ok(std::make_shared()) : compilation(remove_wrapper(execution)); } return rust::Ok(SemanticPtr()); } bool ToolWrapper::is_ccache_call(const fs::path &program) { const auto string = program.filename().string(); return string == "ccache"; } bool ToolWrapper::is_ccache_query(const std::list &arguments) { if (arguments.size() <= 1) { return true; } if (const auto second = std::next(arguments.begin()); looks_like_ccache_parameter(*second)) { return true; } return false; } bool ToolWrapper::is_distcc_call(const fs::path &program) { const auto string = program.filename().string(); return string == "distcc"; } bool ToolWrapper::is_distcc_query(const std::list &arguments) { if (arguments.size() <= 1) { return true; } if (const auto second = std::next(arguments.begin()); looks_like_distcc_parameter(*second)) { return true; } return false; } domain::Execution ToolWrapper::remove_wrapper(const Execution &execution) { el::Resolver resolver; return remove_wrapper(resolver, execution); } domain::Execution ToolWrapper::remove_wrapper(el::Resolver &resolver, const Execution &execution) { auto environment = execution.environment; if (const auto path = environment.find("PATH"); path != environment.end()) { // take the second argument as a program candidate const auto program = std::next(execution.arguments.begin()); if (program != execution.arguments.end()) { // use resolver to get the full path to the executable. const auto candidate = resolver.from_search_path(*program, path->second.c_str()); if (candidate.is_ok()) { domain::Execution copy = execution; copy.arguments.pop_front(); copy.executable = candidate.unwrap(); return copy; } } } // fall back to the second argument as an executable. domain::Execution copy = execution; copy.arguments.pop_front(); copy.executable = copy.arguments.front(); return copy; } } rizsotto-Bear-14c2e01/source/citnames/source/semantic/ToolWrapper.h000066400000000000000000000030461476774233700254630ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "ToolGcc.h" #include #include namespace el { class Resolver; } namespace cs::semantic { struct ToolWrapper : public ToolGcc { [[nodiscard]] rust::Result recognize(const Execution &execution) const override; // visible for testing public: static bool is_ccache_call(const fs::path &program); static bool is_ccache_query(const std::list &arguments); static bool is_distcc_call(const fs::path &program); static bool is_distcc_query(const std::list &arguments); static domain::Execution remove_wrapper(const domain::Execution &); static domain::Execution remove_wrapper(el::Resolver &, const domain::Execution &); }; } rizsotto-Bear-14c2e01/source/citnames/test/000077500000000000000000000000001476774233700207055ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/citnames/test/OutputTest.cc000066400000000000000000000267211476774233700233640ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "Output.h" #include namespace { constexpr cs::Format AS_ARGUMENTS { true, false }; constexpr cs::Format AS_COMMAND { false, false }; constexpr cs::Format AS_ARGUMENTS_NO_OUTPUT { true, true }; constexpr cs::Format AS_COMMAND_NO_OUTPUT { false, true }; cs::Content DEFAULT_CONTENT{}; void value_serialized_and_read_back( const std::list& input, const std::list& expected, cs::Format format, cs::Content content = DEFAULT_CONTENT ) { cs::CompilationDatabase sut(format, content); std::stringstream buffer; auto serialized = sut.to_json(buffer, input); EXPECT_TRUE(serialized.is_ok()); std::list deserialized; auto count = sut.from_json(buffer, deserialized); EXPECT_TRUE(count.is_ok()); count.on_success([&expected, &deserialized](auto result) { EXPECT_EQ(expected.size(), result); EXPECT_EQ(expected, deserialized); }); } TEST(compilation_database, empty_value_serialized_and_read_back) { std::list expected = {}; value_serialized_and_read_back(expected, expected, AS_ARGUMENTS); value_serialized_and_read_back(expected, expected, AS_COMMAND); } TEST(compilation_database, same_entries_read_back) { std::list expected = { { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" } }, }; value_serialized_and_read_back(expected, expected, AS_ARGUMENTS); value_serialized_and_read_back(expected, expected, AS_COMMAND); } TEST(compilation_database, entries_without_output_read_back) { std::list input = { { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, { "entries.c", "/path/to", { "entries.o" }, { "cc", "-c", "-o", "entries.o", "entries.c" } }, }; std::list expected = { { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, { "entries.c", "/path/to", std::nullopt, { "cc", "-c", "-o", "entries.o", "entries.c" } }, }; value_serialized_and_read_back(input, expected, AS_ARGUMENTS_NO_OUTPUT); value_serialized_and_read_back(input, expected, AS_COMMAND_NO_OUTPUT); } TEST(compilation_database, merged_entries_read_back) { std::list input = { { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, { "entry_one.c", "/path/to", std::nullopt, { "cc1", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc1", "-c", "entry_two.c" } }, }; std::list expected = { { "entry_one.c", "/path/to", std::nullopt, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", std::nullopt, { "cc", "-c", "entry_two.c" } }, }; value_serialized_and_read_back(input, expected, AS_ARGUMENTS); value_serialized_and_read_back(input, expected, AS_COMMAND); value_serialized_and_read_back(input, expected, AS_ARGUMENTS_NO_OUTPUT); value_serialized_and_read_back(input, expected, AS_COMMAND_NO_OUTPUT); } TEST(compilation_database, duplicate_entries_file_read_back) { std::list input = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_one.c", "/path/to/changed", { "entry_one2.o" }, { "cc1", "-c", "-o", "entry_one2.o", "entry_one.c" } }, { "entry_two.c", "/path/to/changed", { "entry_two2.o" }, { "cc1", "-c", "-o", "entry_two2.o", "entry_two.c" } }, }; std::list expected = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, }; cs::Content content; content.duplicate_filter_fields = cs::DUPLICATE_FILE; value_serialized_and_read_back(input, expected, AS_ARGUMENTS, content); } TEST(compilation_database, duplicate_entries_file_output_read_back) { std::list input = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_one.c", "/path/to/changed", { "entry_one2.o" }, { "cc1", "-c", "-o", "entry_one2.o", "entry_one.c" } }, { "entry_two.c", "/path/to/changed", { "entry_two2.o" }, { "cc1", "-c", "-o", "entry_two2.o", "entry_two.c" } }, { "entry_one.c", "/path/to/changed", { "entry_one.o" }, { "cc1", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to/changed", { "entry_two.o" }, { "cc1", "-c", "entry_two.c" } }, }; std::list expected = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_one.c", "/path/to/changed", { "entry_one2.o" }, { "cc1", "-c", "-o", "entry_one2.o", "entry_one.c" } }, { "entry_two.c", "/path/to/changed", { "entry_two2.o" }, { "cc1", "-c", "-o", "entry_two2.o", "entry_two.c" } }, }; cs::Content content; content.duplicate_filter_fields = cs::DUPLICATE_FILE_OUTPUT; value_serialized_and_read_back(input, expected, AS_ARGUMENTS, content); } TEST(compilation_database, duplicate_entries_all_read_back) { std::list input = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_three.c", "/path/to", { "entry_three.o" }, { "cc", "-c", "entry_three.c" } }, // Filename changed { "entry_one.changed.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, // Output changed { "entry_two.c", "/path/to", { "entry_two_changed.o" }, { "cc", "-c", "entry_two.c" } }, // Flags changed { "entry_three.c", "/path/to", { "entry_three.o" }, { "cc", "-DCHANGED", "-c", "entry_three.c" } }, { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_three.c", "/path/to", { "entry_three.o" }, { "cc", "-c", "entry_three.c" } }, }; std::list expected = { { "entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_three.c", "/path/to", { "entry_three.o" }, { "cc", "-c", "entry_three.c" } }, { "entry_one.changed.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "entry_two.c", "/path/to", { "entry_two_changed.o" }, { "cc", "-c", "entry_two.c" } }, { "entry_three.c", "/path/to", { "entry_three.o" }, { "cc", "-DCHANGED", "-c", "entry_three.c" } }, }; cs::Content content; content.duplicate_filter_fields = cs::DUPLICATE_ALL; value_serialized_and_read_back(input, expected, AS_ARGUMENTS, content); } TEST(compilation_database, deserialize_fails_with_empty_stream) { cs::CompilationDatabase sut(AS_COMMAND, DEFAULT_CONTENT); std::stringstream buffer; std::list deserialized; auto count = sut.from_json(buffer, deserialized); EXPECT_FALSE(count.is_ok()); } TEST(compilation_database, deserialize_fails_with_missing_fields) { cs::CompilationDatabase sut(AS_COMMAND, DEFAULT_CONTENT); std::stringstream buffer; buffer << "[ { } ]"; std::list deserialized; auto count = sut.from_json(buffer, deserialized); EXPECT_FALSE(count.is_ok()); } TEST(compilation_database, deserialize_fails_with_empty_fields) { cs::CompilationDatabase sut(AS_COMMAND, DEFAULT_CONTENT); std::stringstream buffer; buffer << R"#([ { "file": "file.c", "directory": "", "command": "cc -c file.c" } ])#"; std::list deserialized; auto count = sut.from_json(buffer, deserialized); EXPECT_FALSE(count.is_ok()); } TEST(compilation_database, include_filter_works_with_trailing_slash) { std::list input = { { "/home/user/project/build/source/entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "/home/user/project/build/source/entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, { "/home/user/project/build/test/entry_one_test.c", "/path/to", { "entry_one_test.o" }, { "cc", "-c", "entry_one.c" } }, { "/home/user/project/build/test/entry_two_test.c", "/path/to", { "entry_two_test.o" }, { "cc", "-c", "entry_two.c" } }, }; std::list expected = { { "/home/user/project/build/source/entry_one.c", "/path/to", { "entry_one.o" }, { "cc", "-c", "entry_one.c" } }, { "/home/user/project/build/source/entry_two.c", "/path/to", { "entry_two.o" }, { "cc", "-c", "entry_two.c" } }, }; cs::Content content; content.paths_to_include = { fs::path("/home/user/project/build/source") }; content.paths_to_exclude = { fs::path("/home/user/project/build/test") }; value_serialized_and_read_back(input, expected, AS_ARGUMENTS, content); content.paths_to_include = { fs::path("/home/user/project/build/source/") }; content.paths_to_exclude = { fs::path("/home/user/project/build/test/") }; value_serialized_and_read_back(input, expected, AS_ARGUMENTS, content); } } rizsotto-Bear-14c2e01/source/citnames/test/ParserTest.cc000066400000000000000000000354461476774233700233240ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "semantic/Parsers.h" using namespace cs::semantic; namespace cs::semantic { std::ostream &operator<<(std::ostream &os, const CompilerFlag &value) { os << '['; for (const auto &v : value.arguments) { os << v << ','; } os << ']'; return os; } bool operator==(const CompilerFlag &lhs, const CompilerFlag &rhs) { return (lhs.arguments == rhs.arguments) && (lhs.type == rhs.type); } ArgumentsView slice(const Arguments &input, size_t start, size_t stop = 0) { const auto begin = std::next(input.begin(), start); const auto end = (stop < start) ? std::next(begin) : std::next(input.begin(), stop); return ArgumentsView(begin, end); } } namespace { TEST(Parser, EverythingElseFlagMatcher) { const auto sut = Repeat(EverythingElseFlagMatcher()); const Arguments input = {"compiler", "this", "is", "all", "parameter"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::LINKER_OBJECT_FILE}, CompilerFlag{slice(input, 2), CompilerFlagType::LINKER_OBJECT_FILE}, CompilerFlag{slice(input, 3), CompilerFlagType::LINKER_OBJECT_FILE}, CompilerFlag{slice(input, 4), CompilerFlagType::LINKER_OBJECT_FILE}, }; EXPECT_EQ(expected, flags.unwrap()); } TEST(Parser, SourceMatcher) { const auto sut = Repeat(SourceMatcher()); { const Arguments input = {"compiler", "source1.c", "source2.c", "source1.c"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::SOURCE}, CompilerFlag{slice(input, 2), CompilerFlagType::SOURCE}, CompilerFlag{slice(input, 3), CompilerFlagType::SOURCE}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "source1.f", "source2.f95", "source1.f08"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::SOURCE}, CompilerFlag{slice(input, 2), CompilerFlagType::SOURCE}, CompilerFlag{slice(input, 3), CompilerFlagType::SOURCE}, }; EXPECT_EQ(expected, flags.unwrap()); } } TEST(Parser, parse_flags_with_separate_options) { const FlagsByName flags_by_name = { {"-a", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-b", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-c", {MatchInstruction::EXACTLY_WITH_2_OPTS, CompilerFlagType::OTHER}}, {"-d", {MatchInstruction::EXACTLY_WITH_3_OPTS, CompilerFlagType::OTHER}}, }; const auto sut = Repeat(FlagParser(flags_by_name)); { const Arguments input = {"compiler", "-a", "-b", "op1", "-c", "op1", "op2", "-d", "op1", "op2", "op3"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 4, 7), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 7, 11), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-a", "op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-b"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-c", "op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-b", "op1", "op2"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } } TEST(Parser, parse_flags_with_glued_options) { const FlagsByName flags_by_name = { {"-a", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-b", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ, CompilerFlagType::OTHER}}, {"-c", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_EQ_OR_SEP, CompilerFlagType::OTHER}}, {"-d", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED, CompilerFlagType::OTHER}}, {"-e", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::OTHER}}, {"-f", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_WITH_OR_WITHOUT_EQ_OR_SEP, CompilerFlagType::OTHER}}, }; const auto sut = Repeat(FlagParser(flags_by_name)); { const Arguments input = {"compiler", "-a", "op1", "-c", "op1", "-e", "op1", "-f", "op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1, 3), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 3, 5), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 5, 7), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 7, 9), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-b=op1", "-c=op1", "-f=op1" }; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 3), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-dop1", "-eop1", "-fop1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 3), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-aopt1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-a=opt1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-b", "opt1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-a"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } } TEST(Parser, parse_flags_with_partial_matches) { const FlagsByName flags_by_name = { {"-a", {MatchInstruction::PREFIX, CompilerFlagType::OTHER}}, {"-b", {MatchInstruction::PREFIX_WITH_1_OPT, CompilerFlagType::OTHER}}, {"-c", {MatchInstruction::PREFIX_WITH_2_OPTS, CompilerFlagType::OTHER}}, {"-d", {MatchInstruction::PREFIX_WITH_3_OPTS, CompilerFlagType::OTHER}}, }; const auto sut = Repeat(FlagParser(flags_by_name)); { const Arguments input = {"compiler", "-a", "-b", "op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-alice", "-bob", "op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-cecil", "opt1", "opt2", "-dave", "opt1", "opt2", "opt3"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1, 4), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 4, 8), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-alice=op1", "-bob=op1", "op2" }; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-f=op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } { const Arguments input = {"compiler", "-a=op1"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } } TEST(Parser, parse_flags_with_common_prefixes) { const FlagsByName flags_by_name = { {"-a", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-l", {MatchInstruction::EXACTLY_WITH_1_OPT_GLUED_OR_SEP, CompilerFlagType::LINKER}}, {"-language", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-linker", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, {"-z", {MatchInstruction::EXACTLY, CompilerFlagType::OTHER}}, }; const auto sut = Repeat(FlagParser(flags_by_name)); { const Arguments input = {"compiler", "-library", "-language", "c"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::LINKER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-language", "c", "-library"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1, 3), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 3), CompilerFlagType::LINKER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-linker", "-lthing",}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 2), CompilerFlagType::LINKER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-l", "m", "-link", "-linker", "-lexec"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1, 3), CompilerFlagType::LINKER}, CompilerFlag{slice(input, 3), CompilerFlagType::LINKER}, CompilerFlag{slice(input, 4), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 5), CompilerFlagType::LINKER}, }; EXPECT_EQ(expected, flags.unwrap()); } { const Arguments input = {"compiler", "-l=thing"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_err()); } } TEST(Parser, parse_flags_with_several_suitable_prefixes) { const FlagsByName flags_by_name = { {"-l", {MatchInstruction::PREFIX, CompilerFlagType::LINKER}}, {"-language", {MatchInstruction::EXACTLY_WITH_1_OPT_SEP, CompilerFlagType::OTHER}}, {"-linker", {MatchInstruction::PREFIX_WITH_2_OPTS, CompilerFlagType::OTHER}}, }; const auto sut = Repeat(FlagParser(flags_by_name)); { const Arguments input = {"compiler", "-lin", "-language", "s", "-linkeriasds", "opt1", "opt2"}; const auto flags = parse(sut, input); EXPECT_TRUE(flags.is_ok()); const CompilerFlags expected = { CompilerFlag{slice(input, 1), CompilerFlagType::LINKER}, CompilerFlag{slice(input, 2, 4), CompilerFlagType::OTHER}, CompilerFlag{slice(input, 4, 7), CompilerFlagType::OTHER}, }; EXPECT_EQ(expected, flags.unwrap()); } } } rizsotto-Bear-14c2e01/source/citnames/test/ToolClangTest.cc000066400000000000000000000223051476774233700237400ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "semantic/Tool.h" #include "semantic/ToolClang.h" using namespace cs::semantic; namespace { TEST(ToolClang, is_compiler_call) { struct Expose : public ToolClang { using ToolClang::is_compiler_call; }; Expose sut; EXPECT_TRUE(sut.is_compiler_call("clang")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/clang")); EXPECT_TRUE(sut.is_compiler_call("clang++")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/clang++")); EXPECT_TRUE(sut.is_compiler_call("clang-6")); EXPECT_TRUE(sut.is_compiler_call("clang6")); EXPECT_TRUE(sut.is_compiler_call("clang-8.1")); EXPECT_TRUE(sut.is_compiler_call("clang8.1")); EXPECT_TRUE(sut.is_compiler_call("clang81")); EXPECT_TRUE(sut.is_compiler_call("flang")); EXPECT_TRUE(sut.is_compiler_call("flang-20")); EXPECT_TRUE(sut.is_compiler_call("flang-new")); EXPECT_TRUE(sut.is_compiler_call("flang-new-18")); } TEST(ToolClang, simple) { const Execution input = { "/usr/bin/clang", {"clang", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, linker_flag_filtered) { const Execution input = { "/usr/bin/clang", {"clang", "-L.", "-lthing", "-o", "exe", "source.c"}, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("exe")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_version) { const Execution input = { "/usr/bin/clang", {"clang", "--version"}, "/home/user/project", {}, }; const QueryCompiler expected; ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_Xclang) { const Execution input = { "/usr/bin/clang", { "clang", "-c", "-o", "source.o", "source.c", "-Xclang", "-load", "-Xclang", "/path/to/LLVMHello.so" }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-Xclang", "-load", "-Xclang", "/path/to/LLVMHello.so"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_Xarch) { const Execution input = { "/usr/bin/clang", { "clang", "-c", "-o", "source.o", "source.c", "-Xarch_arg1", "arg2", "-Xarch_device", "device1", "-Xarch_host", "host1" }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-Xarch_arg1", "arg2", "-Xarch_device", "device1", "-Xarch_host", "host1"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_Xcuda) { const Execution input = { "/usr/bin/clang", { "clang", "-c", "-o", "source.o", "source.c", "-Xcuda-fatbinary", "arg1", "-Xcuda-ptxas", "arg2" }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-Xcuda-fatbinary", "arg1", "-Xcuda-ptxas", "arg2"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_Xopenmp) { const Execution input = { "/usr/bin/clang", { "clang", "-c", "-o", "source.o", "source.c", "-Xopenmp-target", "arg1", "-Xopenmp-target=arg1", "arg2" }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-Xopenmp-target", "arg1", "-Xopenmp-target=arg1", "arg2"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_analyze) { const Execution input = { "/usr/bin/clang", { "clang", "-c", "-o", "source.o", "source.c", "-Z", "arg1", "-aargs", "--analyze" }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-Z", "arg1", "-aargs", "--analyze"}, {fs::path("source.c")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } TEST(ToolClang, pass_on_fintrinsic_modules_path) { const Execution input = { "/usr/bin/flang", { "flang", "-c", "-o", "source.o", "source.f90", "-fintrinsic-modules-path", "arg1", }, "/home/user/project", {}, }; const Compile expected( input.working_dir, input.executable, {"-c", "-fintrinsic-modules-path", "arg1"}, {fs::path("source.f90")}, {fs::path("source.o")} ); ToolClang sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_EQ(expected, *(result.unwrap().get())); } } rizsotto-Bear-14c2e01/source/citnames/test/ToolCrayFtnfeTest.cc000066400000000000000000000067651476774233700246110ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "semantic/ToolCrayFtnfe.h" using namespace cs::semantic; namespace { TEST(ToolCrayFtnfe, is_compiler_call) { struct Expose : public ToolCrayFtnfe { using ToolCrayFtnfe::is_compiler_call; }; Expose sut; EXPECT_TRUE(sut.is_compiler_call("ftnfe")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/ftnfe")); EXPECT_TRUE(sut.is_compiler_call("/opt/cray/pe/cce/18.0.0/cce/x86_64/bin/ftnfe")); EXPECT_FALSE(sut.is_compiler_call("gfortran")); EXPECT_FALSE(sut.is_compiler_call("gcc")); // `crayftn` and `ftn` are not the real Cray Fortran compiler. `crayftn` // and `ftn` are generic drivers that may call other compilers depending // on the configuration of the system. The real Cray Fortran compiler is // `ftnfe`! EXPECT_FALSE(sut.is_compiler_call("/opt/cray/pe/cce/18.0.0/bin/crayftn")); EXPECT_FALSE(sut.is_compiler_call("/opt/cray/pe/craype/2.7.32/bin/ftn")); EXPECT_FALSE(sut.is_compiler_call("crayftn")); EXPECT_FALSE(sut.is_compiler_call("ftn")); } TEST(ToolCrayFtnfe, fails_on_empty) { ToolCrayFtnfe sut; EXPECT_TRUE(Tool::not_recognized(sut.recognize(Execution {}))); } TEST(ToolCrayFtnfe, simple) { Execution input = { "/opt/cray/pe/cce/18.0.0/cce/x86_64/bin/ftnfe", { "ftnfe", "-b", "source_out.o", "-r", "file.listing", "source.c" }, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, { "-c", "-r", "file.listing" }, { fs::path("source.c") }, { fs::path("source_out.o") })); ToolCrayFtnfe sut; auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolCrayFtnfe, linker_flag_filtered) { Execution input = { "/opt/cray/pe/cce/18.0.0/cce/x86_64/bin/ftnfe", { "ftnfe", "-L.", "-lthing", "-o", "exe", "source.c" }, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, { "-c" }, { fs::path("source.c") }, { fs::path("exe") })); ToolCrayFtnfe sut; auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } } rizsotto-Bear-14c2e01/source/citnames/test/ToolGccTest.cc000066400000000000000000000124101476774233700234040ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "semantic/Tool.h" #include "semantic/ToolGcc.h" using namespace cs::semantic; namespace { TEST(ToolGcc, is_compiler_call) { struct Expose : public ToolGcc { using ToolGcc::is_compiler_call; }; Expose sut; EXPECT_TRUE(sut.is_compiler_call("cc")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/cc")); EXPECT_TRUE(sut.is_compiler_call("x86_64-pc-linux-gnu-cc")); EXPECT_FALSE(sut.is_compiler_call("cc1")); EXPECT_TRUE(sut.is_compiler_call("gcc")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/gcc")); EXPECT_TRUE(sut.is_compiler_call("c++")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/c++")); EXPECT_TRUE(sut.is_compiler_call("x86_64-pc-linux-gnu-c++")); EXPECT_TRUE(sut.is_compiler_call("g++")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/g++")); EXPECT_TRUE(sut.is_compiler_call("arm-none-eabi-g++")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/arm-none-eabi-g++")); EXPECT_TRUE(sut.is_compiler_call("gcc-6")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/gcc-6")); EXPECT_TRUE(sut.is_compiler_call("gfortran")); EXPECT_TRUE(sut.is_compiler_call("fortran")); } TEST(ToolGcc, fails_on_empty) { Execution input = {}; ToolGcc sut; EXPECT_TRUE(Tool::not_recognized(sut.recognize(input))); } TEST(ToolGcc, simple) { Execution input = { "/usr/bin/cc", {"cc", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("source.o")}) ); ToolGcc sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolGcc, linker_flag_filtered) { Execution input = { "/usr/bin/cc", {"cc", "-L.", "-lthing", "-o", "exe", "source.c"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("exe")} ) ); ToolGcc sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolGcc, pass_on_help) { Execution input = { "/usr/bin/gcc", {"gcc", "--version"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr(new QueryCompiler()); ToolGcc sut({}); auto result = sut.recognize(input); EXPECT_TRUE(result.is_ok()); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolGcc, simple_with_C_PATH) { Execution input = { "/usr/bin/cc", {"cc", "-c", "source.c"}, "/home/user/project", {{"CPATH", "/usr/include/path1:/usr/include/path2"}, {"C_INCLUDE_PATH", ":/usr/include/path3"}}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, { "-c", "-I", "/usr/include/path1", "-I", "/usr/include/path2", "-I", ".", "-I", "/usr/include/path3", }, {fs::path("source.c")}, std::nullopt ) ); ToolGcc sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } } rizsotto-Bear-14c2e01/source/citnames/test/ToolIntelFortranTest.cc000066400000000000000000000074641476774233700253340ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "semantic/Tool.h" #include "semantic/ToolIntelFortran.h" using namespace cs::semantic; namespace { TEST(ToolIntelFortran, is_compiler_call) { struct Expose : public ToolIntelFortran { using ToolIntelFortran::is_compiler_call; }; Expose sut; EXPECT_TRUE(sut.is_compiler_call("ifx")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/ifx")); EXPECT_TRUE(sut.is_compiler_call("ifort")); EXPECT_TRUE(sut.is_compiler_call("/usr/bin/ifort")); EXPECT_TRUE(sut.is_compiler_call("/opt/intel/oneapi/compiler/2025.0/bin/ifx")); EXPECT_TRUE(sut.is_compiler_call("ifx2023")); EXPECT_TRUE(sut.is_compiler_call("ifx2025.0")); EXPECT_TRUE(sut.is_compiler_call("ifx-avx2")); EXPECT_FALSE(sut.is_compiler_call("gfortran")); EXPECT_FALSE(sut.is_compiler_call("gcc")); } TEST(ToolIntelFortran, fails_on_empty) { Execution input = {}; ToolIntelFortran sut; EXPECT_TRUE(Tool::not_recognized(sut.recognize(input))); } TEST(ToolIntelFortran, simple) { Execution input = { "/opt/intel/oneapi/compiler/2025.0/bin/ifx", {"ifx", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("source.o")}) ); ToolIntelFortran sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolIntelFortran, linker_flag_filtered) { Execution input = { "/opt/intel/oneapi/compiler/2025.0/bin/ifx", {"ifx", "-L.", "-lthing", "-o", "exe", "source.c"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr( new Compile( input.working_dir, input.executable, {"-c"}, {fs::path("source.c")}, {fs::path("exe")} ) ); ToolIntelFortran sut({}); auto result = sut.recognize(input); EXPECT_TRUE(Tool::recognized_ok(result)); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } TEST(ToolIntelFortran, pass_on_help) { Execution input = { "/opt/intel/oneapi/compiler/2025.0/bin/ifx", {"ifx", "--version"}, "/home/user/project", {}, }; SemanticPtr expected = SemanticPtr(new QueryCompiler()); ToolIntelFortran sut({}); auto result = sut.recognize(input); EXPECT_TRUE(result.is_ok()); EXPECT_PRED2([](auto lhs, auto rhs) { return lhs->operator==(*rhs); }, expected, result.unwrap()); } } rizsotto-Bear-14c2e01/source/citnames/test/ToolWrapperTest.cc000066400000000000000000000123631476774233700243370ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "gmock/gmock.h" #include "semantic/Tool.h" #include "semantic/ToolWrapper.h" #include "report/libexec/Resolver.h" using namespace cs::semantic; using ::testing::_; using ::testing::Eq; using ::testing::Return; namespace { class ResolverMock : public el::Resolver { public: MOCK_METHOD( (rust::Result), from_current_directory, (std::string_view const &), (override) ); MOCK_METHOD( (rust::Result), from_path, (std::string_view const &, const char **), (override) ); MOCK_METHOD( (rust::Result), from_search_path, (std::string_view const &, const char *), (override) ); }; TEST(ToolWrapper, is_ccache_call) { EXPECT_FALSE(ToolWrapper::is_ccache_call("cc")); EXPECT_FALSE(ToolWrapper::is_ccache_call("/usr/bin/cc")); EXPECT_FALSE(ToolWrapper::is_ccache_call("gcc")); EXPECT_FALSE(ToolWrapper::is_ccache_call("/usr/bin/gcc")); EXPECT_FALSE(ToolWrapper::is_ccache_call("c++")); EXPECT_FALSE(ToolWrapper::is_ccache_call("/usr/bin/c++")); EXPECT_FALSE(ToolWrapper::is_ccache_call("g++")); EXPECT_FALSE(ToolWrapper::is_ccache_call("/usr/bin/g++")); EXPECT_TRUE(ToolWrapper::is_ccache_call("ccache")); } TEST(ToolWrapper, is_ccache_query) { EXPECT_TRUE(ToolWrapper::is_ccache_query({"ccache"})); EXPECT_TRUE(ToolWrapper::is_ccache_query({"ccache", "-c"})); EXPECT_TRUE(ToolWrapper::is_ccache_query({"ccache", "--cleanup"})); EXPECT_FALSE(ToolWrapper::is_ccache_query({"ccache", "cc", "-c"})); } TEST(ToolWrapper, is_distcc_call) { EXPECT_FALSE(ToolWrapper::is_distcc_call("cc")); EXPECT_FALSE(ToolWrapper::is_distcc_call("/usr/bin/cc")); EXPECT_FALSE(ToolWrapper::is_distcc_call("gcc")); EXPECT_FALSE(ToolWrapper::is_distcc_call("/usr/bin/gcc")); EXPECT_FALSE(ToolWrapper::is_distcc_call("c++")); EXPECT_FALSE(ToolWrapper::is_distcc_call("/usr/bin/c++")); EXPECT_FALSE(ToolWrapper::is_distcc_call("g++")); EXPECT_FALSE(ToolWrapper::is_distcc_call("/usr/bin/g++")); EXPECT_TRUE(ToolWrapper::is_distcc_call("distcc")); } TEST(ToolWrapper, is_distcc_query) { EXPECT_TRUE(ToolWrapper::is_ccache_query({"distcc"})); EXPECT_TRUE(ToolWrapper::is_ccache_query({"distcc", "--help"})); EXPECT_TRUE(ToolWrapper::is_ccache_query({"distcc", "--show-hosts"})); EXPECT_TRUE(ToolWrapper::is_ccache_query({"distcc", "-j"})); EXPECT_FALSE(ToolWrapper::is_ccache_query({"distcc", "cc", "--help"})); EXPECT_FALSE(ToolWrapper::is_ccache_query({"distcc", "cc", "-c"})); } TEST(ToolWrapper, remove_wrapper) { const Execution input = { "/usr/bin/ccache", {"ccache", "cc", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {{"PATH", "/usr/bin:/usr/sbin"}}, }; const Execution expected = { "/usr/bin/cc", {"cc", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {{"PATH", "/usr/bin:/usr/sbin"}}, }; ResolverMock resolver; EXPECT_CALL(resolver, from_search_path(Eq(std::string_view("cc")), _)) .Times(1) .WillOnce(Return(rust::Result(rust::Ok("/usr/bin/cc")))); auto result = ToolWrapper::remove_wrapper(resolver, input); EXPECT_EQ(expected, result); } TEST(ToolWrapper, remove_wrapper_fails_to_resolve) { const Execution input = { "/usr/bin/ccache", {"ccache", "cc", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {{"PATH", "/usr/bin:/usr/sbin"}}, }; const Execution expected = { "cc", {"cc", "-c", "-o", "source.o", "source.c"}, "/home/user/project", {{"PATH", "/usr/bin:/usr/sbin"}}, }; ResolverMock resolver; EXPECT_CALL(resolver, from_search_path(Eq(std::string_view("cc")), _)) .Times(1) .WillOnce(Return(rust::Result(rust::Err(12)))); auto result = ToolWrapper::remove_wrapper(resolver, input); EXPECT_EQ(expected, result); } } rizsotto-Bear-14c2e01/source/config.h.in000066400000000000000000000122611476774233700201500ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once // specific platform indicator, should not using like this. (needs to be deprecated) #cmakedefine SUPPORT_PRELOAD #cmakedefine SUPPORT_MULTILIB // header checks #cmakedefine HAVE_SPAWN_H #cmakedefine HAVE_UNISTD_H #cmakedefine HAVE_DLFCN_H #cmakedefine HAVE_ERRNO_H #cmakedefine HAVE_SYS_UTSNAME_H #cmakedefine HAVE_SYS_WAIT_H #cmakedefine HAVE_SYS_TIME_H #cmakedefine HAVE_SYS_STAT_H #cmakedefine HAVE_GNU_LIB_NAMES_H // OSX specific (needs to be deprecated) #cmakedefine HAVE_NSGETENVIRON // dynamic linker #cmakedefine HAVE_DLOPEN #cmakedefine HAVE_DLSYM #cmakedefine HAVE_DLERROR #cmakedefine HAVE_DLCLOSE #cmakedefine HAVE_RTLD_NEXT // errno values #cmakedefine HAVE_EACCES #cmakedefine HAVE_ENOENT #cmakedefine HAVE_UNAME // string.h function #cmakedefine HAVE_STRERROR_R // unistd.h might declare the environ symbol #cmakedefine HAVE_ENVIRON // confstr and macros #cmakedefine HAVE_CONFSTR #cmakedefine HAVE_CS_PATH #cmakedefine HAVE_CS_GNU_LIBC_VERSION #cmakedefine HAVE_CS_GNU_LIBPTHREAD_VERSION // fmt version #cmakedefine HAVE_FMT_STD_H #cmakedefine FMT_NEEDS_OSTREAM_FORMATTER // macros to disable compiler generated methods #define NON_DEFAULT_CONSTRUCTABLE(T) \ T() noexcept = delete; #define NON_COPYABLE_NOR_MOVABLE(T) \ T(T const &) = delete; \ T& operator=(T const &) = delete; \ T(T &&) noexcept = delete; \ T& operator=(T &&) noexcept = delete; // const expression about this project namespace cmd { constexpr char VERSION[] = "@CMAKE_PROJECT_VERSION@"; namespace bear { constexpr char DEFAULT_PATH[] = "@ROOT_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/bear"; constexpr char FLAG_BEAR[] = "--bear-path"; } namespace citnames { constexpr char FLAG_INPUT[] = "--input"; constexpr char FLAG_OUTPUT[] = "--output"; constexpr char FLAG_APPEND[] = "--append"; constexpr char FLAG_RUN_CHECKS[] = "--run-checks"; constexpr char FLAG_CONFIG[] = "--config"; constexpr char DEFAULT_OUTPUT[] = "compile_commands.json"; } namespace intercept { constexpr char FLAG_OUTPUT[] = "--output"; constexpr char FLAG_LIBRARY[] = "--library"; constexpr char FLAG_WRAPPER[] = "--wrapper"; constexpr char FLAG_WRAPPER_DIR[] = "--wrapper-dir"; constexpr char FLAG_COMMAND[] = "--"; constexpr char FLAG_FORCE_WRAPPER[] = "--force-wrapper"; constexpr char FLAG_FORCE_PRELOAD[] = "--force-preload"; constexpr char DEFAULT_OUTPUT[] = "events.json"; } namespace wrapper { constexpr char DEFAULT_PATH[] = "@ROOT_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/bear/wrapper"; constexpr char DEFAULT_DIR_PATH[] = "@ROOT_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/bear/wrapper.d"; constexpr char FLAG_VERBOSE[] = "--verbose"; constexpr char FLAG_DESTINATION[] = "--destination"; constexpr char FLAG_EXECUTE[] = "--execute"; constexpr char FLAG_COMMAND[] = "--"; constexpr char KEY_DESTINATION[] = "INTERCEPT_REPORT_DESTINATION"; constexpr char KEY_VERBOSE[] = "INTERCEPT_VERBOSE"; } namespace library { #ifdef SUPPORT_MULTILIB // Multilib is one of the solutions allowing users to run applications // built for various application binary interfaces (ABIs) of the same // architecture. The most common use of multilib is to run 32-bit // applications on 64-bit kernel. // // For Linux, a small tune is needed at build time. Need to compile // `libexec.so` library for 32-bit and for 64-bit too. Then install // these libraries to the OS preferred multilib directories. // And use the `libexec.so` path default value with a single path, // that matches both. (The match can be achieved by the $LIB token // expansion from the dynamic loader. See `man ld.so` for more.) constexpr char DEFAULT_PATH[] = "@ROOT_INSTALL_PREFIX@/$LIB/bear/@CMAKE_SHARED_LIBRARY_PREFIX@exec@CMAKE_SHARED_LIBRARY_SUFFIX@"; #else constexpr char DEFAULT_PATH[] = "@ROOT_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/bear/@CMAKE_SHARED_LIBRARY_PREFIX@exec@CMAKE_SHARED_LIBRARY_SUFFIX@"; #endif constexpr char KEY_REPORTER[] = "INTERCEPT_REPORT_COMMAND"; constexpr char KEY_DESTINATION[] = "INTERCEPT_REPORT_DESTINATION"; constexpr char KEY_VERBOSE[] = "INTERCEPT_VERBOSE"; } } constexpr char OS_DIR_SEPARATOR = '/'; constexpr char OS_PATH_SEPARATOR = ':'; rizsotto-Bear-14c2e01/source/intercept/000077500000000000000000000000001476774233700201205ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/CMakeLists.txt000066400000000000000000000154261476774233700226700ustar00rootroot00000000000000include(GNUInstallDirs) # RPC related types and stubs. add_subdirectory(proto) # Domain types and methods for intercept artefacts. add_library(domain_a OBJECT) target_include_directories(domain_a PRIVATE source) target_sources(domain_a PRIVATE source/Domain.cc source/Convert.cc INTERFACE $) target_link_libraries(domain_a PUBLIC rpc_a) # Event database writing/reading classes. add_library(events_db_a OBJECT) target_include_directories(events_db_a PUBLIC source) target_sources(events_db_a PRIVATE source/collect/db/EventsDatabaseReader.cc source/collect/db/EventsDatabaseWriter.cc INTERFACE $) target_link_libraries(events_db_a PUBLIC result_a rpc_a sys_a fmt::fmt) # Intercept orchestrator code. add_library(intercept_a OBJECT) target_include_directories(intercept_a PRIVATE source/ include/) if (SUPPORT_PRELOAD) target_sources(intercept_a PRIVATE source/collect/SessionLibrary.cc) endif() target_sources(intercept_a PRIVATE source/collect/Intercept.cc source/collect/Reporter.cc source/collect/RpcServices.cc source/collect/Session.cc source/collect/SessionWrapper.cc) target_sources(intercept_a INTERFACE $) target_link_libraries(intercept_a PUBLIC domain_a main_a events_db_a exec_a flags_a rpc_a sys_a result_a spdlog::spdlog) # Markdown file is the source to the man file. Please modify that and generate # the man file from it with pandoc. # # $ pandoc -s -t man bear-intercept.1.md -o bear-intercept.1 # # This is not automated, because pandoc has big dependencies on different OS # distributions and packaging would require to install those. Which might be # too much effort to generate a single text file. install(FILES man/bear-intercept.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) # Intercept report sender code. add_library(wrapper_a OBJECT) target_include_directories(wrapper_a PUBLIC source/) target_sources(wrapper_a PRIVATE source/report/wrapper/EventFactory.cc source/report/wrapper/EventReporter.cc source/report/wrapper/RpcClients.cc source/report/wrapper/Application.cc INTERFACE $) target_link_libraries(wrapper_a PUBLIC domain_a flags_a main_a result_a sys_a rpc_a spdlog::spdlog) # Intercept report sender executable. add_executable(wrapper source/report/wrapper/main.cc) target_include_directories(wrapper PUBLIC source/) target_link_libraries(wrapper main_a wrapper_a) install(TARGETS wrapper RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/bear) install(DIRECTORY DESTINATION ${CMAKE_INSTALL_LIBDIR}/bear/wrapper.d) install(CODE " execute_process( COMMAND ln -sf ../wrapper ar COMMAND ln -sf ../wrapper as COMMAND ln -sf ../wrapper cc COMMAND ln -sf ../wrapper c++ COMMAND ln -sf ../wrapper cpp COMMAND ln -sf ../wrapper gcc COMMAND ln -sf ../wrapper g++ COMMAND ln -sf ../wrapper clang COMMAND ln -sf ../wrapper clang++ COMMAND ln -sf ../wrapper f77 COMMAND ln -sf ../wrapper flang COMMAND ln -sf ../wrapper flang-new COMMAND ln -sf ../wrapper ftnfe COMMAND ln -sf ../wrapper gfortran COMMAND ln -sf ../wrapper ifx COMMAND ln -sf ../wrapper ifort COMMAND ln -sf ../wrapper m2c COMMAND ln -sf ../wrapper pc COMMAND ln -sf ../wrapper lex COMMAND ln -sf ../wrapper flex COMMAND ln -sf ../wrapper yacc COMMAND ln -sf ../wrapper bison COMMAND ln -sf ../wrapper lint COMMAND ln -sf ../wrapper makeinfo COMMAND ln -sf ../wrapper tex COMMAND ln -sf ../wrapper tex2dvi COMMAND ln -sf ../wrapper weave COMMAND ln -sf ../wrapper cweave COMMAND ln -sf ../wrapper tangle COMMAND ln -sf ../wrapper ctangle COMMAND ln -sf ../wrapper nm COMMAND ln -sf ../wrapper ld COMMAND ln -sf ../wrapper strip COMMAND ln -sf ../wrapper objcopy COMMAND ln -sf ../wrapper objdump COMMAND ln -sf ../wrapper ranlib COMMAND ln -sf ../wrapper readelf WORKING_DIRECTORY ${DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/bear/wrapper.d ) ") # Execution interceptor code. add_library(exec_a OBJECT) target_include_directories(exec_a PUBLIC source) target_sources(exec_a PRIVATE source/report/libexec/Buffer.cc source/report/libexec/Environment.cc source/report/libexec/Executor.cc source/report/libexec/Linker.cc source/report/libexec/Logger.cc source/report/libexec/Paths.cc source/report/libexec/Resolver.cc source/report/libexec/Session.cc INTERFACE $) target_link_libraries(exec_a PUBLIC ${CMAKE_DL_LIBS} result_a) set_target_properties(exec_a PROPERTIES LINKER_LANGUAGE "C" POSITION_INDEPENDENT_CODE 1) # Execution interceptor shared library. add_library(exec SHARED source/report/libexec/lib.cc source/report/libexec/std.cc ) target_link_libraries(exec exec_a) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set_target_properties(exec PROPERTIES LINKER_LANGUAGE "C" CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "" LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libexec.version" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") set_target_properties(exec PROPERTIES LINKER_LANGUAGE "CXX" OSX_ARCHITECTURES:STRING "i386;x86_64" MACOSX_RPATH:BOOL ON ) endif () install(TARGETS exec LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/bear) # Create unit test. if (ENABLE_UNIT_TESTS) add_executable(exec_unit_test test/libexec/ArrayTest.cc test/libexec/BufferTest.cc test/libexec/ExecutorTest.cc test/libexec/PathsTest.cc test/libexec/SessionTest.cc ) target_include_directories(exec_unit_test PRIVATE test) target_link_libraries(exec_unit_test exec_a) target_link_libraries(exec_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::exec_unit_test COMMAND $) add_executable(intercept_unit_test test/EventFactoryTest.cc test/SessionTest.cc ) target_link_libraries(intercept_unit_test intercept_a) target_link_libraries(intercept_unit_test wrapper_a) target_link_libraries(intercept_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::intercept_unit_test COMMAND $) endif () rizsotto-Bear-14c2e01/source/intercept/include/000077500000000000000000000000001476774233700215435ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/include/intercept/000077500000000000000000000000001476774233700235405ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/include/intercept/intercept-forward.h000066400000000000000000000021711476774233700273510ustar00rootroot00000000000000/* Copyright (C) 2023 by Samu698 This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libmain/SubcommandFromArgs.h" namespace ic { struct Intercept : ps::SubcommandFromArgs { Intercept(const ps::ApplicationLogConfig&) noexcept; rust::Result command(const flags::Arguments &args, const char **envp) const override; NON_DEFAULT_CONSTRUCTABLE(Intercept) }; } rizsotto-Bear-14c2e01/source/intercept/intercept-library.md000066400000000000000000000060741476774233700241100ustar00rootroot00000000000000# Intercept Library This sub project implements intercept logic which is using the operating system [dynamic loader][DYN_LOADER] pre-load functionality. [DYN_LOADER]: https://en.wikipedia.org/wiki/Dynamic_linker ## When to use this way of intercepting? This method of intercepting compiler calls can work only if the following conditions are met: - The operating system is supported by this code. (Linux, FreeBSD, OSX were tried and tested) - The operating system do support (or enable) library preload. (Notable example for enabled security modes is the [SIP][OSX_SIP] in recent OSX versions.) - Executables which are linked dynamically. (Few distribution does ship statically linked compilers, which will be not working this method.) [OSX_SIP]: https://support.apple.com/en-us/HT204899 ## How it works? This project implements a shared library and a statically linked executable. The library can be pre-loaded by the dynamic linker of the Operating System. It implements a few function related to process creation. By pre-load this library the executed process uses these functions instead of those from the standard library. The idea here is to hijack the process creation methods: do not execute the requested file, but execute another one. The another process is a supervisor process, which executes the requested file, but it also reports the lifecycle related events, like start, stop or signal received. ## Limitations * If a process can be executed it will execute it. But if the execution request would fail for some reason, it might report a successful execution because the statically linked executable will start (but the child process might fail). The type of errors that are might not detected by the library are: `E2BIG`, `EAGAIN`, `EINVAL`, `EIO`, `ELIBAD`, `ELOOP`, `ENFILE`, `ENAMETOOLONG`, `ENOEXEC` and `EPERM`. * The IO redirection might not working properly. Since the requested execution is not a direct child process (indirect child process relationship) the standard input/output might not be closed/forwarded as requested. * `posix_spawn` and `posix_spawn` some of the attributes might not making effect, because the indirect child process relationship. * There are still a few POSIX system call that are not covered by the library. (Like `execveat` or `fexecve` which are using file descriptor to identify the file to execute.) With these limitation, an average build process can still be intercepted. ## Implementation details ### `libexec` It's a shared library. - It's written in C++ 17. - It's using symbols only from the `libc` and `libdl`. - Memory handling: - It does not allocates heap memory. (no malloc, no new) - It allocates static memory and uses the stack. - Error handling: - Any error is fatal. - Errors are reported on `stderr` only if requested. ## `wrapper` supervisor It's an executable. - It's written in C++ 17. - It's using the standard library and some 3rd party libraries. - Memory handling: - It does allocates heap memory. - Error handling: - Any error is fatal. - Errors are reported on `stderr` only if requested. rizsotto-Bear-14c2e01/source/intercept/libexec.version000066400000000000000000000004031476774233700231370ustar00rootroot00000000000000{ global: on_load; on_unload; execv; execve; execvp; execvP; exect; execvpe; execl; execlp; execle; posix_spawn; posix_spawnp; local: *; };rizsotto-Bear-14c2e01/source/intercept/man/000077500000000000000000000000001476774233700206735ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/man/bear-intercept.1000066400000000000000000000064721476774233700236720ustar00rootroot00000000000000.\" Automatically generated by Pandoc 2.14.0.3 .\" .TH "BEAR-INTERCEPT" "1" "Sep 04, 2021" "Bear User Manuals" "" .hy .SH NAME .PP bear-intercept - intercept command executions in user space. .SH SYNOPSIS .PP bear intercept [\f[I]options\f[R]] -- [\f[I]build command\f[R]] .SH DESCRIPTION .PP The command executes the given build command and generates an output file which contains all process execution related events has happened during the build. .PP The process execution events are: start, signal, termination. The output will contain only the child processes. Depending on the interception mode the output might only contain a subset of the executed commands. Interception modes are: .IP \[bu] 2 \f[I]preload\f[R] uses the \f[C]LD_PRELOAD\f[R] or \f[C]DYLD_INSERT_LIBRARIES\f[R] mechanisms provided by the dynamic linker. The pre-loaded library hijacks the process execution calls, and executes a supervisor process, which reports the execution. The method fails when the executable statically linked, or security protection disables the dynamic linker. .IP \[bu] 2 \f[I]wrapper\f[R] mode interpose a wrapper program to the build. The wrapper sends execution report and calls the original program. The method fails when the build system is not flexible enough for interposing build tools. .PP The reports are collected by the \f[C]intercept\f[R] over a gRPC interface, and written into an output file. .SH OPTIONS .TP --version Print version number. .TP --help Print help message. .TP --verbose Enable verbose logging. .TP --output \f[I]file\f[R] Specify output file. (Default file name provided.) The output is a command execution list, with some extra information. The syntax is detailed in a separate section. .TP --force-preload Force to use the preload method to intercept the children processes. .TP --force-wrapper Force to use the wrapper method to intercept the children processes. .SH EXIT STATUS .PP The exit status of the program is the exit status of the build command. Except when the program itself crashes, then it sets to non-zero. .SH OUTPUT FILE .PP The output file has JSON lines (https://jsonlines.org/) format, where each line terminated with \f[C]\[rs]n\f[R] line separator and each line is a JSON object. .PP The JSON objects are process execution events: process start, process got signal, process terminated. (For the schema of these events, please consult with the source code of this project.) .SH TROUBLESHOOTING .PP The potential problems you can face with are: the build with and without the interception behaves differently (eg.: the build crash with the intercept tool, but succeed otherwise). The output is empty, and it failed to intercept the children process execution by the build command. .PP The most common cause for empty outputs is that the build command did not execute any commands. The reason for that could be, because incremental builds not running the compilers if everything is up-to-date. Remember, this program does not understand the build file (eg.: makefile), but intercepts the executed commands. .PP There could be many reasons for any of these failures. It\[cq]s better to consult with the project wiki page for known problems, before open a bug report. .SH SEE ALSO .PP \f[C]bear(1)\f[R] .SH COPYRIGHT .PP Copyright (C) 2012-2024 by L\['a]szl\['o] Nagy .SH AUTHORS L\['a]szl\['o] Nagy. rizsotto-Bear-14c2e01/source/intercept/man/bear-intercept.1.md000066400000000000000000000061531476774233700242650ustar00rootroot00000000000000% BEAR-INTERCEPT(1) Bear User Manuals % László Nagy % Sep 04, 2021 # NAME bear-intercept - intercept command executions in user space. # SYNOPSIS bear intercept [*options*] \-\- [*build command*] # DESCRIPTION The command executes the given build command and generates an output file which contains all process execution related events has happened during the build. The process execution events are: start, signal, termination. The output will contain only the child processes. Depending on the interception mode the output might only contain a subset of the executed commands. Interception modes are: - _preload_ uses the `LD_PRELOAD` or `DYLD_INSERT_LIBRARIES` mechanisms provided by the dynamic linker. The pre-loaded library hijacks the process execution calls, and executes a supervisor process, which reports the execution. The method fails when the executable statically linked, or security protection disables the dynamic linker. - _wrapper_ mode interpose a wrapper program to the build. The wrapper sends execution report and calls the original program. The method fails when the build system is not flexible enough for interposing build tools. The reports are collected by the `intercept` over a gRPC interface, and written into an output file. # OPTIONS \--version : Print version number. \--help : Print help message. \--verbose : Enable verbose logging. \--output *file* : Specify output file. (Default file name provided.) The output is a command execution list, with some extra information. The syntax is detailed in a separate section. \--force-preload : Force to use the preload method to intercept the children processes. \--force-wrapper : Force to use the wrapper method to intercept the children processes. # EXIT STATUS The exit status of the program is the exit status of the build command. Except when the program itself crashes, then it sets to non-zero. # OUTPUT FILE The output file has [JSON lines](https://jsonlines.org/) format, where each line terminated with `\n` line separator and each line is a JSON object. The JSON objects are process execution events: process start, process got signal, process terminated. (For the schema of these events, please consult with the source code of this project.) # TROUBLESHOOTING The potential problems you can face with are: the build with and without the interception behaves differently (eg.: the build crash with the intercept tool, but succeed otherwise). The output is empty, and it failed to intercept the children process execution by the build command. The most common cause for empty outputs is that the build command did not execute any commands. The reason for that could be, because incremental builds not running the compilers if everything is up-to-date. Remember, this program does not understand the build file (eg.: makefile), but intercepts the executed commands. There could be many reasons for any of these failures. It's better to consult with the project wiki page for known problems, before open a bug report. # SEE ALSO `bear(1)` # COPYRIGHT Copyright (C) 2012-2024 by László Nagy rizsotto-Bear-14c2e01/source/intercept/proto/000077500000000000000000000000001476774233700212635ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/proto/CMakeLists.txt000066400000000000000000000047131476774233700240300ustar00rootroot00000000000000find_program(_PROTOBUF_PROTOC protoc HINTS ${gRPC_BINDIR}) message(STATUS "Looking for protoc ... ${_PROTOBUF_PROTOC}") find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin HINTS ${gRPC_BINDIR}) message(STATUS "Looking for grpc_cpp_plugin ... ${_GRPC_CPP_PLUGIN_EXECUTABLE}") get_filename_component(SUPERVISE_PROTO "supervise.proto" ABSOLUTE) get_filename_component(SUPERVISE_PROTO_PATH "${SUPERVISE_PROTO}" PATH) add_custom_command( COMMAND ${_PROTOBUF_PROTOC} ARGS -I "${SUPERVISE_PROTO_PATH}" --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" "${SUPERVISE_PROTO}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/supervise.pb.h ${CMAKE_CURRENT_BINARY_DIR}/supervise.grpc.pb.h ${CMAKE_CURRENT_BINARY_DIR}/supervise.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/supervise.grpc.pb.cc DEPENDS "${SUPERVISE_PROTO}" ) get_filename_component(INTERCEPT_PROTO "intercept.proto" ABSOLUTE) get_filename_component(INTERCEPT_PROTO_PATH "${SUPERVISE_PROTO}" PATH) add_custom_command( COMMAND ${_PROTOBUF_PROTOC} ARGS -I "${SUPERVISE_PROTO_PATH}" --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" "${INTERCEPT_PROTO}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/intercept.pb.h ${CMAKE_CURRENT_BINARY_DIR}/intercept.grpc.pb.h ${CMAKE_CURRENT_BINARY_DIR}/intercept.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/intercept.grpc.pb.cc DEPENDS "${INTERCEPT_PROTO}" ) # Create a static library, which contains the rpc stubs and clients. add_library(rpc_a OBJECT) target_include_directories(rpc_a PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") target_sources(rpc_a PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/supervise.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/supervise.grpc.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/intercept.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/intercept.grpc.pb.cc INTERFACE $ ) target_link_libraries(rpc_a PUBLIC PkgConfig::gRPC) rizsotto-Bear-14c2e01/source/intercept/proto/intercept.proto000066400000000000000000000047541476774233700243570ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ syntax = "proto3"; package rpc; import "google/protobuf/timestamp.proto"; import "google/protobuf/empty.proto"; import "supervise.proto"; // Represents the remote sink of supervised process events. // // Events from a process execution can be sent from many actors (mostly // supervisor processes). The events are collected in a common place // in order to reconstruct of final report of a build process. service Interceptor { // Register a process related events. rpc Register (Event) returns (google.protobuf.Empty) {} } // Represent a relevant life cycle event of a process. // // Currently it's only the process life cycle events (start, signal, // terminate), but can be extended later with performance related // events like monitoring the CPU usage or the memory allocation if // those information are available. message Event { // Required. uint64 rid = 1; // Required. // (ISO-8601 date-tie format with microsecond precision in UTC timezone) google.protobuf.Timestamp timestamp = 2; reserved 3 to 10; // Required. oneof event { Started started = 11; Terminated terminated = 12; Signalled signalled = 13; } // Represents a successful process execution. message Started { // Required. Execution execution = 1; // Required. uint32 pid = 2; // Optional. uint32 ppid = 3; } // Represents the termination of the supervised process. message Terminated { // Optional. // (Killed processes have no exit status.) int64 status = 1; } // Represents the supervised process received a signal. message Signalled { // Required. int32 number = 1; } } rizsotto-Bear-14c2e01/source/intercept/proto/supervise.proto000066400000000000000000000036561476774233700244070ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ syntax = "proto3"; package rpc; // This represents the executed command itself. Describes all the // context that the caller was given. And these are the those parameters // which are essential for re-run the command. message Execution { string executable = 1; repeated string arguments = 2; string working_dir = 3; map environment = 4; } // This represents a (remote) supervisor process to set up program execution. // // A supervisor process executes a single program in the hope that it can // get information about the state of the child process. But to decide what // is the right strategy to intercept the relevant events from the child // process is done in the external supervisor process. // // This interface describes a remote service which can be queried for how to // execute the child program. Can resolve the executable name or update the // environment variables. service Supervisor { // Resolve the execution for future child process. rpc Resolve (ResolveRequest) returns (ResolveResponse) { } } message ResolveRequest { Execution execution = 1; } message ResolveResponse { Execution execution = 1; } rizsotto-Bear-14c2e01/source/intercept/source/000077500000000000000000000000001476774233700214205ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/Convert.cc000066400000000000000000000032361476774233700233530ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Convert.h" namespace domain { Execution from(const rpc::Execution &input) noexcept { return Execution{ fs::path(input.executable()), std::list(input.arguments().begin(), input.arguments().end()), fs::path(input.working_dir()), std::map(input.environment().begin(), input.environment().end()) }; } rpc::Execution into(const Execution &input) noexcept { rpc::Execution result; result.set_executable(input.executable.string()); result.mutable_arguments()->Reserve(input.arguments.size()); for (const auto &argument : input.arguments) { result.add_arguments(argument); } result.set_working_dir(input.working_dir); result.mutable_environment()->insert(input.environment.begin(), input.environment.end()); return result; } } rizsotto-Bear-14c2e01/source/intercept/source/Convert.h000066400000000000000000000017231476774233700232140ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Domain.h" #include "supervise.pb.h" namespace domain { Execution from(const rpc::Execution &) noexcept; rpc::Execution into(const Execution &) noexcept; } rizsotto-Bear-14c2e01/source/intercept/source/Domain.cc000066400000000000000000000027351476774233700231450ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Domain.h" #include "Convert.h" #include #include namespace domain { bool operator==(const Execution &lhs, const Execution &rhs) { return (lhs.executable == rhs.executable) && (lhs.arguments == rhs.arguments) && (lhs.working_dir == rhs.working_dir) && (lhs.environment == rhs.environment); } std::ostream &operator<<(std::ostream &os, const Execution &rhs) { const auto rpc = into(rhs); std::string json; const auto rc = google::protobuf::util::MessageToJsonString(rpc, &json); if (rc.ok()) { os << json; } return os; } } rizsotto-Bear-14c2e01/source/intercept/source/Domain.h000066400000000000000000000025571476774233700230110ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include #include #include namespace fs = std::filesystem; namespace domain { using ReporterId = uint64_t; using ProcessId = uint32_t; struct Execution { fs::path executable; std::list arguments; fs::path working_dir; std::map environment; }; bool operator==(const Execution& lhs, const Execution& rhs); std::ostream& operator<<(std::ostream&, const Execution&); using SessionLocator = std::string; } rizsotto-Bear-14c2e01/source/intercept/source/collect/000077500000000000000000000000001476774233700230455ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/collect/Intercept.cc000066400000000000000000000115301476774233700253110ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "collect/Intercept.h" #include "collect/Reporter.h" #include "collect/RpcServices.h" #include "collect/Session.h" #include "report/libexec/Resolver.h" #include "libsys/Environment.h" #include "libsys/Errors.h" #include "libsys/Os.h" #include #include #include #include #include #include namespace fs = std::filesystem; namespace { rust::Result capture_execution(const flags::Arguments& args, sys::env::Vars &&environment) { const auto path = sys::os::get_path(environment); const auto command = args.as_string_list(cmd::intercept::FLAG_COMMAND) .and_then>([](auto args) { using Result = rust::Result>; return (args.empty()) ? Result(rust::Err(std::runtime_error("Command is empty."))) : Result(rust::Ok(args)); }); const auto executable = rust::merge(path, command) .and_then([](auto tuple) { const auto&[path, command] = tuple; auto executable = command.front(); el::Resolver resolver; return resolver.from_search_path(executable, path.c_str()) .template map([](const auto &ptr) { return fs::path(ptr); }) .template map_err([&executable](auto error) { return std::runtime_error( fmt::format("Could not find: {}: {}", executable, sys::error_string(error))); }); }); return rust::merge(executable, command) .map([&environment](auto tuple) { const auto&[executable, command] = tuple; return ic::Execution{ executable, std::list(command.begin(), command.end()), fs::path("ignored"), std::move(environment) }; }); } } namespace ic { rust::Result Command::execute() const { // Create and start the gRPC server int port = 0; ic::SupervisorImpl supervisor(*session_); ic::InterceptorImpl interceptor(*reporter_); auto server = grpc::ServerBuilder() .RegisterService(&supervisor) .RegisterService(&interceptor) .AddListeningPort("dns:///localhost:0", grpc::InsecureServerCredentials(), &port) .BuildAndStart(); // Create session_locator URL for the services auto session_locator = SessionLocator(fmt::format("dns:///localhost:{}", port)); spdlog::debug("Running gRPC server. {0}", session_locator); // Execute the build command auto result = session_->run(execution_, session_locator); // Stop the gRPC server spdlog::debug("Stopping gRPC server."); server->Shutdown(); // Exit with the build status return result; } Intercept::Intercept(const ps::ApplicationLogConfig& log_config) noexcept : ps::SubcommandFromArgs("intercept", log_config) { } rust::Result Intercept::command(const flags::Arguments &args, const char **envp) const { const auto execution = capture_execution(args, sys::env::from(envp)); const auto session = Session::from(args, envp); const auto reporter = Reporter::from(args); return rust::merge(execution, session, reporter) .map([](auto tuple) { const auto&[execution, session, reporter] = tuple; return std::make_unique(execution, session, reporter); }); } } rizsotto-Bear-14c2e01/source/intercept/source/collect/Intercept.h000066400000000000000000000031071476774233700251540ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libmain/SubcommandFromArgs.h" #include "Session.h" #include "Reporter.h" #include #include #include #include "intercept/intercept-forward.h" namespace ic { struct Command : ps::Command { Command(Execution execution, Session::Ptr session, Reporter::Ptr reporter) : ps::Command() , execution_(std::move(execution)) , session_(std::move(session)) , reporter_(std::move(reporter)) { } [[nodiscard]] rust::Result execute() const override; NON_DEFAULT_CONSTRUCTABLE(Command) NON_COPYABLE_NOR_MOVABLE(Command) private: Execution execution_; Session::Ptr session_; Reporter::Ptr reporter_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/Reporter.cc000066400000000000000000000035071476774233700251630ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "collect/Reporter.h" #include #include namespace ic { rust::Result Reporter::from(const flags::Arguments& flags) { return flags .as_string(cmd::intercept::FLAG_OUTPUT) .and_then([](auto file) { return ic::collect::db::EventsDatabaseWriter::create(file); }) .map([](auto events) { return std::make_shared(events); }); } Reporter::Reporter(ic::collect::db::EventsDatabaseWriter::Ptr database) : database_(std::move(database)) , mutex_() { } void Reporter::report(const rpc::Event& event) { const std::lock_guard lock(mutex_); database_->insert_event(event) .on_error([](auto error) { spdlog::warn("Writing event into database failed: {} Ignored.", error.what()); }); } } rizsotto-Bear-14c2e01/source/intercept/source/collect/Reporter.h000066400000000000000000000031061476774233700250200ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "collect/db/EventsDatabaseWriter.h" #include "libflags/Flags.h" #include "libresult/Result.h" #include "intercept.pb.h" #include #include namespace ic { // Responsible to collect executions and persist them into an output file. class Reporter { public: using Ptr = std::shared_ptr; static rust::Result from(const flags::Arguments &flags); void report(const rpc::Event &event); public: explicit Reporter(ic::collect::db::EventsDatabaseWriter::Ptr database); ~Reporter() noexcept = default; NON_DEFAULT_CONSTRUCTABLE(Reporter) NON_COPYABLE_NOR_MOVABLE(Reporter) private: ic::collect::db::EventsDatabaseWriter::Ptr database_; std::mutex mutex_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/RpcServices.cc000066400000000000000000000040371476774233700256100ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "collect/Reporter.h" #include "collect/RpcServices.h" #include "collect/Session.h" namespace ic { SupervisorImpl::SupervisorImpl(const Session &session) : rpc::Supervisor::Service() , session_(session) { } grpc::Status SupervisorImpl::Resolve(grpc::ServerContext *, const rpc::ResolveRequest *request, rpc::ResolveResponse *response) { return session_.resolve(from(request->execution())) .map([&response](auto execution) { // Need to copy the execution into the response. response->mutable_execution()->CopyFrom(into(execution)); // Confirm it with an OK. return ::grpc::Status::OK; }) .unwrap_or_else([](const auto &error) { return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, error.what()); }); } InterceptorImpl::InterceptorImpl(Reporter &reporter) : rpc::Interceptor::Service() , reporter_(reporter) { } grpc::Status InterceptorImpl::Register(grpc::ServerContext*, const rpc::Event* request, google::protobuf::Empty*) { reporter_.report(*request); return ::grpc::Status::OK; } } rizsotto-Bear-14c2e01/source/intercept/source/collect/RpcServices.h000066400000000000000000000034761476774233700254600ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "intercept.grpc.pb.h" #include "supervise.grpc.pb.h" namespace ic { class Reporter; class Session; class SupervisorImpl final : public rpc::Supervisor::Service { public: explicit SupervisorImpl(const Session&); ~SupervisorImpl() override = default; grpc::Status Resolve(grpc::ServerContext *context, const rpc::ResolveRequest *request, rpc::ResolveResponse *response) override; NON_DEFAULT_CONSTRUCTABLE(SupervisorImpl) NON_COPYABLE_NOR_MOVABLE(SupervisorImpl) private: const Session &session_; }; class InterceptorImpl final : public rpc::Interceptor::Service { public: explicit InterceptorImpl(Reporter&); ~InterceptorImpl() override = default; ::grpc::Status Register(::grpc::ServerContext* context, const rpc::Event* request, google::protobuf::Empty* response) override; NON_DEFAULT_CONSTRUCTABLE(InterceptorImpl) NON_COPYABLE_NOR_MOVABLE(InterceptorImpl) private: Reporter& reporter_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/Session.cc000066400000000000000000000064541476774233700250100ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "collect/Session.h" #include "collect/SessionWrapper.h" #ifdef SUPPORT_PRELOAD #include "collect/SessionLibrary.h" #endif #include "libsys/Path.h" #include "libsys/Signal.h" #include namespace ic { rust::Result Session::from(const flags::Arguments& args, const char **envp) #ifdef SUPPORT_PRELOAD { if (args.as_bool(cmd::intercept::FLAG_FORCE_WRAPPER).unwrap_or(false)) return WrapperSession::from(args, envp); if (args.as_bool(cmd::intercept::FLAG_FORCE_PRELOAD).unwrap_or(false)) return LibraryPreloadSession::from(args); return LibraryPreloadSession::from(args); } #else { return WrapperSession::from(args, envp); } #endif std::string Session::keep_front_in_path(const std::string& path, const std::string& paths) { if (paths == path) { return paths; } else { std::list result = {path}; auto existing = sys::path::split(paths); std::copy_if(existing.begin(), existing.end(), std::back_inserter(result), [&path](auto current) { return current != path; } ); return sys::path::join(result); } } std::string Session::remove_from_path(const std::string& path, const std::string& paths) { std::list result = { }; auto existing = sys::path::split(paths); std::copy_if(existing.begin(), existing.end(), std::back_inserter(result), [&path](auto current) { return current != path; } ); return sys::path::join(result); } rust::Result Session::run(const ic::Execution &execution, const SessionLocator &session_locator) { session_locator_ = std::make_unique(session_locator); return supervise(execution) .spawn() .and_then([](auto child) { sys::SignalForwarder guard(child); return child.wait(); }) .map([](auto status) { return status.code().value_or(EXIT_FAILURE); }) .on_error([](auto error) { spdlog::warn("Command execution failed: {}", error.what()); }) .on_success([](auto status) { spdlog::debug("Running command. [Exited with {0}]", status); }); } } rizsotto-Bear-14c2e01/source/intercept/source/collect/Session.h000066400000000000000000000036071476774233700246470ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libflags/Flags.h" #include "libresult/Result.h" #include "libsys/Os.h" #include "libsys/Process.h" #include "Domain.h" #include "Convert.h" #include #include #include #include namespace ic { using namespace domain; class Session { public: using Ptr = std::shared_ptr; static rust::Result from(const flags::Arguments &args, const char **envp); public: virtual ~Session() = default; [[nodiscard]] virtual rust::Result resolve(const ic::Execution &execution) const = 0; [[nodiscard]] virtual sys::Process::Builder supervise(const ic::Execution &execution) const = 0; [[nodiscard]] rust::Result run(const ic::Execution &execution, const SessionLocator &session_locator); protected: static std::string keep_front_in_path(const std::string& path, const std::string& paths); static std::string remove_from_path(const std::string& path, const std::string& paths); protected: std::unique_ptr session_locator_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/SessionLibrary.cc000066400000000000000000000101741476774233700263270ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "collect/SessionLibrary.h" #include "libsys/Errors.h" #include "libsys/Path.h" #include "libsys/Process.h" #include #include namespace { constexpr char GLIBC_PRELOAD_KEY[] = "LD_PRELOAD"; using env_t = std::map; using mapper_t = std::function; void insert_or_merge( env_t& target, const char* key, const std::string& value, const mapper_t& merger) noexcept { if (auto it = target.find(key); it != target.end()) { it->second = merger(value, it->second); } else { target.emplace(key, value); } } } namespace ic { rust::Result LibraryPreloadSession::from(const flags::Arguments& args) { auto verbose = args.as_bool(flags::VERBOSE).unwrap_or(false); const auto library = args.as_string(cmd::intercept::FLAG_LIBRARY); const auto wrapper = args.as_string(cmd::intercept::FLAG_WRAPPER); return merge(library, wrapper) .map([&verbose](auto tuple) { const auto& [library, wrapper] = tuple; return std::make_shared(verbose, library, wrapper); }); } LibraryPreloadSession::LibraryPreloadSession( bool verbose, const std::string_view &library, const std::string_view &executor) : Session() , verbose_(verbose) , library_(library) , executor_(executor) { spdlog::debug("Created library preload session. [library={0}, executor={1}]", library_, executor_); } rust::Result LibraryPreloadSession::resolve(const ic::Execution &execution) const { spdlog::debug("trying to resolve for library: {}", execution.executable.string()); return rust::Ok(ic::Execution{ execution.executable, execution.arguments, execution.working_dir, update(execution.environment) }); } sys::Process::Builder LibraryPreloadSession::supervise(const ic::Execution &execution) const { auto builder = sys::Process::Builder(executor_) .add_argument(executor_) .add_argument(cmd::wrapper::FLAG_DESTINATION) .add_argument(*session_locator_); if (verbose_) { builder.add_argument(cmd::wrapper::FLAG_VERBOSE); } return builder .add_argument(cmd::wrapper::FLAG_EXECUTE) .add_argument(execution.executable) .add_argument(cmd::wrapper::FLAG_COMMAND) .add_arguments(execution.arguments.begin(), execution.arguments.end()) .set_environment(update(execution.environment)); } std::map LibraryPreloadSession::update(const std::map &env) const { std::map copy(env); if (verbose_) { copy[cmd::library::KEY_VERBOSE] = "true"; } copy[cmd::library::KEY_DESTINATION] = *session_locator_; copy[cmd::library::KEY_REPORTER] = executor_; insert_or_merge(copy, GLIBC_PRELOAD_KEY, library_, Session::keep_front_in_path); return copy; } } rizsotto-Bear-14c2e01/source/intercept/source/collect/SessionLibrary.h000066400000000000000000000032011476774233700261620ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "collect/Session.h" namespace ic { class LibraryPreloadSession : public ic::Session { public: LibraryPreloadSession(bool verbose, const std::string_view &library, const std::string_view &executor); static rust::Result from(const flags::Arguments&); [[nodiscard]] rust::Result resolve(const ic::Execution &execution) const override; [[nodiscard]] sys::Process::Builder supervise(const ic::Execution &execution) const override; NON_DEFAULT_CONSTRUCTABLE(LibraryPreloadSession) NON_COPYABLE_NOR_MOVABLE(LibraryPreloadSession) private: [[nodiscard]] std::map update(const std::map& env) const; private: bool verbose_; std::string library_; std::string executor_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/SessionWrapper.cc000066400000000000000000000227371476774233700263530ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "collect/SessionWrapper.h" #include "report/libexec/Resolver.h" #include "report/libexec/Environment.h" #include "libsys/Errors.h" #include "libsys/Os.h" #include "libsys/Path.h" #ifdef HAVE_FMT_STD_H #include #endif #include #include #include #include #include #include namespace { struct Rule { const char* env; const char* wrapper; }; // The list of implicit rules for the build systems. // // The environment variable names an executable (or an executable plus an // argument) which will be run for a given build step. // // NOTES: current implementation depends on the list has unique environment // names, but also unique wrapper names too. // // https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html constexpr Rule IMPLICITS[] = { { "AR", "ar" }, { "AS", "as" }, { "CC", "cc" }, { "CXX", "c++" }, { "CPP", "cpp" }, { "FC", "f77" }, { "M2C", "m2c" }, { "PC", "pc" }, { "LEX", "lex" }, { "YACC", "yacc" }, { "LINT", "lint" }, { "MAKEINFO", "makeinfo" }, { "TEX", "tex" }, { "TEXI2DVI", "texi2dvi" }, { "WEAVE", "weave" }, { "CWEAVE", "cweave" }, { "TANGLE", "tangle" }, { "CTANGLE", "ctangle" } }; rust::Result> list_dir(const fs::path& path) { std::list result; std::error_code error_code; for (auto& candidate : fs::directory_iterator(path, error_code)) { if (error_code) { return rust::Err(std::runtime_error(error_code.message())); } if (candidate.is_regular_file()) { result.push_back(candidate.path()); } } return rust::Ok(std::move(result)); } } namespace ic { rust::Result WrapperSession::from(const flags::Arguments &args, const char **envp) { const bool verbose = args.as_bool(flags::VERBOSE).unwrap_or(false); auto wrapper_dir = args.as_string(cmd::intercept::FLAG_WRAPPER_DIR); auto wrappers = wrapper_dir.and_then>(list_dir); auto mapping = wrappers .map>([&envp](auto wrappers) { // Find the executables with the same name from the path. std::map result; el::Resolver resolver; for (const auto& wrapper : wrappers) { auto basename = wrapper.filename(); auto candidate = resolver.from_path(basename.c_str(), envp); candidate.on_success([&result, &basename](auto candidate) { result[basename] = fs::path(candidate); }); } return result; }); return rust::merge(wrapper_dir, mapping) .map([&envp, &verbose](const auto &tuple) { const auto& [wrapper_dir, const_mapping] = tuple; std::map mapping(const_mapping); std::map override; el::Resolver resolver; // check if any environment variable is naming the real compiler for (auto implicit : IMPLICITS) { // find any of the implicit defined in environment. if (auto env_value = el::env::get_env_value(envp, implicit.env); env_value != nullptr) { // FIXME: it would be more correct if we shell-split the `env_value->second` // and use only the program name, but not the argument. But then how // to deal with the errors? resolver.from_path(std::string_view(env_value), envp) .on_success([&mapping, &implicit, &override](auto executable) { // find the current mapping for the program the user wants to run. // and replace the program what the wrapper will call. if (auto mapping_it = mapping.find(implicit.wrapper); mapping_it != mapping.end()) { mapping_it->second = executable; override[implicit.env] = mapping_it->first; } else { mapping[implicit.wrapper] = executable; override[implicit.env] = implicit.wrapper; } }); } } return std::make_shared(verbose, std::string(wrapper_dir), std::move(mapping), std::move(override)); }); } WrapperSession::WrapperSession( bool verbose, std::string wrapper_dir, std::map mapping, std::map override) : Session() , verbose_(verbose) , wrapper_dir_(std::move(wrapper_dir)) , mapping_(std::move(mapping)) , override_(std::move(override)) { spdlog::debug("session initialized with: wrapper_dir: {}", wrapper_dir_); spdlog::debug("session initialized with: mapping: {}", mapping_); spdlog::debug("session initialized with: override: {}", override_); } rust::Result WrapperSession::resolve(const ic::Execution &execution) const { spdlog::debug("trying to resolve for wrapper: {}", execution.executable.string()); return resolve(execution.executable) .map([this, &execution](auto executable) { auto arguments = execution.arguments; arguments.front() = executable; return ic::Execution{ fs::path(executable), std::move(arguments), fs::path(execution.working_dir), update(execution.environment) }; }); } sys::Process::Builder WrapperSession::supervise(const ic::Execution &execution) const { return sys::Process::Builder(execution.executable) .add_arguments(execution.arguments.begin(), execution.arguments.end()) .set_environment(set_up(execution.environment)); } rust::Result WrapperSession::resolve(const fs::path &name) const { const auto &basename = name.filename(); if (auto candidate = mapping_.find(basename.string()); candidate != mapping_.end()) { return rust::Ok(candidate->second); } return rust::Err(std::runtime_error("not recognized wrapper")); } std::map WrapperSession::update(const std::map& env) const { std::map copy(env); // remove wrapper directory from path if (auto it = copy.find("PATH"); it != copy.end()) { it->second = remove_from_path(wrapper_dir_, it->second); } // remove verbose flag if (const auto it = copy.find(cmd::wrapper::KEY_VERBOSE); it != copy.end()) { copy.erase(it); } // remove destination if (const auto it = copy.find(cmd::wrapper::KEY_DESTINATION); it != copy.end()) { copy.erase(it); } // remove all implicits for (const auto& override : override_) { if (auto it = copy.find(override.first); it != copy.end()) { copy.erase(it); } } return copy; } std::map WrapperSession::set_up(const std::map& env) const { std::map environment(env); // enable verbose logging to wrappers if (verbose_) { environment[cmd::wrapper::KEY_VERBOSE] = "true"; } // sets the server address to wrappers environment[cmd::wrapper::KEY_DESTINATION] = *session_locator_; // change PATH to put the wrapper directory at the front. if (auto it = environment.find("PATH"); it != environment.end()) { it->second = keep_front_in_path(wrapper_dir_, it->second); } // replace all implicit program to the wrapper for (const auto& it : override_) { environment[it.first] = it.second; } return environment; } } rizsotto-Bear-14c2e01/source/intercept/source/collect/SessionWrapper.h000066400000000000000000000037511476774233700262100ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "collect/Session.h" namespace ic { class WrapperSession : public ic::Session { public: WrapperSession( bool verbose, std::string wrapper_dir, std::map mapping, std::map override); static rust::Result from(const flags::Arguments &args, const char **envp); [[nodiscard]] rust::Result resolve(const ic::Execution &execution) const override; [[nodiscard]] sys::Process::Builder supervise(const ic::Execution &execution) const override; NON_DEFAULT_CONSTRUCTABLE(WrapperSession) NON_COPYABLE_NOR_MOVABLE(WrapperSession) private: [[nodiscard]] rust::Result resolve(const fs::path &name) const; [[nodiscard]] std::map update(const std::map& env) const; [[nodiscard]] std::map set_up(const std::map& env) const; private: bool verbose_; std::string wrapper_dir_; std::map mapping_; std::map override_; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/db/000077500000000000000000000000001476774233700234325ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/collect/db/EventsDatabaseReader.cc000066400000000000000000000123101476774233700277520ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "EventsDatabaseReader.h" #include "libsys/Errors.h" #include #include #include #include #include using google::protobuf::util::JsonParseOptions; namespace { const JsonParseOptions parse_options; } namespace ic::collect::db { rust::Result EventsDatabaseReader::from(const fs::path &path) { std::unique_ptr file = std::make_unique(path, std::ios::in); std::shared_ptr result = std::make_shared(path, std::move(file)); return rust::Ok(result); } EventsDatabaseReader::EventsDatabaseReader(fs::path path, StreamPtr file) noexcept : path_(std::move(path)) , file_(std::move(file)) { } EventsDatabaseReader::Iterator EventsDatabaseReader::begin() noexcept { return EventsDatabaseReader::Iterator(*this, false); } EventsDatabaseReader::Iterator EventsDatabaseReader::end() noexcept { return EventsDatabaseReader::Iterator(*this, true); } std::optional> EventsDatabaseReader::next() noexcept { const auto line = next_line(); if (line.has_value()) { return line.value() .and_then([this](const auto &line) { return from_json(line); }); } return {}; } std::optional> EventsDatabaseReader::next_line() noexcept { std::string line; if (std::getline(*file_, line)) { return line.empty() ? std::optional>() : std::make_optional(rust::Ok(std::move(line))); } else { const std::runtime_error error( fmt::format( "Events db read failed (from file {}): io error", path_.string())); return file_->eof() ? std::optional>() : std::make_optional(rust::Err(error)); } } rust::Result EventsDatabaseReader::from_json(const std::string &line) noexcept { std::shared_ptr event = std::make_shared(); if (const auto status = google::protobuf::util::JsonStringToMessage(line, event.get(), parse_options); !status.ok()) { auto message = fmt::format( "Events db read failed (from file {}): JSON parsing error", path_.string() ); return rust::Err(std::runtime_error(message)); } return rust::Ok(event); } EventsDatabaseReader::Iterator::Iterator(EventsDatabaseReader &reader, bool end) noexcept : reader_(reader) { if (!end) { auto candidate = reader_.next(); while (candidate) { if (candidate.value().is_ok()) { current = candidate.value().unwrap(); break; } candidate = reader_.next(); } } } const EventsDatabaseReader::Iterator::value_type &EventsDatabaseReader::Iterator::operator*() const { return current.operator*(); } EventsDatabaseReader::Iterator::pointer EventsDatabaseReader::Iterator::operator->() const { return current.operator->(); } EventsDatabaseReader::Iterator &EventsDatabaseReader::Iterator::operator++() { if (current) { current = nullptr; auto candidate = reader_.next(); while (candidate) { if (candidate.value().is_ok()) { current = candidate.value().unwrap(); break; } candidate = reader_.next(); } } return *this; } EventsDatabaseReader::Iterator EventsDatabaseReader::Iterator::operator++(int) { auto result(*this); this->operator++(); return result; } bool operator==(const EventsDatabaseReader::Iterator &lhs, const EventsDatabaseReader::Iterator &rhs) { return (&lhs.reader_ == &rhs.reader_) && (lhs.current == rhs.current); } bool operator!=(const EventsDatabaseReader::Iterator &lhs, const EventsDatabaseReader::Iterator &rhs) { return !(lhs == rhs); } } rizsotto-Bear-14c2e01/source/intercept/source/collect/db/EventsDatabaseReader.h000066400000000000000000000053771476774233700276330ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include "intercept.pb.h" #include #include #include #include namespace fs = std::filesystem; namespace ic::collect::db { using EventPtr = std::shared_ptr; class EventsDatabaseReader { public: class Iterator; friend class Iterator; using Ptr = std::shared_ptr; using StreamPtr = std::unique_ptr; [[nodiscard]] static rust::Result from(const fs::path &path); [[nodiscard]] Iterator begin() noexcept; [[nodiscard]] Iterator end() noexcept; private: [[nodiscard]] std::optional> next() noexcept; [[nodiscard]] std::optional> next_line() noexcept; [[nodiscard]] rust::Result from_json(const std::string &) noexcept; public: explicit EventsDatabaseReader(fs::path path, StreamPtr file) noexcept; NON_DEFAULT_CONSTRUCTABLE(EventsDatabaseReader) NON_COPYABLE_NOR_MOVABLE(EventsDatabaseReader) private: fs::path path_; StreamPtr file_; }; class EventsDatabaseReader::Iterator { public: using value_type = rpc::Event; using difference_type = std::ptrdiff_t; using reference = const value_type &; using pointer = const value_type *; using iterator_category = std::forward_iterator_tag; explicit Iterator(EventsDatabaseReader &reader, bool end) noexcept; reference operator*() const; pointer operator->() const; Iterator &operator++(); Iterator operator++(int); NON_DEFAULT_CONSTRUCTABLE(Iterator) friend bool operator==(const Iterator &lhs, const Iterator &rhs); friend bool operator!=(const Iterator &lhs, const Iterator &rhs); private: EventsDatabaseReader &reader_; EventPtr current; }; } rizsotto-Bear-14c2e01/source/intercept/source/collect/db/EventsDatabaseWriter.cc000066400000000000000000000100301476774233700300210ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "EventsDatabaseWriter.h" #include "libsys/Errors.h" #include #include #include #include #include #include #include #include using google::protobuf::util::JsonPrintOptions; namespace { JsonPrintOptions create_print_options() { JsonPrintOptions print_options; print_options.add_whitespace = false; #if GOOGLE_PROTOBUF_VERSION < 5026000 print_options.always_print_primitive_fields = true; #endif print_options.preserve_proto_field_names = true; print_options.always_print_enums_as_ints = false; return print_options; } const JsonPrintOptions print_options = create_print_options(); } namespace ic::collect::db { rust::Result EventsDatabaseWriter::create(const fs::path &file) { int fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 00644); if (fd == -1) { auto message = fmt::format("Events db open failed (file {}): {}", file.string(), sys::error_string(errno)); return rust::Err(std::runtime_error(message)); } std::shared_ptr result = std::make_shared(file, fd); return rust::Ok(result); } EventsDatabaseWriter::EventsDatabaseWriter(fs::path path, int file) noexcept : path_(std::move(path)) , file_(file) { } EventsDatabaseWriter::~EventsDatabaseWriter() noexcept { close(file_); } rust::Result EventsDatabaseWriter::insert_event(const rpc::Event &event) { return to_json(event) .and_then([this](const auto &json) { return write_to_file(json); }) .and_then([this](const auto &) { return write_to_file("\n"); }); } rust::Result EventsDatabaseWriter::to_json(const rpc::Event &event) noexcept { std::string json; if (const auto status = google::protobuf::util::MessageToJsonString(event, &json, print_options); !status.ok()) { auto message = fmt::format( "Events db write failed (to file {}): JSON formatting error", path_.string() ); return rust::Err(std::runtime_error(message)); } return rust::Ok(std::move(json)); } rust::Result EventsDatabaseWriter::write_to_file(const std::string& content) noexcept { const char* content_ptr = content.c_str(); size_t content_length = content.size(); while (content_length) { const int written_length = write(file_, content_ptr, content_length); if (written_length == -1) { auto message = fmt::format( "Events db write failed (to file {}): {}", path_.string(), sys::error_string(errno) ); errno = 0; return rust::Err(std::runtime_error(message)); } content_length -= written_length; content_ptr += written_length; } return rust::Ok(1); } } rizsotto-Bear-14c2e01/source/intercept/source/collect/db/EventsDatabaseWriter.h000066400000000000000000000034051476774233700276730ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include "intercept.pb.h" #include #include #include namespace fs = std::filesystem; namespace ic::collect::db { class EventsDatabaseWriter { public: using Ptr = std::shared_ptr; [[nodiscard]] static rust::Result create(const fs::path &file); [[nodiscard]] rust::Result insert_event(const rpc::Event &event); public: explicit EventsDatabaseWriter(fs::path path, int file) noexcept; ~EventsDatabaseWriter() noexcept; NON_DEFAULT_CONSTRUCTABLE(EventsDatabaseWriter) NON_COPYABLE_NOR_MOVABLE(EventsDatabaseWriter) private: rust::Result to_json(const rpc::Event &event) noexcept; rust::Result write_to_file(const std::string &content) noexcept; private: fs::path path_; int file_; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/000077500000000000000000000000001476774233700227335ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/000077500000000000000000000000001476774233700243465ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Array.h000066400000000000000000000052171476774233700256020ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include namespace el::array { /** * Return a pointer to the last element of a nullptr terminated array. * * @param it the input array to count, * @return the pointer which points the nullptr. */ template constexpr T* end(T* it) noexcept { if (it == nullptr) return nullptr; while (*it != 0) ++it; return it; } /** * Return the size of a nullptr terminated array. * * @param begin the input array to count, * @return the size of the array. */ template constexpr size_t length(T* const begin) noexcept { return end(begin) - begin; } /** * Re-implementation of std::copy to avoid `memmove` symbol. * * @tparam I input type * @tparam O output type * @param src_begin * @param src_end * @param dst_begin * @param dst_end * @return output iterator to the last copied element. */ template constexpr O* copy(I* const src_begin, I* const src_end, O* const dst_begin, O* const dst_end) noexcept { auto src_it = src_begin; auto dst_it = dst_begin; for (; src_it != src_end && dst_it != dst_end;) *dst_it++ = *src_it++; return (src_it == src_end) ? dst_it : nullptr; } /** * Check if two nullptr terminated array is equal till the limit. * * @tparam T * @param lhs * @param rhs * @param length * @return true if the two array is equal till the limit. */ template constexpr bool equal_n(T* const lhs, T* const rhs, const size_t length) noexcept { for (size_t idx = 0; idx < length; ++idx) { if (lhs[idx] != rhs[idx]) return false; } return true; } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Buffer.cc000066400000000000000000000023641476774233700260730ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Array.h" #include "report/libexec/Buffer.h" #include namespace el { char const* Buffer::store(char const* const input) noexcept { if (input == nullptr) return nullptr; const auto input_end = el::array::end(input) + 1; // include the zero element auto top = el::array::copy(input, input_end, top_, end_); if (top != nullptr) std::swap(top_, top); return top; } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Buffer.h000066400000000000000000000034651476774233700257400ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" namespace el { /** * Represents a character buffer. * * Define helper methods to persist character sequences. The covered * functionality is not more than a `memcpy` to a static char array. */ class Buffer { public: /** * Takes the memory addresses of the buffer. * * @param begin of the buffer. * @param end of the buffer. */ Buffer(char* begin, char* end) noexcept; ~Buffer() noexcept = default; /** * Copy the input to the buffer. * * @param input to persist. * @return the address of the persisted input. */ char const* store(char const* input) noexcept; NON_DEFAULT_CONSTRUCTABLE(Buffer) NON_COPYABLE_NOR_MOVABLE(Buffer) private: char* top_; char* const end_; }; inline Buffer::Buffer(char* const begin, char* const end) noexcept : top_(begin) , end_(end) { } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Environment.cc000066400000000000000000000030311476774233700271560ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Environment.h" #include "report/libexec/Array.h" namespace el::env { const char* get_env_value(const char **envp, const char *const key) noexcept { const size_t key_size = el::array::length(key); for (const char** it = envp; *it != nullptr; ++it) { const char* const current = *it; // Is the key a prefix of the pointed string? if (!el::array::equal_n(key, current, key_size)) continue; // Is the next character is the equal sign? if (current[key_size] != '=') continue; // It must be the one! Calculate the address of the value. return current + key_size + 1; } return nullptr; } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Environment.h000066400000000000000000000023201476774233700270200ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once namespace el::env { /** * Returns the value for the given environment name, from the given * environment array. * * It's a re-implementation of the standard library function.. * * @param envp the environment array. * @param key the name of the environment. * @return the value of the environment. */ const char* get_env_value(const char** envp, const char* key) noexcept; } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Executor.cc000066400000000000000000000154331476774233700264610ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Executor.h" #include "Array.h" #include "Logger.h" #include "Resolver.h" #include "Linker.h" #include "Session.h" #include #include namespace { constexpr el::log::Logger LOGGER("Executor.cc"); #define CHECK_SESSION(SESSION_) \ do { \ if (!el::session::is_valid(SESSION_)) { \ LOGGER.warning("session is not initialized"); \ return rust::Err(EIO); \ } \ } while (false) #define CHECK_POINTER(PTR_) \ do { \ if (nullptr == (PTR_)) { \ LOGGER.debug("null pointer received"); \ return rust::Err(EFAULT); \ } \ } while (false) // Util class to create command arguments to execute the intercept process. // // Use this class to allocate buffer and assemble the content of it. class CommandBuilder { public: constexpr CommandBuilder(const el::Session& session, const char* path, char* const* const argv) : session(session) , path(path) , argv(argv) { } [[nodiscard]] constexpr size_t length() const noexcept { return (session.verbose ? 6 : 7) + el::array::length(argv) + 1; } constexpr void assemble(const char** it) const noexcept { const char** const it_end = it + length(); *it++ = session.reporter; *it++ = cmd::wrapper::FLAG_DESTINATION; *it++ = session.destination; if (session.verbose) { *it++ = cmd::wrapper::FLAG_VERBOSE; } *it++ = cmd::wrapper::FLAG_EXECUTE; *it++ = path; *it++ = cmd::wrapper::FLAG_COMMAND; { char* const* const argv_end = el::array::end(argv); it = el::array::copy(argv, argv_end, it, it_end); } *it = nullptr; } [[nodiscard]] constexpr const char* file() const noexcept { return session.reporter; } private: const el::Session& session; const char* path; char* const* const argv; }; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" namespace el { Executor::Executor(el::Linker const& linker, el::Session const& session, el::Resolver &resolver) noexcept : linker_(linker) , session_(session) , resolver_(resolver) { } rust::Result Executor::execve(const char* path, char* const* argv, char* const* envp) const { CHECK_SESSION(session_); CHECK_POINTER(path); if (auto executable = resolver_.from_current_directory(path); executable.is_ok()) { const CommandBuilder cmd(session_, executable.unwrap(), argv); const char* dst[cmd.length()]; cmd.assemble(dst); return linker_.execve(cmd.file(), const_cast(dst), envp); } else { return rust::Err(executable.unwrap_err()); } } rust::Result Executor::execvpe(const char* file, char* const* argv, char* const* envp) const { CHECK_SESSION(session_); CHECK_POINTER(file); if (auto executable = resolver_.from_path(file, const_cast(envp)); executable.is_ok()) { const CommandBuilder cmd(session_, executable.unwrap(), argv); const char* dst[cmd.length()]; cmd.assemble(dst); return linker_.execve(cmd.file(), const_cast(dst), envp); } else { return rust::Err(executable.unwrap_err()); } } rust::Result Executor::execvP(const char* file, const char* search_path, char* const* argv, char* const* envp) const { CHECK_SESSION(session_); CHECK_POINTER(file); if (auto executable = resolver_.from_search_path(file, search_path); executable.is_ok()) { const CommandBuilder cmd(session_, executable.unwrap(), argv); const char* dst[cmd.length()]; cmd.assemble(dst); return linker_.execve(cmd.file(), const_cast(dst), envp); } else { return rust::Err(executable.unwrap_err()); } } rust::Result Executor::posix_spawn(pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const* argv, char* const* envp) const { CHECK_SESSION(session_); CHECK_POINTER(path); if (auto executable = resolver_.from_current_directory(path); executable.is_ok()) { const CommandBuilder cmd(session_, executable.unwrap(), argv); const char* dst[cmd.length()]; cmd.assemble(dst); return linker_.posix_spawn(pid, cmd.file(), file_actions, attrp, const_cast(dst), envp); } else { return rust::Err(executable.unwrap_err()); } } rust::Result Executor::posix_spawnp(pid_t* pid, const char* file, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const* argv, char* const* envp) const { CHECK_SESSION(session_); CHECK_POINTER(file); if (auto executable = resolver_.from_path(file, const_cast(envp)); executable.is_ok()) { const CommandBuilder cmd(session_, executable.unwrap(), argv); const char* dst[cmd.length()]; cmd.assemble(dst); return linker_.posix_spawn(pid, cmd.file(), file_actions, attrp, const_cast(dst), envp); } else { return rust::Err(executable.unwrap_err()); } } } #pragma GCC diagnostic pop rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Executor.h000066400000000000000000000055361476774233700263260ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #ifdef HAVE_SPAWN_H #include #endif #include namespace el { struct Linker; struct Session; class Resolver; /** * This class implements the process execution logic. * * The caller of this is the POSIX interface for process creation. * This class encapsulate most of the logic and leave the C wrapper light * in order to test the functionality in unit tests. * * This is just a subset of all process creation calls. * * - Variable argument methods are not implemented. (The `execl*` methods.) * Caller needs to convert those convenient functions by collecting the * arguments into a C array. * * - Environment needs to pass for this methods. If a method does not have * the environment explicitly passed as argument, it needs to grab it * and pass to these methods. */ class Executor { public: Executor(el::Linker const& linker, el::Session const& session, el::Resolver &resolver) noexcept; ~Executor() noexcept = default; public: rust::Result execve(const char* path, char* const argv[], char* const envp[]) const; rust::Result execvpe(const char* file, char* const argv[], char* const envp[]) const; rust::Result execvP(const char* file, const char* search_path, char* const argv[], char* const envp[]) const; rust::Result posix_spawn(pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) const; rust::Result posix_spawnp(pid_t* pid, const char* file, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) const; private: el::Linker const &linker_; el::Session const &session_; el::Resolver &resolver_; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Linker.cc000066400000000000000000000046011476774233700261020ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Linker.h" #include #include namespace { template T dynamic_linker(char const* const name) { return reinterpret_cast(dlsym(RTLD_NEXT, name)); } } namespace el { rust::Result Linker::execve(const char* path, char* const* argv, char* const* envp) const noexcept { using type = int (*)(const char*, char* const[], char* const[]); const auto fp = dynamic_linker("execve"); if (fp == nullptr) { return rust::Err(EINVAL); } auto result = fp(path, argv, envp); return (result == -1) ? rust::Result(rust::Err(errno)) : rust::Result(rust::Ok(result)); } rust::Result Linker::posix_spawn( pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const* argv, char* const* envp) const noexcept { using type = int (*)( pid_t * pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]); const auto fp = dynamic_linker("posix_spawn"); if (fp == nullptr) { return rust::Err(EINVAL); } auto result = fp(pid, path, file_actions, attrp, argv, envp); return (result != 0) ? rust::Result(rust::Err(errno)) : rust::Result(rust::Ok(result)); } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Linker.h000066400000000000000000000031741476774233700257500ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #ifdef HAVE_SPAWN_H #include #endif namespace el { /** * It is an abstraction of the symbol resolver. * * It uses the provided symbol resolver method and cast the result * to a specific type. */ struct Linker { virtual ~Linker() noexcept = default; [[nodiscard]] virtual rust::Result execve( const char* path, char* const argv[], char* const envp[]) const noexcept; [[nodiscard]] virtual rust::Result posix_spawn( pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) const noexcept; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Logger.cc000066400000000000000000000043161476774233700261000ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Logger.h" #include #include #include namespace { el::log::Level LEVEL = el::log::SILENT; void verbose_message(char const* name, char const* message, char const* variable) { struct timespec ts { 0, 0 }; if (::clock_gettime(CLOCK_REALTIME, &ts) == -1) { // ignore failure, default values will be good } struct tm local_time {}; ::localtime_r(&ts.tv_sec, &local_time); const unsigned long micros = ts.tv_nsec / 1000; const pid_t pid = ::getpid(); ::dprintf(STDERR_FILENO, "[%02d:%02d:%02d.%06ld, el, %d] %s; %s%s\n", local_time.tm_hour, local_time.tm_min, local_time.tm_sec, micros, pid, name, message, variable); } } namespace el::log { void set(Level level) { LEVEL = level; fsync(STDERR_FILENO); } void Logger::debug(char const* message) const noexcept { this->debug(message, ""); } void Logger::debug(char const* message, char const* variable) const noexcept { if (el::log::VERBOSE == LEVEL) { verbose_message(name_, message, variable); } } void Logger::warning(char const* message) const noexcept { if (el::log::VERBOSE == LEVEL) { verbose_message(name_, message, ""); } else { dprintf(STDERR_FILENO, "libexec.so: %s; %s\n", name_, message); } } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Logger.h000066400000000000000000000026011476774233700257350ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once namespace el::log { enum Level { SILENT = 0, VERBOSE = 1 }; // Not MT safe void set(Level); class Logger { public: constexpr explicit Logger(const char *name) noexcept; ~Logger() noexcept = default; void debug(char const *message) const noexcept; void debug(char const *message, char const *variable) const noexcept; void warning(char const *message) const noexcept; private: const char *name_; }; inline constexpr Logger::Logger(const char *name) noexcept : name_(name) { } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Paths.cc000066400000000000000000000055651476774233700257470ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Paths.h" #include "report/libexec/Array.h" namespace { const char *next_path_separator(const char *const current, const char *const end) { auto it = current; while ((it != end) && (*it != OS_PATH_SEPARATOR)) { ++it; } return it; } } namespace el { Paths::Paths(const char *const path) noexcept : begin_(path) , end_(array::end(path)) { } Paths::Iterator Paths::begin() const { if (begin_ == end_) return {*this, nullptr, nullptr}; const auto candidate = next_path_separator(begin_, end_); return {*this, begin_, candidate}; } Paths::Iterator Paths::end() const { return {*this, nullptr, nullptr}; } std::pair Paths::next(const char *const current) const { if (current == end_) return std::make_pair(nullptr, nullptr); const auto begin = std::next(current); if (begin == end_) return std::make_pair(nullptr, nullptr); const auto candidate = next_path_separator(begin, end_); return std::make_pair(begin, candidate); } Paths::Iterator::Iterator(const Paths &paths, const char *begin, const char *end) noexcept : paths_(paths) , begin_(begin) , end_(end) { } Paths::Iterator::value_type Paths::Iterator::operator*() const { return std::string_view(begin_, (end_ - begin_)); } Paths::Iterator Paths::Iterator::operator++(int) { Paths::Iterator result(*this); this->operator++(); return result; } Paths::Iterator &Paths::Iterator::operator++() { const auto&[begin, end] = paths_.next(end_); begin_ = begin; end_ = end; return *this; } bool Paths::Iterator::operator==(const Paths::Iterator &other) const { return &paths_ == &other.paths_ && begin_ == other.begin_ && end_ == other.end_; } bool Paths::Iterator::operator!=(const Paths::Iterator &other) const { return !(this->operator==(other)); } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Paths.h000066400000000000000000000040621476774233700256000ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include #include namespace el { class Paths { public: class Iterator; friend class Iterator; public: explicit Paths(const char *path) noexcept; NON_DEFAULT_CONSTRUCTABLE(Paths) NON_COPYABLE_NOR_MOVABLE(Paths) [[nodiscard]] Iterator begin() const; [[nodiscard]] Iterator end() const; private: [[nodiscard]] std::pair next(const char *current) const; private: const char *const begin_; const char *const end_; }; class Paths::Iterator { public: using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; using value_type = std::string_view; using pointer = value_type const*; using reference = value_type const&; public: Iterator(const Paths &paths, const char *begin, const char *end) noexcept; value_type operator*() const; Iterator operator++(int); Iterator &operator++(); bool operator==(const Iterator &other) const; bool operator!=(const Iterator &other) const; private: const Paths &paths_; const char *begin_; const char *end_; }; }rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Resolver.cc000066400000000000000000000114631476774233700264630ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/libexec/Resolver.h" #include "report/libexec/Array.h" #include "report/libexec/Environment.h" #include "report/libexec/Paths.h" #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif namespace { bool contains_dir_separator(std::string_view const &candidate) { return std::find(candidate.begin(), candidate.end(), OS_DIR_SEPARATOR) != candidate.end(); } } namespace el { Resolver::Resolver() noexcept : result_() { result_[0] = 0; } rust::Result Resolver::from_current_directory(std::string_view const &file) { // copy the input to result. array::copy(file.begin(), file.end() + 1, result_, result_ + PATH_MAX); // check if this is a file struct stat sb {}; ::stat(result_, &sb); if ((sb.st_mode & S_IFMT) != S_IFREG) { return rust::Err(ENOENT); } // check if it's okay to execute. if (0 == ::access(result_, X_OK)) { const char *ptr = result_; return rust::Ok(ptr); } // try to set a meaningful error value. if (0 == ::access(result_, F_OK)) { return rust::Err(EACCES); } return rust::Err(ENOENT); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" rust::Result Resolver::from_path(std::string_view const &file, const char **envp) { if (contains_dir_separator(file)) { // the file contains a dir separator, it is treated as path. return from_current_directory(file); } else { // otherwise use the PATH variable to locate the executable. const char *const paths = el::env::get_env_value(envp, "PATH"); if (paths != nullptr) { return from_search_path(file, paths); } #if defined HAVE_CS_PATH && defined HAVE_CONFSTR // fall back to `confstr` PATH value if the environment has no value. const size_t search_path_length = ::confstr(_CS_PATH, nullptr, 0); if (search_path_length != 0) { char search_path[search_path_length]; if (::confstr(_CS_PATH, search_path, search_path_length) != 0) { return from_search_path(file, search_path); } } #endif return rust::Err(ENOENT); } } #pragma GCC diagnostic pop rust::Result Resolver::from_search_path(std::string_view const &file, const char *search_path) { if (contains_dir_separator(file)) { // the file contains a dir separator, it is treated as path. return from_current_directory(file); } else { // otherwise use the given search path to locate the executable. for (const auto &path : el::Paths(search_path)) { // ignore empty entries if (path.empty()) { continue; } // check if it's possible to assemble a PATH if ((file.size() + path.size() + 2) > PATH_MAX) { continue; } // create a path char candidate[PATH_MAX]; { char *const candidate_begin = candidate; char *const candidate_end = candidate + PATH_MAX; auto it = el::array::copy(path.begin(), path.end(), candidate_begin, candidate_end); *it++ = OS_DIR_SEPARATOR; it = el::array::copy(file.begin(), file.end(), it, candidate_end); *it = 0; } // check if it's okay to execute. if (auto result = from_current_directory(candidate); result.is_ok()) { return result; } } // if all attempt were failing, then quit with a failure. return rust::Err(ENOENT); } } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Resolver.h000066400000000000000000000036361476774233700263300ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include #include namespace el { /** * This class implements the logic how the program execution resolves the * executable path from the system environment. * * The resolution logic implemented as a class to be able to unit test * the code and avoid memory allocation. */ class Resolver { public: Resolver() noexcept; virtual ~Resolver() noexcept = default; /** * Resolve the executable from system environments. * * @return resolved executable path as absolute path. */ [[nodiscard]] virtual rust::Result from_current_directory(std::string_view const &file); [[nodiscard]] virtual rust::Result from_path(std::string_view const &file, const char **envp); [[nodiscard]] virtual rust::Result from_search_path(std::string_view const &file, const char *search_path); NON_COPYABLE_NOR_MOVABLE(Resolver) private: char result_[PATH_MAX]; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Session.cc000066400000000000000000000034121476774233700263000ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "report/libexec/Session.h" #include "report/libexec/Buffer.h" #include "report/libexec/Environment.h" namespace el::session { void from(Session &session, const char **environment) noexcept { if (nullptr == environment) return; session.reporter = env::get_env_value(environment, cmd::library::KEY_REPORTER); session.destination = env::get_env_value(environment, cmd::library::KEY_DESTINATION); session.verbose = env::get_env_value(environment, cmd::library::KEY_VERBOSE) != nullptr; } void persist(Session &session, char *begin, char *end) noexcept { if (!is_valid(session)) return; Buffer buffer(begin, end); session.reporter = buffer.store(session.reporter); session.destination = buffer.store(session.destination); } bool is_valid(Session const &session) noexcept { return (session.reporter != nullptr && session.destination != nullptr); } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/Session.h000066400000000000000000000027361476774233700261520ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once namespace el { class Buffer; /** * Represents an intercept session parameter set. * * It does not own the memory (of the pointed areas). */ struct Session { char const* reporter = nullptr; char const* destination = nullptr; bool verbose = false; }; namespace session { // Util method to initialize instance. void from(Session& session, const char** environment) noexcept; // Util method to store the values. void persist(Session& session, char* begin, char* end) noexcept; // Util method to check if session is initialized. bool is_valid(Session const& session) noexcept; } } rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/lib.cc000066400000000000000000000231011476774233700254200ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "report/libexec/Executor.h" #include "report/libexec/Linker.h" #include "report/libexec/Logger.h" #include "report/libexec/Resolver.h" #include "report/libexec/Session.h" #ifdef HAVE_SPAWN_H #include #endif #if defined HAVE_NSGETENVIRON #include #else extern char **environ; #endif namespace { size_t va_length(va_list& args) { size_t arg_count = 0; while (va_arg(args, const char*) != nullptr) ++arg_count; return arg_count; } void va_copy_n(va_list& args, char* argv[], size_t const argc) { for (size_t idx = 0; idx < argc; ++idx) argv[idx] = va_arg(args, char*); } /** * Abstraction to get the current environment. * * When the dynamic linker loads the library the `environ` variable * might not be available. (This is the case for OSX.) This method * makes it uniform to access the current environment on all platform. * * @return the current environment. */ const char** environment() noexcept { #ifdef HAVE_NSGETENVIRON return const_cast(*_NSGetEnviron()); #else return const_cast(environ); #endif } } /** * Library static data * * Will be initialized, when the library loaded into memory. */ namespace { // This is the only non stack memory that this library is using. constexpr size_t BUFFER_SIZE = PATH_MAX * 2; char BUFFER[BUFFER_SIZE]; // This is used for being multi thread safe (loading time only). std::atomic LOADED(false); // These are related to the functionality of this library. el::Session SESSION; el::Linker LINKER; constexpr el::log::Logger LOGGER("lib.cc"); } /** * Library entry point. * * The first method to call after the library is loaded into memory. */ extern "C" void on_load() __attribute__((constructor)); extern "C" void on_load() { // Test whether on_load was called already. if (LOADED.exchange(true)) return; el::session::from(SESSION, environment()); el::session::persist(SESSION, BUFFER, BUFFER + BUFFER_SIZE); el::log::Level level = SESSION.verbose ? el::log::VERBOSE : el::log::SILENT; el::log::set(level); LOGGER.debug("on_load"); errno = 0; } /** * Library exit point. * * The last method which needs to be called when the library is unloaded. */ extern "C" void on_unload() __attribute__((destructor)); extern "C" void on_unload() { // Test whether on_unload was called already. if (not LOADED.exchange(false)) return; LOGGER.debug("on_unload"); errno = 0; } extern "C" int execve(const char* path, char* const argv[], char* const envp[]) { LOGGER.debug("execve path: ", path); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execve(path, argv, envp); if (result.is_err()) { LOGGER.debug("execve failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int execv(const char* path, char* const argv[]) { LOGGER.debug("execv path: ", path); auto envp = const_cast(environment()); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execve(path, argv, envp); if (result.is_err()) { LOGGER.debug("execv failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int execvpe(const char* file, char* const argv[], char* const envp[]) { LOGGER.debug("execvpe file: ", file); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execvpe(file, argv, envp); if (result.is_err()) { LOGGER.debug("execvpe failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int execvp(const char* file, char* const argv[]) { LOGGER.debug("execvp file: ", file); auto envp = const_cast(environment()); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execvpe(file, argv, envp); if (result.is_err()) { LOGGER.debug("execvp failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int execvP(const char* file, const char* search_path, char* const argv[]) { LOGGER.debug("execvP file: ", file); auto envp = const_cast(environment()); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execvP(file, search_path, argv, envp); if (result.is_err()) { LOGGER.debug("execvP failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int exect(const char* path, char* const argv[], char* const envp[]) { LOGGER.debug("exect path: ", path); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execve(path, argv, envp); if (result.is_err()) { LOGGER.debug("exect failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wvla" extern "C" int execl(const char* path, const char* arg, ...) { LOGGER.debug("execl path: ", path); // Count the number of arguments. va_list ap; va_start(ap, arg); const size_t argc = va_length(ap); va_end(ap); // Copy the arguments to the stack. va_start(ap, arg); char* argv[argc + 2]; argv[0] = const_cast(path); va_copy_n(ap, &argv[1], argc + 1); va_end(ap); auto envp = const_cast(environment()); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execve(path, argv, envp); if (result.is_err()) { LOGGER.debug("execl failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int execlp(const char* file, const char* arg, ...) { LOGGER.debug("execlp file: ", file); // Count the number of arguments. va_list ap; va_start(ap, arg); const size_t argc = va_length(ap); va_end(ap); // Copy the arguments to the stack. va_start(ap, arg); char* argv[argc + 2]; argv[0] = const_cast(file); va_copy_n(ap, &argv[1], argc + 1); va_end(ap); auto envp = const_cast(environment()); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execvpe(file, argv, envp); if (result.is_err()) { LOGGER.debug("execlp failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } // int execle(const char *path, const char *arg, ..., char * const envp[]); extern "C" int execle(const char* path, const char* arg, ...) { LOGGER.debug("execle path: ", path); // Count the number of arguments. va_list ap; va_start(ap, arg); const size_t argc = va_length(ap); va_end(ap); // Copy the arguments to the stack. va_start(ap, arg); char* argv[argc + 2]; argv[0] = const_cast(path); va_copy_n(ap, &argv[1], argc + 1); char** envp = va_arg(ap, char**); va_end(ap); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).execve(path, argv, envp); if (result.is_err()) { LOGGER.debug("execle failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } #pragma GCC diagnostic pop extern "C" int posix_spawn(pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) { LOGGER.debug("posix_spawn path:", path); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).posix_spawn(pid, path, file_actions, attrp, argv, envp); if (result.is_err()) { LOGGER.debug("posix_spawn failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } extern "C" int posix_spawnp(pid_t* pid, const char* file, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]) { LOGGER.debug("posix_spawnp file:", file); el::Resolver resolver; const auto result = el::Executor(LINKER, SESSION, resolver).posix_spawnp(pid, file, file_actions, attrp, argv, envp); if (result.is_err()) { LOGGER.debug("posix_spawnp failed."); errno = result.unwrap_err(); } return result.unwrap_or(-1); } //extern "C" //FILE *popen(const char *command, const char *type) { //} //extern "C" //int execveat(int dirfd, // const char *pathname, // char *const argv[], // char *const envp[], // int flags) { //} //extern "C" //int fexecve(int fd, char *const argv[], char *const envp[]) { //} rizsotto-Bear-14c2e01/source/intercept/source/report/libexec/std.cc000066400000000000000000000022471476774233700254540ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include /** * To avoid undefined symbol from `libc++.so` or `libstdc++.so` define a * no-op delete operator. It's safe to not call `free` because we are not * `malloc` or `new` in this library. * * But it's needed since we were declaring a few classes virtual, which * requires this symbol to be present. */ void operator delete(void*, std::size_t) { } void operator delete(void*) { } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/000077500000000000000000000000001476774233700244135ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/Application.cc000066400000000000000000000226531476774233700271750ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/wrapper/EventFactory.h" #include "report/wrapper/EventReporter.h" #include "report/wrapper/RpcClients.h" #include "report/wrapper/Application.h" #include "libmain/ApplicationLogConfig.h" #include "libsys/Environment.h" #include "libsys/Path.h" #include "libsys/Process.h" #include "libsys/Signal.h" #include #include #include #include #include #include namespace fs = std::filesystem; #ifdef FMT_NEEDS_OSTREAM_FORMATTER template <> struct fmt::formatter : ostream_formatter {}; #endif namespace { struct ApplicationLogConfig : ps::ApplicationLogConfig { ApplicationLogConfig() : ps::ApplicationLogConfig("wrapper", "wr") { } void initForVerbose() const override { spdlog::set_pattern(fmt::format("[%H:%M:%S.%f, wr, {0}, ppid: {1}] %v", getpid(), getppid())); spdlog::set_level(spdlog::level::debug); } }; const ApplicationLogConfig APPLICATION_LOG_CONFIG; bool is_wrapper_call(int argc, const char **argv) { if (argc > 0) { auto cmd = fs::path(argv[0]); auto prg = cmd.filename(); return prg != fs::path("wrapper"); } return false; } namespace Wrapper { rust::Result make_session(const sys::env::Vars &environment) noexcept { auto destination = environment.find(cmd::wrapper::KEY_DESTINATION); return (destination == environment.end()) ? rust::Result(rust::Err(std::runtime_error("Unknown destination."))) : rust::Result(rust::Ok(wr::SessionLocator{destination->second})); } std::list from(const char **argv) { const char** end = argv; while (*end != nullptr) ++end; return {argv, end}; } rust::Result make_execution(const char **argv, sys::env::Vars &&environment) noexcept { auto program = fs::path(argv[0]); auto arguments = from(argv); return sys::path::get_cwd() .map([&program, &arguments, &environment](auto working_dir) { return wr::Execution{program, arguments, working_dir, environment}; }); } } namespace Supervisor { rust::Result make_session(const flags::Arguments &args) noexcept { return args.as_string(cmd::wrapper::FLAG_DESTINATION) .map([](const auto &destination) { return wr::SessionLocator{std::string(destination)}; }); } rust::Result make_execution(const flags::Arguments &args, sys::env::Vars &&environment) noexcept { auto program = args.as_string(cmd::wrapper::FLAG_EXECUTE) .map([](auto file) { return fs::path(file); }); auto arguments = args.as_string_list(cmd::wrapper::FLAG_COMMAND) .map>([](auto args) { return std::list(args.begin(), args.end()); }); auto working_dir = sys::path::get_cwd(); return merge(program, arguments, working_dir) .map([&environment](const auto &tuple) { const auto&[program, arguments, working_dir] = tuple; return wr::Execution{program, arguments, working_dir, environment}; }); } } bool is_exited(const rust::Result &status) { return status .map([](auto _status) { return _status.is_exited(); }) .unwrap_or(true); } } namespace wr { Command::Command(wr::SessionLocator session, wr::Execution execution) noexcept : ps::Command() , session_(std::move(session)) , execution_(std::move(execution)) { } rust::Result Command::execute() const { wr::EventReporter event_reporter(session_); wr::SupervisorClient supervisor_client(session_); return supervisor_client.resolve(execution_) .and_then([&event_reporter](auto execution) { return sys::Process::Builder(execution.executable, true) .add_arguments(execution.arguments.begin(), execution.arguments.end()) .set_environment(execution.environment) .set_redirect_io() .spawn() .on_success([&event_reporter, &execution](auto &child) { event_reporter.report_start(child.get_pid(), execution); }); }) .and_then([&event_reporter](auto child) { sys::SignalForwarder guard(child); while (true) { auto status = child.wait(true) .on_success([&event_reporter](auto exit) { event_reporter.report_wait(exit); }); if (is_exited(status)) { return status; } } }) .map([](auto status) { return status.code().value_or(EXIT_FAILURE); }); } Application::Application() noexcept : ps::Application() , log_config(APPLICATION_LOG_CONFIG) { log_config.initForSilent(); } rust::Result Application::command(int argc, const char **argv, const char **envp) const { if (const bool wrapper = is_wrapper_call(argc, argv); wrapper) { if (const bool verbose = (nullptr != getenv(cmd::wrapper::KEY_VERBOSE)); verbose) { log_config.initForVerbose(); } log_config.record(argv, envp); return Application::from_envs(argc, argv, envp); } else { return Application::parse(argc, argv) .on_success([this, &argv, &envp](const auto& args) { if (args.as_bool(flags::VERBOSE).unwrap_or(false)) { log_config.initForVerbose(); } log_config.record(argv, envp); spdlog::debug("arguments parsed: {0}", args); }) .and_then([&envp](auto args) { // if parsing success, we create the main command and execute it. return Application::from_args(args, envp); }); } } rust::Result Application::from_envs(int, const char **argv, const char **envp) { auto environment = sys::env::from(const_cast(envp)); auto session = Wrapper::make_session(environment); auto execution = Wrapper::make_execution(argv, std::move(environment)); return rust::merge(session, execution) .map([](const auto &tuple) { const auto&[session, execution] = tuple; return std::make_unique(session, execution); }); } rust::Result Application::from_args(const flags::Arguments &args, const char **envp) { auto environment = sys::env::from(const_cast(envp)); auto session = Supervisor::make_session(args); auto execution = Supervisor::make_execution(args, std::move(environment)); return rust::merge(session, execution) .map([](const auto &tuple) { const auto&[session, execution] = tuple; return std::make_unique(session, execution); }); } rust::Result Application::parse(int argc, const char **argv) { const flags::Parser parser("wrapper", cmd::VERSION, { {cmd::wrapper::FLAG_DESTINATION, {1, true, "path to report directory", std::nullopt, std::nullopt}}, {cmd::wrapper::FLAG_EXECUTE, {1, true, "the path to the executable", std::nullopt, std::nullopt}}, {cmd::wrapper::FLAG_COMMAND, {-1, true, "the command arguments", std::nullopt, std::nullopt}}, }); return parser.parse_or_exit(argc, const_cast(argv)); } } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/Application.h000066400000000000000000000035721476774233700270360ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Domain.h" #include "libmain/Application.h" #include "libmain/ApplicationLogConfig.h" #include "libflags/Flags.h" #include "libresult/Result.h" namespace wr { using namespace domain; struct Command : ps::Command { Command(wr::SessionLocator session, wr::Execution execution) noexcept; [[nodiscard]] rust::Result execute() const override; NON_DEFAULT_CONSTRUCTABLE(Command) NON_COPYABLE_NOR_MOVABLE(Command) protected: wr::SessionLocator session_; wr::Execution execution_; }; struct Application : ps::Application { Application() noexcept; rust::Result command(int argc, const char **argv, const char **envp) const override; static rust::Result from_envs(int argc, const char **argv, const char **envp); static rust::Result from_args(const flags::Arguments &args, const char **envp); static rust::Result parse(int argc, const char **argv) ; private: ps::ApplicationLogConfig const &log_config; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/EventFactory.cc000066400000000000000000000055461476774233700273450ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #include "report/wrapper/EventFactory.h" #include "Convert.h" #include #ifdef HAVE_SYS_TIME_H #include #else #include #endif using namespace google::protobuf::util; namespace { google::protobuf::Timestamp now() { #ifdef HAVE_SYS_TIME_H timeval tv; gettimeofday(&tv, nullptr); google::protobuf::Timestamp timestamp; timestamp.set_seconds(tv.tv_sec); timestamp.set_nanos(tv.tv_usec * 1000); return timestamp; #else return TimeUtil::GetCurrentTime(); #endif } std::uint64_t generate_unique_id() { std::random_device random_device; std::mt19937_64 generator(random_device()); std::uniform_int_distribution distribution; return distribution(generator); } } namespace wr { EventFactory::EventFactory() noexcept : rid_(generate_unique_id()) { } rpc::Event EventFactory::start(ProcessId pid, ProcessId ppid, const Execution &execution) const { rpc::Event event; event.set_rid(rid_); event.mutable_timestamp()->CopyFrom(now()); { rpc::Event_Started &event_started = *event.mutable_started(); event_started.set_pid(pid); event_started.set_ppid(ppid); *event_started.mutable_execution() = into(execution); } return event; } rpc::Event EventFactory::signal(int number) const { rpc::Event event; event.set_rid(rid_); event.mutable_timestamp()->CopyFrom(now()); { rpc::Event_Signalled &event_signalled = *event.mutable_signalled(); event_signalled.set_number(number); } return event; } rpc::Event EventFactory::terminate(int code) const { rpc::Event event; event.set_rid(rid_); event.mutable_timestamp()->CopyFrom(now()); { rpc::Event_Terminated &event_terminated = *event.mutable_terminated(); event_terminated.set_status(code); } return event; } } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/EventFactory.h000066400000000000000000000024031476774233700271740ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Domain.h" #include "intercept.pb.h" namespace wr { using namespace domain; class EventFactory { public: EventFactory() noexcept; ~EventFactory() noexcept = default; [[nodiscard]] rpc::Event start(ProcessId pid, ProcessId ppid, const Execution &execution) const; [[nodiscard]] rpc::Event signal(int number) const; [[nodiscard]] rpc::Event terminate(int code) const; private: ReporterId rid_; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/EventReporter.cc000066400000000000000000000027441476774233700275350ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/wrapper/EventReporter.h" namespace wr { EventReporter::EventReporter(const SessionLocator &session_locator) noexcept : event_factory() , client(session_locator) { } void EventReporter::report_start(ProcessId pid, const Execution &execution) { const auto &event = event_factory.start(pid, getppid(), execution); client.report(event); } void EventReporter::report_wait(const sys::ExitStatus exit_status) { const auto event = (exit_status.is_signaled()) ? event_factory.signal(exit_status.signal().value()) : event_factory.terminate(exit_status.code().value()); client.report(event); } } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/EventReporter.h000066400000000000000000000032241476774233700273710ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "Domain.h" #include "report/wrapper/EventFactory.h" #include "report/wrapper/RpcClients.h" #include "libresult/Result.h" #include "libsys/Process.h" namespace wr { /** * Reports events to the interceptor gRPC service. * * Depend on the implementation, it can collect the events and send at the very * end, or it can send it immediately (sync or async). */ class EventReporter { public: explicit EventReporter(const wr::SessionLocator& session_locator) noexcept; ~EventReporter() noexcept = default; void report_start(ProcessId pid, const Execution &execution); void report_wait(sys::ExitStatus exit_status); NON_DEFAULT_CONSTRUCTABLE(EventReporter) NON_COPYABLE_NOR_MOVABLE(EventReporter) private: EventFactory event_factory; InterceptorClient client; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/RpcClients.cc000066400000000000000000000055371476774233700270020ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "report/wrapper/RpcClients.h" #include "Convert.h" #include #include #include namespace { std::runtime_error create_error(const grpc::Status& status) { return std::runtime_error(fmt::format("gRPC call failed: {}", status.error_message().data())); } } namespace wr { SupervisorClient::SupervisorClient(const SessionLocator &session_locator) : channel_(grpc::CreateChannel(session_locator, grpc::InsecureChannelCredentials())) , supervisor_(rpc::Supervisor::NewStub(channel_)) { } rust::Result SupervisorClient::resolve(const wr::Execution &execution) { spdlog::debug("gRPC call requested: supervise::Supervisor::Resolve"); grpc::ClientContext context; rpc::ResolveRequest request; rpc::ResolveResponse response; request.set_allocated_execution(new rpc::Execution(into(execution))); const grpc::Status status = supervisor_->Resolve(&context, request, &response); spdlog::debug("gRPC call [Resolve] finished: {}", status.ok()); return status.ok() ? rust::Result(rust::Ok(from(response.execution()))) : rust::Result(rust::Err(create_error(status))); } InterceptorClient::InterceptorClient(const SessionLocator &session_locator) : channel_(grpc::CreateChannel(session_locator, grpc::InsecureChannelCredentials())) , interceptor_(rpc::Interceptor::NewStub(channel_)) { } rust::Result InterceptorClient::report(const rpc::Event &event) { spdlog::debug("gRPC call requested: supervise::Interceptor::Register"); grpc::ClientContext context; google::protobuf::Empty response; const grpc::Status status = interceptor_->Register(&context, event, &response); spdlog::debug("gRPC call [Register] finished: {}", status.ok()); return status.ok() ? rust::Result(rust::Ok(0)) : rust::Result(rust::Err(create_error(status))); } } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/RpcClients.h000066400000000000000000000035711476774233700266400ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "Domain.h" #include "report/wrapper/EventFactory.h" #include "libresult/Result.h" #include #include #include "intercept.grpc.pb.h" #include "supervise.grpc.pb.h" namespace wr { using namespace domain; class SupervisorClient { public: explicit SupervisorClient(const wr::SessionLocator& session_locator); rust::Result resolve(const wr::Execution &); NON_DEFAULT_CONSTRUCTABLE(SupervisorClient) NON_COPYABLE_NOR_MOVABLE(SupervisorClient) private: std::shared_ptr<::grpc::Channel> channel_; std::unique_ptr supervisor_; }; class InterceptorClient { public: explicit InterceptorClient(const wr::SessionLocator& session_locator); rust::Result report(const rpc::Event &); NON_DEFAULT_CONSTRUCTABLE(InterceptorClient) NON_COPYABLE_NOR_MOVABLE(InterceptorClient) private: std::shared_ptr<::grpc::Channel> channel_; std::unique_ptr interceptor_; }; } rizsotto-Bear-14c2e01/source/intercept/source/report/wrapper/main.cc000066400000000000000000000017041476774233700256500ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libmain/main.h" #include "report/wrapper/Application.h" int main(int argc, char* argv[], char* envp[]) { return ps::main(argc, argv, envp); } rizsotto-Bear-14c2e01/source/intercept/test/000077500000000000000000000000001476774233700210775ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/test/EventFactoryTest.cc000066400000000000000000000034711476774233700246640ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/wrapper/EventFactory.h" namespace { const wr::ProcessId TEST_PID = 11; const wr::ProcessId TEST_PPID = 10; const wr::Execution TEST_EXECUTION = wr::Execution { fs::path("/usr/bin/ls"), {"ls", "-la"}, fs::path("/home/user"), { {"PATH", "/usr/bin:/usr/sbin"} } }; TEST(event_factory, same_factory_creates_events_with_same_id) { wr::EventFactory sut; auto start = sut.start(TEST_PID, TEST_PPID, TEST_EXECUTION); auto signal = sut.signal(11); auto stop = sut.terminate(5); EXPECT_EQ(start.rid(), signal.rid()); EXPECT_EQ(start.rid(), stop.rid()); } TEST(event_factory, different_factory_creates_event_with_different_id) { wr::EventFactory sut1; auto start1 = sut1.start(TEST_PID, TEST_PPID, TEST_EXECUTION); wr::EventFactory sut2; auto start2 = sut2.start(TEST_PID, TEST_PPID, TEST_EXECUTION); EXPECT_NE(start1.rid(), start2.rid()); } } rizsotto-Bear-14c2e01/source/intercept/test/SessionTest.cc000066400000000000000000000060731476774233700236770ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "collect/Session.h" namespace { struct SessionFixture : ic::Session { public: MOCK_METHOD( rust::Result, resolve, (const ic::Execution &input), (const, override)); MOCK_METHOD( (sys::Process::Builder), supervise, (const ic::Execution &), (const, override)); using Session::keep_front_in_path; using Session::remove_from_path; }; TEST(session, remove_from_path) { EXPECT_EQ("", SessionFixture::remove_from_path("/opt", "")); EXPECT_EQ("", SessionFixture::remove_from_path("/opt", "/opt")); EXPECT_EQ("", SessionFixture::remove_from_path("/opt", "/opt:/opt")); EXPECT_EQ("/usr/bin:/usr/local/bin", SessionFixture::remove_from_path("/opt", "/usr/bin:/usr/local/bin")); EXPECT_EQ("/usr/bin:/usr/local/bin", SessionFixture::remove_from_path("/opt", "/opt:/usr/bin:/usr/local/bin")); EXPECT_EQ("/usr/bin:/usr/local/bin", SessionFixture::remove_from_path("/opt", "/usr/bin:/opt:/usr/local/bin")); EXPECT_EQ("/usr/bin:/usr/local/bin", SessionFixture::remove_from_path("/opt", "/usr/bin:/usr/local/bin:/opt")); } TEST(session, keep_front_in_path) { EXPECT_EQ("/opt", SessionFixture::keep_front_in_path("/opt", "")); EXPECT_EQ("/opt", SessionFixture::keep_front_in_path("/opt", "/opt")); EXPECT_EQ("/opt", SessionFixture::keep_front_in_path("/opt", "/opt:/opt")); EXPECT_EQ("/opt:/usr/bin:/usr/local/bin", SessionFixture::keep_front_in_path("/opt", "/usr/bin:/usr/local/bin")); EXPECT_EQ("/opt:/usr/bin:/usr/local/bin", SessionFixture::keep_front_in_path("/opt", "/opt:/usr/bin:/usr/local/bin")); EXPECT_EQ("/opt:/usr/bin:/usr/local/bin", SessionFixture::keep_front_in_path("/opt", "/usr/bin:/opt:/usr/local/bin")); EXPECT_EQ("/opt:/usr/bin:/usr/local/bin", SessionFixture::keep_front_in_path("/opt", "/usr/bin:/usr/local/bin:/opt")); } }rizsotto-Bear-14c2e01/source/intercept/test/libexec/000077500000000000000000000000001476774233700225125ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/intercept/test/libexec/ArrayTest.cc000066400000000000000000000062511476774233700247430ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/libexec/Array.h" namespace { TEST(array_end, dont_crash_on_nullptr) { const char** input = nullptr; EXPECT_EQ(nullptr, el::array::end(input)); } TEST(array_end, dont_crash_on_empty) { const char* input[] = { nullptr }; EXPECT_EQ(&input[0], el::array::end(input)); } TEST(array_end, works_on_strings) { const char* input = "hello"; EXPECT_EQ(input + 5, el::array::end(input)); } TEST(array_end, finds_the_last_one) { const char* input0 = "this"; const char* input1 = "that"; const char* input[] = { input0, input1, 0 }; EXPECT_EQ(&input[2], el::array::end(input)); } TEST(array_length, dont_crash_on_nullptr) { const char** input = nullptr; EXPECT_EQ(0, el::array::length(input)); } TEST(array_length, dont_crash_on_empty) { const char* input[] = { nullptr }; EXPECT_EQ(0, el::array::length(input)); } TEST(array_length, finds_the_last_one) { const char* input0 = "this"; const char* input1 = "that"; const char* input[] = { input0, input1, 0 }; EXPECT_EQ(2, el::array::length(input)); } TEST(array_length, works_on_strings) { const char* input = "hello"; EXPECT_EQ(5, el::array::length(input)); } TEST(array_copy, works_with_zero_length_input) { const char src[5] = ""; char dst[8] = {}; auto result = el::array::copy(src, src, dst, dst + 8); EXPECT_EQ(dst, result); } TEST(array_copy, does_copy_elements_over) { const char src[5] = "this"; char dst[8] = {}; auto result = el::array::copy(src, src + 5, dst, dst + 8); EXPECT_NE(result, nullptr); EXPECT_EQ((dst + 5), result); EXPECT_STREQ(src, dst); } TEST(array_copy, does_copy_elements_into_same_size) { const char src[5] = "this"; char dst[5] = {}; auto result = el::array::copy(src, src + 5, dst, dst + 5); EXPECT_NE(result, nullptr); EXPECT_EQ((dst + 5), result); EXPECT_STREQ(src, dst); } TEST(array_copy, stops_when_short) { const char src[5] = "this"; char dst[8] = {}; auto result = el::array::copy(src, src + 5, dst, dst + 3); EXPECT_EQ(nullptr, result); } } rizsotto-Bear-14c2e01/source/intercept/test/libexec/BufferTest.cc000066400000000000000000000040421476774233700250720ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/libexec/Buffer.h" namespace { TEST(Buffer, dont_crash_on_nullptr) { char buffer[64]; el::Buffer sut(buffer, buffer + 64); EXPECT_EQ(nullptr, sut.store(nullptr)); } TEST(Buffer, stores) { char buffer[64]; el::Buffer sut(buffer, buffer + 64); const char* literal = "Hi there people"; EXPECT_STREQ(literal, sut.store(literal)); } TEST(Buffer, not_same_ptr) { char buffer[64]; el::Buffer sut(buffer, buffer + 64); const char* literal = "Hi there people"; EXPECT_NE(literal, sut.store(literal)); } TEST(Buffer, works_multiple_times) { char buffer[64]; el::Buffer sut(buffer, buffer + 64); const char* literal0 = "Hi there people"; const char* literal1 = "Hallo Leute"; const char* result0 = sut.store(literal0); const char* result1 = sut.store(literal1); EXPECT_STREQ(literal0, result0); EXPECT_STREQ(literal1, result1); } TEST(Buffer, handles_size_issue) { char buffer[8]; el::Buffer sut(buffer, buffer + 8); const char* literal = "Hi there people"; EXPECT_EQ(nullptr, sut.store(literal)); } } rizsotto-Bear-14c2e01/source/intercept/test/libexec/ExecutorTest.cc000066400000000000000000000317351476774233700254700ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/libexec/Executor.h" #include "ResolverMock.h" #include "LinkerMock.h" #include "report/libexec/Session.h" #include "report/libexec/Array.h" #include using ::testing::_; using ::testing::Args; using ::testing::ElementsAre; using ::testing::ElementsAreArray; using ::testing::Matcher; using ::testing::NotNull; using ::testing::Return; namespace { const char* LS_PATH = "/usr/bin/ls"; char* LS_FILE = const_cast("ls"); char* LS_ARGV[] = { const_cast("ls"), const_cast("-l"), nullptr }; char* LS_ENVP[] = { const_cast("PATH=/usr/bin:/usr/sbin"), nullptr }; char SEARCH_PATH[] = "/usr/bin:/usr/sbin"; el::Session SILENT_SESSION = { "/usr/bin/intercept", "/tmp/intercept.random", false }; el::Session VERBOSE_SESSION = { "/usr/bin/intercept", "/tmp/intercept.random", true }; MATCHER_P(CStyleArrayEqual, expecteds, "") { size_t idx = 0; for (const auto &expected: expecteds) { if (std::string_view(arg[idx]) != std::string_view(expected)) { *result_listener << "expected: " << expected << ", but got: " << arg[idx]; return false; } ++idx; } return true; } TEST(Executor, fails_without_session) { const rust::Result expected = rust::Err(EIO); el::Session session; LinkerMock linker; EXPECT_CALL(linker, execve(_, _, _)).Times(0); EXPECT_CALL(linker, posix_spawn(_, _, _, _, _, _)).Times(0); ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(_)).Times(0); EXPECT_CALL(resolver, from_path(_, _)).Times(0); EXPECT_CALL(resolver, from_search_path(_, _)).Times(0); EXPECT_EQ(expected, el::Executor(linker, session, resolver).execve(LS_PATH, LS_ARGV, LS_ENVP)); EXPECT_EQ(expected, el::Executor(linker, session, resolver).execvpe(LS_FILE, LS_ARGV, LS_ENVP)); EXPECT_EQ(expected, el::Executor(linker, session, resolver).execvP(LS_FILE, SEARCH_PATH, LS_ARGV, LS_ENVP)); pid_t pid; EXPECT_EQ(expected, el::Executor(linker, session, resolver).posix_spawn(&pid, LS_PATH, nullptr, nullptr, LS_ARGV, LS_ENVP)); EXPECT_EQ(expected, el::Executor(linker, session, resolver).posix_spawnp(&pid, LS_FILE, nullptr, nullptr, LS_ARGV, LS_ENVP)); } TEST(Executor, execve_silent_library) { const rust::Result expected = rust::Ok(0); ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(testing::Eq(std::string_view(LS_PATH)))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker,execve(SILENT_SESSION.reporter, CStyleArrayEqual(std::vector { SILENT_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, SILENT_SESSION.destination, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, SILENT_SESSION, resolver).execve(LS_PATH, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, execve_verbose_library) { const rust::Result expected = rust::Ok(0); ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(testing::Eq(std::string_view(LS_PATH)))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker, execve(VERBOSE_SESSION.reporter, CStyleArrayEqual(std::vector { VERBOSE_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, VERBOSE_SESSION.destination, cmd::wrapper::FLAG_VERBOSE, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).execve(LS_PATH, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, execvpe_fails_on_resolve) { const rust::Result expected = rust::Err(ENOENT); ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(testing::Eq(std::string_view(LS_PATH)))) .Times(1) .WillOnce(Return(rust::Result(rust::Err(ENOENT)))); LinkerMock linker; EXPECT_CALL(linker, execve(_, _, _)).Times(0); EXPECT_CALL(linker, posix_spawn(_, _, _, _, _, _)).Times(0); auto result = el::Executor(linker, SILENT_SESSION, resolver).execve(LS_PATH, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, execvpe_passes) { const rust::Result expected = rust::Ok(0); ResolverMock resolver; EXPECT_CALL(resolver, from_path(testing::Eq(std::string_view(LS_FILE)), testing::Eq(LS_ENVP))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker, execve(VERBOSE_SESSION.reporter, CStyleArrayEqual(std::vector { VERBOSE_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, VERBOSE_SESSION.destination, cmd::wrapper::FLAG_VERBOSE, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).execvpe(LS_FILE, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, execvp2_passes) { const rust::Result expected = rust::Ok(0); ResolverMock resolver; EXPECT_CALL(resolver, from_search_path(testing::Eq(std::string_view(LS_FILE)), testing::Eq(SEARCH_PATH))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker, execve(VERBOSE_SESSION.reporter, CStyleArrayEqual(std::vector { VERBOSE_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, VERBOSE_SESSION.destination, cmd::wrapper::FLAG_VERBOSE, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).execvP(LS_FILE, SEARCH_PATH, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, spawn_passes) { const rust::Result expected = rust::Ok(0); pid_t pid; ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(testing::Eq(std::string_view(LS_PATH)))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker, posix_spawn(&pid, VERBOSE_SESSION.reporter, nullptr, nullptr, CStyleArrayEqual(std::vector { VERBOSE_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, VERBOSE_SESSION.destination, cmd::wrapper::FLAG_VERBOSE, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).posix_spawn(&pid, LS_PATH, nullptr, nullptr, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, spawn_fails_on_access) { const rust::Result expected = rust::Err(ENOENT); pid_t pid; ResolverMock resolver; EXPECT_CALL(resolver, from_current_directory(testing::Eq(std::string_view(LS_PATH)))) .Times(1) .WillOnce(Return(rust::Result(rust::Err(ENOENT)))); LinkerMock linker; EXPECT_CALL(linker, execve(_, _, _)).Times(0); EXPECT_CALL(linker, posix_spawn(_, _, _, _, _, _)).Times(0); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).posix_spawn(&pid, LS_PATH, nullptr, nullptr, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } TEST(Executor, spawnp_passes) { const rust::Result expected = rust::Ok(0); pid_t pid; ResolverMock resolver; EXPECT_CALL(resolver, from_path(testing::Eq(std::string_view(LS_FILE)), testing::Eq(LS_ENVP))) .Times(1) .WillOnce(Return(rust::Result(rust::Ok(LS_PATH)))); LinkerMock linker; EXPECT_CALL(linker, posix_spawn(&pid, VERBOSE_SESSION.reporter, nullptr, nullptr, CStyleArrayEqual(std::vector { VERBOSE_SESSION.reporter, cmd::wrapper::FLAG_DESTINATION, VERBOSE_SESSION.destination, cmd::wrapper::FLAG_VERBOSE, cmd::wrapper::FLAG_EXECUTE, LS_PATH, cmd::wrapper::FLAG_COMMAND, LS_ARGV[0], LS_ARGV[1] }), LS_ENVP)) .Times(1) .WillOnce(Return(expected)); auto result = el::Executor(linker, VERBOSE_SESSION, resolver).posix_spawnp(&pid, LS_FILE, nullptr, nullptr, LS_ARGV, LS_ENVP); EXPECT_EQ(expected, result); } } rizsotto-Bear-14c2e01/source/intercept/test/libexec/LinkerMock.h000066400000000000000000000026311476774233700247230ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "report/libexec/Linker.h" #include "gmock/gmock.h" class LinkerMock : public el::Linker { public: MOCK_METHOD( (rust::Result), execve, (const char* path, char* const argv[], char* const envp[]), (const, noexcept, override) ); MOCK_METHOD( (rust::Result), posix_spawn, ( pid_t* pid, const char* path, const posix_spawn_file_actions_t* file_actions, const posix_spawnattr_t* attrp, char* const argv[], char* const envp[]), (const, noexcept, override) ); }; rizsotto-Bear-14c2e01/source/intercept/test/libexec/PathsTest.cc000066400000000000000000000044001476774233700247360ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/libexec/Paths.h" namespace { TEST(PathIterator, works_on_empty) { el::Paths paths(""); for (auto path : paths) { EXPECT_EQ(std::string_view("shall not match"), path); } } TEST(PathIterator, works_on_single) { el::Paths paths("/bin"); for (auto path : paths) { EXPECT_EQ(path, std::string_view("/bin")); } } TEST(PathIterator, works_on_multiple) { size_t count = 0; el::Paths paths("/bin:/sbin:/usr/bin:/usr/sbin"); for (auto path : paths) { EXPECT_FALSE(path.empty()); ++count; } EXPECT_EQ(4, count); el::Paths::Iterator it = paths.begin(); el::Paths::Iterator end = paths.end(); EXPECT_NE(it, end); EXPECT_EQ(std::string_view("/bin"), *(it++)); EXPECT_NE(it, end); EXPECT_EQ(std::string_view("/sbin"), *it); it++; EXPECT_NE(it, end); EXPECT_EQ(std::string_view("/usr/bin"), *it); ++it; EXPECT_NE(it, end); EXPECT_EQ(std::string_view("/usr/sbin"), *it); ++it; EXPECT_EQ(it, end); } TEST(PathIterator, works_with_empty_values) { size_t count = 0; size_t empty = 0; el::Paths paths("/bin::/sbin::"); for (auto path : paths) { ++count; empty += path.empty() ? 1 : 0; } EXPECT_EQ(4, count); EXPECT_EQ(2, empty); } }rizsotto-Bear-14c2e01/source/intercept/test/libexec/ResolverMock.h000066400000000000000000000026411476774233700253010ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "report/libexec/Resolver.h" #include "gmock/gmock.h" class ResolverMock : public el::Resolver { public: MOCK_METHOD( (rust::Result), from_current_directory, (std::string_view const &), (override) ); MOCK_METHOD( (rust::Result), from_path, (std::string_view const &, const char **), (override) ); MOCK_METHOD( (rust::Result), from_search_path, (std::string_view const &, const char *), (override) ); }; rizsotto-Bear-14c2e01/source/intercept/test/libexec/SessionTest.cc000066400000000000000000000046171476774233700253140ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "report/libexec/Session.h" namespace { TEST(Session, dont_crash_on_nullptr) { el::Session sut {}; el::session::from(sut, nullptr); ASSERT_FALSE(el::session::is_valid(sut)); } TEST(Session, capture_on_empty) { const char* envp[] = { "this=is", "these=are", nullptr }; el::Session sut {}; el::session::from(sut, envp); ASSERT_FALSE(el::session::is_valid(sut)); } TEST(Session, capture_silent) { const char* envp[] = { "INTERCEPT_LIBRARY=/usr/libexec/libexec.so", "INTERCEPT_REPORT_DESTINATION=/tmp/intercept.random", "INTERCEPT_REPORT_COMMAND=/usr/bin/intercept", nullptr }; el::Session sut {}; el::session::from(sut, envp); ASSERT_TRUE(el::session::is_valid(sut)); EXPECT_STREQ("/tmp/intercept.random", sut.destination); EXPECT_STREQ("/usr/bin/intercept", sut.reporter); EXPECT_EQ(false, sut.verbose); } TEST(Session, capture_verbose) { const char* envp[] = { "INTERCEPT_LIBRARY=/usr/libexec/libexec.so", "INTERCEPT_REPORT_DESTINATION=/tmp/intercept.random", "INTERCEPT_REPORT_COMMAND=/usr/bin/intercept", "INTERCEPT_VERBOSE=true", nullptr }; el::Session sut {}; el::session::from(sut, envp); ASSERT_TRUE(el::session::is_valid(sut)); EXPECT_STREQ("/tmp/intercept.random", sut.destination); EXPECT_STREQ("/usr/bin/intercept", sut.reporter); EXPECT_EQ(true, sut.verbose); } }rizsotto-Bear-14c2e01/source/libflags/000077500000000000000000000000001476774233700177065ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libflags/CMakeLists.txt000066400000000000000000000011271476774233700224470ustar00rootroot00000000000000add_library(flags_a OBJECT) target_include_directories(flags_a PUBLIC include/) target_sources(flags_a PRIVATE source/Flags.cc INTERFACE $ ) target_link_libraries(flags_a PUBLIC result_a fmt::fmt) if (ENABLE_UNIT_TESTS) add_executable(flags_unit_test test/FlagsTest.cc ) target_link_libraries(flags_unit_test flags_a) target_link_libraries(flags_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::flags_unit_test COMMAND $) endif ()rizsotto-Bear-14c2e01/source/libflags/include/000077500000000000000000000000001476774233700213315ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libflags/include/libflags/000077500000000000000000000000001476774233700231145ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libflags/include/libflags/Flags.h000066400000000000000000000123171476774233700243250ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include #include #include #include #include #include #include namespace flags { constexpr char HELP[] = "--help"; constexpr char VERSION[] = "--version"; constexpr char VERBOSE[] = "--verbose"; constexpr char COMMAND[] = "command"; class Parser; // Represents a successful parsing result. // // Instance can be created by the `Parser` object `parse` method. The flag // values can be queried by the `as_*` methods, which are returning result // objects. // // The object is hold references to the parser input. class Arguments { public: [[nodiscard]] rust::Result as_bool(const std::string_view& key) const; [[nodiscard]] rust::Result as_string(const std::string_view& key) const; [[nodiscard]] rust::Result> as_string_list(const std::string_view& key) const; public: NON_DEFAULT_CONSTRUCTABLE(Arguments) private: using Parameter = std::vector; using Parameters = std::map; friend class Parser; friend std::ostream& operator<<(std::ostream&, const Arguments&); Arguments(std::string_view program, Parameters&& parameters); private: std::string_view program_; Parameters parameters_; }; std::ostream& operator<<(std::ostream&, const Arguments&); // Represent instruction how the associated parsing option shall be interpreted. // // `arguments` tells how many argument it has. // - negative value represent zero or more. // - zero value represent zero // - positive value represent exact number of arguments // `required` tells that it is a mandatory option. // `help` is a short message about the option. // `default_value` is a string representation of the value it will have if the // user was not given any. // `group_name` is a label like name, which is used to group flags and option // which are semantically belongs together. struct Option { int arguments; bool required; const std::string_view help; const std::optional default_value; const std::optional group_name; }; using OptionMap = std::map; using OptionValue = OptionMap::value_type; // Represents a command line parser. // // Why write another one when `getopt` is available. Simply because `getopt` is // not standard enough across operating systems. // // Usage of the parser is the following: // - Create it on the stack. (Make sure all passed parameter outlives the parser) // - Call the `parse` or `parse_or_exit` method. (Can call the same parser multiple // times with different arguments. Note that the result `Arguments` object will // holds reference to the input.) // // Functionalities: // - It adds `--help` flag automatically to every parser. Which will produce a // usage description in case of `parse_or_exit` method is called. // - It adds `--version` flag, which will produce a simple output if `parse_or_exit` // method is called. // - It adds `--verbose` flag automatically to every parser. Which will appear in // the result `Arguments` object. // - Sub-command can be created by passing parser objects. class Parser { public: Parser(std::string_view name, std::string_view version, std::initializer_list options); Parser(std::string_view name, std::initializer_list options); Parser(std::string_view name, std::string_view version, std::initializer_list commands, std::initializer_list default_options = {}); ~Parser() = default; rust::Result parse(int argc, const char** argv) const; rust::Result parse_or_exit(int argc, const char** argv) const; void print_help(const Parser*, std::ostream&) const; void print_usage(const Parser*, std::ostream&) const; void print_version(std::ostream&) const; public: NON_DEFAULT_CONSTRUCTABLE(Parser) private: const std::string_view name_; const std::string_view version_; OptionMap options_; std::list commands_; }; } rizsotto-Bear-14c2e01/source/libflags/source/000077500000000000000000000000001476774233700212065ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libflags/source/Flags.cc000066400000000000000000000345141476774233700225600ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libflags/Flags.h" #include #include #include #include #include #include #include #include namespace { constexpr char QUERY_GROUP[] = "query options"; std::optional> take(const flags::Option& option, const char** const begin, const char** const end) noexcept { return (option.arguments < 0) ? std::optional(std::make_tuple(begin, end)) : (begin + option.arguments > end) ? std::nullopt : std::optional(std::make_tuple(begin, begin + option.arguments)); } std::list order_by_relevance(const flags::OptionMap& options, const std::optional& group) { std::list result; std::copy_if(std::begin(options), std::end(options), std::back_inserter(result), [&group](auto& option) { return option.second.group_name == group && option.second.arguments >= 0; }); std::copy_if(std::begin(options), std::end(options), std::back_inserter(result), [&group](auto& option) { return option.second.group_name == group && option.second.arguments < 0; }); return result; } std::list> group_by(const flags::OptionMap& options) { // find out what are the option groups. std::set> groups; for (const auto& [_, option] : options) { groups.emplace(option.group_name); } std::list> result; // insert to the result list. for (auto& group : groups) { result.emplace_back(order_by_relevance(options, group)); } return result; } std::string format_parameters(const flags::Option& option) { switch (option.arguments) { case 0: return ""; case 1: return " "; case 2: return " >"; case 3: return " "; default: return " ..."; } } void format_options(std::ostream& os, const std::list& options) { for (auto& it : options) { const auto& [flag, option] = it; const std::string parameters = format_parameters(option); const std::string short_help = option.required ? fmt::format(" {0}{1}", flag, parameters) : fmt::format(" [{0}{1}]", flag, parameters); os << short_help; } } void format_options_long(std::ostream& os, const std::list& main_options) { for (auto& it : main_options) { const auto& [flag, option] = it; const std::string flag_name = fmt::format(" {0}{1}", flag, format_parameters(option)); const size_t flag_size = flag_name.length(); // print flag name os << flag_name; // decide if the help text goes into the same line or not if (flag_size > 22) { os << std::endl << std::string(15, ' '); } else { os << std::string(23 - flag_size, ' '); } os << option.help; // print default value if exists if (option.default_value) { os << " (default: " << option.default_value.value() << ')'; } os << std::endl; } } } namespace flags { Arguments::Arguments(std::string_view program, Arguments::Parameters&& parameters) : program_(program) , parameters_(parameters) { } rust::Result Arguments::as_bool(const std::string_view& key) const { return rust::Ok(parameters_.find(key) != parameters_.end()); } rust::Result Arguments::as_string(const std::string_view& key) const { if (auto values = parameters_.find(key); values != parameters_.end()) { return (values->second.size() == 1) ? rust::Result( rust::Ok(values->second.front())) : rust::Result( rust::Err(std::runtime_error( fmt::format("Parameter \"{0}\" is not a single string.", key)))); } return rust::Err(std::runtime_error( fmt::format("Parameter \"{0}\" is not available.", key))); } rust::Result> Arguments::as_string_list(const std::string_view& key) const { if (auto values = parameters_.find(key); values != parameters_.end()) { return rust::Ok(values->second); } return rust::Err(std::runtime_error( fmt::format("Parameter \"{0}\" is not available.", key))); } std::ostream& operator<<(std::ostream& os, const Arguments& args) { os << '{'; os << "program: " << args.program_ << ", arguments: ["; for (auto arg_it = args.parameters_.begin(); arg_it != args.parameters_.end(); ++arg_it) { if (arg_it != args.parameters_.begin()) { os << ", "; } os << '{' << arg_it->first << ": ["; for (auto param_it = arg_it->second.begin(); param_it != arg_it->second.end(); ++param_it) { if (param_it != arg_it->second.begin()) { os << ", "; } os << *param_it; } os << "]}"; } os << "]}"; return os; } Parser::Parser(std::string_view name, std::string_view version, std::initializer_list options) : name_(name) , version_(version) , options_(options) , commands_() { options_.insert({ VERBOSE, { 0, false, "run in verbose mode", std::nullopt, std::nullopt } }); options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); options_.insert({ VERSION, { 0, false, "print version and exit", std::nullopt, { QUERY_GROUP } } }); } Parser::Parser(std::string_view name, std::initializer_list options) : name_(name) , version_() , options_(options) , commands_() { options_.insert({ VERBOSE, { 0, false, "run in verbose mode", std::nullopt, std::nullopt } }); options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); } Parser::Parser(std::string_view name, std::string_view version, std::initializer_list commands, std::initializer_list default_options) : name_(name) , version_(version) , options_(default_options) , commands_(commands) { if (default_options.size() != 0) { options_.insert({ VERBOSE, { 0, false, "run in verbose mode", std::nullopt, std::nullopt } }); } options_.insert({ HELP, { 0, false, "print help and exit", std::nullopt, { QUERY_GROUP } } }); options_.insert({ VERSION, { 0, false, "print version and exit", std::nullopt, { QUERY_GROUP } } }); } rust::Result Parser::parse(const int argc, const char** argv) const { if (argc < 1 || argv == nullptr) { return rust::Err(std::runtime_error("Empty argument list.")); } if (!commands_.empty() && argc >= 2) { const std::string_view command = argv[1]; const auto sub_command = std::find_if(commands_.begin(), commands_.end(), [&command](auto candidate) { return candidate.name_ == command; }); if (sub_command != commands_.end()) { return sub_command->parse(argc - 1, argv + 1) .map([&sub_command](auto arguments) { arguments.parameters_[COMMAND] = {sub_command->name_}; return arguments; }); } } std::string_view program(argv[0]); Arguments::Parameters parameters; const char** const args_end = argv + argc; for (const char** args_it = ++argv; args_it != args_end;) { // find which option is it. if (auto option = options_.find(*args_it); option != options_.end()) { // take the required number of arguments if founded. if (const auto params = take(option->second, args_it + 1, args_end); params) { const auto& [begin, end] = params.value(); auto args = std::vector(begin, end); if (auto it = parameters.find(option->first); parameters.end() != it) { std::copy(args.begin(), args.end(), std::back_inserter(it->second)); } else { parameters.emplace(option->first, args); } args_it = end; } else { return rust::Err(std::runtime_error( fmt::format("Not enough parameters for: \"{0}\"", *args_it))); } } else { return rust::Err(std::runtime_error( fmt::format("Unrecognized parameter: \"{0}\"", *args_it))); } } // add default values to the parameters as it would given by the user. for (const auto& [flag, option] : options_) { if (option.default_value.has_value() && parameters.find(flag) == parameters.end()) { std::vector args = { option.default_value.value() }; parameters.emplace(flag, args); } } // if this is not a help or version query, then validate the parameters strict. if (parameters.find(HELP) == parameters.end() && parameters.find(VERSION) == parameters.end()) { for (const auto& [flag, option] : options_) { // check if the parameter is required, but not present. if (option.required && parameters.find(flag) == parameters.end()) { return rust::Err(std::runtime_error( fmt::format("Parameter is required, but not given: \"{0}\"", flag))); } } } return rust::Ok(Arguments(program, std::move(parameters))); } rust::Result Parser::parse_or_exit(int argc, const char** argv) const { auto sub_command = [this](const std::string_view &name) -> const Parser * { const auto it = std::find_if(commands_.begin(), commands_.end(), [&name](auto command) { return command.name_ == name; }); return (it != commands_.end()) ? &(*it) : nullptr; }; return parse(argc, argv) // print error if anything bad happens. .on_error([this](auto error) { std::cerr << error.what() << std::endl; print_usage(nullptr, std::cerr); exit(EXIT_FAILURE); }) // if parsing success, check for the `--help` and `--version` flags .on_success([this, &sub_command](auto args) { // print version message and exit zero if (args.as_bool(VERSION).unwrap_or(false)) { print_version(std::cout); exit(EXIT_SUCCESS); } // print help message and exit zero if (args.as_bool(HELP).unwrap_or(false)) { if (const auto command = args.as_string(COMMAND); command.is_ok()) { print_help(sub_command(command.unwrap()), std::cout); } else { print_help(nullptr, std::cout); } exit(EXIT_SUCCESS); } }); } void Parser::print_help(const Parser *const sub_command, std::ostream& os) const { print_usage(sub_command, os); const Parser &parser = (sub_command != nullptr) ? *sub_command : *this; // print commands if exists. if (!parser.commands_.empty()) { os << std::endl << "commands" << std::endl; for (const auto& command : parser.commands_) { os << " " << command.name_ << std::endl; } } // print options const std::list> options = group_by(parser.options_); for (const auto& group : options) { os << std::endl; if (auto group_name = group.front().second.group_name; group_name) { os << group_name.value() << std::endl; } format_options_long(os, group); } } void Parser::print_usage(const Parser *const sub_command, std::ostream& os) const { os << "Usage: " << name_; // check for the given command if (sub_command != nullptr) { os << " " << sub_command->name_; const auto options = order_by_relevance(sub_command->options_, std::nullopt); format_options(os, options); } else { if (!commands_.empty()) { os << " "; } const auto options = order_by_relevance(options_, std::nullopt); format_options(os, options); } os << std::endl; } void Parser::print_version(std::ostream& os) const { os << name_ << " " << version_ << std::endl; } } rizsotto-Bear-14c2e01/source/libflags/test/000077500000000000000000000000001476774233700206655ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libflags/test/FlagsTest.cc000066400000000000000000000375611476774233700231040ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libflags/Flags.h" #include #include using namespace flags; namespace { constexpr char HELP[] = "--help"; constexpr char FLAG[] = "--flag"; constexpr char OPTION[] = "--option"; constexpr char OPTIONS[] = "--options"; constexpr char SEPARATOR[] = "--"; TEST(flags, parse_successful) { const char* argv[] = { "executable", FLAG, OPTION, "0", OPTIONS, "1", "2", "3", SEPARATOR, "4", "5" }; const int argc = sizeof(argv) / sizeof(const char*); const Parser sut("test", "version", { {FLAG, {0, false, "a single flag", std::nullopt, std::nullopt}}, {OPTION, {1, false, "a flag with a value", std::nullopt, std::nullopt}}, {OPTIONS, {3, false, "a flag with 3 values", std::nullopt, std::nullopt}}, {SEPARATOR, {-1, false, "rest of the arguments", std::nullopt, std::nullopt}} }); sut.parse(argc, const_cast(argv)) .on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_FALSE(params.as_bool(HELP).unwrap_or(true)); EXPECT_TRUE(params.as_bool(FLAG).is_ok()); EXPECT_TRUE(params.as_bool(FLAG).unwrap_or(true)); auto option = params.as_string(OPTION); EXPECT_TRUE(option.is_ok()); EXPECT_STREQ(option.unwrap_or("").data(), "0"); std::vector expected_options = { "1", "2", "3" }; auto options = params.as_string_list(OPTIONS); EXPECT_TRUE(options.is_ok()); EXPECT_EQ(expected_options, options.unwrap_or({})); std::vector expected_separator = { "4", "5" }; auto separator = params.as_string_list(SEPARATOR); EXPECT_TRUE(separator.is_ok()); EXPECT_EQ(expected_separator, separator.unwrap_or({})); }) .on_error([](auto) { EXPECT_FALSE(true); }); } TEST(flags, parse_with_default_values) { const char* argv[] = { "executable" }; const int argc = sizeof(argv) / sizeof(const char*); const Parser sut("test", "version", { {FLAG, {0, false, "a single flag", {"true"}, std::nullopt}}, {OPTION, {1, false, "a flag with a value", {"42"}, std::nullopt}} }); sut.parse(argc, const_cast(argv)) .on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_FALSE(params.as_bool(HELP).unwrap_or(true)); EXPECT_TRUE(params.as_bool(FLAG).is_ok()); EXPECT_TRUE(params.as_bool(FLAG).unwrap_or(true)); auto option = params.as_string(OPTION); EXPECT_TRUE(option.is_ok()); EXPECT_STREQ(option.unwrap_or("").data(), "42"); }) .on_error([](auto) { EXPECT_FALSE(true); }); } TEST(flags, parse_fails_for_unkown_flags) { const char* argv[] = { "executable", FLAG, OPTION, "0" }; const int argc = sizeof(argv) / sizeof(const char*); const Parser sut("test", "version", { {FLAG, {0, false, "a single flag", std::nullopt, std::nullopt}} }); sut.parse(argc, const_cast(argv)) .on_success([](auto) { EXPECT_FALSE(true); }) .on_error([](auto error) { EXPECT_TRUE(true); EXPECT_STREQ(error.what(), "Unrecognized parameter: \"--option\""); }); } TEST(flags, parse_fails_for_not_enough_params) { const char* argv[] = { "executable", FLAG, OPTIONS, "1" }; const int argc = sizeof(argv) / sizeof(const char*); const Parser sut("test", "version", { {FLAG, {0, false, "a single flag", std::nullopt, std::nullopt}}, {OPTIONS, {3, false, "a flag with 3 values", std::nullopt, std::nullopt}} }); sut.parse(argc, const_cast(argv)) .on_success([](auto) { EXPECT_FALSE(true); }) .on_error([](auto error) { EXPECT_TRUE(true); EXPECT_STREQ(error.what(), "Not enough parameters for: \"--options\""); }); } TEST(flags, parse_fails_for_required_parameters_missing) { const char* argv[] = { "executable", OPTIONS, "1", "2" }; const int argc = sizeof(argv) / sizeof(const char*); const Parser sut("test", "version", { {OPTION, {1, true, "a flag with 1 value", std::nullopt, std::nullopt}}, {OPTIONS, {2, false, "a flag with 2 values", std::nullopt, std::nullopt}} }); sut.parse(argc, const_cast(argv)) .on_success([](auto) { EXPECT_FALSE(true); }) .on_error([](auto error) { EXPECT_TRUE(true); EXPECT_STREQ(error.what(), "Parameter is required, but not given: \"--option\""); }); } TEST(flags, usage_for_simple_parser) { const Parser sut("test", "version", { {FLAG, {0, false, "a single flag", std::nullopt, std::nullopt}}, {OPTION, {1, false, "a flag with a value", std::nullopt, std::nullopt}}, {OPTIONS, {3, false, "a flag with 3 values", std::nullopt, std::nullopt}}, {SEPARATOR, {-1, false, "rest of the arguments", std::nullopt, std::nullopt}} }); { const char *expected = "Usage: test [--flag] [--option ] [--options ] [--verbose] [-- ...]\n"; std::ostringstream out; sut.print_usage(nullptr, out); EXPECT_EQ( expected, out.str() ); } { const char *expected = "Usage: test [--flag] [--option ] [--options ] [--verbose] [-- ...]\n" "\n" " --flag a single flag\n" " --option a flag with a value\n" " --options \n" " a flag with 3 values\n" " --verbose run in verbose mode\n" " -- ... rest of the arguments\n" "\n" "query options\n" " --help print help and exit\n" " --version print version and exit\n"; std::ostringstream out; sut.print_help(nullptr, out); EXPECT_EQ(expected, out.str()); } { const char *expected = "test version\n"; std::ostringstream out; sut.print_version(out); EXPECT_EQ(expected, out.str()); } } TEST(flags, parse_successful_subcommands) { const Parser append("append", { {OPTION, {1, false, "a flag with a value", std::nullopt, std::nullopt}} }); const Parser dump("dump", { {OPTIONS, {3, false, "a flag with 3 values", std::nullopt, std::nullopt}} }); const Parser sut("test", "version", { append, dump }, { {OPTION, {1, false, "a flag with a value", std::nullopt, std::nullopt}} }); { const char *argv[] = {"executable", "append", OPTION, "0"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_FALSE(params.as_bool(HELP).unwrap_or(true)); auto command = params.as_string(COMMAND); EXPECT_TRUE(command.is_ok()); EXPECT_STREQ("append", command.unwrap_or("").data()); auto option = params.as_string(OPTION); EXPECT_TRUE(option.is_ok()); EXPECT_STREQ(option.unwrap_or("").data(), "0"); auto options = params.as_string_list(OPTIONS); EXPECT_TRUE(options.is_err()); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", "dump", OPTIONS, "1", "2", "3"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_FALSE(params.as_bool(HELP).unwrap_or(true)); auto command = params.as_string(COMMAND); EXPECT_TRUE(command.is_ok()); EXPECT_STREQ("dump", command.unwrap_or("").data()); auto option = params.as_string(OPTION); EXPECT_TRUE(option.is_err()); std::vector expected_options = { "1", "2", "3" }; auto options = params.as_string_list(OPTIONS); EXPECT_TRUE(options.is_ok()); EXPECT_EQ(expected_options, options.unwrap_or({})); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", OPTION, "0"}; const int argc = sizeof(argv) / sizeof(const char*); const auto result = sut.parse(argc, argv); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_FALSE(params.as_bool(HELP).unwrap_or(true)); auto command = params.as_string(COMMAND); EXPECT_TRUE(command.is_err()); auto option = params.as_string(OPTION); EXPECT_TRUE(option.is_ok()); EXPECT_STREQ(option.unwrap_or("").data(), "0"); auto options = params.as_string_list(OPTIONS); EXPECT_TRUE(options.is_err()); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", "--help"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_TRUE(params.as_bool(HELP).unwrap_or(true)); EXPECT_TRUE(params.as_string(COMMAND).is_err()); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", "append", "--help"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(HELP).is_ok()); EXPECT_TRUE(params.as_bool(HELP).unwrap_or(true)); auto command = params.as_string(COMMAND); EXPECT_TRUE(command.is_ok()); EXPECT_STREQ("append", command.unwrap_or("").data()); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", "--version"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_ok()); result.on_success([](auto params) { EXPECT_TRUE(params.as_bool(VERSION).is_ok()); EXPECT_TRUE(params.as_bool(VERSION).unwrap_or(true)); }) .on_error([](auto) { EXPECT_FALSE(true); }); } { const char* argv[] = {"executable", "remove"}; const int argc = sizeof(argv) / sizeof(const char *); const auto result = sut.parse(argc, const_cast(argv)); EXPECT_TRUE(result.is_err()); } } TEST(flags, usage_for_sub_command_parser) { const Parser append("append", { {OPTION, {1, false, "a flag with a value", std::nullopt, std::nullopt}} }); const Parser dump("dump", { {OPTIONS, {3, false, "a flag with 3 values", std::nullopt, std::nullopt}} }); const Parser sut("test", "1.0", { append, dump }); { const char *expected = "Usage: test \n"; std::ostringstream out; sut.print_usage(nullptr, out); EXPECT_EQ( expected, out.str() ); } { const char *expected = "Usage: test \n" "\n" "commands\n" " append\n" " dump\n" "\n" "query options\n" " --help print help and exit\n" " --version print version and exit\n"; std::ostringstream out; sut.print_help(nullptr, out); EXPECT_EQ(expected, out.str()); } { const char *expected = "Usage: test append [--option ] [--verbose]\n" "\n" " --option a flag with a value\n" " --verbose run in verbose mode\n" "\n" "query options\n" " --help print help and exit\n"; std::ostringstream out; sut.print_help(&append, out); EXPECT_EQ(expected, out.str()); } { const char *expected = "test 1.0\n"; std::ostringstream out; sut.print_version(out); EXPECT_EQ(expected, out.str()); } } } rizsotto-Bear-14c2e01/source/libmain/000077500000000000000000000000001476774233700175365ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libmain/CMakeLists.txt000066400000000000000000000006411476774233700222770ustar00rootroot00000000000000add_library(main_a OBJECT) target_include_directories(main_a PUBLIC include/) target_sources(main_a PRIVATE source/ApplicationLogConfig.cc source/ApplicationFromArgs.cc source/SubcommandFromArgs.cc INTERFACE $ ) target_link_libraries(main_a PUBLIC result_a flags_a fmt::fmt spdlog::spdlog) rizsotto-Bear-14c2e01/source/libmain/include/000077500000000000000000000000001476774233700211615ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libmain/include/libmain/000077500000000000000000000000001476774233700225745ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libmain/include/libmain/Application.h000066400000000000000000000027771476774233700252250ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libresult/Result.h" #include "libflags/Flags.h" #include namespace ps { struct Command { virtual ~Command() noexcept = default; [[nodiscard]] virtual rust::Result execute() const = 0; }; using CommandPtr = std::unique_ptr; struct Application { virtual ~Application() noexcept = default; [[nodiscard]] virtual rust::Result command(int argc, const char** argv, const char** envp) const = 0; }; struct Subcommand { virtual ~Subcommand() noexcept = default; [[nodiscard]] virtual rust::Result subcommand(const flags::Arguments &args, const char** envp) const = 0; }; } rizsotto-Bear-14c2e01/source/libmain/include/libmain/ApplicationFromArgs.h000066400000000000000000000027711476774233700266600ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libmain/Application.h" #include "libmain/ApplicationLogConfig.h" #include "libresult/Result.h" #include "libflags/Flags.h" namespace ps { struct ApplicationFromArgs : Application { explicit ApplicationFromArgs(const ApplicationLogConfig&) noexcept; rust::Result command(int argc, const char** argv, const char** envp) const override; virtual rust::Result parse(int argc, const char** argv) const = 0; virtual rust::Result command(const flags::Arguments &args, const char** envp) const = 0; NON_DEFAULT_CONSTRUCTABLE(ApplicationFromArgs) protected: ApplicationLogConfig log_config_; }; } rizsotto-Bear-14c2e01/source/libmain/include/libmain/ApplicationLogConfig.h000066400000000000000000000024361476774233700270050ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" namespace ps { struct ApplicationLogConfig { ApplicationLogConfig(const char *name, const char *id); virtual ~ApplicationLogConfig() = default; virtual void initForSilent() const; virtual void initForVerbose() const; void record(const char** argv, const char** envp) const; void context() const; NON_DEFAULT_CONSTRUCTABLE(ApplicationLogConfig) protected: const char *name_; const char *id_; }; } rizsotto-Bear-14c2e01/source/libmain/include/libmain/SubcommandFromArgs.h000066400000000000000000000027761476774233700265120ustar00rootroot00000000000000/* Copyright (C) 2023 by Samu698 This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libmain/Application.h" #include "libmain/ApplicationLogConfig.h" #include "libresult/Result.h" #include "libflags/Flags.h" #include namespace ps { struct SubcommandFromArgs : Subcommand { explicit SubcommandFromArgs(const char* name, const ApplicationLogConfig&) noexcept; bool matches(const flags::Arguments &args); rust::Result subcommand(const flags::Arguments &args, const char** envp) const override; virtual rust::Result command(const flags::Arguments &args, const char** envp) const = 0; NON_DEFAULT_CONSTRUCTABLE(SubcommandFromArgs) protected: std::string name_; ApplicationLogConfig log_config_; }; } rizsotto-Bear-14c2e01/source/libmain/include/libmain/main.h000066400000000000000000000033151476774233700236730ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libmain/Application.h" #include namespace ps { template int main(int argc, char* argv[], char* envp[]) { App app; auto ptr = reinterpret_cast(&app); return ptr->command(argc, const_cast(argv), const_cast(envp)) .and_then([](const ps::CommandPtr &cmd) { return cmd->execute(); }) // print out the result of the run .on_error([](auto error) { spdlog::error("failed with: {}", error.what()); }) .on_success([](auto status_code) { spdlog::debug("succeeded with: {}", status_code); }) // set the return code from error .unwrap_or(EXIT_FAILURE); } } rizsotto-Bear-14c2e01/source/libmain/source/000077500000000000000000000000001476774233700210365ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libmain/source/ApplicationFromArgs.cc000066400000000000000000000037171476774233700252610ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libmain/ApplicationFromArgs.h" #include #include #include #ifdef FMT_NEEDS_OSTREAM_FORMATTER template <> struct fmt::formatter : ostream_formatter {}; #endif namespace ps { ApplicationFromArgs::ApplicationFromArgs(const ApplicationLogConfig &log_config) noexcept : Application() , log_config_(log_config) { log_config_.initForSilent(); } rust::Result ApplicationFromArgs::command(int argc, const char** argv, const char** envp) const { return parse(argc, argv) .on_success([this, &argv, &envp](const auto& args) { if (args.as_bool(flags::VERBOSE).unwrap_or(false)) { log_config_.initForVerbose(); } log_config_.record(argv, envp); log_config_.context(); spdlog::debug("arguments parsed: {0}", args); }) // if parsing success, we create the main command and execute it. .and_then([this, &envp](auto args) { return this->command(args, envp); }); } } rizsotto-Bear-14c2e01/source/libmain/source/ApplicationLogConfig.cc000066400000000000000000000050651476774233700254060ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libmain/ApplicationLogConfig.h" #include "config.h" #include #include #include #ifdef HAVE_SYS_UTSNAME_H #include #endif namespace { struct Array { const char** ptr; const char** begin() const { return ptr; } const char** end() const { const char** it = ptr; while (*it != nullptr) { ++it; } return it; } }; } namespace ps { ApplicationLogConfig::ApplicationLogConfig(const char *name, const char *id) : name_(name) , id_(id) { spdlog::set_default_logger(spdlog::stderr_logger_mt("stderr")); } void ApplicationLogConfig::initForSilent() const { spdlog::set_pattern(fmt::format("{0}: %v", name_)); spdlog::set_level(spdlog::level::info); } void ApplicationLogConfig::initForVerbose() const { spdlog::set_pattern(fmt::format("[%H:%M:%S.%f, {0}, %P] %v", id_)); spdlog::set_level(spdlog::level::debug); } void ApplicationLogConfig::record(const char** argv, const char** envp) const { spdlog::debug("{0}: {1}", name_, cmd::VERSION); spdlog::debug("arguments: {0}", Array { argv }); spdlog::debug("environment: {0}", Array { envp }); } void ApplicationLogConfig::context() const { #ifdef HAVE_UNAME auto name = utsname{}; if (const int status = uname(&name); status >= 0) { spdlog::debug("sysname: {0}", name.sysname); spdlog::debug("release: {0}", name.release); spdlog::debug("version: {0}", name.version); spdlog::debug("machine: {0}", name.machine); } errno = 0; #endif } } rizsotto-Bear-14c2e01/source/libmain/source/SubcommandFromArgs.cc000066400000000000000000000032441476774233700251010ustar00rootroot00000000000000/* Copyright (C) 2023 by Samu698 This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libmain/SubcommandFromArgs.h" #include #include #include #include #ifdef FMT_NEEDS_OSTREAM_FORMATTER template <> struct fmt::formatter : ostream_formatter {}; #endif namespace ps { SubcommandFromArgs::SubcommandFromArgs(const char* name, const ApplicationLogConfig &log_config) noexcept : Subcommand() , name_(name) , log_config_(log_config) { log_config_.initForSilent(); } bool SubcommandFromArgs::matches(const flags::Arguments &args) { return args.as_string(flags::COMMAND).unwrap_or("") == name_; } rust::Result SubcommandFromArgs::subcommand(const flags::Arguments &args, const char** envp) const { if (args.as_bool(flags::VERBOSE).unwrap_or(false)) { log_config_.initForVerbose(); } return this->command(args, envp); } } rizsotto-Bear-14c2e01/source/libresult/000077500000000000000000000000001476774233700201305ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libresult/CMakeLists.txt000066400000000000000000000006511476774233700226720ustar00rootroot00000000000000add_library(result_a INTERFACE) target_include_directories(result_a INTERFACE include/) if (ENABLE_UNIT_TESTS) add_executable(result_unit_test test/ResultTest.cc ) target_link_libraries(result_unit_test result_a) target_link_libraries(result_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::result_unit_test COMMAND $) endif ()rizsotto-Bear-14c2e01/source/libresult/include/000077500000000000000000000000001476774233700215535ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libresult/include/libresult/000077500000000000000000000000001476774233700235605ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libresult/include/libresult/Result.h000066400000000000000000000402621476774233700252130ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include namespace rust { namespace types { template struct Ok { explicit Ok(const T& value) : value_(value) { } explicit Ok(T&& value) noexcept : value_(std::move(value)) { } T value_; }; template struct Err { explicit Err(const E& value) : value_(value) { } explicit Err(E&& value) noexcept : value_(value) { } E value_; }; } // Most of the internal is about to implement a storage for the values. // // This can be done with `std::variant` which is available in C++17. // To make this code more portable the implementation is using C++14 // language constructs only. namespace internals { template struct Storage { static constexpr size_t Size = sizeof(T) > sizeof(E) ? sizeof(T) : sizeof(E); static constexpr size_t Align = sizeof(T) > sizeof(E) ? alignof(T) : alignof(E); typedef typename std::aligned_storage::type type; Storage() : initialized_(false) { } void construct(types::Ok ok) { new (&storage_) T(std::move(ok.value_)); initialized_ = true; } void construct(types::Err err) { new (&storage_) E(std::move(err.value_)); initialized_ = true; } template void raw_construct(U&& value) { typedef typename std::decay::type CleanU; new (&storage_) CleanU(std::forward(value)); initialized_ = true; } template const U& get() const { return *reinterpret_cast(&storage_); } template U& get() { return *reinterpret_cast(&storage_); } void destroy_ok() { if (initialized_) { get().~T(); initialized_ = false; } } void destroy_err() { if (initialized_) { get().~E(); initialized_ = false; } } type storage_; bool initialized_; }; } // Util methods which help to create `Result` types easier. template ::type> types::Ok Ok(T&& value) { return types::Ok(std::forward(value)); } template ::type> types::Err Err(E&& value) { return types::Err(std::forward(value)); } // This class represent a result of a computation. // // It's planned to implement such construct in later C++ language dialects. // That is referred as `std::expected` in proposals. // // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf // // This implementation is more close to the rust language `std::result::Result` // type. Where the public functions are following the namings of the rust // implementation. // // The public interface is also trimmed down. The main motivation was: // // * remove the access methods `ok()` or `err()` methods. // (std::optional in C++17 only) // * remove the access methods `unwrap()` or `expect(...)` methods. // (no exception, would be hard to signal wrong access.) // // Contrast to the C++ std::expected, this type is encourage to use // higher order functions (monadic methods) to use the results. // // https://doc.rust-lang.org/std/result/enum.Result.html template class Result { public: Result() = delete; ~Result(); Result(Result&& other) noexcept; Result(const Result& other); Result& operator=(Result&& other) noexcept; Result& operator=(const Result& other); Result(types::Ok&& ok) noexcept; Result(types::Err&& err) noexcept; public: [[nodiscard]] bool is_ok() const; [[nodiscard]] bool is_err() const; template Result map(std::function const& f) const; template Result map_or(U&& value, std::function const& func) const; template Result map_or_else(std::function const& provider, std::function const& f) const; template Result map_err(std::function const& f) const; template Result and_(const Result& rhs) const; template Result and_then(std::function(const T&)> const& f) const; Result or_(const Result& rhs) const; Result or_else(std::function(const E&)> const& f) const; const T& unwrap() const; const E& unwrap_err() const; const T& unwrap_or(const T& value) const; T unwrap_or_else(std::function const& provider) const; const Result& on_success(std::function const& f) const; const Result& on_error(std::function const& f) const; private: bool ok_; internals::Storage storage_; }; template bool operator==(Result const &lhs, Result const &rhs) { return (lhs.is_ok() && rhs.is_ok() && (lhs.unwrap() == rhs.unwrap())) || (lhs.is_err() && rhs.is_err() && (std::strcmp(lhs.unwrap_err().what(), rhs.unwrap_err().what()) == 0)); } template bool operator==(Result const &lhs, Result const &rhs) { return (lhs.is_ok() && rhs.is_ok() && (lhs.unwrap() == rhs.unwrap())) || (lhs.is_err() && rhs.is_err() && (lhs.unwrap_err() == rhs.unwrap_err())); } template Result> merge(const Result& t1, const Result& t2) { return t1.template and_then>([&t2](auto& t1_value) { return t2.template map>([&t1_value](auto& t2_value) { return std::make_tuple(t1_value, t2_value); }); }); } template Result> merge(const Result& t1, const Result& t2, const Result& t3) { return t1.template and_then>([&t2, &t3](auto& t1_value) { return t2.template and_then>([&t1_value, &t3](auto& t2_value) { return t3.template map>([&t1_value, &t2_value](auto& t3_value) { return std::make_tuple(t1_value, t2_value, t3_value); }); }); }); } template Result> merge(const Result &t1, const Result &t2, const Result &t3, const Result &t4) { return merge(merge(t1, t2), merge(t3, t4)) .template map>([](auto tuple) { const auto&[t12, t34] = tuple; const auto&[t1, t2] = t12; const auto&[t3, t4] = t34; return std::make_tuple(t1, t2, t3, t4); }); } template Result::~Result() { if (ok_) { storage_.destroy_ok(); } else { storage_.destroy_err(); } } template Result::Result(Result&& other) noexcept : ok_(other.ok_) , storage_() { if (other.ok_) { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_ok(); } else { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_err(); } } template Result::Result(const Result& other) : ok_(other.ok_) , storage_() { if (other.ok_) { storage_.raw_construct(other.storage_.template get()); } else { storage_.raw_construct(other.storage_.template get()); } } template Result& Result::operator=(Result&& other) noexcept { if (this != &other) { if (ok_) { storage_.destroy_ok(); ok_ = other.ok_; if (other.ok_) { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_ok(); } else { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_err(); } } else { storage_.destroy_err(); ok_ = other.ok_; if (other.ok_) { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_ok(); } else { storage_.raw_construct(std::move(other.storage_.template get())); other.storage_.destroy_err(); } } } return *this; } template Result& Result::operator=(const Result& other) { if (this != &other) { if (ok_) { storage_.destroy_ok(); ok_ = other.ok_; if (other.ok_) { storage_.raw_construct(other.storage_.template get()); } else { storage_.raw_construct(other.storage_.template get()); } } else { storage_.destroy_err(); ok_ = other.ok_; if (other.ok_) { storage_.raw_construct(other.storage_.template get()); } else { storage_.raw_construct(other.storage_.template get()); } } } return *this; } template Result::Result(types::Ok&& ok) noexcept : ok_(true) , storage_() { storage_.construct(std::move(ok)); } template Result::Result(types::Err&& err) noexcept : ok_(false) , storage_() { storage_.construct(std::move(err)); } template bool Result::is_ok() const { return ok_; } template bool Result::is_err() const { return !ok_; } template template Result Result::map(const std::function& f) const { if (ok_) { auto res = f(storage_.template get()); return types::Ok(std::move(res)); } else { return types::Err(storage_.template get()); } } template template Result Result::map_or(U&& value, const std::function& f) const { if (ok_) { auto res = f(storage_.template get()); return types::Ok(std::move(res)); } else { return types::Ok(value); } } template template Result Result::map_or_else(const std::function& provider, const std::function& f) const { if (ok_) { auto res = f(storage_.template get()); return types::Ok(std::move(res)); } else { auto res = provider(storage_.template get()); return types::Ok(std::move(res)); } } template template Result Result::map_err(const std::function& f) const { if (ok_) { auto res = storage_.template get(); return types::Ok(std::move(res)); } else { auto res = f(storage_.template get()); return types::Err(std::move(res)); } } template template Result Result::and_(const Result& rhs) const { if (ok_) { return rhs; } else { auto res = storage_.template get(); return types::Err(std::move(res)); } } template template Result Result::and_then(const std::function(const T&)>& f) const { if (ok_) { return f(storage_.template get()); } else { return types::Err(storage_.template get()); } } template Result Result::or_(const Result& rhs) const { if (ok_) { return *this; } else { return rhs; } } template Result Result::or_else(const std::function(const E&)>& f) const { if (ok_) { return *this; } else { return f(storage_.template get()); } } template const T& Result::unwrap() const { return storage_.template get(); } template const E& Result::unwrap_err() const { return storage_.template get(); } template const T& Result::unwrap_or(const T& value) const { if (ok_) { return storage_.template get(); } else { return value; } } template T Result::unwrap_or_else(const std::function& provider) const { if (ok_) { return storage_.template get(); } else { return provider(storage_.template get()); } } template const Result& Result::on_success(const std::function& f) const { if (ok_) { f(storage_.template get()); } return *this; } template const Result& Result::on_error(const std::function& f) const { if (!ok_) { f(storage_.template get()); } return *this; } } rizsotto-Bear-14c2e01/source/libresult/test/000077500000000000000000000000001476774233700211075ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libresult/test/ResultTest.cc000066400000000000000000000274471476774233700235520ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libresult/Result.h" namespace { using Error = const char*; using namespace rust; TEST(result, unwrap_or_on_success) { EXPECT_EQ(2, (Result(Ok(2)).unwrap_or(8))); EXPECT_EQ('c', (Result(Ok('c')).unwrap_or('+'))); } TEST(result, unwrap_or_on_failure) { EXPECT_EQ(8, (Result(Err("problem")).unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")).unwrap_or('+'))); } TEST(result, unwrap_or_else_on_success) { EXPECT_EQ(2, (Result(Ok(2)).unwrap_or_else([](Error) { return 8; }))); EXPECT_EQ('c', (Result(Ok('c')).unwrap_or_else([](Error) { return '+'; }))); } TEST(result, unwrap_or_else_on_failure) { EXPECT_EQ(8, (Result(Err("problem")).unwrap_or_else([](auto error) { EXPECT_STREQ("problem", error); return 8; }))); EXPECT_EQ('+', (Result(Err("problem")).unwrap_or_else([](auto error) { EXPECT_STREQ("problem", error); return '+'; }))); } TEST(result, map_on_success) { EXPECT_EQ(4, (Result(Ok(2)) .map([](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ(2.5f, (Result(Ok(2)) .map([](auto& in) { return in + 0.5f; }) .unwrap_or(8.0f))); EXPECT_EQ('d', (Result(Ok('c')) .map([](auto& in) { return in + 1; }) .unwrap_or(42))); } TEST(result, map_on_failure) { EXPECT_EQ(8, (Result(Err("problem")) .map([](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")) .map([](const char& in) { return char(in + 1); }) .unwrap_or('+'))); } TEST(result, map_or_on_success) { EXPECT_EQ(4, (Result(Ok(2)) .map_or(7, [](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ(2.5f, (Result(Ok(2)) .map_or(7.8, [](auto& in) { return in + 0.5f; }) .unwrap_or(8.0f))); EXPECT_EQ('d', (Result(Ok('c')) .map_or(13, [](auto& in) { return in + 1; }) .unwrap_or(42))); } TEST(result, map_or_on_failure) { EXPECT_EQ(9, (Result(Err("problem")) .map_or(9, [](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ('#', (Result(Err("problem")) .map_or('#', [](const char& in) { return char(in + 1); }) .unwrap_or('+'))); } TEST(result, map_or_else_on_success) { EXPECT_EQ(4, (Result(Ok(2)) .map_or_else([](Error) { return 9; }, [](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ(2.5f, (Result(Ok(2)) .map_or_else([](Error) { return 7.8; }, [](auto& in) { return in + 0.5f; }) .unwrap_or(8.0f))); EXPECT_EQ('d', (Result(Ok('c')) .map_or_else([](Error) { return 13; }, [](auto& in) { return in + 1; }) .unwrap_or(42))); } TEST(result, map_or_else_on_failure) { EXPECT_EQ(9, (Result(Err("problem")) .map_or_else([](Error) { return 9; }, [](auto& in) { return in * 2; }) .unwrap_or(8))); EXPECT_EQ('#', (Result(Err("problem")) .map_or_else([](Error) { return '#'; }, [](const char& in) { return char(in + 1); }) .unwrap_or('+'))); } TEST(result, map_err_on_success) { EXPECT_EQ(2, (Result(Ok(2)) .map_err([](Error) { return 9; }) .unwrap_or(8))); EXPECT_EQ(2.5f, (Result(Ok(2.5f)) .map_err([](Error) { return '+'; }) .unwrap_or(8.0f))); } TEST(result, map_err_on_failure) { EXPECT_EQ(8, (Result(Err("problem")) .map_err([](auto error) { EXPECT_STREQ("problem", error); return 9; }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")) .map_err([](auto error) { EXPECT_STREQ("problem", error); return '#'; }) .unwrap_or('+'))); } TEST(result, and_) { { auto x = Result(Ok(2)); auto y = Result(Err("late error")); x.and_(y).map_err([](auto error) { EXPECT_STREQ("late error", error); return 0; }); } { auto x = Result(Err("early error")); auto y = Result(Ok(2)); x.and_(y).map_err([](auto error) { EXPECT_STREQ("early error", error); return 0; }); } { auto x = Result(Err("early error")); auto y = Result(Err("late error")); x.and_(y).map_err([](auto error) { EXPECT_STREQ("early error", error); return 0; }); } { auto x = Result(Ok(2)); auto y = Result(Ok('x')); x.and_(y).map([](auto value) { EXPECT_EQ('x', value); return 0; }); } } TEST(result, and_then_on_success) { EXPECT_EQ(2, (Result(Ok(1)) .and_then([](auto& in) { return Ok(in * 2); }) .unwrap_or(8))); EXPECT_EQ('d', (Result(Ok('c')) .and_then([](auto& in) { return Ok(char(in + 1)); }) .unwrap_or('+'))); EXPECT_EQ(8, (Result(Ok(1)) .and_then([](auto& in) { EXPECT_EQ(1, in); return Err("problem"); }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Ok('c')) .and_then([](auto& in) { EXPECT_EQ('c', in); return Err("problem"); }) .unwrap_or('+'))); } TEST(result, and_then_on_failure) { EXPECT_EQ(8, (Result(Err("problem")) .and_then([](auto& in) { return Ok(in * 2); }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")) .and_then([](auto& in) { return Ok(char(in + 1)); }) .unwrap_or('+'))); EXPECT_EQ(8, (Result(Err("problem")) .and_then([](int) { return Err("another problem"); }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")) .and_then([](char) { return Err("another problem"); }) .unwrap_or('+'))); } TEST(result, or_) { { auto x = Result(Ok(2)); auto y = Result(Err("late error")); x.or_(y).map([](auto value) { EXPECT_EQ(2, value); return 0; }); } { auto x = Result(Err("early error")); auto y = Result(Ok(2)); x.or_(y).map([](auto value) { EXPECT_EQ(2, value); return 0; }); } { auto x = Result(Err("early error")); auto y = Result(Err("late error")); x.or_(y).map_err([](auto error) { EXPECT_STREQ("late error", error); return 0; }); } { auto x = Result(Ok(2)); auto y = Result(Ok(100)); x.or_(y).map([](auto value) { EXPECT_EQ(2, value); return 0; }); } } TEST(result, or_else_on_success) { EXPECT_EQ(1, (Result(Ok(1)) .or_else([](Error) { return Ok(2); }) .unwrap_or(8))); EXPECT_EQ('c', (Result(Ok('c')) .or_else([](Error) { return Ok('x'); }) .unwrap_or('+'))); EXPECT_EQ(1, (Result(Ok(1)) .or_else([](Error) { return Err("problem"); }) .unwrap_or(8))); EXPECT_EQ('c', (Result(Ok('c')) .or_else([](Error) { return Err("problem"); }) .unwrap_or('+'))); } TEST(result, or_else_on_failure) { EXPECT_EQ(2, (Result(Err("problem")) .or_else([](auto& error) { EXPECT_STREQ("problem", error); return Ok(2); }) .unwrap_or(8))); EXPECT_EQ('x', (Result(Err("problem")) .or_else([](auto& error) { EXPECT_STREQ("problem", error); return Ok('x'); }) .unwrap_or('+'))); EXPECT_EQ(8, (Result(Err("problem")) .or_else([](auto& error) { EXPECT_STREQ("problem", error); return Err("another problem"); }) .unwrap_or(8))); EXPECT_EQ('+', (Result(Err("problem")) .or_else([](auto& error) { EXPECT_STREQ("problem", error); return Err("another problem"); }) .unwrap_or('+'))); } } rizsotto-Bear-14c2e01/source/libshell/000077500000000000000000000000001476774233700177215ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libshell/CMakeLists.txt000066400000000000000000000011231476774233700224560ustar00rootroot00000000000000add_library(shell_a OBJECT) target_include_directories(shell_a PUBLIC include/) target_sources(shell_a PRIVATE source/Command.cc INTERFACE $ ) target_link_libraries(shell_a PUBLIC result_a) if (ENABLE_UNIT_TESTS) add_executable(shell_unit_test test/CommandTest.cc ) target_link_libraries(shell_unit_test shell_a) target_link_libraries(shell_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::shell_unit_test COMMAND $) endif () rizsotto-Bear-14c2e01/source/libshell/include/000077500000000000000000000000001476774233700213445ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libshell/include/libshell/000077500000000000000000000000001476774233700231425ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libshell/include/libshell/Command.h000066400000000000000000000041471476774233700246770ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libresult/Result.h" #include #include namespace sh { // Escapes a string so it will be interpreted as a single word by the UNIX Bourne shell. // // If the input string is empty, this function returns an empty quoted string. std::string escape(const std::string& input); // Builds a command line string from a list of arguments. // // The arguments are combined into a single string with each word separated by a space. // Each individual word is escaped as necessary via `escape`. std::string join(const std::list& arguments); // Splits a string into a vector of words in the same way the UNIX Bourne shell does. // // This function does not behave like a full command line parser. Only single quotes, // double quotes, and backslashes are treated as metacharacters. Within double quoted // strings, backslashes are only treated as metacharacters when followed by one of the // following characters: // // * $ // * ` // * " // * backslash // * newline // // The pipe character has no special meaning. // // If the input contains mismatched quotes (a quoted string missing a matching ending // quote), an error is returned. rust::Result> split(const std::string& input); } rizsotto-Bear-14c2e01/source/libshell/source/000077500000000000000000000000001476774233700212215ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libshell/source/Command.cc000066400000000000000000000060731476774233700231140ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libshell/Command.h" #include #include namespace sh { std::string escape(const std::string& input) { if (input.empty()) { return "''"; } const std::regex ESCAPE_PATTERN(R"#(([^A-Za-z0-9_\-.,:/@\n]))#"); const std::regex LINE_FEED(R"#(\n)#"); const auto output = std::regex_replace(input, ESCAPE_PATTERN, "\\$1"); return std::regex_replace(output, LINE_FEED, "'\n'"); } std::string join(const std::list& arguments) { std::string result; for (auto it = arguments.begin(); it != arguments.end(); ++it) { if (it != arguments.begin()) { result += " "; } result += escape(*it); } return result; } rust::Result> split(const std::string& input) { const std::regex MAIN_PATTERN(R"#((?:\s*(?:([^\s\\'"]+)|'([^']*)'|"((?:[^"\\]|\\.)*)"|(\\.?)|(\S))(\s|$)?))#", std::regex::ECMAScript); const std::regex ESCAPE_PATTERN(R"#(\\(.))#"); const std::regex METACHAR_PATTERN(R"(\\([$`"\\\n]))"); std::list words; std::string field; const auto input_begin = std::sregex_iterator(input.begin(), input.end(), MAIN_PATTERN); const auto input_end = std::sregex_iterator(); for (auto it = input_begin; it != input_end; ++it) { if (it->ready()) { if (it->operator[](1).matched) { field += it->str(1); } else if (it->operator[](2).matched) { field += it->str(2); } else if (it->operator[](3).matched) { field += std::regex_replace(it->str(3), METACHAR_PATTERN, "$1"); } else if (it->operator[](4).matched) { field += std::regex_replace(it->str(4), ESCAPE_PATTERN, "$1"); } else if (it->operator[](5).matched) { return rust::Err(std::runtime_error("Mismatched quotes.")); } if (it->operator[](6).matched) { words.push_back(field); field.clear(); } } } return rust::Ok(std::move(words)); } } rizsotto-Bear-14c2e01/source/libshell/test/000077500000000000000000000000001476774233700207005ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libshell/test/CommandTest.cc000066400000000000000000000073571476774233700234410ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libshell/Command.h" namespace { TEST(command, empty) { std::list expected = {}; EXPECT_EQ(expected,sh::split("").unwrap_or({"fake"})); } TEST(command, whitespace) { std::list expected = {}; EXPECT_EQ(expected,sh::split(" ").unwrap_or({"fake"})); } TEST(command, single_word) { std::list expected = {"abcd"}; EXPECT_EQ(expected,sh::split("abcd").unwrap_or({})); } TEST(command, nothing_special) { std::list expected = {"a", "b", "c", "d"}; EXPECT_EQ(expected,sh::split("a b c d").unwrap_or({})); } TEST(command, quoted_strings) { std::list expected = {"a", "b b", "a"}; EXPECT_EQ(expected,sh::split("a \"b b\" a").unwrap_or({})); } TEST(command, escaped_double_quotes) { std::list expected = {"a", "\"b\" c", "d"}; EXPECT_EQ(expected,sh::split("a \"\\\"b\\\" c\" d").unwrap_or({})); } TEST(command, escaped_single_quoutes) { std::list expected = {"a", "'b' c", "d"}; EXPECT_EQ(expected,sh::split("a \"'b' c\" d").unwrap_or({})); } TEST(command, escaped_spaces) { std::list expected = {"a", "b c", "d"}; EXPECT_EQ(expected,sh::split("a b\\ c d").unwrap_or({})); } TEST(command, bad_double_quotes) { EXPECT_FALSE(sh::split("a \"b c d e").is_ok()); } TEST(command, bad_single_quotes) { EXPECT_FALSE(sh::split("a 'b c d e").is_ok()); } TEST(command, bad_quotes) { EXPECT_FALSE(sh::split("one '\"\"\"").is_ok()); } TEST(command, trailing_whitespace) { std::list expected = {"a", "b", "c", "d"}; EXPECT_EQ(expected,sh::split("a b c d ").unwrap_or({})); } TEST(command, percent_signs) { std::list expected = {"abc", "%foo bar%"}; EXPECT_EQ(expected,sh::split("abc '%foo bar%'").unwrap_or({})); } TEST(command, empty_escape) { EXPECT_EQ("''",sh::escape("")); } TEST(command, full_escape) { EXPECT_EQ("foo\\ \\'\\\"\\'\\ bar",sh::escape("foo '\"' bar")); } TEST(command, escape_and_join_whitespace) { std::string empty; std::string space(" "); std::string newline("\n"); std::string tab("\t"); std::list tokens = { empty, space, space + space, newline, newline + newline, tab, tab + tab, empty, space + newline + tab, empty }; for (const auto& token : tokens) { const std::list expected = { token }; EXPECT_EQ(expected, sh::split(sh::escape(token)).unwrap_or({})); } EXPECT_EQ(tokens, sh::split(sh::join(tokens)).unwrap_or({})); } }rizsotto-Bear-14c2e01/source/libsys/000077500000000000000000000000001476774233700174305ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libsys/CMakeLists.txt000066400000000000000000000037231476774233700221750ustar00rootroot00000000000000add_library(sys_a OBJECT) target_include_directories(sys_a PUBLIC include/) target_include_directories(sys_a PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include/) target_sources(sys_a PRIVATE source/Os.cc source/Guard.cc source/Errors.cc source/Path.cc source/Process.cc source/Signal.cc INTERFACE $ ) target_link_libraries(sys_a PUBLIC ${CMAKE_DL_LIBS} result_a fmt::fmt spdlog::spdlog) if (ENABLE_UNIT_TESTS) add_executable(sys_unit_test test/EnvironmentTest.cc test/ErrorsTest.cc test/PathTest.cc ) target_include_directories(sys_unit_test PRIVATE source/) target_link_libraries(sys_unit_test sys_a) target_link_libraries(sys_unit_test PkgConfig::GTest ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME bear::sys_unit_test COMMAND $) endif () if (SUPPORT_PRELOAD AND NOT HAVE_GNU_LIB_NAMES_H) if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") execute_process( COMMAND sh -c "ls /lib/libc.so.*" OUTPUT_VARIABLE LIBC_SO_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) # cmake_path(GET LIBC_SO_PATH FILENAME LIBC_SO) get_filename_component(LIBC_SO ${LIBC_SO_PATH} NAME) elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") execute_process( COMMAND sh -c "ls /usr/lib/libc.so.*" OUTPUT_VARIABLE LIBC_SO_PATH OUTPUT_STRIP_TRAILING_WHITESPACE ) # cmake_path(GET LIBC_SO_PATH FILENAME LIBC_SO) get_filename_component(LIBC_SO ${LIBC_SO_PATH} NAME) elseif (HAIKU) set(LIBC_SO "libroot.so") else() set(LIBC_SO "libc.so") endif() message(STATUS "libc.so filename ${LIBC_SO}") endif() configure_file( include/libsys/lib-names.h.in include/libsys/lib-names.h @ONLY ) rizsotto-Bear-14c2e01/source/libsys/include/000077500000000000000000000000001476774233700210535ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libsys/include/libsys/000077500000000000000000000000001476774233700223605ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libsys/include/libsys/Environment.h000066400000000000000000000017471476774233700250460ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include namespace sys::env { using Vars = std::map; // Convert an environment array into a map. Vars from(const char** value); } rizsotto-Bear-14c2e01/source/libsys/include/libsys/Errors.h000066400000000000000000000015741476774233700240140ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include namespace sys { std::string error_string(int error) noexcept; } rizsotto-Bear-14c2e01/source/libsys/include/libsys/Os.h000066400000000000000000000020501476774233700231070ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "libresult/Result.h" #include "libsys/Environment.h" #include namespace sys::os { // Return PATH from environment and fall back to confstr default one. [[nodiscard]] rust::Result get_path(const sys::env::Vars& env); } rizsotto-Bear-14c2e01/source/libsys/include/libsys/Path.h000066400000000000000000000023121476774233700234230ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include #include #include namespace fs = std::filesystem; namespace sys::path { // PATH variable manipulation functions // // https://en.wikipedia.org/wiki/PATH_(variable) std::list split(const std::string &input); std::string join(const std::list &input); rust::Result get_cwd(); } rizsotto-Bear-14c2e01/source/libsys/include/libsys/Process.h000066400000000000000000000055731476774233700241610ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include "libresult/Result.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace sys { class ExitStatus { public: ExitStatus(bool is_code, int code); ExitStatus() = delete ; ~ExitStatus() noexcept = default; [[nodiscard]] std::optional code() const; [[nodiscard]] std::optional signal() const; [[nodiscard]] bool is_signaled() const; [[nodiscard]] bool is_exited() const; private: const bool is_code_; const int code_; }; class Process { public: class Builder; [[nodiscard]] pid_t get_pid() const; rust::Result wait(bool request_for_signals = false); rust::Result kill(int num); public: NON_DEFAULT_CONSTRUCTABLE(Process) private: explicit Process(pid_t pid); const pid_t pid_; }; class Process::Builder { public: explicit Builder(fs::path program, bool with_preload = false); ~Builder() = default; Builder& add_argument(const char* param); Builder& add_argument(std::string&& param); Builder& add_argument(const std::string_view& param); template Builder& add_arguments(InputIt first, InputIt last) { for (InputIt it = first; it != last; ++it) { add_argument(*it); } return *this; } Builder& set_environment(std::map&&); Builder& set_environment(const std::map&); Builder& set_redirect_io(); rust::Result spawn() const; public: NON_DEFAULT_CONSTRUCTABLE(Builder) private: const fs::path program_; const bool with_preload_; std::list parameters_; std::map environment_; bool redirect_io_; }; } rizsotto-Bear-14c2e01/source/libsys/include/libsys/Signal.h000066400000000000000000000023501476774233700237460ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include namespace sys { class Process; class SignalForwarder { public: explicit SignalForwarder(const Process &child) noexcept; ~SignalForwarder() noexcept; public: NON_DEFAULT_CONSTRUCTABLE(SignalForwarder) NON_COPYABLE_NOR_MOVABLE(SignalForwarder) private: pid_t pid_; using handler_t = void (*)(int); handler_t handlers_[NSIG]; }; } rizsotto-Bear-14c2e01/source/libsys/include/libsys/lib-names.h.in000066400000000000000000000015001476774233700250010ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #define LIBC_SO "@LIBC_SO@" rizsotto-Bear-14c2e01/source/libsys/source/000077500000000000000000000000001476774233700207305ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libsys/source/Errors.cc000066400000000000000000000025061476774233700225160ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libsys/Errors.h" #include "config.h" #ifdef HAVE_STRERROR_R #include #else #include #endif namespace sys { std::string error_string(const int error) noexcept { #ifdef HAVE_STRERROR_R #if defined(__GLIBC__) && defined(_GNU_SOURCE) char buffer[256]; char *const result = ::strerror_r(error, buffer, 255); return {result}; #else char buffer[256]; ::strerror_r(error, buffer, 255); return {buffer}; #endif #else return fmt::format("{0}", error); #endif } } rizsotto-Bear-14c2e01/source/libsys/source/Guard.cc000066400000000000000000000054221476774233700223040ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Guard.h" #include "libsys/Environment.h" #include #include #include namespace { const char** to_c_array(const std::map& entries) { // allocate the array for the pointer array const auto results = new const char*[entries.size() + 1]; // copy the elements auto results_it = results; for (const auto& entry : entries) { const auto& [key, value] = entry; // allocate the entry const size_t entry_size = key.size() + value.size() + 2; auto result = new char[entry_size]; // assemble the content { auto it = std::copy(key.begin(), key.end(), result); *it++ = '='; it = std::copy(value.begin(), value.end(), it); *it = '\0'; } // put into the pointer array *results_it++ = result; } // set the terminator null pointer *results_it = nullptr; return results; } } namespace sys::env { Guard::Guard(const std::map &environment) : data_(to_c_array(environment)) { } Guard::~Guard() noexcept { for (const char** it = data_; *it != nullptr; ++it) { delete[] * it; } delete[] data_; } const char** Guard::data() const { return data_; } Vars from(const char** const input) { Vars result; if (input == nullptr) return result; for (const char** it = input; *it != nullptr; ++it) { const auto end = *it + std::strlen(*it); const auto sep = std::find(*it, end, '='); const std::string key = (sep != end) ? std::string(*it, sep) : std::string(*it, end); const std::string value = (sep != end) ? std::string(sep + 1, end) : std::string(); result.emplace(key, value); } return result; } } rizsotto-Bear-14c2e01/source/libsys/source/Guard.h000066400000000000000000000027631476774233700221530ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include "config.h" #include #include namespace sys::env { // Memory resource guard class. // // The OS expect `const char**`, but the caller usually manipulated // the values in different form. This class let the caller use a more // convenient form (`std::map`) to use, // but makes the final `const char**` not leak. class Guard { public: explicit Guard(const std::map &environment); ~Guard() noexcept; [[nodiscard]] const char** data() const; public: NON_DEFAULT_CONSTRUCTABLE(Guard) NON_COPYABLE_NOR_MOVABLE(Guard) private: const char** data_; }; } rizsotto-Bear-14c2e01/source/libsys/source/Os.cc000066400000000000000000000042511476774233700216220ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libsys/Os.h" #include "libsys/Errors.h" #include "config.h" #if defined HAVE_CONFSTR #include #include #endif #include namespace sys::os { #if defined HAVE_CONFSTR constexpr const size_t BUFFER_SIZE = 1024; rust::Result get_confstr(const int key) { errno = 0; const size_t buffer_size = ::confstr(key, nullptr, 0); if (buffer_size != 0 && buffer_size < BUFFER_SIZE) { char buffer[BUFFER_SIZE]; if (const size_t size = ::confstr(key, buffer, buffer_size); size != 0) { return rust::Ok(std::string(buffer)); } } return rust::Err(std::runtime_error( fmt::format("System call \"confstr\" failed.: {}", error_string(errno)))); } #endif rust::Result get_path(const sys::env::Vars& environment) { if (auto candidate = environment.find("PATH"); candidate != environment.end()) { return rust::Ok(candidate->second); } #if defined HAVE_CS_PATH && defined HAVE_CONFSTR return get_confstr(_CS_PATH) .map_err([](auto error) { return std::runtime_error( fmt::format("Could not find PATH: {}", error.what())); }); #else return rust::Err(std::runtime_error("Could not find PATH in environment.")); #endif } } rizsotto-Bear-14c2e01/source/libsys/source/Path.cc000066400000000000000000000047111476774233700221360ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libsys/Path.h" #include #include namespace { std::list split_by(const std::string &input, const char sep) { std::list result; // make an early return if there is no chance to have something. if (input.empty()) { return result; } // otherwise start to collect the elements into result. std::string::size_type previous = 0; do { const std::string::size_type current = input.find(sep, previous); result.emplace_back(input.substr(previous, current - previous)); previous = (current != std::string::npos) ? current + 1 : current; } while (previous != std::string::npos); return result; } std::string join_with(const std::list &input, const char sep) { std::string result; for (auto it = input.begin(); it != input.end(); ++it) { if (it != input.begin()) { result.push_back(sep); } result.append(it->string()); } return result; } } namespace sys::path { std::list split(const std::string &input) { return split_by(input, OS_PATH_SEPARATOR); } std::string join(const std::list &input) { return join_with(input, OS_PATH_SEPARATOR); } rust::Result get_cwd() { std::error_code error_code; auto result = fs::current_path(error_code); return (error_code) ? rust::Result(rust::Err(std::runtime_error(error_code.message()))) : rust::Result(rust::Ok(std::move(result))); } } rizsotto-Bear-14c2e01/source/libsys/source/Process.cc000066400000000000000000000257511476774233700226670ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libsys/Process.h" #include "libsys/Path.h" #include "libsys/Errors.h" #include "Guard.h" #include #include #include #include #include #include #include #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SPAWN_H #include #endif #ifdef HAVE_DLFCN_H #include #endif #ifdef HAVE_GNU_LIB_NAMES_H # include #else # include "libsys/lib-names.h" #endif #include #include #include namespace { constexpr char PATH_TO_SH[] = "/bin/sh"; using posix_spawn_t = int (*)( pid_t * pid, const char* path, const posix_spawn_file_actions_t* file_actions_ptr, const posix_spawnattr_t* attr_ptr, char* const argv[], char* const envp[]); #ifdef SUPPORT_PRELOAD rust::Result resolve_spawn_function() { errno = 0; void *handle = ::dlopen(LIBC_SO, RTLD_LAZY); if (handle == nullptr) { const auto message = fmt::format("System call \"dlopen\" failed: {}", ::dlerror()); return rust::Err(std::runtime_error(message)); } errno = 0; auto fp = reinterpret_cast(::dlsym(handle, "posix_spawnp")); if (fp == nullptr) { const auto message = fmt::format("System call \"dlsym\" failed: {}", ::dlerror()); return rust::Err(std::runtime_error(message)); } return rust::Ok(fp); } #endif bool is_open(int fd) { struct stat stat_buf; errno = 0; if ((0 != fstat(fd, &stat_buf)) && (errno == EBADF)) { return false; } return true; } rust::Result spawn_process( posix_spawn_t fp, const fs::path& program, const std::list& parameters, const std::map& environment, const bool redirect_io) { // convert the arguments into a c-style array std::vector args; std::transform(parameters.begin(), parameters.end(), std::back_insert_iterator(args), [](const auto& arg) { return const_cast(arg.c_str()); }); args.push_back(nullptr); // convert the environment into a c-style array sys::env::Guard env(environment); // deal with file handles posix_spawn_file_actions_t file_actions; posix_spawn_file_actions_t *file_actionsp = nullptr; if (redirect_io) { errno = 0; if (0 != posix_spawn_file_actions_init(&file_actions)) { const auto message = fmt::format("System call \"posix_spawn_file_actions_init\" failed: {}", sys::error_string(errno)); return rust::Err(std::runtime_error(message)); } for (int fd = 0; fd < 3; ++fd) { if (!is_open(fd)) { errno = 0; if (0 != posix_spawn_file_actions_addclose(&file_actions, fd)) { const auto message = fmt::format("System call \"posix_spawn_file_actions_addclose\" failed: {}", sys::error_string(errno)); return rust::Err(std::runtime_error(message)); } } } file_actionsp = &file_actions; } pid_t child; errno = 0; if (0 != (*fp)(&child, program.c_str(), file_actionsp, nullptr, const_cast(args.data()), const_cast(env.data()))) { const auto message = fmt::format("System call \"posix_spawnp\" failed: {}", sys::error_string(errno)); if (redirect_io) { posix_spawn_file_actions_destroy(&file_actions); } return rust::Err(std::runtime_error(message)); } else { if (redirect_io) { posix_spawn_file_actions_destroy(&file_actions); } return rust::Ok(child); } } rust::Result spawn_process_with_retry( posix_spawn_t fp, const fs::path& program, const std::list& parameters, const std::map& environment, const bool redirect_io) { return spawn_process(fp, program, parameters, environment, redirect_io) // The file is accessible, but it is not an executable file. // Invoke the shell to interpret it as a script. .or_else([&](const std::runtime_error&) { spdlog::debug("Process spawn failed. [will retry as shell]"); std::list args(parameters); args.insert(args.begin(), std::string(PATH_TO_SH)); return spawn_process(fp, PATH_TO_SH, args, environment, redirect_io); }) .on_success([¶meters](const auto& pid) { spdlog::debug("Process spawned. [pid: {}, command: {}]", pid, parameters); }) .on_error([¶meters](const auto& error) { spdlog::debug("Process spawn failed. [error: {}, command: {}]", error.what(), parameters); }); } rust::Result wait_for(const pid_t pid, const bool request_for_signals) { const int mask = request_for_signals ? (WUNTRACED | WCONTINUED) : 0; errno = 0; if (int status; -1 != ::waitpid(pid, &status, mask)) { if (WIFEXITED(status)) { return rust::Ok(sys::ExitStatus(true, WEXITSTATUS(status))); } else if (WIFSIGNALED(status)) { return rust::Ok(sys::ExitStatus(false, WTERMSIG(status))); } else if (WIFSTOPPED(status)) { return rust::Ok(sys::ExitStatus(false, WSTOPSIG(status))); } else if (WIFCONTINUED(status)) { return rust::Ok(sys::ExitStatus(false, SIGCONT)); } else { return rust::Err(std::runtime_error("System call \"waitpid\" result is broken.")); } } else { auto message = fmt::format("System call \"waitpid\" failed: {}", sys::error_string(errno)); return rust::Err(std::runtime_error(message)); } } rust::Result send_signal(const pid_t pid, const int num) { errno = 0; if (const int result = ::kill(pid, num); 0 == result) { return rust::Ok(result); } else { auto message = fmt::format("System call \"kill\" failed: {}", sys::error_string(errno)); return rust::Err(std::runtime_error(message)); } } } namespace sys { ExitStatus::ExitStatus(bool is_code, int code) : is_code_(is_code) , code_(code) { } std::optional ExitStatus::code() const { return is_code_ ? std::make_optional(code_) : std::optional(); } std::optional ExitStatus::signal() const { return is_code_ ? std::optional() : std::make_optional(code_); } bool ExitStatus::is_signaled() const { return !is_code_; } bool ExitStatus::is_exited() const { return is_code_ || ((code_ != SIGCONT) && (code_ != SIGSTOP)); } Process::Process(pid_t pid) : pid_(pid) { } pid_t Process::get_pid() const { return pid_; } rust::Result Process::wait(const bool request_for_signals) { spdlog::debug("Process wait requested. [pid: {}]", pid_); return wait_for(pid_, request_for_signals) .on_success([this](const auto&) { spdlog::debug("Process wait request: done. [pid: {}]", pid_); }) .on_error([this](const auto& error) { spdlog::debug("Process wait request: failed. [pid: {}] {}", pid_, error.what()); }); } rust::Result Process::kill(int num) { spdlog::debug("Process kill requested. [pid: {}, signum: {}]", pid_, num); return send_signal(pid_, num) .on_success([this](const auto&) { spdlog::debug("Process kill request: done. [pid: {}]", pid_); }) .on_error([this](const auto& error) { spdlog::debug("Process kill request: failed. [pid: {}] {}", pid_, error.what()); }); } Process::Builder::Builder(fs::path program, bool with_preload) : program_(std::move(program)) , with_preload_(with_preload) , parameters_() , environment_() , redirect_io_(false) { } Process::Builder& Process::Builder::add_argument(const char* param) { parameters_.emplace_back(std::string(param)); return *this; } Process::Builder& Process::Builder::add_argument(std::string&& param) { parameters_.emplace_back(param); return *this; } Process::Builder& Process::Builder::add_argument(const std::string_view& param) { parameters_.emplace_back(std::string(param)); return *this; } Process::Builder& Process::Builder::set_environment(std::map&& environment) { std::swap(environment_, environment); return *this; } Process::Builder& Process::Builder::set_environment(const std::map& environment) { environment_ = environment; return *this; } Process::Builder& Process::Builder::set_redirect_io() { redirect_io_ = true; return *this; } rust::Result Process::Builder::spawn() const { #ifdef SUPPORT_PRELOAD const rust::Result fp = with_preload_ ? resolve_spawn_function() : rust::Ok(&::posix_spawn); #else const rust::Result fp = rust::Ok(&::posix_spawn); #endif return fp .and_then([this](auto fp) { return spawn_process_with_retry(fp, program_, parameters_, environment_, redirect_io_); }) .map([](auto pid) { return Process(pid); }); } } rizsotto-Bear-14c2e01/source/libsys/source/Signal.cc000066400000000000000000000035401476774233700224560ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "libsys/Signal.h" #include "libsys/Process.h" #include namespace { inline constexpr bool shall_forward(const int signum) { switch (signum) { case SIGKILL: case SIGCHLD: return false; default: return true; } } std::set CHILD_PROCESSES; void handler(const int signum) { if (shall_forward(signum)) { for (const auto pid : CHILD_PROCESSES) { ::kill(pid, signum); } } } } namespace sys { SignalForwarder::SignalForwarder(const Process &child) noexcept : pid_(child.get_pid()) , handlers_() { CHILD_PROCESSES.insert(pid_); for (int signum = 1; signum < NSIG; ++signum) { handlers_[signum] = ::signal(signum, &handler); } } SignalForwarder::~SignalForwarder() noexcept { CHILD_PROCESSES.erase(pid_); for (int signum = 1; signum < NSIG; ++signum) { ::signal(signum, handlers_[signum]); } } } rizsotto-Bear-14c2e01/source/libsys/test/000077500000000000000000000000001476774233700204075ustar00rootroot00000000000000rizsotto-Bear-14c2e01/source/libsys/test/EnvironmentTest.cc000066400000000000000000000060131476774233700240620ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libsys/Environment.h" #include "Guard.h" namespace { TEST(environment, nullptr_to_empty_map) { auto result = sys::env::from(nullptr); EXPECT_TRUE(result.empty()); } TEST(environment, non_nullptr_to_non_empty_map) { const char* envp[] = { "sky=blue", nullptr }; auto result = sys::env::from(envp); EXPECT_FALSE(result.empty()); EXPECT_EQ("blue", result["sky"]); } TEST(environment, missing_value_does_not_crash) { const char* envp[] = { "only_key", nullptr }; auto result = sys::env::from(envp); EXPECT_FALSE(result.empty()); EXPECT_EQ("", result["only_key"]); } TEST(environment, missing_value_with_assign_does_not_crash) { const char* envp[] = { "only_key=", nullptr }; auto result = sys::env::from(envp); EXPECT_FALSE(result.empty()); EXPECT_EQ("", result["only_key"]); } TEST(environment, empty_value_does_not_crash) { const char* envp[] = { "", nullptr }; auto result = sys::env::from(envp); EXPECT_FALSE(result.empty()); EXPECT_EQ("", result[""]); } TEST(environment, empty_value_with_assign_does_not_crash) { const char* envp[] = { "=", nullptr }; auto result = sys::env::from(envp); EXPECT_FALSE(result.empty()); EXPECT_EQ("", result[""]); } TEST(environment, empty_map_creates_empty_array) { const std::map input = {}; const sys::env::Guard sut(input); EXPECT_TRUE(sut.data() != nullptr); EXPECT_TRUE(sut.data()[0] == nullptr); } TEST(environment, non_empty_map_creates_array) { const std::map input = { { "grass", "green" }, { "sky", "blue" } }; const sys::env::Guard sut(input); EXPECT_TRUE(sut.data() != nullptr); EXPECT_STREQ(sut.data()[0], "grass=green"); EXPECT_STREQ(sut.data()[1], "sky=blue"); EXPECT_TRUE(sut.data()[2] == nullptr); } }rizsotto-Bear-14c2e01/source/libsys/test/ErrorsTest.cc000066400000000000000000000020061476774233700230300ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libsys/Errors.h" #include namespace { TEST(errors, ENOENT) { auto result = sys::error_string(ENOENT); EXPECT_STREQ("No such file or directory", result.data()); } }rizsotto-Bear-14c2e01/source/libsys/test/PathTest.cc000066400000000000000000000042471476774233700224610ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gtest/gtest.h" #include "libsys/Path.h" namespace { TEST(path, split_produces_empty_list_for_empty_string) { const auto result = sys::path::split(""); EXPECT_TRUE(result.empty()); } TEST(path, split_produces_list_for_single_entry) { const auto result = sys::path::split("/path/to"); const std::list expected = { "/path/to" }; EXPECT_EQ(expected, result); } TEST(path, split_produces_list_for_multiple_entries) { const auto result = sys::path::split("/path/to:/path/to/another"); const std::list expected = { "/path/to", "/path/to/another" }; EXPECT_EQ(expected, result); } TEST(path, join_empty_list) { const std::list input = {}; const auto result = sys::path::join(input); EXPECT_TRUE(result.empty()); } TEST(path, join_single_entry) { const std::list input = { "/path/to" }; const auto result = sys::path::join(input); const std::string expected = "/path/to"; EXPECT_EQ(expected, result); } TEST(path, join_multiple_entries) { const std::list input = { "/path/to", "/path/to/another" }; const auto result = sys::path::join(input); const std::string expected = "/path/to:/path/to/another"; EXPECT_EQ(expected, result); } }rizsotto-Bear-14c2e01/test/000077500000000000000000000000001476774233700156025ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/CMakeLists.txt000066400000000000000000000047511476774233700203510ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12 FATAL_ERROR) cmake_policy(VERSION 3.12) project(BearTest LANGUAGES C) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) set(CMAKE_REQUIRED_FLAGS -D_GNU_SOURCE) include(CheckIncludeFile) check_include_file(spawn.h HAVE_SPAWN_H) check_include_file(unistd.h HAVE_UNISTD_H) check_include_file(sys/wait.h HAVE_SYS_WAIT_H) check_include_file(sys/types.h HAVE_SYS_TYPES_H) include(CheckSymbolExists) check_symbol_exists(execve "unistd.h" HAVE_EXECVE) check_symbol_exists(execv "unistd.h" HAVE_EXECV) check_symbol_exists(execvpe "unistd.h" HAVE_EXECVPE) check_symbol_exists(execvp "unistd.h" HAVE_EXECVP) check_symbol_exists(execvP "unistd.h" HAVE_EXECVP2) check_symbol_exists(exect "unistd.h" HAVE_EXECT) check_symbol_exists(execl "unistd.h" HAVE_EXECL) check_symbol_exists(execlp "unistd.h" HAVE_EXECLP) check_symbol_exists(execle "unistd.h" HAVE_EXECLE) check_symbol_exists(execveat "unistd.h" HAVE_EXECVEAT) check_symbol_exists(fexecve "unistd.h" HAVE_FEXECVE) check_symbol_exists(posix_spawn "spawn.h" HAVE_POSIX_SPAWN) check_symbol_exists(posix_spawnp "spawn.h" HAVE_POSIX_SPAWNP) check_symbol_exists(popen "stdio.h" HAVE_POPEN) check_symbol_exists(system "stdlib.h" HAVE_SYSTEM) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg @ONLY) enable_testing() message(STATUS "Looking for lit") find_program(LIT_EXECUTABLE NAMES lit PATHS ENV LIT_PATH) if (LIT_EXECUTABLE) message(STATUS "Looking for lit -- ${LIT_EXECUTABLE}") include(GNUInstallDirs) add_test(NAME bear::func_test COMMAND ${LIT_EXECUTABLE} -D_TEST_EXEC_ROOT=${CMAKE_CURRENT_BINARY_DIR} -D_BIN_BEAR=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/bear -D_BIN_CITNAMES=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/citnames -D_BIN_INTERCEPT=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/intercept -D_BIN_WRAPPER=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/bear/wrapper -D_BIN_WRAPPER_DIR=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/bear/wrapper.d -D_LIB_EXEC=${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/bear/${CMAKE_SHARED_LIBRARY_PREFIX}exec${CMAKE_SHARED_LIBRARY_SUFFIX} -v ${CMAKE_CURRENT_BINARY_DIR}) set_tests_properties(bear::func_test PROPERTIES TIMEOUT 90 ) endif () rizsotto-Bear-14c2e01/test/bin/000077500000000000000000000000001476774233700163525ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/bin/assert_compilation000077500000000000000000000065671476774233700222150ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import argparse import json import logging import shlex def main(): logging.basicConfig(format="assert_compilation: %(message)s", level=logging.INFO) parser = argparse.ArgumentParser() parser.add_argument(dest='input', type=argparse.FileType(mode='r')) subparsers = parser.add_subparsers(dest='command') count_parser = subparsers.add_parser('count').add_mutually_exclusive_group() count_parser.add_argument('-eq', dest='eq', type=int) count_parser.add_argument('-gt', dest='gt', type=int) count_parser.add_argument('-ge', dest='ge', type=int) count_parser.add_argument('-lt', dest='lt', type=int) count_parser.add_argument('-le', dest='le', type=int) entry_parser = subparsers.add_parser('contains') entry_parser.add_argument('-file') entry_parser.add_argument('-output') entry_parser.add_argument('-directory') entry_parser.add_argument('-arguments', nargs=argparse.REMAINDER) args = parser.parse_args() logging.debug(args) entries = json.load(args.input) if args.command == 'count': count = len(entries) logging.debug("%s has %d command entries", args.input.name, count) if args.eq is not None and args.eq != count: logging.info('failed: expected %d, but got %d', args.eq, count) return 1 elif args.gt is not None and args.gt >= count: logging.info('failed: expected greater than %d, but got %d', args.gt, count) return 1 elif args.ge is not None and args.ge > count: logging.info('failed: expected greater or equal %d, but got %d', args.ge, count) return 1 elif args.lt is not None and args.lt <= count: logging.info('failed: expected less than %d, but got %d', args.lt, count) return 1 elif args.le is not None and args.le < count: logging.info('failed: expected less or equal %d, but got %d', args.le, count) return 1 elif args.command == 'contains': logging.debug("%s has command entries: %s", args.input.name, entries) check = filter_from(args) count = len([cmd for cmd in entries if check(cmd)]) if count == 0: request = to_string(args) logging.info('failed: expected at least one entry, but found none: %s', request) return 1 return 0 def filter_from(args): def test(entry): p1 = True if args.file is None else entry['file'] == args.file p2 = True if args.output is None else entry['output'] == args.output p3 = True if args.directory is None else entry['directory'] == args.directory if args.arguments is not None: if 'arguments' in entry: p4 = entry['arguments'] == args.arguments else: p4 = shlex.split(entry['command']) == args.arguments else: p4 = True return p1 and p2 and p3 and p4 return test def to_string(args): p1 = '' if args.file is None else '-file {}'.format(args.file) p2 = '' if args.output is None else '-output {}'.format(args.output) p3 = '' if args.directory is None else '-directory {}'.format(args.directory) p4 = '' if args.arguments is None else ' '.join(['-arguments'] + args.arguments) return ' '.join([p1, p2, p3, p4]) if __name__ == "__main__": sys.exit(main()) rizsotto-Bear-14c2e01/test/bin/assert_intercepted000077500000000000000000000066401476774233700221750ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import argparse import json import logging def main(): logging.basicConfig(format="assert_intercepted: %(message)s", level=logging.INFO) parser = argparse.ArgumentParser() parser.add_argument(dest='input', type=argparse.FileType(mode='r')) subparsers = parser.add_subparsers(dest='command') count_parser = subparsers.add_parser('count').add_mutually_exclusive_group() count_parser.add_argument('-eq', dest='eq', type=int) count_parser.add_argument('-gt', dest='gt', type=int) count_parser.add_argument('-ge', dest='ge', type=int) count_parser.add_argument('-lt', dest='lt', type=int) count_parser.add_argument('-le', dest='le', type=int) entry_parser = subparsers.add_parser('contains') entry_parser.add_argument('-program') entry_parser.add_argument('-working_dir') entry_parser.add_argument('-arguments', nargs=argparse.REMAINDER) args = parser.parse_args() logging.debug(args) commands = list(load_commands(args.input)) if args.command == 'count': count = len(commands) logging.debug("%s has %d command entries", args.input.name, count) if args.eq is not None and args.eq != count: logging.info('failed: expected %d, but got %d', args.eq, count) return 1 elif args.gt is not None and args.gt >= count: logging.info('failed: expected greater than %d, but got %d', args.gt, count) return 1 elif args.ge is not None and args.ge > count: logging.info('failed: expected greater or equal %d, but got %d', args.ge, count) return 1 elif args.lt is not None and args.lt <= count: logging.info('failed: expected less than %d, but got %d', args.lt, count) return 1 elif args.le is not None and args.le < count: logging.info('failed: expected less or equal %d, but got %d', args.le, count) return 1 elif args.command == 'contains': logging.debug("%s has command entries: %s", args.input.name, commands) check = filter_from(args) count = len([cmd for cmd in commands if check(cmd)]) if count == 0: request = to_string(args) logging.info('failed: expected at least one entry, but found none: %s', request) return 1 return 0 def load_commands(input): for line in input: event = json.loads(line) if 'started' in event: yield { 'program': event['started']['execution']['executable'], 'working_dir': event['started']['execution']['working_dir'], 'arguments': event['started']['execution']['arguments'] } def filter_from(args): def test(command): p1 = True if args.program is None else command['program'] == args.program p2 = True if args.working_dir is None else command['working_dir'] == args.working_dir p3 = True if args.arguments is None else command['arguments'] == args.arguments return p1 and p2 and p3 return test def to_string(args): p1 = '' if args.program is None else '-program {}'.format(args.program) p2 = '' if args.working_dir is None else '-working_dir {}'.format(args.working_dir) p3 = '' if args.arguments is None else ' '.join(['-arguments'] + args.arguments) return ' '.join([p1, p2, p3]) if __name__ == "__main__": sys.exit(main()) rizsotto-Bear-14c2e01/test/cases/000077500000000000000000000000001476774233700167005ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/citnames/000077500000000000000000000000001476774233700205035ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/citnames/exit_code/000077500000000000000000000000001476774233700224465ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/citnames/exit_code/exit_code_for_fail.sh000066400000000000000000000001171476774233700266050ustar00rootroot00000000000000#!/usr/bin/env sh # XFAIL: * # RUN: %{citnames} --verbose --input /non/exists rizsotto-Bear-14c2e01/test/cases/citnames/exit_code/exit_code_for_help.sh000066400000000000000000000000541476774233700266220ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: %{citnames} --helprizsotto-Bear-14c2e01/test/cases/citnames/exit_code/exit_code_for_success.sh000066400000000000000000000030501476774233700273410ustar00rootroot00000000000000#!/usr/bin/env sh # UNSUPPORTED: true # RUN: cd %T; %{shell} %s %t.commands.json # RUN: %{citnames} --verbose --input %t.commands.json --output %t.compilations.json # RUN: assert_compilation %t.compilations.json count -eq 0 cat > $1 << EOF { "context": { "host_info": { "_CS_GNU_LIBC_VERSION": "glibc 2.31", "_CS_GNU_LIBPTHREAD_VERSION": "NPTL 2.31", "_CS_PATH": "/usr/bin", "machine": "x86_64", "release": "5.8.4-200.fc32.x86_64", "sysname": "Linux", "version": "#1 SMP Wed Aug 26 22:28:08 UTC 2020" }, "intercept": "library preload" }, "executions": [ { "command": { "arguments": [ "/usr/bin/bash", "/home/user/build.sh" ], "environment": { "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin" }, "program": "/usr/bin/bash", "working_dir": "/home/user" }, "run": { "events": [ { "at": "2020-09-13T21:13:04.724530Z", "type": "started" }, { "at": "2020-09-13T21:13:04.798790Z", "status": 0, "type": "terminated" } ], "pid": 629422, "ppid": 629392 } } ] } EOF rizsotto-Bear-14c2e01/test/cases/citnames/output/000077500000000000000000000000001476774233700220435ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/citnames/output/clang_plugin.sh000066400000000000000000000030201476774233700250340ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: cd %T; %{shell} %s %t # RUN: %{citnames} --verbose --input %t.commands.json --output %t.compilations.json --config %t.config.json # RUN: assert_compilation %t.compilations.json count -eq 1 # RUN: assert_compilation %t.compilations.json contains -file /home/user/broken_build.c -directory /home/user -arguments /usr/bin/clang -c -Xclang -load -Xclang /path/to/LLVMHello.so -o broken_build.o broken_build.c cat > "$1.config.json" << EOF { "compilation": { "compilers_to_recognize": [] }, "output": { "content": { "include_only_existing_source": false }, "format": { "command_as_array": true, "drop_output_field": false } } } EOF cat << EOF | tr '\r\n' ' ' > "$1.commands.json" { "rid": "13711651845693228889", "started": { "execution": { "executable": "/usr/bin/clang", "arguments": [ "/usr/bin/clang", "-c", "-o", "broken_build.o", "broken_build.c", "-Xclang", "-load", "-Xclang", "/path/to/LLVMHello.so" ], "working_dir": "/home/user", "environment": { "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin" } }, "pid": 380296, "ppid":380286 }, "timestamp": "2021-07-17T02:59:36.338446Z" } EOF echo "" >> "$1.commands.json" cat << EOF | tr '\r\n' ' ' >> "$1.commands.json" { "rid": "13711651845693228889", "terminated": { "status": "0" }, "timestamp": "2021-07-17T02:59:36.344702Z" } EOF echo "" >> "$1.commands.json" rizsotto-Bear-14c2e01/test/cases/citnames/output/convert_format.sh000066400000000000000000000024601476774233700254310ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: cd %T; %{shell} %s %t # RUN: assert_compilation %t.compilations.json count -eq 1 # RUN: assert_compilation %t.compilations.json contains -file /home/user/broken_build.c -directory /home/user -arguments /usr/bin/gcc -c -o broken_build.o broken_build.c # RUN: %{citnames} --verbose --input %t.commands.json --output %t.compilations.json --config %t.config.json --append # RUN: assert_compilation %t.compilations.json count -eq 1 # RUN: assert_compilation %t.compilations.json contains -file /home/user/broken_build.c -directory /home/user -arguments /usr/bin/gcc -c -o broken_build.o broken_build.c cat > "$1.config.json" << EOF { "compilation": { "compilers_to_recognize": [ { "executable": "/usr/bin/gcc" }, { "executable": "/usr/bin/c++" } ] }, "output": { "content": { "include_only_existing_source": false }, "format": { "command_as_array": false, "drop_output_field": false } } } EOF cat > "$1.compilations.json" << EOF [ { "arguments": [ "/usr/bin/gcc", "-c", "-o", "broken_build.o", "broken_build.c" ], "directory": "/home/user", "file": "/home/user/broken_build.c", "output": "/home/user/broken_build.o" } ] EOF cat > "$1.commands.json" << EOF EOF rizsotto-Bear-14c2e01/test/cases/citnames/output/relative_paths_converted.sh000066400000000000000000000027421476774233700274670ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: cd %T; %{shell} %s %t # RUN: %{citnames} --verbose --input %t.commands.json --output %t.compilations.json --config %t.config.json # RUN: assert_compilation %t.compilations.json count -eq 1 # RUN: assert_compilation %t.compilations.json contains -file /home/user/broken_build.c -directory /home/user/build -arguments /usr/bin/wrapper -c -o broken_build.o ../broken_build.c cat > "$1.config.json" << EOF { "compilation": { "compilers_to_recognize": [ { "executable": "/usr/bin/wrapper" } ] }, "output": { "content": { "include_only_existing_source": false }, "format": { "command_as_array": true, "drop_output_field": false } } } EOF cat << EOF | tr '\r\n' ' ' > "$1.commands.json" { "rid": "13711651845693228889", "started": { "execution": { "executable": "/usr/bin/wrapper", "arguments": [ "/usr/bin/wrapper", "-c", "-o", "broken_build.o", "../broken_build.c" ], "working_dir": "/home/user/build", "environment": { "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin" } }, "pid": 380296, "ppid":380286 }, "timestamp": "2021-07-17T02:59:36.338446Z" } EOF echo "" >> "$1.commands.json" cat << EOF | tr '\r\n' ' ' >> "$1.commands.json" { "rid": "13711651845693228889", "terminated": { "status": "0" }, "timestamp": "2021-07-17T02:59:36.344702Z" } EOF echo "" >> "$1.commands.json" rizsotto-Bear-14c2e01/test/cases/citnames/output/wrapper_flags_extended.sh000066400000000000000000000030701476774233700271130ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: cd %T; %{shell} %s %t # RUN: %{citnames} --verbose --input %t.commands.json --output %t.compilations.json --config %t.config.json # RUN: assert_compilation %t.compilations.json count -eq 1 # RUN: assert_compilation %t.compilations.json contains -file /home/user/broken_build.c -directory /home/user -arguments /usr/bin/wrapper -c -Dwrapper -o broken_build.o broken_build.c cat > "$1.config.json" << EOF { "compilation": { "compilers_to_recognize": [ { "executable": "/usr/bin/wrapper", "flags_to_add": ["-Dwrapper"], "flags_to_remove": ["-Wall"] } ] }, "output": { "content": { "include_only_existing_source": false }, "format": { "command_as_array": true, "drop_output_field": false } } } EOF cat << EOF | tr '\r\n' ' ' > "$1.commands.json" { "rid": "13711651845693228889", "started": { "execution": { "executable": "/usr/bin/wrapper", "arguments": [ "/usr/bin/wrapper", "-c", "-o", "broken_build.o", "broken_build.c", "-Wall" ], "working_dir": "/home/user", "environment": { "PATH": "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin" } }, "pid": 380296, "ppid":380286 }, "timestamp": "2021-07-17T02:59:36.338446Z" } EOF echo "" >> "$1.commands.json" cat << EOF | tr '\r\n' ' ' >> "$1.commands.json" { "rid": "13711651845693228889", "terminated": { "status": "0" }, "timestamp": "2021-07-17T02:59:36.344702Z" } EOF echo "" >> "$1.commands.json" rizsotto-Bear-14c2e01/test/cases/compilation/000077500000000000000000000000001476774233700212165ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/compilation/exit_code/000077500000000000000000000000001476774233700231615ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/compilation/exit_code/exit_code_for_empty.sh000066400000000000000000000000551476774233700275440ustar00rootroot00000000000000#!/usr/bin/env sh # XFAIL: * # RUN: %{bear} rizsotto-Bear-14c2e01/test/cases/compilation/exit_code/exit_code_for_fail.sh000066400000000000000000000001241476774233700273160ustar00rootroot00000000000000#!/usr/bin/env sh # XFAIL: * # RUN: %{bear} --verbose --output %t.json -- %{false} rizsotto-Bear-14c2e01/test/cases/compilation/exit_code/exit_code_for_help.sh000066400000000000000000000000501476774233700273310ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: %{bear} --helprizsotto-Bear-14c2e01/test/cases/compilation/exit_code/exit_code_for_success.sh000066400000000000000000000001101476774233700300460ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: %{bear} --verbose --output %t.json -- %{true} rizsotto-Bear-14c2e01/test/cases/compilation/output/000077500000000000000000000000001476774233700225565ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/compilation/output/assembly_sources.mk000066400000000000000000000011361476774233700264720ustar00rootroot00000000000000#!/usr/bin/env make # REQUIRES: make # RUN: %{make} -C %T -f %s clean # RUN: %{bear} --verbose --output %t.json -- %{make} -C %T -f %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/main.c -directory %T -arguments %{c_compiler} -S -o main.s main.c # RUN: assert_compilation %t.json contains -file %T/main.s -directory %T -arguments %{c_compiler} -c -o main.o main.s main: main.o $(CC) $< -o $@ main.s: main.c $(CC) -S $< -o $@ main.o: main.s $(CC) -c $< -o $@ main.c: echo "int main() { return 0; }" > $@ clean: rm -f main main.o main.s main.c rizsotto-Bear-14c2e01/test/cases/compilation/output/broken_build.sh000066400000000000000000000005721476774233700255550ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -ge 1 # RUN: assert_compilation %t.json contains -file %T/broken_build.c -directory %T -arguments %{c_compiler} -c -o broken_build.o broken_build.c echo "int test() { ;" > broken_build.c $CC -c -o broken_build.o broken_build.c || true rizsotto-Bear-14c2e01/test/cases/compilation/output/bug439.mk000066400000000000000000000012731476774233700241270ustar00rootroot00000000000000#!/usr/bin/env make # REQUIRES: make, shell # RUN: mkdir -p %T/make # RUN: %{make} -C %T -f %s clean # RUN: %{shell} -c "PATH=%T:$PATH %{bear} --verbose --output %t.json -- %{make} -C %T -f %s" # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/bug439.c -directory %T -arguments %{c_compiler} -S -o bug439.s bug439.c # RUN: assert_compilation %t.json contains -file %T/bug439.s -directory %T -arguments %{c_compiler} -c -o bug439.o bug439.s bug439: bug439.o $(CC) $< -o $@ bug439.s: bug439.c $(CC) -S $< -o $@ bug439.o: bug439.s $(CC) -c $< -o $@ bug439.c: echo "int main() { return 0; }" > $@ clean: rm -f bug439 bug439.o bug439.s bug439.c rizsotto-Bear-14c2e01/test/cases/compilation/output/compile_cuda.sh000066400000000000000000000007701476774233700255420ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell, cuda # RUN: cd %T; env CC=%{cuda} %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/successful_build_1.cu -directory %T # RUN: assert_compilation %t.json contains -file %T/successful_build_2.cu -directory %T touch successful_build_1.cu successful_build_2.cu $CC -c -o successful_build_1.o successful_build_1.cu; $CC -c -o successful_build_2.o successful_build_2.cu; rizsotto-Bear-14c2e01/test/cases/compilation/output/compile_fortran.sh000066400000000000000000000007571476774233700263060ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell, fortran # RUN: cd %T; env FC=%{fortran} %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 1 # RUN: assert_compilation %t.json contains -file %T/compile_fortran.f95 -directory %T -arguments %{fortran} -c -o compile_fortran.o compile_fortran.f95 cat > compile_fortran.f95 << EOF ! Test Program program first print *,'This is my first program' end program first EOF $FC -c -o compile_fortran.o compile_fortran.f95 rizsotto-Bear-14c2e01/test/cases/compilation/output/config/000077500000000000000000000000001476774233700240235ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/compilation/output/config/filter_compilers.sh000066400000000000000000000020551476774233700277230ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s %t # RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c %t/source_2.c TEST=$1 mkdir -p $TEST; touch $TEST/source_1.c; touch $TEST/source_2.c; mkdir -p $TEST/exclude; touch $TEST/exclude/source_1.cc; touch $TEST/exclude/source_2.cc; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh \$CC -c $TEST/source_1.c; \$CC -c $TEST/source_2.c; \$CXX -c $TEST/exclude/source_1.cc; \$CXX -c $TEST/exclude/source_2.cc; EOF cat > "$TEST/config.json" << EOF { "compilation": { "compilers_to_exclude": [ "$CXX" ] }, "output": { "content": { "include_only_existing_source": true }, "format": { "command_as_array": true, "drop_output_field": true } } } EOF rizsotto-Bear-14c2e01/test/cases/compilation/output/config/filter_flags.sh000066400000000000000000000030631476774233700270220ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s %t # RUN: cd %T; env CC=%t/wrapper %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %t/wrapper -c -I. %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %t/wrapper -c -Werror -I. %t/source_2.c # RUN: cd %T; env CC=%t/wrapper %{bear} --verbose --output %t.json --config %t/config.json --force-wrapper -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %t/wrapper -c -I. %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %t/wrapper -c -Werror -I. %t/source_2.c TEST=$1 mkdir $TEST touch $TEST/source_1.c; touch $TEST/source_2.c; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh \$CC -c "$TEST/source_1.c" -Wall; \$CC -c "$TEST/source_2.c" -Werror; EOF cat > "$TEST/config.json" << EOF { "output": { "content": { "include_only_existing_source": true }, "format": { "command_as_array": true, "drop_output_field": true } }, "compilation": { "compilers_to_recognize": [ { "executable": "$TEST/wrapper", "flags_to_add": ["-I."], "flags_to_remove": ["-Wall"] } ] } } EOF cat > "$TEST/wrapper" << EOF #!/usr/bin/env sh echo "wrapper \$*" EOF chmod +x "$TEST/wrapper" rizsotto-Bear-14c2e01/test/cases/compilation/output/config/filter_flags_on_known_compiler.sh000066400000000000000000000026571476774233700326340ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s %t # RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c -I. %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c -Werror -I. %t/source_2.c # RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json --force-wrapper -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c -I. %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c -Werror -I. %t/source_2.c TEST=$1 mkdir $TEST touch $TEST/source_1.c; touch $TEST/source_2.c; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh \$CC -c "$TEST/source_1.c" -Wall; \$CC -c "$TEST/source_2.c" -Werror; EOF cat > "$TEST/config.json" << EOF { "output": { "content": { "include_only_existing_source": true }, "format": { "command_as_array": true, "drop_output_field": true } }, "compilation": { "compilers_to_recognize": [ { "executable": "$CC", "flags_to_add": ["-I."], "flags_to_remove": ["-Wall"] } ] } } EOF rizsotto-Bear-14c2e01/test/cases/compilation/output/config/filter_sources.sh000066400000000000000000000021201476774233700274020ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s %t # RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c %t/source_2.c TEST=$1 mkdir -p $TEST; touch $TEST/source_1.c; touch $TEST/source_2.c; mkdir -p $TEST/exclude; touch $TEST/exclude/source_1.c; touch $TEST/exclude/source_2.c; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh \$CC -c $TEST/source_1.c; \$CC -c $TEST/source_2.c; \$CC -c $TEST/exclude/source_1.c; \$CC -c $TEST/exclude/source_2.c; EOF cat > "$TEST/config.json" << EOF { "output": { "content": { "include_only_existing_source": true, "paths_to_include": [ "$TEST" ], "paths_to_exclude": [ "$TEST/exclude" ] }, "format": { "command_as_array": true, "drop_output_field": true } } } EOF rizsotto-Bear-14c2e01/test/cases/compilation/output/config/filter_sources_relative.sh000066400000000000000000000021011476774233700312740ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s %t # RUN: cd %T; %{bear} --verbose --output %t.json --config %t/config.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c %t/source_2.c TEST=$1 TEST_RELATIVE=$(basename $1) mkdir -p $TEST; touch $TEST/source_1.c; touch $TEST/source_2.c; mkdir -p $TEST/exclude; touch $TEST/exclude/source_1.c; touch $TEST/exclude/source_2.c; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh \$CC -c $TEST/source_1.c; \$CC -c $TEST/source_2.c; \$CC -c $TEST/exclude/source_1.c; \$CC -c $TEST/exclude/source_2.c; EOF cat > "$TEST/config.json" << EOF { "output": { "content": { "include_only_existing_source": true, "paths_to_exclude": [ "$TEST_RELATIVE/exclude" ] }, "format": { "command_as_array": true, "drop_output_field": true } } } EOF rizsotto-Bear-14c2e01/test/cases/compilation/output/define_with_escaped_quote.sh000066400000000000000000000010541476774233700303000ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -ge 1 # RUN: assert_compilation %t.json contains -file %T/define_with_escaped_quote.c -directory %T -arguments %{c_compiler} -c '-DMESSAGE="Hello World\n"' -o define_with_escaped_quote define_with_escaped_quote.c cat > define_with_escaped_quote.c < int main() { printf(MESSAGE); return 0; } EOF $CC '-DMESSAGE="Hello World\n"' -o define_with_escaped_quote define_with_escaped_quote.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/define_with_quote.sh000066400000000000000000000010271476774233700266140ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -ge 1 # RUN: assert_compilation %t.json contains -file %T/define_with_quote.c -directory %T -arguments %{cxx_compiler} -c -DEXPORT="extern \"C\"" -o define_with_quote define_with_quote.c cat > define_with_quote.c < EXPORT void foo(void) { printf("Hello world!\n"); } int main() { foo(); } EOF $CXX -DEXPORT="extern \"C\"" -o define_with_quote define_with_quote.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/duplicate_entries.sh000066400000000000000000000025371476774233700266240ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 4 # RUN: assert_compilation %t.json contains -file %T/duplicate_entries_1.c -directory %T -arguments %{c_compiler} -c -o duplicate_entries_1.o duplicate_entries_1.c # RUN: assert_compilation %t.json contains -file %T/duplicate_entries_2.c -directory %T -arguments %{c_compiler} -c -o duplicate_entries_2.o duplicate_entries_2.c # RUN: assert_compilation %t.json contains -file %T/duplicate_entries_1.c -directory %T -arguments %{c_compiler} -c -D_FLAG=value -o duplicate_entries_3.o duplicate_entries_1.c # RUN: assert_compilation %t.json contains -file %T/duplicate_entries_2.c -directory %T -arguments %{c_compiler} -c -D_FLAG=value -o duplicate_entries_4.o duplicate_entries_2.c touch duplicate_entries_1.c duplicate_entries_2.c $CC -c duplicate_entries_1.c -o duplicate_entries_1.o; $CC -c duplicate_entries_2.c -o duplicate_entries_2.o; $CC -c duplicate_entries_1.c -o duplicate_entries_3.o -D_FLAG=value; $CC -c duplicate_entries_2.c -o duplicate_entries_4.o -D_FLAG=value; $CC -c duplicate_entries_1.c -o duplicate_entries_1.o; $CC -c duplicate_entries_2.c -o duplicate_entries_2.o; $CC -c duplicate_entries_1.c -o duplicate_entries_3.o -D_FLAG=value; $CC -c duplicate_entries_2.c -o duplicate_entries_4.o -D_FLAG=value; rizsotto-Bear-14c2e01/test/cases/compilation/output/empty_argument.sh000066400000000000000000000006061476774233700261540ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 0 touch empty_argument_1.c empty_argument_2.c # empty argument for a command $ECHO "" ""; # empty argument for a compiler $CC -c -o empty_argument_1.o empty_argument_1.c "" || true; $CC -c -o empty_argument_2.o "" empty_argument_2.c || true; rizsotto-Bear-14c2e01/test/cases/compilation/output/empty_env.sh000066400000000000000000000012641476774233700251230ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell # RUN: %{shell} %s %t # RUN: cd %T; /usr/bin/env - %{bear} --verbose --output %t.json -- %{shell} %t/build.sh # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %t/source_1.c -directory %T -arguments %{c_compiler} -c -o %t/source_1.o %t/source_1.c # RUN: assert_compilation %t.json contains -file %t/source_2.c -directory %T -arguments %{c_compiler} -c -o %t/source_2.o %t/source_2.c TEST=$1 mkdir -p $TEST; touch $TEST/source_1.c; touch $TEST/source_2.c; cat > "$TEST/build.sh" << EOF #!/usr/bin/env sh $CC -c -o $TEST/source_1.o $TEST/source_1.c; $CC -c -o $TEST/source_2.o $TEST/source_2.c; EOF rizsotto-Bear-14c2e01/test/cases/compilation/output/existing_files_only.sh000066400000000000000000000016371476774233700271760ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s -build # RUN: assert_compilation %t.json count -ge 2 # RUN: assert_compilation %t.json contains -file %T/exists/src/source_1.c -directory %T -arguments %{c_compiler} -c -o exists/src/source_1.o exists/src/source_1.c # RUN: assert_compilation %t.json contains -file %T/exists/src/source_2.c -directory %T -arguments %{c_compiler} -c -o exists/src/source_2.o exists/src/source_2.c mkdir -p exists exists/config touch exists/config/source_1.c exists/config/source_2.c $CC -c -o exists/config/source_1.o exists/config/source_1.c $CC -c -o exists/config/source_2.o exists/config/source_2.c rm exists/config/source_1.c exists/config/source_2.c mkdir -p exists exists/src touch exists/src/source_1.c exists/src/source_2.c $CC -c -o exists/src/source_1.o exists/src/source_1.c $CC -c -o exists/src/source_2.o exists/src/source_2.c rizsotto-Bear-14c2e01/test/cases/compilation/output/flag/000077500000000000000000000000001476774233700234675ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/compilation/output/flag/append.sh000066400000000000000000000036721476774233700253020ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s -build # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/append/src/source_1.c -directory %T -arguments %{c_compiler} -c -o append/src/source_1.o append/src/source_1.c # RUN: assert_compilation %t.json contains -file %T/append/src/source_2.c -directory %T -arguments %{c_compiler} -c -o append/src/source_2.o append/src/source_2.c # RUN: cd %T; %{bear} --verbose --output %t.json --append -- %{shell} %s -test # RUN: assert_compilation %t.json count -eq 4 # RUN: assert_compilation %t.json contains -file %T/append/src/source_1.c -directory %T -arguments %{c_compiler} -c -o append/src/source_1.o append/src/source_1.c # RUN: assert_compilation %t.json contains -file %T/append/src/source_2.c -directory %T -arguments %{c_compiler} -c -o append/src/source_2.o append/src/source_2.c # RUN: assert_compilation %t.json contains -file %T/append/test/source_1.c -directory %T -arguments %{c_compiler} -c -o append/test/source_1.o append/test/source_1.c # RUN: assert_compilation %t.json contains -file %T/append/test/source_2.c -directory %T -arguments %{c_compiler} -c -o append/test/source_2.o append/test/source_2.c # RUN: cd %T; %{bear} --verbose --output %t.json --append -- %{shell} %s -clean # RUN: assert_compilation %t.json count -eq 0 build() { mkdir -p append append/src touch append/src/source_1.c append/src/source_2.c $CC -c -o append/src/source_1.o append/src/source_1.c $CC -c -o append/src/source_2.o append/src/source_2.c } verify() { mkdir -p append append/test touch append/test/source_1.c append/test/source_2.c $CC -c -o append/test/source_1.o append/test/source_1.c $CC -c -o append/test/source_2.o append/test/source_2.c } clean() { rm -rf append } case $1 in -build) build ;; -test) verify ;; -clean) clean ;; *) # unknown option ;; esac true rizsotto-Bear-14c2e01/test/cases/compilation/output/flag/field_output.sh000066400000000000000000000011671476774233700265330ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/field_output_1.c -output %T/field_output_1.o -directory %T -arguments %{c_compiler} -c -o field_output_1.o field_output_1.c # RUN: assert_compilation %t.json contains -file %T/field_output_2.c -output %T/field_output_2.o -directory %T -arguments %{c_compiler} -c -o field_output_2.o field_output_2.c touch field_output_1.c field_output_2.c $CC -c -o field_output_1.o field_output_1.c; $CC -c -o field_output_2.o field_output_2.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/flag/use_cc.sh000066400000000000000000000033471476774233700252730ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --force-preload --output %t.known.json -- %{shell} %s # RUN: assert_compilation %t.known.json count -eq 2 # RUN: assert_compilation %t.known.json contains -file %T/use_cc_1.c -directory %T -arguments %{c_compiler} -c -o use_cc_1.o use_cc_1.c # RUN: assert_compilation %t.known.json contains -file %T/use_cc_2.c -directory %T -arguments %{c_compiler} -c -o use_cc_2.o use_cc_2.c # RUN: cd %T; env CC=%{true} %{bear} --verbose --force-preload --output %t.all.json -- %{shell} %s # RUN: assert_compilation %t.all.json count -eq 2 # RUN: assert_compilation %t.all.json contains -file %T/use_cc_1.c -directory %T -arguments %{true} -c -o use_cc_1.o use_cc_1.c # RUN: assert_compilation %t.all.json contains -file %T/use_cc_2.c -directory %T -arguments %{true} -c -o use_cc_2.o use_cc_2.c # RUN: cd %T; %{bear} --verbose --force-wrapper --output %t.known.json -- %{shell} %s # RUN: assert_compilation %t.known.json count -eq 2 # RUN: assert_compilation %t.known.json contains -file %T/use_cc_1.c -directory %T -arguments %{c_compiler} -c -o use_cc_1.o use_cc_1.c # RUN: assert_compilation %t.known.json contains -file %T/use_cc_2.c -directory %T -arguments %{c_compiler} -c -o use_cc_2.o use_cc_2.c # RUN: cd %T; env CC=%{true} %{bear} --verbose --force-wrapper --output %t.all.json -- %{shell} %s # RUN: assert_compilation %t.all.json count -eq 2 # RUN: assert_compilation %t.all.json contains -file %T/use_cc_1.c -directory %T -arguments %{true} -c -o use_cc_1.o use_cc_1.c # RUN: assert_compilation %t.all.json contains -file %T/use_cc_2.c -directory %T -arguments %{true} -c -o use_cc_2.o use_cc_2.c touch use_cc_1.c use_cc_2.c $CC -c -o use_cc_1.o use_cc_1.c; $CC -c -o use_cc_2.o use_cc_2.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/flag/use_cxx.sh000066400000000000000000000034451476774233700255070ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --force-preload --output %t.known.json -- %{shell} %s # RUN: assert_compilation %t.known.json count -eq 2 # RUN: assert_compilation %t.known.json contains -file %T/use_cxx_1.cc -directory %T -arguments %{cxx_compiler} -c -o use_cxx_1.o use_cxx_1.cc # RUN: assert_compilation %t.known.json contains -file %T/use_cxx_2.cc -directory %T -arguments %{cxx_compiler} -c -o use_cxx_2.o use_cxx_2.cc # RUN: cd %T; env CXX=%{true} %{bear} --verbose --force-preload --output %t.all.json -- %{shell} %s # RUN: assert_compilation %t.all.json count -eq 2 # RUN: assert_compilation %t.all.json contains -file %T/use_cxx_1.cc -directory %T -arguments %{true} -c -o use_cxx_1.o use_cxx_1.cc # RUN: assert_compilation %t.all.json contains -file %T/use_cxx_2.cc -directory %T -arguments %{true} -c -o use_cxx_2.o use_cxx_2.cc # RUN: cd %T; %{bear} --verbose --force-wrapper --output %t.known.json -- %{shell} %s # RUN: assert_compilation %t.known.json count -eq 2 # RUN: assert_compilation %t.known.json contains -file %T/use_cxx_1.cc -directory %T -arguments %{cxx_compiler} -c -o use_cxx_1.o use_cxx_1.cc # RUN: assert_compilation %t.known.json contains -file %T/use_cxx_2.cc -directory %T -arguments %{cxx_compiler} -c -o use_cxx_2.o use_cxx_2.cc # RUN: cd %T; env CXX=%{true} %{bear} --verbose --force-wrapper --output %t.all.json -- %{shell} %s # RUN: assert_compilation %t.all.json count -eq 2 # RUN: assert_compilation %t.all.json contains -file %T/use_cxx_1.cc -directory %T -arguments %{true} -c -o use_cxx_1.o use_cxx_1.cc # RUN: assert_compilation %t.all.json contains -file %T/use_cxx_2.cc -directory %T -arguments %{true} -c -o use_cxx_2.o use_cxx_2.cc touch use_cxx_1.cc use_cxx_2.cc $CXX -c -o use_cxx_1.o use_cxx_1.cc; $CXX -c -o use_cxx_2.o use_cxx_2.cc; rizsotto-Bear-14c2e01/test/cases/compilation/output/flags_filtered_link.sh000066400000000000000000000032221476774233700271000ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 4 # RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_1.c -directory %T -arguments %{c_compiler} -c -fpic -o flags_filtered_link_1.o flags_filtered_link_1.c # RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_2.c -directory %T -arguments %{c_compiler} -c -fpic -o flags_filtered_link_2.o flags_filtered_link_2.c # RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_3.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_link_3 flags_filtered_link_3.c # RUN: assert_compilation %t.json contains -file %T/flags_filtered_link_4.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_link_4 flags_filtered_link_4.c # set up platform specific linker options PREFIX="foobar"; if [ $(uname | grep -i "darwin") ]; then LD_FLAGS="-o lib${PREFIX}.dylib -dynamiclib -install_name @rpath/${PREFIX}" else LD_FLAGS="-o lib${PREFIX}.so -shared -Wl,-soname,${PREFIX}" fi # create the source files echo "int foo() { return 2; }" > flags_filtered_link_1.c; echo "int bar() { return 2; }" > flags_filtered_link_2.c; echo "int main() { return 0; }" > flags_filtered_link_3.c; echo "int main() { return 0; }" > flags_filtered_link_4.c; $CC -c -o flags_filtered_link_1.o -fpic flags_filtered_link_1.c; $CC -c -o flags_filtered_link_2.o -fpic flags_filtered_link_2.c; $CC ${LD_FLAGS} flags_filtered_link_1.o flags_filtered_link_2.o; $CC -o flags_filtered_link_3 -l${PREFIX} -L. flags_filtered_link_3.c; $CC -o flags_filtered_link_4 -l ${PREFIX} -L . flags_filtered_link_4.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/flags_filtered_preproc.sh000066400000000000000000000017141476774233700276210ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/flags_filtered_preproc_1.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_preproc_1.o flags_filtered_preproc_1.c # RUN: assert_compilation %t.json contains -file %T/flags_filtered_preproc_2.c -directory %T -arguments %{c_compiler} -c -o flags_filtered_preproc_2.o flags_filtered_preproc_2.c touch flags_filtered_preproc_1.c flags_filtered_preproc_2.c # these shall not be in the output $CC -c -M -o flags_filtered_preproc_1.d flags_filtered_preproc_1.c; $CC -c -MM -o flags_filtered_preproc_2.d flags_filtered_preproc_2.c; # these shall be in the output $CC -c -MD -MF flags_filtered_preproc_1.d -o flags_filtered_preproc_1.o flags_filtered_preproc_1.c; $CC -c -MMD -MF flags_filtered_preproc_2.d -o flags_filtered_preproc_2.o flags_filtered_preproc_2.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/multiple_source_build.sh000066400000000000000000000016631476774233700275120ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 3 # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_1.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_1.c # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_2.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_2.c # RUN: assert_compilation %t.json contains -file %T/multiple_source_build_3.c -directory %T -arguments %{c_compiler} -c -o multiple_source_build multiple_source_build_3.c echo "int foo() { return 1; }" > multiple_source_build_1.c echo "int bar() { return 1; }" > multiple_source_build_2.c echo "int main() { return 0; }" > multiple_source_build_3.c $CC -o multiple_source_build multiple_source_build_1.c multiple_source_build_2.c multiple_source_build_3.c rizsotto-Bear-14c2e01/test/cases/compilation/output/parallel_build.sh000066400000000000000000000020441476774233700260650ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 4 # RUN: assert_compilation %t.json contains -file %T/parallel_build_1.c -directory %T -arguments %{c_compiler} -c -o parallel_build_1.o parallel_build_1.c # RUN: assert_compilation %t.json contains -file %T/parallel_build_2.c -directory %T -arguments %{c_compiler} -c -o parallel_build_2.o parallel_build_2.c # RUN: assert_compilation %t.json contains -file %T/parallel_build_3.c -directory %T -arguments %{c_compiler} -c -o parallel_build_3.o parallel_build_3.c # RUN: assert_compilation %t.json contains -file %T/parallel_build_4.c -directory %T -arguments %{c_compiler} -c -o parallel_build_4.o parallel_build_4.c touch parallel_build_1.c parallel_build_2.c parallel_build_3.c parallel_build_4.c $CC -c -o parallel_build_1.o parallel_build_1.c & $CC -c -o parallel_build_2.o parallel_build_2.c & $CC -c -o parallel_build_3.o parallel_build_3.c & $CC -c -o parallel_build_4.o parallel_build_4.c & wait true; rizsotto-Bear-14c2e01/test/cases/compilation/output/successful_build.sh000066400000000000000000000021111476774233700264430ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 4 # RUN: assert_compilation %t.json contains -file %T/successful_build_1.c -directory %T -arguments %{c_compiler} -c -o successful_build_1.o successful_build_1.c # RUN: assert_compilation %t.json contains -file %T/successful_build_2.c -directory %T -arguments %{c_compiler} -c -o successful_build_2.o successful_build_2.c # RUN: assert_compilation %t.json contains -file %T/successful_build_3.c -directory %T -arguments %{cxx_compiler} -c -o successful_build_3.o successful_build_3.c # RUN: assert_compilation %t.json contains -file %T/successful_build_4.c -directory %T -arguments %{cxx_compiler} -c -o successful_build_4.o successful_build_4.c touch successful_build_1.c successful_build_2.c successful_build_3.c successful_build_4.c $CC -c -o successful_build_1.o successful_build_1.c; $CC -c -o successful_build_2.o successful_build_2.c; $CXX -c -o successful_build_3.o successful_build_3.c; $CXX -c -o successful_build_4.o successful_build_4.c; rizsotto-Bear-14c2e01/test/cases/compilation/output/wrapper.sh000066400000000000000000000020031476774233700245650ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: cd %T; %{bear} --verbose --output %t.json -- %{shell} %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/wrapper_1.c -directory %T -arguments %{c_compiler} -c -o wrapper_1.o wrapper_1.c # RUN: assert_compilation %t.json contains -file %T/wrapper_2.c -directory %T -arguments %{c_compiler} -c -o wrapper_2.o wrapper_2.c # RUN: cd %T; %{bear} --verbose --output %t.json --force-wrapper -- %{shell} %s # RUN: assert_compilation %t.json count -eq 2 # RUN: assert_compilation %t.json contains -file %T/wrapper_1.c -directory %T -arguments %{c_compiler} -c -o wrapper_1.o wrapper_1.c # RUN: assert_compilation %t.json contains -file %T/wrapper_2.c -directory %T -arguments %{c_compiler} -c -o wrapper_2.o wrapper_2.c touch wrapper_1.c wrapper_2.c cat > wrapper << EOF #!/usr/bin/env sh exec \$* EOF chmod +x wrapper ORIGINAL=$CC CC=./wrapper $CC $ORIGINAL -c -o wrapper_1.o wrapper_1.c; $CC $ORIGINAL -c -o wrapper_2.o wrapper_2.c; rizsotto-Bear-14c2e01/test/cases/intercept/000077500000000000000000000000001476774233700206755ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/exit_code/000077500000000000000000000000001476774233700226405ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/exit_code/exit_code_for_empty.sh000066400000000000000000000000621476774233700272210ustar00rootroot00000000000000#!/usr/bin/env sh # XFAIL: * # RUN: %{intercept} rizsotto-Bear-14c2e01/test/cases/intercept/exit_code/exit_code_for_fail.sh000066400000000000000000000001361476774233700270000ustar00rootroot00000000000000#!/usr/bin/env sh # XFAIL: * # RUN: %{intercept} --verbose --output %t.events.db -- %{false} rizsotto-Bear-14c2e01/test/cases/intercept/exit_code/exit_code_for_help.sh000066400000000000000000000000551476774233700270150ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: %{intercept} --helprizsotto-Bear-14c2e01/test/cases/intercept/exit_code/exit_code_for_success.sh000066400000000000000000000001241476774233700275320ustar00rootroot00000000000000#!/usr/bin/env sh # RUN: %{intercept} --verbose --output %t.events.json -- %{true} rizsotto-Bear-14c2e01/test/cases/intercept/output/000077500000000000000000000000001476774233700222355ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/output/shell_command_with_iso8859-2.check000066400000000000000000000003301476774233700304370ustar00rootroot00000000000000#!/usr/bin/env sh # -*- coding: iso-8859-2 -*- assert_intercepted $1 count -ge 2 assert_intercepted $1 contains -program $TRUE assert_intercepted $1 contains -program $ECHO -arguments $ECHO "árvíztûrõ tükörfúrógép" rizsotto-Bear-14c2e01/test/cases/intercept/output/shell_command_with_iso8859-2.input000066400000000000000000000001271476774233700305250ustar00rootroot00000000000000#!/usr/bin/env sh # -*- coding: iso-8859-2 -*- $ECHO "árvíztûrõ tükörfúrógép"; $TRUE; rizsotto-Bear-14c2e01/test/cases/intercept/output/shell_command_with_iso8859-2.sh000066400000000000000000000003521476774233700300000ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # TODO: %{intercept} --verbose --output %t.json -- %{shell} %S/shell_command_with_iso8859-2.input # TODO: %{shell} shell_command_with_iso8859-2.check %t.json # RUN: %{true} rizsotto-Bear-14c2e01/test/cases/intercept/output/shell_commands_with_unicode.sh000066400000000000000000000017321476774233700303250ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{intercept} --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 7 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there \"people\"" # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi again" # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "מה שלומך?" # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "Как дела?" # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "This line might cause an exception in json load" # RUN: assert_intercepted %t.json contains -arguments %{shell} %s $ECHO "hi there \"people\"" $ECHO "hi again" $ECHO "מה שלומך?" $ECHO "Как дела?" $ECHO "This line might cause an exception in json load" $TRUE rizsotto-Bear-14c2e01/test/cases/intercept/preload/000077500000000000000000000000001476774233700223235ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/build_command_captured.sh000066400000000000000000000003441476774233700273440ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload # RUN: %{intercept} --verbose --output %t.json -- %{true} %s # RUN: assert_intercepted %t.json count -eq 1 # RUN: assert_intercepted %t.json contains -program %{true} -arguments %{true} %s rizsotto-Bear-14c2e01/test/cases/intercept/preload/build_stderr_captured.sh000066400000000000000000000005451476774233700272340ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{shell} %s 2> %t.orig.stderr # RUN: %{intercept} --output %t.json -- %{shell} %s 2> %t.fwd.stderr # RUN: diff %t.orig.stderr %t.fwd.stderr >&2 $ECHO "Lorem ipsum dolor sit amet, consectetur adipiscing elit," >&2 $ECHO "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." rizsotto-Bear-14c2e01/test/cases/intercept/preload/build_stdout_captured.sh000066400000000000000000000005331476774233700272500ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{shell} %s > %t.orig.stdout # RUN: %{intercept} --output %t.json -- %{shell} %s > %t.fwd.stdout # RUN: diff %t.orig.stdout %t.fwd.stdout $ECHO "Lorem ipsum dolor sit amet, consectetur adipiscing elit," $ECHO "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." rizsotto-Bear-14c2e01/test/cases/intercept/preload/errno_reset.c000066400000000000000000000004121476774233700250130ustar00rootroot00000000000000// REQUIRES: preload // RUN: %{compile} -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t #include #include #include #include int main() { printf("error %i: %s\n", errno, strerror(errno)); return 0; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/libtool_commands_captured.sh000066400000000000000000000014221476774233700300720ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell, libtool # RUN: %{intercept} --force-preload --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 4 # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -g -O -c main.c -o main.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -g -O -c hello.c -o hello.o echo "int main() { return 0; }" > main.c echo "int hello() { return 1; }" > hello.c $LIBTOOL --mode=compile --tag=CC $CC -g -O -c main.c -o main.o; $LIBTOOL --mode=compile --tag=CC $CC -g -O -c hello.c -o hello.o; $LIBTOOL --mode=link --tag=CC $CC -g -O -o libhello.la hello.lo $LIBTOOL --mode=link --tag=CC $CC -g -O -o libtool_test main.lo libhello.la rizsotto-Bear-14c2e01/test/cases/intercept/preload/path_resolver/000077500000000000000000000000001476774233700252005ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/path_resolver/same_name_in_path.c000066400000000000000000000011701476774233700307720ustar00rootroot00000000000000// REQUIRES: preload, shell, dynamic-shell // RUN: mkdir -p %T/same_name_in_path/a %T/same_name_in_path/b // RUN: %{compile} '-D_MESSAGE="one"' -o %T/same_name_in_path/a/a.out %s // RUN: %{compile} '-D_MESSAGE="two"' -o %T/same_name_in_path/b/a.out %s // RUN: env PATH=%T/same_name_in_path/a:%T/same_name_in_path/b %{shell} -c a.out > %t.without.txt // RUN: env PATH=%T/same_name_in_path/a:%T/same_name_in_path/b %{intercept} --output %t.json -- %{shell} -c a.out > %t.with.txt // RUN: diff %t.without.txt %t.with.txt #include int main() { const char *const message = _MESSAGE; printf(message); return 0; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/path_resolver/same_name_without_exec_flag.c000066400000000000000000000012521476774233700330510ustar00rootroot00000000000000// REQUIRES: preload, shell, dynamic-shell // RUN: mkdir -p %T/same_name_without_exec_flag/a %T/same_name_without_exec_flag/b // RUN: touch %T/same_name_without_exec_flag/a/a.out // RUN: %{compile} '-D_MESSAGE="two"' -o %T/same_name_without_exec_flag/b/a.out %s // RUN: env PATH=%T/same_name_without_exec_flag/a:%T/same_name_without_exec_flag/b %{shell} -c a.out > %t.without.txt // RUN: env PATH=%T/same_name_without_exec_flag/a:%T/same_name_without_exec_flag/b %{intercept} --output %t.json -- %{shell} -c a.out > %t.with.txt // RUN: diff %t.without.txt %t.with.txt #include int main() { const char *const message = _MESSAGE; printf(message); return 0; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/000077500000000000000000000000001476774233700234655ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execl/000077500000000000000000000000001476774233700245655ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execl/failure_prints_errno.c000066400000000000000000000013601476774233700311640ustar00rootroot00000000000000// REQUIRES: preload, c_api_execl // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; if (-1 == execl(program, program, "hi there", 0)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execl/success.c000066400000000000000000000010151476774233700263760ustar00rootroot00000000000000// REQUIRES: preload, c_api_execl // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; return execl(program, program, "hi there", 0); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execle/000077500000000000000000000000001476774233700247325ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execle/failure_prints_errno.c000066400000000000000000000014231476774233700313310ustar00rootroot00000000000000// REQUIRES: preload, c_api_execle // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; if (-1 == execle(program, program, "hi there", 0, environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execle/success.c000066400000000000000000000011021476774233700265400ustar00rootroot00000000000000// REQUIRES: preload, c_api_execle // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const envp[] = { "THIS=THAT", 0 }; return execle(program, program, "hi there", 0, envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execlp/000077500000000000000000000000001476774233700247455ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execlp/failure_prints_errno.c000066400000000000000000000013621476774233700313460ustar00rootroot00000000000000// REQUIRES: preload, c_api_execlp // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; if (-1 == execlp(program, program, "hi there", 0)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execlp/success.c000066400000000000000000000010171476774233700265600ustar00rootroot00000000000000// REQUIRES: preload, c_api_execlp // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; return execlp(program, program, "hi there", 0); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execlp/success_to_resolve.c000066400000000000000000000010111476774233700310130ustar00rootroot00000000000000// REQUIRES: preload, c_api_execlp // RUN: %{compile} '-D_PROGRAM="echo"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments echo "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; return execlp(program, program, "hi there", 0); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exect/000077500000000000000000000000001476774233700245755ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exect/success.c000066400000000000000000000011441476774233700264110ustar00rootroot00000000000000// REQUIRES: preload, c_api_exect // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; return exect(program, argv, envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execv/000077500000000000000000000000001476774233700245775ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execv/failure_prints_errno.c000066400000000000000000000014251476774233700312000ustar00rootroot00000000000000// REQUIRES: preload, c_api_execv // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; if (-1 == execv(program, argv)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execv/success.c000066400000000000000000000010611476774233700264110ustar00rootroot00000000000000// REQUIRES: preload, c_api_execv // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; return execv(program, argv); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execve/000077500000000000000000000000001476774233700247445ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execve/failure_prints_errno.c000066400000000000000000000014701476774233700313450ustar00rootroot00000000000000// REQUIRES: preload, c_api_execve // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; if (-1 == execve(program, argv, environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execve/success.c000066400000000000000000000011461476774233700265620ustar00rootroot00000000000000// REQUIRES: preload, c_api_execve // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; return execve(program, argv, envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvp2/000077500000000000000000000000001476774233700250415ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvp2/failure_prints_errno.c000066400000000000000000000015501476774233700314410ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvP // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; if (-1 == execvP(program, argv, "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin", environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvp2/success.c000066400000000000000000000012201476774233700266500ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvP // RUN: %{compile} '-D_PROGRAM="echo"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments echo "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; return execvP(program, argv, "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin", envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvpe/000077500000000000000000000000001476774233700251245ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvpe/failure_prints_errno.c000066400000000000000000000014711476774233700315260ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvpe // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; if (-1 == execve(program, argv, environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvpe/success.c000066400000000000000000000011741476774233700267430ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvpe // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #define _GNU_SOURCE #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; return execvpe(program, argv, envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/execvpe/success_to_resolve.c000066400000000000000000000011461476774233700312030ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvpe // RUN: %{compile} '-D_PROGRAM="echo"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -arguments echo "hi there" #include "config.h" #define _GNU_SOURCE #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; return execvpe(program, argv, envp); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exevp/000077500000000000000000000000001476774233700246145ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exevp/failure_prints_errno.c000066400000000000000000000014271476774233700312170ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvp // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; if (-1 == execvp(program, argv)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exevp/success.c000066400000000000000000000010631476774233700264300ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvp // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; return execvp(program, argv); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/exevp/success_to_resolve.c000066400000000000000000000010551476774233700306720ustar00rootroot00000000000000// REQUIRES: preload, c_api_execvp // RUN: %{compile} '-D_PROGRAM="echo"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments echo "hi there" #include "config.h" #if defined HAVE_UNISTD_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; return execvp(program, argv); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/no_exec.c000066400000000000000000000004011476774233700252440ustar00rootroot00000000000000// REQUIRES: preload // RUN: %{compile} -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -ge 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" int main() { return 0; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/popen/000077500000000000000000000000001476774233700246065ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/popen/success.c000066400000000000000000000016231476774233700264240ustar00rootroot00000000000000// REQUIRES: preload, c_api_popen // RUN: %{compile} -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -ge 2 // RUN: assert_intercepted %t.json contains -program %t // RUN: assert_intercepted %t.json contains -arguments more #include #include void write_data (FILE * stream) { int i; for (i = 0; i < 100; i++) fprintf (stream, "%d\n", i); if (ferror (stream)) { fprintf (stderr, "Output to stream failed.\n"); exit (EXIT_FAILURE); } } int main (void) { FILE *output; output = popen ("more", "w"); if (!output) { fprintf (stderr, "incorrect parameters or too many files.\n"); return EXIT_FAILURE; } write_data (output); if (pclose (output) != 0) { fprintf (stderr, "Could not run more or other error.\n"); } return EXIT_SUCCESS; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawn/000077500000000000000000000000001476774233700260375ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawn/failure_prints_errno.c000066400000000000000000000015361476774233700324430ustar00rootroot00000000000000// REQUIRES: preload, c_api_posix_spawn // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_SPAWN_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; pid_t child; if (0 != posix_spawn(&child, program, 0, 0, argv, environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawn/success.c000066400000000000000000000022741476774233700276600ustar00rootroot00000000000000// REQUIRES: preload, c_api_posix_spawn // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #include #include #include #if defined HAVE_SPAWN_H #include #endif #if defined HAVE_SYS_TYPES_H #include #endif #if defined HAVE_SYS_WAIT_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; pid_t child; if (0 != posix_spawn(&child, program, 0, 0, argv, envp)) { perror("posix_spawn"); exit(EXIT_FAILURE); } int status; if (-1 == waitpid(child, &status, 0)) { perror("wait"); return EXIT_FAILURE; } if (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE) { fprintf(stderr, "child process has non zero exit code\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawnp/000077500000000000000000000000001476774233700262175ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawnp/failure_prints_errno.c000066400000000000000000000015401476774233700326160ustar00rootroot00000000000000// REQUIRES: preload, c_api_posix_spawnp // RUN: %{compile} '-D_PROGRAM="/path/to/not/existing"' -o %t %s // RUN: %t > %t.without.errno // RUN: %{intercept} --output %t.json -- %t > %t.with.errno // RUN: diff %t.with.errno %t.without.errno // RUN: assert_intercepted %t.json count -eq 1 // RUN: assert_intercepted %t.json contains -program %t #include "config.h" #include #include #include #include #if defined HAVE_SPAWN_H #include #endif extern char **environ; int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; pid_t child; if (0 != posix_spawnp(&child, program, 0, 0, argv, environ)) { const int error = errno; printf("errno: %d (%s)\n", error, strerror(error)); return EXIT_SUCCESS; } return EXIT_FAILURE; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawnp/success.c000066400000000000000000000022761476774233700300420ustar00rootroot00000000000000// REQUIRES: preload, c_api_posix_spawnp // RUN: %{compile} '-D_PROGRAM="%{echo}"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" #include "config.h" #include #include #include #if defined HAVE_SPAWN_H #include #endif #if defined HAVE_SYS_TYPES_H #include #endif #if defined HAVE_SYS_WAIT_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; pid_t child; if (0 != posix_spawnp(&child, program, 0, 0, argv, envp)) { perror("posix_spawn"); exit(EXIT_FAILURE); } int status; if (-1 == waitpid(child, &status, 0)) { perror("wait"); return EXIT_FAILURE; } if (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE) { fprintf(stderr, "child process has non zero exit code\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/posix_spawnp/success_to_resolve.c000066400000000000000000000022471476774233700323010ustar00rootroot00000000000000// REQUIRES: preload, c_api_posix_spawnp // RUN: %{compile} '-D_PROGRAM="echo"' -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -eq 2 // RUN: assert_intercepted %t.json contains -program %t -arguments %t // RUN: assert_intercepted %t.json contains -arguments echo "hi there" #include "config.h" #include #include #include #if defined HAVE_SPAWN_H #include #endif #if defined HAVE_SYS_TYPES_H #include #endif #if defined HAVE_SYS_WAIT_H #include #endif int main() { char *const program = _PROGRAM; char *const argv[] = { _PROGRAM, "hi there", 0 }; char *const envp[] = { "THIS=THAT", 0 }; pid_t child; if (0 != posix_spawnp(&child, program, 0, 0, argv, envp)) { perror("posix_spawn"); exit(EXIT_FAILURE); } int status; if (-1 == waitpid(child, &status, 0)) { perror("wait"); return EXIT_FAILURE; } if (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE) { fprintf(stderr, "child process has non zero exit code\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/system/000077500000000000000000000000001476774233700250115ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/posix/system/success.c000066400000000000000000000006221476774233700266250ustar00rootroot00000000000000// REQUIRES: preload, c_api_system // RUN: %{compile} -o %t %s // RUN: %{intercept} --verbose --output %t.json -- %t // RUN: assert_intercepted %t.json count -ge 2 // RUN: assert_intercepted %t.json contains -program %t // RUN: assert_intercepted %t.json contains -arguments ls -l #include int main () { char *const command = "ls -l"; system(command); return EXIT_SUCCESS; } rizsotto-Bear-14c2e01/test/cases/intercept/preload/preload_append/000077500000000000000000000000001476774233700253005ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/preload/preload_append/fakeroot.sh000066400000000000000000000005461476774233700274530ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, fakeroot # RUN: %{fakeroot} whoami | xargs test 'root' = # RUN: %{intercept} --output %t.json -- %{fakeroot} whoami | xargs test 'root' = # RUN: assert_intercepted %t.json count -ge 2 # RUN: assert_intercepted %t.json contains -arguments %{fakeroot} whoami # RUN: assert_intercepted %t.json contains -arguments whoami rizsotto-Bear-14c2e01/test/cases/intercept/preload/preload_append/io_calls.c000066400000000000000000000011761476774233700272360ustar00rootroot00000000000000// REQUIRES: preload, shell, dynamic-shell // RUN: %{compile} '-D_FILE="%t.state"' -fpic -shared -o %t.so %s // RUN: env LD_PRELOAD=%t.so %{intercept} --verbose --output %t.json -- %{shell} -c %{true} // RUN: assert_intercepted %t.json count -ge 2 // RUN: assert_intercepted %t.json contains -arguments %{shell} -c %{true} // RUN: assert_intercepted %t.json contains -program %{true} -arguments %{true} // RUN: test -f %t.state #include void on_load() __attribute__((constructor)); void on_load() { const char* file = _FILE; FILE* handle = fopen(file, "a"); fprintf(handle, "here we go\n"); fclose(handle); } rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_commands_intercepted.sh000066400000000000000000000005171476774233700302400ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{intercept} --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 4 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %{shell} -arguments %{shell} %s $TRUE; $TRUE; $TRUE; rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_commands_intercepted_without_shebang.sh000066400000000000000000000005751476774233700335160ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{shell} %s > %t.sh # RUN: chmod +x %t.sh # RUN: %{intercept} --verbose --output %t.json -- %t.sh # RUN: assert_intercepted %t.json count -ge 4 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %t.sh -arguments %t.sh cat << EOF $TRUE $TRUE $TRUE EOF rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_commands_parallel_intercepted.sh000066400000000000000000000005021476774233700321060ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload # RUN: %{intercept} --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 4 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %{shell} -arguments %{shell} %s $TRUE & $TRUE & $TRUE & wait rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_commands_with_empty_env.sh000066400000000000000000000005061476774233700307710ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload # RUN: %{intercept} --verbose --output %t.json -- env - %{shell} %s %{true} # RUN: assert_intercepted %t.json count -ge 5 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %{shell} -arguments %{shell} %s %{true} $1; $1; $1; rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_fail_commands_intercepted.sh000066400000000000000000000003661476774233700312350ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{intercept} --verbose --output %t.json -- %{shell} %s || true # RUN: assert_intercepted %t.json count -ge 2 # RUN: assert_intercepted %t.json contains -program %{false} $FALSE;rizsotto-Bear-14c2e01/test/cases/intercept/preload/shell_redirect_works.sh000066400000000000000000000007241476774233700270770ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{intercept} --output %t.json -- %{shell} -c "%{echo} hi | %{shell} %s" # RUN: assert_intercepted %t.json count -eq 4 # RUN: assert_intercepted %t.json contains -program %{shell} # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi" # RUN: assert_intercepted %t.json contains -program %{echo} -arguments %{echo} "hi there" while read line do $ECHO "$line there" done rizsotto-Bear-14c2e01/test/cases/intercept/preload/signal_inside_build.sh000066400000000000000000000016031476774233700266460ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{intercept} --verbose --output %t.json -- %{shell} %s --sleep %{sleep} --true %{true} # RUN: assert_intercepted %t.json count -ge 3 # RUN: assert_intercepted %t.json contains -program %{true} -arguments %{true} # RUN: assert_intercepted %t.json contains -program %{sleep} -arguments %{sleep} 1 # RUN: assert_intercepted %t.json contains -program %{sleep} -arguments %{sleep} 5 for i in "$@" do case $i in --sleep) SLEEP=$2 shift shift ;; --true) TRUE=$2 shift shift ;; *) # unknown option ;; esac done echo "SLEEP = $SLEEP" echo "TRUE = $TRUE" if [ -z "$SLEEP" ]; then echo "SLEEP is not defined"; exit 1; fi if [ -z "$TRUE" ]; then echo "TRUE is not defined"; exit 1; fi # do the test $SLEEP 5 & $SLEEP 1 kill -15 %1; wait; $TRUE rizsotto-Bear-14c2e01/test/cases/intercept/preload/signal_outside_build.sh000066400000000000000000000015741476774233700270560ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell # RUN: %{shell} -c "%{intercept} --verbose --output %t.json -- %{shell} %s --sleep %{sleep} --true %{true} & %{sleep} 1; kill -15 %1; wait;" # RUN: assert_intercepted %t.json count -eq 3 # RUN: assert_intercepted %t.json contains -program %{true} # RUN: assert_intercepted %t.json contains -program %{sleep} -arguments %{sleep} 5 for i in "$@" do case $i in --sleep) SLEEP=$2 shift shift ;; --true) TRUE=$2 shift shift ;; *) # unknown option ;; esac done echo "SLEEP = $SLEEP" echo "TRUE = $TRUE" if [ -z "$SLEEP" ]; then echo "SLEEP is not defined"; exit 1; fi if [ -z "$TRUE" ]; then echo "TRUE is not defined"; exit 1; fi forward() { kill -15 $child; } trap forward SIGTERM # do the test $TRUE $SLEEP 5& child=$! wait $child rizsotto-Bear-14c2e01/test/cases/intercept/valgrind/000077500000000000000000000000001476774233700225035ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/valgrind/shell_commands_intercepted.sh000066400000000000000000000003721476774233700304170ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: preload, shell, dynamic-shell, valgrind # RUN: %{valgrind} --tool=memcheck --trace-children=yes %{intercept} --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 4 $TRUE; $TRUE; $TRUE; rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/000077500000000000000000000000001476774233700223555ustar00rootroot00000000000000rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/build_command_captured.sh000066400000000000000000000002311476774233700273710ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{intercept} --force-wrapper --verbose --output %t.json -- env # RUN: assert_intercepted %t.json count -eq 0 rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/build_stderr_captured.sh000066400000000000000000000005351476774233700272650ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s 2> %t.orig.stderr # RUN: %{intercept} --force-wrapper --output %t.json -- %{shell} %s 2> %t.fwd.stderr # RUN: diff %t.orig.stderr %t.fwd.stderr >&2 $ECHO "Lorem ipsum dolor sit amet, consectetur adipiscing elit," >&2 $ECHO "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/build_stdout_captured.sh000066400000000000000000000005231476774233700273010ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s > %t.orig.stdout # RUN: %{intercept} --force-wrapper --output %t.json -- %{shell} %s > %t.fwd.stdout # RUN: diff %t.orig.stdout %t.fwd.stdout $ECHO "Lorem ipsum dolor sit amet, consectetur adipiscing elit," $ECHO "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/libtool_commands_captured.sh000066400000000000000000000013721476774233700301300ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell, libtool # RUN: %{intercept} --force-wrapper --verbose --output %t.json -- %{shell} %s # RUN: assert_intercepted %t.json count -ge 4 # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -g -O -c main.c -o main.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -g -O -c hello.c -o hello.o echo "int main() { return 0; }" > main.c echo "int hello() { return 1; }" > hello.c $LIBTOOL --mode=compile --tag=CC $CC -g -O -c main.c -o main.o; $LIBTOOL --mode=compile --tag=CC $CC -g -O -c hello.c -o hello.o; $LIBTOOL --mode=link --tag=CC $CC -g -O -o libhello.la hello.lo $LIBTOOL --mode=link --tag=CC $CC -g -O -o libtool_test main.lo libhello.la rizsotto-Bear-14c2e01/test/cases/intercept/wrapper/shell_commands_basename_compiler.sh000066400000000000000000000017021476774233700314260ustar00rootroot00000000000000#!/usr/bin/env sh # REQUIRES: shell # RUN: %{shell} %s > %t.sh # RUN: chmod +x %t.sh # RUN: cd %T; %{intercept} --force-wrapper --verbose --output %t.json -- %t.sh # RUN: assert_intercepted %t.json count -ge 3 # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.1.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.2.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.3.o cat < %t.sh # RUN: chmod +x %t.sh # RUN: cd %T; %{intercept} --force-wrapper --verbose --output %t.json -- %t.sh # RUN: assert_intercepted %t.json count -ge 3 # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.1.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.2.o # RUN: assert_intercepted %t.json contains -program %{c_compiler} -arguments %{c_compiler} -c shell_commands_intercepted.c -o shell_commands_intercepted.3.o cat < %t.wrapper # RUN: chmod +x %t.wrapper # RUN: cd %T; env CC=%t.wrapper %{intercept} --force-wrapper --verbose --output %t.json -- %{shell} %s || true # RUN: assert_intercepted %t.json count -ge 1 # RUN: assert_intercepted %t.json contains -program %t.wrapper -arguments %t.wrapper -c use_env.c -o use_env.o touch use_env.c $CC -c use_env.c -o use_env.o; rizsotto-Bear-14c2e01/test/config.h.in000066400000000000000000000024721476774233700176320ustar00rootroot00000000000000/* Copyright (C) 2012-2024 by László Nagy This file is part of Bear. Bear is a tool to generate compilation database for clang tooling. Bear is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Bear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once // header checks #cmakedefine HAVE_SPAWN_H #cmakedefine HAVE_UNISTD_H #cmakedefine HAVE_SYS_WAIT_H #cmakedefine HAVE_SYS_TYPES_H // symbol checks #cmakedefine HAVE_EXECVE #cmakedefine HAVE_EXECV #cmakedefine HAVE_EXECVPE #cmakedefine HAVE_EXECVP #cmakedefine HAVE_EXECVP2 #cmakedefine HAVE_EXECT #cmakedefine HAVE_EXECL #cmakedefine HAVE_EXECLP #cmakedefine HAVE_EXECLE #cmakedefine HAVE_EXECVEAT #cmakedefine HAVE_FEXECVE #cmakedefine HAVE_POSIX_SPAWN #cmakedefine HAVE_POSIX_SPAWNP #cmakedefine HAVE_POPEN #cmakedefine HAVE_SYSTEM rizsotto-Bear-14c2e01/test/lit.cfg000066400000000000000000000136231476774233700170600ustar00rootroot00000000000000# -*- coding: utf-8 -*- import sys import re import os.path import subprocess import lit.util this_dir = os.path.dirname(__file__) project_dir = os.path.dirname(this_dir) # Input parameters for this test module are: # # _BEAR_BIN_DIR : the directory where the `bear` and `intercept` installed. # (this is necessary only if these files are not in the `PATH`.) # _TEST_EXEC_ROOT : the test execution root directory (optional) # config.name = 'bear' config.test_format = lit.formats.ShTest() config.test_source_root = this_dir if '_TEST_EXEC_ROOT' in lit_config.params: config.test_exec_root = lit_config.params['_TEST_EXEC_ROOT'] else: import tempfile config.test_exec_root = tempfile.mkdtemp(prefix='bear-') config.suffixes = ['.sh', '.mk', '.c'] config.excludes = [] config.environment['LC_CTYPE'] = 'en_US.UTF-8' config.environment['PATH'] = ":".join([os.path.join(this_dir, 'bin'), os.environ.get('PATH')]) # add bear install directory in case if it's not in the path if '_BEAR_BIN_DIR' in lit_config.params: bin_dir = lit_config.params['_BEAR_BIN_DIR'] config.environment['PATH'] = ":".join([bin_dir, config.environment['PATH']]) def static_linked(program): try: subprocess.call(['ldd', program], stdout=subprocess.PIPE) except: return True else: return False def which(program): path = config.environment['PATH'] result = lit.util.which(program, path) if result is not None: return os.path.abspath(result) else: return None # set up bear parameters bin_bear = lit_config.params['_BIN_BEAR'] bin_wrapper = lit_config.params['_BIN_WRAPPER'] bin_wrapper_dir = lit_config.params['_BIN_WRAPPER_DIR'] lib_exec = lit_config.params['_LIB_EXEC'] bear_cmd = '{} --bear-path {} --library {} --wrapper {} --wrapper-dir {}'.format( bin_bear, bin_bear, lib_exec, bin_wrapper, bin_wrapper_dir ) config.substitutions.append(('%{bear}', bear_cmd)) intercept_cmd = '{} intercept --wrapper {} --library {} --wrapper-dir {}'.format( bin_bear, bin_wrapper, lib_exec, bin_wrapper_dir ) config.substitutions.append(('%{intercept}', intercept_cmd)) citnames_cmd = '{} citnames'.format( bin_bear ) config.substitutions.append(('%{citnames}', citnames_cmd)) # check if a shell is available shell_path = None if which('sh'): shell_path = which('sh') elif which('zsh'): shell_path = which('zsh') elif which('bash'): shell_path = which('bash') if shell_path: config.substitutions.append(('%{shell}', shell_path)) config.available_features.add('shell') if not static_linked(shell_path): config.available_features.add('dynamic-shell') # check if a make is available make_path = None if which('make'): make_path = which('make') elif which('mingw32-make'): make_path = which('mingw32-make') if make_path: config.substitutions.append(('%{make}', make_path)) config.available_features.add('make') if not static_linked(make_path): config.available_features.add('dynamic-make') # check if util commands are available if which('true'): path = which('true') config.substitutions.append(('%{true}', path)) config.environment['TRUE'] = path if which('false'): path = which('false') config.substitutions.append(('%{false}', path)) config.environment['FALSE'] = path if which('echo'): path = which('echo') config.substitutions.append(('%{echo}', path)) config.environment['ECHO'] = path if which('sleep'): path = which('sleep') config.substitutions.append(('%{sleep}', path)) config.environment['SLEEP'] = path # check if C compiler is available if which('cc'): path = which('cc') config.substitutions.append(('%{c_compiler}', path)) config.environment['CC'] = path # check if C++ compiler is available if which('c++'): path = which('c++') config.substitutions.append(('%{cxx_compiler}', path)) config.environment['CXX'] = path # check if fortran compiler is available if which('gfortran'): path = which('gfortran') config.substitutions.append(('%{fortran}', path)) config.available_features.add('fortran') # check if cuda compiler is available if which('nvcc'): path = which('nvcc') config.substitutions.append(('%{cuda}', path)) config.available_features.add('cuda') # check if libtool command is available if which('libtool') and sys.platform == 'linux': path = which('libtool') config.environment['LIBTOOL'] = path config.substitutions.append(('%{libtool}', path)) config.available_features.add('libtool') # check if fakeroot is available if which('fakeroot'): path = which('fakeroot') config.substitutions.append(('%{fakeroot}', path)) config.available_features.add('fakeroot') # check if valgrind is available if which('valgrind'): path = which('valgrind') config.substitutions.append(('%{valgrind}', path)) config.available_features.add('valgrind') # classify os script language is_windows = sys.platform in {'win32', 'cygwin'} if is_windows: config.available_features.add('batch') config.suffixes.append('.bat') config.environment['windows'] = 'True' # check for library preload is available def is_preload_disabled(): # type: () -> bool """ Library-based interposition will fail silently if SIP is enabled, so this should be detected. You can detect whether SIP is enabled on Darwin by checking whether (1) there is a binary called 'csrutil' in the path and, if so, (2) whether the output of executing 'csrutil status' contains 'System Integrity Protection status: enabled'. :return: True if library preload will fail by the dynamic linker. """ if is_windows: return True elif sys.platform == 'darwin': return True else: return False if not is_preload_disabled(): config.available_features.add('preload') print(config.substitutions) print(config.environment) print(config.available_features) rizsotto-Bear-14c2e01/test/lit.site.cfg.in000066400000000000000000000031331476774233700204230ustar00rootroot00000000000000config.test_src_root = '@CMAKE_SOURCE_DIR@' config.test_bin_root = '@CMAKE_BINARY_DIR@' config.substitutions.append(('%{compile}', 'cc -I @CMAKE_CURRENT_BINARY_DIR@')) lit_config.load_config(config, '@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg') if 'PREFIX_' != 'PREFIX_@HAVE_EXECVE@': config.available_features.add('c_api_execve') if 'PREFIX_' != 'PREFIX_@HAVE_EXECV@': config.available_features.add('c_api_execv') if 'PREFIX_' != 'PREFIX_@HAVE_EXECVPE@': config.available_features.add('c_api_execvpe') if 'PREFIX_' != 'PREFIX_@HAVE_EXECVP@': config.available_features.add('c_api_execvp') if 'PREFIX_' != 'PREFIX_@HAVE_EXECVP2@': config.available_features.add('c_api_execvP') if 'PREFIX_' != 'PREFIX_@HAVE_EXECT@': config.available_features.add('c_api_exect') if 'PREFIX_' != 'PREFIX_@HAVE_EXECL@': config.available_features.add('c_api_execl') if 'PREFIX_' != 'PREFIX_@HAVE_EXECLP@': config.available_features.add('c_api_execlp') if 'PREFIX_' != 'PREFIX_@HAVE_EXECLE@': config.available_features.add('c_api_execle') if 'PREFIX_' != 'PREFIX_@HAVE_EXECVEAT@': config.available_features.add('c_api_execveat') if 'PREFIX_' != 'PREFIX_@HAVE_FEXECVE@': config.available_features.add('c_api_fexecve') if 'PREFIX_' != 'PREFIX_@HAVE_POSIX_SPAWN@': config.available_features.add('c_api_posix_spawn') if 'PREFIX_' != 'PREFIX_@HAVE_POSIX_SPAWNP@': config.available_features.add('c_api_posix_spawnp') if 'PREFIX_' != 'PREFIX_@HAVE_POPEN@': config.available_features.add('c_api_popen') if 'PREFIX_' != 'PREFIX_@HAVE_SYSTEM@': config.available_features.add('c_api_system') rizsotto-Bear-14c2e01/test/requirements.txt000066400000000000000000000000201476774233700210560ustar00rootroot00000000000000lit pycodestyle rizsotto-Bear-14c2e01/third_party/000077500000000000000000000000001476774233700171545ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/CMakeLists.txt000066400000000000000000000003351476774233700217150ustar00rootroot00000000000000add_subdirectory(nlohmann_json) add_subdirectory(fmt) add_subdirectory(spdlog) add_subdirectory(grpc) if (ENABLE_UNIT_TESTS) add_subdirectory(googletest) else () add_custom_target(googletest_dependency) endif () rizsotto-Bear-14c2e01/third_party/fmt/000077500000000000000000000000001476774233700177425ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/fmt/CMakeLists.txt000066400000000000000000000044221476774233700225040ustar00rootroot00000000000000 message(STATUS "Looking for fmt dependency") find_package(fmt 6.1 QUIET CONFIG) if (fmt_FOUND) message(STATUS "Looking for fmt dependency -- found") add_custom_target(fmt_dependency) else () message(STATUS "Looking for fmt dependency -- not found") include(ExternalProject) ExternalProject_Add(fmt_dependency URL https://github.com/fmtlib/fmt/archive/11.0.2.tar.gz URL_HASH MD5=3fe10c5184c8ecd0d2f9536c1b1ae95c DOWNLOAD_NO_PROGRESS 1 UPDATE_COMMAND "" LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 CMAKE_ARGS -DFMT_INSTALL:BOOL=ON -DFMT_TEST:BOOL=OFF -DFMT_FUZZ:BOOL=OFF -DFMT_DOC:BOOL=OFF -DCMAKE_INSTALL_PREFIX:PATH=${DEPENDENCIES_INSTALL_PREFIX}/fmt_dependency CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} ) include(GNUInstallDirs) list(APPEND PREFIX_PATH "${DEPENDENCIES_INSTALL_PREFIX}/fmt_dependency") set(CMAKE_PREFIX_PATH ${PREFIX_PATH};${CMAKE_PREFIX_PATH} CACHE PATH "append fmt library into the search path" FORCE) endif () rizsotto-Bear-14c2e01/third_party/googletest/000077500000000000000000000000001476774233700213305ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/googletest/CMakeLists.txt000066400000000000000000000044671476774233700241030ustar00rootroot00000000000000message(STATUS "Looking for GTest dependency") find_package(PkgConfig REQUIRED) pkg_check_modules(GTest IMPORTED_TARGET gtest>=1.10 gtest_main>=1.10 gmock>=1.10) if (GTest_FOUND) message(STATUS "Looking for GTest dependency -- found") add_custom_target(googletest_dependency) else () message(STATUS "Looking for GTest dependency -- not found") include(ExternalProject) ExternalProject_Add(googletest_dependency URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz URL_HASH MD5=c8340a482851ef6a3fe618a082304cfc DOWNLOAD_NO_PROGRESS 1 UPDATE_COMMAND "" LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 CMAKE_ARGS -DINSTALL_GTEST:BOOL=ON -DCMAKE_INSTALL_PREFIX:PATH=${DEPENDENCIES_INSTALL_PREFIX}/googletest_dependency CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} ) include(GNUInstallDirs) list(APPEND PREFIX_PATH "${DEPENDENCIES_INSTALL_PREFIX}/googletest_dependency") set(CMAKE_PREFIX_PATH ${PREFIX_PATH};${CMAKE_PREFIX_PATH} CACHE PATH "append googletest library into the search path" FORCE) endif () rizsotto-Bear-14c2e01/third_party/grpc/000077500000000000000000000000001476774233700201075ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/grpc/CMakeLists.txt000066400000000000000000000055211476774233700226520ustar00rootroot00000000000000message(STATUS "Looking for gRPC::grpc++ dependency") find_package(PkgConfig REQUIRED) pkg_check_modules(gRPC protobuf>=3.11 grpc++>=1.26) if (gRPC_FOUND) message(STATUS "Looking for gRPC::grpc++ dependency -- found") message(STATUS "Looking for protoc") find_program(PROTOC protoc) if (PROTOC) message(STATUS "Looking for protoc -- found") else() message(FATAL_ERROR "Looking for protoc -- not found") endif() message(STATUS "Looking for grpc_cpp_plugin") find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin) if (GRPC_CPP_PLUGIN) message(STATUS "Looking for grpc_cpp_plugin -- found") else() message(FATAL_ERROR "Looking for grpc_cpp_plugin -- not found") endif() add_custom_target(grpc_dependency) else () message(STATUS "Looking for gRPC::grpc++ dependency -- not found") include(ExternalProject) ExternalProject_Add(grpc_dependency GIT_REPOSITORY https://github.com/grpc/grpc GIT_TAG v1.49.2 GIT_SUBMODULES GIT_SHALLOW 1 UPDATE_COMMAND "" LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 CMAKE_ARGS -DgRPC_INSTALL:BOOL=ON -DgRPC_BUILD_TESTS:BOOL=OFF -DgRPC_BUILD_CSHARP_EXT:BOOL=OFF -DCMAKE_INSTALL_PREFIX:PATH=${DEPENDENCIES_INSTALL_PREFIX}/grpc_dependency CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} ) include(GNUInstallDirs) list(APPEND PREFIX_PATH "${DEPENDENCIES_INSTALL_PREFIX}/grpc_dependency") set(CMAKE_PREFIX_PATH ${PREFIX_PATH};${CMAKE_PREFIX_PATH} CACHE PATH "append gRPC libraries and binaries into the search path" FORCE) endif () rizsotto-Bear-14c2e01/third_party/nlohmann_json/000077500000000000000000000000001476774233700220175ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/nlohmann_json/CMakeLists.txt000066400000000000000000000044511476774233700245630ustar00rootroot00000000000000message(STATUS "Looking for nlohman_json dependency") find_package(nlohmann_json 3.7.3 QUIET) if (nlohmann_json_FOUND) message(STATUS "Looking for nlohman_json dependency -- found") add_custom_target(nlohmann_json_dependency) else () message(STATUS "Looking for nlohman_json dependency -- not found") include(ExternalProject) ExternalProject_Add(nlohmann_json_dependency URL https://github.com/nlohmann/json/archive/v3.11.3.tar.gz URL_HASH MD5=d603041cbc6051edbaa02ebb82cf0aa9 DOWNLOAD_NO_PROGRESS 1 UPDATE_COMMAND "" LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 CMAKE_ARGS -DJSON_Install:BOOL=ON -DJSON_BuildTests:BOOL=OFF -DCMAKE_INSTALL_PREFIX:PATH=${DEPENDENCIES_INSTALL_PREFIX}/nlohmann_json_dependency CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} ) include(GNUInstallDirs) list(APPEND PREFIX_PATH "${DEPENDENCIES_INSTALL_PREFIX}/nlohmann_json_dependency") set(CMAKE_PREFIX_PATH ${PREFIX_PATH};${CMAKE_PREFIX_PATH} CACHE PATH "append JSON library into the search path" FORCE) endif () rizsotto-Bear-14c2e01/third_party/spdlog/000077500000000000000000000000001476774233700204445ustar00rootroot00000000000000rizsotto-Bear-14c2e01/third_party/spdlog/CMakeLists.txt000066400000000000000000000050001476774233700231770ustar00rootroot00000000000000 message(STATUS "Looking for spdlog dependency") find_package(spdlog 1.5.0 QUIET CONFIG) if (spdlog_FOUND) message(STATUS "Looking for spdlog dependency -- found") add_custom_target(spdlog_dependency) else () message(STATUS "Looking for spdlog dependency -- not found") include(ExternalProject) ExternalProject_Add(spdlog_dependency URL https://github.com/gabime/spdlog/archive/v1.14.1.tar.gz URL_HASH MD5=f2c3f15c20e67b261836ff7bfda302cf DOWNLOAD_NO_PROGRESS 1 UPDATE_COMMAND "" LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 DEPENDS fmt_dependency CMAKE_CACHE_ARGS -DSPDLOG_FMT_EXTERNAL:BOOL=ON -DSPDLOG_INSTALL:BOOL=ON -DSPDLOG_NO_EXCEPTIONS:BOOL=ON -DSPDLOG_BUILD_TESTS:BOOL=OFF -DSPDLOG_BUILD_EXAMPLE:BOOL=OFF -DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX:PATH=${DEPENDENCIES_INSTALL_PREFIX}/spdlog_dependency CMAKE_CACHE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_FIND_ROOT_PATH:PATH=${CMAKE_FIND_ROOT_PATH} -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} -DCMAKE_C_COMPILER_TARGET:STRING=${CMAKE_C_COMPILER_TARGET} -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_COMPILER_TARGET:STRING=${CMAKE_CXX_COMPILER_TARGET} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} -DCMAKE_EXE_LINKER_FLAGS:STRING=${CMAKE_EXE_LINKER_FLAGS} -DCMAKE_SHARED_LINKER_FLAGS:STRING=${CMAKE_SHARED_LINKER_FLAGS} -DCMAKE_MODULE_LINKER_FLAGS:STRING=${CMAKE_MODULE_LINKER_FLAGS} ) include(GNUInstallDirs) list(APPEND PREFIX_PATH "${DEPENDENCIES_INSTALL_PREFIX}/spdlog_dependency") set(CMAKE_PREFIX_PATH ${PREFIX_PATH};${CMAKE_PREFIX_PATH} CACHE PATH "append spdlog library into the search path" FORCE) endif ()