pythonの全角スペースによるエラーメッセージを明示的なものに変更してみた

/タイトル:pythonの全角スペースによるエラーメッセージを明示的なものに変更してみた/

はじめに

この記事は, 東京大学工学部電子情報工学科・電気電子工学科(通称EEIC)の3年後期実験の1つである「大規模ソフトウェアを手探る」のレポートとして書かれたものです.当実験は任意のOSSを1つ選んでその改造を試みるというものであり, 私たちの班「cuatro」はpythonを改造するOSSに選びました.

概要

私達の班「cuatro」では、以下の3点の改変を行いました.

2つめと3つめの記事については班のメンバーが書いておりますので, 興味のある方は合わせてご覧ください.なお改変したコードについては、本実験のgitlabリポジトリにあります.

環境

Ubuntu 18.04 LTS
python 3.11.0 (cpython)

変更したこと

全角空白文字" "を含むようなコードを書くと以下のようなエラーメッセージが表示されます.わかりにくいですが,8行目の部分では全角スペースのみを打ち込んでいます.

$ ./bin/python3
Python 3.11.0a1+ (heads/main-dirty:be21706f37, Oct 14 2021, 14:27:35) [GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>  
  File "<stdin>", line 1
     
    ^
SyntaxError: invalid non-printable character U+3000

全角スペースのようなnon-printableな文字の入力があった際には同様のエラーメッセージが出力されます.ここで"U+3000"とは全角スペースのUnicodeが3000であることに対応しています.

このエラーメッセージを以下のように変更しました.全角スペースが含まれていることをより明示的に示したエラーメッセージとなっています.

$ ./bin/python3
Python 3.11.0a1+ (heads/main-dirty:be21706f37, Nov  7 2021, 05:13:17) [GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>  
  File "<stdin>", line 1
     
    ^
SyntaxError: there are one or more  Full-width spaces U+3000

ソースのクローンとビルド

c言語によるpythonソースコードgithub上に公開されていますのでそれをcloneしました.つまりターミナルで以下のコマンドを打ちました.

$ git clone https://github.com/python/cpython.git

ビルドはconfigureのあるディレクトリで以下のようにしました.CFLAGS="-O0 -g"の部分はデバッグ用のオプションなのでデバッガを使用しないとき(完成版をビルドする場合など)は省略します.

$ CFLAGS="-O0 -g" ./configure --prefix="インストール先へのパス"
$ make clean
$ make
$ make install

ソースコードの改変

エラーメッセージをgrepコマンドで検索したところ,/Parser/tokenizer.c:1316に出力するエラーメッセージを生成していると思われる箇所がありました.

$ grep -r -n -I "invalid non-printable character"
Parser/tokenizer.c:1316:            syntaxerror(tok, "invalid non-printable character U+%s", hex);
Lib/test/test_fstring.py:661:        self.assertAllRaise(SyntaxError, r"invalid non-printable character U\+00A0",

周辺のコード(下)をみるとわかるのですが全角記号を入力したときのエラー出力は"if (Py_UNICODE_ISPRINTABLE(ch))"によって表示可能な文字か非表示文字(制御文字)かに場合分けされていて,今回変更したいのは全角スペースのエラーメッセージ"SyntaxError: invalid non-printable character U+3000"の形式から非表示文字のエラーメッセージの方だとわかります.

    ...
        // PyUnicode_FromFormatV() does not support %X
        char hex[9];
        (void)PyOS_snprintf(hex, sizeof(hex), "%04X", ch);
        if (Py_UNICODE_ISPRINTABLE(ch)) {
            syntaxerror(tok, "invalid character '%c' (U+%s)", ch, hex);
        }
        else {
            syntaxerror(tok, "invalid non-printable character U+%s", hex);
        }
        return 0;
    ...

またこのとき,文字のUnicodeを変数hexで持っているのでそれによって更に細かく処理を分けることができます.つまり全角スペースのとき限定のエラーメッセージを出力することが出来るはずです.変更は以下のように行いました.

        // PyUnicode_FromFormatV() does not support %X
        char hex[9];
        (void)PyOS_snprintf(hex, sizeof(hex), "%04X", ch);
        if (Py_UNICODE_ISPRINTABLE(ch)) {
            syntaxerror(tok, "invalid character '%c' (U+%s)", ch, hex);
        }
        else {
#if 1   //changed part
            int fwspace_flag = 1;
            char hex_compare[9] = "3000";
            for(int i = 0; hex[i] != '\0'; i++){
                if(hex[i] != hex_compare[i]){
                    fwspace_flag = 0;
                    break;
                }
            }
            if (fwspace_flag){
                syntaxerror(tok, "there are one or more  Full-width spaces U+%s", hex);
            }else {
                syntaxerror(tok, "invalid non-printable character U+%s", hex);
            }
#endif
#if 0   //original
            syntaxerror(tok, "invalid non-printable character U+%s", hex);
#endif
        }
        return 0;

先に述べたようにhexはinvalidな文字のUnicodeを文字列として持っているようなので,それと全角スペースの"3000"を比較することによって全角スペース特有のエラーメッセージを出力させました.

感想

今回はpythonの出力に関する部分の改変でしたので比較的簡単に実装することが出来たのでしょう.加えてここではエラーメッセージという強力なキーワードがありましたのでgrep文でなんとかなりそうだと取り掛かりの段階で感じていました.

当初は一旦grep文を封印してデバッガ(gdb)による追跡によって該当箇所を見つけ出そうともしたのですが, python全体の処理構造を理解していなったために断念してしまいました.今一度冷静になってみるとinvalidな文字を検出しうるのはトークン列取得のタイミングであろうことは推測できそうで幾分か悔しい思いです.

参考文献

大規模ソフトウェアを手探る(https://doss.eidos.ic.i.u-tokyo.ac.jp) Python Developer’s Guide(https://devguide.python.org)

おまけ(先輩方の記事のまとめ)

別記事にまとめました.

2015年度

2016年度

2017年度, 2018年度, 2019年度

  • なし

2020年度