Blogical

AWS/Salesforceを中心に様々な情報を配信していきます(/・ω・)/

Pythonコードを静的解析してみた

こんにちは、ロジカル・アーツの井川です。

Pythonで書いたコードのレビューを受けました。
そのときに静的解析してね、という話がありましたが、初めて聞いた言葉で何のことが分かりませんでした。。。
今回はそのときに調べた内容を書きたいと思います。

静的解析とは

プログラムを実行せずにバグを見つけることです。
バグはソースコードの構文に問題が無いか、コーディング規約に違反していないかなどの観点でコードを確認すると見えてきますが、 これらをソフトやツールで確認することを静的解析といいます。

ちなみにプログラムを実行して不具合を見つける方法は動的解析と呼ぶそうです。
ただし、バグにはプログラムを実行しても表面化しないものがあるので、動的解析をする予定でも静的解析はした方が良いですね。

Pythonの静的解析ツール

Pythonには以下の静的解析ツールがありました。

  • pycodestyle (旧称 pep8)
  • pyflakes
  • flake8
  • bandit

簡単にですがそれぞれご紹介したいと思います。

pycodestyle (旧称 pep8)

Pythonコードがコーディング規約(PEP8)に準じているかを確認します。
もともとは pep8 という名称のライブラリでしたが、名称変更があり現在は pycodestyle という名称になっています。
pep8 と pycodestyle は別々のライブラリとして利用できる状態になっていますが、基本的には pycodestyle を使うで良いです。

pyflakes

コードに論理的なエラーが無いかを確認します。
pycodestyle のように構文がコーディング規約に沿っているかどうかには触れません。

flake8

pycodestyle と pyflakes のチェックを同時に行います。
pythonコードの静的解析ツールを選択するなら flake8 が良いと思います。
実際に試してみたのでサンプルを次にご紹介したいと思います。

bandit

コードの脆弱性やモジュールの問題などを指摘しセキュリティ問題を見つけることができます。
pyflakes 同様にコーディング規約に準じているかは確認しません。
こちらも実際に試してみたのでサンプルを次にご紹介したいと思います。

flake8を使った確認

以下は flake8 でチェックしても問題のないコードです。

hello.py

hello = "hello"

print(hello)

以下のように形を崩してみました。

hello.py

import sys

hello =    "hello"

print(hello )

静的解析を実行したところ、以下のような指摘がありました。

>flake8 hello.py
hello.py:1:1: F401 'sys' imported but unused
hello.py:3:8: E222 multiple spaces after operator
hello.py:5:12: E202 whitespace before ')'

>

使っていないモジュールや不要なスペースが指摘されていますね!

ここではご紹介していませんが、autopep8 というライブラリを使うと上記の指摘を自動で修正するところまでツールが対応してくれます。

banditを使った確認

続いてbanditです。
以下の input.yaml と test_bandit.py を使ってbanditの動きを確認してみます。

input.yaml

x: "load yaml success"

test_bandit.py

import yaml

with open('input.yaml') as f:
    obj = yaml.load(f)
    print(obj['x'])

test_bandit.py が input.yaml を読み取ってxの値を出力するコードになります。
yaml.load()は読み取ったデータに埋め込まれているコードを実行してしまう脆弱性があり、代わりにyaml.safe_load()を使うことが推奨されています。
yaml.load()でも実行は問題なく可能で、結果は以下のようになります。

load yaml success

flake8によるチェックでのエラー報告はありませんでした。

>flake8 test_bandit.py

>

banditによるチェックを行ってみましょう。

>bandit -r test_bandit.py
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.8.0
[node_visitor]  INFO    Unable to find qualified name for module: test_bandit.py
Run started:2020-02-28 05:33:25.910750

Test results:
>> Issue: [B506:yaml_load] Use of unsafe yaml load. Allows instantiation of arbitrary objects. Consider yaml.safe_load().
   Severity: Medium   Confidence: High
   Location: test_bandit.py:4
   More Info: https://bandit.readthedocs.io/en/latest/plugins/b506_yaml_load.html
3       with open('input.yaml') as f:
4           obj = yaml.load(f)
5           print(obj['x'])

--------------------------------------------------

Code scanned:
        Total lines of code: 4
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0.0
                Low: 0.0
                Medium: 1.0
                High: 0.0
        Total issues (by confidence):
                Undefined: 0.0
                Low: 0.0
                Medium: 0.0
                High: 1.0
Files skipped (0):

>

Test results:yaml.load()が指摘されていますね。

Issue: [B506:yaml_load] Use of unsafe yaml load. Allows instantiation of arbitrary objects. Consider yaml.safe_load().

yaml.safe_load()に変更して再チェックしてみます。

>bandit -r test_bandit.py
[main]  INFO    profile include tests: None
[main]  INFO    profile exclude tests: None
[main]  INFO    cli include tests: None
[main]  INFO    cli exclude tests: None
[main]  INFO    running on Python 3.8.0
[node_visitor]  INFO    Unable to find qualified name for module: test_bandit.py
Run started:2020-02-28 06:24:50.742448

Test results:
        No issues identified.

Code scanned:
        Total lines of code: 4
        Total lines skipped (#nosec): 0

Run metrics:
        Total issues (by severity):
                Undefined: 0.0
                Low: 0.0
                Medium: 0.0
                High: 0.0
        Total issues (by confidence):
                Undefined: 0.0
                Low: 0.0
                Medium: 0.0
                High: 0.0
Files skipped (0):

エラーが無くなりました!

まとめ

複数人でコードを作成すると人によって構文の違いが出てくるかもしれません。
それを防ぐためのコーディングルールですが、意識していても対応が漏れてしまうことはあると思います。
人の目では品質を担保しにくいところは、今回の静的解析のように積極的にツールを活用して対応していきたいですね!

この記事がどなたかのお役に立てば幸いです。

参考サイト

コーディング規約とPEP8 — Pythonオンライン学習サービス PyQ(パイキュー)ドキュメント

Pythonのスタイルガイドとそれを守るための各種Lint・解析ツール5種まとめ! - Sider Blog

Pythonコードの安全を保つSAST(静的解析)ツール ~Bandit, Pyt~ - 好奇心の足跡

PyYAML yaml.load(input) Deprecation · yaml/pyyaml Wiki · GitHub