Skip to content

Commit b825c6d

Browse files
authored
Merge pull request matplotlib#31661 from tacaswell/bld/static_analysis
BLD: add more static analysis
2 parents 6238300 + 98a8893 commit b825c6d

17 files changed

Lines changed: 420 additions & 39 deletions

.clang-tidy

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
# clang-tidy configuration for matplotlib's src/ directory.
3+
#
4+
# Philosophy: enable checks that find real bugs (memory safety, undefined
5+
# behaviour, security) and suppress checks that are high-noise style rules
6+
# inappropriate for a C/C++ codebase that interfaces heavily with C APIs
7+
# (CPython, FreeType, libagg) via pybind11.
8+
#
9+
# Run with:
10+
# clang-tidy -p <build_dir> --config-file=src/.clang-tidy <file>
11+
12+
Checks: >
13+
bugprone-*,
14+
clang-analyzer-*,
15+
objc-*,
16+
performance-move-const-arg,
17+
performance-move-constructor-init,
18+
performance-no-automatic-move,
19+
portability-*,
20+
-bugprone-assignment-in-if-condition,
21+
-bugprone-easily-swappable-parameters,
22+
-bugprone-implicit-widening-of-multiplication-result,
23+
-bugprone-macro-parentheses,
24+
-bugprone-narrowing-conversions,
25+
-bugprone-reserved-identifier,
26+
-bugprone-throwing-static-initialization,
27+
-clang-analyzer-optin.cplusplus.UninitializedObject,
28+
-clang-analyzer-optin.performance.Padding,
29+
30+
# Only report findings in matplotlib's own src/ headers, not in pybind11,
31+
# Python.h, agg, or other vendored includes.
32+
HeaderFilterRegex: '.*/matplotlib/src/.*'
33+
34+
WarningsAsErrors: ''
35+
36+
CheckOptions: []

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ updates:
55
directory: "/"
66
schedule:
77
interval: "weekly"
8+
cooldown:
9+
default-days: 7
810
groups:
911
actions:
1012
patterns:
@@ -13,9 +15,13 @@ updates:
1315
directory: "/"
1416
schedule:
1517
interval: "weekly"
18+
cooldown:
19+
default-days: 7
1620
exclude-paths:
1721
- "ci/minver-requirements.txt"
1822
- package-ecosystem: "pre-commit"
1923
directory: "/"
2024
schedule:
2125
interval: "monthly"
26+
cooldown:
27+
default-days: 7

.github/workflows/autoclose_schedule.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@ jobs:
2323
runs-on: ubuntu-latest
2424
steps:
2525
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26+
with:
27+
persist-credentials: false
2628
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
2729
with:
2830
python-version: '3.13'
2931
- name: Install PyGithub
3032
run: pip install -Uq PyGithub
3133

32-
- name: Checkout repository
33-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
34-
3534
- name: Close PRs labeled more than 14 days ago
3635
run: |
3736
python tools/autoclose_prs.py

.github/workflows/linting.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,37 @@ jobs:
8484
-tee -reporter=github-check -filter-mode nofilter
8585
8686
87+
clang-tidy:
88+
name: clang-tidy (macOS / Objective-C)
89+
runs-on: macos-latest # run on macOS so we can lint the objectiveC file
90+
permissions:
91+
contents: read
92+
steps:
93+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
94+
with:
95+
persist-credentials: false
96+
97+
- name: Set up Python 3
98+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
99+
with:
100+
python-version: '3.14'
101+
102+
- name: Install OS dependencies
103+
run: brew install llvm
104+
105+
- name: Install build dependencies
106+
run: pip3 install meson ninja pybind11 setuptools-scm
107+
108+
- name: Run clang-tidy
109+
run: |
110+
set -o pipefail
111+
LLVM_PREFIX=$(brew --prefix llvm)
112+
export CC=$LLVM_PREFIX/bin/clang
113+
export CXX=$LLVM_PREFIX/bin/clang++
114+
export OBJC=$LLVM_PREFIX/bin/clang
115+
export PATH=$LLVM_PREFIX/bin:$PATH
116+
python tools/run_clang_tidy.py
117+
87118
eslint:
88119
name: eslint
89120
runs-on: ubuntu-latest

.github/workflows/zizmor.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: zizmor
3+
4+
on:
5+
push:
6+
branches: [main, v*.x]
7+
pull_request:
8+
branches: [main]
9+
schedule:
10+
- cron: '45 19 * * 1'
11+
12+
permissions: {}
13+
14+
jobs:
15+
zizmor:
16+
name: zizmor
17+
if: github.repository == 'matplotlib/matplotlib'
18+
runs-on: ubuntu-latest
19+
permissions:
20+
security-events: write
21+
contents: read
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26+
with:
27+
persist-credentials: false
28+
29+
- name: Run zizmor
30+
uses: zizmorcore/zizmor-action@b572f7b1a1c2d41efaab43d504f68d215c3cd727 # v0.5.4

.github/zizmor.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
rules:
3+
dangerous-triggers:
4+
ignore:
5+
# These workflows use pull_request_target solely to obtain write access
6+
# for API operations (labeling, commenting) on fork PRs. None of them
7+
# check out or execute any PR-supplied code.
8+
- autoclose_comment.yml:11
9+
- conflictcheck.yml:3
10+
- labeler.yml:3
11+
- pr_welcome.yml:4
12+
cache-poisoning:
13+
ignore:
14+
# cygwin.yml is a test-only workflow; no artifacts are published.
15+
# This is triggering a low-confidence flag that the workflow_dispatch:
16+
# trigger may imply artifact publishing
17+
- cygwin.yml:144:9
18+
- cygwin.yml:151:9
19+
- cygwin.yml:158:9

