Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# clang-tidy configuration for matplotlib's src/ directory.
#
# Philosophy: enable checks that find real bugs (memory safety, undefined
# behaviour, security) and suppress checks that are high-noise style rules
# inappropriate for a C/C++ codebase that interfaces heavily with C APIs
# (CPython, FreeType, libagg) via pybind11.
#
# Run with:
# clang-tidy -p <build_dir> --config-file=src/.clang-tidy <file>

Checks: >
bugprone-*,
clang-analyzer-*,
objc-*,
performance-move-const-arg,
performance-move-constructor-init,
performance-no-automatic-move,
portability-*,
-bugprone-assignment-in-if-condition,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-reserved-identifier,
-bugprone-throwing-static-initialization,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-optin.performance.Padding,

# Only report findings in matplotlib's own src/ headers, not in pybind11,
# Python.h, agg, or other vendored includes.
HeaderFilterRegex: '.*/matplotlib/src/.*'

WarningsAsErrors: ''

CheckOptions: []
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
actions:
patterns:
Expand All @@ -13,9 +15,13 @@ updates:
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
exclude-paths:
- "ci/minver-requirements.txt"
- package-ecosystem: "pre-commit"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
5 changes: 2 additions & 3 deletions .github/workflows/autoclose_schedule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.13'
- name: Install PyGithub
run: pip install -Uq PyGithub

- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Close PRs labeled more than 14 days ago
run: |
python tools/autoclose_prs.py
31 changes: 31 additions & 0 deletions .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,37 @@ jobs:
-tee -reporter=github-check -filter-mode nofilter


clang-tidy:
name: clang-tidy (macOS / Objective-C)
runs-on: macos-latest # run on macOS so we can lint the objectiveC file
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Set up Python 3
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.14'

- name: Install OS dependencies
run: brew install llvm

- name: Install build dependencies
run: pip3 install meson ninja pybind11 setuptools-scm

- name: Run clang-tidy
run: |
set -o pipefail
LLVM_PREFIX=$(brew --prefix llvm)
export CC=$LLVM_PREFIX/bin/clang
export CXX=$LLVM_PREFIX/bin/clang++
export OBJC=$LLVM_PREFIX/bin/clang
export PATH=$LLVM_PREFIX/bin:$PATH
python tools/run_clang_tidy.py

eslint:
name: eslint
runs-on: ubuntu-latest
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: zizmor

on:
push:
branches: [main, v*.x]
pull_request:
branches: [main]
schedule:
- cron: '45 19 * * 1'

permissions: {}

jobs:
zizmor:
name: zizmor
if: github.repository == 'matplotlib/matplotlib'
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Run zizmor
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
19 changes: 19 additions & 0 deletions .github/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
rules:
dangerous-triggers:
ignore:
# These workflows use pull_request_target solely to obtain write access
# for API operations (labeling, commenting) on fork PRs. None of them
# check out or execute any PR-supplied code.
- autoclose_comment.yml:11
- conflictcheck.yml:3
- labeler.yml:3
- pr_welcome.yml:4
cache-poisoning:
ignore:
# cygwin.yml is a test-only workflow; no artifacts are published.
# This is triggering a low-confidence flag that the workflow_dispatch:
# trigger may imply artifact publishing
- cygwin.yml:144:9
- cygwin.yml:151:9
- cygwin.yml:158:9
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ repos:
hooks:
- id: yamllint
args: ["--strict", "--config-file=.yamllint.yml"]
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: 745eface02aef23e168a8afb6b5737818efbea95 # frozen: v0.11.0.1
hooks:
- id: shellcheck
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: a4727cbbcd26d7098e96b9cb738169b59711ae51 # frozen: v1.24.1
hooks:
- id: zizmor
- repo: https://github.com/simple-icons/svglint
rev: 8402586b94f073686e46707a163082e270ee5768 # frozen: v4.2.1
hooks:
- id: svglint
# Override the top-level exclude so that mpl-data/images/ toolbar
# icons are also linted. Exemptions for the intentional interactive
# SVG examples are handled in .svglintrc.mjs.
exclude: '^$'
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: f805888065fdb6162e1f800e50bb9460cbd223d6 # frozen: 0.37.2
hooks:
Expand Down
56 changes: 56 additions & 0 deletions .svglintrc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/** @type {import('svglint').Config} */
const config = {
rules: {
// Ensure all SVGs are valid XML.
valid: true,

// Block elements that can execute code or embed arbitrary content.
// <script> can run arbitrary JavaScript; <foreignObject> and <iframe>
// can embed arbitrary HTML. Unlike event-handler attributes (which
// are a legitimate tool for matplotlib's interactive SVG examples),
// there is no use case in this repository for these elements outside
// of a <script> already paired with its own exemption.
elm: {
"script": false,
"foreignObject": false,
"iframe": false,
},

custom: [
// Block external URL references in href / xlink:href.
// Internal fragment references (#id), data: URIs, and relative
// paths are all fine. http/https/ftp and protocol-relative URLs
// are blocked because they cause the SVG renderer to make an
// outbound network request, leaking the viewer's IP and UA to an
// attacker-controlled server.
(reporter, $, _ast) => {
reporter.name = "no-external-references";
const externalPattern = /^(https?:|ftp:|\/\/)/i;
$("[href], [xlink\\:href]").each((_i, el) => {
if (!el.attribs) { return; }
const href =
el.attribs["href"] ?? el.attribs["xlink:href"];
if (href && externalPattern.test(href)) {
reporter.error(
`Found external reference '${href}' on <${el.name}>. ` +
"External URL references in SVGs cause the renderer " +
"to make an outbound request, leaking viewer IP/UA."
);
}
});
},
],
},

// These four files are intentional interactive SVG examples that
// demonstrate matplotlib's SVG interactivity features. They contain
// embedded ECMAScript by design and are exempted from the <script> rule.
ignore: [
"doc/_static/svg_histogram.svg",
"doc/_static/svg_tooltip.svg",
"galleries/examples/user_interfaces/images/svg_histogram.svg",
"galleries/examples/user_interfaces/images/svg_tooltip.svg",
],
};

export default config;
38 changes: 38 additions & 0 deletions doc/devel/coding_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,44 @@ C/C++ extensions
implement new features only if the required changes cannot be made elsewhere
in the codebase. In particular, avoid making style fixes to it.

.. _clang-tidy:

Static analysis with clang-tidy
-------------------------------

Matplotlib's C/C++ sources in :file:`src/` are checked with
`clang-tidy <https://clang.llvm.org/extra/clang-tidy/>`__ in CI (see
:file:`.github/workflows/linting.yml`). The check
configuration lives in :file:`.clang-tidy`.

The logic lives in :file:`tools/run_clang_tidy.py`. It requires
``clang-tidy`` on ``PATH`` and ``meson`` and ``pybind11`` installed::

pip install meson pybind11 setuptools-scm

On macOS, ``clang-tidy`` is not on ``PATH`` after a Homebrew install::

brew install llvm
export PATH=$(brew --prefix llvm)/bin:$PATH

The script uses a dedicated ``build/clang-tidy/`` directory (created
automatically on first run) and delegates to meson's built-in
``clang-tidy`` target. To run locally:

.. code-block:: bash

python tools/run_clang_tidy.py


To suppress false-positives use narrow checks and a comment:

.. code-block:: c++

*indices++ = value; // NOLINT(clang-analyzer-security.ArrayBound): loop
// iterates exactly N times; the analyzer cannot prove this from the macro.



.. _keyword-argument-processing:

Keyword argument processing
Expand Down
Loading
Loading