├── security.md ├── query ├── free-disk-space.sh ├── filter.sh ├── download-artifacts.sh ├── query.sh └── summary.sh ├── .github └── workflows │ ├── monthly.yml │ ├── metadata.yml │ ├── summary.yml │ ├── scan-build.yml │ ├── infer.yml │ ├── ossf-scorecard.yml │ ├── query.yml │ └── srs.yml ├── scan-build ├── Dockerfile ├── README.md └── scan-build.sh ├── COPYING ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md └── infer └── infer.sh /security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /query/free-disk-space.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo systemd-run docker system prune --force --all --volumes 3 | sudo systemd-run rm -rf \ 4 | "$AGENT_TOOLSDIRECTORY" \ 5 | /opt/* \ 6 | /usr/local/* \ 7 | /usr/share/az* \ 8 | /usr/share/dotnet \ 9 | /usr/share/gradle* \ 10 | /usr/share/miniconda \ 11 | /usr/share/swift \ 12 | /var/lib/gems \ 13 | /var/lib/mysql \ 14 | /var/lib/snapd 15 | sudo apt-get -y autoremove 16 | sudo apt-get clean 17 | df -h / 18 | -------------------------------------------------------------------------------- /.github/workflows/monthly.yml: -------------------------------------------------------------------------------- 1 | name: Trigger monthly scan 2 | permissions: read-all 3 | on: 4 | schedule: 5 | - cron: '0 0 1 * *' 6 | jobs: 7 | build: 8 | name: Trigger monthly scan 9 | runs-on: ubuntu-latest 10 | env: 11 | GH_TOKEN: ${{ secrets.GHPAT }} 12 | steps: 13 | - name: checkout repo 14 | uses: actions/checkout@v4 15 | 16 | - run: gh workflow run query.yml -f search="archived:false language:c language:c++" -f minstars="400" -f increment="50" 17 | -------------------------------------------------------------------------------- /query/filter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPOSJSON=${1:-"repos.json"} 4 | 5 | search() { 6 | BUILD=$1 7 | jq ".repos[] | select( any(.; .repo.languages.nodes[].name == \"$BUILD\") )" $REPOSJSON | jq -r '.repo.nameWithOwner' >> matching-repos.txt 8 | } 9 | 10 | if [ ! -f $REPOSJSON ]; then 11 | echo "Please specify input (repos.json)" 12 | exit 1 13 | fi 14 | 15 | rm matching-repos.txt || : 16 | 17 | search M4 18 | search CMake 19 | search Meson 20 | 21 | REPOS=$(cat matching-repos.txt | sort -u) 22 | 23 | JSON="[" 24 | for repo in $REPOS; do 25 | JSON+="{\"repo\":\"$repo\"}," 26 | done 27 | JSON="${JSON%?}" 28 | JSON+="]" 29 | 30 | echo $JSON 31 | -------------------------------------------------------------------------------- /scan-build/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DISTRO=debian:testing-slim 2 | FROM $DISTRO 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | ENV USER root 6 | 7 | COPY scan-build.sh /scan-build.sh 8 | 9 | RUN apt-get update && apt-get install -y \ 10 | build-essential pkg-config coreutils binutils \ 11 | autoconf autoconf-archive automake autopoint \ 12 | meson ninja-build nasm yasm \ 13 | cmake cmake-data cmake-extras \ 14 | gettext bison flex libtool libssl-dev \ 15 | wget curl libz3-dev time jq tar sudo bc \ 16 | python3 libpython3-dev libboost-all-dev \ 17 | gnupg lsb-release software-properties-common git \ 18 | clang-15 clang-tools-15 clang-tidy-15 \ 19 | && apt-get clean \ 20 | && chmod +x /scan-build.sh \ 21 | && git config --global --add safe.directory '*' 22 | 23 | ENTRYPOINT ["/scan-build.sh"] 24 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Intel Corporation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/metadata.yml: -------------------------------------------------------------------------------- 1 | name: Collect metadata about repository and lines-of-code 2 | permissions: read-all 3 | on: 4 | workflow_call: 5 | inputs: 6 | repo: 7 | description: 'repo' 8 | required: true 9 | default: '' 10 | type: string 11 | rate-limit: 12 | description: 'rate limit' 13 | required: false 14 | default: 150 15 | type: number 16 | secrets: 17 | GHPAT: 18 | required: true 19 | 20 | jobs: 21 | metadata: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: rate limit 26 | env: 27 | GHPAT: ${{ secrets.GHPAT }} 28 | run: | 29 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 30 | echo "Rate limit remaining: $rl" 31 | while [ $rl -lt ${{ inputs.rate-limit }} ]; do 32 | sleep 1h 33 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 34 | echo "Rate limit remaining: $rl" 35 | done 36 | 37 | - name: generate save directory name 38 | shell: bash 39 | id: vars 40 | run: | 41 | SREPO=$(echo -n ${{ inputs.repo }} | tr '/' .) 42 | echo "SREPO set to ${SREPO}" 43 | echo "SREPO=${SREPO}" >> $GITHUB_OUTPUT 44 | 45 | - name: checkout repo 46 | uses: actions/checkout@v4 47 | with: 48 | repository: ${{ inputs.repo }} 49 | token: ${{ secrets.GHPAT }} 50 | 51 | - name: run cloc 52 | run: | 53 | sudo apt-get update 54 | sudo apt-get install -y cloc 55 | cloc --json --report-file ${{ steps.vars.outputs.SREPO }}.cloc.json . 56 | 57 | - name: get metadata 58 | env: 59 | GH_TOKEN: ${{ secrets.GHPAT }} 60 | run: | 61 | gh api -H "Accept: application/vnd.github+json" /repos/${{ inputs.repo }} > ${{ steps.vars.outputs.SREPO }}.metadata.json 62 | 63 | - name: save results 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: ${{ steps.vars.outputs.SREPO }}.metadata 67 | path: | 68 | ${{ steps.vars.outputs.SREPO }}.cloc.json 69 | ${{ steps.vars.outputs.SREPO }}.metadata.json 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### License 4 | 5 | Scaling Repo Scanner is licensed under the terms of the [MIT LICENSE]. By contributing to the project, you agree to the license and copyright terms therein and release your contribution under these terms. 6 | 7 | ### Sign your work 8 | 9 | Please use the sign-off line at the end of the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify 10 | the below (from [developercertificate.org](http://developercertificate.org/)): 11 | 12 | ``` 13 | Developer Certificate of Origin 14 | Version 1.1 15 | 16 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 17 | 660 York Street, Suite 102, 18 | San Francisco, CA 94110 USA 19 | 20 | Everyone is permitted to copy and distribute verbatim copies of this 21 | license document, but changing it is not allowed. 22 | 23 | Developer's Certificate of Origin 1.1 24 | 25 | By making a contribution to this project, I certify that: 26 | 27 | (a) The contribution was created in whole or in part by me and I 28 | have the right to submit it under the open source license 29 | indicated in the file; or 30 | 31 | (b) The contribution is based upon previous work that, to the best 32 | of my knowledge, is covered under an appropriate open source 33 | license and I have the right under that license to submit that 34 | work with modifications, whether created in whole or in part 35 | by me, under the same open source license (unless I am 36 | permitted to submit under a different license), as indicated 37 | in the file; or 38 | 39 | (c) The contribution was provided directly to me by some other 40 | person who certified (a), (b) or (c) and I have not modified 41 | it. 42 | 43 | (d) I understand and agree that this project and the contribution 44 | are public and that a record of the contribution (including all 45 | personal information I submit with it, including my sign-off) is 46 | maintained indefinitely and may be redistributed consistent with 47 | this project or the open source license(s) involved. 48 | ``` 49 | 50 | Then you just add a line to every git commit message: 51 | 52 | Signed-off-by: Joe Smith 53 | 54 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 55 | 56 | If you set your `user.name` and `user.email` git configs, you can sign your 57 | commit automatically with `git commit -s`. 58 | -------------------------------------------------------------------------------- /query/download-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | RUN_IDS=$1 3 | 4 | if [ -z $RUN_IDS ]; then 5 | echo "No run-id specified as input" 6 | exit 1 7 | fi 8 | 9 | if [ -z $GITHUB_REPOSITORY ]; then 10 | echo "No GITHUB_REPOSITORY env variable found" 11 | exit 1 12 | fi 13 | 14 | if [ -z $GH_TOKEN ]; then 15 | echo "No GH_TOKEN env variable found" 16 | exit 1 17 | fi 18 | 19 | rate_limit() { 20 | rl=$(gh api -H "Accept: application/vnd.github+json" /rate_limit | jq '.rate.remaining') 21 | echo "Rate limit remaining: $rl" 22 | while [ $rl -lt 150 ]; do 23 | sleep 1m 24 | rl=$(gh api -H "Accept: application/vnd.github+json" /rate_limit | jq '.rate.remaining') 25 | echo "Rate limit remaining: $rl" 26 | done 27 | } 28 | 29 | download_workflow_id() { 30 | RUN_ID=$1 31 | DEST=$2 32 | 33 | page=1 34 | got=0 35 | while [ true ]; do 36 | rate_limit 37 | gh api -H "Accept: application/vnd.github+json" "/repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts?per_page=100&page=$page" > $DEST/artifacts.$RUN_ID.$page.json 38 | 39 | total=$(jq '.total_count' $DEST/artifacts.$RUN_ID.1.json) 40 | got=$(( page * 100 )) 41 | (( page++ )) 42 | 43 | [[ $got -lt $total ]] && continue 44 | 45 | break 46 | done 47 | 48 | for a in $(ls $DEST/artifacts.$RUN_ID.*.json); do 49 | for b in $(jq -r '.artifacts[] | "\(.id),\(.name),\(.expired)"' $a); do 50 | id=$(echo $b | awk -F',' '{ print $1 }') 51 | name=$(echo $b | awk -F',' '{ print $2 }') 52 | expired=$(echo $b | awk -F',' '{ print $3 }') 53 | 54 | [[ $expired == "true" ]] && continue 55 | rate_limit 56 | gh api -H "Accept: application/vnd.github+json" /repos/$GITHUB_REPOSITORY/actions/artifacts/$id/zip > $DEST/$name.zip || continue 57 | mkdir -p $DEST/$name 58 | unzip -o -d $DEST/$name $DEST/$name.zip || continue 59 | rm $DEST/$name.zip 60 | 61 | if [ -f $DEST/$name/$name.tar.gz ]; then 62 | tar -xvf $DEST/$name/$name.tar.gz -C $DEST/$name --wildcards --no-anchored --strip-components 1 '*.scan-build.json' 63 | rm $DEST/$name/$name.tar.gz 64 | fi 65 | done 66 | done 67 | 68 | rm $DEST/artifacts.*.json 69 | } 70 | 71 | DEST=$(echo $RUN_IDS | awk -F',' '{ print $1 }') 72 | mkdir -p $DEST 73 | 74 | for RUN_ID in $(echo $RUN_IDS | tr "," "\n"); do 75 | download_workflow_id $RUN_ID $DEST 76 | done 77 | -------------------------------------------------------------------------------- /.github/workflows/summary.yml: -------------------------------------------------------------------------------- 1 | name: Aggregate results and create summary 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | run-ids: 7 | description: 'The workflow run-ids to look at' 8 | required: true 9 | type: string 10 | delay: 11 | description: 'Delay start to give previous workflow time to settle' 12 | required: false 13 | type: string 14 | jobs: 15 | delay: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: delay 19 | if: inputs.delay != '' 20 | run: sleep ${{ inputs.delay }} 21 | 22 | summary: 23 | needs: delay 24 | runs-on: ubuntu-latest 25 | env: 26 | GH_TOKEN: ${{ secrets.GHPAT }} 27 | steps: 28 | - name: checkout repo 29 | uses: actions/checkout@v4 30 | with: 31 | token: ${{ secrets.GHPAT }} 32 | 33 | - name: free disk space 34 | run: ./query/free-disk-space.sh 35 | 36 | - id: vars 37 | run: | 38 | firstid=$(echo "${{ inputs.run-ids }}" | awk -F',' '{ print $1 }') 39 | echo "firstid=$firstid" >> $GITHUB_OUTPUT 40 | 41 | - name: Download artifacts with rate limit 42 | run: ./query/download-artifacts.sh "${{ inputs.run-ids }}" 43 | 44 | - name: Summarize results 45 | run: ./query/summary.sh "${{ steps.vars.outputs.firstid }}" "${{ inputs.run-ids }}" 46 | 47 | - name: Github step summary 48 | run: cat summary.md >> $GITHUB_STEP_SUMMARY 49 | 50 | - name: save results 51 | uses: actions/upload-artifact@v4 52 | with: 53 | retention-days: 90 54 | name: aggregate-results 55 | path: | 56 | score_vs_bugs.csv 57 | summary.md 58 | all-results.tar.gz 59 | 60 | - name: save scan results to gh pages 61 | run: | 62 | git fetch origin gh-pages --depth 1 63 | git checkout gh-pages 64 | 65 | git config --global user.name ${{ github.actor }} 66 | git config --global user.email '${{ github.actor }}@github.com' 67 | 68 | cd=$(date +"%Y.%m.%d") 69 | year=$(date +"%Y") 70 | d="$cd-${{ steps.vars.outputs.firstid }}" 71 | built=$(cat stats | awk -F',' '{ print $1 }') 72 | scored=$(cat stats | awk -F',' '{ print $2 }') 73 | bugs=$(cat stats | awk -F',' '{ print $3 }') 74 | cmplxf=$(cat stats | awk -F',' '{ print $4 }') 75 | 76 | mkdir -p scans/$d 77 | 78 | cp ${{ steps.vars.outputs.firstid }}/query-repositories/filtered-repos.json scans/$d 79 | cp all-results.tar.gz scans/$d 80 | cp summary.csv scans/$d 81 | 82 | echo "---" > scans/$d/index.md 83 | echo "layout: default" >> scans/$d/index.md 84 | echo "---" >> scans/$d/index.md 85 | echo "" >> scans/$d/index.md 86 | 87 | echo "## Scan date: $cd" >> scans/$d/index.md 88 | echo "[All results (tar.gz)](./all-results.tar.gz) [Repo list (JSON)](./filtered-repos.json) [Summary (CSV)](./summary.csv)" >> scans/$d/index.md 89 | 90 | echo "" >> scans/$d/index.md 91 | echo "***" >> scans/$d/index.md 92 | echo "" >> scans/$d/index.md 93 | 94 | cat ${{ steps.vars.outputs.firstid }}/query-repositories/query-summary.md >> scans/$d/index.md 95 | cat summary.md >> scans/$d/index.md 96 | 97 | echo "[back](../..)" >> scans/$d/index.md 98 | 99 | echo "$d,$cd,$year,$bugs,$built,$scored,$cmplxf" >> _data/scans.csv 100 | 101 | git add scans/$d 102 | git add _data/scans.csv 103 | 104 | git commit -m "Add scan results of date $d" 105 | git push 106 | -------------------------------------------------------------------------------- /.github/workflows/scan-build.yml: -------------------------------------------------------------------------------- 1 | name: Run clang scan-build and clang-tidy cognitive complexity analysis 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: 'repo' 8 | required: true 9 | default: '' 10 | type: string 11 | rate-limit: 12 | description: 'rate limit' 13 | required: false 14 | default: 150 15 | type: number 16 | workflow_call: 17 | inputs: 18 | repo: 19 | description: 'repo' 20 | required: true 21 | default: '' 22 | type: string 23 | rate-limit: 24 | description: 'rate limit' 25 | required: false 26 | default: 150 27 | type: number 28 | secrets: 29 | GHPAT: 30 | required: true 31 | 32 | jobs: 33 | scan-build: 34 | runs-on: ubuntu-latest 35 | 36 | env: 37 | LLVM_VERSION: "15" 38 | TIMEOUT: "260m" 39 | 40 | defaults: 41 | run: 42 | shell: bash 43 | 44 | steps: 45 | - name: rate limit 46 | env: 47 | GHPAT: ${{ secrets.GHPAT }} 48 | run: | 49 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 50 | echo "Rate limit remaining: $rl" 51 | while [ $rl -lt ${{ inputs.rate-limit }} ]; do 52 | sleep 1h 53 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 54 | echo "Rate limit remaining: $rl" 55 | done 56 | 57 | - name: generate vars 58 | id: vars 59 | run: | 60 | srepo=$(echo ${{ inputs.repo }} | tr '/' .) 61 | echo "SREPO set to ${SREPO}" 62 | echo "SREPO=$srepo" >> $GITHUB_OUTPUT 63 | 64 | - name: checkout repo 65 | uses: actions/checkout@v4 66 | with: 67 | token: ${{ secrets.GHPAT }} 68 | 69 | - name: checkout target repo 70 | uses: actions/checkout@v4 71 | with: 72 | repository: ${{ inputs.repo }} 73 | token: ${{ secrets.GHPAT }} 74 | path: repo 75 | 76 | - name: Cache docker 77 | id: cache 78 | uses: actions/cache@v3 79 | with: 80 | path: scan-build.tar 81 | key: scan-build 82 | 83 | - name: Set up Docker Buildx 84 | if: steps.cache.outputs.cache-hit != 'true' 85 | uses: docker/setup-buildx-action@v3 86 | 87 | - name: Build production image 88 | if: steps.cache.outputs.cache-hit != 'true' 89 | uses: docker/build-push-action@v5 90 | with: 91 | context: ./scan-build 92 | file: ./scan-build/Dockerfile 93 | tags: scan-build:latest 94 | outputs: type=docker,dest=scan-build.tar 95 | cache-from: type=gha 96 | cache-to: type=gha,mode=max 97 | - name: Load image 98 | run: docker load -i scan-build.tar 99 | 100 | - name: scan-build 101 | timeout-minutes: 300 102 | run: docker run -v "$GITHUB_WORKSPACE/repo":/work scan-build -r "${{ inputs.repo }}" -l "$LLVM_VERSION" -t "$TIMEOUT" -o "${{ steps.vars.outputs.SREPO }}" 103 | 104 | - name: final steps 105 | if: ${{ always() }} 106 | run: | 107 | sudo mv $GITHUB_WORKSPACE/repo/${{ steps.vars.outputs.SREPO }} $GITHUB_WORKSPACE 108 | sudo chown -R $USER.$USER ${{ steps.vars.outputs.SREPO }} 109 | cat ${{ steps.vars.outputs.SREPO }}/summary.md >> $GITHUB_STEP_SUMMARY || : 110 | tar -czvf ${{ steps.vars.outputs.SREPO }}.${{ github.job }}.tar.gz ${{ steps.vars.outputs.SREPO }} 111 | 112 | - name: save results 113 | if: ${{ always() }} 114 | uses: actions/upload-artifact@v4 115 | with: 116 | name: ${{ steps.vars.outputs.SREPO }}.${{ github.job }} 117 | path: ${{ steps.vars.outputs.SREPO }}.${{ github.job }}.tar.gz 118 | -------------------------------------------------------------------------------- /.github/workflows/infer.yml: -------------------------------------------------------------------------------- 1 | name: Run Infer 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: 'repo' 8 | required: true 9 | default: '' 10 | type: string 11 | version: 12 | description: 'version' 13 | required: false 14 | default: '1.1.0' 15 | type: string 16 | rate-limit: 17 | description: 'rate limit' 18 | required: false 19 | default: 150 20 | type: number 21 | 22 | workflow_call: 23 | inputs: 24 | repo: 25 | description: 'repo' 26 | required: true 27 | default: '' 28 | type: string 29 | version: 30 | description: 'version' 31 | required: false 32 | default: '1.1.0' 33 | type: string 34 | rate-limit: 35 | description: 'rate limit' 36 | required: false 37 | default: 150 38 | type: number 39 | secrets: 40 | GHPAT: 41 | required: true 42 | 43 | jobs: 44 | infer: 45 | runs-on: ubuntu-latest 46 | env: 47 | VERSION: ${{ inputs.version }} 48 | 49 | steps: 50 | - name: rate limit 51 | env: 52 | GHPAT: ${{ secrets.GHPAT }} 53 | run: | 54 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 55 | echo "Rate limit remaining: $rl" 56 | while [ $rl -lt ${{ inputs.rate-limit }} ]; do 57 | sleep 1h 58 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 59 | echo "Rate limit remaining: $rl" 60 | done 61 | 62 | - name: generate save directory name 63 | shell: bash 64 | id: vars 65 | run: | 66 | SREPO=$(echo -n ${{ inputs.repo }} | tr '/' .) 67 | echo "SREPO set to ${SREPO}" 68 | echo "SREPO=${SREPO}" >> $GITHUB_OUTPUT 69 | 70 | - name: Cache infer 71 | id: cache 72 | uses: actions/cache@v4 73 | with: 74 | path: infer.tar.xz 75 | key: infer 76 | 77 | - name: process cached infer 78 | if: steps.cache.outputs.cache-hit == 'true' 79 | run: | 80 | sudo tar -C /opt -xvf infer.tar.xz 81 | sudo ln -s /opt/infer-linux64-v$VERSION/bin/infer /usr/local/bin 82 | mv infer.tar.xz /tmp 83 | 84 | - name: download infer 85 | if: steps.cache.outputs.cache-hit != 'true' 86 | run: | 87 | curl -sSL "https://github.com/facebook/infer/releases/download/v$VERSION/infer-linux64-v$VERSION.tar.xz" > infer.tar.xz 88 | sudo tar -C /opt -xvf infer.tar.xz 89 | sudo ln -s /opt/infer-linux64-v$VERSION/bin/infer /usr/local/bin 90 | mv infer.tar.xz /tmp 91 | 92 | - name: checkout SRS 93 | uses: actions/checkout@v4 94 | with: 95 | token: ${{ secrets.GHPAT }} 96 | 97 | - name: checkout target repo 98 | uses: actions/checkout@v4 99 | with: 100 | repository: ${{ inputs.repo }} 101 | token: ${{ secrets.GHPAT }} 102 | path: repo 103 | 104 | - name: run infer 105 | timeout-minutes: 300 106 | run: | 107 | sudo -E ./infer/infer.sh -w $GITHUB_WORKSPACE/repo 108 | sudo chown -R $USER.$USER $GITHUB_WORKSPACE/repo 109 | 110 | - name: combine results 111 | if: ${{ always() }} 112 | run: | 113 | find $GITHUB_WORKSPACE/repo -type f -name 'report.json' -exec jq -cn '{ bugs: [ inputs ] | add }' {} + > ${{ steps.vars.outputs.SREPO }}.infer.json 114 | 115 | - name: save results 116 | if: ${{ always() }} 117 | uses: actions/upload-artifact@v4 118 | with: 119 | name: ${{ steps.vars.outputs.SREPO }}.infer 120 | path: | 121 | ${{ steps.vars.outputs.SREPO }}.infer.json 122 | 123 | - name: move infer back for caching 124 | if: ${{ always() }} 125 | run: mv /tmp/infer.tar.xz . 126 | -------------------------------------------------------------------------------- /query/query.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SEARCH=${1:-"archived:false language:c language:c++"} 3 | MINSTARS=${2:-1000} # Starting range, no results from below this 4 | MAXSTARS=${3:-10000} # Stop when reached 5 | INCREMENT=${4:-300} # Group size to split search into 6 | ABOVEMAX=${5:-1} # Include results above MAXSTARS in a single query 7 | 8 | # Github search is limited to return 1000 results at most 9 | # even if the repositoryCount returns total number of matches (may also be limited to 4000). 10 | # Workaround is to break the search up into slots based on stars 11 | 12 | if [ -z "$SEARCH" ]; then 13 | echo "No search term specified" 14 | exit 1 15 | fi 16 | 17 | execute_query() { 18 | local counter=0 19 | local cursor="" 20 | local s=$1 21 | local retry=$2 22 | local retry_counter=$retry 23 | 24 | local query=' 25 | query($search: String!, $endCursor: String) { 26 | search( 27 | type: REPOSITORY, 28 | query: $search, 29 | first: 100 30 | after: $endCursor 31 | ) 32 | { 33 | repositoryCount 34 | repos: edges { 35 | repo: node { 36 | ... on Repository { 37 | nameWithOwner 38 | languages(first: 100) { nodes { name } } 39 | } 40 | } 41 | } 42 | pageInfo { hasNextPage endCursor } 43 | } 44 | rateLimit { remaining resetAt } 45 | } 46 | ' 47 | 48 | while : ; do 49 | 50 | echo "Running iteration $counter of query $s" 51 | 52 | if [ -z ${cursor} ]; then 53 | gh api graphql -F search="$s" -f query="$query" > data.${counter}.json 54 | else 55 | gh api graphql -F search="$s" -F endCursor="${cursor}" -f query="$query" > data.${counter}.json 56 | fi 57 | 58 | r=$? 59 | 60 | if [ $r -ne 0 ] && [ $retry_counter -gt 0 ]; then 61 | sleep 5 62 | (( retry_counter-- )) || : 63 | 64 | # TODO: check rate limiting and sleep till resetAt if needed and then retry 65 | 66 | continue 67 | fi 68 | 69 | c=$(jq '.data.search.repositoryCount' data.0.json) 70 | t=$(jq '.data.search.pageInfo | "\(.hasNextPage) \(.endCursor)"' data.${counter}.json | tr -d '"') 71 | next=$(echo -n $t | awk '{ print $1 }') 72 | cursor=$(echo -n $t | awk '{ print $2 }') 73 | 74 | if [ $c -gt 1000 ]; then 75 | echo "Results will be truncated due to more then a 1000 matches to the query ($c), consider decreasing the INCREMENT threshold" 76 | exit 1 77 | fi 78 | 79 | echo "Total count: $c, more data? $next $cursor" 80 | 81 | if [ "${next}" == "false" ]; then 82 | break 83 | fi 84 | 85 | retry_counter=$retry 86 | (( counter++ )) || : 87 | 88 | done 89 | 90 | # merge jsons 91 | find . -type f -name 'data.*.json' -exec jq -cn '{ repos: [ inputs.data.search.repos ] | add }' {} + > merged.json 92 | rm data* 93 | 94 | if [ -f repos.json ]; then 95 | jq -cn '{ repos: [ inputs.repos ] | add }' merged.json repos.json > tmp.json 96 | mv tmp.json repos.json 97 | rm merged.json 98 | else 99 | mv merged.json repos.json 100 | fi 101 | 102 | } 103 | 104 | if [ $MAXSTARS -lt $MINSTARS ]; then 105 | MAXSTARS=$MINSTARS 106 | fi 107 | 108 | for s in `seq $MINSTARS $INCREMENT $MAXSTARS`; do 109 | if [ $s -lt $MAXSTARS ]; then 110 | sp=$(( s + INCREMENT - 1 )) 111 | q="$SEARCH stars:$s..$sp" 112 | else 113 | [[ $ABOVEMAX -eq 0 ]] && exit 0 114 | 115 | q="$SEARCH stars:>=$MAXSTARS" 116 | fi 117 | 118 | echo $q 119 | 120 | execute_query "${q}" 5 121 | done 122 | -------------------------------------------------------------------------------- /.github/workflows/ossf-scorecard.yml: -------------------------------------------------------------------------------- 1 | name: Run OSSF Scorecard 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: 'repo' 8 | required: true 9 | type: string 10 | rate-limit: 11 | description: 'rate limit' 12 | required: false 13 | default: 500 14 | type: number 15 | version: 16 | description: 'scorecard version' 17 | required: false 18 | default: '4.13.1' 19 | type: string 20 | workflow_call: 21 | inputs: 22 | repo: 23 | description: 'repo' 24 | required: true 25 | default: '' 26 | type: string 27 | rate-limit: 28 | description: 'rate limit' 29 | required: false 30 | default: 500 31 | type: number 32 | version: 33 | description: 'scorecard version' 34 | required: false 35 | default: '4.13.1' 36 | type: string 37 | secrets: 38 | GHPAT: 39 | required: true 40 | 41 | jobs: 42 | ossf-scorecard: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: generate save directory name 46 | shell: bash 47 | id: vars 48 | run: | 49 | SREPO=$(echo -n ${{ inputs.repo }} | tr '/' .) 50 | echo "SREPO set to ${SREPO}" 51 | echo "SREPO=${SREPO}" >> $GITHUB_OUTPUT 52 | 53 | - name: get official score 54 | id: official 55 | run: | 56 | curl https://api.securityscorecards.dev/projects/github.com/${{ inputs.repo }} > ${{ steps.vars.outputs.SREPO }}.ossf-scorecard.json 57 | if [ -s ${{ steps.vars.outputs.SREPO }}.ossf-scorecard.json ]; then 58 | version=$(jq -r '.scorecard.version' ${{ steps.vars.outputs.SREPO }}.ossf-scorecard.json) 59 | if [ "$version" == "v${{ inputs.version }}" ]; then 60 | echo result=1 >> $GITHUB_OUTPUT 61 | else 62 | echo result=0 >> $GITHUB_OUTPUT 63 | rm *.ossf-scorecard.json 64 | fi 65 | else 66 | echo result=0 >> $GITHUB_OUTPUT 67 | fi 68 | 69 | - name: rate limit 70 | if: steps.official.outputs.result == '0' 71 | env: 72 | GHPAT: ${{ secrets.GHPAT }} 73 | run: | 74 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 75 | echo "Rate limit remaining: $rl" 76 | while [ $rl -lt ${{ inputs.rate-limit }} ]; do 77 | sleep 1h 78 | rl=$(curl -H "Authorization: token $GHPAT" -X GET https://api.github.com/rate_limit | jq '.rate.remaining') 79 | echo "Rate limit remaining: $rl" 80 | done 81 | 82 | - name: Cache scorecard 83 | id: cache 84 | if: steps.official.outputs.result == '0' 85 | uses: actions/cache@v3 86 | with: 87 | path: scorecard-linux-amd64 88 | key: scorecard-linux-amd64-${{ inputs.version }} 89 | 90 | - name: Download scorecard 91 | if: steps.official.outputs.result == '0' && steps.cache.outputs.cache-hit != 'true' 92 | run: | 93 | wget https://github.com/ossf/scorecard/releases/download/v${{ inputs.version }}/scorecard_${{ inputs.version }}_linux_amd64.tar.gz 94 | tar xvf scorecard*.tar.gz 95 | 96 | - name: Run scorecard 97 | if: steps.official.outputs.result == '0' 98 | env: 99 | GITHUB_AUTH_TOKEN: ${{ secrets.GHPAT }} 100 | GITHUB_TOKEN: ${{ secrets.GHPAT }} 101 | run: | 102 | ./scorecard-linux-amd64 --repo="${{ inputs.repo }}" --format=json \ 103 | --checks="Vulnerabilities,Signed-Releases,Maintained,CI-Tests,CII-Best-Practices,Security-Policy,Pinned-Dependencies,Dangerous-Workflow,Code-Review,Packaging,Binary-Artifacts,License,Token-Permissions" \ 104 | > ${{ steps.vars.outputs.SREPO }}.ossf-scorecard.json 105 | 106 | - name: save results 107 | if: ${{ always() }} 108 | uses: actions/upload-artifact@v4 109 | with: 110 | name: ${{ steps.vars.outputs.SREPO }}.ossf-scorecard 111 | path: ${{ steps.vars.outputs.SREPO }}.ossf-scorecard.json 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PROJECT NOT UNDER ACTIVE MANAGEMENT 2 | 3 | This project will no longer be maintained by Intel. 4 | 5 | Intel has ceased development and contributions including, but not limited to, maintenance, bug fixes, new releases, or updates, to this project. 6 | 7 | Intel no longer accepts patches to this project. 8 | 9 | If you have an ongoing need to use this project, are interested in independently developing it, or would like to maintain patches for the open source software community, please create your own fork of this project. 10 | 11 | Contact: webadmin@linux.intel.com 12 | # Scaling Repo Scanner (SRS) 13 | 14 | GitHub Actions based repository scanning workflows with a primary goal of evaluating C & C++ repositories for risks. 15 | 16 | Current scans being performed: 17 | - [clang's scan-build](https://clang-analyzer.llvm.org/scan-build.html): Detect common C & C++ bugs using static source analysis. [More details on how to integrate this scan into your CI using GitHub Actions](scan-build). 18 | - [clang-tidy cognitive complexity](https://clang.llvm.org/extra/clang-tidy/checks/readability/function-cognitive-complexity.html): Calculate readability score for every function. [More details on how to integrate this scan into your CI using GitHub Actions](scan-build). 19 | - [OSSF Scorecard](https://github.com/ossf/scorecard): Measure software development practices. 20 | - [CLoC](https://github.com/AlDanial/cloc): Calculate lines of code & comments. 21 | - [Infer](https://fbinfer.com): Infer checks for null pointer dereferences, memory leaks, coding conventions and unavailable API’s in C & C++ code. 22 | 23 | Scans run monthly and results are automatically published at [https://intel.github.io/srs](https://intel.github.io/srs) 24 | 25 | # License 26 | 27 | [MIT](https://github.com/intel/srs/blob/main/COPYING) 28 | 29 | # Forking 30 | 31 | The repository can be forked and the existing scans replaced or new ones added. All you need to add is a GitHub PAT to secrets with the name `GHPAT`. 32 | 33 | ## Adding more scans 34 | 35 | 1. Create a workflow YAML file under `.github/workflows/my-new-scan.yml` with the following required inputs: 36 | 37 | ```yaml 38 | on: 39 | workflow_call: 40 | inputs: 41 | repo: 42 | description: 'repo' 43 | required: true 44 | default: '' 45 | type: string 46 | rate-limit: 47 | description: 'rate limit GitHub API requests' 48 | required: false 49 | default: 150 50 | type: number 51 | ``` 52 | 53 | For steps you can define whatever is needed to perform the scan as you would with a workflow. Use [Upload-Artifact Action](https://github.com/actions/upload-artifact) to store the results of the scan with a key that uniquely identifies the repo and the scan, for example `some-repo.my-new-scan.results.zip`). It is advisable to check the GitHub API rate limit and sleep if there are fewer then 150 calls remaining for your token. 54 | 55 | 2. Add call to the new workflow in `.github/workflows/srs.yml`: 56 | 57 | ```yaml 58 | on: 59 | workflow_dispatch: 60 | inputs: 61 | ... 62 | my-new-scan: 63 | description: 'Run my-new-scan workflow' 64 | required: false 65 | type: number 66 | default: 0 67 | ... 68 | jobs: 69 | ... 70 | my-new-scan: 71 | if: inputs.my-new-scan == 1 72 | needs: matrix 73 | secrets: inherit 74 | strategy: 75 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 76 | fail-fast: false # don't stop other jobs if one fails 77 | uses: ./.github/workflows/my-new-scan.yml 78 | with: 79 | repo: ${{ matrix.repo }} 80 | ``` 81 | 82 | 3. Add the new scan to the `next` job's `needs` list: 83 | 84 | ```yaml 85 | next: 86 | needs: [..., my-new-scan] 87 | ``` 88 | 89 | 4. Add my-new-scan to the enabled workflows in `query.yml`: 90 | 91 | ```yaml 92 | ... 93 | workflows: 94 | description: 'List of workflows to enable (CSV)' 95 | required: false 96 | type: string 97 | default: '...,my-new-scan' 98 | ... 99 | ``` 100 | 101 | 5. Add the scan's result file (for example `my-new-scan.results.zip`) to the `aggregate` function in `query/summary.sh`. 102 | 103 | ```bash 104 | for f in $(find $ARTIFACT_DIR -type f -name '*.my-new-scan.results.zip'); do 105 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 106 | done 107 | ``` 108 | 109 | Results will be saved and published on GitHub Pages as part of the next scan. 110 | -------------------------------------------------------------------------------- /.github/workflows/query.yml: -------------------------------------------------------------------------------- 1 | name: Query GitHub for list of repositories and start SRS 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | search: 7 | description: 'The search terms to query' 8 | required: true 9 | type: string 10 | default: 'archived:false language:c language:c++' 11 | minstars: 12 | description: 'Minimum stars the repository should have to be included in query' 13 | required: false 14 | number: string 15 | default: 400 16 | maxstars: 17 | description: 'Maximum stars to break query up to' 18 | required: false 19 | typer: number 20 | default: 4000 21 | increment: 22 | description: 'Increment to break query up to in terms of stars' 23 | required: false 24 | type: number 25 | default: 50 26 | abovemax: 27 | description: 'Include repositories above the max number of stars in a single query' 28 | required: false 29 | type: string 30 | default: 'true' 31 | workflows: 32 | description: 'List of workflows to enable (CSV)' 33 | required: false 34 | type: string 35 | default: 'scan-build,ossf-scorecard,metadata,infer' 36 | 37 | jobs: 38 | query-repositories: 39 | runs-on: ubuntu-latest 40 | env: 41 | GH_TOKEN: ${{ secrets.GHPAT }} 42 | steps: 43 | - name: checkout repo 44 | uses: actions/checkout@v4 45 | with: 46 | token: ${{ secrets.GHPAT }} 47 | 48 | - name: execute search 49 | env: 50 | SEARCH: ${{ inputs.search }} 51 | MINSTARS: ${{ inputs.minstars }} 52 | MAXSTARS: ${{ inputs.maxstars }} 53 | INCREMENT: ${{ inputs.increment }} 54 | ABOVEMAX: ${{ inputs.abovemax }} 55 | run: | 56 | ./query/query.sh "${SEARCH}" $MINSTARS $MAXSTARS $INCREMENT $ABOVEMAX 57 | 58 | count=$(jq '.repos | length' repos.json) 59 | 60 | echo "---" >> query-summary.md 61 | echo "## Repositories matching search query '${SEARCH} stars:>=$MINSTARS': $count" >> query-summary.md 62 | 63 | - name: filter by build system 64 | run: | 65 | JSON=$(./query/filter.sh) 66 | echo "${JSON}" > filtered-repos.json 67 | COUNT=$(jq '. | length' filtered-repos.json) 68 | 69 | echo "## Repositories with supported build systems: $COUNT" >> query-summary.md 70 | 71 | - name: Github step summary 72 | run: cat query-summary.md >> $GITHUB_STEP_SUMMARY 73 | 74 | - name: create matrix 75 | id: matrix 76 | run: | 77 | 78 | COUNT=1 79 | MATRIXID=1 80 | 81 | JSON="{ include :[" 82 | 83 | for row in $(jq -rc '.[]' filtered-repos.json); do 84 | 85 | # Build matrices are limited to 256 items so we split the work 86 | if [ $(( $COUNT % 256 )) -eq 0 ]; then 87 | # Remove last "," 88 | JSON="${JSON%?}" 89 | JSON+="]}" 90 | 91 | echo "$MATRIXID: $JSON" 92 | echo "$JSON" > matrix${MATRIXID}.json 93 | 94 | JSON="{ include: [" 95 | (( MATRIXID++ )) 96 | fi 97 | 98 | JSON+="$row," 99 | 100 | (( COUNT++ )) 101 | 102 | done 103 | 104 | # Remove last "," 105 | JSON="${JSON%?}" 106 | JSON+="]}" 107 | 108 | echo "number-of-matrices=${MATRIXID}" >> $GITHUB_OUTPUT 109 | echo "$JSON" > matrix${MATRIXID}.json 110 | 111 | echo "$MATRIXID: $JSON" 112 | 113 | - name: save results 114 | uses: actions/upload-artifact@v4 115 | with: 116 | name: query-repositories 117 | path: | 118 | repos.json 119 | filtered-repos.json 120 | query-summary.md 121 | matrix*.json 122 | 123 | - name: save matrices 124 | uses: actions/upload-artifact@v4 125 | with: 126 | name: matrix 127 | path: | 128 | matrix*.json 129 | 130 | - name: start scans 131 | run: | 132 | workflows="" 133 | for w in $(echo '${{ inputs.workflows }}' | tr "," " "); do 134 | workflows="$workflows -f $w=1" 135 | done 136 | 137 | gh workflow run srs.yml -f run-ids='${{ github.run_id }}' -f number-of-matrices=${{ steps.matrix.outputs.number-of-matrices }} -f matrix=1 $workflows 138 | -------------------------------------------------------------------------------- /.github/workflows/srs.yml: -------------------------------------------------------------------------------- 1 | name: Run SRS scans 2 | permissions: read-all 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | run-ids: 7 | description: 'The list of run ids used for this scan chain' 8 | required: true 9 | type: string 10 | matrix: 11 | description: 'Current matrix id to process' 12 | required: true 13 | type: number 14 | number-of-matrices: 15 | description: 'Total number of matrices' 16 | required: true 17 | type: number 18 | scan-build: 19 | description: 'Run scan-build workflow' 20 | required: false 21 | type: number 22 | default: 0 23 | ossf-scorecard: 24 | description: 'Run OSSF scorecard workflow' 25 | required: false 26 | type: number 27 | default: 0 28 | metadata: 29 | description: 'Run metadata workflow' 30 | required: false 31 | type: number 32 | default: 0 33 | infer: 34 | description: 'Run Infer workflow' 35 | required: false 36 | type: number 37 | default: 0 38 | jobs: 39 | matrix: 40 | runs-on: ubuntu-latest 41 | env: 42 | GH_TOKEN: ${{ secrets.GHPAT }} 43 | outputs: 44 | matrix: ${{ steps.matrix.outputs.matrix }} 45 | steps: 46 | - name: checkout repo 47 | uses: actions/checkout@v4 48 | with: 49 | token: ${{ secrets.GHPAT }} 50 | 51 | - name: get first run id 52 | run: | 53 | runid=$(echo '${{ inputs.run-ids }}' | awk -F',' '{ print $1 }') 54 | gh run download $runid -n matrix 55 | 56 | - name: get matrix 57 | id: matrix 58 | run: | 59 | [[ ! -f matrix${{ inputs.matrix }}.json ]] && exit 1 60 | 61 | cat matrix${{ inputs.matrix }}.json 62 | matrix=$(cat matrix${{ inputs.matrix }}.json) 63 | echo "matrix=$matrix" >> $GITHUB_OUTPUT 64 | 65 | scan-build: 66 | if: inputs.scan-build == 1 67 | needs: matrix 68 | secrets: inherit 69 | strategy: 70 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 71 | fail-fast: false # don't stop other jobs if one fails 72 | uses: ./.github/workflows/scan-build.yml 73 | with: 74 | repo: ${{ matrix.repo }} 75 | 76 | ossf-scorecard: 77 | if: inputs.ossf-scorecard == 1 78 | needs: matrix 79 | secrets: inherit 80 | strategy: 81 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 82 | fail-fast: false # don't stop other jobs if one fails 83 | uses: ./.github/workflows/ossf-scorecard.yml 84 | with: 85 | repo: ${{ matrix.repo }} 86 | 87 | metadata: 88 | if: inputs.metadata == 1 89 | needs: matrix 90 | secrets: inherit 91 | strategy: 92 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 93 | fail-fast: false # don't stop other jobs if one fails 94 | uses: ./.github/workflows/metadata.yml 95 | with: 96 | repo: ${{ matrix.repo }} 97 | 98 | infer: 99 | if: inputs.infer == 1 100 | needs: matrix 101 | secrets: inherit 102 | strategy: 103 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 104 | fail-fast: false # don't stop other jobs if one fails 105 | uses: ./.github/workflows/infer.yml 106 | with: 107 | repo: ${{ matrix.repo }} 108 | 109 | next: 110 | needs: [scan-build, ossf-scorecard, metadata, infer] 111 | if: ${{ !cancelled() }} 112 | runs-on: ubuntu-latest 113 | env: 114 | GH_TOKEN: ${{ secrets.GHPAT }} 115 | steps: 116 | - name: checkout repo 117 | uses: actions/checkout@v4 118 | with: 119 | token: ${{ secrets.GHPAT }} 120 | 121 | - run: | 122 | start_next() { 123 | matrix=$1 124 | runids=$2 125 | retry=0 126 | if [ $matrix -le ${{ inputs.number-of-matrices }} ]; then 127 | gh workflow run srs.yml -f run-ids=${runids} -f number-of-matrices=${{ inputs.number-of-matrices }} -f matrix=${matrix} \ 128 | -f scan-build=${{ inputs.scan-build }} \ 129 | -f ossf-scorecard=${{ inputs.ossf-scorecard }} \ 130 | -f metadata=${{ inputs.metadata }} \ 131 | -f infer=${{ inputs.infer }} \ 132 | || retry=1 133 | else 134 | gh workflow run summary.yml -f delay="1h" -f run-ids=${runids} || retry=1 135 | fi 136 | return $retry 137 | } 138 | 139 | runids="${{ inputs.run-ids }},${{ github.run_id }}" 140 | 141 | matrix=${{ inputs.matrix }} 142 | (( matrix++ )) 143 | 144 | while : ; do 145 | start_next $matrix $runids 146 | [[ $? -eq 0 ]] && break 147 | sleep 5m 148 | done 149 | -------------------------------------------------------------------------------- /scan-build/README.md: -------------------------------------------------------------------------------- 1 | ## scan-build dockerized 2 | 3 | This folder contains the scripts and Dockerfile necessary to build a Dockerized 4 | version of clang's scan-build with Z3 cross-check support. It also performs a clang-tidy cognitive 5 | complexity analysis on all functions found in the project during build. 6 | 7 | ### scan-build.sh 8 | 9 | This script is set as the ENTRYPOINT for the Docker image. It expects a 10 | shared folder to be mounted at /work with the code to be scanned placed inside. 11 | 12 | Inputs: 13 | 14 | ``` 15 | -r: Name of the repository in {owner}/{repo} format (for example "intel/srs"). 16 | -l: LLVM version to use, default is 15 17 | -t: Timeout value for each scan , default is "30m" 18 | -o: Name of the folder to use for storing the results 19 | ``` 20 | 21 | Outputs: 22 | 23 | The scan-build HTMLs will placed in the shared host folder at /work/{owner}.{repo}/scan-build-result or 24 | if no name has been specified at /work/scan-build-result/scan-build-result. 25 | 26 | The clang-tidy cognitive complexity log will be in cognitive-complexity.log in the result folder. 27 | 28 | A combined json of the scan-build results and the cognitive complexity analysis will be in scan-build.json. 29 | 30 | ### Build it 31 | 32 | ``` 33 | cd scan-build 34 | docker build . -t scan-build 35 | ``` 36 | 37 | ### Run it 38 | 39 | ``` 40 | docker run -v $FOLDER_CONTAINING_REPOSITORY:/work scan-build 41 | ``` 42 | 43 | ### Use it as a GitHub Action 44 | 45 | The branch `scan-build-action-v1` contains the action.yml to run this Dockerfile as 46 | a GitHub Action so you can integrate it into your CI easily. 47 | 48 | #### Inputs (all optional) 49 | 50 | - `repository`: {owner}/{repo} (for example `${{ github.repository }}`) 51 | - `error-on-bugs`: Make action exit with error code in case bugs were found 52 | - `timeout`: limit how long scan-build process runs (default: 30m) 53 | - `llvm-version`: version of clang to use (default: 15) 54 | 55 | #### Output 56 | - `bugs`: The number of bugs found during the scan 57 | - `json`: The json formatted results of the scan-build and cognitive complexity analysis 58 | 59 | #### Defining additional build-steps 60 | 61 | In case your repository needs additional packages and/or build preparation steps 62 | before scan-build you can add a `bootstrap.sh` script which will be run in the 63 | Docker container before scan-build. 64 | 65 | #### Example workflows 66 | 67 | The simplest way is to run the scan and error on any bugs found 68 | 69 | ```yaml 70 | name: Intel SRS scan-build action using clang 71 | on: 72 | pull_request: 73 | branches: [ main ] 74 | jobs: 75 | test: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v3 79 | 80 | - uses: intel/srs@scan-build-action-v1 81 | with: 82 | error-on-bugs: 1 83 | ``` 84 | 85 | You can also save the results if you prefer: 86 | 87 | ```yaml 88 | name: Intel SRS scan-build action using clang 89 | on: 90 | pull_request: 91 | branches: [ main ] 92 | jobs: 93 | test: 94 | runs-on: ubuntu-latest 95 | steps: 96 | - uses: actions/checkout@v3 97 | 98 | - uses: intel/srs@scan-build-action-v1 99 | id: scan-build 100 | with: 101 | repository: ${{ github.repository }} 102 | 103 | - name: create scan-build-result tarball 104 | run: tar czvf scan-build-result.tar.gz scan-build-result 105 | 106 | - name: save results 107 | uses: actions/upload-artifact@v3 108 | with: 109 | name: ${{github.repository }}.scan-build-result 110 | path: scan-build-result.tar.gz 111 | 112 | - name: error if bug(s) found 113 | run: | 114 | [[ ${{ steps.scan-build.outputs.bugs }} -gt 0 ]] && exit 1 115 | ``` 116 | 117 | If you want to error only on certain types of bugs you can parse the `scan-build-result/scan-build.json` 118 | file using `jq`. For example, if you don't want to error on cognitive complexity issues: 119 | 120 | ```yaml 121 | - name: error only on non-complex bugs 122 | run: | 123 | BUGS=$(jq '.bugs[] | select( any(.; .type != "Cognitive complexity") ) | length' scan-build-result/scan-build.json | wc -l) 124 | [[ $BUGS -gt 0 ]] && exit 1 125 | ``` 126 | 127 | If you want to error only if the bug count increased compared to your main branch: 128 | 129 | ```yaml 130 | name: Intel SRS scan-build action using clang 131 | on: 132 | pull_request: 133 | branches: [ main ] 134 | jobs: 135 | test: 136 | runs-on: ubuntu-latest 137 | steps: 138 | - uses: actions/checkout@v3 139 | - uses: intel/srs@scan-build-action-v1 140 | 141 | - name: backup scan-build-result 142 | run: mv scan-build-result /tmp 143 | 144 | - uses: actions/checkout@v3 145 | with: 146 | ref: main 147 | - uses: intel/srs@scan-build-action-v1 148 | 149 | - name: error if bug count increased 150 | run: | 151 | BUGS_IN_MAIN=$(jq '.bugs | length' scan-build-result/scan-build.json) 152 | BUGS_IN_PR=$(jq '.bugs | length' /tmp/scan-build-result/scan-build.json) 153 | 154 | [[ ${{ BUGS_IN_PR }} -gt ${{ BUGS_IN_MAIN }} ]] && exit 1 155 | ``` 156 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | CommunityCodeOfConduct AT intel DOT com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | -------------------------------------------------------------------------------- /query/summary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ARTIFACT_DIR=${1:-"."} 3 | RUD_IDS=$2 4 | 5 | create_table() { 6 | echo "" > summary.md 7 | 8 | built=$(find $ARTIFACT_DIR -type f -name '*.scan-build.json' | wc -l) 9 | scored=$(find $ARTIFACT_DIR -type f -name '*.ossf-scorecard.json' | wc -l) 10 | bugs=$(find $ARTIFACT_DIR -type f -name '*.scan-build.json' -exec jq '.bugs | length' {} + | awk '{ sum += $1 } END { print sum }') 11 | complex_functions=$(find $ARTIFACT_DIR -type f -name '*.scan-build.json' -exec jq '.bugs[] | select( any(.; .type == "Cognitive complexity") ) | length' {} + | wc -l | awk '{ sum += $1 } END { print sum }') 12 | bugs=$(( bugs - complex_functions )) 13 | 14 | # Save stats for website summary 15 | echo "$built, $scored, $bugs, $complex_functions" > stats 16 | 17 | echo "### Repositories built with scan-build: ${built}" >> summary.md 18 | echo "### Repositories scored with OSSF scorecard: ${scored}" >> summary.md 19 | echo "" >> summary.md 20 | echo "***" >> summary.md 21 | echo "" >> summary.md 22 | echo "### Total number of bugs found: ${bugs}" >> summary.md 23 | echo "### Total number of high cognitivite complexity functions found: ${complex_functions}" >> summary.md 24 | echo "" >> summary.md 25 | echo "***" >> summary.md 26 | echo "" >> summary.md 27 | 28 | if [ ! -z $RUN_IDS ]; then 29 | echo -n "GitHub Action Run IDS: " >> summary.md 30 | for r in $(echo $RUN_IDS | tr ',' ' '); do 31 | echo -n "[$r](https://github.com/$GITHUB_REPOSITORY/actions/runs/$r) " >> summary.md 32 | done 33 | echo "" >> summary.md 34 | echo "" >> summary.md 35 | echo "***" >> summary.md 36 | echo "" >> summary.md 37 | fi 38 | 39 | echo "### Breakdown" >> summary.md 40 | 41 | echo "| # | Repo | Scan-build Bugs | Infer Bugs | OSSF Score | High cognitive complexity functions / Total functions | Comments / LoC |" >> summary.md 42 | echo "| --- | ----------- | --------------- | ---------- | ---------- | ------------------------------------------------------- | -------------- |" >> summary.md 43 | 44 | rm *.tmp || : 45 | 46 | for f in $(find $ARTIFACT_DIR -type f -name '*.metadata.json'); do 47 | repo=$(jq -r '.full_name' $f) 48 | srepo=$(echo $repo | tr '/' .) 49 | 50 | unset bugs 51 | unset functions 52 | unset complex_functions 53 | if [ -f $ARTIFACT_DIR/$srepo.scan-build/$srepo.scan-build.json ]; then 54 | functions=$(jq '.functions' $ARTIFACT_DIR/$srepo.scan-build/$srepo.scan-build.json) 55 | complex_functions=$(jq '.bugs[] | select( any(.; .type == "Cognitive complexity") ) | length' $ARTIFACT_DIR/$srepo.scan-build/$srepo.scan-build.json | wc -l) 56 | bugs=$(jq '.bugs | length' $ARTIFACT_DIR/$srepo.scan-build/$srepo.scan-build.json) 57 | bugs=$(( bugs - complex_functions )) 58 | fi 59 | [[ -z $functions ]] && functions="-1" 60 | [[ -z $complex_functions ]] && complex_functions="-1" 61 | [[ -z $bugs ]] && bugs="-1" 62 | 63 | unset score 64 | if [ -f $ARTIFACT_DIR/$srepo.ossf-scorecard/$srepo.ossf-scorecard.json ]; then 65 | score=$(jq '.score' $ARTIFACT_DIR/$srepo.ossf-scorecard/$srepo.ossf-scorecard.json) 66 | fi 67 | [[ -z $score ]] && score="-1" 68 | 69 | unset lines 70 | unset comments 71 | if [ -f $ARTIFACT_DIR/$srepo.metadata/$srepo.cloc.json ]; then 72 | lines=$(jq -r '[."C".code, ."C++".code, ."C/C++ Header".code ] | add' $ARTIFACT_DIR/$srepo.metadata/$srepo.cloc.json) 73 | comments=$(jq -r '[."C".comment, ."C++".comment, ."C/C++ Header".comment ] | add' $ARTIFACT_DIR/$srepo.metadata/$srepo.cloc.json) 74 | fi 75 | [[ -z $lines ]] && lines="-1" 76 | [[ -z $comments ]] && comments="-1" 77 | 78 | unset infer 79 | if [ -f $ARTIFACT_DIR/$srepo.infer/$srepo.infer.json ]; then 80 | infer=$(jq -r '.bugs | length' $ARTIFACT_DIR/$srepo.infer/$srepo.infer.json) 81 | fi 82 | [[ -z $infer ]] && infer="-1" 83 | 84 | echo "$repo $bugs $infer $score $functions $complex_functions $comments $lines" >> s.tmp 85 | done 86 | 87 | sort -k 2 -n -r s.tmp > s2.tmp 88 | 89 | echo "repo, scan-build-bugs, infer-bugs, ossf-score, complex-functions, total-functions, lines-of-code, lines-of-comments" > summary.csv 90 | count=1 91 | while read -r repo bugs infer score functions complex_functions comments lines; do 92 | echo "| $count | [$repo](https://github.com/$repo) | $bugs | $infer | $score | $complex_functions / $functions | $comments / $lines |" >> summary.md 93 | echo "$repo,$bugs,$infer,$score,$complex_functions,$functions,$comments,$lines" >> summary.csv 94 | (( count++ )) 95 | done < s2.tmp 96 | 97 | rm *.tmp || : 98 | } 99 | 100 | aggregate() { 101 | mkdir -p $ARTIFACT_DIR/aggregate-results 102 | for f in $(find $ARTIFACT_DIR -type f -name '*.scan-build.json'); do 103 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 104 | done 105 | 106 | for f in $(find $ARTIFACT_DIR -type f -name '*.ossf-scorecard.json'); do 107 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 108 | done 109 | 110 | for f in $(find $ARTIFACT_DIR -type f -name '*.metadata.json'); do 111 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 112 | done 113 | 114 | for f in $(find $ARTIFACT_DIR -type f -name '*.cloc.json'); do 115 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 116 | done 117 | 118 | for f in $(find $ARTIFACT_DIR -type f -name '*.infer.json'); do 119 | cp $f $ARTIFACT_DIR/aggregate-results/ || : 120 | done 121 | 122 | tar -C $ARTIFACT_DIR/aggregate-results -czvf all-results.tar.gz . 123 | } 124 | 125 | ############################### 126 | 127 | create_table 128 | aggregate 129 | -------------------------------------------------------------------------------- /infer/infer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o pipefail 3 | 4 | usage() { echo "Usage: $0 [-w ] [-t ]" 1>&2; exit 1; } 5 | 6 | WORKDIR=$PWD 7 | TIMEOUT="1200" 8 | 9 | while getopts ":t:o:w:" o; do 10 | case "${o}" in 11 | t) 12 | TIMEOUT=${OPTARG} 13 | ;; 14 | w) 15 | WORKDIR=${OPTARG} 16 | ;; 17 | *) 18 | usage 19 | ;; 20 | esac 21 | done 22 | shift $((OPTIND-1)) 23 | 24 | echo "Setting options:" 25 | echo " WORKDIR: $WORKDIR" 26 | echo " TIMEOUT: $TIMEOUT" 27 | 28 | ############## 29 | 30 | find_autoconf_builddir() { 31 | local search="configure autogen.sh bootstrap.sh bootstrap boot buildconf configure.ac" 32 | touch $WORKDIR/build_autoconf 33 | 34 | for f in $search; do 35 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 36 | for d in $dir; do 37 | if [ $(grep "$PWD/$d" $WORKDIR/build_autoconf | wc -l) -eq 0 ]; then 38 | pushd $d 39 | s=$(find . -iname "$f" -type f | head -n1 | awk -F'/' '{ print $2 }') 40 | echo "Found $s in $PWD" 41 | echo "$PWD $s" >> $WORKDIR/build_autoconf 42 | popd 43 | fi 44 | done 45 | done 46 | } 47 | 48 | find_cmake_builddir() { 49 | local search="CMakeLists.txt" 50 | touch $WORKDIR/build_cmake 51 | 52 | for f in $search; do 53 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 54 | for d in $dir; do 55 | echo "Found $f in $PWD/$d" 56 | project=$(grep 'project(' ${d}/${f} | wc -l) 57 | if [ ${project} -gt 0 ]; then 58 | echo "Top level $f found at ${d}/${f}" 59 | echo "$PWD/$d $f" >> $WORKDIR/build_cmake 60 | fi 61 | done 62 | done 63 | } 64 | 65 | find_meson_builddir() { 66 | local search="meson.build" 67 | touch $WORKDIR/build_meson 68 | 69 | for f in $search; do 70 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 71 | for d in $dir; do 72 | project=$(grep 'project(' "$d/$f" | wc -l) 73 | if [ $project -gt 0 ]; then 74 | echo "Found $f in $PWD/$d" 75 | echo "$PWD/$d $f" >> $WORKDIR/build_meson 76 | fi 77 | done 78 | done 79 | } 80 | 81 | find_builddirs() { 82 | find_autoconf_builddir 83 | find_cmake_builddir 84 | find_meson_builddir 85 | } 86 | 87 | search_and_install_dependencies() { 88 | packages="" 89 | declare -a files=( 90 | $(grep -rl "apt-get install") 91 | $(grep -rl "apt install") 92 | $(grep -rl "aptitude install") 93 | ) 94 | 95 | for r in "${files[@]}"; do 96 | found=0 97 | newline=0 98 | 99 | while read -r line; do 100 | if [ $(echo -n "${line}" | grep "apt" | grep "install" | wc -l) -eq 1 ]; then 101 | found=1 102 | fi 103 | 104 | # found install line, grab all potential packages from this line plus lines after while there is linebreak 105 | if [ $found -eq 1 ]; then 106 | if [ $newline -eq 0 ]; then 107 | packages+=$(echo -n $line | sed -r 's/^.*install //' | sed -r 's/^;//' | sed 's/\\/ /g' | awk '{split($0,a," "); for (x in a) { if (a[x] ~ /^[^-].*$/) { printf("%s ", a[x]) } } }') 108 | else 109 | packages+=$(echo -n $line | sed -r 's/^;//' | sed 's/\\/ /g' | awk '{split($0,a," "); for (x in a) { if (a[x] ~ /^[^-].*$/) { printf("%s ", a[x]) } } }') 110 | fi 111 | 112 | # check if there is a linebreak at the end 113 | if [ $(echo -n $line | awk '{print $NF}' | grep "\\\\" | wc -l) -eq 0 ]; then 114 | newline=0 115 | found=0 116 | else 117 | newline=1 118 | fi 119 | fi 120 | done < $r 121 | done 122 | 123 | packages=$(echo -n $packages | tr -dc '[:alnum:]\-\_ ') 124 | 125 | apt-get update 126 | for p in ${packages}; do 127 | apt-get -y install "${p}" || : 128 | done 129 | } 130 | 131 | get_submodules() { 132 | local dir=$(find . -iname ".gitmodules" -type f -printf '%h\n') 133 | for d in $dir; do 134 | pushd $dir 135 | git submodule update --init --recursive || : 136 | popd 137 | done 138 | } 139 | 140 | build_autoconf() { 141 | while read -r build; do 142 | 143 | echo "Autoconf $build" 144 | 145 | dir=$(echo $build | awk '{ print $1 }') 146 | script=$(echo $build | awk '{ print $2 }') 147 | 148 | cd $dir 149 | 150 | # don't build the same folder multiple times 151 | if [ -f "build-done" ]; then 152 | continue; 153 | fi 154 | 155 | echo "Scanning $dir with setup $script" 156 | 157 | case $script in 158 | configure.ac) 159 | autoreconf -vif 160 | ;; 161 | 162 | Configure) 163 | mv Configure configure 164 | ;; 165 | 166 | configure) 167 | ;; 168 | 169 | *) 170 | chmod +x ./$script 171 | ./$script 172 | ;; 173 | esac 174 | 175 | echo $dir >> ${WORKDIR}/configure.log 176 | 177 | ./configure | tee -a ${WORKDIR}/configure.log 2>&1 || continue 178 | infer capture -- make | tee -a ${WORKDIR}/infer.log 2>&1 || continue 179 | 180 | timeout -s 2 ${TIMEOUT} \ 181 | infer analyze --bufferoverrun --no-liveness | tee -a ${WORKDIR}/infer.log 2>&1 182 | 183 | touch "build-done" 184 | 185 | done < $WORKDIR/build_autoconf 186 | } 187 | 188 | build_cmake() { 189 | while read -r build; do 190 | 191 | echo "CMake $build" 192 | 193 | dir=$(echo $build | awk '{ print $1 }') 194 | 195 | cd $dir 196 | 197 | # don't build the same folder multiple times 198 | if [ -f "build-done" ]; then 199 | continue; 200 | fi 201 | 202 | echo "Attempting to build $GITHUB_WORKSPACE/repo/$dir" 203 | 204 | mkdir -p $dir/build 205 | cd $dir/build 206 | 207 | echo $dir >> $WORKDIR/cmake.log 208 | 209 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=YES $dir | tee -a ${WORKDIR}/cmake.log 2>&1 || continue 210 | cd .. 211 | infer capture --compilation-database build/compile_commands.json | tee -a ${WORKDIR}/infer.log 2>&1 212 | 213 | timeout -s 2 ${TIMEOUT} \ 214 | infer analyze --bufferoverrun --no-liveness | tee -a ${WORKDIR}/infer.log 2>&1 215 | 216 | touch $dir/build-done 217 | 218 | done < $WORKDIR/build_cmake 219 | } 220 | 221 | build_meson() { 222 | while read -r build; do 223 | 224 | echo "Meson $build" 225 | 226 | dir=$(echo $build | awk '{ print $1 }') 227 | 228 | cd $dir 229 | 230 | # don't build the same folder multiple times 231 | if [ -f "build-done" ]; then 232 | continue; 233 | fi 234 | 235 | echo $dir >> ${WORKDIR}/meson-setup.log 236 | 237 | meson setup build --buildtype debug 2>&1 | tee -a $WORKDIR/meson-setup.log || continue 238 | infer capture --compilation-database build/compile_commands.json | tee -a ${WORKDIR}/infer.log 2>&1 239 | 240 | timeout -s 2 ${TIMEOUT} \ 241 | infer analyze --bufferoverrun --no-liveness | tee -a ${WORKDIR}/infer.log 2>&1 242 | 243 | touch $dir/build-done 244 | 245 | done < $WORKDIR/build_meson 246 | } 247 | 248 | 249 | build_and_run_infer() { 250 | build_autoconf 251 | build_cmake 252 | build_meson 253 | } 254 | 255 | ###### 256 | 257 | cd $WORKDIR 258 | 259 | find_builddirs 260 | search_and_install_dependencies 261 | get_submodules 262 | build_and_run_infer 263 | 264 | exit 0 265 | -------------------------------------------------------------------------------- /scan-build/scan-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o pipefail 3 | 4 | usage() { echo "Usage: $0 [-r ] [-o ] [-l ] [-t ] [-b ]" 1>&2; exit 1; } 5 | 6 | WORKDIR=$PWD 7 | [[ -d /work ]] && WORKDIR="/work" 8 | [[ -d /github/workspace ]] && WORKDIR="/github/workspace" 9 | 10 | OUTPUT="$WORKDIR/scan-build-result" 11 | LLVM_VERSION="15" 12 | TIMEOUT="30m" 13 | ERROR_ON_BUGS="0" 14 | REPO="placeholder" 15 | SREPO="placeholder" 16 | 17 | if [ ! -z $GITHUB_REPOSITORY ]; then 18 | REPO="$GITHUB_REPOSITORY_OWNER/$GITHUB_REPOSITORY" 19 | SREPO="$GITHUB_REPOSITORY_OWNER.$GITHUB_REPOSITORY" 20 | fi 21 | 22 | while getopts ":r:l:t:b:o:" o; do 23 | case "${o}" in 24 | r) 25 | REPO=${OPTARG} 26 | SREPO=$(echo $REPO | tr '/' .) 27 | ;; 28 | l) 29 | LLVM_VERSION=${OPTARG} 30 | ;; 31 | t) 32 | TIMEOUT=${OPTARG} 33 | ;; 34 | b) 35 | ERROR_ON_BUGS=${OPTARG} 36 | ;; 37 | o) 38 | OUTPUT=$WORKDIR/${OPTARG} 39 | ;; 40 | *) 41 | usage 42 | ;; 43 | esac 44 | done 45 | shift $((OPTIND-1)) 46 | 47 | echo "Setting options:" 48 | echo " WORKDIR: $WORKDIR" 49 | echo " OUTPUT: $OUTPUT" 50 | echo " REPO: $REPO" 51 | echo " TIMEOUT: $TIMEOUT" 52 | echo " ERROR_ON_BUGS: $ERROR_ON_BUGS" 53 | 54 | ############## 55 | 56 | find_autoconf_builddir() { 57 | local search="configure autogen.sh bootstrap.sh bootstrap boot buildconf configure.ac" 58 | touch $OUTPUT/build_autoconf 59 | 60 | for f in $search; do 61 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 62 | for d in $dir; do 63 | if [ $(grep "$PWD/$d" $OUTPUT/build_autoconf | wc -l) -eq 0 ]; then 64 | ODIR=$PWD 65 | cd $d 66 | s=$(find . -iname "$f" -type f | head -n1 | awk -F'/' '{ print $2 }') 67 | echo "Found $s in $PWD" 68 | echo "$PWD $s" >> $OUTPUT/build_autoconf 69 | cd $ODIR 70 | fi 71 | done 72 | done 73 | } 74 | 75 | find_cmake_builddir() { 76 | local search="CMakeLists.txt" 77 | touch $OUTPUT/build_cmake 78 | 79 | for f in $search; do 80 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 81 | for d in $dir; do 82 | echo "Found $f in $PWD/$d" 83 | project=$(grep 'project(' ${d}/${f} | wc -l) 84 | if [ ${project} -gt 0 ]; then 85 | echo "Top level $f found at ${d}/${f}" 86 | echo "$PWD/$d $f" >> $OUTPUT/build_cmake 87 | fi 88 | done 89 | done 90 | } 91 | 92 | find_meson_builddir() { 93 | local search="meson.build" 94 | touch $OUTPUT/build_meson 95 | 96 | for f in $search; do 97 | local dir=$(find . -iname "$f" -type f -printf '%h\n') 98 | for d in $dir; do 99 | project=$(grep 'project(' "$d/$f" | wc -l) 100 | if [ $project -gt 0 ]; then 101 | echo "Found $f in $PWD/$d" 102 | echo "$PWD/$d $f" >> $OUTPUT/build_meson 103 | fi 104 | done 105 | done 106 | } 107 | 108 | find_builddirs() { 109 | find_autoconf_builddir 110 | find_cmake_builddir 111 | find_meson_builddir 112 | } 113 | 114 | search_and_install_dependencies() { 115 | packages="" 116 | declare -a files=( 117 | $(grep -rl "apt-get install") 118 | $(grep -rl "apt install") 119 | $(grep -rl "aptitude install") 120 | ) 121 | 122 | for r in "${files[@]}"; do 123 | found=0 124 | newline=0 125 | 126 | while read -r line; do 127 | if [ $(echo -n "${line}" | grep "apt" | grep "install" | wc -l) -eq 1 ]; then 128 | found=1 129 | fi 130 | 131 | # found install line, grab all potential packages from this line plus lines after while there is linebreak 132 | if [ $found -eq 1 ]; then 133 | if [ $newline -eq 0 ]; then 134 | packages+=$(echo -n $line | sed -r 's/^.*install //' | sed -r 's/^;//' | sed 's/\\/ /g' | awk '{split($0,a," "); for (x in a) { if (a[x] ~ /^[^-].*$/) { printf("%s ", a[x]) } } }') 135 | else 136 | packages+=$(echo -n $line | sed -r 's/^;//' | sed 's/\\/ /g' | awk '{split($0,a," "); for (x in a) { if (a[x] ~ /^[^-].*$/) { printf("%s ", a[x]) } } }') 137 | fi 138 | 139 | # check if there is a linebreak at the end 140 | if [ $(echo -n $line | awk '{print $NF}' | grep "\\\\" | wc -l) -eq 0 ]; then 141 | newline=0 142 | found=0 143 | else 144 | newline=1 145 | fi 146 | fi 147 | done < $r 148 | done 149 | 150 | packages=$(echo -n $packages | tr -dc '[:alnum:]\-\_ ') 151 | 152 | apt-get update 153 | for p in ${packages}; do 154 | apt-get -y install "${p}" || : 155 | done 156 | } 157 | 158 | get_submodules() { 159 | local dir=$(find . -iname ".gitmodules" -type f -printf '%h\n') 160 | for d in $dir; do 161 | cd $d 162 | git submodule update --init --recursive || : 163 | done 164 | } 165 | 166 | scan_build_autoconf() { 167 | while read -r build; do 168 | 169 | echo "Autoconf $build" 170 | 171 | dir=$(echo $build | awk '{ print $1 }') 172 | script=$(echo $build | awk '{ print $2 }') 173 | 174 | cd $dir 175 | 176 | # don't scan the same folder multiple times 177 | if [ -f "scan-build-done" ]; then 178 | continue; 179 | fi 180 | 181 | echo "Scanning $dir with setup $script" 182 | 183 | case $script in 184 | configure.ac) 185 | autoreconf -vif 186 | ;; 187 | 188 | Configure) 189 | mv Configure configure 190 | ;; 191 | 192 | configure) 193 | ;; 194 | 195 | *) 196 | chmod +x ./$script 197 | ./$script 198 | ;; 199 | esac 200 | 201 | echo $dir >> ${OUTPUT}/scan-build-configure.log 202 | 203 | ./configure | tee -a ${OUTPUT}/configure.log 2>&1 || continue 204 | intercept-build-${LLVM_VERSION} make -j2 | tee -a ${OUTPUT}/make.log 2>&1 || continue 205 | 206 | timeout -s 2 ${TIMEOUT} \ 207 | run-clang-tidy-${LLVM_VERSION} -quiet \ 208 | -config="{Checks: 'readability-function-cognitive-complexity', CheckOptions: [{key: readability-function-cognitive-complexity.Threshold, value: 0}, {key: readability-function-cognitive-complexity.DescribeBasicIncrements, value: False}]}" \ 209 | 2>/dev/null | \ 210 | grep warning | grep "cognitive complexity" | tee -a ${OUTPUT}/cognitive-complexity.log || : 211 | 212 | echo $dir >> ${OUTPUT}/scan-build.log 213 | 214 | timeout -s 2 ${TIMEOUT} \ 215 | /usr/bin/time -p -o ${OUTPUT}/scan-build-time \ 216 | analyze-build-${LLVM_VERSION} -v --cdb compile_commands.json --no-failure-reports --analyze-headers --force-analyze-debug-code -o ${OUTPUT}/scan-build-result \ 217 | --analyzer-config crosscheck-with-z3=true \ 218 | --disable-checker deadcode.DeadStores \ 219 | --enable-checker security.FloatLoopCounter \ 220 | --enable-checker security.insecureAPI.strcpy | tee -a ${OUTPUT}/analyze-build.log 2>&1 || continue 221 | 222 | t=$(cat $OUTPUT/scan-build-time | grep real | awk '{ print $2 }') 223 | echo "$dir $t" > $OUTPUT/time.log 224 | 225 | touch $dir/scan-build-done 226 | 227 | done < $OUTPUT/build_autoconf 228 | } 229 | 230 | scan_build_cmake() { 231 | while read -r build; do 232 | 233 | echo "CMake $build" 234 | 235 | dir=$(echo $build | awk '{ print $1 }') 236 | 237 | cd $dir 238 | 239 | # don't scan the same folder multiple times 240 | if [ -f "scan-build-done" ]; then 241 | continue; 242 | fi 243 | 244 | echo "Attempting to build $GITHUB_WORKSPACE/repo/$dir" 245 | 246 | mkdir -p $dir/build 247 | cd $dir/build 248 | 249 | echo $dir >> $OUTPUT/cmake.log 250 | 251 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=YES $dir | tee -a ${OUTPUT}/cmake.log 2>&1 || continue 252 | 253 | timeout -s 2 ${TIMEOUT} \ 254 | run-clang-tidy-${LLVM_VERSION} -quiet \ 255 | -config="{Checks: 'readability-function-cognitive-complexity', CheckOptions: [{key: readability-function-cognitive-complexity.Threshold, value: 0}, {key: readability-function-cognitive-complexity.DescribeBasicIncrements, value: False}]}" \ 256 | 2>/dev/null | \ 257 | grep warning | grep "cognitive complexity" | tee -a ${OUTPUT}/cognitive-complexity.log || : 258 | 259 | echo $dir >> $OUTPUT/analyze-build.log 260 | 261 | timeout -s 2 ${TIMEOUT} \ 262 | /usr/bin/time -p -o ${OUTPUT}/analyze-build-time \ 263 | analyze-build-${LLVM_VERSION} -v --cdb compile_commands.json --no-failure-reports --analyze-headers --force-analyze-debug-code -o ${OUTPUT}/scan-build-result \ 264 | --analyzer-config crosscheck-with-z3=true \ 265 | --disable-checker deadcode.DeadStores \ 266 | --enable-checker security.FloatLoopCounter \ 267 | --enable-checker security.insecureAPI.strcpy | tee -a ${OUTPUT}/analyze-build.log 2>&1 || continue 268 | 269 | t=$(cat $OUTPUT/analyze-build-time | grep real | awk '{ print $2 }') 270 | echo "$dir $t" > $OUTPUT/time.log 271 | 272 | touch $dir/scan-build-done 273 | 274 | done < $OUTPUT/build_cmake 275 | } 276 | 277 | scan_build_meson() { 278 | while read -r build; do 279 | 280 | echo "Meson $build" 281 | 282 | dir=$(echo $build | awk '{ print $1 }') 283 | 284 | cd $dir 285 | 286 | # don't scan the same folder multiple times 287 | if [ -f "scan-build-done" ]; then 288 | continue; 289 | fi 290 | 291 | echo $dir >> ${OUTPUT}/meson-setup.log 292 | 293 | meson setup builddir --buildtype debug 2>&1 | tee -a $OUTPUT/meson-setup.log || continue 294 | 295 | cd builddir 296 | 297 | timeout -s 2 ${TIMEOUT} \ 298 | run-clang-tidy-${LLVM_VERSION} -quiet \ 299 | -config="{Checks: 'readability-function-cognitive-complexity', CheckOptions: [{key: readability-function-cognitive-complexity.Threshold, value: 0}, {key: readability-function-cognitive-complexity.DescribeBasicIncrements, value: False}]}" \ 300 | 2>/dev/null | \ 301 | grep warning | grep "cognitive complexity" | tee -a ${OUTPUT}/cognitive-complexity.log || : 302 | 303 | echo $dir >> ${OUTPUT}/analyze-build.log 304 | 305 | timeout -s 2 ${TIMEOUT} \ 306 | /usr/bin/time -p -o ${OUTPUT}/analyze-build-time \ 307 | analyze-build-${LLVM_VERSION} -v --cdb compile_commands.json --no-failure-reports --analyze-headers --force-analyze-debug-code -o ${OUTPUT}/scan-build-result \ 308 | --analyzer-config crosscheck-with-z3=true \ 309 | --disable-checker deadcode.DeadStores \ 310 | --enable-checker security.FloatLoopCounter \ 311 | --enable-checker security.insecureAPI.strcpy | tee -a ${OUTPUT}/analyze-build.log 2>&1 || continue 312 | 313 | t=$(cat $OUTPUT/analyze-build-time | grep real | awk '{ print $2 }') 314 | echo "$dir $t" > $OUTPUT/time.log 315 | 316 | touch $dir/scan-build-done 317 | 318 | done < $OUTPUT/build_meson 319 | } 320 | 321 | scan_build() { 322 | scan_build_autoconf 323 | scan_build_cmake 324 | scan_build_meson 325 | 326 | if [ ! -d $OUTPUT/scan-build-result ]; then 327 | exit 10 328 | fi 329 | 330 | if [ $(cat $OUTPUT/cognitive-complexity.log | wc -l) -eq 0 ]; then 331 | exit 11 332 | fi 333 | } 334 | 335 | parse_info() { 336 | local f=$1 337 | local d=$2 338 | grep $d $f | awk -F "$d " '{ print $2 }' | rev | cut -c5- | rev | tr '"' "'" 339 | } 340 | 341 | generate_json() { 342 | bugfound=0 343 | now=$(date) 344 | functions=$(cat ${OUTPUT}/cognitive-complexity.log 2>/dev/null | wc -l) 345 | 346 | JSON="{ \"repo\": \"$REPO\", \"scan-date\": \"$now\", \"functions\": $functions, \"bugs\": [" 347 | 348 | for f in $(find ${OUTPUT}/scan-build-result -type f -name '*.html' | grep report); do 349 | bugfound=1 350 | bugtype=$(parse_info $f BUGTYPE) 351 | bugcategory=$(parse_info $f BUGCATEGORY) 352 | bugfile=$(parse_info $f BUGFILE) 353 | bugline=$(parse_info $f BUGLINE) 354 | bugdescription=$(parse_info $f BUGDESC) 355 | bugfunction=$(parse_info $f FUNCTIONNAME) 356 | report=$(echo -n $f | awk -F'scan-build-result/' '{ print $2 }') 357 | 358 | JSON+="{" 359 | JSON+=" \"category\": \"$bugcategory\"," 360 | JSON+=" \"type\": \"$bugtype\"," 361 | JSON+=" \"file\": \"$bugfile\"," 362 | JSON+=" \"line\": $bugline," 363 | JSON+=" \"function\": \"$bugfunction\"," 364 | JSON+=" \"description\": \"$bugdescription\"," 365 | JSON+=" \"report\": \"$report\"" 366 | JSON+=" }," 367 | done 368 | 369 | if [ -f ${OUTPUT}/cognitive-complexity.log ]; then 370 | while read -r line; do 371 | bugtype="Cognitive complexity" 372 | bugcategory="Readability" 373 | bugfile=$(echo $line | awk -F":" '{ print $1 }') 374 | bugline=$(echo $line | awk -F":" '{ print $2 }') 375 | bugfunction=$(echo $line | awk -F"function" '{ print $2 }' | awk '{ print $1 }' | sed "s/'//g") 376 | bugdescription=$(echo $line | awk -F"cognitive complexity of" '{ print $2 }' | awk '{ print $1 }') 377 | 378 | [[ $bugdescription -lt 25 ]] && continue 379 | 380 | bugfound=1 381 | 382 | JSON+="{" 383 | JSON+=" \"category\": \"$bugcategory\"," 384 | JSON+=" \"type\": \"$bugtype\"," 385 | JSON+=" \"file\": \"$bugfile\"," 386 | JSON+=" \"line\": $bugline," 387 | JSON+=" \"function\": \"$bugfunction\"," 388 | JSON+=" \"description\": \"$bugdescription\"" 389 | JSON+=" }," 390 | done < ${OUTPUT}/cognitive-complexity.log 391 | fi 392 | 393 | if [ $bugfound -eq 1 ]; then 394 | JSON="${JSON%?}" # Remove last "," 395 | fi 396 | 397 | JSON+="]" 398 | JSON+="}" 399 | 400 | echo $JSON > $OUTPUT/$SREPO.scan-build.json 401 | 402 | if [ $bugfound -eq 0 ]; then 403 | exit 0 404 | fi 405 | 406 | jq '.bugs[].category' $OUTPUT/$SREPO.scan-build.json | sort | uniq -c > $OUTPUT/bug-categories.txt 407 | jq '.bugs[].type' $OUTPUT/$SREPO.scan-build.json | sort | uniq -c > $OUTPUT/bug-types.txt 408 | 409 | JSON="${JSON%?}" # Remove last "}" 410 | JSON+="," 411 | 412 | JSON+="\"categories\": [" 413 | 414 | while read -r line; do 415 | c=$(echo $line | awk -F ' ' '{ print $1 }') 416 | d=$(echo $line | awk -F '"' '{ print $2 }') 417 | 418 | JSON+="{\"category\": \"$d\", \"count\": $c }," 419 | done < $OUTPUT/bug-categories.txt 420 | 421 | JSON="${JSON%?}" # Remove last "," 422 | JSON+="],\"types\":[" 423 | 424 | while read -r line; do 425 | c=$(echo $line | awk -F ' ' '{ print $1 }') 426 | d=$(echo $line | awk -F '"' '{ print $2 }') 427 | 428 | JSON+="{\"type\": \"$d\", \"count\": $c }," 429 | done < $OUTPUT/bug-types.txt 430 | 431 | JSON="${JSON%?}" # Remove last "," 432 | JSON+="]}" 433 | echo $JSON > $OUTPUT/$SREPO.scan-build.json 434 | } 435 | 436 | make_markdown_summary() { 437 | bugs=$(jq '.bugs | length' $OUTPUT/$SREPO.scan-build.json) 438 | complex_functions=$(jq '.bugs[] | select( any(.; .type == "Cognitive complexity") ) | length' $OUTPUT/$SREPO.scan-build.json | wc -l) 439 | bugs=$(( bugs - complex_functions )) 440 | functions=$(jq '.functions' $OUTPUT/$SREPO.scan-build.json) 441 | percent=$(echo "scale=2; $complex_functions / $functions * 100" | bc) 442 | 443 | echo "### $REPO: ${bugs} bugs found" >> $OUTPUT/summary.md 444 | echo "### Cognitively complex functions: $complex_functions (${percent}%)" >> $OUTPUT/summary.md 445 | 446 | [[ ${bugs} -eq 0 ]] && exit 0 447 | 448 | echo "#### Bug categories" >> $OUTPUT/summary.md 449 | echo "| Category | Count |" >> $OUTPUT/summary.md 450 | echo "| --- | --- |" >> $OUTPUT/summary.md 451 | jq -r '.categories[] | select( any(.; .category != "Readability") ) | "| \(.category) | \(.count) |"' $OUTPUT/$SREPO.scan-build.json >> $OUTPUT/summary.md 452 | echo "#### Bug types" >> $OUTPUT/summary.md 453 | echo "| Type | Count |" >> $OUTPUT/summary.md 454 | echo "| --- | --- |" >> $OUTPUT/summary.md 455 | jq -r '.types[] | select( any(.; .type != "Cognitive complexity") ) | "| \(.type) | \(.count) |"' $OUTPUT/$SREPO.scan-build.json >> $OUTPUT/summary.md 456 | 457 | [[ ! -z $GITHUB_STEP_SUMMARY ]] && cat $OUTPUT/summary.md >> $GITHUB_STEP_SUMMARY 458 | } 459 | 460 | finalize() { 461 | if [ ! -f $OUTPUT/$SREPO.scan-build.json ]; then 462 | echo "Converting to JSON failed" 463 | exit 1 464 | fi 465 | 466 | if [ ! -z $GITHUB_OUTPUT ]; then 467 | JSON=$(cat $OUTPUT/$SREPO.scan-build.json) 468 | echo "json=${JSON}" >> $GITHUB_OUTPUT 469 | else 470 | jq '.' $OUTPUT/$SREPO.scan-build.json 471 | fi 472 | 473 | BUGCOUNT=$(jq '.bugs | length' $OUTPUT/$SREPO.scan-build.json) 474 | 475 | if [ ! -z $GITHUB_OUTPUT ]; then 476 | echo "bugs=${BUGCOUNT}" >> $GITHUB_OUTPUT 477 | fi 478 | 479 | if [ $ERROR_ON_BUGS != "0" ] && [ $BUGCOUNT -gt 0 ]; then 480 | echo "Found $BUGCOUNT bugs." 481 | exit 1 482 | fi 483 | 484 | if [ -f $OUTPUT/placeholder.scan-build.json ]; then 485 | mv $OUTPUT/placeholder.scan-build.json $OUTPUT/scan-build.json 486 | fi 487 | 488 | chmod -R +r $OUTPUT 489 | } 490 | 491 | ###### 492 | 493 | mkdir -p $OUTPUT 494 | cd $WORKDIR 495 | 496 | find_builddirs 497 | search_and_install_dependencies 498 | get_submodules 499 | scan_build 500 | generate_json 501 | make_markdown_summary 502 | finalize 503 | 504 | exit 0 505 | --------------------------------------------------------------------------------