【Psalm/phar】Notice: Uninitialized string offset: 0 in ... で Cannot locate エラー


PsalmCannot locate エラー

composer require psalm/phar でインストールした pharpsalm を実行すると、Notice: Uninitialized string offset: 0 ... command_functions.php ...Cannot locate エラーが出るようになりました。

以前までは動いていたと思ったのですが、気付いたらエラーが出ていました。特に Psalm の issues にも上がっておらず、ググりまくっても解決策が見つからなかったので、自分のググラビリティ/備忘録として。

TL; DR

psalm のコマンド引数で、空のオプションを渡していませんか。

空の引数を渡している
$ use_alter=''
$ ./vendor/bin/psalm \
  --config="$(pwd)/tests/conf/psalm.xml" \
  --root="$(pwd)" \
  "$use_alter"   # <--- !! w/quote
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 178
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 184
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 202
Cannot locate 

クォートで変数を囲うと、変数の中身が空の場合でもオプションとして渡してしまうので、クォートせずに指定するか if 文で分岐します。

$ use_alter=''
$ ./vendor/bin/psalm \
  --config="$(pwd)/tests/conf/psalm.xml" \
  --root="$(pwd)" \
  $use_alter   # <--- !! w/out quote
Scanning files...
Analyzing files...

░░░
------------------------------
No errors found!
------------------------------

Checks took 5.92 seconds and used 171.358MB of memory
Psalm was able to infer types for 100% of the codebase

TS; DR

各種テストを実行するシェル・スクリプトで、psalm の自動補完/修正機能オプション --alter を条件によって付けたり付けなかったりしたかったので、変数にオプションを入れて指定していました。

run-tests.sh
#!/bin/bash
...
# psalm の追加オプション指定
use_alter=''
isFlagSet 'psalter' && {
  use_alter='--alter'
}

# psalm の実行
./vendor/bin/psalm \
  --config=$path_file_conf_psalm \
  --root=$path_dir_parent \
  $use_alter || {
    echo >&2 'Psalm check failed.'
  }
...

あるときから、シェル・スクリプトの静的解析ツールの「ShellCheck」を使うようになりました。

この shellcheck の解析内容には「変数をダブル・クォーテーションで囲う」(SC2086)と言うルールがあります。これはシェルの場合、変数内にスペースが含まれていた場合、クォーテーションで囲わないと複数の引数として展開してしまうためです。

$ hoge='fuga piyo'
$ ./myCommand $hoge    #<- ./myCommand 'fuga' 'piyo' と同等
$ ./myCommand "$hoge"  #<- ./myCommand 'fuga piyo' と同等

このことは、「スペース入りディレクトリのあるパス」を扱う時など意図しない(見つけづらい)エラーを起こします。

「まぁ、そうだよな」と、盲目的に修正して(変数をクォートで囲って)いたのですが、逆の「引数の値が空でも『空の値』として渡してしまう」と言うパターンを失念しておりました。このことが原因でエラーが発生していたのですが、気づくのに時間がかなりかかってしまいました。

$user_alterは空
$ # NG
$ ./vendor/bin/psalm \
  --config="${path_file_conf_psalm}" \
  --root="${path_dir_app_root}" \
  "$use_alter"
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 178
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 184
Notice: Uninitialized string offset: 0 in phar:///workspaces/SampleApp/vendor/psalm/phar/psalm.phar/src/command_functions.php on line 202
Cannot locate

$ # OK
$ ./vendor/bin/psalm \
  --config="${path_file_conf_psalm}" \
  --root="${path_dir_app_root}" \
  $use_alter
Scanning files...
Analyzing files...

░░░
------------------------------
No errors found!
------------------------------

Checks took 5.92 seconds and used 171.358MB of memory
Psalm was able to infer types for 100% of the codebase
バージョン情報
$ ./vendor/bin/psalm --version
Psalm 4.3.1@2feba22a005a18bf31d4c7b9bdb9252c73897476

$ php -v
PHP 7.3.25 (cli) (built: Dec 11 2020 09:22:30) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.25, Copyright (c) 1998-2018 Zend Technologies
    with Xdebug v3.0.1, Copyright (c) 2002-2020, by Derick Rethans