├── .dockerignore ├── example_review.png ├── post ├── README.md ├── Dockerfile ├── action.yml └── clang_tidy_review │ ├── pyproject.toml │ └── clang_tidy_review │ ├── post.py │ ├── review.py │ └── __init__.py ├── tests ├── src │ ├── hello_original.cxx │ ├── hello.cxx │ ├── test_clang_tidy_review.yaml │ └── clang-tidy-profile │ │ └── 20240521223715110927964-hello.cxx.json └── test_review.py ├── upload └── action.yml ├── Dockerfile ├── .github └── workflows │ ├── test.yml │ └── black.yml ├── LICENSE ├── .gitignore ├── action.yml ├── CHANGELOG.md └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | **/venv 2 | **/__pycache__ 3 | -------------------------------------------------------------------------------- /example_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZedThree/clang-tidy-review/HEAD/example_review.png -------------------------------------------------------------------------------- /post/README.md: -------------------------------------------------------------------------------- 1 | # Clang-Tidy Review - Post 2 | 3 | This is a child-action that only posts the review from the [parent action](../README.md). 4 | -------------------------------------------------------------------------------- /post/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | COPY clang_tidy_review /clang_tidy_review 4 | 5 | RUN pip3 install --upgrade pip && \ 6 | pip3 install /clang_tidy_review 7 | 8 | ENTRYPOINT ["post"] 9 | -------------------------------------------------------------------------------- /tests/src/hello_original.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | std::string hello(std::string name) { 6 | using namespace std::string_literals; 7 | return "Hello "s + name + "!\n"s; 8 | } 9 | 10 | int main() { 11 | std::cout << hello("World"); 12 | } 13 | -------------------------------------------------------------------------------- /upload/action.yml: -------------------------------------------------------------------------------- 1 | name: 'clang-tidy review - upload artefacts' 2 | author: 'Peter Hill' 3 | description: 'Upload artefacts created from a clang-tidy-review run' 4 | branding: 5 | icon: 'book-open' 6 | color: 'red' 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - uses: actions/upload-artifact@v4 11 | with: 12 | name: clang-tidy-review 13 | path: | 14 | clang-tidy-review-output.json 15 | clang-tidy-review-metadata.json 16 | clang_fixes.json 17 | -------------------------------------------------------------------------------- /tests/src/hello.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | const std::string selective_hello(std::string name) { 6 | if (name.compare("Peter")) { 7 | return "Sorry, I thought you were someone else\n"; 8 | } else { 9 | return "I'm so happy to see you!\n"; 10 | } 11 | } 12 | 13 | const std::string hello() { 14 | return "Hello!\n"; 15 | } 16 | 17 | std::string hello(std::string name) { 18 | using namespace std::string_literals; 19 | return "Hello "s + name + "!\n"s; 20 | } 21 | 22 | int main() { 23 | std::cout << hello("World"); 24 | } 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:25.10 2 | 3 | RUN apt update && \ 4 | DEBIAN_FRONTEND=noninteractive \ 5 | apt-get install -y --no-install-recommends\ 6 | build-essential cmake git \ 7 | tzdata \ 8 | clang-tidy-14 \ 9 | clang-tidy-17 \ 10 | clang-tidy-18 \ 11 | clang-tidy-19 \ 12 | clang-tidy-20 \ 13 | clang-tidy-21 \ 14 | python3 \ 15 | python3-pip \ 16 | && rm -rf /var/lib/apt/lists/ 17 | 18 | COPY . /clang_tidy_review/ 19 | 20 | RUN python3 -m pip install --break-system-packages /clang_tidy_review/post/clang_tidy_review 21 | 22 | ENTRYPOINT ["review"] 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.py' 7 | pull_request: 8 | paths: 9 | - '**.py' 10 | 11 | jobs: 12 | pytest: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: "3.10" 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install post/clang_tidy_review[tests] 25 | - name: Test with pytest 26 | run: "pytest -v" 27 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: black 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.py' 7 | 8 | defaults: 9 | run: 10 | shell: bash 11 | 12 | jobs: 13 | black: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Setup Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: 3.x 21 | - name: Install black 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install black 25 | - name: Version 26 | run: | 27 | python --version 28 | black --version 29 | - name: Run black 30 | run: | 31 | black . 32 | - uses: stefanzweifel/git-auto-commit-action@v4 33 | with: 34 | commit_message: "Apply black changes" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Peter Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /post/action.yml: -------------------------------------------------------------------------------- 1 | name: 'clang-tidy review - post comments' 2 | author: 'Peter Hill' 3 | description: 'Create a pull request review based on warnings produced by the parent action' 4 | branding: 5 | icon: 'book-open' 6 | color: 'red' 7 | inputs: 8 | token: 9 | description: 'Authentication token' 10 | default: ${{ github.token }} 11 | required: false 12 | repo: 13 | default: ${{ github.repository }} 14 | max_comments: 15 | description: 'Maximum number of comments to post at once' 16 | required: false 17 | default: '25' 18 | lgtm_comment_body: 19 | description: 'Message to post on PR if no issues are found. An empty string will post no LGTM comment.' 20 | required: false 21 | default: 'clang-tidy review says "All clean, LGTM! :+1:"' 22 | annotations: 23 | description: "Use annotations instead of comments. See README for limitations on annotations" 24 | required: false 25 | default: false 26 | num_comments_as_exitcode: 27 | description: "Set the exit code to be the amount of comments" 28 | required: false 29 | default: 'true' 30 | workflow_id: 31 | description: 'ID of the review workflow' 32 | default: ${{ github.event.workflow_run.id }} 33 | outputs: 34 | total_comments: 35 | description: 'Total number of warnings from clang-tidy' 36 | runs: 37 | using: 'docker' 38 | image: 'Dockerfile' 39 | args: 40 | - --token=${{ inputs.token }} 41 | - --repo=${{ inputs.repo }} 42 | - --max-comments=${{ inputs.max_comments }} 43 | - --lgtm-comment-body='${{ inputs.lgtm_comment_body }}' 44 | - --workflow_id=${{ inputs.workflow_id }} 45 | - --annotations=${{ inputs.annotations }} 46 | - --num-comments-as-exitcode=${{ inputs.num_comments_as_exitcode }} 47 | -------------------------------------------------------------------------------- /post/clang_tidy_review/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 65", 4 | "setuptools_scm[toml] >= 6.2", 5 | "wheel >= 0.29.0", 6 | ] 7 | build-backend = "setuptools.build_meta" 8 | 9 | [project] 10 | name = "clang-tidy-review" 11 | description = "Run clang-tidy as a GitHub action and automatically post warnings as comments" 12 | readme = "README.md" 13 | authors = [{name = "Peter Hill", email = "peter.hill@york.ac.uk"}] 14 | license = {text = "MIT"} 15 | dependencies = [ 16 | "PyGithub ~= 2.6", 17 | "unidiff ~= 0.6.0", 18 | "pyyaml ~= 6.0.1", 19 | "urllib3 ~= 2.2.1", 20 | ] 21 | keywords = ["C++", "static-analysis"] 22 | dynamic = ["version"] 23 | requires-python = ">= 3.10" 24 | 25 | [project.urls] 26 | source = "https://github.com/ZedThree/clang-tidy-review" 27 | tracker = "https://github.com/ZedThree/clang-tidy-review/issues" 28 | 29 | [project.scripts] 30 | review = "clang_tidy_review.review:main" 31 | post = "clang_tidy_review.post:main" 32 | 33 | [project.optional-dependencies] 34 | tests = [ 35 | "pytest >= 3.3.0", 36 | ] 37 | lint = [ 38 | "black", 39 | "ruff", 40 | ] 41 | 42 | [tool.setuptools] 43 | packages = ["clang_tidy_review"] 44 | 45 | [tool.setuptools_scm] 46 | root = "../.." 47 | fallback_version = "0.0.0-dev" 48 | 49 | [tool.black] 50 | extend_exclude = "_version.py" 51 | 52 | [tool.ruff.lint] 53 | extend-select = [ 54 | "B", # flake8-bugbear 55 | "I", # isort 56 | "C4", # flake8-comprehensions 57 | "ICN", # flake8-import-conventions 58 | "PT", # flake8-pytest-style 59 | "PTH", # flake8-use-pathlib 60 | "RET", # flake8-return 61 | "RUF", # Ruff-specific 62 | "SIM", # flake8-simplify 63 | "UP", # pyupgrade 64 | "YTT", # flake8-2020 65 | "EXE", # flake8-executable 66 | "FURB", # refurb 67 | ] 68 | ignore = [ 69 | "UP045", # non-pep604-annotation-optional 70 | ] 71 | -------------------------------------------------------------------------------- /post/clang_tidy_review/clang_tidy_review/post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # clang-tidy review - post comments 4 | # Copyright (c) 2022 Peter Hill 5 | # SPDX-License-Identifier: MIT 6 | # See LICENSE for more information 7 | 8 | import argparse 9 | import pathlib 10 | import pprint 11 | import sys 12 | 13 | from clang_tidy_review import ( 14 | REVIEW_FILE, 15 | PullRequest, 16 | add_auth_arguments, 17 | bool_argument, 18 | download_artifacts, 19 | get_auth_from_arguments, 20 | load_and_merge_reviews, 21 | load_metadata, 22 | post_annotations, 23 | post_review, 24 | strip_enclosing_quotes, 25 | ) 26 | 27 | 28 | def main() -> int: 29 | parser = argparse.ArgumentParser( 30 | description="Post a review based on feedback generated by the clang-tidy-review action" 31 | ) 32 | 33 | parser.add_argument("--repo", help="Repo name in form 'owner/repo'") 34 | parser.add_argument( 35 | "--max-comments", 36 | help="Maximum number of comments to post at once", 37 | type=int, 38 | default=25, 39 | ) 40 | parser.add_argument( 41 | "--lgtm-comment-body", 42 | help="Message to post on PR if no issues are found. An empty string will post no LGTM comment.", 43 | type=str, 44 | default='clang-tidy review says "All clean, LGTM! :+1:"', 45 | ) 46 | parser.add_argument( 47 | "--dry-run", help="Run and generate review, but don't post", action="store_true" 48 | ) 49 | parser.add_argument( 50 | "--workflow_id", 51 | help="ID of the workflow that generated the review", 52 | default=None, 53 | ) 54 | parser.add_argument( 55 | "--annotations", 56 | help="Use annotations instead of comments", 57 | type=bool_argument, 58 | default=False, 59 | ) 60 | parser.add_argument( 61 | "--num-comments-as-exitcode", 62 | help="Set the exit code to be the amount of comments", 63 | type=bool_argument, 64 | default=True, 65 | ) 66 | add_auth_arguments(parser) 67 | parser.add_argument( 68 | "reviews", 69 | metavar="REVIEW_FILES", 70 | type=pathlib.Path, 71 | nargs="*", 72 | default=[pathlib.Path(REVIEW_FILE)], 73 | help="Split workflow review results", 74 | ) 75 | 76 | args = parser.parse_args() 77 | 78 | pull_request = PullRequest(args.repo, None, get_auth_from_arguments(args)) 79 | 80 | # Try to read the review artifacts if they're already present 81 | metadata = load_metadata() 82 | review = load_and_merge_reviews(args.reviews) 83 | 84 | # If not, try to download them automatically 85 | if metadata is None and args.workflow_id is not None: 86 | print("Attempting to automatically download review artifacts", flush=True) 87 | metadata, review = download_artifacts(pull_request, int(args.workflow_id)) 88 | 89 | if metadata is None: 90 | raise RuntimeError("Couldn't find review metadata") 91 | 92 | pull_request.pr_number = metadata["pr_number"] 93 | 94 | print( 95 | "clang-tidy-review generated the following review", 96 | pprint.pformat(review, width=130), 97 | flush=True, 98 | ) 99 | 100 | if args.annotations: 101 | exit_code = post_annotations(pull_request, review) 102 | else: 103 | lgtm_comment_body = strip_enclosing_quotes(args.lgtm_comment_body) 104 | exit_code = post_review( 105 | pull_request, review, args.max_comments, lgtm_comment_body, args.dry_run 106 | ) 107 | 108 | return (exit_code or 0) if args.num_comments_as_exitcode else 0 109 | 110 | 111 | if __name__ == "__main__": 112 | sys.exit(main()) 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv*/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Generated by review action 163 | clang-tidy-review-output.json 164 | clang-tidy-review-metadata.json 165 | 166 | # Generated by clang-tidy 167 | clang_tidy_review.yaml 168 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'clang-tidy review' 2 | author: 'Peter Hill' 3 | description: 'Create a pull request review based on warnings from clang-tidy' 4 | branding: 5 | icon: 'book-open' 6 | color: 'red' 7 | inputs: 8 | token: 9 | description: 'Authentication token' 10 | default: ${{ github.token }} 11 | required: false 12 | build_dir: 13 | description: 'Directory containing the compile_commands.json file' 14 | default: '.' 15 | required: false 16 | base_dir: 17 | description: 'Absolute path to initial working directory. Useful if generating `compile_commands.json` outside of the Action' 18 | default: ${{ github.workspace }} 19 | require: false 20 | clang_tidy_version: 21 | description: 'Version of clang-tidy to use; one of 14, 17, 18, 19, 20, 21' 22 | default: '21' 23 | required: false 24 | clang_tidy_checks: 25 | description: 'List of checks' 26 | default: '-*,performance-*,readability-*,bugprone-*,clang-analyzer-*,cppcoreguidelines-*,mpi-*,misc-*' 27 | required: false 28 | extra_arguments: 29 | description: 'Extra arguments to pass to the clang-tidy invocation' 30 | default: '' 31 | required: false 32 | config_file: 33 | description: 'Location of .clang-tidy config file. If specified, takes preference over `clang_tidy_checks`' 34 | default: '' 35 | required: false 36 | include: 37 | description: 'Comma-separated list of files or patterns to include' 38 | default: "*.[ch],*.[ch]xx,*.[ch]pp,*.[ch]++,*.cc,*.hh" 39 | required: false 40 | exclude: 41 | description: 'Comma-separated list of files or patterns to exclude' 42 | required: false 43 | default: '' 44 | apt_packages: 45 | description: 'Comma-separated list of apt packages to install' 46 | required: false 47 | default: '' 48 | install_commands: 49 | description: 'Commands to execute after `apt_packages` and before `cmake_command`' 50 | required: false 51 | default: '' 52 | cmake_command: 53 | description: 'If set, run CMake as part of the action using this command' 54 | required: false 55 | default: '' 56 | max_comments: 57 | description: 'Maximum number of comments to post at once' 58 | required: false 59 | default: '25' 60 | lgtm_comment_body: 61 | description: 'Message to post on PR if no issues are found. An empty string will post no LGTM comment.' 62 | required: false 63 | default: 'clang-tidy review says "All clean, LGTM! :+1:"' 64 | split_workflow: 65 | description: "Only generate but don't post the review, leaving it for the second workflow. Relevant when receiving PRs from forks that don't have the required permissions to post reviews." 66 | required: false 67 | default: false 68 | annotations: 69 | description: "Use annotations instead of comments. See README for limitations on annotations" 70 | required: false 71 | default: false 72 | parallel: 73 | description: "Number of tidy instances to be run in parallel. Zero will automatically determine the right number." 74 | required: false 75 | default: "0" 76 | include_context_lines: 77 | description: "Include the 3 context lines above and below changes (default: false). This is useful when removing lines, as it allows `clang-tidy` to raise warnings on the context around deleted lines. Include the 3 context lines above and below changes. These can still be commented on in Github Reviews." 78 | required: false 79 | default: false 80 | pr: 81 | default: ${{ github.event.pull_request.number }} 82 | repo: 83 | default: ${{ github.repository }} 84 | outputs: 85 | total_comments: 86 | description: 'Total number of warnings from clang-tidy' 87 | runs: 88 | using: 'docker' 89 | image: 'Dockerfile' 90 | args: 91 | - --clang_tidy_binary=clang-tidy-${{ inputs.clang_tidy_version }} 92 | - --token=${{ inputs.token }} 93 | - --repo=${{ inputs.repo }} 94 | - --pr=${{ inputs.pr }} 95 | - --build_dir=${{ inputs.build_dir }} 96 | - --base_dir=${{ inputs.base_dir }} 97 | - --clang_tidy_checks=${{ inputs.clang_tidy_checks }} 98 | - --extra-arguments=${{ inputs.extra_arguments }} 99 | - --config_file=${{ inputs.config_file }} 100 | - --include='${{ inputs.include }}' 101 | - --exclude='${{ inputs.exclude }}' 102 | - --apt-packages=${{ inputs.apt_packages }} 103 | - --install-commands='${{ inputs.install_commands }}' 104 | - --cmake-command='${{ inputs.cmake_command }}' 105 | - --max-comments=${{ inputs.max_comments }} 106 | - --lgtm-comment-body='${{ inputs.lgtm_comment_body }}' 107 | - --split_workflow=${{ inputs.split_workflow }} 108 | - --annotations=${{ inputs.annotations }} 109 | - --parallel=${{ inputs.parallel }} 110 | - --include-context-lines=${{ inputs.include_context_lines }} 111 | -------------------------------------------------------------------------------- /tests/src/test_clang_tidy_review.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | MainSourceFile: '/clang_tidy_review/src/hello.cxx' 3 | Diagnostics: 4 | - DiagnosticName: readability-const-return-type 5 | DiagnosticMessage: 6 | Message: 'return type ''const std::string'' (aka ''const basic_string'') is ''const''-qualified at the top level, which may reduce code readability without improving const correctness' 7 | FilePath: '/clang_tidy_review/src/hello.cxx' 8 | FileOffset: 41 9 | Replacements: 10 | - FilePath: '/clang_tidy_review/src/hello.cxx' 11 | Offset: 41 12 | Length: 6 13 | ReplacementText: '' 14 | Ranges: 15 | - FilePath: '/clang_tidy_review/src/hello.cxx' 16 | FileOffset: 41 17 | Length: 5 18 | Level: Warning 19 | BuildDirectory: '/clang_tidy_review/build' 20 | - DiagnosticName: performance-unnecessary-value-param 21 | DiagnosticMessage: 22 | Message: 'the parameter ''name'' is copied for each invocation but only used as a const reference; consider making it a const reference' 23 | FilePath: '/clang_tidy_review/src/hello.cxx' 24 | FileOffset: 87 25 | Replacements: 26 | - FilePath: '/clang_tidy_review/src/hello.cxx' 27 | Offset: 75 28 | Length: 0 29 | ReplacementText: 'const ' 30 | - FilePath: '/clang_tidy_review/src/hello.cxx' 31 | Offset: 86 32 | Length: 0 33 | ReplacementText: '&' 34 | Level: Warning 35 | BuildDirectory: '/clang_tidy_review/build' 36 | - DiagnosticName: readability-implicit-bool-conversion 37 | DiagnosticMessage: 38 | Message: 'implicit conversion ''int'' -> bool' 39 | FilePath: '/clang_tidy_review/src/hello.cxx' 40 | FileOffset: 101 41 | Replacements: 42 | - FilePath: '/clang_tidy_review/src/hello.cxx' 43 | Offset: 122 44 | Length: 0 45 | ReplacementText: ' != 0' 46 | Level: Warning 47 | BuildDirectory: '/clang_tidy_review/build' 48 | - DiagnosticName: readability-string-compare 49 | DiagnosticMessage: 50 | Message: 'do not use ''compare'' to test equality of strings; use the string equality operator instead' 51 | FilePath: '/clang_tidy_review/src/hello.cxx' 52 | FileOffset: 101 53 | Replacements: [] 54 | Level: Warning 55 | BuildDirectory: '/clang_tidy_review/build' 56 | - DiagnosticName: readability-else-after-return 57 | DiagnosticMessage: 58 | Message: 'do not use ''else'' after ''return''' 59 | FilePath: '/clang_tidy_review/src/hello.cxx' 60 | FileOffset: 185 61 | Replacements: 62 | - FilePath: '/clang_tidy_review/src/hello.cxx' 63 | Offset: 185 64 | Length: 5 65 | ReplacementText: '' 66 | - FilePath: '/clang_tidy_review/src/hello.cxx' 67 | Offset: 190 68 | Length: 46 69 | ReplacementText: " return \"I'm so happy to see you!\\n\";\n " 70 | Ranges: 71 | - FilePath: '/clang_tidy_review/src/hello.cxx' 72 | FileOffset: 185 73 | Length: 4 74 | Level: Warning 75 | BuildDirectory: '/clang_tidy_review/build' 76 | - DiagnosticName: readability-const-return-type 77 | DiagnosticMessage: 78 | Message: 'return type ''const std::string'' (aka ''const basic_string'') is ''const''-qualified at the top level, which may reduce code readability without improving const correctness' 79 | FilePath: '/clang_tidy_review/src/hello.cxx' 80 | FileOffset: 240 81 | Replacements: 82 | - FilePath: '/clang_tidy_review/src/hello.cxx' 83 | Offset: 240 84 | Length: 6 85 | ReplacementText: '' 86 | Ranges: 87 | - FilePath: '/clang_tidy_review/src/hello.cxx' 88 | FileOffset: 240 89 | Length: 5 90 | Level: Warning 91 | BuildDirectory: '/clang_tidy_review/build' 92 | - DiagnosticName: performance-unnecessary-value-param 93 | DiagnosticMessage: 94 | Message: 'the parameter ''name'' is copied for each invocation but only used as a const reference; consider making it a const reference' 95 | FilePath: '/clang_tidy_review/src/hello.cxx' 96 | FileOffset: 322 97 | Replacements: 98 | - FilePath: '/clang_tidy_review/src/hello.cxx' 99 | Offset: 310 100 | Length: 0 101 | ReplacementText: 'const ' 102 | - FilePath: '/clang_tidy_review/src/hello.cxx' 103 | Offset: 321 104 | Length: 0 105 | ReplacementText: '&' 106 | Level: Warning 107 | BuildDirectory: '/clang_tidy_review/build' 108 | ... 109 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.8.2](https://github.com/ZedThree/clang-tidy-review/tree/v0.8.2) (2022-02-11) 4 | 5 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.8.1...v0.8.2) 6 | 7 | **Merged pull requests:** 8 | 9 | - Fix KeyError when checking previous comments [\#31](https://github.com/ZedThree/clang-tidy-review/pull/31) ([ZedThree](https://github.com/ZedThree)) 10 | 11 | ## [v0.8.1](https://github.com/ZedThree/clang-tidy-review/tree/v0.8.1) (2022-02-09) 12 | 13 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.8.0...v0.8.1) 14 | 15 | **Merged pull requests:** 16 | 17 | - Fix for clang-tidy not making any comments [\#28](https://github.com/ZedThree/clang-tidy-review/pull/28) ([ZedThree](https://github.com/ZedThree)) 18 | 19 | ## [v0.8.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.8.0) (2022-02-07) 20 | 21 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.7.1...v0.8.0) 22 | 23 | **Merged pull requests:** 24 | 25 | - Add `base_dir` argument to more accurately fix absolute paths [\#26](https://github.com/ZedThree/clang-tidy-review/pull/26) ([ZedThree](https://github.com/ZedThree)) 26 | - Add argument to pass in CMake command [\#25](https://github.com/ZedThree/clang-tidy-review/pull/25) ([ZedThree](https://github.com/ZedThree)) 27 | - Add option to use config file [\#24](https://github.com/ZedThree/clang-tidy-review/pull/24) ([ZedThree](https://github.com/ZedThree)) 28 | - Add clang tidy 12 [\#23](https://github.com/ZedThree/clang-tidy-review/pull/23) ([ZedThree](https://github.com/ZedThree)) 29 | - Suggest fixits [\#16](https://github.com/ZedThree/clang-tidy-review/pull/16) ([ZedThree](https://github.com/ZedThree)) 30 | 31 | ## [v0.7.1](https://github.com/ZedThree/clang-tidy-review/tree/v0.7.1) (2022-01-11) 32 | 33 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.7.0...v0.7.1) 34 | 35 | **Merged pull requests:** 36 | 37 | - \#20 - Fix path replace problem [\#21](https://github.com/ZedThree/clang-tidy-review/pull/21) ([kgfoundrydig](https://github.com/kgfoundrydig)) 38 | - Update README.md to use 0.7.0 version [\#18](https://github.com/ZedThree/clang-tidy-review/pull/18) ([asafHalely](https://github.com/asafHalely)) 39 | 40 | ## [v0.7.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.7.0) (2021-04-30) 41 | 42 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.6.1...v0.7.0) 43 | 44 | **Merged pull requests:** 45 | 46 | - Update Dockerfile, scripts to allow clang-tidy-11 from focal [\#15](https://github.com/ZedThree/clang-tidy-review/pull/15) ([ghutchis](https://github.com/ghutchis)) 47 | 48 | ## [v0.6.1](https://github.com/ZedThree/clang-tidy-review/tree/v0.6.1) (2021-04-22) 49 | 50 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.6.0...v0.6.1) 51 | 52 | **Merged pull requests:** 53 | 54 | - Add users to readme [\#12](https://github.com/ZedThree/clang-tidy-review/pull/12) ([vadi2](https://github.com/vadi2)) 55 | 56 | ## [v0.6.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.6.0) (2021-02-23) 57 | 58 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.5.1...v0.6.0) 59 | 60 | ## [v0.5.1](https://github.com/ZedThree/clang-tidy-review/tree/v0.5.1) (2021-02-23) 61 | 62 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.5.0...v0.5.1) 63 | 64 | **Merged pull requests:** 65 | 66 | - Avoid apt warning when building [\#8](https://github.com/ZedThree/clang-tidy-review/pull/8) ([vadi2](https://github.com/vadi2)) 67 | 68 | ## [v0.5.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.5.0) (2021-02-12) 69 | 70 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.4.0...v0.5.0) 71 | 72 | **Merged pull requests:** 73 | 74 | - Make it easier to pass through arguments for include/exclude [\#7](https://github.com/ZedThree/clang-tidy-review/pull/7) ([ZedThree](https://github.com/ZedThree)) 75 | 76 | ## [v0.4.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.4.0) (2020-12-03) 77 | 78 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.3.0...v0.4.0) 79 | 80 | **Merged pull requests:** 81 | 82 | - Fix issue with creating a review with too many comments [\#5](https://github.com/ZedThree/clang-tidy-review/pull/5) ([ZedThree](https://github.com/ZedThree)) 83 | 84 | ## [v0.3.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.3.0) (2020-12-01) 85 | 86 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.2.0...v0.3.0) 87 | 88 | **Merged pull requests:** 89 | 90 | - Various fixes, plus install additional apt packages [\#3](https://github.com/ZedThree/clang-tidy-review/pull/3) ([ZedThree](https://github.com/ZedThree)) 91 | - Fix typo in README [\#2](https://github.com/ZedThree/clang-tidy-review/pull/2) ([schra](https://github.com/schra)) 92 | 93 | ## [v0.2.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.2.0) (2020-05-31) 94 | 95 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/v0.1.0...v0.2.0) 96 | 97 | ## [v0.1.0](https://github.com/ZedThree/clang-tidy-review/tree/v0.1.0) (2020-05-30) 98 | 99 | [Full Changelog](https://github.com/ZedThree/clang-tidy-review/compare/2ad79d07d6e3891cc8affd18082758b3b6c6b4aa...v0.1.0) 100 | 101 | 102 | 103 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 104 | -------------------------------------------------------------------------------- /post/clang_tidy_review/clang_tidy_review/review.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # clang-tidy review 4 | # Copyright (c) 2020 Peter Hill 5 | # SPDX-License-Identifier: MIT 6 | # See LICENSE for more information 7 | 8 | import argparse 9 | import re 10 | import subprocess 11 | from pathlib import Path 12 | 13 | from clang_tidy_review import ( 14 | PullRequest, 15 | add_auth_arguments, 16 | bool_argument, 17 | create_review, 18 | fix_absolute_paths, 19 | get_auth_from_arguments, 20 | message_group, 21 | post_annotations, 22 | post_review, 23 | save_metadata, 24 | set_output, 25 | strip_enclosing_quotes, 26 | ) 27 | 28 | BAD_CHARS_APT_PACKAGES_PATTERN = "[;&|($]" 29 | 30 | 31 | def main(): 32 | parser = argparse.ArgumentParser( 33 | description="Create a review from clang-tidy warnings" 34 | ) 35 | parser.add_argument("--repo", help="Repo name in form 'owner/repo'") 36 | parser.add_argument("--pr", help="PR number", type=int) 37 | parser.add_argument( 38 | "--clang_tidy_binary", 39 | help="clang-tidy binary", 40 | default="clang-tidy-14", 41 | type=Path, 42 | ) 43 | parser.add_argument( 44 | "--build_dir", help="Directory with compile_commands.json", default="." 45 | ) 46 | parser.add_argument( 47 | "--base_dir", 48 | help="Absolute path of initial working directory if compile_commands.json generated outside of Action", 49 | default=".", 50 | ) 51 | parser.add_argument( 52 | "--clang_tidy_checks", 53 | help="checks argument", 54 | default="'-*,performance-*,readability-*,bugprone-*,clang-analyzer-*,cppcoreguidelines-*,mpi-*,misc-*'", 55 | ) 56 | parser.add_argument( 57 | "--extra-arguments", 58 | help="Extra arguments to pass to the clang-tidy invocation", 59 | default="", 60 | ) 61 | parser.add_argument( 62 | "--config_file", 63 | help="Path to .clang-tidy config file. If not empty, takes precedence over --clang_tidy_checks", 64 | default="", 65 | ) 66 | parser.add_argument( 67 | "--include", 68 | help="Comma-separated list of files or patterns to include", 69 | type=str, 70 | nargs="?", 71 | default="*.[ch],*.[ch]xx,*.[ch]pp,*.[ch]++,*.cc,*.hh", 72 | ) 73 | parser.add_argument( 74 | "--exclude", 75 | help="Comma-separated list of files or patterns to exclude", 76 | nargs="?", 77 | default="", 78 | ) 79 | parser.add_argument( 80 | "--apt-packages", 81 | help="Comma-separated list of apt packages to install", 82 | type=str, 83 | default="", 84 | ) 85 | parser.add_argument( 86 | "--install-commands", 87 | help="Comma-separated list of shell commands to execute", 88 | type=str, 89 | default="", 90 | ) 91 | parser.add_argument( 92 | "--cmake-command", 93 | help="If set, run CMake as part of the action with this command", 94 | type=str, 95 | default="", 96 | ) 97 | parser.add_argument( 98 | "--max-comments", 99 | help="Maximum number of comments to post at once", 100 | type=int, 101 | default=25, 102 | ) 103 | parser.add_argument( 104 | "--lgtm-comment-body", 105 | help="Message to post on PR if no issues are found. An empty string will post no LGTM comment.", 106 | type=str, 107 | default='clang-tidy review says "All clean, LGTM! :+1:"', 108 | ) 109 | parser.add_argument( 110 | "--split_workflow", 111 | help=( 112 | "Only generate but don't post the review, leaving it for the second workflow. " 113 | "Relevant when receiving PRs from forks that don't have the required permissions to post reviews." 114 | ), 115 | type=bool_argument, 116 | default=False, 117 | ) 118 | parser.add_argument( 119 | "--annotations", 120 | help="Use annotations instead of comments", 121 | type=bool_argument, 122 | default=False, 123 | ) 124 | parser.add_argument( 125 | "-j", 126 | "--parallel", 127 | help="Number of tidy instances to be run in parallel.", 128 | type=int, 129 | default=0, 130 | ) 131 | parser.add_argument( 132 | "--include-context-lines", 133 | help="Include the 3 context lines above and below changes. These can still be commented on in Github Reviews.", 134 | type=bool_argument, 135 | default=False, 136 | ) 137 | parser.add_argument( 138 | "--dry-run", help="Run and generate review, but don't post", action="store_true" 139 | ) 140 | add_auth_arguments(parser) 141 | 142 | args = parser.parse_args() 143 | 144 | # Remove any enclosing quotes and extra whitespace 145 | exclude = strip_enclosing_quotes(args.exclude).split(",") 146 | include = strip_enclosing_quotes(args.include).split(",") 147 | 148 | if args.apt_packages: 149 | # Try to make sure only 'apt install' is run 150 | apt_packages = re.split(BAD_CHARS_APT_PACKAGES_PATTERN, args.apt_packages)[ 151 | 0 152 | ].split(",") 153 | apt_packages = [pkg.strip() for pkg in apt_packages] 154 | with message_group(f"Installing additional packages: {apt_packages}"): 155 | subprocess.run(["apt-get", "update"], check=True) 156 | subprocess.run( 157 | ["apt-get", "install", "-y", "--no-install-recommends", *apt_packages], 158 | check=True, 159 | ) 160 | 161 | install_commands = strip_enclosing_quotes(args.install_commands) 162 | 163 | if install_commands: 164 | with message_group(f"Running additional install commands: {install_commands}"): 165 | subprocess.run(install_commands, shell=True, check=True) 166 | 167 | build_compile_commands = f"{args.build_dir}/compile_commands.json" 168 | 169 | cmake_command = strip_enclosing_quotes(args.cmake_command) 170 | 171 | # If we run CMake as part of the action, then we know the paths in 172 | # the compile_commands.json file are going to be correct 173 | if cmake_command: 174 | with message_group(f"Running cmake: {cmake_command}"): 175 | subprocess.run(cmake_command, shell=True, check=True) 176 | 177 | elif Path(build_compile_commands).exists(): 178 | fix_absolute_paths(build_compile_commands, args.base_dir) 179 | 180 | pull_request = PullRequest(args.repo, args.pr, get_auth_from_arguments(args)) 181 | 182 | review = create_review( 183 | pull_request, 184 | args.build_dir, 185 | args.clang_tidy_checks, 186 | args.clang_tidy_binary, 187 | args.config_file, 188 | args.parallel, 189 | args.include_context_lines, 190 | args.extra_arguments, 191 | include, 192 | exclude, 193 | ) 194 | 195 | with message_group("Saving metadata"): 196 | save_metadata(args.pr) 197 | 198 | if args.split_workflow: 199 | total_comments = 0 if review is None else len(review["comments"]) 200 | set_output("total_comments", str(total_comments)) 201 | print("split_workflow is enabled, not posting review") 202 | return 203 | 204 | if args.annotations: 205 | post_annotations(pull_request, review) 206 | else: 207 | lgtm_comment_body = strip_enclosing_quotes(args.lgtm_comment_body) 208 | post_review( 209 | pull_request, review, args.max_comments, lgtm_comment_body, args.dry_run 210 | ) 211 | 212 | 213 | if __name__ == "__main__": 214 | main() 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clang-Tidy Review 2 | 3 | Create a pull-request review based on the warnings from clang-tidy. 4 | 5 | Inspired by `clang-tidy-diff`, Clang-Tidy Review only runs on the 6 | changes in the pull request. This makes it nice and speedy, as well as 7 | being useful for projects that aren't completely clang-tidy clean yet. 8 | 9 | Where possible, makes the warnings into suggestions so you can apply 10 | them immediately. 11 | 12 | Returns the number of comments, so you can decide whether the warnings 13 | act as suggestions, or check failure. 14 | 15 | Doesn't spam by repeating identical warnings for the same line. 16 | 17 | Can use `compile_commands.json`, so you can optionally configure the 18 | build how you like first. 19 | 20 | ![Example review](example_review.png) 21 | 22 | Example usage: 23 | 24 | ```yaml 25 | name: clang-tidy-review 26 | 27 | # You can be more specific, but it currently only works on pull requests 28 | on: [pull_request] 29 | 30 | jobs: 31 | build: 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | # Optionally generate compile_commands.json 38 | 39 | - uses: ZedThree/clang-tidy-review@v0.22.0 40 | id: review 41 | 42 | # Uploads an artefact containing clang_fixes.json 43 | - uses: ZedThree/clang-tidy-review/upload@v0.22.0 44 | id: upload-review 45 | 46 | # If there are any comments, fail the check 47 | - if: steps.review.outputs.total_comments > 0 48 | run: exit 1 49 | ``` 50 | 51 | The `ZedThree/clang-tidy-review/upload` Action is optional (unless using the 52 | split workflow, see below), and will upload some of the output files as workflow 53 | artefacts. These are useful when there are more comments than can be posted, as 54 | well as for applying fixes locally. 55 | 56 | ## Limitations 57 | 58 | This is a Docker container-based Action because it needs to install 59 | some system packages (the different `clang-tidy` versions) as well as 60 | some Python packages. This that means that there's a two-three minutes 61 | start-up in order to build the Docker container. If you need to 62 | install some additional packages you can pass them via the 63 | `apt_packages` argument or run some install commands with `install_commands`. 64 | 65 | Except for very simple projects, a `compile_commands.json` file is necessary for 66 | clang-tidy to find headers, set preprocessor macros, and so on. You can generate 67 | one as part of this Action by setting `cmake_command` to something like `cmake 68 | . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=on`. 69 | 70 | GitHub only mounts the `GITHUB_WORKSPACE` directory (that is, the 71 | default place where it clones your repository) on the container. If 72 | you install additional libraries/packages yourself, you'll need to 73 | make sure they are in this directory, otherwise they won't be 74 | accessible from inside this container. 75 | 76 | It seems the GitHub API might only accept a limited number of comments 77 | at once, so `clang-tidy-review` will only attempt to post the first 78 | `max_comments` of them (default 25, as this has worked for me). 79 | 80 | ## Inputs 81 | 82 | - `token`: Authentication token 83 | - default: `${{ github.token }}` 84 | - `build_dir`: Directory containing the `compile_commands.json` file. This 85 | should be relative to `GITHUB_WORKSPACE` (the default place where your 86 | repository is cloned) 87 | - default: `'.'` 88 | - `base_dir`: Absolute path to initial working directory 89 | `GITHUB_WORKSPACE`. 90 | - default: `GITHUB_WORKSPACE` 91 | - `clang_tidy_version`: Version of clang-tidy to use; one of 14, 17, 18, 19, 20, 21 92 | - default: '21' 93 | - `clang_tidy_checks`: List of checks 94 | - default: `'-*,performance-*,readability-*,bugprone-*,clang-analyzer-*,cppcoreguidelines-*,mpi-*,misc-*'` 95 | - `config_file`: Path to clang-tidy config file, replaces `clang_tidy_checks` 96 | - default: '' which will use `clang_tidy_checks` if there are any, else closest `.clang-tidy` to each file 97 | - `include`: Comma-separated list of files or patterns to include 98 | - default: `"*.[ch],*.[ch]xx,*.[ch]pp,*.[ch]++,*.cc,*.hh"` 99 | - `exclude`: Comma-separated list of files or patterns to exclude 100 | - default: '' 101 | - `apt_packages`: Comma-separated list of apt packages to install 102 | - default: '' 103 | - `install_commands`: Commands to execute after `apt_packages` before `cmake_command` 104 | - default: '' 105 | - `cmake_command`: A CMake command to configure your project and generate 106 | `compile_commands.json` in `build_dir`. You _almost certainly_ want 107 | to include `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`! 108 | - default: '' 109 | - `max_comments`: Maximum number of comments to post at once 110 | - default: '25' 111 | - `lgtm_comment_body`: Message to post on PR if no issues are 112 | found. An empty string will post no LGTM comment. 113 | - default: 'clang-tidy review says "All clean, LGTM! :+1:"' 114 | - `split_workflow`: Only generate but don't post the review, leaving 115 | it for the second workflow. Relevant when receiving PRs from forks 116 | that don't have the required permissions to post reviews. 117 | - default: false 118 | - `annotations`: Use Annotations instead of comments. A maximum of 10 119 | annotations can be written fully, the rest will be summarised. This is a 120 | limitation of the GitHub API. 121 | - `num_comments_as_exitcode`: Set the exit code to be the amount of comments (enabled by default). 122 | - `extra_arguments`: Extra arguments to pass to the clang-tidy invocation. 123 | - `include_context_lines`: Include the 3 context lines above and below changes (default: false). 124 | This is useful when removing lines, as it allows `clang-tidy` to raise warnings on the context 125 | around deleted lines. 126 | 127 | ## Outputs 128 | 129 | - `total_comments`: Total number of warnings from clang-tidy 130 | 131 | ## Generating `compile_commands.json` 132 | 133 | Very simple projects can get away without a `compile_commands.json` 134 | file, but for most projects `clang-tidy` needs this file in order to 135 | find include paths and macro definitions. 136 | 137 | If you use the GitHub `ubuntu-latest` image as your normal `runs-on` 138 | container, you only install packages from the system package manager, 139 | and don't need to build or install other tools yourself, then you can 140 | generate `compile_commands.json` as part of the `clang-tidy-review` 141 | action: 142 | 143 | ```yaml 144 | name: clang-tidy-review 145 | on: [pull_request] 146 | 147 | jobs: 148 | build: 149 | runs-on: ubuntu-latest 150 | 151 | steps: 152 | - uses: actions/checkout@v4 153 | 154 | - uses: ZedThree/clang-tidy-review@v0.22.0 155 | id: review 156 | with: 157 | # List of packages to install 158 | apt_packages: liblapack-dev,devscripts,equivs 159 | install_commands: 'ln -s foo bar; mk-build-deps -i' 160 | # CMake command to run in order to generate compile_commands.json 161 | cmake_command: cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=on 162 | ``` 163 | 164 | If you don't use CMake, this may still work for you if you can use a 165 | tool like [bear](https://github.com/rizsotto/Bear) for example. 166 | 167 | You can also generate this file outside the container, e.g. by adding 168 | `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to a cmake command in an earlier 169 | action and omitting the `cmake_command` paramter. 170 | 171 | ## Use in a non-default location 172 | 173 | If you're using the `container` argument in your GitHub workflow, 174 | downloading/building other tools manually, or not using CMake, you 175 | will need to generate `compile_commands.json` before the 176 | `clang-tidy-review` action. However, the Action is run inside another 177 | container, and due to the way GitHub Actions work, `clang-tidy-review` 178 | ends up running with a different absolute path. 179 | 180 | What this means is that if `compile_commands.json` contains absolute 181 | paths, `clang-tidy-review` needs to adjust them to where it is being 182 | run instead. By default, it replaces absolute paths that start with 183 | the value of [`${GITHUB_WORKSPACE}`][env_vars] with the new working 184 | directory. 185 | 186 | If you're running in a container other than a default GitHub 187 | container, then you may need to pass the working directory to 188 | `base_dir`. Unfortunately there's not an easy way for 189 | `clang-tidy-review` to auto-detect this, so in order to pass the 190 | current directory you will need to do something like the following: 191 | 192 | ```yaml 193 | name: clang-tidy-review 194 | on: [pull_request] 195 | 196 | jobs: 197 | build: 198 | runs-on: ubuntu-latest 199 | # Using another container changes the 200 | # working directory from GITHUB_WORKSPACE 201 | container: 202 | image: my-container 203 | 204 | steps: 205 | - uses: actions/checkout@v4 206 | 207 | # Get the current working directory and set it 208 | # as an environment variable 209 | - name: Set base_dir 210 | run: echo "base_dir=$(pwd)" >> $GITHUB_ENV 211 | 212 | - uses: ZedThree/clang-tidy-review@v0.22.0 213 | id: review 214 | with: 215 | # Tell clang-tidy-review the base directory. 216 | # This will get replaced by the new working 217 | # directory inside the action 218 | base_dir: ${{ env.base_dir }} 219 | ``` 220 | 221 | ## Usage in fork environments (Split workflow) 222 | 223 | Actions from forks are limited in their permissions for your security. To 224 | support this use case, you can use the split workflow described below. 225 | 226 | Example review workflow: 227 | 228 | ```yaml 229 | name: clang-tidy-review 230 | 231 | # You can be more specific, but it currently only works on pull requests 232 | on: [pull_request] 233 | 234 | jobs: 235 | build: 236 | runs-on: ubuntu-latest 237 | 238 | steps: 239 | - uses: actions/checkout@v4 240 | 241 | # Optionally generate compile_commands.json 242 | 243 | - uses: ZedThree/clang-tidy-review@v0.22.0 244 | with: 245 | split_workflow: true 246 | 247 | - uses: ZedThree/clang-tidy-review/upload@v0.22.0 248 | ``` 249 | The `clang-tidy-review/upload` Action will automatically upload the following 250 | files as workflow artefacts: 251 | 252 | - `clang-tidy-review-output.json` 253 | - `clang-tidy-review-metadata.json` 254 | - `clang_fixes.json` 255 | 256 | Example post comments workflow: 257 | 258 | ```yaml 259 | name: Post clang-tidy review comments 260 | 261 | on: 262 | workflow_run: 263 | # The name field of the lint action 264 | workflows: ["clang-tidy-review"] 265 | types: 266 | - completed 267 | 268 | jobs: 269 | build: 270 | runs-on: ubuntu-latest 271 | 272 | steps: 273 | - uses: ZedThree/clang-tidy-review/post@v0.22.0 274 | # lgtm_comment_body, max_comments, and annotations need to be set on the posting workflow in a split setup 275 | with: 276 | # adjust options as necessary 277 | lgtm_comment_body: '' 278 | annotations: false 279 | max_comments: 10 280 | ``` 281 | 282 | This Action will try to automatically download 283 | `clang-tidy-review-{output,metadata}.json` from the workflow that triggered it. 284 | 285 | The review workflow runs with limited permissions and no access to 286 | repo/organisation secrets, while the post comments workflow has the required 287 | permissions because it's triggered by the `workflow_run` event and always uses 288 | the version of the workflow in the original repo. 289 | 290 | Read more about workflow security limitations 291 | [here](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/). 292 | 293 | Ensure that your workflow name doesn't contain any [special characters](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet) as Github [does not treat](https://github.com/orgs/community/discussions/50835#discussioncomment-5428789) `on.workflow_run.workflows` literally. 294 | 295 | ## Project layout 296 | 297 | This project is laid out as follows: 298 | 299 | ``` 300 | . 301 | ├── action.yml # The `review` Action 302 | ├── Dockerfile 303 | └── post 304 | ├── action.yml # The `post` Action 305 | ├── Dockerfile 306 | └── clang_tidy_review # Common python package 307 | └── clang_tidy_review 308 | ├── __init__.py 309 | ├── post.py # Entry point for `post` 310 | └── review.py # Entry point for `review` 311 | ``` 312 | 313 | In order to accommodate the split workflow, the `review` and `post` 314 | actions must have their own Action metadata files. GitHub requires 315 | this file to be named exactly `action.yml`, so they have to be in 316 | separate directories. The associated `Dockerfile`s must also be named 317 | exactly `Dockerfile`, so they also have to be separate directories. 318 | 319 | Lastly, we want to be able to reuse the python package between the two 320 | Actions, which means it must be in a subdirectory of _both_ 321 | `Dockerfile`s because they can't see parent directories. 322 | 323 | Which is why we've ended up with this slightly strange structure! This 324 | way, we can `COPY` the python package into both Docker images. 325 | 326 | 327 | ## Real world project samples 328 | |Project|Workflow| 329 | |----------|-------| 330 | |[BOUT++](https://github.com/boutproject/BOUT-dev) |[CMake](https://github.com/boutproject/BOUT-dev/blob/master/.github/workflows/clang-tidy-review.yml) | 331 | |[Mudlet](https://github.com/Mudlet/Mudlet) |[CMake + Qt](https://github.com/Mudlet/Mudlet/blob/development/.github/workflows/clangtidy-diff-analysis.yml) | 332 | 333 | 334 | 335 | [env_vars]: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables 336 | -------------------------------------------------------------------------------- /tests/test_review.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import clang_tidy_review as ctr 4 | 5 | import difflib 6 | import json 7 | import os 8 | import pathlib 9 | import textwrap 10 | import unidiff 11 | 12 | import pytest 13 | 14 | from pathlib import Path 15 | 16 | TEST_DIR = pathlib.Path(__file__).parent 17 | TEST_FILE = TEST_DIR / "src/hello.cxx" 18 | TEST_DIFF = [ 19 | unidiff.PatchSet( 20 | r"""diff --git a/src/hello.cxx b/src/hello.cxx 21 | index 98edef4..6651631 100644 22 | --- a/src/hello.cxx 23 | +++ b/src/hello.cxx 24 | @@ -2,6 +2,18 @@ 25 | 26 | #include 27 | 28 | +const std::string selective_hello(std::string name) { 29 | + if (name.compare("Peter")) { 30 | + return "Sorry, I thought you were someone else\n"; 31 | + } else { 32 | + return "I'm so happy to see you!\n"; 33 | + } 34 | +} 35 | + 36 | +const std::string hello() { 37 | + return "Hello!\n"; 38 | +} 39 | + 40 | std::string hello(std::string name) { 41 | using namespace std::string_literals; 42 | return "Hello "s + name + "!\n"s; 43 | """ 44 | )[0] 45 | ] 46 | TEST_OFFSET_LOOKUP = { 47 | str(TEST_FILE): [ 48 | 0, 49 | 20, 50 | 21, 51 | 40, 52 | 41, 53 | 95, 54 | 126, 55 | 181, 56 | 192, 57 | 233, 58 | 237, 59 | 239, 60 | 240, 61 | 268, 62 | 289, 63 | 291, 64 | 292, 65 | 330, 66 | 370, 67 | 406, 68 | 408, 69 | 409, 70 | 422, 71 | 453, 72 | 455, 73 | ] 74 | } 75 | TEST_DIAGNOSTIC = { 76 | "Message": ( 77 | "return type 'const std::string' (aka 'const basic_string') is 'const'-" 78 | "qualified at the top level, which may reduce code readability without improving " 79 | "const correctness" 80 | ), 81 | "FilePath": str(TEST_FILE), 82 | "FileOffset": 41, 83 | "Replacements": [ 84 | { 85 | "FilePath": str(TEST_FILE), 86 | "Offset": 41, 87 | "Length": 6, 88 | "ReplacementText": "", 89 | } 90 | ], 91 | } 92 | 93 | 94 | class MockClangTidyVersionProcess: 95 | """Mock out subprocess call to clang-tidy --version""" 96 | 97 | def __init__(self, version: int): 98 | self.stdout = f"""\ 99 | LLVM (http://llvm.org/): 100 | LLVM version {version}.1.7 101 | Optimized build. 102 | Default target: x86_64 103 | Host CPU: skylake 104 | """ 105 | 106 | 107 | def test_message_group(capsys): 108 | with ctr.message_group("some_title"): 109 | print("message body") 110 | 111 | captured = capsys.readouterr() 112 | 113 | assert captured.out == "::group::some_title\nmessage body\n::endgroup::\n" 114 | 115 | 116 | def test_set_output(tmp_path): 117 | test_output = tmp_path / "test_output.txt" 118 | os.environ["GITHUB_OUTPUT"] = str(test_output) 119 | 120 | ctr.set_output("key", "value") 121 | 122 | with open(test_output) as f: 123 | contents = f.read() 124 | 125 | assert contents.strip() == "key=value" 126 | 127 | 128 | def test_format_ordinary_line(): 129 | text = ctr.format_ordinary_line("123456", 4) 130 | 131 | assert text == textwrap.dedent( 132 | """\ 133 | ```cpp 134 | 123456 135 | ^ 136 | ``` 137 | """ 138 | ) 139 | 140 | 141 | @pytest.mark.parametrize( 142 | ("in_text", "expected_text"), 143 | [ 144 | (""" 'some text' """, "some text"), 145 | (""" "some text" """, "some text"), 146 | (""" "some 'text'" """, "some 'text'"), 147 | (""" 'some "text"' """, 'some "text"'), 148 | (""" "'some text'" """, "some text"), 149 | (""" '"some 'text'"' """, "some 'text'"), 150 | ], 151 | ) 152 | def test_strip_enclosing_quotes(in_text, expected_text): 153 | assert ctr.strip_enclosing_quotes(in_text) == expected_text 154 | 155 | 156 | def test_try_relative(): 157 | here = pathlib.Path.cwd() 158 | 159 | path = ctr.try_relative(".") 160 | assert path == here 161 | 162 | path = ctr.try_relative(here / "..") 163 | assert path == pathlib.Path("..") 164 | 165 | path = ctr.try_relative("/fake/path") 166 | assert path == pathlib.Path("/fake/path") 167 | 168 | 169 | def test_fix_absolute_paths(tmp_path): 170 | compile_commands = """ 171 | [ 172 | { 173 | "directory": "/fake/path/to/project/build", 174 | "command": "/usr/bin/c++ -o CMakeFiles/hello.dir/src/hello.cxx.o -c /fake/path/to/project/src/hello.cxx", 175 | "file": "/fake/path/to/project/src/hello.cxx" 176 | } 177 | ] 178 | """ 179 | 180 | compile_commands_path = tmp_path / "compile_commands.json" 181 | with open(compile_commands_path, "w") as f: 182 | f.write(compile_commands) 183 | 184 | ctr.fix_absolute_paths(compile_commands_path, "/fake/path/to/project") 185 | 186 | with open(compile_commands_path, "r") as f: 187 | contents = json.load(f)[0] 188 | 189 | here = pathlib.Path.cwd() 190 | assert contents["directory"] == str(here / "build") 191 | assert contents["command"].split()[-1] == str(here / "src/hello.cxx") 192 | assert contents["file"] == str(here / "src/hello.cxx") 193 | 194 | 195 | def test_save_load_metadata(tmp_path, monkeypatch): 196 | monkeypatch.setattr(ctr, "METADATA_FILE", tmp_path / ctr.METADATA_FILE) 197 | 198 | ctr.save_metadata(42) 199 | meta = ctr.load_metadata() 200 | 201 | assert meta["pr_number"] == 42 202 | 203 | 204 | def make_diff(): 205 | with open(TEST_DIR / "src/hello_original.cxx") as f: 206 | old = f.read() 207 | 208 | with open(TEST_FILE) as f: 209 | new = f.read() 210 | 211 | diff = "\n".join( 212 | difflib.unified_diff( 213 | old.splitlines(), 214 | new.splitlines(), 215 | fromfile="a/src/hello.cxx", 216 | tofile="b/src/hello.cxx", 217 | lineterm="", 218 | ) 219 | ) 220 | 221 | diff_cxx = f"diff --git a/src/hello.cxx b/src/hello.cxx\nindex 98edef4..6651631 100644\n{diff}" 222 | diff_cpp = diff_cxx.replace("cxx", "cpp") 223 | diff_goodbye = diff_cxx.replace("hello", "goodbye") 224 | 225 | diff_list = [unidiff.PatchSet(f)[0] for f in [diff_cxx, diff_cpp, diff_goodbye]] 226 | 227 | return diff_list 228 | 229 | 230 | def test_filter_files(): 231 | filtered = ctr.filter_files(make_diff(), ["*.cxx"], ["*goodbye.*"]) 232 | assert filtered == ["src/hello.cxx"] 233 | 234 | 235 | def test_line_ranges(): 236 | line_ranges = ctr.get_line_ranges(TEST_DIFF, ["src/hello.cxx"], False) 237 | 238 | expected_line_ranges = '[{"name":"src/hello.cxx","lines":[[5,16]]}]' 239 | assert line_ranges == expected_line_ranges 240 | 241 | 242 | def test_line_ranges_with_context_lines(): 243 | line_ranges = ctr.get_line_ranges(TEST_DIFF, ["src/hello.cxx"], True) 244 | 245 | expected_line_ranges = '[{"name":"src/hello.cxx","lines":[[2,19]]}]' 246 | assert line_ranges == expected_line_ranges 247 | 248 | 249 | def test_line_ranges_with_removals(): 250 | """Test that removed lines are handled (feature disabled when constant is 0).""" 251 | removal_diff_text = r"""diff --git a/test.cxx b/test.cxx 252 | index 1234567..abcdefg 100644 253 | --- a/test.cxx 254 | +++ b/test.cxx 255 | @@ -1,8 +1,5 @@ 256 | MyClass::~MyClass() 257 | { 258 | - delete m_Member1; 259 | - delete m_Member2; 260 | - delete m_Member3; 261 | } 262 | 263 | MyClass::MyClass() { 264 | """ 265 | 266 | removal_diff = [unidiff.PatchSet(removal_diff_text)[0]] 267 | line_ranges = ctr.get_line_ranges(removal_diff, ["test.cxx"], True) 268 | 269 | # With GITHUB_REVIEW_EXTRA_COMMENTABLE_CONTEXT_LINES=3, should process removed lines 270 | result = json.loads(line_ranges) 271 | assert result[0]["name"] == "test.cxx" 272 | # Should have ranges for both the removed context processing 273 | assert len(result[0]["lines"]) > 0, "Should have at least one range" 274 | 275 | 276 | def test_line_ranges_respects_file_boundaries(): 277 | """Test that line ranges respect min/max file line boundaries. 278 | 279 | For very small files (few lines), ranges should be clamped appropriately. 280 | For ranges extending past file end, they're allowed but shouldn't go unreasonably far. 281 | """ 282 | # Create a diff where added lines are at the start of the file 283 | # This tests that we don't create ranges before line 1 284 | small_file_diff_text = r"""diff --git a/tiny.cxx b/tiny.cxx 285 | index 1234567..abcdefg 100644 286 | --- a/tiny.cxx 287 | +++ b/tiny.cxx 288 | @@ -1,2 +1,4 @@ 289 | +int x = 1; 290 | +int y = 2; 291 | void foo(); 292 | void bar(); 293 | """ 294 | 295 | small_file_diff = [unidiff.PatchSet(small_file_diff_text)[0]] 296 | line_ranges = ctr.get_line_ranges(small_file_diff, ["tiny.cxx"], True) 297 | 298 | result = json.loads(line_ranges) 299 | assert result[0]["name"] == "tiny.cxx" 300 | ranges = result[0]["lines"] 301 | 302 | # Verify ranges respect line 1 minimum and reasonable max from expansion 303 | expected_range = [1, 4] 304 | assert ranges == [expected_range], f"Expected {[expected_range]} but got {ranges}" 305 | 306 | for range_pair in ranges: 307 | assert range_pair[0] >= 1, f"Range start {range_pair[0]} is before line 1" 308 | assert ( 309 | range_pair[0] == expected_range[0] 310 | ), f"Expected start {expected_range[0]}, got {range_pair[0]}" 311 | assert ( 312 | range_pair[1] == expected_range[1] 313 | ), f"Expected end {expected_range[1]}, got {range_pair[1]}" 314 | 315 | 316 | def test_line_ranges_merges_overlapping(): 317 | """Test that line ranges are calculated correctly.""" 318 | line_ranges = ctr.get_line_ranges(TEST_DIFF, ["src/hello.cxx"], True) 319 | result = json.loads(line_ranges) 320 | 321 | assert result[0]["name"] == "src/hello.cxx" 322 | ranges = result[0]["lines"] 323 | 324 | # Should have ranges 325 | assert len(ranges) >= 1, f"Expected at least 1 range, got {len(ranges)}: {ranges}" 326 | 327 | 328 | def test_line_ranges_merges_with_gap(): 329 | """Test that line ranges handle various scenarios.""" 330 | line_ranges = ctr.get_line_ranges(TEST_DIFF, ["src/hello.cxx"], True) 331 | result = json.loads(line_ranges) 332 | 333 | assert result[0]["name"] == "src/hello.cxx" 334 | ranges = result[0]["lines"] 335 | 336 | # Should have at least one range 337 | assert len(ranges) >= 1, f"Expected at least 1 range, got {len(ranges)}: {ranges}" 338 | 339 | 340 | def test_load_clang_tidy_warnings(): 341 | warnings = ctr.load_clang_tidy_warnings(TEST_DIR / f"src/test_{ctr.FIXES_FILE}") 342 | 343 | assert sorted(list(warnings.keys())) == ["Diagnostics", "MainSourceFile"] 344 | assert warnings["MainSourceFile"] == "/clang_tidy_review/src/hello.cxx" 345 | assert len(warnings["Diagnostics"]) == 7 346 | 347 | 348 | def test_file_line_lookup(): 349 | line_lookup = ctr.make_file_line_lookup(TEST_DIFF) 350 | 351 | assert line_lookup == {"src/hello.cxx": dict(zip(range(2, 20), range(1, 19)))} 352 | 353 | 354 | def test_file_offset_lookup(): 355 | offset_lookup = ctr.make_file_offset_lookup([TEST_FILE]) 356 | 357 | assert offset_lookup == TEST_OFFSET_LOOKUP 358 | 359 | 360 | def test_find_linenumber_from_offset(): 361 | line_num = ctr.find_line_number_from_offset(TEST_OFFSET_LOOKUP, TEST_FILE, 42) 362 | assert line_num == 4 363 | 364 | 365 | def test_read_one_line(): 366 | line = ctr.read_one_line(TEST_FILE, TEST_OFFSET_LOOKUP[str(TEST_FILE)][4]) 367 | assert line == "const std::string selective_hello(std::string name) {" 368 | 369 | 370 | def test_format_diff_line(): 371 | code_blocks, end_line = ctr.format_diff_line(TEST_DIAGNOSTIC, TEST_OFFSET_LOOKUP, 4) 372 | 373 | expected_replacement = textwrap.dedent( 374 | """ 375 | ```suggestion 376 | std::string selective_hello(std::string name) { 377 | ``` 378 | """ 379 | ) 380 | assert code_blocks == expected_replacement 381 | assert end_line == 4 382 | 383 | 384 | def test_make_comment(): 385 | comment, end_line = ctr.make_comment_from_diagnostic( 386 | "readability-const-return-type", 387 | TEST_DIAGNOSTIC, 388 | str(TEST_FILE), 389 | TEST_OFFSET_LOOKUP, 390 | [], 391 | ) 392 | 393 | expected_comment = textwrap.dedent( 394 | """\ 395 | warning: return type 'const std::string' (aka 'const basic_string') is 'const'-qualified at the top level, which may reduce code readability without improving const correctness [readability-const-return-type] 396 | 397 | ```suggestion 398 | std::string selective_hello(std::string name) { 399 | ``` 400 | """ # noqa: E501 401 | ) 402 | assert comment == expected_comment 403 | assert end_line == 5 404 | 405 | 406 | def test_format_notes(): 407 | message = ctr.format_notes([], TEST_OFFSET_LOOKUP) 408 | assert message == "" 409 | 410 | notes = [ 411 | {"Message": "Test message 1", "FilePath": str(TEST_FILE), "FileOffset": 42}, 412 | {"Message": "Test message 2", "FilePath": str(TEST_FILE), "FileOffset": 98}, 413 | ] 414 | 415 | # Make sure we're in the test directory so the relative paths work 416 | os.chdir(TEST_DIR) 417 | message = ctr.format_notes(notes, TEST_OFFSET_LOOKUP) 418 | 419 | assert message == textwrap.dedent( 420 | """\ 421 |
422 | Additional context 423 | 424 | **src/hello.cxx:4:** Test message 1 425 | ```cpp 426 | const std::string selective_hello(std::string name) { 427 | ^ 428 | ``` 429 | **src/hello.cxx:5:** Test message 2 430 | ```cpp 431 | if (name.compare("Peter")) { 432 | ^ 433 | ``` 434 | 435 |
436 | """ 437 | ) 438 | 439 | 440 | def test_make_comment_with_notes(): 441 | comment, end_line = ctr.make_comment_from_diagnostic( 442 | "readability-const-return-type", 443 | TEST_DIAGNOSTIC, 444 | str(TEST_FILE), 445 | TEST_OFFSET_LOOKUP, 446 | [ 447 | {"Message": "Test message 1", "FilePath": str(TEST_FILE), "FileOffset": 42}, 448 | {"Message": "Test message 2", "FilePath": str(TEST_FILE), "FileOffset": 98}, 449 | ], 450 | ) 451 | 452 | expected_comment = textwrap.dedent( 453 | """\ 454 | warning: return type 'const std::string' (aka 'const basic_string') is 'const'-qualified at the top level, which may reduce code readability without improving const correctness [readability-const-return-type] 455 | 456 | ```suggestion 457 | std::string selective_hello(std::string name) { 458 | ``` 459 |
460 | Additional context 461 | 462 | **src/hello.cxx:4:** Test message 1 463 | ```cpp 464 | const std::string selective_hello(std::string name) { 465 | ^ 466 | ``` 467 | **src/hello.cxx:5:** Test message 2 468 | ```cpp 469 | if (name.compare("Peter")) { 470 | ^ 471 | ``` 472 | 473 |
474 | """ # noqa: E501 475 | ) 476 | assert comment == expected_comment 477 | assert end_line == 5 478 | 479 | 480 | def test_version(monkeypatch): 481 | # Mock out the actual call so this test doesn't depend on a 482 | # particular version of clang-tidy being installed 483 | expected_version = 42 484 | monkeypatch.setattr( 485 | ctr.subprocess, 486 | "run", 487 | lambda *args, **kwargs: MockClangTidyVersionProcess(expected_version), 488 | ) 489 | 490 | version = ctr.clang_tidy_version(Path("not-clang-tidy")) 491 | assert version == expected_version 492 | 493 | 494 | def test_config_file(monkeypatch, tmp_path): 495 | # Mock out the actual call so this test doesn't depend on a 496 | # particular version of clang-tidy being installed 497 | monkeypatch.setattr( 498 | ctr.subprocess, "run", lambda *args, **kwargs: MockClangTidyVersionProcess(15) 499 | ) 500 | 501 | config_file = tmp_path / ".clang-tidy" 502 | 503 | # If you set clang_tidy_checks to something and config_file to something, config_file is sent to clang-tidy. 504 | flag = ctr.config_file_or_checks( 505 | Path("not-clang-tidy"), 506 | clang_tidy_checks="readability", 507 | config_file=str(config_file), 508 | ) 509 | assert flag == f"--config-file={config_file}" 510 | 511 | # If you set clang_tidy_checks and config_file to an empty string, neither are sent to the clang-tidy. 512 | flag = ctr.config_file_or_checks( 513 | Path("not-clang-tidy"), clang_tidy_checks="", config_file="" 514 | ) 515 | assert flag is None 516 | 517 | # If you get config_file to something, config_file is sent to clang-tidy. 518 | flag = ctr.config_file_or_checks( 519 | Path("not-clang-tidy"), clang_tidy_checks="", config_file=str(config_file) 520 | ) 521 | assert flag == f"--config-file={config_file}" 522 | 523 | # If you get clang_tidy_checks to something and config_file to nothing, clang_tidy_checks is sent to clang-tidy. 524 | flag = ctr.config_file_or_checks( 525 | Path("not-clang-tidy"), clang_tidy_checks="readability", config_file="" 526 | ) 527 | assert flag == "--checks=readability" 528 | 529 | 530 | def test_decorate_comment_body(): 531 | # No link to generic error so the message shouldn't be changed 532 | error_message = ( 533 | "warning: no member named 'ranges' in namespace 'std' [clang-diagnostic-error]" 534 | ) 535 | assert ctr.decorate_check_names(error_message) == error_message 536 | 537 | todo_message = "warning: missing username/bug in TODO [google-readability-todo]" 538 | todo_message_decorated = "warning: missing username/bug in TODO [[google-readability-todo](https://clang.llvm.org/extra/clang-tidy/checks/google/readability-todo.html)]" 539 | assert ctr.decorate_check_names(todo_message) == todo_message_decorated 540 | 541 | naming_message = "warning: invalid case style for constexpr variable 'foo' [readability-identifier-naming]" 542 | naming_message_decorated = "warning: invalid case style for constexpr variable 'foo' [[readability-identifier-naming](https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html)]" 543 | assert ctr.decorate_check_names(naming_message) == naming_message_decorated 544 | 545 | clang_analyzer_message = "warning: Array access (from variable 'foo') results in a null pointer dereference [clang-analyzer-core.NullDereference]" 546 | clang_analyzer_message_decorated = "warning: Array access (from variable 'foo') results in a null pointer dereference [[clang-analyzer-core.NullDereference](https://clang.llvm.org/extra/clang-tidy/checks/clang-analyzer/core.NullDereference.html)]" 547 | assert ( 548 | ctr.decorate_check_names(clang_analyzer_message) 549 | == clang_analyzer_message_decorated 550 | ) 551 | 552 | clazy_message = "warning: The QColor ctor taking RGB int value is cheaper than one taking string literals [clazy-qcolor-from-literal]" 553 | clazy_message_decorated = "warning: The QColor ctor taking RGB int value is cheaper than one taking string literals [[clazy-qcolor-from-literal](https://invent.kde.org/sdk/clazy/-/blob/master/docs/checks/README-qcolor-from-literal.md)]" 554 | assert ctr.decorate_check_names(clazy_message) == clazy_message_decorated 555 | 556 | # Not sure it's necessary to link to prior version documentation especially since we have to map versions such as 557 | # "17" to "17.0.1" and "18" to "18.1.0" because no other urls exist 558 | # version_message_pre_15_version = "14.0.0" 559 | # version_message_pre_15 = "warning: missing username/bug in TODO [google-readability-todo]" 560 | # version_message_pre_15_decorated = "warning: missing username/bug in TODO [[google-readability-todo](https://releases.llvm.org/14.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/google-readability-todo.html)]" 561 | # assert ctr.decorate_check_names(version_message_pre_15, version_message_pre_15_version) == version_message_pre_15_decorated 562 | # 563 | # version_message_1500_version = "15.0.0" 564 | # version_message_1500 = "warning: missing username/bug in TODO [google-readability-todo]" 565 | # version_message_1500_decorated = "warning: missing username/bug in TODO [[google-readability-todo](https://releases.llvm.org/15.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/google/readability-todo.html)]" 566 | # assert ctr.decorate_check_names(version_message_1500, version_message_1500_version) == version_message_1500_decorated 567 | 568 | 569 | def test_timing_summary(monkeypatch): 570 | monkeypatch.setattr(ctr, "PROFILE_DIR", TEST_DIR / "src/clang-tidy-profile") 571 | profiling = ctr.load_and_merge_profiling() 572 | assert "time.clang-tidy.total.wall" in profiling["hello.cxx"].keys() 573 | assert "time.clang-tidy.total.user" in profiling["hello.cxx"].keys() 574 | assert "time.clang-tidy.total.sys" in profiling["hello.cxx"].keys() 575 | summary = ctr.make_timing_summary(profiling, datetime.timedelta(seconds=42)) 576 | assert len(summary.split("\n")) == 22 577 | -------------------------------------------------------------------------------- /post/clang_tidy_review/clang_tidy_review/__init__.py: -------------------------------------------------------------------------------- 1 | # clang-tidy review 2 | # Copyright (c) 2020 Peter Hill 3 | # SPDX-License-Identifier: MIT 4 | # See LICENSE for more information 5 | 6 | import argparse 7 | import base64 8 | import contextlib 9 | import datetime 10 | import fnmatch 11 | import io 12 | import itertools 13 | import json 14 | import multiprocessing 15 | import os 16 | import pathlib 17 | import pprint 18 | import queue 19 | import re 20 | import shlex 21 | import shutil 22 | import subprocess 23 | import sys 24 | import tempfile 25 | import textwrap 26 | import threading 27 | import zipfile 28 | from operator import itemgetter 29 | from pathlib import Path 30 | from typing import Any, Literal, Optional, TypedDict 31 | 32 | import unidiff 33 | import urllib3 34 | import yaml 35 | from github import Auth, Github 36 | from github.PaginatedList import PaginatedList 37 | from github.PullRequest import PullRequest as RGHPullRequest 38 | from github.PullRequest import ReviewComment 39 | from github.PullRequestComment import PullRequestComment 40 | from github.WorkflowRun import WorkflowRun 41 | 42 | DIFF_HEADER_LINE_LENGTH = 5 43 | FIXES_FILE = Path("clang_tidy_review.yaml") 44 | METADATA_FILE = Path("clang-tidy-review-metadata.json") 45 | REVIEW_FILE = Path("clang-tidy-review-output.json") 46 | PROFILE_DIR = Path("clang-tidy-review-profile") 47 | MAX_ANNOTATIONS = 10 48 | 49 | 50 | class Metadata(TypedDict): 51 | """Loaded from `METADATA_FILE` 52 | Contains information necessary to post a review without pull request knowledge 53 | 54 | """ 55 | 56 | pr_number: int 57 | 58 | 59 | class PRReview(TypedDict): 60 | body: str 61 | event: str 62 | comments: list[ReviewComment] 63 | 64 | 65 | # These types are from include/clang/Tooling/DiagnosticsYaml.h 66 | # and include/clang/Tooling/ReplacementsYaml.h 67 | class ClangFileByteRange(TypedDict): 68 | FilePath: str 69 | FileOffset: int 70 | Length: int 71 | 72 | 73 | class ClangReplacement(TypedDict): 74 | FilePath: str 75 | Offset: int 76 | Length: int 77 | ReplacementText: str 78 | # These are added by us 79 | LineNumber: Optional[int] 80 | EndLineNumber: Optional[int] 81 | 82 | 83 | class ClangDiagnosticMessage(TypedDict): 84 | Message: str 85 | FilePath: Optional[str] 86 | FileOffset: Optional[int] 87 | Replacements: list[ClangReplacement] 88 | Ranges: Optional[ClangFileByteRange] 89 | 90 | 91 | class ClangDiagnostic(TypedDict): 92 | DiagnosticName: str 93 | DiagnosticMessage: ClangDiagnosticMessage 94 | Notes: Optional[list[ClangDiagnosticMessage]] 95 | Level: Optional[Literal["Warning", "Error", "Remark"]] 96 | BuildDirectory: Optional[str] 97 | 98 | 99 | class ClangTUDiagnostics(TypedDict): 100 | MainSourceFile: str 101 | Diagnostics: list[ClangDiagnostic] 102 | 103 | 104 | OffsetLookup = dict[str, list[int]] 105 | """A dictionary to look up file offsets for a given line (filename -> file-offsets). 106 | For each file, the file offsets contain the cumulative line lengths. 107 | To look up the file offset where line 2 starts in "foo.c", look up ``map["foo.c"][2]``. 108 | Lines are 0-indexed. 109 | """ 110 | 111 | 112 | class HashableComment: 113 | def __init__(self, body: str, line: int, path: str, side: str, **kwargs): 114 | self.body = body 115 | self.line = line 116 | self.path = path 117 | self.side = side 118 | 119 | def __hash__(self): 120 | return hash( 121 | ( 122 | self.body, 123 | self.line, 124 | self.path, 125 | self.side, 126 | ) 127 | ) 128 | 129 | def __eq__(self, other): 130 | return ( 131 | type(self) is type(other) 132 | and self.body == other.body 133 | and self.line == self.line 134 | and other.path == other.path 135 | and self.side == other.side 136 | ) 137 | 138 | def __lt__(self, other): 139 | if self.path != other.path: 140 | return self.path < other.path 141 | if self.line != other.line: 142 | return self.line < other.line 143 | if self.side != other.side: 144 | return self.side < other.side 145 | if self.body != other.body: 146 | return self.body < other.body 147 | return id(self) < id(other) 148 | 149 | 150 | def add_auth_arguments(parser: argparse.ArgumentParser): 151 | # Token 152 | parser.add_argument("--token", help="github auth token") 153 | # App 154 | group_app = parser.add_argument_group( 155 | """Github app installation authentication 156 | Permissions required: Contents (Read) and Pull requests (Read and Write)""" 157 | ) 158 | group_app.add_argument("--app-id", type=int, help="app ID") 159 | group_app.add_argument( 160 | "--private-key", type=str, help="app private key as a string" 161 | ) 162 | group_app.add_argument( 163 | "--private-key-base64", 164 | type=str, 165 | help="app private key as a string encoded as base64", 166 | ) 167 | group_app.add_argument( 168 | "--private-key-file-path", 169 | type=pathlib.Path, 170 | help="app private key .pom file path", 171 | ) 172 | group_app.add_argument("--installation-id", type=int, help="app installation ID") 173 | 174 | 175 | def get_auth_from_arguments(args: argparse.Namespace) -> Auth.Auth: 176 | if args.token: 177 | return Auth.Token(args.token) 178 | 179 | if ( 180 | args.app_id 181 | and (args.private_key or args.private_key_file_path or args.private_key_base64) 182 | and args.installation_id 183 | ): 184 | if args.private_key: 185 | private_key = args.private_key 186 | elif args.private_key_base64: 187 | private_key = base64.b64decode(args.private_key_base64).decode("ascii") 188 | else: 189 | private_key = pathlib.Path(args.private_key_file_path).read_text() 190 | return Auth.AppAuth(args.app_id, private_key).get_installation_auth( 191 | args.installation_id 192 | ) 193 | if ( 194 | args.app_id 195 | or args.private_key 196 | or args.private_key_file_path 197 | or args.private_key_base64 198 | or args.installation_id 199 | ): 200 | raise argparse.ArgumentError( 201 | None, 202 | "--app-id, --private-key[-file-path|-base64] and --installation-id must be supplied together", 203 | ) 204 | 205 | raise argparse.ArgumentError(None, "authentication method not supplied") 206 | 207 | 208 | def build_clang_tidy_warnings( 209 | base_invocation: list[str], 210 | env: dict[str, str], 211 | tmpdir: Path, 212 | task_queue: queue.Queue, 213 | lock: threading.Lock, 214 | failed_files: list[str], 215 | ) -> None: 216 | """Run clang-tidy on the given files and save output into a temporary file""" 217 | 218 | while True: 219 | name = task_queue.get() 220 | invocation = base_invocation[:] 221 | 222 | # Get a temporary file. We immediately close the handle so clang-tidy can 223 | # overwrite it. 224 | (handle, fixes_file) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) 225 | os.close(handle) 226 | invocation.append(f"--export-fixes={fixes_file}") 227 | 228 | invocation.append(name) 229 | 230 | proc = subprocess.Popen( 231 | invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env 232 | ) 233 | output, err = proc.communicate() 234 | 235 | if proc.returncode != 0: 236 | if proc.returncode < 0: 237 | msg = f"{name}: terminated by signal {-proc.returncode}\n" 238 | err += msg.encode("utf-8") 239 | failed_files.append(name) 240 | with lock: 241 | subprocess.list2cmdline(invocation) 242 | sys.stdout.write( 243 | f'{name}: {subprocess.list2cmdline(invocation)}\n{output.decode("utf-8")}' 244 | ) 245 | if len(err) > 0: 246 | sys.stdout.flush() 247 | sys.stderr.write(err.decode("utf-8")) 248 | 249 | task_queue.task_done() 250 | 251 | 252 | def clang_tidy_version(clang_tidy_binary: pathlib.Path) -> int: 253 | try: 254 | version_out = subprocess.run( 255 | [clang_tidy_binary, "--version"], 256 | capture_output=True, 257 | check=True, 258 | text=True, 259 | ).stdout 260 | except subprocess.CalledProcessError as e: 261 | print(f"\n\nWARNING: Couldn't get clang-tidy version, error was: {e}") 262 | return 0 263 | 264 | if version := re.search(r"version (\d+)", version_out): 265 | return int(version.group(1)) 266 | 267 | print( 268 | f"\n\nWARNING: Couldn't get clang-tidy version number, '{clang_tidy_binary} --version' reported: {version_out}" 269 | ) 270 | return 0 271 | 272 | 273 | def config_file_or_checks( 274 | clang_tidy_binary: pathlib.Path, clang_tidy_checks: str, config_file: str 275 | ) -> Optional[str]: 276 | version = clang_tidy_version(clang_tidy_binary) 277 | 278 | if config_file == "": 279 | if clang_tidy_checks: 280 | return f"--checks={clang_tidy_checks}" 281 | return None 282 | 283 | if version >= 12: 284 | return f"--config-file={config_file}" 285 | 286 | if config_file != ".clang-tidy": 287 | print( 288 | f"\n\nWARNING: non-default config file name '{config_file}' will be ignored for " 289 | "selected clang-tidy version {version}. This version expects exactly '.clang-tidy'\n" 290 | ) 291 | 292 | return "--config" 293 | 294 | 295 | def merge_replacement_files(tmpdir: Path, mergefile: Path) -> None: 296 | """Merge all replacement files in a directory into a single file""" 297 | # The fixes suggested by clang-tidy >= 4.0.0 are given under 298 | # the top level key 'Diagnostics' in the output yaml files 299 | mergekey = "Diagnostics" 300 | merged: list[ClangDiagnostic] = [] 301 | for replacefile in tmpdir.glob("*.yaml"): 302 | with replacefile.open() as f: 303 | content: ClangTUDiagnostics | None = yaml.safe_load(f) 304 | if not content: 305 | continue # Skip empty files. 306 | merged.extend(content.get(mergekey, [])) 307 | 308 | if merged: 309 | # MainSourceFile: The key is required by the definition inside 310 | # include/clang/Tooling/ReplacementsYaml.h, but the value 311 | # is actually never used inside clang-apply-replacements, 312 | # so we set it to '' here. 313 | output: ClangTUDiagnostics = {"MainSourceFile": "", mergekey: merged} 314 | with mergefile.open("w") as out: 315 | yaml.safe_dump(output, out) 316 | 317 | 318 | def load_clang_tidy_warnings(fixes_file: Path) -> ClangTUDiagnostics: 319 | """Read clang-tidy warnings from fixes_file. Can be produced by build_clang_tidy_warnings""" 320 | try: 321 | with fixes_file.open() as f: 322 | return yaml.safe_load(f) 323 | except FileNotFoundError: 324 | return {} 325 | 326 | 327 | class PullRequest: 328 | """Add some convenience functions not in PyGithub""" 329 | 330 | def __init__(self, repo: str, pr_number: Optional[int], auth: Auth.Auth) -> None: 331 | self.repo_name = repo 332 | self.pr_number = pr_number 333 | self.auth = auth 334 | 335 | # Choose API URL, default to public GitHub 336 | self.api_url = os.environ.get("GITHUB_API_URL", "https://api.github.com") 337 | 338 | github = Github(auth=self.auth, base_url=self.api_url) 339 | self.repo = github.get_repo(f"{repo}") 340 | self._pull_request: Optional[RGHPullRequest] = None 341 | 342 | @property 343 | def token(self): 344 | return self.auth.token 345 | 346 | @property 347 | def pull_request(self): 348 | if self._pull_request is None: 349 | if self.pr_number is None: 350 | raise RuntimeError("Missing PR number") 351 | 352 | self._pull_request = self.repo.get_pull(int(self.pr_number)) 353 | return self._pull_request 354 | 355 | @property 356 | def head_sha(self) -> str: 357 | if self._pull_request is None: 358 | raise RuntimeError("Missing PR") 359 | 360 | return self._pull_request.get_commits().reversed[0].sha 361 | 362 | def get_pr_diff(self) -> list[unidiff.PatchedFile]: 363 | """Download the PR diff, return a list of PatchedFile""" 364 | 365 | _, data = self.repo._requester.requestJsonAndCheck( 366 | "GET", 367 | self.pull_request.url, 368 | headers={"Accept": f"application/vnd.github.{'v3.diff'}"}, 369 | ) 370 | if not data: 371 | return [] 372 | 373 | diffs = data["data"] 374 | 375 | # PatchSet is the easiest way to construct what we want, but the 376 | # diff_line_no property on lines is counted from the top of the 377 | # whole PatchSet, whereas GitHub is expecting the "position" 378 | # property to be line count within each file's diff. So we need to 379 | # do this little bit of faff to get a list of file-diffs with 380 | # their own diff_line_no range 381 | return [unidiff.PatchSet(str(file))[0] for file in unidiff.PatchSet(diffs)] 382 | 383 | def get_pr_author(self) -> str: 384 | """Get the username of the PR author. This is used in google-readability-todo""" 385 | return self.pull_request.user.login 386 | 387 | def get_pr_comments(self) -> PaginatedList[PullRequestComment]: 388 | """Download the PR review comments using the comfort-fade preview headers""" 389 | return self.pull_request.get_review_comments() 390 | 391 | def post_lgtm_comment(self, body: str) -> None: 392 | """Post a "LGTM" comment if everything's clean, making sure not to spam""" 393 | 394 | if not body: 395 | return 396 | 397 | comments = self.get_pr_comments() 398 | 399 | for comment in comments: 400 | if comment.body == body: 401 | print("Already posted, no need to update") 402 | return 403 | 404 | self.pull_request.create_issue_comment(body) 405 | 406 | def post_review(self, review: PRReview) -> None: 407 | """Submit a completed review""" 408 | self.pull_request.create_review(**review) 409 | 410 | def post_annotations(self, review: dict[str, Any]) -> None: 411 | headers = { 412 | "Accept": "application/vnd.github+json", 413 | "Authorization": f"Bearer {self.token}", 414 | } 415 | url = f"{self.api_url}/repos/{self.repo_name}/check-runs" 416 | 417 | self.repo._requester.requestJsonAndCheck( 418 | "POST", url, parameters=review, headers=headers 419 | ) 420 | 421 | 422 | @contextlib.contextmanager 423 | def message_group(title: str): 424 | print(f"::group::{title}", flush=True) 425 | try: 426 | yield 427 | finally: 428 | print("::endgroup::", flush=True) 429 | 430 | 431 | def make_file_line_lookup(diff: list[unidiff.PatchedFile]) -> dict[str, dict[int, int]]: 432 | """Get a lookup table for each file in diff, to convert between source 433 | line number to line number in the diff 434 | 435 | """ 436 | lookup: dict[str, dict[int, int]] = {} 437 | for file in diff: 438 | filename = file.target_file[2:] 439 | lookup[filename] = {} 440 | for hunk in file: 441 | for line in hunk: 442 | if line.diff_line_no is None: 443 | continue 444 | if not line.is_removed: 445 | lookup[filename][line.target_line_no] = ( 446 | line.diff_line_no - DIFF_HEADER_LINE_LENGTH 447 | ) 448 | return lookup 449 | 450 | 451 | def make_file_offset_lookup(filenames: list[str]) -> OffsetLookup: 452 | """Create a lookup table to convert between character offset and line 453 | number for the list of files in `filenames`. 454 | 455 | This is a dict of the cumulative sum of the line lengths for each file. 456 | 457 | """ 458 | lookup: OffsetLookup = {} 459 | 460 | for filename in filenames: 461 | with Path(filename).open() as file: 462 | lines = file.readlines() 463 | # Length of each line 464 | line_lengths = map(len, lines) 465 | # Cumulative sum of line lengths => offset at end of each line 466 | lookup[Path(filename).resolve().as_posix()] = [ 467 | 0, 468 | *list(itertools.accumulate(line_lengths)), 469 | ] 470 | 471 | return lookup 472 | 473 | 474 | def get_diagnostic_file_path( 475 | clang_tidy_diagnostic: ClangDiagnostic, build_dir: str 476 | ) -> str: 477 | # Sometimes, clang-tidy gives us an absolute path, so everything is fine. 478 | # Sometimes however it gives us a relative path that is realtive to the 479 | # build directory, so we prepend that. 480 | 481 | # Modern clang-tidy 482 | if ("DiagnosticMessage" in clang_tidy_diagnostic) and ( 483 | "FilePath" in clang_tidy_diagnostic["DiagnosticMessage"] 484 | ): 485 | file_path = clang_tidy_diagnostic["DiagnosticMessage"]["FilePath"] 486 | if file_path == "": 487 | return "" 488 | file_path = Path(file_path) 489 | if file_path.is_absolute(): 490 | return os.path.normpath(file_path.resolve()) 491 | if "BuildDirectory" in clang_tidy_diagnostic: 492 | return os.path.normpath( 493 | (Path(clang_tidy_diagnostic["BuildDirectory"]) / file_path).resolve() 494 | ) 495 | return os.path.normpath(file_path.resolve()) 496 | 497 | # Pre-clang-tidy-9 format 498 | if "FilePath" in clang_tidy_diagnostic: 499 | file_path = clang_tidy_diagnostic["FilePath"] 500 | if file_path == "": 501 | return "" 502 | return os.path.normpath((Path(build_dir) / file_path).resolve()) 503 | 504 | return "" 505 | 506 | 507 | def find_line_number_from_offset( 508 | offset_lookup: OffsetLookup, filename: str, offset: int 509 | ) -> int: 510 | """Work out which line number `offset` corresponds to using `offset_lookup`. 511 | 512 | The line number (0-indexed) is the index of the first line offset 513 | which is larger than `offset`. 514 | 515 | """ 516 | name = str(pathlib.Path(filename).resolve().absolute()) 517 | 518 | if name not in offset_lookup: 519 | # Let's make sure we've the file offsets for this other file 520 | offset_lookup.update(make_file_offset_lookup([name])) 521 | 522 | for line_num, line_offset in enumerate(offset_lookup[name]): 523 | if line_offset > offset: 524 | return line_num - 1 525 | return -1 526 | 527 | 528 | def read_one_line(filename: str, line_offset: int) -> str: 529 | """Read a single line from a source file""" 530 | # Could cache the files instead of opening them each time? 531 | with Path(filename).open() as file: 532 | file.seek(line_offset) 533 | return file.readline().rstrip("\n") 534 | 535 | 536 | def collate_replacement_sets( 537 | diagnostic: ClangDiagnosticMessage, offset_lookup: OffsetLookup 538 | ) -> dict[int, list[ClangReplacement]]: 539 | """Return a dict of replacements on the same or consecutive lines, indexed by line number 540 | 541 | We need this as we have to apply all the replacements on one line at the same time 542 | 543 | This could break if there are replacements in with the same line 544 | number but in different files. 545 | 546 | """ 547 | 548 | # First, make sure each replacement contains "LineNumber", and 549 | # "EndLineNumber" in case it spans multiple lines 550 | for replacement in diagnostic["Replacements"]: 551 | # Sometimes, the FilePath may include ".." in "." as a path component 552 | # However, file paths are stored in the offset table only after being 553 | # converted to an abs path, in which case the stored path will differ 554 | # from the FilePath and we'll end up looking for a path that's not in 555 | # the lookup dict 556 | # To fix this, we'll convert all the FilePaths to absolute paths 557 | replacement["FilePath"] = Path(replacement["FilePath"]).resolve().as_posix() 558 | 559 | # It's possible the replacement is needed in another file? 560 | # Not really sure how that could come about, but let's 561 | # cover our behinds in case it does happen: 562 | if replacement["FilePath"] not in offset_lookup: 563 | # Let's make sure we've the file offsets for this other file 564 | offset_lookup.update(make_file_offset_lookup([replacement["FilePath"]])) 565 | 566 | replacement["LineNumber"] = find_line_number_from_offset( 567 | offset_lookup, replacement["FilePath"], replacement["Offset"] 568 | ) 569 | replacement["EndLineNumber"] = find_line_number_from_offset( 570 | offset_lookup, 571 | replacement["FilePath"], 572 | replacement["Offset"] + replacement["Length"], 573 | ) 574 | 575 | # Now we can group them into consecutive lines 576 | groups: list[list[ClangReplacement]] = [] 577 | for index, replacement in enumerate(diagnostic["Replacements"]): 578 | if index == 0: 579 | # First one starts a new group, always 580 | groups.append([replacement]) 581 | elif ( 582 | replacement["LineNumber"] == groups[-1][-1]["LineNumber"] 583 | or replacement["LineNumber"] - 1 == groups[-1][-1]["LineNumber"] 584 | ): 585 | # Same or adjacent line to the last line in the last group 586 | # goes in the same group 587 | groups[-1].append(replacement) 588 | else: 589 | # Otherwise, start a new group 590 | groups.append([replacement]) 591 | 592 | # Turn the list into a dict 593 | return {g[0]["LineNumber"]: g for g in groups} 594 | 595 | 596 | def replace_one_line( 597 | replacement_set: list[ClangReplacement], 598 | line_num: int, 599 | offset_lookup: OffsetLookup, 600 | ) -> tuple[str, str]: 601 | """Apply all the replacements in replacement_set at the same time""" 602 | 603 | filename = replacement_set[0]["FilePath"] 604 | # File offset at the start of the first line 605 | line_offset = offset_lookup[filename][line_num] 606 | 607 | # List of (start, end) offsets from line_offset 608 | insert_offsets: list[tuple[Optional[int], Optional[int]]] = [(0, 0)] 609 | # Read all the source lines into a dict so we only get one copy of 610 | # each line, though we might read the same line in multiple times 611 | source_lines: dict[int, str] = {} 612 | for replacement in replacement_set: 613 | start = replacement["Offset"] - line_offset 614 | end = start + replacement["Length"] 615 | insert_offsets.append((start, end)) 616 | 617 | # Make sure to read any extra lines we need too 618 | for replacement_line_num in range( 619 | replacement["LineNumber"], replacement["EndLineNumber"] + 1 620 | ): 621 | replacement_line_offset = offset_lookup[filename][replacement_line_num] 622 | source_lines[replacement_line_num] = ( 623 | read_one_line(filename, replacement_line_offset) + "\n" 624 | ) 625 | 626 | # Replacements might cross multiple lines, so squash them all together 627 | source_line = "".join(source_lines.values()).rstrip("\n") 628 | 629 | insert_offsets.append((None, None)) 630 | 631 | fragments: list[str] = [] 632 | for (_, start), (end, _) in zip(insert_offsets[:-1], insert_offsets[1:]): 633 | fragments.append(source_line[start:end]) 634 | 635 | new_line = "" 636 | for fragment, replacement in zip(fragments, replacement_set): 637 | new_line += fragment + replacement["ReplacementText"] 638 | 639 | return source_line, new_line + fragments[-1] 640 | 641 | 642 | def format_ordinary_line(source_line: str, line_offset: int) -> str: 643 | """Format a single C++ line with a diagnostic indicator""" 644 | 645 | return textwrap.dedent( 646 | f"""\ 647 | ```cpp 648 | {source_line} 649 | {line_offset * " " + "^"} 650 | ``` 651 | """ 652 | ) 653 | 654 | 655 | def format_diff_line( 656 | diagnostic: ClangDiagnosticMessage, 657 | offset_lookup: OffsetLookup, 658 | line_num: int, 659 | ) -> tuple[str, int]: 660 | """Format a replacement as a Github suggestion or diff block""" 661 | 662 | end_line = line_num 663 | 664 | # We're going to be appending to this 665 | code_blocks = "" 666 | 667 | replacement_sets = collate_replacement_sets(diagnostic, offset_lookup) 668 | 669 | for replacement_line_num, replacement_set in replacement_sets.items(): 670 | old_line, new_line = replace_one_line( 671 | replacement_set, replacement_line_num, offset_lookup 672 | ) 673 | 674 | print(f"----------\n{old_line=}\n{new_line=}\n----------") 675 | 676 | # If the replacement is for the same line as the 677 | # diagnostic (which is where the comment will be), then 678 | # format the replacement as a suggestion. Otherwise, 679 | # format it as a diff 680 | if replacement_line_num == line_num: 681 | code_blocks += f""" 682 | ```suggestion 683 | {new_line} 684 | ``` 685 | """ 686 | end_line = replacement_set[-1]["EndLineNumber"] 687 | else: 688 | # Prepend each line in the replacement line with "+ " 689 | # in order to make a nice diff block. The extra 690 | # whitespace is so the multiline dedent-ed block below 691 | # doesn't come out weird. 692 | whitespace = "\n " 693 | new_line = whitespace.join([f"+ {line}" for line in new_line.splitlines()]) 694 | old_line = whitespace.join([f"- {line}" for line in old_line.splitlines()]) 695 | 696 | rel_path = try_relative(replacement_set[0]["FilePath"]).as_posix() 697 | code_blocks += textwrap.dedent( 698 | f"""\ 699 | 700 | {rel_path}:{replacement_line_num}: 701 | ```diff 702 | {old_line} 703 | {new_line} 704 | ``` 705 | """ 706 | ) 707 | return code_blocks, end_line 708 | 709 | 710 | def try_relative(path: str) -> pathlib.Path: 711 | """Try making `path` relative to current directory, otherwise make it an absolute path""" 712 | try: 713 | here = pathlib.Path.cwd() 714 | return pathlib.Path(path).relative_to(here) 715 | except ValueError: 716 | return pathlib.Path(path).resolve() 717 | 718 | 719 | def fix_absolute_paths(build_compile_commands: str, base_dir: str) -> None: 720 | """Update absolute paths in compile_commands.json to new location, if 721 | compile_commands.json was created outside the Actions container 722 | """ 723 | 724 | basedir = pathlib.Path(base_dir).resolve() 725 | newbasedir = Path.cwd() 726 | 727 | if basedir == newbasedir: 728 | return 729 | 730 | print(f"Found '{build_compile_commands}', updating absolute paths") 731 | # We might need to change some absolute paths if we're inside 732 | # a docker container 733 | with Path(build_compile_commands).open() as f: 734 | compile_commands = json.load(f) 735 | 736 | print(f"Replacing '{basedir}' with '{newbasedir}'", flush=True) 737 | 738 | modified_compile_commands = json.dumps(compile_commands).replace( 739 | str(basedir), str(newbasedir) 740 | ) 741 | 742 | with Path(build_compile_commands).open("w") as f: 743 | f.write(modified_compile_commands) 744 | 745 | 746 | def format_notes( 747 | notes: list[ClangDiagnosticMessage], offset_lookup: OffsetLookup 748 | ) -> str: 749 | """Format an array of notes into a single string""" 750 | 751 | code_blocks = "" 752 | 753 | for note in notes: 754 | filename = note["FilePath"] 755 | 756 | if filename == "": 757 | return note["Message"] 758 | 759 | resolved_path = str(pathlib.Path(filename).resolve().absolute()) 760 | 761 | line_num = find_line_number_from_offset( 762 | offset_lookup, resolved_path, note["FileOffset"] 763 | ) 764 | line_offset: int = note["FileOffset"] - offset_lookup[resolved_path][line_num] 765 | source_line = read_one_line( 766 | resolved_path, offset_lookup[resolved_path][line_num] 767 | ) 768 | 769 | path = try_relative(resolved_path) 770 | message = f"**{path.as_posix()}:{line_num}:** {note['Message']}" 771 | code = format_ordinary_line(source_line, line_offset) 772 | code_blocks += f"{message}\n{code}" 773 | 774 | if notes: 775 | code_blocks = f"
\nAdditional context\n\n{code_blocks}\n
\n" 776 | 777 | return code_blocks 778 | 779 | 780 | def make_comment_from_diagnostic( 781 | diagnostic_name: str, 782 | diagnostic: ClangDiagnosticMessage, 783 | filename: str, 784 | offset_lookup: OffsetLookup, 785 | notes: list[ClangDiagnosticMessage], 786 | ) -> tuple[str, int]: 787 | """Create a comment from a diagnostic 788 | 789 | Comment contains the diagnostic message, plus its name, along with 790 | code block(s) containing either the exact location of the 791 | diagnostic, or suggested fix(es). 792 | 793 | """ 794 | 795 | line_num = find_line_number_from_offset( 796 | offset_lookup, filename, diagnostic["FileOffset"] 797 | ) 798 | line_offset: int = diagnostic["FileOffset"] - offset_lookup[filename][line_num] 799 | 800 | source_line = read_one_line(filename, offset_lookup[filename][line_num]) 801 | end_line = line_num 802 | 803 | print( 804 | f"""{diagnostic} 805 | {line_num=}; {line_offset=}; {source_line=} 806 | """ 807 | ) 808 | 809 | if diagnostic["Replacements"]: 810 | code_blocks, end_line = format_diff_line(diagnostic, offset_lookup, line_num) 811 | else: 812 | # No fixit, so just point at the problem 813 | code_blocks = format_ordinary_line(source_line, line_offset) 814 | 815 | code_blocks += format_notes(notes, offset_lookup) 816 | 817 | comment_body = ( 818 | f"warning: {diagnostic['Message']} [{diagnostic_name}]\n{code_blocks}" 819 | ) 820 | 821 | return comment_body, end_line + 1 822 | 823 | 824 | def create_review_file( 825 | clang_tidy_warnings: ClangTUDiagnostics, 826 | diff_lookup: dict[str, dict[int, int]], 827 | offset_lookup: OffsetLookup, 828 | build_dir: str, 829 | ) -> Optional[PRReview]: 830 | """Create a Github review from a set of clang-tidy diagnostics""" 831 | 832 | if "Diagnostics" not in clang_tidy_warnings: 833 | return None 834 | 835 | comments: list[ReviewComment] = [] 836 | 837 | for diagnostic in clang_tidy_warnings["Diagnostics"]: 838 | try: 839 | diagnostic_message = diagnostic["DiagnosticMessage"] 840 | except KeyError: 841 | # Pre-clang-tidy-9 format 842 | diagnostic_message = diagnostic 843 | 844 | if diagnostic_message["FilePath"] == "": 845 | continue 846 | 847 | comment_body, end_line = make_comment_from_diagnostic( 848 | diagnostic["DiagnosticName"], 849 | diagnostic_message, 850 | get_diagnostic_file_path(diagnostic, build_dir), 851 | offset_lookup, 852 | notes=diagnostic.get("Notes", []), 853 | ) 854 | 855 | rel_path = try_relative( 856 | get_diagnostic_file_path(diagnostic, build_dir) 857 | ).as_posix() 858 | # diff lines are 1-indexed 859 | source_line = 1 + find_line_number_from_offset( 860 | offset_lookup, 861 | get_diagnostic_file_path(diagnostic, build_dir), 862 | diagnostic_message["FileOffset"], 863 | ) 864 | 865 | if rel_path not in diff_lookup or end_line not in diff_lookup[rel_path]: 866 | print( 867 | f"WARNING: Skipping comment for file '{rel_path}' not in PR changeset. Comment body is:\n{comment_body}" 868 | ) 869 | continue 870 | 871 | comments.append( 872 | { 873 | "path": rel_path, 874 | "body": comment_body, 875 | "side": "RIGHT", 876 | "line": end_line, 877 | } 878 | ) 879 | # If this is a multiline comment, we need a couple more bits: 880 | if end_line != source_line: 881 | comments[-1].update( 882 | { 883 | "start_side": "RIGHT", 884 | "start_line": source_line, 885 | } 886 | ) 887 | 888 | review: PRReview = { 889 | "body": "clang-tidy made some suggestions", 890 | "event": "COMMENT", 891 | "comments": comments, 892 | } 893 | return review 894 | 895 | 896 | def make_timing_summary( 897 | clang_tidy_profiling: dict[str, dict[str, float]], 898 | real_time: datetime.timedelta, 899 | sha: Optional[str] = None, 900 | ) -> str: 901 | if not clang_tidy_profiling: 902 | return "" 903 | top_amount = 10 904 | wall_key = "time.clang-tidy.total.wall" 905 | user_key = "time.clang-tidy.total.user" 906 | sys_key = "time.clang-tidy.total.sys" 907 | total_wall = sum(timings[wall_key] for timings in clang_tidy_profiling.values()) 908 | total_user = sum(timings[user_key] for timings in clang_tidy_profiling.values()) 909 | total_sys = sum(timings[sys_key] for timings in clang_tidy_profiling.values()) 910 | print(f"Took: {total_user:.2f}s user {total_sys:.2f} system {total_wall:.2f} total") 911 | file_summary = textwrap.dedent( 912 | f"""\ 913 | ### Top {top_amount} files 914 | | File | user (s) | system (s) | total (s) | 915 | | ----- | ---------------- | --------------- | ---------------- | 916 | | Total | {total_user:.2f} | {total_sys:.2f} | {total_wall:.2f} | 917 | """ 918 | ) 919 | topfiles = sorted( 920 | ( 921 | ( 922 | os.path.relpath(file), 923 | timings[user_key], 924 | timings[sys_key], 925 | timings[wall_key], 926 | ) 927 | for file, timings in clang_tidy_profiling.items() 928 | ), 929 | key=lambda x: x[3], 930 | reverse=True, 931 | ) 932 | 933 | if "GITHUB_SERVER_URL" in os.environ and "GITHUB_REPOSITORY" in os.environ: 934 | blob = f"{os.environ['GITHUB_SERVER_URL']}/{os.environ['GITHUB_REPOSITORY']}/blob/{sha}" 935 | else: 936 | blob = None 937 | for f, u, s, w in list(topfiles)[:top_amount]: 938 | if blob is not None: 939 | f = f"[{f}]({blob}/{f})" 940 | file_summary += f"|{f}|{u:.2f}|{s:.2f}|{w:.2f}|\n" 941 | 942 | check_timings: dict[str, tuple[float, float, float]] = {} 943 | for timings in clang_tidy_profiling.values(): 944 | for check, timing in timings.items(): 945 | if check in [wall_key, user_key, sys_key]: 946 | continue 947 | base_check, time_type = check.rsplit(".", 1) 948 | check_name = base_check.split(".", 2)[2] 949 | t = check_timings.get(check_name, (0.0, 0.0, 0.0)) 950 | if time_type == "user": 951 | t = t[0] + timing, t[1], t[2] 952 | elif time_type == "sys": 953 | t = t[0], t[1] + timing, t[2] 954 | elif time_type == "wall": 955 | t = t[0], t[1], t[2] + timing 956 | check_timings[check_name] = t 957 | 958 | check_summary = "" 959 | if check_timings: 960 | check_summary = textwrap.dedent( 961 | f"""\ 962 | ### Top {top_amount} checks 963 | | Check | user (s) | system (s) | total (s) | 964 | | ----- | -------- | ---------- | --------- | 965 | | Total | {total_user:.2f} | {total_sys:.2f} | {total_wall:.2f} | 966 | """ 967 | ) 968 | topchecks = sorted( 969 | ((check_name, *timings) for check_name, timings in check_timings.items()), 970 | key=lambda x: x[3], 971 | reverse=True, 972 | ) 973 | for c, u, s, w in list(topchecks)[:top_amount]: 974 | c = decorate_check_names(f"[{c}]").replace("[[", "[").rstrip("]") 975 | check_summary += f"|{c}|{u:.2f}|{s:.2f}|{w:.2f}|\n" 976 | 977 | return ( 978 | f"## Timing\nReal time: {real_time.seconds:.2f}\n{file_summary}{check_summary}" 979 | ) 980 | 981 | 982 | def filter_files( 983 | diff: list[unidiff.PatchedFile], include: list[str], exclude: list[str] 984 | ) -> list[str]: 985 | changed_files = [filename.target_file[2:] for filename in diff] 986 | files: list[str] = [] 987 | for pattern in include: 988 | files.extend(fnmatch.filter(changed_files, pattern)) 989 | print(f"include: {pattern}, file list now: {files}") 990 | for pattern in exclude: 991 | files = [f for f in files if not fnmatch.fnmatch(f, pattern)] 992 | print(f"exclude: {pattern}, file list now: {files}") 993 | 994 | return files 995 | 996 | 997 | def create_review( 998 | pull_request: PullRequest, 999 | build_dir: str, 1000 | clang_tidy_checks: str, 1001 | clang_tidy_binary: pathlib.Path, 1002 | config_file: str, 1003 | max_task: int, 1004 | include_context_lines: bool, 1005 | extra_arguments: str, 1006 | include: list[str], 1007 | exclude: list[str], 1008 | ) -> Optional[PRReview]: 1009 | """Given the parameters, runs clang-tidy and creates a review. 1010 | If no files were changed, or no warnings could be found, None will be returned. 1011 | 1012 | """ 1013 | 1014 | diff = pull_request.get_pr_diff() 1015 | print(f"\nDiff from GitHub PR:\n{diff}\n") 1016 | 1017 | files = filter_files(diff, include, exclude) 1018 | 1019 | if files == []: 1020 | with message_group("No files to check!"), REVIEW_FILE.open("w") as review_file: 1021 | json.dump( 1022 | { 1023 | "body": "clang-tidy found no files to check", 1024 | "event": "COMMENT", 1025 | "comments": [], 1026 | }, 1027 | review_file, 1028 | ) 1029 | return None 1030 | 1031 | print(f"Checking these files: {files}", flush=True) 1032 | 1033 | line_ranges = get_line_ranges(diff, files, include_context_lines) 1034 | if line_ranges == "[]": 1035 | with ( 1036 | message_group("No lines added in this PR!"), 1037 | REVIEW_FILE.open("w") as review_file, 1038 | ): 1039 | json.dump( 1040 | { 1041 | "body": "clang-tidy found no lines added", 1042 | "event": "COMMENT", 1043 | "comments": [], 1044 | }, 1045 | review_file, 1046 | ) 1047 | return None 1048 | 1049 | print(f"Line filter for clang-tidy:\n{line_ranges}\n") 1050 | 1051 | username = pull_request.get_pr_author() or "your name here" 1052 | 1053 | # Run clang-tidy with the configured parameters and produce the CLANG_TIDY_FIXES file 1054 | export_fixes_dir = Path(tempfile.mkdtemp()) 1055 | env = dict(os.environ, USER=username) 1056 | config = config_file_or_checks(clang_tidy_binary, clang_tidy_checks, config_file) 1057 | base_invocation = [ 1058 | clang_tidy_binary, 1059 | f"-p={build_dir}", 1060 | f"-line-filter={line_ranges}", 1061 | "--enable-check-profile", 1062 | f"-store-check-profile={PROFILE_DIR}", 1063 | ] 1064 | 1065 | if extra_arguments: 1066 | base_invocation += shlex.split(extra_arguments) 1067 | 1068 | if config: 1069 | print(f"Using config: {config}") 1070 | base_invocation.append(config) 1071 | else: 1072 | print("Using recursive directory config") 1073 | 1074 | if max_task == 0: 1075 | max_task = multiprocessing.cpu_count() 1076 | 1077 | # Don't run on more threads than there are files 1078 | max_task = min(max_task, len(files)) 1079 | 1080 | print(f"Spawning a task queue with {max_task} processes") 1081 | start = datetime.datetime.now() 1082 | try: 1083 | # Spin up a bunch of tidy-launching threads. 1084 | task_queue = queue.Queue(max_task) 1085 | # List of files with a non-zero return code. 1086 | failed_files = [] 1087 | lock = threading.Lock() 1088 | for _ in range(max_task): 1089 | t = threading.Thread( 1090 | target=build_clang_tidy_warnings, 1091 | args=( 1092 | base_invocation, 1093 | env, 1094 | export_fixes_dir, 1095 | task_queue, 1096 | lock, 1097 | failed_files, 1098 | ), 1099 | ) 1100 | t.daemon = True 1101 | t.start() 1102 | 1103 | # Fill the queue with files. 1104 | for name in files: 1105 | task_queue.put(name) 1106 | 1107 | # Wait for all threads to be done. 1108 | task_queue.join() 1109 | 1110 | except KeyboardInterrupt: 1111 | # This is a sad hack. Unfortunately subprocess goes 1112 | # bonkers with ctrl-c and we start forking merrily. 1113 | print("\nCtrl-C detected, goodbye.") 1114 | os.kill(0, 9) 1115 | raise 1116 | real_duration = datetime.datetime.now() - start 1117 | 1118 | # Read and parse the CLANG_TIDY_FIXES file 1119 | print(f"Writing fixes to {FIXES_FILE} ...") 1120 | merge_replacement_files(export_fixes_dir, FIXES_FILE) 1121 | shutil.rmtree(export_fixes_dir) 1122 | clang_tidy_warnings = load_clang_tidy_warnings(FIXES_FILE) 1123 | 1124 | # Read and parse the timing data 1125 | clang_tidy_profiling = load_and_merge_profiling() 1126 | 1127 | try: 1128 | sha = pull_request.head_sha 1129 | except Exception: 1130 | sha = os.environ.get("GITHUB_SHA") 1131 | 1132 | # Post to the action job summary 1133 | step_summary = make_timing_summary(clang_tidy_profiling, real_duration, sha) 1134 | set_summary(step_summary) 1135 | 1136 | print("clang-tidy had the following warnings:\n", clang_tidy_warnings, flush=True) 1137 | 1138 | diff_lookup = make_file_line_lookup(diff) 1139 | offset_lookup = make_file_offset_lookup(files) 1140 | 1141 | with message_group("Creating review from warnings"): 1142 | review = create_review_file( 1143 | clang_tidy_warnings, diff_lookup, offset_lookup, build_dir 1144 | ) 1145 | with REVIEW_FILE.open("w") as review_file: 1146 | json.dump(review, review_file) 1147 | 1148 | return review 1149 | 1150 | 1151 | def download_artifacts( 1152 | pull: PullRequest, workflow_id: int 1153 | ) -> tuple[Optional[Metadata], Optional[PRReview]]: 1154 | """Attempt to automatically download the artifacts from a previous 1155 | run of the review Action""" 1156 | 1157 | # workflow id is an input: ${{github.event.workflow_run.id }} 1158 | workflow: WorkflowRun = pull.repo.get_workflow_run(workflow_id) 1159 | # I don't understand why mypy complains about the next line! 1160 | for artifact in workflow.get_artifacts(): 1161 | if artifact.name == "clang-tidy-review": 1162 | break 1163 | else: 1164 | # Didn't find the artefact, so bail 1165 | print( 1166 | f"Couldn't find 'clang-tidy-review' artifact for workflow '{workflow_id}'. " 1167 | f"Available artifacts are: {list(workflow.get_artifacts())}" 1168 | ) 1169 | return None, None 1170 | 1171 | headers = { 1172 | "Accept": "application/vnd.github+json", 1173 | "Authorization": f"Bearer {pull.token}", 1174 | } 1175 | r = urllib3.request("GET", artifact.archive_download_url, headers=headers) 1176 | if r.status != 200: 1177 | print( 1178 | f"WARNING: Couldn't automatically download artifacts for workflow '{workflow_id}', response was: {r}: {r.reason}" 1179 | ) 1180 | return None, None 1181 | 1182 | data = zipfile.ZipFile(io.BytesIO(r.data)) 1183 | filenames = data.namelist() 1184 | 1185 | metadata = ( 1186 | json.loads(data.read(str(METADATA_FILE))) 1187 | if str(METADATA_FILE) in filenames 1188 | else None 1189 | ) 1190 | review = ( 1191 | json.loads(data.read(str(REVIEW_FILE))) 1192 | if str(REVIEW_FILE) in filenames 1193 | else None 1194 | ) 1195 | return metadata, review 1196 | 1197 | 1198 | def load_metadata() -> Optional[Metadata]: 1199 | """Load metadata from the METADATA_FILE path""" 1200 | 1201 | if not METADATA_FILE.exists(): 1202 | print(f"WARNING: Could not find metadata file ('{METADATA_FILE}')", flush=True) 1203 | return None 1204 | 1205 | with METADATA_FILE.open() as metadata_file: 1206 | return json.load(metadata_file) 1207 | 1208 | 1209 | def save_metadata(pr_number: int) -> None: 1210 | """Save metadata to the METADATA_FILE path""" 1211 | 1212 | metadata: Metadata = {"pr_number": pr_number} 1213 | 1214 | with METADATA_FILE.open("w") as metadata_file: 1215 | json.dump(metadata, metadata_file) 1216 | 1217 | 1218 | def load_review(review_file: pathlib.Path) -> Optional[PRReview]: 1219 | """Load review output""" 1220 | 1221 | if not review_file.exists(): 1222 | print(f"WARNING: Could not find review file ('{review_file}')", flush=True) 1223 | return None 1224 | 1225 | with review_file.open() as review_file_handle: 1226 | payload = json.load(review_file_handle) 1227 | return payload or None 1228 | 1229 | 1230 | def load_and_merge_profiling() -> dict[str, dict[str, float]]: 1231 | result = {} 1232 | for profile_file in PROFILE_DIR.glob("*.json"): 1233 | profile_dict = json.load(profile_file.open()) 1234 | filename = profile_dict["file"] 1235 | current_profile = result.get(filename, {}) 1236 | for check, timing in profile_dict["profile"].items(): 1237 | current_profile[check] = current_profile.get(check, 0.0) + timing 1238 | result[filename] = current_profile 1239 | for filename, timings in list(result.items()): 1240 | timings["time.clang-tidy.total.wall"] = sum( 1241 | v for k, v in timings.items() if k.endswith("wall") 1242 | ) 1243 | timings["time.clang-tidy.total.user"] = sum( 1244 | v for k, v in timings.items() if k.endswith("user") 1245 | ) 1246 | timings["time.clang-tidy.total.sys"] = sum( 1247 | v for k, v in timings.items() if k.endswith("sys") 1248 | ) 1249 | result[filename] = timings 1250 | return result 1251 | 1252 | 1253 | def load_and_merge_reviews(review_files: list[pathlib.Path]) -> Optional[PRReview]: 1254 | reviews: list[PRReview] = [] 1255 | for file in review_files: 1256 | review = load_review(file) 1257 | if review is not None and len(review.get("comments", [])) > 0: 1258 | reviews.append(review) 1259 | 1260 | if not reviews: 1261 | return None 1262 | 1263 | result = reviews[0] 1264 | 1265 | comments: set[HashableComment] = set() 1266 | for review in reviews: 1267 | comments.update(HashableComment(**c) for c in review["comments"]) 1268 | 1269 | result["comments"] = [c.__dict__ for c in sorted(comments)] 1270 | 1271 | return result 1272 | 1273 | 1274 | def get_line_ranges( 1275 | diff: list[unidiff.PatchedFile], files: list[str], include_context_lines: bool 1276 | ) -> str: 1277 | """Return the line ranges of added lines in diff, suitable for the 1278 | line-filter argument of clang-tidy. 1279 | 1280 | Optionally also include context lines (3 above and 3 below) added and 1281 | removed lines which are commentable on Github. 1282 | """ 1283 | 1284 | lines_by_file: dict[str, list[int]] = {} 1285 | for filename in diff: 1286 | if filename.target_file[2:] not in files: 1287 | continue 1288 | commentable_line_numbers: list[int] = [] 1289 | 1290 | for hunk in filename: 1291 | for line in hunk: 1292 | if line.is_added or (line.is_context and include_context_lines): 1293 | commentable_line_numbers.append(line.target_line_no) 1294 | 1295 | for _, group in itertools.groupby( 1296 | enumerate(commentable_line_numbers), lambda ix: ix[0] - ix[1] 1297 | ): 1298 | groups: list[int] = list(map(itemgetter(1), group)) 1299 | lines_by_file.setdefault(filename.target_file[2:], []).append( 1300 | [groups[0], groups[-1]] 1301 | ) 1302 | 1303 | line_filter_json: list[dict[str, Any]] = [] 1304 | for name, lines in lines_by_file.items(): 1305 | line_filter_json.append({"name": name, "lines": lines}) 1306 | # On windows, unidiff has forward slashes but cl.exe expects backslashes. 1307 | # However, clang.exe on windows expects forward slashes. 1308 | # Adding a copy of the line filters with backslashes allows for both cl.exe and clang.exe to work. 1309 | if os.path.sep == "\\": 1310 | # Converts name to backslashes for the cl.exe line filter using `WindowsPath.__str__` 1311 | name = str(Path(name)) 1312 | line_filter_json.append({"name": name, "lines": lines}) 1313 | return json.dumps(line_filter_json, separators=(",", ":")) 1314 | 1315 | 1316 | def cull_comments( 1317 | pull_request: PullRequest, review: PRReview, max_comments: int 1318 | ) -> PRReview: 1319 | """Remove comments from review that have already been posted, and keep 1320 | only the first max_comments 1321 | 1322 | """ 1323 | 1324 | unposted_comments = {HashableComment(**c) for c in review["comments"]} 1325 | posted_comments = { 1326 | HashableComment(c.body, c.line or c.original_line, c.path, c.side) 1327 | for c in pull_request.get_pr_comments() 1328 | } 1329 | 1330 | review["comments"] = [ 1331 | c.__dict__ for c in sorted(unposted_comments - posted_comments) 1332 | ] 1333 | 1334 | if len(review["comments"]) > max_comments: 1335 | review["body"] += ( 1336 | "\n\nThere were too many comments to post at once. " 1337 | f"Showing the first {max_comments} out of {len(review['comments'])}. " 1338 | "Check the log or trigger a new build to see more." 1339 | ) 1340 | review["comments"] = review["comments"][:max_comments] 1341 | 1342 | return review 1343 | 1344 | 1345 | def strip_enclosing_quotes(string: str) -> str: 1346 | """Strip leading/trailing whitespace and remove any enclosing quotes""" 1347 | stripped = string.strip() 1348 | 1349 | # Need to check double quotes again in case they're nested inside 1350 | # single quotes 1351 | for quote in ['"', "'", '"']: 1352 | if stripped.startswith(quote) and stripped.endswith(quote): 1353 | stripped = stripped[1:-1] 1354 | return stripped 1355 | 1356 | 1357 | def set_output(key: str, val: str) -> bool: 1358 | if "GITHUB_OUTPUT" not in os.environ: 1359 | return False 1360 | 1361 | # append key-val pair to file 1362 | with Path(os.environ["GITHUB_OUTPUT"]).open("a") as f: 1363 | f.write(f"{key}={val}\n") 1364 | 1365 | return True 1366 | 1367 | 1368 | def set_summary(val: str) -> bool: 1369 | if "GITHUB_STEP_SUMMARY" not in os.environ: 1370 | return False 1371 | 1372 | # append key-val pair to file 1373 | with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a") as f: 1374 | f.write(val) 1375 | 1376 | return True 1377 | 1378 | 1379 | def decorate_check_names(comment: str) -> str: 1380 | """ 1381 | Split on first dash into two groups of string in [] at end of line 1382 | exception: if the first group starts with 'clang' such as 'clang-diagnostic-error' 1383 | exception to the exception: if the string starts with 'clang-analyzer', in which case, make it the first group 1384 | """ 1385 | regex = r"\[(((?:clang-analyzer)|(?:(?!clang)[\w]+))-([\.\w-]+))\]$" 1386 | 1387 | def repl(m: re.Match[str]) -> str: 1388 | match m.group(2): 1389 | case "clazy": 1390 | url = f"https://invent.kde.org/sdk/clazy/-/blob/master/docs/checks/README-{m.group(3)}.md" 1391 | case other: 1392 | url = f"https://clang.llvm.org/extra/clang-tidy/checks/{other}/{m.group(3)}.html" 1393 | return f"[[{m.group(1)}]({url})]" 1394 | 1395 | return re.sub(regex, repl, comment, count=1, flags=re.MULTILINE) 1396 | 1397 | 1398 | def decorate_comment(comment: ReviewComment) -> ReviewComment: 1399 | comment["body"] = decorate_check_names(comment["body"]) 1400 | return comment 1401 | 1402 | 1403 | def decorate_comments(review: PRReview) -> PRReview: 1404 | review["comments"] = list(map(decorate_comment, review["comments"])) 1405 | return review 1406 | 1407 | 1408 | def post_review( 1409 | pull_request: PullRequest, 1410 | review: Optional[PRReview], 1411 | max_comments: int, 1412 | lgtm_comment_body: str, 1413 | dry_run: bool, 1414 | ) -> int: 1415 | print( 1416 | "Created the following review:\n", pprint.pformat(review, width=130), flush=True 1417 | ) 1418 | 1419 | if not review or review["comments"] == []: 1420 | print("No warnings to report, LGTM!") 1421 | if not dry_run: 1422 | pull_request.post_lgtm_comment(lgtm_comment_body) 1423 | return 0 1424 | 1425 | total_comments = len(review["comments"]) 1426 | 1427 | set_output("total_comments", str(total_comments)) 1428 | 1429 | decorated_review = decorate_comments(review) 1430 | 1431 | print("Removing already posted or extra comments", flush=True) 1432 | trimmed_review = cull_comments(pull_request, decorated_review, max_comments) 1433 | 1434 | if not trimmed_review["comments"]: 1435 | print("Everything already posted!") 1436 | return total_comments 1437 | 1438 | if dry_run: 1439 | pprint.pprint(review, width=130) 1440 | return total_comments 1441 | 1442 | print("Posting the review:\n", pprint.pformat(trimmed_review), flush=True) 1443 | pull_request.post_review(trimmed_review) 1444 | 1445 | return total_comments 1446 | 1447 | 1448 | def convert_comment_to_annotations(comment: ReviewComment) -> dict[str, Any]: 1449 | return { 1450 | "path": comment["path"], 1451 | "start_line": comment.get("start_line", comment["line"]), 1452 | "end_line": comment["line"], 1453 | "annotation_level": "warning", 1454 | "title": "clang-tidy", 1455 | "message": comment["body"], 1456 | } 1457 | 1458 | 1459 | def post_annotations( 1460 | pull_request: PullRequest, review: Optional[PRReview] 1461 | ) -> Optional[int]: 1462 | """Post the first 10 comments in the review as annotations""" 1463 | 1464 | body: dict[str, Any] = { 1465 | "name": "clang-tidy-review", 1466 | "head_sha": pull_request.pull_request.head.sha, 1467 | "status": "completed", 1468 | "conclusion": "success", 1469 | } 1470 | 1471 | if review is None: 1472 | return None 1473 | 1474 | if review["comments"] == []: 1475 | print("No warnings to report, LGTM!") 1476 | pull_request.post_annotations(body) 1477 | 1478 | comments: list[str] = [] 1479 | for comment in review["comments"]: 1480 | first_line = comment["body"].splitlines()[0] 1481 | comments.append( 1482 | f"{comment['path']}:{comment.get('start_line', comment.get('line', 0))}: {first_line}" 1483 | ) 1484 | 1485 | total_comments = len(review["comments"]) 1486 | 1487 | body["conclusion"] = "neutral" 1488 | body["output"] = { 1489 | "title": "clang-tidy-review", 1490 | "summary": f"There were {total_comments} warnings", 1491 | "text": "\n".join(comments), 1492 | "annotations": [ 1493 | convert_comment_to_annotations(comment) 1494 | for comment in review["comments"][:MAX_ANNOTATIONS] 1495 | ], 1496 | } 1497 | 1498 | pull_request.post_annotations(body) 1499 | return total_comments 1500 | 1501 | 1502 | def bool_argument(user_input: str) -> bool: 1503 | """Convert text to bool""" 1504 | user_input = str(user_input).upper() 1505 | if user_input == "TRUE": 1506 | return True 1507 | if user_input == "FALSE": 1508 | return False 1509 | raise ValueError("Invalid value passed to bool_argument") 1510 | -------------------------------------------------------------------------------- /tests/src/clang-tidy-profile/20240521223715110927964-hello.cxx.json: -------------------------------------------------------------------------------- 1 | { 2 | "file": "hello.cxx", 3 | "timestamp": "2024-05-21 22:37:15.110927964", 4 | "profile": { 5 | "time.clang-tidy.cppcoreguidelines-virtual-class-destructor.wall": 7.0521831512451172e-03, 6 | "time.clang-tidy.cppcoreguidelines-virtual-class-destructor.user": 4.2479999999960327e-03, 7 | "time.clang-tidy.cppcoreguidelines-virtual-class-destructor.sys": 2.8169999999996254e-03, 8 | "time.clang-tidy.cppcoreguidelines-pro-type-reinterpret-cast.wall": 4.5537948608398438e-05, 9 | "time.clang-tidy.cppcoreguidelines-pro-type-reinterpret-cast.user": 4.0999999999957737e-05, 10 | "time.clang-tidy.cppcoreguidelines-pro-type-reinterpret-cast.sys": 4.0000000001150227e-06, 11 | "time.clang-tidy.hicpp-vararg.wall": 9.9234580993652344e-03, 12 | "time.clang-tidy.hicpp-vararg.user": 5.8040000000039171e-03, 13 | "time.clang-tidy.hicpp-vararg.sys": 4.0180000000080707e-03, 14 | "time.clang-tidy.google-readability-namespace-comments.wall": 7.7388286590576172e-03, 15 | "time.clang-tidy.google-readability-namespace-comments.user": 5.3390000000002047e-03, 16 | "time.clang-tidy.google-readability-namespace-comments.sys": 2.3929999999994234e-03, 17 | "time.clang-tidy.cppcoreguidelines-noexcept-move-operations.wall": 3.3085346221923828e-03, 18 | "time.clang-tidy.cppcoreguidelines-noexcept-move-operations.user": 1.9319999999907189e-03, 19 | "time.clang-tidy.cppcoreguidelines-noexcept-move-operations.sys": 1.4070000000003802e-03, 20 | "time.clang-tidy.cert-oop57-cpp.wall": 5.1546096801757812e-03, 21 | "time.clang-tidy.cert-oop57-cpp.user": 3.2830000000032555e-03, 22 | "time.clang-tidy.cert-oop57-cpp.sys": 1.8459999999957954e-03, 23 | "time.clang-tidy.performance-no-automatic-move.wall": 1.6140937805175781e-03, 24 | "time.clang-tidy.performance-no-automatic-move.user": 1.0000000000007780e-03, 25 | "time.clang-tidy.performance-no-automatic-move.sys": 6.0700000000202259e-04, 26 | "time.clang-tidy.hicpp-avoid-c-arrays.wall": 4.0050029754638672e-02, 27 | "time.clang-tidy.hicpp-avoid-c-arrays.user": 2.2819999999994067e-02, 28 | "time.clang-tidy.hicpp-avoid-c-arrays.sys": 1.7392999999996661e-02, 29 | "time.clang-tidy.android-cloexec-pipe2.wall": 3.2284259796142578e-03, 30 | "time.clang-tidy.android-cloexec-pipe2.user": 2.0420000000034300e-03, 31 | "time.clang-tidy.android-cloexec-pipe2.sys": 1.1790000000020395e-03, 32 | "time.clang-tidy.fuchsia-header-anon-namespaces.wall": 1.5044212341308594e-04, 33 | "time.clang-tidy.fuchsia-header-anon-namespaces.user": 1.0499999999957765e-04, 34 | "time.clang-tidy.fuchsia-header-anon-namespaces.sys": 3.7000000000064759e-05, 35 | "time.clang-tidy.hicpp-noexcept-move.wall": 2.8979778289794922e-03, 36 | "time.clang-tidy.hicpp-noexcept-move.user": 1.6849999999961618e-03, 37 | "time.clang-tidy.hicpp-noexcept-move.sys": 1.2029999999962904e-03, 38 | "time.clang-tidy.abseil-duration-division.wall": 4.1122436523437500e-03, 39 | "time.clang-tidy.abseil-duration-division.user": 2.3809999999921949e-03, 40 | "time.clang-tidy.abseil-duration-division.sys": 1.6610000000010228e-03, 41 | "time.clang-tidy.hicpp-deprecated-headers.wall": 1.1411428451538086e-02, 42 | "time.clang-tidy.hicpp-deprecated-headers.user": 1.1240999999999612e-02, 43 | "time.clang-tidy.hicpp-deprecated-headers.sys": 1.4399999999992197e-04, 44 | "time.clang-tidy.performance-trivially-destructible.wall": 1.5735626220703125e-04, 45 | "time.clang-tidy.performance-trivially-destructible.user": 9.3999999999816453e-05, 46 | "time.clang-tidy.performance-trivially-destructible.sys": 7.8000000001354763e-05, 47 | "time.clang-tidy.google-global-names-in-headers.wall": 1.9238233566284180e-02, 48 | "time.clang-tidy.google-global-names-in-headers.user": 1.0919000000013668e-02, 49 | "time.clang-tidy.google-global-names-in-headers.sys": 8.1479999999949371e-03, 50 | "time.clang-tidy.readability-inconsistent-declaration-parameter-name.wall": 4.4579505920410156e-03, 51 | "time.clang-tidy.readability-inconsistent-declaration-parameter-name.user": 2.4429999999968643e-03, 52 | "time.clang-tidy.readability-inconsistent-declaration-parameter-name.sys": 1.9489999999977581e-03, 53 | "time.clang-tidy.modernize-replace-random-shuffle.wall": 4.6191215515136719e-03, 54 | "time.clang-tidy.modernize-replace-random-shuffle.user": 2.9509999999870473e-03, 55 | "time.clang-tidy.modernize-replace-random-shuffle.sys": 1.6490000000037863e-03, 56 | "time.clang-tidy.cert-dcl59-cpp.wall": 9.9182128906250000e-05, 57 | "time.clang-tidy.cert-dcl59-cpp.user": 6.8999999999874717e-05, 58 | "time.clang-tidy.cert-dcl59-cpp.sys": 2.3999999999801958e-05, 59 | "time.clang-tidy.zircon-temporary-objects.wall": 3.2949447631835938e-04, 60 | "time.clang-tidy.zircon-temporary-objects.user": 2.1799999999894126e-04, 61 | "time.clang-tidy.zircon-temporary-objects.sys": 1.0700000000007925e-04, 62 | "time.clang-tidy.readability-redundant-access-specifiers.wall": 4.0946006774902344e-03, 63 | "time.clang-tidy.readability-redundant-access-specifiers.user": 2.4959999999971672e-03, 64 | "time.clang-tidy.readability-redundant-access-specifiers.sys": 1.6299999999993542e-03, 65 | "time.clang-tidy.cert-pos47-c.wall": 3.8197040557861328e-03, 66 | "time.clang-tidy.cert-pos47-c.user": 2.4260000000002613e-03, 67 | "time.clang-tidy.cert-pos47-c.sys": 1.3980000000046733e-03, 68 | "time.clang-tidy.hicpp-uppercase-literal-suffix.wall": 4.3672084808349609e-02, 69 | "time.clang-tidy.hicpp-uppercase-literal-suffix.user": 2.6537999999982187e-02, 70 | "time.clang-tidy.hicpp-uppercase-literal-suffix.sys": 1.7105000000003923e-02, 71 | "time.clang-tidy.performance-inefficient-string-concatenation.wall": 1.0006427764892578e-03, 72 | "time.clang-tidy.performance-inefficient-string-concatenation.user": 6.5399999999860015e-04, 73 | "time.clang-tidy.performance-inefficient-string-concatenation.sys": 3.3699999999958763e-04, 74 | "time.clang-tidy.modernize-make-shared.wall": 8.0657005310058594e-04, 75 | "time.clang-tidy.modernize-make-shared.user": 4.9899999999869493e-04, 76 | "time.clang-tidy.modernize-make-shared.sys": 3.0399999999874971e-04, 77 | "time.clang-tidy.cert-msc30-c.wall": 3.1509399414062500e-03, 78 | "time.clang-tidy.cert-msc30-c.user": 2.0229999999989978e-03, 79 | "time.clang-tidy.cert-msc30-c.sys": 1.1199999999997878e-03, 80 | "time.clang-tidy.bugprone-sizeof-container.wall": 3.7097930908203125e-04, 81 | "time.clang-tidy.bugprone-sizeof-container.user": 2.1699999999791331e-04, 82 | "time.clang-tidy.bugprone-sizeof-container.sys": 1.6500000000041481e-04, 83 | "time.clang-tidy.boost-use-to-string.wall": 3.3724308013916016e-03, 84 | "time.clang-tidy.boost-use-to-string.user": 2.1240000000077863e-03, 85 | "time.clang-tidy.boost-use-to-string.sys": 1.2290000000014789e-03, 86 | "time.clang-tidy.cert-exp42-c.wall": 3.5216808319091797e-03, 87 | "time.clang-tidy.cert-exp42-c.user": 2.2400000000017961e-03, 88 | "time.clang-tidy.cert-exp42-c.sys": 1.2760000000020533e-03, 89 | "time.clang-tidy.readability-const-return-type.wall": 3.9372444152832031e-03, 90 | "time.clang-tidy.readability-const-return-type.user": 2.2400000000044606e-03, 91 | "time.clang-tidy.readability-const-return-type.sys": 1.6650000000018039e-03, 92 | "time.clang-tidy.abseil-string-find-startswith.wall": 2.1224021911621094e-03, 93 | "time.clang-tidy.abseil-string-find-startswith.user": 1.2660000000086491e-03, 94 | "time.clang-tidy.abseil-string-find-startswith.sys": 8.6399999999731136e-04, 95 | "time.clang-tidy.google-upgrade-googletest-case.wall": 7.2291135787963867e-02, 96 | "time.clang-tidy.google-upgrade-googletest-case.user": 4.1725000000016443e-02, 97 | "time.clang-tidy.google-upgrade-googletest-case.sys": 3.0206000000001954e-02, 98 | "time.clang-tidy.modernize-pass-by-value.wall": 7.2646141052246094e-04, 99 | "time.clang-tidy.modernize-pass-by-value.user": 4.1800000000069559e-04, 100 | "time.clang-tidy.modernize-pass-by-value.sys": 3.0899999999967065e-04, 101 | "time.clang-tidy.readability-avoid-const-params-in-decls.wall": 5.6788921356201172e-03, 102 | "time.clang-tidy.readability-avoid-const-params-in-decls.user": 3.2590000000030095e-03, 103 | "time.clang-tidy.readability-avoid-const-params-in-decls.sys": 2.5280000000014180e-03, 104 | "time.clang-tidy.abseil-no-namespace.wall": 1.3303756713867188e-04, 105 | "time.clang-tidy.abseil-no-namespace.user": 8.5000000000778897e-05, 106 | "time.clang-tidy.abseil-no-namespace.sys": 4.0000000000040004e-05, 107 | "time.clang-tidy.cert-oop58-cpp.wall": 4.0805339813232422e-03, 108 | "time.clang-tidy.cert-oop58-cpp.user": 2.2739999999989990e-03, 109 | "time.clang-tidy.cert-oop58-cpp.sys": 1.7540000000055844e-03, 110 | "time.clang-tidy.cppcoreguidelines-c-copy-assignment-signature.wall": 8.6638927459716797e-03, 111 | "time.clang-tidy.cppcoreguidelines-c-copy-assignment-signature.user": 4.9659999999898119e-03, 112 | "time.clang-tidy.cppcoreguidelines-c-copy-assignment-signature.sys": 3.6759999999937953e-03, 113 | "time.clang-tidy.cert-oop54-cpp.wall": 3.2560825347900391e-03, 114 | "time.clang-tidy.cert-oop54-cpp.user": 1.7519999999970892e-03, 115 | "time.clang-tidy.cert-oop54-cpp.sys": 1.4360000000031015e-03, 116 | "time.clang-tidy.modernize-use-bool-literals.wall": 9.4058513641357422e-03, 117 | "time.clang-tidy.modernize-use-bool-literals.user": 5.1390000000224312e-03, 118 | "time.clang-tidy.modernize-use-bool-literals.sys": 4.3770000000009635e-03, 119 | "time.clang-tidy.misc-redundant-expression.wall": 1.4100313186645508e-02, 120 | "time.clang-tidy.misc-redundant-expression.user": 8.4430000000184080e-03, 121 | "time.clang-tidy.misc-redundant-expression.sys": 5.7490000000033348e-03, 122 | "time.clang-tidy.performance-noexcept-swap.wall": 4.1360855102539062e-03, 123 | "time.clang-tidy.performance-noexcept-swap.user": 2.3660000000091941e-03, 124 | "time.clang-tidy.performance-noexcept-swap.sys": 1.7959999999990206e-03, 125 | "time.clang-tidy.abseil-no-internal-dependencies.wall": 3.5114288330078125e-03, 126 | "time.clang-tidy.abseil-no-internal-dependencies.user": 2.2000000000073072e-03, 127 | "time.clang-tidy.abseil-no-internal-dependencies.sys": 1.2449999999988304e-03, 128 | "time.clang-tidy.modernize-deprecated-headers.wall": 8.0726146697998047e-03, 129 | "time.clang-tidy.modernize-deprecated-headers.user": 7.9720000000000901e-03, 130 | "time.clang-tidy.modernize-deprecated-headers.sys": 9.9999999991773336e-07, 131 | "time.clang-tidy.bugprone-suspicious-realloc-usage.wall": 1.6269683837890625e-03, 132 | "time.clang-tidy.bugprone-suspicious-realloc-usage.user": 9.2499999999562377e-04, 133 | "time.clang-tidy.bugprone-suspicious-realloc-usage.sys": 6.2299999999937405e-04, 134 | "time.clang-tidy.hicpp-move-const-arg.wall": 3.3003091812133789e-02, 135 | "time.clang-tidy.hicpp-move-const-arg.user": 2.0145999999976016e-02, 136 | "time.clang-tidy.hicpp-move-const-arg.sys": 1.2732999999986561e-02, 137 | "time.clang-tidy.abseil-redundant-strcat-calls.wall": 3.9427280426025391e-03, 138 | "time.clang-tidy.abseil-redundant-strcat-calls.user": 2.4979999999974467e-03, 139 | "time.clang-tidy.abseil-redundant-strcat-calls.sys": 1.4180000000010295e-03, 140 | "time.clang-tidy.modernize-avoid-bind.wall": 3.5469532012939453e-03, 141 | "time.clang-tidy.modernize-avoid-bind.user": 2.2519999999945917e-03, 142 | "time.clang-tidy.modernize-avoid-bind.sys": 1.2970000000018800e-03, 143 | "time.clang-tidy.cert-dcl16-c.wall": 3.3996105194091797e-02, 144 | "time.clang-tidy.cert-dcl16-c.user": 2.1805000000037822e-02, 145 | "time.clang-tidy.cert-dcl16-c.sys": 1.2153999999993781e-02, 146 | "time.clang-tidy.android-cloexec-accept.wall": 3.2320022583007812e-03, 147 | "time.clang-tidy.android-cloexec-accept.user": 2.0449999999958557e-03, 148 | "time.clang-tidy.android-cloexec-accept.sys": 1.1340000000010786e-03, 149 | "time.clang-tidy.performance-inefficient-vector-operation.wall": 8.1777572631835938e-05, 150 | "time.clang-tidy.performance-inefficient-vector-operation.user": 5.8000000000557606e-05, 151 | "time.clang-tidy.performance-inefficient-vector-operation.sys": 1.8999999999991246e-05, 152 | "time.clang-tidy.llvm-else-after-return.wall": 7.2233676910400391e-03, 153 | "time.clang-tidy.llvm-else-after-return.user": 4.7029999999943506e-03, 154 | "time.clang-tidy.llvm-else-after-return.sys": 2.5580000000007264e-03, 155 | "time.clang-tidy.hicpp-member-init.wall": 1.1773824691772461e-02, 156 | "time.clang-tidy.hicpp-member-init.user": 6.9369999999828735e-03, 157 | "time.clang-tidy.hicpp-member-init.sys": 4.7520000000025320e-03, 158 | "time.clang-tidy.cert-flp30-c.wall": 5.3167343139648438e-05, 159 | "time.clang-tidy.cert-flp30-c.user": 4.4000000000377071e-05, 160 | "time.clang-tidy.cert-flp30-c.sys": 1.6000000000016001e-05, 161 | "time.clang-tidy.llvmlibc-inline-function-decl.wall": 1.3705730438232422e-02, 162 | "time.clang-tidy.llvmlibc-inline-function-decl.user": 7.8970000000029295e-03, 163 | "time.clang-tidy.llvmlibc-inline-function-decl.sys": 5.8870000000017519e-03, 164 | "time.clang-tidy.misc-uniqueptr-reset-release.wall": 3.1828880310058594e-04, 165 | "time.clang-tidy.misc-uniqueptr-reset-release.user": 1.9300000000255224e-04, 166 | "time.clang-tidy.misc-uniqueptr-reset-release.sys": 1.2000000000034206e-04, 167 | "time.clang-tidy.fuchsia-trailing-return.wall": 3.7443637847900391e-03, 168 | "time.clang-tidy.fuchsia-trailing-return.user": 2.0960000000065371e-03, 169 | "time.clang-tidy.fuchsia-trailing-return.sys": 1.6159999999987296e-03, 170 | "time.clang-tidy.android-cloexec-socket.wall": 3.5159587860107422e-03, 171 | "time.clang-tidy.android-cloexec-socket.user": 2.2699999999984399e-03, 172 | "time.clang-tidy.android-cloexec-socket.sys": 1.2510000000045540e-03, 173 | "time.clang-tidy.fuchsia-statically-constructed-objects.wall": 8.2118511199951172e-03, 174 | "time.clang-tidy.fuchsia-statically-constructed-objects.user": 4.6340000000069104e-03, 175 | "time.clang-tidy.fuchsia-statically-constructed-objects.sys": 3.5259999999950331e-03, 176 | "time.clang-tidy.readability-misplaced-array-index.wall": 2.0718574523925781e-04, 177 | "time.clang-tidy.readability-misplaced-array-index.user": 1.1599999999889476e-04, 178 | "time.clang-tidy.readability-misplaced-array-index.sys": 9.3999999999816453e-05, 179 | "time.clang-tidy.modernize-deprecated-ios-base-aliases.wall": 3.8014411926269531e-02, 180 | "time.clang-tidy.modernize-deprecated-ios-base-aliases.user": 2.1541999999987294e-02, 181 | "time.clang-tidy.modernize-deprecated-ios-base-aliases.sys": 1.6469000000004508e-02, 182 | "time.clang-tidy.cert-con36-c.wall": 3.2949447631835938e-03, 183 | "time.clang-tidy.cert-con36-c.user": 2.2180000000004974e-03, 184 | "time.clang-tidy.cert-con36-c.sys": 1.1109999999991960e-03, 185 | "time.clang-tidy.google-runtime-operator.wall": 5.4092407226562500e-03, 186 | "time.clang-tidy.google-runtime-operator.user": 2.9900000000053772e-03, 187 | "time.clang-tidy.google-runtime-operator.sys": 2.3159999999997627e-03, 188 | "time.clang-tidy.misc-no-recursion.wall": 8.7034702301025391e-03, 189 | "time.clang-tidy.misc-no-recursion.user": 8.6709999999996512e-03, 190 | "time.clang-tidy.misc-no-recursion.sys": 0.0000000000000000e+00, 191 | "time.clang-tidy.cppcoreguidelines-pro-bounds-array-to-pointer-decay.wall": 1.8010854721069336e-02, 192 | "time.clang-tidy.cppcoreguidelines-pro-bounds-array-to-pointer-decay.user": 1.0702000000000655e-02, 193 | "time.clang-tidy.cppcoreguidelines-pro-bounds-array-to-pointer-decay.sys": 7.3830000000019158e-03, 194 | "time.clang-tidy.bugprone-switch-missing-default-case.wall": 4.2915344238281250e-06, 195 | "time.clang-tidy.bugprone-switch-missing-default-case.user": 5.0000000002548006e-06, 196 | "time.clang-tidy.bugprone-switch-missing-default-case.sys": 0.0000000000000000e+00, 197 | "time.clang-tidy.android-cloexec-pipe.wall": 3.1669139862060547e-03, 198 | "time.clang-tidy.android-cloexec-pipe.user": 2.0130000000020409e-03, 199 | "time.clang-tidy.android-cloexec-pipe.sys": 1.1639999999981665e-03, 200 | "time.clang-tidy.bugprone-bool-pointer-implicit-conversion.wall": 2.1967887878417969e-03, 201 | "time.clang-tidy.bugprone-bool-pointer-implicit-conversion.user": 1.4419999999999433e-03, 202 | "time.clang-tidy.bugprone-bool-pointer-implicit-conversion.sys": 7.5500000000028322e-04, 203 | "time.clang-tidy.altera-id-dependent-backward-branch.wall": 1.3964343070983887e-01, 204 | "time.clang-tidy.altera-id-dependent-backward-branch.user": 8.6064999999990732e-02, 205 | "time.clang-tidy.altera-id-dependent-backward-branch.sys": 5.4357000000012867e-02, 206 | "time.clang-tidy.cppcoreguidelines-non-private-member-variables-in-classes.wall": 4.5387744903564453e-03, 207 | "time.clang-tidy.cppcoreguidelines-non-private-member-variables-in-classes.user": 2.9289999999964067e-03, 208 | "time.clang-tidy.cppcoreguidelines-non-private-member-variables-in-classes.sys": 1.6259999999992392e-03, 209 | "time.clang-tidy.modernize-use-using.wall": 1.3533592224121094e-02, 210 | "time.clang-tidy.modernize-use-using.user": 7.9959999999967835e-03, 211 | "time.clang-tidy.modernize-use-using.sys": 5.5910000000019000e-03, 212 | "time.clang-tidy.abseil-duration-comparison.wall": 1.9152164459228516e-03, 213 | "time.clang-tidy.abseil-duration-comparison.user": 1.0889999999945665e-03, 214 | "time.clang-tidy.abseil-duration-comparison.sys": 7.9999999999791349e-04, 215 | "time.clang-tidy.bugprone-throw-keyword-missing.wall": 7.2860717773437500e-04, 216 | "time.clang-tidy.bugprone-throw-keyword-missing.user": 3.5899999999955412e-04, 217 | "time.clang-tidy.bugprone-throw-keyword-missing.sys": 1.3700000000049783e-04, 218 | "time.clang-tidy.cppcoreguidelines-avoid-do-while.wall": 1.6212463378906250e-04, 219 | "time.clang-tidy.cppcoreguidelines-avoid-do-while.user": 8.4999999999446629e-05, 220 | "time.clang-tidy.cppcoreguidelines-avoid-do-while.sys": 8.0999999999775696e-05, 221 | "time.clang-tidy.android-cloexec-epoll-create1.wall": 3.7992000579833984e-03, 222 | "time.clang-tidy.android-cloexec-epoll-create1.user": 2.3329999999925910e-03, 223 | "time.clang-tidy.android-cloexec-epoll-create1.sys": 1.3110000000016164e-03, 224 | "time.clang-tidy.bugprone-suspicious-memset-usage.wall": 5.8763027191162109e-03, 225 | "time.clang-tidy.bugprone-suspicious-memset-usage.user": 3.7640000000109808e-03, 226 | "time.clang-tidy.bugprone-suspicious-memset-usage.sys": 2.0870000000088318e-03, 227 | "time.clang-tidy.cert-dcl37-c.wall": 1.6343545913696289e-01, 228 | "time.clang-tidy.cert-dcl37-c.user": 1.5972499999999989e-01, 229 | "time.clang-tidy.cert-dcl37-c.sys": 3.3669999999998979e-03, 230 | "time.clang-tidy.cert-msc24-c.wall": 6.4532756805419922e-03, 231 | "time.clang-tidy.cert-msc24-c.user": 3.9180000000049731e-03, 232 | "time.clang-tidy.cert-msc24-c.sys": 2.4689999999967238e-03, 233 | "time.clang-tidy.bugprone-redundant-branch-condition.wall": 9.5343589782714844e-04, 234 | "time.clang-tidy.bugprone-redundant-branch-condition.user": 6.5400000000348513e-04, 235 | "time.clang-tidy.bugprone-redundant-branch-condition.sys": 3.2300000000273776e-04, 236 | "time.clang-tidy.hicpp-use-equals-default.wall": 5.9161186218261719e-03, 237 | "time.clang-tidy.hicpp-use-equals-default.user": 3.5569999999998103e-03, 238 | "time.clang-tidy.hicpp-use-equals-default.sys": 2.3500000000009624e-03, 239 | "time.clang-tidy.cert-err60-cpp.wall": 6.8664550781250000e-05, 240 | "time.clang-tidy.cert-err60-cpp.user": 4.5000000000516849e-05, 241 | "time.clang-tidy.cert-err60-cpp.sys": 1.9999999999464890e-05, 242 | "time.clang-tidy.modernize-use-uncaught-exceptions.wall": 1.0484218597412109e-02, 243 | "time.clang-tidy.modernize-use-uncaught-exceptions.user": 6.5999999999903913e-03, 244 | "time.clang-tidy.modernize-use-uncaught-exceptions.sys": 3.9269999999991256e-03, 245 | "time.clang-tidy.readability-redundant-smartptr-get.wall": 6.5665245056152344e-03, 246 | "time.clang-tidy.readability-redundant-smartptr-get.user": 3.9570000000179739e-03, 247 | "time.clang-tidy.readability-redundant-smartptr-get.sys": 2.4280000000032054e-03, 248 | "time.clang-tidy.hicpp-undelegated-constructor.wall": 3.2951831817626953e-03, 249 | "time.clang-tidy.hicpp-undelegated-constructor.user": 2.0119999999987925e-03, 250 | "time.clang-tidy.hicpp-undelegated-constructor.sys": 1.2199999999993327e-03, 251 | "time.clang-tidy.cert-err52-cpp.wall": 3.3173561096191406e-03, 252 | "time.clang-tidy.cert-err52-cpp.user": 2.1019999999967176e-03, 253 | "time.clang-tidy.cert-err52-cpp.sys": 1.1650000000007488e-03, 254 | "time.clang-tidy.misc-new-delete-overloads.wall": 5.1236152648925781e-03, 255 | "time.clang-tidy.misc-new-delete-overloads.user": 2.9099999999964155e-03, 256 | "time.clang-tidy.misc-new-delete-overloads.sys": 2.1950000000001690e-03, 257 | "time.clang-tidy.bugprone-move-forwarding-reference.wall": 4.7557353973388672e-03, 258 | "time.clang-tidy.bugprone-move-forwarding-reference.user": 3.0429999999976864e-03, 259 | "time.clang-tidy.bugprone-move-forwarding-reference.sys": 1.7579999999994822e-03, 260 | "time.clang-tidy.google-readability-braces-around-statements.wall": 1.1115789413452148e-02, 261 | "time.clang-tidy.google-readability-braces-around-statements.user": 7.2019999999977102e-03, 262 | "time.clang-tidy.google-readability-braces-around-statements.sys": 3.9129999999980569e-03, 263 | "time.clang-tidy.readability-redundant-string-cstr.wall": 2.6252269744873047e-03, 264 | "time.clang-tidy.readability-redundant-string-cstr.user": 1.7159999999942777e-03, 265 | "time.clang-tidy.readability-redundant-string-cstr.sys": 9.0000000000101110e-04, 266 | "time.clang-tidy.bugprone-string-constructor.wall": 4.6801567077636719e-04, 267 | "time.clang-tidy.bugprone-string-constructor.user": 3.2100000000045981e-04, 268 | "time.clang-tidy.bugprone-string-constructor.sys": 1.5699999999929659e-04, 269 | "time.clang-tidy.bugprone-incorrect-roundings.wall": 4.6954154968261719e-03, 270 | "time.clang-tidy.bugprone-incorrect-roundings.user": 2.6829999999864462e-03, 271 | "time.clang-tidy.bugprone-incorrect-roundings.sys": 1.8429999999951541e-03, 272 | "time.clang-tidy.google-explicit-constructor.wall": 2.3362636566162109e-03, 273 | "time.clang-tidy.google-explicit-constructor.user": 1.3949999999947060e-03, 274 | "time.clang-tidy.google-explicit-constructor.sys": 9.4400000000094408e-04, 275 | "time.clang-tidy.altera-struct-pack-align.wall": 2.2187232971191406e-03, 276 | "time.clang-tidy.altera-struct-pack-align.user": 1.3059999999995853e-03, 277 | "time.clang-tidy.altera-struct-pack-align.sys": 9.0299999999920999e-04, 278 | "time.clang-tidy.modernize-use-transparent-functors.wall": 3.9747714996337891e-02, 279 | "time.clang-tidy.modernize-use-transparent-functors.user": 2.2638999999992748e-02, 280 | "time.clang-tidy.modernize-use-transparent-functors.sys": 1.7135999999996487e-02, 281 | "time.clang-tidy.misc-use-anonymous-namespace.wall": 1.0103464126586914e-02, 282 | "time.clang-tidy.misc-use-anonymous-namespace.user": 5.6219999999989057e-03, 283 | "time.clang-tidy.misc-use-anonymous-namespace.sys": 4.3809999999961935e-03, 284 | "time.clang-tidy.bugprone-forwarding-reference-overload.wall": 7.8487396240234375e-04, 285 | "time.clang-tidy.bugprone-forwarding-reference-overload.user": 4.7100000000455111e-04, 286 | "time.clang-tidy.bugprone-forwarding-reference-overload.sys": 3.1700000000101092e-04, 287 | "time.clang-tidy.bugprone-misplaced-pointer-arithmetic-in-alloc.wall": 1.8367767333984375e-03, 288 | "time.clang-tidy.bugprone-misplaced-pointer-arithmetic-in-alloc.user": 1.0750000000041560e-03, 289 | "time.clang-tidy.bugprone-misplaced-pointer-arithmetic-in-alloc.sys": 7.4600000000235589e-04, 290 | "time.clang-tidy.bugprone-reserved-identifier.wall": 1.5588665008544922e-01, 291 | "time.clang-tidy.bugprone-reserved-identifier.user": 1.4900800000000025e-01, 292 | "time.clang-tidy.bugprone-reserved-identifier.sys": 6.5129999999999910e-03, 293 | "time.clang-tidy.cert-err09-cpp.wall": 1.1229515075683594e-04, 294 | "time.clang-tidy.cert-err09-cpp.user": 8.7999999999865963e-05, 295 | "time.clang-tidy.cert-err09-cpp.sys": 2.9999999999530402e-05, 296 | "time.clang-tidy.hicpp-use-equals-delete.wall": 3.4523010253906250e-03, 297 | "time.clang-tidy.hicpp-use-equals-delete.user": 1.9700000000093532e-03, 298 | "time.clang-tidy.hicpp-use-equals-delete.sys": 1.4240000000007580e-03, 299 | "time.clang-tidy.hicpp-use-override.wall": 3.8192272186279297e-03, 300 | "time.clang-tidy.hicpp-use-override.user": 2.1519999999846107e-03, 301 | "time.clang-tidy.hicpp-use-override.sys": 1.6570000000013518e-03, 302 | "time.clang-tidy.cppcoreguidelines-misleading-capture-default-by-value.wall": 9.2983245849609375e-06, 303 | "time.clang-tidy.cppcoreguidelines-misleading-capture-default-by-value.user": 6.9999999992020889e-06, 304 | "time.clang-tidy.cppcoreguidelines-misleading-capture-default-by-value.sys": 3.9999999998929781e-06, 305 | "time.clang-tidy.cert-flp37-c.wall": 3.3900737762451172e-03, 306 | "time.clang-tidy.cert-flp37-c.user": 2.1649999999953096e-03, 307 | "time.clang-tidy.cert-flp37-c.sys": 1.2259999999935101e-03, 308 | "time.clang-tidy.abseil-string-find-str-contains.wall": 3.4123659133911133e-02, 309 | "time.clang-tidy.abseil-string-find-str-contains.user": 2.0379000000032121e-02, 310 | "time.clang-tidy.abseil-string-find-str-contains.sys": 1.2961999999993701e-02, 311 | "time.clang-tidy.cppcoreguidelines-narrowing-conversions.wall": 1.3095617294311523e-02, 312 | "time.clang-tidy.cppcoreguidelines-narrowing-conversions.user": 7.4889999999867563e-03, 313 | "time.clang-tidy.cppcoreguidelines-narrowing-conversions.sys": 5.6040000000001644e-03, 314 | "time.clang-tidy.cert-pos44-c.wall": 3.3152103424072266e-03, 315 | "time.clang-tidy.cert-pos44-c.user": 2.1070000000049660e-03, 316 | "time.clang-tidy.cert-pos44-c.sys": 1.1949999999976146e-03, 317 | "time.clang-tidy.hicpp-avoid-goto.wall": 2.3841857910156250e-06, 318 | "time.clang-tidy.hicpp-avoid-goto.user": 2.0000000002795559e-06, 319 | "time.clang-tidy.hicpp-avoid-goto.sys": 0.0000000000000000e+00, 320 | "time.clang-tidy.cppcoreguidelines-noexcept-swap.wall": 4.4786930084228516e-03, 321 | "time.clang-tidy.cppcoreguidelines-noexcept-swap.user": 2.5470000000096249e-03, 322 | "time.clang-tidy.cppcoreguidelines-noexcept-swap.sys": 1.9369999999938603e-03, 323 | "time.clang-tidy.portability-simd-intrinsics.wall": 7.8909397125244141e-03, 324 | "time.clang-tidy.portability-simd-intrinsics.user": 5.0120000000024589e-03, 325 | "time.clang-tidy.portability-simd-intrinsics.sys": 2.9120000000026902e-03, 326 | "time.clang-tidy.cert-msc51-cpp.wall": 4.2593479156494141e-03, 327 | "time.clang-tidy.cert-msc51-cpp.user": 2.6739999999958464e-03, 328 | "time.clang-tidy.cert-msc51-cpp.sys": 1.5430000000009603e-03, 329 | "time.clang-tidy.cppcoreguidelines-avoid-non-const-global-variables.wall": 8.1450939178466797e-03, 330 | "time.clang-tidy.cppcoreguidelines-avoid-non-const-global-variables.user": 4.6649999999974767e-03, 331 | "time.clang-tidy.cppcoreguidelines-avoid-non-const-global-variables.sys": 3.4919999999971640e-03, 332 | "time.clang-tidy.google-build-explicit-make-pair.wall": 8.5129737854003906e-03, 333 | "time.clang-tidy.google-build-explicit-make-pair.user": 6.0280000000036971e-03, 334 | "time.clang-tidy.google-build-explicit-make-pair.sys": 2.5260000000024707e-03, 335 | "time.clang-tidy.bugprone-assert-side-effect.wall": 3.1441926956176758e-02, 336 | "time.clang-tidy.bugprone-assert-side-effect.user": 1.8935999999959652e-02, 337 | "time.clang-tidy.bugprone-assert-side-effect.sys": 1.2025999999990322e-02, 338 | "time.clang-tidy.abseil-duration-addition.wall": 1.2702941894531250e-03, 339 | "time.clang-tidy.abseil-duration-addition.user": 7.3899999999760269e-04, 340 | "time.clang-tidy.abseil-duration-addition.sys": 5.1599999999796253e-04, 341 | "time.clang-tidy.llvmlibc-implementation-in-namespace.wall": 7.7181577682495117e-02, 342 | "time.clang-tidy.llvmlibc-implementation-in-namespace.user": 6.2155999999998990e-02, 343 | "time.clang-tidy.llvmlibc-implementation-in-namespace.sys": 1.4738999999995173e-02, 344 | "time.clang-tidy.google-build-using-namespace.wall": 9.2983245849609375e-06, 345 | "time.clang-tidy.google-build-using-namespace.user": 9.0000000003698233e-06, 346 | "time.clang-tidy.google-build-using-namespace.sys": 0.0000000000000000e+00, 347 | "time.clang-tidy.mpi-type-mismatch.wall": 3.7734508514404297e-03, 348 | "time.clang-tidy.mpi-type-mismatch.user": 2.4629999999974395e-03, 349 | "time.clang-tidy.mpi-type-mismatch.sys": 1.3740000000037611e-03, 350 | "time.clang-tidy.abseil-upgrade-duration-conversions.wall": 1.2427568435668945e-02, 351 | "time.clang-tidy.abseil-upgrade-duration-conversions.user": 7.6219999999991295e-03, 352 | "time.clang-tidy.abseil-upgrade-duration-conversions.sys": 4.8799999999886712e-03, 353 | "time.clang-tidy.bugprone-signed-char-misuse.wall": 1.2284040451049805e-02, 354 | "time.clang-tidy.bugprone-signed-char-misuse.user": 7.1219999999705408e-03, 355 | "time.clang-tidy.bugprone-signed-char-misuse.sys": 5.1330000000078257e-03, 356 | "time.clang-tidy.readability-else-after-return.wall": 5.1138401031494141e-03, 357 | "time.clang-tidy.readability-else-after-return.user": 3.2830000000010351e-03, 358 | "time.clang-tidy.readability-else-after-return.sys": 1.8490000000013218e-03, 359 | "time.clang-tidy.bugprone-unsafe-functions.wall": 6.7646503448486328e-03, 360 | "time.clang-tidy.bugprone-unsafe-functions.user": 4.2329999999992651e-03, 361 | "time.clang-tidy.bugprone-unsafe-functions.sys": 2.5970000000044013e-03, 362 | "time.clang-tidy.cppcoreguidelines-use-default-member-init.wall": 1.1191368103027344e-03, 363 | "time.clang-tidy.cppcoreguidelines-use-default-member-init.user": 7.0800000000215135e-04, 364 | "time.clang-tidy.cppcoreguidelines-use-default-member-init.sys": 4.2299999999872995e-04, 365 | "time.clang-tidy.readability-function-size.wall": 1.3463735580444336e-02, 366 | "time.clang-tidy.readability-function-size.user": 8.4160000000035318e-03, 367 | "time.clang-tidy.readability-function-size.sys": 5.1540000000038777e-03, 368 | "time.clang-tidy.android-comparison-in-temp-failure-retry.wall": 1.3544559478759766e-03, 369 | "time.clang-tidy.android-comparison-in-temp-failure-retry.user": 8.1900000000389994e-04, 370 | "time.clang-tidy.android-comparison-in-temp-failure-retry.sys": 5.4499999999935156e-04, 371 | "time.clang-tidy.misc-unused-alias-decls.wall": 3.2312870025634766e-03, 372 | "time.clang-tidy.misc-unused-alias-decls.user": 2.0830000000020554e-03, 373 | "time.clang-tidy.misc-unused-alias-decls.sys": 1.1839999999991857e-03, 374 | "time.clang-tidy.bugprone-stringview-nullptr.wall": 7.8516483306884766e-02, 375 | "time.clang-tidy.bugprone-stringview-nullptr.user": 4.6620000000016315e-02, 376 | "time.clang-tidy.bugprone-stringview-nullptr.sys": 3.1772999999998719e-02, 377 | "time.clang-tidy.cert-msc32-c.wall": 3.9227008819580078e-03, 378 | "time.clang-tidy.cert-msc32-c.user": 2.5059999999959004e-03, 379 | "time.clang-tidy.cert-msc32-c.sys": 1.4179999999943682e-03, 380 | "time.clang-tidy.cert-err34-c.wall": 3.6230087280273438e-03, 381 | "time.clang-tidy.cert-err34-c.user": 2.2889999999931021e-03, 382 | "time.clang-tidy.cert-err34-c.sys": 1.2900000000002354e-03, 383 | "time.clang-tidy.cppcoreguidelines-avoid-magic-numbers.wall": 2.2354125976562500e-03, 384 | "time.clang-tidy.cppcoreguidelines-avoid-magic-numbers.user": 1.2820000000068887e-03, 385 | "time.clang-tidy.cppcoreguidelines-avoid-magic-numbers.sys": 9.6599999999824604e-04, 386 | "time.clang-tidy.modernize-concat-nested-namespaces.wall": 2.7298927307128906e-04, 387 | "time.clang-tidy.modernize-concat-nested-namespaces.user": 2.0799999999976393e-04, 388 | "time.clang-tidy.modernize-concat-nested-namespaces.sys": 5.4000000000220538e-05, 389 | "time.clang-tidy.android-cloexec-accept4.wall": 3.2424926757812500e-03, 390 | "time.clang-tidy.android-cloexec-accept4.user": 2.0170000000092614e-03, 391 | "time.clang-tidy.android-cloexec-accept4.sys": 1.1470000000060043e-03, 392 | "time.clang-tidy.bugprone-unhandled-self-assignment.wall": 3.5259723663330078e-03, 393 | "time.clang-tidy.bugprone-unhandled-self-assignment.user": 2.2010000000065588e-03, 394 | "time.clang-tidy.bugprone-unhandled-self-assignment.sys": 1.3199999999990997e-03, 395 | "time.clang-tidy.modernize-use-override.wall": 3.2141208648681641e-03, 396 | "time.clang-tidy.modernize-use-override.user": 1.7870000000033137e-03, 397 | "time.clang-tidy.modernize-use-override.sys": 1.3430000000025366e-03, 398 | "time.clang-tidy.readability-simplify-boolean-expr.wall": 1.1194705963134766e-02, 399 | "time.clang-tidy.readability-simplify-boolean-expr.user": 1.1122000000000298e-02, 400 | "time.clang-tidy.readability-simplify-boolean-expr.sys": 0.0000000000000000e+00, 401 | "time.clang-tidy.cppcoreguidelines-interfaces-global-init.wall": 7.5609683990478516e-03, 402 | "time.clang-tidy.cppcoreguidelines-interfaces-global-init.user": 4.3190000000050688e-03, 403 | "time.clang-tidy.cppcoreguidelines-interfaces-global-init.sys": 3.2790000000029185e-03, 404 | "time.clang-tidy.readability-identifier-naming.wall": 4.1101694107055664e-02, 405 | "time.clang-tidy.readability-identifier-naming.user": 3.7573999999999774e-02, 406 | "time.clang-tidy.readability-identifier-naming.sys": 3.3840000000000536e-03, 407 | "time.clang-tidy.bugprone-too-small-loop-variable.wall": 1.0240077972412109e-03, 408 | "time.clang-tidy.bugprone-too-small-loop-variable.user": 1.4299999999867197e-04, 409 | "time.clang-tidy.bugprone-too-small-loop-variable.sys": 8.8200000000004941e-04, 410 | "time.clang-tidy.misc-static-assert.wall": 9.3545913696289062e-03, 411 | "time.clang-tidy.misc-static-assert.user": 6.2150000000071870e-03, 412 | "time.clang-tidy.misc-static-assert.sys": 3.1350000000018863e-03, 413 | "time.clang-tidy.bugprone-unused-return-value.wall": 3.8152694702148438e-02, 414 | "time.clang-tidy.bugprone-unused-return-value.user": 2.3322000000029597e-02, 415 | "time.clang-tidy.bugprone-unused-return-value.sys": 1.4688000000010915e-02, 416 | "time.clang-tidy.android-cloexec-open.wall": 4.0051937103271484e-03, 417 | "time.clang-tidy.android-cloexec-open.user": 2.5470000000025195e-03, 418 | "time.clang-tidy.android-cloexec-open.sys": 1.4420000000001654e-03, 419 | "time.clang-tidy.android-cloexec-epoll-create.wall": 3.0281543731689453e-03, 420 | "time.clang-tidy.android-cloexec-epoll-create.user": 1.9519999999926263e-03, 421 | "time.clang-tidy.android-cloexec-epoll-create.sys": 1.1070000000004132e-03, 422 | "time.clang-tidy.misc-non-private-member-variables-in-classes.wall": 9.6695423126220703e-03, 423 | "time.clang-tidy.misc-non-private-member-variables-in-classes.user": 4.6629999999989735e-03, 424 | "time.clang-tidy.misc-non-private-member-variables-in-classes.sys": 5.0040000000011187e-03, 425 | "time.clang-tidy.altera-single-work-item-barrier.wall": 3.9558410644531250e-03, 426 | "time.clang-tidy.altera-single-work-item-barrier.user": 2.2079999999928823e-03, 427 | "time.clang-tidy.altera-single-work-item-barrier.sys": 1.7299999999975668e-03, 428 | "time.clang-tidy.hicpp-signed-bitwise.wall": 3.5564899444580078e-03, 429 | "time.clang-tidy.hicpp-signed-bitwise.user": 2.0070000000087518e-03, 430 | "time.clang-tidy.hicpp-signed-bitwise.sys": 1.6219999999993462e-03, 431 | "time.clang-tidy.bugprone-forward-declaration-namespace.wall": 3.6160945892333984e-03, 432 | "time.clang-tidy.bugprone-forward-declaration-namespace.user": 2.1619999999966666e-03, 433 | "time.clang-tidy.bugprone-forward-declaration-namespace.sys": 1.4039999999995167e-03, 434 | "time.clang-tidy.abseil-duration-subtraction.wall": 1.2226104736328125e-03, 435 | "time.clang-tidy.abseil-duration-subtraction.user": 7.4199999999446931e-04, 436 | "time.clang-tidy.abseil-duration-subtraction.sys": 5.1199999999873569e-04, 437 | "time.clang-tidy.cppcoreguidelines-no-malloc.wall": 4.3196678161621094e-03, 438 | "time.clang-tidy.cppcoreguidelines-no-malloc.user": 2.7740000000093801e-03, 439 | "time.clang-tidy.cppcoreguidelines-no-malloc.sys": 1.5419999999974898e-03, 440 | "time.clang-tidy.hicpp-use-nullptr.wall": 3.3916711807250977e-02, 441 | "time.clang-tidy.hicpp-use-nullptr.user": 2.0611999999976760e-02, 442 | "time.clang-tidy.hicpp-use-nullptr.sys": 1.3180000000006631e-02, 443 | "time.clang-tidy.modernize-return-braced-init-list.wall": 1.4960765838623047e-03, 444 | "time.clang-tidy.modernize-return-braced-init-list.user": 9.6699999999483310e-04, 445 | "time.clang-tidy.modernize-return-braced-init-list.sys": 5.0300000000080836e-04, 446 | "time.clang-tidy.readability-string-compare.wall": 5.7086944580078125e-03, 447 | "time.clang-tidy.readability-string-compare.user": 3.3480000000003507e-03, 448 | "time.clang-tidy.readability-string-compare.sys": 2.3009999999938913e-03, 449 | "time.clang-tidy.modernize-redundant-void-arg.wall": 1.5767097473144531e-02, 450 | "time.clang-tidy.modernize-redundant-void-arg.user": 9.0940000000072629e-03, 451 | "time.clang-tidy.modernize-redundant-void-arg.sys": 6.6829999999942213e-03, 452 | "time.clang-tidy.fuchsia-virtual-inheritance.wall": 1.7867088317871094e-03, 453 | "time.clang-tidy.fuchsia-virtual-inheritance.user": 1.0320000000025864e-03, 454 | "time.clang-tidy.fuchsia-virtual-inheritance.sys": 7.3700000000020971e-04, 455 | "time.clang-tidy.performance-noexcept-destructor.wall": 1.4376640319824219e-04, 456 | "time.clang-tidy.performance-noexcept-destructor.user": 8.1999999999915474e-05, 457 | "time.clang-tidy.performance-noexcept-destructor.sys": 5.8000000000113516e-05, 458 | "time.clang-tidy.cert-dcl58-cpp.wall": 3.0324459075927734e-02, 459 | "time.clang-tidy.cert-dcl58-cpp.user": 1.7588000000003490e-02, 460 | "time.clang-tidy.cert-dcl58-cpp.sys": 1.2815000000002685e-02, 461 | "time.clang-tidy.readability-static-definition-in-anonymous-namespace.wall": 1.9739627838134766e-02, 462 | "time.clang-tidy.readability-static-definition-in-anonymous-namespace.user": 1.1222999999993988e-02, 463 | "time.clang-tidy.readability-static-definition-in-anonymous-namespace.sys": 8.2980000000045795e-03, 464 | "time.clang-tidy.bugprone-shared-ptr-array-mismatch.wall": 2.1004676818847656e-04, 465 | "time.clang-tidy.bugprone-shared-ptr-array-mismatch.user": 1.3300000000171508e-04, 466 | "time.clang-tidy.bugprone-shared-ptr-array-mismatch.sys": 7.1999999999627917e-05, 467 | "time.clang-tidy.bugprone-unique-ptr-array-mismatch.wall": 1.7404556274414062e-04, 468 | "time.clang-tidy.bugprone-unique-ptr-array-mismatch.user": 1.2000000000167432e-04, 469 | "time.clang-tidy.bugprone-unique-ptr-array-mismatch.sys": 6.2999999999480139e-05, 470 | "time.clang-tidy.misc-non-copyable-objects.wall": 2.1961450576782227e-02, 471 | "time.clang-tidy.misc-non-copyable-objects.user": 1.2473999999964125e-02, 472 | "time.clang-tidy.misc-non-copyable-objects.sys": 9.2359999999924725e-03, 473 | "time.clang-tidy.altera-unroll-loops.wall": 3.2154798507690430e-02, 474 | "time.clang-tidy.altera-unroll-loops.user": 1.9489999999970919e-02, 475 | "time.clang-tidy.altera-unroll-loops.sys": 1.2449000000001487e-02, 476 | "time.clang-tidy.readability-identifier-length.wall": 1.2805223464965820e-02, 477 | "time.clang-tidy.readability-identifier-length.user": 7.2930000000188677e-03, 478 | "time.clang-tidy.readability-identifier-length.sys": 5.5519999999991132e-03, 479 | "time.clang-tidy.readability-braces-around-statements.wall": 7.4517726898193359e-03, 480 | "time.clang-tidy.readability-braces-around-statements.user": 5.1630000000013609e-03, 481 | "time.clang-tidy.readability-braces-around-statements.sys": 2.3030000000003881e-03, 482 | "time.clang-tidy.bugprone-multiple-new-in-one-expression.wall": 1.2129068374633789e-02, 483 | "time.clang-tidy.bugprone-multiple-new-in-one-expression.user": 7.6810000000064882e-03, 484 | "time.clang-tidy.bugprone-multiple-new-in-one-expression.sys": 4.6000000000094854e-03, 485 | "time.clang-tidy.performance-move-constructor-init.wall": 6.5183639526367188e-04, 486 | "time.clang-tidy.performance-move-constructor-init.user": 3.8799999999783452e-04, 487 | "time.clang-tidy.performance-move-constructor-init.sys": 3.0000000000041105e-04, 488 | "time.clang-tidy.android-cloexec-dup.wall": 3.4224987030029297e-03, 489 | "time.clang-tidy.android-cloexec-dup.user": 2.1170000000010347e-03, 490 | "time.clang-tidy.android-cloexec-dup.sys": 1.1799999999964061e-03, 491 | "time.clang-tidy.bugprone-not-null-terminated-result.wall": 1.3634681701660156e-02, 492 | "time.clang-tidy.bugprone-not-null-terminated-result.user": 8.3610000000042817e-03, 493 | "time.clang-tidy.bugprone-not-null-terminated-result.sys": 5.2120000000042133e-03, 494 | "time.clang-tidy.cppcoreguidelines-pro-type-cstyle-cast.wall": 3.2734870910644531e-04, 495 | "time.clang-tidy.cppcoreguidelines-pro-type-cstyle-cast.user": 1.8799999999918882e-04, 496 | "time.clang-tidy.cppcoreguidelines-pro-type-cstyle-cast.sys": 1.4300000000067037e-04, 497 | "time.clang-tidy.cert-dcl21-cpp.wall": 3.7267208099365234e-03, 498 | "time.clang-tidy.cert-dcl21-cpp.user": 2.1230000000040938e-03, 499 | "time.clang-tidy.cert-dcl21-cpp.sys": 1.6089999999995275e-03, 500 | "time.clang-tidy.llvmlibc-callee-namespace.wall": 9.5252990722656250e-03, 501 | "time.clang-tidy.llvmlibc-callee-namespace.user": 5.8230000000052407e-03, 502 | "time.clang-tidy.llvmlibc-callee-namespace.sys": 3.6969999999998393e-03, 503 | "time.clang-tidy.fuchsia-overloaded-operator.wall": 5.0721168518066406e-03, 504 | "time.clang-tidy.fuchsia-overloaded-operator.user": 2.8909999999990887e-03, 505 | "time.clang-tidy.fuchsia-overloaded-operator.sys": 2.2049999999980141e-03, 506 | "time.clang-tidy.llvm-twine-local.wall": 6.7658424377441406e-03, 507 | "time.clang-tidy.llvm-twine-local.user": 3.7620000000040399e-03, 508 | "time.clang-tidy.llvm-twine-local.sys": 2.8399999999959569e-03, 509 | "time.clang-tidy.fuchsia-default-arguments-declarations.wall": 1.1453151702880859e-02, 510 | "time.clang-tidy.fuchsia-default-arguments-declarations.user": 5.9509999999898255e-03, 511 | "time.clang-tidy.fuchsia-default-arguments-declarations.sys": 5.5490000000002482e-03, 512 | "time.clang-tidy.readability-redundant-control-flow.wall": 3.4821033477783203e-02, 513 | "time.clang-tidy.readability-redundant-control-flow.user": 2.0923000000028669e-02, 514 | "time.clang-tidy.readability-redundant-control-flow.sys": 1.3490000000001112e-02, 515 | "time.clang-tidy.bugprone-empty-catch.wall": 1.0228157043457031e-04, 516 | "time.clang-tidy.bugprone-empty-catch.user": 8.5999999999142318e-05, 517 | "time.clang-tidy.bugprone-empty-catch.sys": 2.2999999999440135e-05, 518 | "time.clang-tidy.bugprone-suspicious-semicolon.wall": 2.8746604919433594e-02, 519 | "time.clang-tidy.bugprone-suspicious-semicolon.user": 1.7428000000029975e-02, 520 | "time.clang-tidy.bugprone-suspicious-semicolon.sys": 1.1148999999994080e-02, 521 | "time.clang-tidy.modernize-macro-to-enum.wall": 2.0339965820312500e-02, 522 | "time.clang-tidy.modernize-macro-to-enum.user": 1.1941999999987907e-02, 523 | "time.clang-tidy.modernize-macro-to-enum.sys": 8.2670000000044652e-03, 524 | "time.clang-tidy.bugprone-fold-init-type.wall": 5.4650306701660156e-03, 525 | "time.clang-tidy.bugprone-fold-init-type.user": 3.5119999999975171e-03, 526 | "time.clang-tidy.bugprone-fold-init-type.sys": 1.9719999999989746e-03, 527 | "time.clang-tidy.bugprone-branch-clone.wall": 2.7198791503906250e-03, 528 | "time.clang-tidy.bugprone-branch-clone.user": 1.8150000000045630e-03, 529 | "time.clang-tidy.bugprone-branch-clone.sys": 9.1900000000166848e-04, 530 | "time.clang-tidy.cert-dcl03-c.wall": 2.8197765350341797e-03, 531 | "time.clang-tidy.cert-dcl03-c.user": 1.8779999999996022e-03, 532 | "time.clang-tidy.cert-dcl03-c.sys": 9.7899999999895293e-04, 533 | "time.clang-tidy.bugprone-undefined-memory-manipulation.wall": 3.9629936218261719e-03, 534 | "time.clang-tidy.bugprone-undefined-memory-manipulation.user": 2.5449999999960227e-03, 535 | "time.clang-tidy.bugprone-undefined-memory-manipulation.sys": 1.4469999999981997e-03, 536 | "time.clang-tidy.bugprone-infinite-loop.wall": 3.8602590560913086e-02, 537 | "time.clang-tidy.bugprone-infinite-loop.user": 2.4729999999990593e-02, 538 | "time.clang-tidy.bugprone-infinite-loop.sys": 1.3929999999988674e-02, 539 | "time.clang-tidy.performance-no-int-to-ptr.wall": 3.4904479980468750e-03, 540 | "time.clang-tidy.performance-no-int-to-ptr.user": 2.0299999999968676e-03, 541 | "time.clang-tidy.performance-no-int-to-ptr.sys": 1.4800000000010360e-03, 542 | "time.clang-tidy.abseil-duration-factory-float.wall": 3.4439563751220703e-03, 543 | "time.clang-tidy.abseil-duration-factory-float.user": 2.1970000000011147e-03, 544 | "time.clang-tidy.abseil-duration-factory-float.sys": 1.2439999999960261e-03, 545 | "time.clang-tidy.cppcoreguidelines-init-variables.wall": 2.2105932235717773e-02, 546 | "time.clang-tidy.cppcoreguidelines-init-variables.user": 1.2521000000004001e-02, 547 | "time.clang-tidy.cppcoreguidelines-init-variables.sys": 9.8219999999957786e-03, 548 | "time.clang-tidy.bugprone-string-literal-with-embedded-nul.wall": 1.1236667633056641e-03, 549 | "time.clang-tidy.bugprone-string-literal-with-embedded-nul.user": 7.2299999999803077e-04, 550 | "time.clang-tidy.bugprone-string-literal-with-embedded-nul.sys": 3.8599999999999746e-04, 551 | "time.clang-tidy.android-cloexec-memfd-create.wall": 3.2145977020263672e-03, 552 | "time.clang-tidy.android-cloexec-memfd-create.user": 2.0570000000010857e-03, 553 | "time.clang-tidy.android-cloexec-memfd-create.sys": 1.1349999999992200e-03, 554 | "time.clang-tidy.performance-type-promotion-in-math-fn.wall": 9.8135471343994141e-03, 555 | "time.clang-tidy.performance-type-promotion-in-math-fn.user": 6.2650000000150641e-03, 556 | "time.clang-tidy.performance-type-promotion-in-math-fn.sys": 3.5940000000040939e-03, 557 | "time.clang-tidy.bugprone-assignment-in-if-condition.wall": 7.3616504669189453e-03, 558 | "time.clang-tidy.bugprone-assignment-in-if-condition.user": 7.3209999999996889e-03, 559 | "time.clang-tidy.bugprone-assignment-in-if-condition.sys": 0.0000000000000000e+00, 560 | "time.clang-tidy.misc-misleading-bidirectional.wall": 1.6927719116210938e-04, 561 | "time.clang-tidy.misc-misleading-bidirectional.user": 1.0300000000018628e-04, 562 | "time.clang-tidy.misc-misleading-bidirectional.sys": 5.6000000000500094e-05, 563 | "time.clang-tidy.bugprone-terminating-continue.wall": 4.0769577026367188e-05, 564 | "time.clang-tidy.bugprone-terminating-continue.user": 1.0000000000065512e-05, 565 | "time.clang-tidy.bugprone-terminating-continue.sys": 3.1999999999809958e-05, 566 | "time.clang-tidy.bugprone-non-zero-enum-to-bool-conversion.wall": 3.6737918853759766e-03, 567 | "time.clang-tidy.bugprone-non-zero-enum-to-bool-conversion.user": 2.1779999999891331e-03, 568 | "time.clang-tidy.bugprone-non-zero-enum-to-bool-conversion.sys": 1.5230000000023836e-03, 569 | "time.clang-tidy.cppcoreguidelines-pro-type-vararg.wall": 1.0499238967895508e-02, 570 | "time.clang-tidy.cppcoreguidelines-pro-type-vararg.user": 6.2069999999949665e-03, 571 | "time.clang-tidy.cppcoreguidelines-pro-type-vararg.sys": 4.3330000000063595e-03, 572 | "time.clang-tidy.android-cloexec-inotify-init1.wall": 3.1909942626953125e-03, 573 | "time.clang-tidy.android-cloexec-inotify-init1.user": 2.0239999999982494e-03, 574 | "time.clang-tidy.android-cloexec-inotify-init1.sys": 1.1749999999999261e-03, 575 | "time.clang-tidy.cert-con54-cpp.wall": 3.5529136657714844e-03, 576 | "time.clang-tidy.cert-con54-cpp.user": 2.6270000000021554e-03, 577 | "time.clang-tidy.cert-con54-cpp.sys": 9.2600000000020444e-04, 578 | "time.clang-tidy.bugprone-misplaced-operator-in-strlen-in-alloc.wall": 4.7876834869384766e-03, 579 | "time.clang-tidy.bugprone-misplaced-operator-in-strlen-in-alloc.user": 3.0619999999994540e-03, 580 | "time.clang-tidy.bugprone-misplaced-operator-in-strlen-in-alloc.sys": 1.7210000000007497e-03, 581 | "time.clang-tidy.bugprone-undelegated-constructor.wall": 2.1650791168212891e-03, 582 | "time.clang-tidy.bugprone-undelegated-constructor.user": 1.3470000000044280e-03, 583 | "time.clang-tidy.bugprone-undelegated-constructor.sys": 8.4700000000093034e-04, 584 | "time.clang-tidy.performance-unnecessary-copy-initialization.wall": 1.4622449874877930e-02, 585 | "time.clang-tidy.performance-unnecessary-copy-initialization.user": 9.4059999999895894e-03, 586 | "time.clang-tidy.performance-unnecessary-copy-initialization.sys": 5.2389999999993275e-03, 587 | "time.clang-tidy.bugprone-spuriously-wake-up-functions.wall": 2.4933815002441406e-03, 588 | "time.clang-tidy.bugprone-spuriously-wake-up-functions.user": 1.6839999999964661e-03, 589 | "time.clang-tidy.bugprone-spuriously-wake-up-functions.sys": 8.0100000000005167e-04, 590 | "time.clang-tidy.performance-faster-string-find.wall": 3.5738945007324219e-04, 591 | "time.clang-tidy.performance-faster-string-find.user": 2.2100000000024878e-04, 592 | "time.clang-tidy.performance-faster-string-find.sys": 1.1800000000028454e-04, 593 | "time.clang-tidy.cppcoreguidelines-pro-type-union-access.wall": 1.0409355163574219e-03, 594 | "time.clang-tidy.cppcoreguidelines-pro-type-union-access.user": 6.1199999999939081e-04, 595 | "time.clang-tidy.cppcoreguidelines-pro-type-union-access.sys": 4.3699999999757821e-04, 596 | "time.clang-tidy.cppcoreguidelines-rvalue-reference-param-not-moved.wall": 1.1698484420776367e-02, 597 | "time.clang-tidy.cppcoreguidelines-rvalue-reference-param-not-moved.user": 6.6160000000028418e-03, 598 | "time.clang-tidy.cppcoreguidelines-rvalue-reference-param-not-moved.sys": 4.8429999999968221e-03, 599 | "time.clang-tidy.bugprone-sizeof-expression.wall": 2.6397943496704102e-02, 600 | "time.clang-tidy.bugprone-sizeof-expression.user": 1.5911999999998816e-02, 601 | "time.clang-tidy.bugprone-sizeof-expression.sys": 1.0464999999991287e-02, 602 | "time.clang-tidy.google-runtime-int.wall": 6.8775415420532227e-02, 603 | "time.clang-tidy.google-runtime-int.user": 3.8228000000018802e-02, 604 | "time.clang-tidy.google-runtime-int.sys": 3.0707000000009144e-02, 605 | "time.clang-tidy.llvm-namespace-comment.wall": 5.7318210601806641e-03, 606 | "time.clang-tidy.llvm-namespace-comment.user": 4.0109999999993207e-03, 607 | "time.clang-tidy.llvm-namespace-comment.sys": 1.7020000000003144e-03, 608 | "time.clang-tidy.misc-definitions-in-headers.wall": 2.4253845214843750e-02, 609 | "time.clang-tidy.misc-definitions-in-headers.user": 1.3878999999999753e-02, 610 | "time.clang-tidy.misc-definitions-in-headers.sys": 1.0253000000002288e-02, 611 | "time.clang-tidy.bugprone-use-after-move.wall": 7.3285579681396484e-02, 612 | "time.clang-tidy.bugprone-use-after-move.user": 4.6233999999990338e-02, 613 | "time.clang-tidy.bugprone-use-after-move.sys": 2.7540000000000342e-02, 614 | "time.clang-tidy.cppcoreguidelines-noexcept-destructor.wall": 1.7499923706054688e-04, 615 | "time.clang-tidy.cppcoreguidelines-noexcept-destructor.user": 1.0600000000104970e-04, 616 | "time.clang-tidy.cppcoreguidelines-noexcept-destructor.sys": 7.1999999999849962e-05, 617 | "time.clang-tidy.fuchsia-default-arguments-calls.wall": 1.9264221191406250e-04, 618 | "time.clang-tidy.fuchsia-default-arguments-calls.user": 1.4599999999997948e-04, 619 | "time.clang-tidy.fuchsia-default-arguments-calls.sys": 4.6000000000212538e-05, 620 | "time.clang-tidy.concurrency-thread-canceltype-asynchronous.wall": 3.2484531402587891e-03, 621 | "time.clang-tidy.concurrency-thread-canceltype-asynchronous.user": 2.0459999999991041e-03, 622 | "time.clang-tidy.concurrency-thread-canceltype-asynchronous.sys": 1.1729999999963159e-03, 623 | "time.clang-tidy.readability-make-member-function-const.wall": 3.2110214233398438e-03, 624 | "time.clang-tidy.readability-make-member-function-const.user": 1.7890000000075901e-03, 625 | "time.clang-tidy.readability-make-member-function-const.sys": 1.3970000000025351e-03, 626 | "time.clang-tidy.darwin-dispatch-once-nonstatic.wall": 1.2310743331909180e-02, 627 | "time.clang-tidy.darwin-dispatch-once-nonstatic.user": 7.0620000000061189e-03, 628 | "time.clang-tidy.darwin-dispatch-once-nonstatic.sys": 5.3309999999993085e-03, 629 | "time.clang-tidy.bugprone-exception-escape.wall": 6.6206455230712891e-03, 630 | "time.clang-tidy.bugprone-exception-escape.user": 3.7869999999959880e-03, 631 | "time.clang-tidy.bugprone-exception-escape.sys": 2.8110000000030055e-03, 632 | "time.clang-tidy.abseil-cleanup-ctad.wall": 2.7761936187744141e-02, 633 | "time.clang-tidy.abseil-cleanup-ctad.user": 1.7057999999970708e-02, 634 | "time.clang-tidy.abseil-cleanup-ctad.sys": 1.0736000000000745e-02, 635 | "time.clang-tidy.abseil-str-cat-append.wall": 1.7788410186767578e-03, 636 | "time.clang-tidy.abseil-str-cat-append.user": 1.4309999999975176e-03, 637 | "time.clang-tidy.abseil-str-cat-append.sys": 3.2600000000226892e-04, 638 | "time.clang-tidy.readability-non-const-parameter.wall": 5.0954580307006836e-02, 639 | "time.clang-tidy.readability-non-const-parameter.user": 3.0495999999973211e-02, 640 | "time.clang-tidy.readability-non-const-parameter.sys": 1.9624999999999782e-02, 641 | "time.clang-tidy.cert-dcl50-cpp.wall": 3.7794113159179688e-03, 642 | "time.clang-tidy.cert-dcl50-cpp.user": 2.1610000000062968e-03, 643 | "time.clang-tidy.cert-dcl50-cpp.sys": 1.6450000000007847e-03, 644 | "time.clang-tidy.misc-unused-using-decls.wall": 6.4182996749877930e-02, 645 | "time.clang-tidy.misc-unused-using-decls.user": 3.6835000000022600e-02, 646 | "time.clang-tidy.misc-unused-using-decls.sys": 2.6949999999999807e-02, 647 | "time.clang-tidy.bugprone-multiple-statement-macro.wall": 2.9764890670776367e-02, 648 | "time.clang-tidy.bugprone-multiple-statement-macro.user": 1.8187000000045472e-02, 649 | "time.clang-tidy.bugprone-multiple-statement-macro.sys": 1.1432000000012987e-02, 650 | "time.clang-tidy.cppcoreguidelines-missing-std-forward.wall": 1.6875267028808594e-02, 651 | "time.clang-tidy.cppcoreguidelines-missing-std-forward.user": 1.0149000000000186e-02, 652 | "time.clang-tidy.cppcoreguidelines-missing-std-forward.sys": 6.8240000000023837e-03, 653 | "time.clang-tidy.cppcoreguidelines-avoid-goto.wall": 1.9073486328125000e-06, 654 | "time.clang-tidy.cppcoreguidelines-avoid-goto.user": 1.9999999998354667e-06, 655 | "time.clang-tidy.cppcoreguidelines-avoid-goto.sys": 0.0000000000000000e+00, 656 | "time.clang-tidy.cert-fio38-c.wall": 2.0780563354492188e-02, 657 | "time.clang-tidy.cert-fio38-c.user": 1.1705999999984229e-02, 658 | "time.clang-tidy.cert-fio38-c.sys": 8.7069999999935810e-03, 659 | "time.clang-tidy.cppcoreguidelines-special-member-functions.wall": 1.0780811309814453e-02, 660 | "time.clang-tidy.cppcoreguidelines-special-member-functions.user": 7.3230000000004125e-03, 661 | "time.clang-tidy.cppcoreguidelines-special-member-functions.sys": 3.4979999999997791e-03, 662 | "time.clang-tidy.modernize-use-nodiscard.wall": 5.1679611206054688e-03, 663 | "time.clang-tidy.modernize-use-nodiscard.user": 2.8129999999984001e-03, 664 | "time.clang-tidy.modernize-use-nodiscard.sys": 2.3279999999994416e-03, 665 | "time.clang-tidy.performance-inefficient-algorithm.wall": 3.6537647247314453e-03, 666 | "time.clang-tidy.performance-inefficient-algorithm.user": 2.3149999999940718e-03, 667 | "time.clang-tidy.performance-inefficient-algorithm.sys": 1.2930000000004327e-03, 668 | "time.clang-tidy.readability-container-size-empty.wall": 4.6033143997192383e-02, 669 | "time.clang-tidy.readability-container-size-empty.user": 2.9048000000012841e-02, 670 | "time.clang-tidy.readability-container-size-empty.sys": 1.6964999999991903e-02, 671 | "time.clang-tidy.readability-misleading-indentation.wall": 6.0284137725830078e-03, 672 | "time.clang-tidy.readability-misleading-indentation.user": 4.0879999999994254e-03, 673 | "time.clang-tidy.readability-misleading-indentation.sys": 1.9339999999996582e-03, 674 | "time.clang-tidy.modernize-type-traits.wall": 6.6205501556396484e-02, 675 | "time.clang-tidy.modernize-type-traits.user": 3.8947999999984884e-02, 676 | "time.clang-tidy.modernize-type-traits.sys": 2.6812000000004721e-02, 677 | "time.clang-tidy.cert-str34-c.wall": 1.0745286941528320e-02, 678 | "time.clang-tidy.cert-str34-c.user": 6.1180000000065071e-03, 679 | "time.clang-tidy.cert-str34-c.sys": 4.5770000000076028e-03, 680 | "time.clang-tidy.hicpp-braces-around-statements.wall": 7.2669982910156250e-03, 681 | "time.clang-tidy.hicpp-braces-around-statements.user": 5.0299999999992018e-03, 682 | "time.clang-tidy.hicpp-braces-around-statements.sys": 2.2149999999985237e-03, 683 | "time.clang-tidy.bugprone-dangling-handle.wall": 1.9539833068847656e-02, 684 | "time.clang-tidy.bugprone-dangling-handle.user": 1.1668999999993268e-02, 685 | "time.clang-tidy.bugprone-dangling-handle.sys": 7.8599999999879877e-03, 686 | "time.clang-tidy.portability-std-allocator-const.wall": 3.3136606216430664e-02, 687 | "time.clang-tidy.portability-std-allocator-const.user": 1.8612999999988222e-02, 688 | "time.clang-tidy.portability-std-allocator-const.sys": 1.4205999999997498e-02, 689 | "time.clang-tidy.performance-unnecessary-value-param.wall": 4.5345544815063477e-02, 690 | "time.clang-tidy.performance-unnecessary-value-param.user": 3.7605000000003219e-02, 691 | "time.clang-tidy.performance-unnecessary-value-param.sys": 7.8230000000012456e-03, 692 | "time.clang-tidy.google-build-namespaces.wall": 9.4413757324218750e-05, 693 | "time.clang-tidy.google-build-namespaces.user": 6.5999999999899472e-05, 694 | "time.clang-tidy.google-build-namespaces.sys": 2.0999999999826713e-05, 695 | "time.clang-tidy.cppcoreguidelines-pro-type-static-cast-downcast.wall": 5.6791305541992188e-04, 696 | "time.clang-tidy.cppcoreguidelines-pro-type-static-cast-downcast.user": 3.2500000000279528e-04, 697 | "time.clang-tidy.cppcoreguidelines-pro-type-static-cast-downcast.sys": 2.5500000000056033e-04, 698 | "time.clang-tidy.hicpp-use-noexcept.wall": 1.2997865676879883e-02, 699 | "time.clang-tidy.hicpp-use-noexcept.user": 7.0289999999837427e-03, 700 | "time.clang-tidy.hicpp-use-noexcept.sys": 5.8680000000039811e-03, 701 | "time.clang-tidy.bugprone-standalone-empty.wall": 6.0884475708007812e-02, 702 | "time.clang-tidy.bugprone-standalone-empty.user": 3.7888999999977635e-02, 703 | "time.clang-tidy.bugprone-standalone-empty.sys": 2.3191999999991886e-02, 704 | "time.clang-tidy.cppcoreguidelines-prefer-member-initializer.wall": 8.2445144653320312e-04, 705 | "time.clang-tidy.cppcoreguidelines-prefer-member-initializer.user": 4.6600000000251995e-04, 706 | "time.clang-tidy.cppcoreguidelines-prefer-member-initializer.sys": 3.5200000000035203e-04, 707 | "time.clang-tidy.readability-implicit-bool-conversion.wall": 1.0226249694824219e-02, 708 | "time.clang-tidy.readability-implicit-bool-conversion.user": 6.0899999999950438e-03, 709 | "time.clang-tidy.readability-implicit-bool-conversion.sys": 4.1709999999912650e-03, 710 | "time.clang-tidy.hicpp-use-emplace.wall": 1.0833740234375000e-03, 711 | "time.clang-tidy.hicpp-use-emplace.user": 7.2000000000027597e-04, 712 | "time.clang-tidy.hicpp-use-emplace.sys": 3.8199999999966039e-04, 713 | "time.clang-tidy.bugprone-virtual-near-miss.wall": 4.2095184326171875e-03, 714 | "time.clang-tidy.bugprone-virtual-near-miss.user": 2.3539999999968586e-03, 715 | "time.clang-tidy.bugprone-virtual-near-miss.sys": 1.8449999999923250e-03, 716 | "time.clang-tidy.modernize-shrink-to-fit.wall": 3.6549568176269531e-04, 717 | "time.clang-tidy.modernize-shrink-to-fit.user": 2.2800000000255949e-04, 718 | "time.clang-tidy.modernize-shrink-to-fit.sys": 1.3799999999908330e-04, 719 | "time.clang-tidy.hicpp-no-malloc.wall": 4.4510364532470703e-03, 720 | "time.clang-tidy.hicpp-no-malloc.user": 2.8459999999967955e-03, 721 | "time.clang-tidy.hicpp-no-malloc.sys": 1.6200000000003989e-03, 722 | "time.clang-tidy.hicpp-function-size.wall": 7.2860717773437500e-03, 723 | "time.clang-tidy.hicpp-function-size.user": 4.3310000000005289e-03, 724 | "time.clang-tidy.hicpp-function-size.sys": 3.0170000000038222e-03, 725 | "time.clang-tidy.android-cloexec-fopen.wall": 3.5364627838134766e-03, 726 | "time.clang-tidy.android-cloexec-fopen.user": 2.2439999999965821e-03, 727 | "time.clang-tidy.android-cloexec-fopen.sys": 1.2530000000026131e-03, 728 | "time.clang-tidy.performance-avoid-endl.wall": 5.0461292266845703e-03, 729 | "time.clang-tidy.performance-avoid-endl.user": 3.1339999999993040e-03, 730 | "time.clang-tidy.performance-avoid-endl.sys": 1.7329999999959877e-03, 731 | "time.clang-tidy.readability-static-accessed-through-instance.wall": 9.3436241149902344e-04, 732 | "time.clang-tidy.readability-static-accessed-through-instance.user": 5.6200000000350414e-04, 733 | "time.clang-tidy.readability-static-accessed-through-instance.sys": 3.5700000000260523e-04, 734 | "time.clang-tidy.modernize-use-nullptr.wall": 3.1411170959472656e-02, 735 | "time.clang-tidy.modernize-use-nullptr.user": 1.8987000000036502e-02, 736 | "time.clang-tidy.modernize-use-nullptr.sys": 1.2096000000004103e-02, 737 | "time.clang-tidy.android-cloexec-creat.wall": 3.1781196594238281e-03, 738 | "time.clang-tidy.android-cloexec-creat.user": 2.0470000000023525e-03, 739 | "time.clang-tidy.android-cloexec-creat.sys": 1.1959999999981985e-03, 740 | "time.clang-tidy.performance-move-const-arg.wall": 3.0680179595947266e-02, 741 | "time.clang-tidy.performance-move-const-arg.user": 1.8751999999977897e-02, 742 | "time.clang-tidy.performance-move-const-arg.sys": 1.1839000000003264e-02, 743 | "time.clang-tidy.bugprone-unused-raii.wall": 2.7405023574829102e-02, 744 | "time.clang-tidy.bugprone-unused-raii.user": 1.6755999999976456e-02, 745 | "time.clang-tidy.bugprone-unused-raii.sys": 1.0517000000007437e-02, 746 | "time.clang-tidy.bugprone-suspicious-memory-comparison.wall": 3.3578872680664062e-03, 747 | "time.clang-tidy.bugprone-suspicious-memory-comparison.user": 2.1790000000048160e-03, 748 | "time.clang-tidy.bugprone-suspicious-memory-comparison.sys": 1.2530000000028352e-03, 749 | "time.clang-tidy.readability-redundant-member-init.wall": 6.8712234497070312e-04, 750 | "time.clang-tidy.readability-redundant-member-init.user": 3.9800000000589364e-04, 751 | "time.clang-tidy.readability-redundant-member-init.sys": 2.9700000000021376e-04, 752 | "time.clang-tidy.readability-magic-numbers.wall": 1.5952587127685547e-03, 753 | "time.clang-tidy.readability-magic-numbers.user": 9.1399999999852710e-04, 754 | "time.clang-tidy.readability-magic-numbers.sys": 7.2199999999988940e-04, 755 | "time.clang-tidy.modernize-use-emplace.wall": 1.4629364013671875e-03, 756 | "time.clang-tidy.modernize-use-emplace.user": 9.5399999999923324e-04, 757 | "time.clang-tidy.modernize-use-emplace.sys": 5.1400000000079160e-04, 758 | "time.clang-tidy.misc-const-correctness.wall": 1.4410519599914551e-01, 759 | "time.clang-tidy.misc-const-correctness.user": 1.1386399999997954e-01, 760 | "time.clang-tidy.misc-const-correctness.sys": 2.9832999999995780e-02, 761 | "time.clang-tidy.bugprone-swapped-arguments.wall": 1.3163328170776367e-02, 762 | "time.clang-tidy.bugprone-swapped-arguments.user": 7.9820000000041524e-03, 763 | "time.clang-tidy.bugprone-swapped-arguments.sys": 5.2209999999963674e-03, 764 | "time.clang-tidy.hicpp-use-auto.wall": 7.8434944152832031e-03, 765 | "time.clang-tidy.hicpp-use-auto.user": 3.8889999999991431e-03, 766 | "time.clang-tidy.hicpp-use-auto.sys": 3.9460000000017814e-03, 767 | "time.clang-tidy.hicpp-special-member-functions.wall": 9.8199844360351562e-03, 768 | "time.clang-tidy.hicpp-special-member-functions.user": 5.7090000000035168e-03, 769 | "time.clang-tidy.hicpp-special-member-functions.sys": 4.0829999999982824e-03, 770 | "time.clang-tidy.hicpp-no-assembler.wall": 6.2932968139648438e-03, 771 | "time.clang-tidy.hicpp-no-assembler.user": 3.5419999999994900e-03, 772 | "time.clang-tidy.hicpp-no-assembler.sys": 2.6750000000006491e-03, 773 | "time.clang-tidy.cert-msc33-c.wall": 6.5572261810302734e-03, 774 | "time.clang-tidy.cert-msc33-c.user": 4.0230000000036625e-03, 775 | "time.clang-tidy.cert-msc33-c.sys": 2.4560000000029003e-03, 776 | "time.clang-tidy.modernize-use-noexcept.wall": 1.2166976928710938e-02, 777 | "time.clang-tidy.modernize-use-noexcept.user": 6.7120000000020497e-03, 778 | "time.clang-tidy.modernize-use-noexcept.sys": 5.2930000000015465e-03, 779 | "time.clang-tidy.cppcoreguidelines-owning-memory.wall": 5.2043199539184570e-02, 780 | "time.clang-tidy.cppcoreguidelines-owning-memory.user": 3.1492000000043596e-02, 781 | "time.clang-tidy.cppcoreguidelines-owning-memory.sys": 2.0631999999998429e-02, 782 | "time.clang-tidy.cppcoreguidelines-explicit-virtual-functions.wall": 3.2677650451660156e-03, 783 | "time.clang-tidy.cppcoreguidelines-explicit-virtual-functions.user": 1.8980000000055064e-03, 784 | "time.clang-tidy.cppcoreguidelines-explicit-virtual-functions.sys": 1.3859999999976669e-03, 785 | "time.clang-tidy.android-cloexec-inotify-init.wall": 3.7245750427246094e-03, 786 | "time.clang-tidy.android-cloexec-inotify-init.user": 2.3309999999989728e-03, 787 | "time.clang-tidy.android-cloexec-inotify-init.sys": 1.3330000000011388e-03, 788 | "time.clang-tidy.hicpp-named-parameter.wall": 6.1960220336914062e-03, 789 | "time.clang-tidy.hicpp-named-parameter.user": 3.5400000000005427e-03, 790 | "time.clang-tidy.hicpp-named-parameter.sys": 2.6050000000019669e-03, 791 | "time.clang-tidy.cppcoreguidelines-avoid-c-arrays.wall": 3.7666797637939453e-02, 792 | "time.clang-tidy.cppcoreguidelines-avoid-c-arrays.user": 2.1304000000001544e-02, 793 | "time.clang-tidy.cppcoreguidelines-avoid-c-arrays.sys": 1.6361000000006065e-02, 794 | "time.clang-tidy.readability-container-data-pointer.wall": 9.4461441040039062e-04, 795 | "time.clang-tidy.readability-container-data-pointer.user": 5.8199999999697383e-04, 796 | "time.clang-tidy.readability-container-data-pointer.sys": 3.4700000000209563e-04, 797 | "time.clang-tidy.hicpp-static-assert.wall": 3.0999183654785156e-03, 798 | "time.clang-tidy.hicpp-static-assert.user": 2.0669999999984867e-03, 799 | "time.clang-tidy.hicpp-static-assert.sys": 1.0679999999994028e-03, 800 | "time.clang-tidy.performance-implicit-conversion-in-loop.wall": 2.6226043701171875e-06, 801 | "time.clang-tidy.performance-implicit-conversion-in-loop.user": 4.0000000005591119e-06, 802 | "time.clang-tidy.performance-implicit-conversion-in-loop.sys": 0.0000000000000000e+00, 803 | "time.clang-tidy.bugprone-string-integer-assignment.wall": 6.8616867065429688e-04, 804 | "time.clang-tidy.bugprone-string-integer-assignment.user": 4.3599999999788253e-04, 805 | "time.clang-tidy.bugprone-string-integer-assignment.sys": 2.3300000000014975e-04, 806 | "time.clang-tidy.modernize-make-unique.wall": 6.3467025756835938e-04, 807 | "time.clang-tidy.modernize-make-unique.user": 4.0899999999854941e-04, 808 | "time.clang-tidy.modernize-make-unique.sys": 2.1900000000063535e-04, 809 | "time.clang-tidy.bugprone-copy-constructor-init.wall": 5.7244300842285156e-04, 810 | "time.clang-tidy.bugprone-copy-constructor-init.user": 3.3100000000141350e-04, 811 | "time.clang-tidy.bugprone-copy-constructor-init.sys": 2.4000000000179433e-04, 812 | "time.clang-tidy.readability-isolate-declaration.wall": 6.8521499633789062e-04, 813 | "time.clang-tidy.readability-isolate-declaration.user": 4.5400000000039853e-04, 814 | "time.clang-tidy.readability-isolate-declaration.sys": 2.0499999999867846e-04, 815 | "time.clang-tidy.llvm-prefer-isa-or-dyn-cast-in-conditionals.wall": 3.4470319747924805e-02, 816 | "time.clang-tidy.llvm-prefer-isa-or-dyn-cast-in-conditionals.user": 2.0990999999995541e-02, 817 | "time.clang-tidy.llvm-prefer-isa-or-dyn-cast-in-conditionals.sys": 1.3323000000007301e-02, 818 | "time.clang-tidy.hicpp-new-delete-operators.wall": 4.9932003021240234e-03, 819 | "time.clang-tidy.hicpp-new-delete-operators.user": 2.8349999999921494e-03, 820 | "time.clang-tidy.hicpp-new-delete-operators.sys": 2.1590000000009102e-03, 821 | "time.clang-tidy.readability-redundant-string-init.wall": 8.8369846343994141e-03, 822 | "time.clang-tidy.readability-redundant-string-init.user": 5.0049999999979278e-03, 823 | "time.clang-tidy.readability-redundant-string-init.sys": 3.8999999999984603e-03, 824 | "time.clang-tidy.bugprone-easily-swappable-parameters.wall": 2.1511793136596680e-02, 825 | "time.clang-tidy.bugprone-easily-swappable-parameters.user": 1.3109999999998401e-02, 826 | "time.clang-tidy.bugprone-easily-swappable-parameters.sys": 8.3879999999922905e-03, 827 | "time.clang-tidy.readability-convert-member-functions-to-static.wall": 3.2825469970703125e-03, 828 | "time.clang-tidy.readability-convert-member-functions-to-static.user": 1.8570000000086573e-03, 829 | "time.clang-tidy.readability-convert-member-functions-to-static.sys": 1.3660000000064176e-03, 830 | "time.clang-tidy.abseil-time-subtraction.wall": 8.7747573852539062e-03, 831 | "time.clang-tidy.abseil-time-subtraction.user": 5.5140000000037936e-03, 832 | "time.clang-tidy.abseil-time-subtraction.sys": 3.2539999999949831e-03, 833 | "time.clang-tidy.readability-uppercase-literal-suffix.wall": 3.1664848327636719e-02, 834 | "time.clang-tidy.readability-uppercase-literal-suffix.user": 1.9245999999986552e-02, 835 | "time.clang-tidy.readability-uppercase-literal-suffix.sys": 1.2213000000010910e-02, 836 | "time.clang-tidy.bugprone-suspicious-enum-usage.wall": 2.3319721221923828e-03, 837 | "time.clang-tidy.bugprone-suspicious-enum-usage.user": 1.3779999999949943e-03, 838 | "time.clang-tidy.bugprone-suspicious-enum-usage.sys": 9.4700000000425000e-04, 839 | "time.clang-tidy.modernize-loop-convert.wall": 4.6801567077636719e-04, 840 | "time.clang-tidy.modernize-loop-convert.user": 3.1399999999903727e-04, 841 | "time.clang-tidy.modernize-loop-convert.sys": 1.5600000000026704e-04, 842 | "time.clang-tidy.bugprone-unchecked-optional-access.wall": 1.9855499267578125e-02, 843 | "time.clang-tidy.bugprone-unchecked-optional-access.user": 1.1293000000000664e-02, 844 | "time.clang-tidy.bugprone-unchecked-optional-access.sys": 8.4130000000088856e-03, 845 | "time.clang-tidy.google-readability-casting.wall": 4.0564537048339844e-03, 846 | "time.clang-tidy.google-readability-casting.user": 2.5680000000036785e-03, 847 | "time.clang-tidy.google-readability-casting.sys": 1.5149999999997110e-03, 848 | "time.clang-tidy.abseil-duration-conversion-cast.wall": 2.5171041488647461e-02, 849 | "time.clang-tidy.abseil-duration-conversion-cast.user": 1.5146999999977151e-02, 850 | "time.clang-tidy.abseil-duration-conversion-cast.sys": 9.9599999999924194e-03, 851 | "time.clang-tidy.readability-suspicious-call-argument.wall": 1.9197225570678711e-02, 852 | "time.clang-tidy.readability-suspicious-call-argument.user": 1.1296000000008632e-02, 853 | "time.clang-tidy.readability-suspicious-call-argument.sys": 8.0550000000003674e-03, 854 | "time.clang-tidy.google-default-arguments.wall": 3.5181045532226562e-03, 855 | "time.clang-tidy.google-default-arguments.user": 1.8879999999916741e-03, 856 | "time.clang-tidy.google-default-arguments.sys": 1.4850000000019570e-03, 857 | "time.clang-tidy.readability-uniqueptr-delete-release.wall": 7.4148178100585938e-05, 858 | "time.clang-tidy.readability-uniqueptr-delete-release.user": 3.7999999999982492e-05, 859 | "time.clang-tidy.readability-uniqueptr-delete-release.sys": 3.5000000000007248e-05, 860 | "time.clang-tidy.cppcoreguidelines-avoid-const-or-ref-data-members.wall": 5.1593780517578125e-04, 861 | "time.clang-tidy.cppcoreguidelines-avoid-const-or-ref-data-members.user": 2.2199999999861220e-04, 862 | "time.clang-tidy.cppcoreguidelines-avoid-const-or-ref-data-members.sys": 2.7800000000111069e-04, 863 | "time.clang-tidy.modernize-use-trailing-return-type.wall": 7.0708990097045898e-02, 864 | "time.clang-tidy.modernize-use-trailing-return-type.user": 4.0364000000002065e-02, 865 | "time.clang-tidy.modernize-use-trailing-return-type.sys": 3.0322000000001070e-02, 866 | "time.clang-tidy.readability-redundant-function-ptr-dereference.wall": 1.2276172637939453e-03, 867 | "time.clang-tidy.readability-redundant-function-ptr-dereference.user": 7.6700000000284874e-04, 868 | "time.clang-tidy.readability-redundant-function-ptr-dereference.sys": 4.7500000000044729e-04, 869 | "time.clang-tidy.cppcoreguidelines-pro-type-const-cast.wall": 2.4318695068359375e-05, 870 | "time.clang-tidy.cppcoreguidelines-pro-type-const-cast.user": 2.7000000000221291e-05, 871 | "time.clang-tidy.cppcoreguidelines-pro-type-const-cast.sys": 0.0000000000000000e+00, 872 | "time.clang-tidy.modernize-avoid-c-arrays.wall": 3.6789417266845703e-02, 873 | "time.clang-tidy.modernize-avoid-c-arrays.user": 2.0782999999990448e-02, 874 | "time.clang-tidy.modernize-avoid-c-arrays.sys": 1.5802999999990686e-02, 875 | "time.clang-tidy.misc-unconventional-assign-operator.wall": 1.0016441345214844e-02, 876 | "time.clang-tidy.misc-unconventional-assign-operator.user": 5.8619999999978134e-03, 877 | "time.clang-tidy.misc-unconventional-assign-operator.sys": 4.2350000000015431e-03, 878 | "time.clang-tidy.readability-qualified-auto.wall": 3.3955574035644531e-03, 879 | "time.clang-tidy.readability-qualified-auto.user": 2.2970000000017698e-03, 880 | "time.clang-tidy.readability-qualified-auto.sys": 1.0840000000014172e-03, 881 | "time.clang-tidy.concurrency-mt-unsafe.wall": 1.4573574066162109e-02, 882 | "time.clang-tidy.concurrency-mt-unsafe.user": 9.1530000000106249e-03, 883 | "time.clang-tidy.concurrency-mt-unsafe.sys": 5.3749999999990195e-03, 884 | "time.clang-tidy.linuxkernel-must-check-errs.wall": 8.1057548522949219e-03, 885 | "time.clang-tidy.linuxkernel-must-check-errs.user": 5.0750000000019391e-03, 886 | "time.clang-tidy.linuxkernel-must-check-errs.sys": 2.9070000000017693e-03, 887 | "time.clang-tidy.bugprone-integer-division.wall": 1.5993118286132812e-03, 888 | "time.clang-tidy.bugprone-integer-division.user": 9.6600000000623965e-04, 889 | "time.clang-tidy.bugprone-integer-division.sys": 6.6099999999735815e-04, 890 | "time.clang-tidy.hicpp-invalid-access-moved.wall": 6.3853502273559570e-02, 891 | "time.clang-tidy.hicpp-invalid-access-moved.user": 3.9818000000033660e-02, 892 | "time.clang-tidy.hicpp-invalid-access-moved.sys": 2.4359000000005349e-02, 893 | "time.clang-tidy.misc-include-cleaner.wall": 4.9948692321777344e-04, 894 | "time.clang-tidy.misc-include-cleaner.user": 4.9800000000033151e-04, 895 | "time.clang-tidy.misc-include-cleaner.sys": 1.0000000001397780e-06, 896 | "time.clang-tidy.google-readability-function-size.wall": 9.4814300537109375e-03, 897 | "time.clang-tidy.google-readability-function-size.user": 5.8889999999918174e-03, 898 | "time.clang-tidy.google-readability-function-size.sys": 3.4319999999954387e-03, 899 | "time.clang-tidy.cppcoreguidelines-slicing.wall": 4.9498081207275391e-03, 900 | "time.clang-tidy.cppcoreguidelines-slicing.user": 3.1010000000049054e-03, 901 | "time.clang-tidy.cppcoreguidelines-slicing.sys": 1.8049999999969479e-03, 902 | "time.clang-tidy.bugprone-posix-return.wall": 1.8124580383300781e-03, 903 | "time.clang-tidy.bugprone-posix-return.user": 1.0529999999961959e-03, 904 | "time.clang-tidy.bugprone-posix-return.sys": 7.4199999999602362e-04, 905 | "time.clang-tidy.bugprone-argument-comment.wall": 4.7996044158935547e-03, 906 | "time.clang-tidy.bugprone-argument-comment.user": 3.0919999999921011e-03, 907 | "time.clang-tidy.bugprone-argument-comment.sys": 1.7150000000023535e-03, 908 | "time.clang-tidy.cppcoreguidelines-pro-type-member-init.wall": 1.4810085296630859e-02, 909 | "time.clang-tidy.cppcoreguidelines-pro-type-member-init.user": 8.3009999999856809e-03, 910 | "time.clang-tidy.cppcoreguidelines-pro-type-member-init.sys": 6.4790000000025660e-03, 911 | "time.clang-tidy.cert-dcl54-cpp.wall": 5.3670406341552734e-03, 912 | "time.clang-tidy.cert-dcl54-cpp.user": 3.0370000000221609e-03, 913 | "time.clang-tidy.cert-dcl54-cpp.sys": 2.2560000000035885e-03, 914 | "time.clang-tidy.darwin-avoid-spinlock.wall": 3.2033920288085938e-03, 915 | "time.clang-tidy.darwin-avoid-spinlock.user": 2.0459999999906664e-03, 916 | "time.clang-tidy.darwin-avoid-spinlock.sys": 1.1339999999988581e-03, 917 | "time.clang-tidy.abseil-time-comparison.wall": 1.8851757049560547e-03, 918 | "time.clang-tidy.abseil-time-comparison.user": 1.1159999999974524e-03, 919 | "time.clang-tidy.abseil-time-comparison.sys": 7.9900000000399096e-04, 920 | "time.clang-tidy.modernize-unary-static-assert.wall": 1.0418891906738281e-04, 921 | "time.clang-tidy.modernize-unary-static-assert.user": 6.3999999999619916e-05, 922 | "time.clang-tidy.modernize-unary-static-assert.sys": 3.9999999999595914e-05, 923 | "time.clang-tidy.hicpp-explicit-conversions.wall": 1.4524459838867188e-03, 924 | "time.clang-tidy.hicpp-explicit-conversions.user": 9.1399999999985937e-04, 925 | "time.clang-tidy.hicpp-explicit-conversions.sys": 5.4299999999951609e-04, 926 | "time.clang-tidy.cert-env33-c.wall": 3.4286975860595703e-03, 927 | "time.clang-tidy.cert-env33-c.user": 2.1809999999979901e-03, 928 | "time.clang-tidy.cert-env33-c.sys": 1.2350000000005412e-03, 929 | "time.clang-tidy.cert-msc50-cpp.wall": 3.1504631042480469e-03, 930 | "time.clang-tidy.cert-msc50-cpp.user": 2.0140000000061775e-03, 931 | "time.clang-tidy.cert-msc50-cpp.sys": 1.1269999999983238e-03, 932 | "time.clang-tidy.modernize-use-default-member-init.wall": 1.0023117065429688e-03, 933 | "time.clang-tidy.modernize-use-default-member-init.user": 6.3199999999730139e-04, 934 | "time.clang-tidy.modernize-use-default-member-init.sys": 3.5599999999869070e-04, 935 | "time.clang-tidy.bugprone-narrowing-conversions.wall": 8.7285041809082031e-03, 936 | "time.clang-tidy.bugprone-narrowing-conversions.user": 5.0510000000083544e-03, 937 | "time.clang-tidy.bugprone-narrowing-conversions.sys": 3.5849999999948423e-03, 938 | "time.clang-tidy.bugprone-implicit-widening-of-multiplication-result.wall": 1.4597177505493164e-02, 939 | "time.clang-tidy.bugprone-implicit-widening-of-multiplication-result.user": 8.3929999999980964e-03, 940 | "time.clang-tidy.bugprone-implicit-widening-of-multiplication-result.sys": 6.2750000000018069e-03, 941 | "time.clang-tidy.modernize-use-equals-delete.wall": 3.2958984375000000e-03, 942 | "time.clang-tidy.modernize-use-equals-delete.user": 1.8479999999980734e-03, 943 | "time.clang-tidy.modernize-use-equals-delete.sys": 1.3729999999978482e-03, 944 | "time.clang-tidy.bugprone-lambda-function-name.wall": 1.6689300537109375e-06, 945 | "time.clang-tidy.bugprone-lambda-function-name.user": 1.0000000001397780e-06, 946 | "time.clang-tidy.bugprone-lambda-function-name.sys": 0.0000000000000000e+00, 947 | "time.clang-tidy.abseil-faster-strsplit-delimiter.wall": 4.6725273132324219e-03, 948 | "time.clang-tidy.abseil-faster-strsplit-delimiter.user": 2.9979999999962814e-03, 949 | "time.clang-tidy.abseil-faster-strsplit-delimiter.sys": 1.6999999999989246e-03, 950 | "time.clang-tidy.readability-use-anyofallof.wall": 3.3378601074218750e-06, 951 | "time.clang-tidy.readability-use-anyofallof.user": 3.0000000004193339e-06, 952 | "time.clang-tidy.readability-use-anyofallof.sys": 1.0000000001397780e-06, 953 | "time.clang-tidy.readability-function-cognitive-complexity.wall": 8.6231231689453125e-03, 954 | "time.clang-tidy.readability-function-cognitive-complexity.user": 5.3809999999936409e-03, 955 | "time.clang-tidy.readability-function-cognitive-complexity.sys": 3.2820000000000071e-03, 956 | "time.clang-tidy.performance-for-range-copy.wall": 2.6226043701171875e-06, 957 | "time.clang-tidy.performance-for-range-copy.user": 1.9999999998354667e-06, 958 | "time.clang-tidy.performance-for-range-copy.sys": 0.0000000000000000e+00, 959 | "time.clang-tidy.performance-noexcept-move-constructor.wall": 2.8913021087646484e-03, 960 | "time.clang-tidy.performance-noexcept-move-constructor.user": 1.6869999999986618e-03, 961 | "time.clang-tidy.performance-noexcept-move-constructor.sys": 1.1979999999969237e-03, 962 | "time.clang-tidy.readability-delete-null-pointer.wall": 1.3563632965087891e-03, 963 | "time.clang-tidy.readability-delete-null-pointer.user": 9.2699999999856786e-04, 964 | "time.clang-tidy.readability-delete-null-pointer.sys": 4.1499999999961013e-04, 965 | "time.clang-tidy.cert-dcl51-cpp.wall": 1.6449522972106934e-01, 966 | "time.clang-tidy.cert-dcl51-cpp.user": 1.6057600000000027e-01, 967 | "time.clang-tidy.cert-dcl51-cpp.sys": 3.2499999999999751e-03, 968 | "time.clang-tidy.fuchsia-multiple-inheritance.wall": 2.1698474884033203e-03, 969 | "time.clang-tidy.fuchsia-multiple-inheritance.user": 1.2760000000020533e-03, 970 | "time.clang-tidy.fuchsia-multiple-inheritance.sys": 8.8800000000177626e-04, 971 | "time.clang-tidy.readability-named-parameter.wall": 7.2009563446044922e-03, 972 | "time.clang-tidy.readability-named-parameter.user": 4.0790000000021642e-03, 973 | "time.clang-tidy.readability-named-parameter.sys": 3.1630000000024694e-03, 974 | "time.clang-tidy.misc-misplaced-const.wall": 1.1611223220825195e-02, 975 | "time.clang-tidy.misc-misplaced-const.user": 6.5279999999998672e-03, 976 | "time.clang-tidy.misc-misplaced-const.sys": 5.0059999999974014e-03, 977 | "time.clang-tidy.bugprone-parent-virtual-call.wall": 7.2622299194335938e-04, 978 | "time.clang-tidy.bugprone-parent-virtual-call.user": 4.7500000000022524e-04, 979 | "time.clang-tidy.bugprone-parent-virtual-call.sys": 2.6099999999962264e-04, 980 | "time.clang-tidy.readability-simplify-subscript-expr.wall": 2.0503997802734375e-04, 981 | "time.clang-tidy.readability-simplify-subscript-expr.user": 1.2000000000034206e-04, 982 | "time.clang-tidy.readability-simplify-subscript-expr.sys": 8.7000000000836408e-05, 983 | "time.clang-tidy.mpi-buffer-deref.wall": 3.5655498504638672e-03, 984 | "time.clang-tidy.mpi-buffer-deref.user": 2.3030000000026085e-03, 985 | "time.clang-tidy.mpi-buffer-deref.sys": 1.2579999999982050e-03, 986 | "time.clang-tidy.llvm-prefer-register-over-unsigned.wall": 7.6169967651367188e-03, 987 | "time.clang-tidy.llvm-prefer-register-over-unsigned.user": 4.3119999999969849e-03, 988 | "time.clang-tidy.llvm-prefer-register-over-unsigned.sys": 3.2699999999963314e-03, 989 | "time.clang-tidy.hicpp-no-array-decay.wall": 8.0807209014892578e-03, 990 | "time.clang-tidy.hicpp-no-array-decay.user": 4.8000000000048004e-03, 991 | "time.clang-tidy.hicpp-no-array-decay.sys": 3.3369999999963706e-03, 992 | "time.clang-tidy.cert-err33-c.wall": 3.6635875701904297e-02, 993 | "time.clang-tidy.cert-err33-c.user": 2.2372000000029590e-02, 994 | "time.clang-tidy.cert-err33-c.sys": 1.4024999999994847e-02, 995 | "time.clang-tidy.bugprone-suspicious-missing-comma.wall": 1.1563301086425781e-04, 996 | "time.clang-tidy.bugprone-suspicious-missing-comma.user": 6.8999999999430628e-05, 997 | "time.clang-tidy.bugprone-suspicious-missing-comma.sys": 4.7000000000130271e-05, 998 | "time.clang-tidy.bugprone-suspicious-string-compare.wall": 3.5453081130981445e-02, 999 | "time.clang-tidy.bugprone-suspicious-string-compare.user": 2.1489000000038061e-02, 1000 | "time.clang-tidy.bugprone-suspicious-string-compare.sys": 1.3785999999999410e-02, 1001 | "time.clang-tidy.abseil-duration-factory-scale.wall": 3.2606124877929688e-03, 1002 | "time.clang-tidy.abseil-duration-factory-scale.user": 2.0719999999907479e-03, 1003 | "time.clang-tidy.abseil-duration-factory-scale.sys": 1.1459999999976489e-03, 1004 | "time.clang-tidy.bugprone-misplaced-widening-cast.wall": 1.6383409500122070e-02, 1005 | "time.clang-tidy.bugprone-misplaced-widening-cast.user": 1.0036000000018142e-02, 1006 | "time.clang-tidy.bugprone-misplaced-widening-cast.sys": 6.3860000000006689e-03, 1007 | "time.clang-tidy.hicpp-exception-baseclass.wall": 8.9406967163085938e-05, 1008 | "time.clang-tidy.hicpp-exception-baseclass.user": 6.0000000000837161e-05, 1009 | "time.clang-tidy.hicpp-exception-baseclass.sys": 2.7999999999916980e-05, 1010 | "time.clang-tidy.cert-oop11-cpp.wall": 6.0558319091796875e-04, 1011 | "time.clang-tidy.cert-oop11-cpp.user": 3.4200000000073061e-04, 1012 | "time.clang-tidy.cert-oop11-cpp.sys": 2.6800000000104518e-04, 1013 | "time.clang-tidy.hicpp-multiway-paths-covered.wall": 3.8146972656250000e-06, 1014 | "time.clang-tidy.hicpp-multiway-paths-covered.user": 4.0000000001150227e-06, 1015 | "time.clang-tidy.hicpp-multiway-paths-covered.sys": 0.0000000000000000e+00, 1016 | "time.clang-tidy.misc-confusable-identifiers.wall": 3.9042472839355469e-02, 1017 | "time.clang-tidy.misc-confusable-identifiers.user": 2.3698999999978376e-02, 1018 | "time.clang-tidy.misc-confusable-identifiers.sys": 1.5578000000010528e-02, 1019 | "time.clang-tidy.bugprone-unhandled-exception-at-new.wall": 4.4822692871093750e-05, 1020 | "time.clang-tidy.bugprone-unhandled-exception-at-new.user": 3.3000000000171781e-05, 1021 | "time.clang-tidy.bugprone-unhandled-exception-at-new.sys": 1.3000000000040757e-05, 1022 | "time.clang-tidy.modernize-raw-string-literal.wall": 3.0064582824707031e-04, 1023 | "time.clang-tidy.modernize-raw-string-literal.user": 1.9599999999808659e-04, 1024 | "time.clang-tidy.modernize-raw-string-literal.sys": 1.0100000000012876e-04, 1025 | "time.clang-tidy.abseil-duration-unnecessary-conversion.wall": 6.6182613372802734e-03, 1026 | "time.clang-tidy.abseil-duration-unnecessary-conversion.user": 4.2790000000074713e-03, 1027 | "time.clang-tidy.abseil-duration-unnecessary-conversion.sys": 2.3719999999993746e-03, 1028 | "time.clang-tidy.misc-misleading-identifier.wall": 2.2807598114013672e-02, 1029 | "time.clang-tidy.misc-misleading-identifier.user": 1.3155999999985291e-02, 1030 | "time.clang-tidy.misc-misleading-identifier.sys": 9.7309999999939389e-03, 1031 | "time.clang-tidy.bugprone-inaccurate-erase.wall": 5.0163269042968750e-04, 1032 | "time.clang-tidy.bugprone-inaccurate-erase.user": 3.1699999999945661e-04, 1033 | "time.clang-tidy.bugprone-inaccurate-erase.sys": 1.8500000000254424e-04, 1034 | "time.clang-tidy.bugprone-bad-signal-to-kill-thread.wall": 3.1561851501464844e-03, 1035 | "time.clang-tidy.bugprone-bad-signal-to-kill-thread.user": 1.9649999999988843e-03, 1036 | "time.clang-tidy.bugprone-bad-signal-to-kill-thread.sys": 1.1260000000028469e-03, 1037 | "time.clang-tidy.llvm-qualified-auto.wall": 1.2001991271972656e-03, 1038 | "time.clang-tidy.llvm-qualified-auto.user": 8.1099999999256767e-04, 1039 | "time.clang-tidy.llvm-qualified-auto.sys": 3.8699999999591839e-04, 1040 | "time.clang-tidy.cppcoreguidelines-pro-bounds-constant-array-index.wall": 1.3384819030761719e-03, 1041 | "time.clang-tidy.cppcoreguidelines-pro-bounds-constant-array-index.user": 8.1900000000345585e-04, 1042 | "time.clang-tidy.cppcoreguidelines-pro-bounds-constant-array-index.sys": 5.1900000000015822e-04, 1043 | "time.clang-tidy.misc-unused-parameters.wall": 4.6737194061279297e-03, 1044 | "time.clang-tidy.misc-unused-parameters.user": 2.6920000000032474e-03, 1045 | "time.clang-tidy.misc-unused-parameters.sys": 2.0019999999996152e-03, 1046 | "time.clang-tidy.modernize-use-auto.wall": 4.8241615295410156e-03, 1047 | "time.clang-tidy.modernize-use-auto.user": 3.2390000000086516e-03, 1048 | "time.clang-tidy.modernize-use-auto.sys": 1.6020000000001033e-03, 1049 | "time.clang-tidy.modernize-replace-auto-ptr.wall": 3.7234544754028320e-02, 1050 | "time.clang-tidy.modernize-replace-auto-ptr.user": 2.1313999999993172e-02, 1051 | "time.clang-tidy.modernize-replace-auto-ptr.sys": 1.5913999999999984e-02, 1052 | "time.clang-tidy.modernize-use-equals-default.wall": 4.7051906585693359e-03, 1053 | "time.clang-tidy.modernize-use-equals-default.user": 2.7880000000055638e-03, 1054 | "time.clang-tidy.modernize-use-equals-default.sys": 1.8970000000018139e-03, 1055 | "time.clang-tidy.cppcoreguidelines-pro-bounds-pointer-arithmetic.wall": 3.0293464660644531e-03, 1056 | "time.clang-tidy.cppcoreguidelines-pro-bounds-pointer-arithmetic.user": 1.7829999999943169e-03, 1057 | "time.clang-tidy.cppcoreguidelines-pro-bounds-pointer-arithmetic.sys": 1.1999999999996458e-03, 1058 | "time.clang-tidy.cert-err58-cpp.wall": 9.1676712036132812e-03, 1059 | "time.clang-tidy.cert-err58-cpp.user": 5.5640000000054535e-03, 1060 | "time.clang-tidy.cert-err58-cpp.sys": 3.6419999999965924e-03, 1061 | "time.clang-tidy.readability-redundant-declaration.wall": 2.4203538894653320e-02, 1062 | "time.clang-tidy.readability-redundant-declaration.user": 1.3489000000008744e-02, 1063 | "time.clang-tidy.readability-redundant-declaration.sys": 1.0371000000003239e-02, 1064 | "time.clang-tidy.cert-err61-cpp.wall": 9.5129013061523438e-05, 1065 | "time.clang-tidy.cert-err61-cpp.user": 7.3999999997909072e-05, 1066 | "time.clang-tidy.cert-err61-cpp.sys": 2.5000000000829914e-05, 1067 | "time.clang-tidy.misc-throw-by-value-catch-by-reference.wall": 7.9154968261718750e-05, 1068 | "time.clang-tidy.misc-throw-by-value-catch-by-reference.user": 6.3999999998731738e-05, 1069 | "time.clang-tidy.misc-throw-by-value-catch-by-reference.sys": 1.7999999999629424e-05 1070 | } 1071 | } 1072 | --------------------------------------------------------------------------------