]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
GHA: add a job to check function complexity
authorDaniel Stenberg <daniel@haxx.se>
Tue, 20 May 2025 14:49:57 +0000 (16:49 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 21 May 2025 20:59:13 +0000 (22:59 +0200)
- Done with the new top-complexity script which uses the pmccabe tool.

- Any function scoring over 100 makes the test fail

- The script outputs all functions scoring over 70

- Two >100 functions are whitelisted by name, but they are not allowed
  to increase their scores.

Closes #17398

.github/workflows/checksrc.yml
scripts/Makefile.am
scripts/top-complexity [new file with mode: 0755]

index 990551aeab86a41c4960c328419a023cbc2c9bee..ae11a8bddaab04c3b32622fe1e2c49c157786815 100644 (file)
@@ -94,6 +94,26 @@ jobs:
       - name: REUSE Compliance Check
         uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5
 
+  complexity:
+    runs-on: ubuntu-latest
+    timeout-minutes: 3
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+        with:
+          persist-credentials: false
+        name: checkout
+
+      - name: install pmccabe
+        run: |
+          sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
+          sudo apt-get -o Dpkg::Use-Pty=0 update
+          sudo rm -f /var/lib/man-db/auto-update
+          sudo apt-get -o Dpkg::Use-Pty=0 install \
+            pmccabe
+
+      - name: check complexity scores
+        run: ./scripts/top-complexity
+
   miscchecks:
     runs-on: ubuntu-latest
     timeout-minutes: 5
index 03eb8044ad67c45bcad92e53d769aebc534d0c29..0b278e62b01fc7415448c555ce75e62f68b348a8 100644 (file)
@@ -25,7 +25,7 @@
 EXTRA_DIST = coverage.sh completion.pl firefox-db2pem.sh checksrc.pl checksrc-all.sh \
   mk-ca-bundle.pl mk-unity.pl schemetable.c cd2nroff nroff2cd cdall cd2cd managen    \
   dmaketgz maketgz release-tools.sh verify-release cmakelint.sh mdlinkcheck          \
-  CMakeLists.txt pythonlint.sh randdisable wcurl
+  CMakeLists.txt pythonlint.sh randdisable wcurl top-complexity
 
 dist_bin_SCRIPTS = wcurl
 
diff --git a/scripts/top-complexity b/scripts/top-complexity
new file mode 100755 (executable)
index 0000000..c7129a1
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/env perl
+#***************************************************************************
+#                                  _   _ ____  _
+#  Project                     ___| | | |  _ \| |
+#                             / __| | | | |_) | |
+#                            | (__| |_| |  _ <| |___
+#                             \___|\___/|_| \_\_____|
+#
+# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at https://curl.se/docs/copyright.html.
+#
+# You may opt to use, copy, modify, merge, publish, distribute and/or sell
+# copies of the Software, and permit persons to whom the Software is
+# furnished to do so, under the terms of the COPYING file.
+#
+# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+# KIND, either express or implied.
+#
+# SPDX-License-Identifier: curl
+#
+###########################################################################
+
+use strict;
+use warnings;
+
+#######################################################################
+# Check for a command in the PATH of the test server.
+#
+sub checkcmd {
+    my ($cmd)=@_;
+    my @paths;
+    if ($^O eq 'MSWin32' || $^O eq 'dos' || $^O eq 'os2') {
+        # PATH separator is different
+        @paths=(split(';', $ENV{'PATH'}));
+    }
+    else {
+        @paths=(split(':', $ENV{'PATH'}), "/usr/sbin", "/usr/local/sbin",
+                "/sbin", "/usr/bin", "/usr/local/bin");
+    }
+    for(@paths) {
+        if( -x "$_/$cmd" && ! -d "$_/$cmd" ) {
+            # executable bit but not a directory!
+            return "$_/$cmd";
+        }
+    }
+    return "";
+}
+
+my $pmccabe = checkcmd("pmccabe");
+if(!$pmccabe) {
+    print "Make sure 'pmccabe' exists in your PATH\n";
+    exit 1;
+}
+if(! -r "lib/url.c" || ! -r "lib/urldata.h") {
+    print "Invoke this script in the curl source tree root\n";
+    exit 1;
+}
+
+my @files;
+open(F, "git ls-files '*.c'|");
+while(<F>) {
+    chomp $_;
+    my $file = $_;
+    # we can't filter these with git so do it here
+    if($file =~ /^(lib|src)/) {
+        push @files, $file;
+    }
+}
+
+my $cmd = "$pmccabe ".join(" ", @files);
+my @output=`$cmd`;
+
+# these functions can have these scores, but not higher
+my %whitelist = (
+    'getparameter' => 142,
+    'single_transfer' => 124
+    );
+
+# functions with complexity above this level causes the function to return error
+my $cutoff = 100;
+
+# functions above this complexity level are shown
+my $show = 70;
+
+my $error = 0;
+my %where;
+my %perm;
+# each line starts with the complexity score
+# 142     417     809     1677    1305    src/tool_getparam.c(1677): getparameter
+for my $l (@output) {
+    chomp $l;
+    if($l =~/^(\d+)\t\d+\t\d+\t\d+\t\d+\t([^\(]+).*: ([^ ]*)/) {
+        my ($score, $path, $func)=($1, $2, $3);
+
+        if($score > $show) {
+            my $allow = 0;
+            if($whitelist{$func} &&
+               ($score <= $whitelist{$func})) {
+                $allow = 1;
+            }
+            $where{"$path:$func"}=$score;
+            $perm{"$path:$func"}=$allow;
+            if(($score > $cutoff) && !$allow) {
+                $error++;
+            }
+        }
+    }
+
+}
+
+my $showncutoff;
+for my $e (sort {$where{$b} <=> $where{$a}} keys %where) {
+    if(!$showncutoff &&
+       ($where{$e} <= $cutoff)) {
+        print "\n---- threshold: $cutoff ----\n\n";
+        $showncutoff = 1;
+    }
+    printf "%-5d %s%s\n", $where{$e}, $e,
+        $perm{$e} ? " [ALLOWED]": "";
+}
+
+exit $error;