| Dmytro Shteflyuk | 3346c01 | 2026-02-13 17:40:41 -0500 | [diff] [blame] | 1 | name: "Static Code Analysis" |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: ["*"] |
| 6 | pull_request: |
| 7 | branches: ["*"] |
| 8 | workflow_dispatch: |
| 9 | |
| 10 | env: |
| 11 | BUILD_DEPS: automake bison flex git libboost-all-dev libevent-dev libssl-dev libtool make pkg-config |
| 12 | SCA_DEPS: cppcheck python3-flake8 sloccount libglib2.0-dev |
| 13 | # Disable all languages for which we don't have SCA checks in place |
| 14 | CONFIG_ARGS_FOR_SCA: > |
| 15 | --enable-tutorial=no |
| 16 | --disable-debug |
| 17 | --disable-tests |
| 18 | --disable-dependency-tracking |
| 19 | --without-java |
| 20 | --without-kotlin |
| 21 | --without-netstd |
| 22 | --without-nodejs |
| 23 | --without-nodets |
| 24 | --without-swift |
| 25 | --without-go |
| 26 | --without-dart |
| 27 | --without-erlang |
| 28 | --without-haxe |
| 29 | --without-ruby |
| 30 | --without-rs |
| 31 | --without-lua |
| 32 | --without-perl |
| 33 | --without-d |
| 34 | --without-cl |
| 35 | |
| 36 | permissions: |
| 37 | contents: read |
| 38 | |
| 39 | jobs: |
| 40 | sca: |
| 41 | runs-on: ubuntu-24.04 |
| 42 | steps: |
| 43 | - uses: actions/checkout@v6 |
| 44 | |
| 45 | - name: Install dependencies |
| 46 | run: | |
| 47 | sudo apt-get update -yq |
| 48 | sudo apt-get install -y --no-install-recommends g++ $BUILD_DEPS $SCA_DEPS |
| 49 | |
| 50 | - name: Set up PHP |
| 51 | uses: shivammathur/setup-php@v2 |
| 52 | with: |
| 53 | # Lowest supported PHP version |
| 54 | php-version: "7.1" |
| 55 | extensions: mbstring, xml, curl, pcntl |
| 56 | |
| 57 | - uses: actions/setup-python@v6 |
| 58 | with: |
| 59 | python-version: "3.12" |
| 60 | |
| 61 | - name: Install Python test dependencies |
| 62 | run: | |
| 63 | python -m pip install --upgrade pip setuptools wheel flake8 |
| 64 | |
| Dmytro Shteflyuk | df22bb1 | 2025-12-20 13:41:21 -0500 | [diff] [blame^] | 65 | - name: Set up Ruby |
| 66 | uses: ruby/setup-ruby@v1 |
| 67 | with: |
| 68 | ruby-version: "2.7" |
| 69 | bundler-cache: true |
| 70 | working-directory: "lib/rb" |
| 71 | |
| Dmytro Shteflyuk | 3346c01 | 2026-02-13 17:40:41 -0500 | [diff] [blame] | 72 | # Generate thrift files so the static code analysis includes an analysis |
| 73 | # of the files the thrift compiler spits out. |
| 74 | - name: Build compiler |
| 75 | id: compile |
| 76 | run: | |
| 77 | ./bootstrap.sh |
| 78 | ./configure $CONFIG_ARGS_FOR_SCA |
| 79 | make -j$(nproc) -C compiler/cpp |
| 80 | |
| 81 | - name: Run cppcheck |
| 82 | id: cppcheck |
| 83 | continue-on-error: true |
| 84 | run: | |
| 85 | make -j$(nproc) -C lib/cpp |
| 86 | make -j$(nproc) -C test/cpp precross |
| 87 | |
| 88 | make -j$(nproc) -C lib/c_glib |
| 89 | make -j$(nproc) -C test/c_glib precross |
| 90 | |
| 91 | # Compiler cppcheck (All) |
| 92 | cppcheck --force --quiet --inline-suppr --enable=all -j2 compiler/cpp/src |
| 93 | |
| 94 | # C++ cppcheck (All) |
| 95 | cppcheck --force --quiet --inline-suppr --enable=all -j2 lib/cpp/src lib/cpp/test test/cpp tutorial/cpp |
| 96 | |
| 97 | # C Glib cppcheck (All) |
| 98 | cppcheck --force --quiet --inline-suppr --enable=all -j2 lib/c_glib/src lib/c_glib/test test/c_glib/src tutorial/c_glib |
| 99 | |
| 100 | # Silent error checks |
| 101 | # See THRIFT-4371: flex generated scanner code causes false positives in cppcheck. |
| 102 | # suppress *:thrift/thriftl.cc -> flex-generated lexer triggers false null pointer paths. |
| 103 | # suppress syntaxError:thrift/thrifty.cc -> bison-generated parser is not fully parseable. |
| 104 | # suppress normalCheckLevelMaxBranches:compiler/cpp/src/* -> avoid info-only branch limit noise. |
| 105 | cppcheck --force --quiet --inline-suppr \ |
| 106 | --suppress="*:thrift/thriftl.cc" \ |
| 107 | --suppress="syntaxError:thrift/thrifty.cc" \ |
| 108 | --suppress="normalCheckLevelMaxBranches:compiler/cpp/src/*" \ |
| 109 | --error-exitcode=1 -j2 compiler/cpp/src |
| 110 | |
| 111 | # suppress unknownMacro:lib/cpp/src/thrift/qt/* -> Qt namespace macro needs Qt preprocessing. |
| 112 | # suppress unknownMacro:lib/cpp/test/* -> Boost.Test macros are unresolved in standalone analysis. |
| 113 | # suppress syntaxError:lib/cpp/src/thrift/transport/TSSLSocket.cpp -> OpenSSL macro branches confuse parser. |
| 114 | # suppress normalCheckLevelMaxBranches:* -> avoid info-only branch limit noise. |
| 115 | # exclude lib/cpp/test/gen-cpp and test/cpp/gen-* -> generated fixtures duplicate source/test coverage. |
| 116 | cppcheck --force --quiet --inline-suppr \ |
| 117 | --suppress="unknownMacro:lib/cpp/src/thrift/qt/*" \ |
| 118 | --suppress="unknownMacro:lib/cpp/test/*" \ |
| 119 | --suppress="syntaxError:lib/cpp/src/thrift/transport/TSSLSocket.cpp" \ |
| 120 | --suppress="normalCheckLevelMaxBranches:lib/cpp/src/*" \ |
| 121 | --suppress="normalCheckLevelMaxBranches:lib/cpp/test/*" \ |
| 122 | --suppress="normalCheckLevelMaxBranches:test/cpp/*" \ |
| 123 | --suppress="normalCheckLevelMaxBranches:tutorial/cpp/*" \ |
| 124 | -i lib/cpp/test/gen-cpp \ |
| 125 | -i test/cpp/gen-cpp \ |
| 126 | -i test/cpp/gen-cpp-forward \ |
| 127 | -i test/cpp/gen-cpp-private \ |
| 128 | -i test/cpp/gen-cpp-enumclass \ |
| 129 | --error-exitcode=1 -j2 lib/cpp/src lib/cpp/test test/cpp tutorial/cpp |
| 130 | |
| 131 | # suppress unknownMacro:lib/c_glib/src/* -> GObject type macros are unresolved in standalone analysis. |
| 132 | # suppress unknownMacro:lib/c_glib/test/* -> test-side GLib macros are unresolved without full preprocess. |
| 133 | # suppress syntaxError:lib/c_glib/test/* -> GLib assert macros parse as syntax errors. |
| 134 | # suppress normalCheckLevelMaxBranches:* -> avoid info-only branch limit noise. |
| 135 | # exclude lib/c_glib/test/gen-c_glib -> generated bindings are covered by generator output checks. |
| 136 | # exclude lib/c_glib/test/gen-cpp -> generated skeleton has placeholder methods without returns. |
| 137 | cppcheck --force --quiet --inline-suppr \ |
| 138 | --suppress="unknownMacro:lib/c_glib/src/*" \ |
| 139 | --suppress="unknownMacro:lib/c_glib/test/*" \ |
| 140 | --suppress="syntaxError:lib/c_glib/test/*" \ |
| 141 | --suppress="normalCheckLevelMaxBranches:lib/c_glib/src/*" \ |
| 142 | --suppress="normalCheckLevelMaxBranches:lib/c_glib/test/*" \ |
| 143 | --suppress="normalCheckLevelMaxBranches:test/c_glib/*" \ |
| 144 | --suppress="normalCheckLevelMaxBranches:tutorial/c_glib/*" \ |
| 145 | -i lib/c_glib/test/gen-c_glib \ |
| 146 | -i lib/c_glib/test/gen-cpp \ |
| 147 | --error-exitcode=1 -j2 lib/c_glib/src lib/c_glib/test test/c_glib/src tutorial/c_glib |
| 148 | |
| 149 | - name: Run flake8 |
| 150 | id: flake8 |
| 151 | continue-on-error: true |
| 152 | run: | |
| 153 | make -j$(nproc) -C test/py precross |
| 154 | |
| 155 | flake8 |
| 156 | |
| 157 | - name: Run phpcs |
| 158 | id: phpcs |
| 159 | continue-on-error: true |
| 160 | run: | |
| 161 | # PHP code style |
| 162 | composer install --quiet |
| 163 | ./vendor/bin/phpcs |
| 164 | |
| Dmytro Shteflyuk | df22bb1 | 2025-12-20 13:41:21 -0500 | [diff] [blame^] | 165 | - name: Run rubocop |
| 166 | id: rubocop |
| 167 | continue-on-error: true |
| 168 | working-directory: "lib/rb" |
| 169 | run: | |
| 170 | bundle exec rubocop --config .rubocop.yml --format progress --format github . ../../test/rb ../../tutorial/rb |
| 171 | |
| Dmytro Shteflyuk | 3346c01 | 2026-02-13 17:40:41 -0500 | [diff] [blame] | 172 | - name: Print statistics |
| 173 | if: ${{ always() }} |
| 174 | run: | |
| 175 | # TODO etc |
| 176 | echo "FIXMEs: $(grep -r FIXME * | wc -l)" |
| 177 | echo "HACKs: $(grep -r HACK * | wc -l)" |
| 178 | echo "TODOs: $(grep -r TODO * | wc -l)" |
| 179 | |
| 180 | # LoC |
| 181 | sloccount . |
| 182 | |
| 183 | # System info |
| 184 | # dpkg -l |
| 185 | uname -a |
| 186 | |
| 187 | - name: Fail if any SCA check failed |
| 188 | if: ${{ always() && steps.compile.outcome == 'success' }} |
| 189 | env: |
| 190 | CPPCHECK_OUTCOME: ${{ steps.cppcheck.outcome }} |
| 191 | FLAKE8_OUTCOME: ${{ steps.flake8.outcome }} |
| 192 | PHPCS_OUTCOME: ${{ steps.phpcs.outcome }} |
| Dmytro Shteflyuk | df22bb1 | 2025-12-20 13:41:21 -0500 | [diff] [blame^] | 193 | RUBOCOP_OUTCOME: ${{ steps.rubocop.outcome }} |
| Dmytro Shteflyuk | 3346c01 | 2026-02-13 17:40:41 -0500 | [diff] [blame] | 194 | run: | |
| 195 | failed=0 |
| 196 | |
| 197 | if [ "$CPPCHECK_OUTCOME" != "success" ]; then |
| 198 | echo "::error::Step 'cppcheck' failed (outcome: $CPPCHECK_OUTCOME)" |
| 199 | failed=1 |
| 200 | fi |
| 201 | if [ "$FLAKE8_OUTCOME" != "success" ]; then |
| 202 | echo "::error::Step 'flake8' failed (outcome: $FLAKE8_OUTCOME)" |
| 203 | failed=1 |
| 204 | fi |
| 205 | if [ "$PHPCS_OUTCOME" != "success" ]; then |
| 206 | echo "::error::Step 'phpcs' failed (outcome: $PHPCS_OUTCOME)" |
| 207 | failed=1 |
| 208 | fi |
| Dmytro Shteflyuk | df22bb1 | 2025-12-20 13:41:21 -0500 | [diff] [blame^] | 209 | if [ "$RUBOCOP_OUTCOME" != "success" ]; then |
| 210 | echo "::error::Step 'rubocop' failed (outcome: $RUBOCOP_OUTCOME)" |
| 211 | failed=1 |
| 212 | fi |
| Dmytro Shteflyuk | 3346c01 | 2026-02-13 17:40:41 -0500 | [diff] [blame] | 213 | |
| 214 | exit $failed |