.pre-commit-config.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ repos:
7777
hooks:
7878
- id: yamllint
7979
args: ["--strict", "--config-file=.yamllint.yml"]
80+
- repo: https://github.com/shellcheck-py/shellcheck-py
81+
rev: 745eface02aef23e168a8afb6b5737818efbea95 # frozen: v0.11.0.1
82+
hooks:
83+
- id: shellcheck
84+
- repo: https://github.com/zizmorcore/zizmor-pre-commit
85+
rev: a4727cbbcd26d7098e96b9cb738169b59711ae51 # frozen: v1.24.1
86+
hooks:
87+
- id: zizmor
88+
- repo: https://github.com/simple-icons/svglint
89+
rev: 8402586b94f073686e46707a163082e270ee5768 # frozen: v4.2.1
90+
hooks:
91+
- id: svglint
92+
# Override the top-level exclude so that mpl-data/images/ toolbar
93+
# icons are also linted. Exemptions for the intentional interactive
94+
# SVG examples are handled in .svglintrc.mjs.
95+
exclude: '^$'
8096
- repo: https://github.com/python-jsonschema/check-jsonschema
8197
rev: f805888065fdb6162e1f800e50bb9460cbd223d6 # frozen: 0.37.2
8298
hooks:

.svglintrc.mjs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/** @type {import('svglint').Config} */
2+
const config = {
3+
rules: {
4+
// Ensure all SVGs are valid XML.
5+
valid: true,
6+
7+
// Block elements that can execute code or embed arbitrary content.
8+
// <script> can run arbitrary JavaScript; <foreignObject> and <iframe>
9+
// can embed arbitrary HTML. Unlike event-handler attributes (which
10+
// are a legitimate tool for matplotlib's interactive SVG examples),
11+
// there is no use case in this repository for these elements outside
12+
// of a <script> already paired with its own exemption.
13+
elm: {
14+
"script": false,
15+
"foreignObject": false,
16+
"iframe": false,
17+
},
18+
19+
custom: [
20+
// Block external URL references in href / xlink:href.
21+
// Internal fragment references (#id), data: URIs, and relative
22+
// paths are all fine. http/https/ftp and protocol-relative URLs
23+
// are blocked because they cause the SVG renderer to make an
24+
// outbound network request, leaking the viewer's IP and UA to an
25+
// attacker-controlled server.
26+
(reporter, $, _ast) => {
27+
reporter.name = "no-external-references";
28+
const externalPattern = /^(https?:|ftp:|\/\/)/i;
29+
$("[href], [xlink\\:href]").each((_i, el) => {
30+
if (!el.attribs) { return; }
31+
const href =
32+
el.attribs["href"] ?? el.attribs["xlink:href"];
33+
if (href && externalPattern.test(href)) {
34+
reporter.error(
35+
`Found external reference '${href}' on <${el.name}>. ` +
36+
"External URL references in SVGs cause the renderer " +
37+
"to make an outbound request, leaking viewer IP/UA."
38+
);
39+
}
40+
});
41+
},
42+
],
43+
},
44+
45+
// These four files are intentional interactive SVG examples that
46+
// demonstrate matplotlib's SVG interactivity features. They contain
47+
// embedded ECMAScript by design and are exempted from the <script> rule.
48+
ignore: [
49+
"doc/_static/svg_histogram.svg",
50+
"doc/_static/svg_tooltip.svg",
51+
"galleries/examples/user_interfaces/images/svg_histogram.svg",
52+
"galleries/examples/user_interfaces/images/svg_tooltip.svg",
53+
],
54+
};
55+
56+
export default config;

doc/devel/coding_guide.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,44 @@ C/C++ extensions
131131
implement new features only if the required changes cannot be made elsewhere
132132
in the codebase. In particular, avoid making style fixes to it.
133133

134+
.. _clang-tidy:
135+
136+
Static analysis with clang-tidy
137+
-------------------------------
138+
139+
Matplotlib's C/C++ sources in :file:`src/` are checked with
140+
`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`__ in CI (see
141+
:file:`.github/workflows/linting.yml`). The check
142+
configuration lives in :file:`.clang-tidy`.
143+
144+
The logic lives in :file:`tools/run_clang_tidy.py`. It requires
145+
``clang-tidy`` on ``PATH`` and ``meson`` and ``pybind11`` installed::
146+
147+
pip install meson pybind11 setuptools-scm
148+
149+
On macOS, ``clang-tidy`` is not on ``PATH`` after a Homebrew install::
150+
151+
brew install llvm
152+
export PATH=$(brew --prefix llvm)/bin:$PATH
153+
154+
The script uses a dedicated ``build/clang-tidy/`` directory (created
155+
automatically on first run) and delegates to meson's built-in
156+
``clang-tidy`` target. To run locally:
157+
158+
.. code-block:: bash
159+
160+
python tools/run_clang_tidy.py
161+
162+
163+
To suppress false-positives use narrow checks and a comment:
164+
165+
.. code-block:: c++
166+
167+
*indices++ = value; // NOLINT(clang-analyzer-security.ArrayBound): loop
168+
// iterates exactly N times; the analyzer cannot prove this from the macro.
169+
170+
171+
134172
.. _keyword-argument-processing:
135173

136174
Keyword argument processing

0 commit comments

Comments
 (0)