Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PCOVから学ぶコードカバレッジ #phpcon_odawara

PCOVから学ぶコードカバレッジ #phpcon_odawara

PHPカンファレンス小田原2026 での登壇資料です
https://fortee.jp/phpconodawara-2026/proposal/aae4efc9-59c8-4edc-a57e-571886c24fd6

Avatar for hideki kinjyo

hideki kinjyo PRO

April 09, 2026

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. ここまでのまとめ 11 実行されたプログラム 1. タイトル 2. 挨拶 3. トークの紹介(forteeから) 4.

    ネットワーキング 5. 豆知識 ロジックとスライドを 対応付けてみると?
  2. ロジックと「実装」ソース 13 実行されたプログラム 1. タイトル 2. 挨拶 3. トークの紹介(forteeから) 4.

    ネットワーキング 5. 豆知識 #1 #2 #3 #4 #5 #6 #7 #8 カバーされたスライド
  3. 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏

    : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 楽しみすぎて有給取って昨日から小田原に居ます • 最近はPodcastをやっています • ハッシュタグ: #readlinefm 
  4. 測定対象コード 関数の定義ファイル。 このうち、 「どこが実行されたか」 を測定していくには? 32 <?php function div( float

    $left, float $right ): ?float { if ($right == 0) { return null; } return $left / $right; } 001 002 003 004 005 006 007 008 009 010 011 012 013
  5. 測定実行コード pcovの関数を利用して カバレッジを測定する コード 33 <?php require 'functions.php'; \pcov\start(); $a

    = div(2.5, 3.2); \pcov\stop(); $coverage = \pcov\collect(); var_dump($coverage); 001 002 003 004 005 006 007 008 009 010 011 012
  6. 測定実行コード 34 <?php require 'functions.php'; \pcov\start(); $a = div(2.5, 3.2);

    \pcov\stop(); $coverage = \pcov\collect(); var_dump($coverage); 001 002 003 004 005 006 007 008 009 010 011 012 ここから測定開始
  7. 測定実行コード 35 <?php require 'functions.php'; \pcov\start(); $a = div(2.5, 3.2);

    \pcov\stop(); $coverage = \pcov\collect(); var_dump($coverage); 001 002 003 004 005 006 007 008 009 010 011 012 ここで測定終了
  8. 測定実行コード 36 <?php require 'functions.php'; \pcov\start(); $a = div(2.5, 3.2);

    \pcov\stop(); $coverage = \pcov\collect(); var_dump($coverage); 001 002 003 004 005 006 007 008 009 010 011 012 ここが測定対象
  9. 測定実行コード 37 <?php require 'functions.php'; \pcov\start(); $a = div(2.5, 3.2);

    \pcov\stop(); $coverage = \pcov\collect(); var_dump($coverage); 001 002 003 004 005 006 007 008 009 010 011 012 測定結果の生成・取得
  10. 測定結果 38 ["/opt/lib/functions.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=>

    int(-1) [11]=> int(1) } $coverage = \pcov\collect(); var_dump($coverage); \pcov\collect()の 実行結果をdumpしたもの
  11. 測定結果 39 ["/opt/lib/functions.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=>

    int(-1) [11]=> int(1) } 対象ファイル 対象行 測定結果
  12. 測定結果 40 ["/opt/lib/functions.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=>

    int(-1) [11]=> int(1) } 1回でもその行を通った 1回もその行を通らなかった
  13. 測定結果 41 [13]=> int(-1) [7]=> int(1) [8]=> int(-1) [11]=> int(1)

    <?php function div( float $left, float $right ): ?float { if ($right == 0) { return null; } return $left / $right; } 001 002 003 004 005 006 007 008 009 010 011 012 013 require 'functions.php'; \pcov\start(); $a = div(2.5, 3.2); \pcov\stop();
  14. 測定結果(余談) 13行目を「未実行」って どういうことでしょうね 42 [13]=> int(-1) [7]=> int(1) [8]=> int(-1)

    [11]=> int(1) <?php function div( float $left, float $right ): ?float { if ($right == 0) { return null; } return $left / $right; } 001 002 003 004 005 006 007 008 009 010 011 012 013
  15. 測定結果(余談) requireの実行を start/stopに 含めればOK 43 [13]=> int(-1) [7]=> int(1) [8]=>

    int(-1) [11]=> int(1) require 'functions.php'; \pcov\start(); div(2.5, 3.2); \pcov\stop(); [13]=> int(1) [7]=> int(1) [8]=> int(-1) [11]=> int(1) \pcov\start(); require 'functions.php'; div(2.5, 3.2); \pcov\stop();
  16. pcov拡張が提供している関数 \pcov\start, \pcov\stop, \pcov\collect Ҏ֎ʹ΋ɾɾ • \pcov\enabled : pcov͕༗ޮʹͳ͍ͬͯΔ͔Λฦ͢ •

    \pcov\clear : ֨ೲ͞Ε͍ͯΔ৘ใΛΫϦΞ • \pcov\waiting : collect͞Ε͍ͯͳ͍ϑΝΠϧϦετΛฦ͢ • \pcov\memory : ֬อࡁΈͰ࢖ΘΕ͍ͯͳ͍ϝϞϦ༰ྔΛฦ͢ 45
  17. りある・おぺこーど <?php function f($a, $b) { if ($b == 0)

    { return null; } return $a / $b; } $a = f(2, 3); $b = f(2, 0); $_main: L10 00 INIT_FCALL 2 144 string("f") L10 01 SEND_VAL int(2) 1 L10 02 SEND_VAL int(3) 2 L10 03 V2 = DO_FCALL L10 04 ASSIGN CV0($a) V2 L11 05 INIT_FCALL 2 144 string("f") L11 06 SEND_VAL int(2) 1 L11 07 SEND_VAL int(0) 2 L11 08 V4 = DO_FCALL L11 09 ASSIGN CV1($b) V4 L12 10 RETURN int(1) f: L02 00 CV0($a) = RECV 1 L02 01 CV1($b) = RECV 2 L04 02 T2 = IS_EQUAL CV1($b) int(0) 56
  18. PHPスクリプトが実行されるまで 57 本当のCPU 仮想マシン ZendVM 機械語 $_main: L10 00 INIT_FCALL

    2 144 string("f") L10 01 SEND_VAL int(2) 1 L10 02 SEND_VAL int(3) 2 L10 03 V2 = DO_FCALL L10 04 ASSIGN CV0($a) V2 L11 05 INIT_FCALL 2 144 string("f") L11 06 SEND_VAL int(2) 1 L11 07 SEND_VAL int(0) 2 L11 08 V4 = DO_FCALL L11 09 ASSIGN CV1($b) V4 L12 10 RETURN int(1) f: L02 00 CV0($a) = RECV 1 L02 01 CV1($b) = RECV 2 L04 02 T2 = IS_EQUAL CV1($b) int(0) L04 03 JMPZ T2 05 L05 04 RETURN null L08 05 T3 = DIV CV0($a) CV1($b) L08 06 RETURN T3 L09 07 RETURN null
  19. ZendVMのメインループ 63 $_main: L10 00 INIT_FCALL 2 144 string("f") L10

    01 SEND_VAL int(2) 1 L10 02 SEND_VAL int(3) 2 L10 03 V2 = DO_FCALL L10 04 ASSIGN CV0($a) V2 L11 05 INIT_FCALL 2 144 string("f") ɾ ɾ ɾ 仮想マシン ZendVM 丸っと受け取る op_array
  20. ZendVMのメインループ 64 L10 00 INIT_FCALL 2 144 string("f") 仮想マシン ZendVM

    1ステップずつ実行 L10 01 SEND_VAL int(2) 1 L10 02 SEND_VAL int(3) 2 L10 03 V2 = DO_FCALL ɾ ɾ ɾ opline
  21. ZendVMのメインループ 65 L10 00 INIT_FCALL 2 144 string("f") ɾ ɾ

    ɾ L10 03 V2 = DO_FCALL 仮想マシン ZendVM f: L02 00 CV0($a) = RECV 1 L02 01 CV1($b) = RECV 2 L04 02 T2 = IS_EQUAL CV1($b) int(0) ɾ ɾ ɾ 別のフレームの実行
  22. zend_execute_ex (っぽいもの) zend_execute_exは、1つのフレームに相当する単位で 実行するデータを受け取り、1ステップずつ処理していく 67 function zend_execute_ex($࣮ߦ͢Δσʔλ) { $op_array =

    $࣮ߦ͢Δσʔλ->ίϨΫγϣϯ; foreach ($op_array as $opline) { εςοϓ͝ͱͷॲཧ($opline); } } PHPer向けの ざっくりイメージ ※ このあたり、拡張の利用有無で挙動が変わることがある
  23. pcovのオーバーライド PHP_MINIT_FUNCTION(pcov) { /** লུ */ if (php_pcov_api_enabled()) { zend_execute_ex_function

    = zend_execute_ex; zend_execute_ex = php_pcov_execute_ex; } ZVAL_LONG(&php_pcov_uncovered, PHP_PCOV_UNCOVERED); ZVAL_LONG(&php_pcov_covered, PHP_PCOV_COVERED); /** লུ */ return SUCCESS; }  pcov.c
  24. pcovのオーバーライド PHP_MINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     PHP_MINIT_FUNCTION(pcov) 「モジュールの初期化」時に 処理をぶら下げるフック のおまじない(マクロ) pcov.c
  25. pcovのオーバーライド PHP_MINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     if (php_pcov_api_enabled()) { モジュールを読み込んでいても、 iniファイル等でdisableされている pcov拡張が有効かをチェックして pcov.c
  26. pcovのオーバーライド PHP_MINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     zend_execute_ex_function = zend_execute_ex; 終了時のフックで 復帰に使う 元の処理を退避させて pcov.c
  27. pcovのオーバーライド PHP_MINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     zend_execute_ex = php_pcov_execute_ex; pcovの独自処理に 差し替える pcov.c
  28. 余談 PHP_MINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     ZVAL_LONG(&php_pcov_uncovered, PHP_PCOV_UNCOVERED); 「実行されていない行」 を示す定数、「-1」 ZVAL_LONG(&php_pcov_covered, PHP_PCOV_COVERED); 「実行されている行」 を示す定数、「1」 pcov.c
  29. zend_execute_ex / _zend_execute_data zend_execute_exは_zend_execute_dataを受け取る 78 ZEND_API extern void (*zend_execute_ex) (zend_execute_data

    *execute_data); struct _zend_execute_data { const zend_op *opline; ɾɾɾ zval *return_value; zend_function *func; }; zend_execute.h zend_execute.h
  30. zend_execute_ex / _zend_execute_data zend_execute_dataはzend_functionをもち、op_arrayを含む 1つのop_arrayが1つのフレームに相当(≒関数の中身のopcode) 79 struct _zend_execute_data { const

    zend_op *opline; ɾɾɾ zend_function *func; }; union _zend_function { uint8_t type; uint32_t quick_arg_flags; struct {...} common; zend_op_array op_array; ɾɾɾ }; zend_types.h zend_types.h
  31. php_pcov_execute_ex void php_pcov_execute_ex(zend_execute_data *execute_data) { int zrc = 0; while

    (1) { zrc = php_pcov_trace(execute_data); if (zrc != SUCCESS) { if (zrc < SUCCESS) { return; } execute_data = EG(current_execute_data); } } }  pcov.c
  32. php_pcov_execute_ex (っぽいもの) function pcovExecuteEx($࣮ߦ͢Δσʔλ) { while (true) { $returnCode =

    pcovTrace($࣮ߦ͢Δσʔλ); if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } } } 
  33. php_pcov_execute_ex (っぽいもの) function pcovExecuteEx($࣮ߦ͢Δσʔλ) { while (true) { $returnCode =

    pcovTrace($࣮ߦ͢Δσʔλ); if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } } }  while (true) { 全部終わるまで反復
  34. php_pcov_execute_ex (っぽいもの) function pcovExecuteEx($࣮ߦ͢Δσʔλ) { while (true) { $returnCode =

    pcovTrace($࣮ߦ͢Δσʔλ); if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } } }  pcovTrace($࣮ߦ͢Δσʔλ); opcodeを1ステップずつ実行 対応するPHPスクリプトの行を記録 実行結果状態を返す
  35. php_pcov_execute_ex (っぽいもの) function pcovExecuteEx($࣮ߦ͢Δσʔλ) { while (true) { $returnCode =

    pcovTrace($࣮ߦ͢Δσʔλ); if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } } }  if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } 状態に応じて 離脱したり次に行ったり
  36. php_pcov_trace static zend_always_inline int php_pcov_trace(zend_execute_data *execute_data) { if (PCG(enabled)) {

    if (php_pcov_wants(EX(func)->op_array.filename) && !php_pcov_ignored_opcode(EX(opline)->opcode) && !php_pcov_has(EX(func)->op_array.filename, EX(opline)->lineno)) { php_coverage_t *coverage = php_pcov_create(execute_data); if (!PCG(start)) { PCG(start) = coverage; } else { *(PCG(next)) = coverage; } PCG(next) = &coverage->next; } } return zend_vm_call_opcode_handler(execute_data); }  pcov.c
  37. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); } 
  38. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); }  if (pcovଌఆελʔτࡁΈ()) { pcov\start()するとフラグが立つ / pcov\stop()するとフラグが折れる 「スタート済み」フラグが 立っていれば処理に入る
  39. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); }  if (/** ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { (この後に詳しく)
  40. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); }  pcovCreate($࣮ߦ͢Δσʔλ); 「実行済み」行のデータ作成 ଌఆσʔλ௥Ճ($ଌఆσʔλ); 「実行済み」行のデータのリスト更新
  41. php_pcov_trace (っぽいもの) - 測定データの追加 if (ଌఆର৅ϑΝΠϧ͔($࣮ߦ͢Δσʔλ->func->ϑΝΠϧ໊) && !ແࢹ͢ΔΦϖίʔυ͔($࣮ߦ͢Δσʔλ->ݱࡏͷopline) && !طʹଌఆࡁΈ͔(

    $࣮ߦ͢Δσʔλ->func->ϑΝΠϧ໊, $࣮ߦ͢Δσʔλ->ߦ൪߸ )) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } 
  42. php_pcov_create static zend_always_inline php_coverage_t* php_pcov_create(zend_execute_data *execute_data) { /* {{{ */

    php_coverage_t *create = (php_coverage_t*) zend_arena_alloc(&PCG(mem), sizeof(php_coverage_t)); create->file = php_pcov_interned_string(EX(func)- >op_array.filename); create->line = EX(opline)->lineno; create->next = NULL; zend_hash_add_empty_element(&PCG(waiting), create->file); return create; } /* }}} */  pcov.c
  43. php_pcov_create - 要点 // php_pcov_create(zend_execute_data *execute_data) { php_coverage_t *create =

    /** ...* / create->file = php_pcov_interned_string( EX(func)->op_array.filename ); create->line = EX(opline)->lineno; create->next = NULL; return create; }  ファイル名、 行番号を持つ 構造体
  44. pcovのオーバーライド PHP_RINIT_FUNCTION(pcov) { /** লུ */ if (!zend_compile_file_function) { zend_compile_file_function

    = zend_compile_file; zend_compile_file = php_pcov_compile_file; } /** লུ */ return SUCCESS; }  pcov.c
  45. pcovのオーバーライド PHP_RINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     pcov.c zend_compile_file = php_pcov_compile_file; phpスクリプト→opcode変換処理の オーバーライド
  46. php_pcov_compile_file (っぽいもの) function pcovCompileFile(FileHandler $file, int $type) { $op_array =

    zendCompilerඪ४ͷॲཧ($file, $type); if (!$op_array || !$op_array->filename || !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->finename) || طʹ௥ՃࡁΈ͔($op_array->finename) ) { return $op_array; } ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); /** GCपΓͷॲཧ͕ίίʹೖ͍ͬͯΔ */ return $op_array; } 
  47. php_pcov_compile_file (っぽいもの) function pcovCompileFile(FileHandler $file, int $type) { $op_array =

    zendCompilerඪ४ͷॲཧ($file, $type); if (!$op_array || !$op_array->filename || !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->filename) || طʹ௥ՃࡁΈ͔($op_array->finename) ) { return $op_array; } ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); /** GCपΓͷॲཧ͕ίίʹೖ͍ͬͯΔ */ return $op_array; }  $op_array = zendCompilerඪ४ͷॲཧ($file, $type); 元の処理を実行して、 この関数の戻り値を取得する このデータ(op_array)が そのまま関数全体の戻り値になる
  48. php_pcov_compile_file (っぽいもの) function pcovCompileFile(FileHandler $file, int $type) { $op_array =

    zendCompilerඪ४ͷॲཧ($file, $type); if (!$op_array || !$op_array->filename || !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->finename) || طʹ௥ՃࡁΈ͔($op_array->finename) ) { return $op_array; } ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); /** GCपΓͷॲཧ͕ίίʹೖ͍ͬͯΔ */ return $op_array; }  !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->finename) 測定/除外対象の 設定内容との照合
  49. php_pcov_compile_file (っぽいもの) function pcovCompileFile(FileHandler $file, int $type) { $op_array =

    zendCompilerඪ४ͷॲཧ($file, $type); if (!$op_array || !$op_array->filename || !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->finename) || طʹ௥ՃࡁΈ͔($op_array->finename) ) { return $op_array; } ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); /** GCपΓͷॲཧ͕ίίʹೖ͍ͬͯΔ */ return $op_array; }  طʹ௥ՃࡁΈ͔($op_array->finename) もうメモしてあるファイルだったらスキップ
  50. php_pcov_compile_file (っぽいもの) function pcovCompileFile(FileHandler $file, int $type) { $op_array =

    zendCompilerඪ४ͷॲཧ($file, $type); if (!$op_array || !$op_array->filename || !ଌఆର৅ʹؚΊ͍͍͔ͯ($op_array->finename) || طʹ௥ՃࡁΈ͔($op_array->finename) ) { return $op_array; } ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); /** GCपΓͷॲཧ͕ίίʹೖ͍ͬͯΔ */ return $op_array; }  ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); ココが肝!!
  51. \pcov\collect() のおさらい • 「実行された行」「されなかった行」のデータを返す関数 • この関数が呼ばれると、集めたデータの統合と整理を行う • コンパイル時に集めた 「対象ファイル」の一覧 •

    コード実行時に記録された 「実行された行」の一覧 111 // var_dump(\pcov\collect()); ["/opt/hoge.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=> int(-1) [11]=> int(1) }
  52. 測定対象行の〜 113 op_array 制御フローグラフ 結果データの初期化 コンパイル ZEND_APIの機能 測定対象行の抽出 カバレッジデータとの統合 PHPスクリプト

    <?php function add(int $a, int $b): int { return $a + $b; } \pcov\start(); $x = add(1, 2); if (false) { return; var_dump('ίίʹ͸དྷͳ͍Α'); } else { var_dump('ίίʹ͸དྷΔͬͯ͞'); } echo $x; \pcov\stop(); // ݁ՌΛऔಘ $coverage = \pcov\collect(); 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018
  53. 測定対象行の〜 制御フローグラフ 結果データの初期化 ZEND_APIの機能 測定対象行の抽出 カバレッジデータとの統合 PHPスクリプト op_array コンパイル [000]

    L6 ZEND_INIT_FCALL op1=JMP->[002] op2 [001] L6 ZEND_DO_ICALL [002] L7 ZEND_INIT_FCALL op1=JMP->[006] op2 [003] L7 ZEND_SEND_VAL op1=CONST(1) op2=J [004] L7 ZEND_SEND_VAL op1=CONST(2) op2=J [005] L7 ZEND_DO_FCALL [006] L7 ZEND_ASSIGN op1=CV($x) op2=VAR [007] L8 ZEND_JMPZ op1=CONST(false) o [008] L9 ZEND_RETURN op1=CONST(null) [009] L10 ZEND_INIT_FCALL op1=JMP->[012] op2 [010] L10 ZEND_SEND_VAL op1=CONST("ίίʹ͸དྷ [011] L10 ZEND_DO_ICALL [012] L8 ZEND_JMP op1=JMP->[016] [013] L12 ZEND_INIT_FCALL op1=JMP->[016] op2 [014] L12 ZEND_SEND_VAL op1=CONST("ίίʹ͸དྷ [015] L12 ZEND_DO_ICALL [016] L14 ZEND_ECHO op1=CV($x) [017] L15 ZEND_INIT_FCALL op1=JMP->[019] op2 [018] L15 ZEND_DO_ICALL [019] L18 ZEND_INIT_FCALL op1=JMP->[021] op2 [020] L18 ZEND_DO_ICALL [021] L18 ZEND_ASSIGN op1=CV($coverage)
  54. 実行に関わりが無い行が消える op_arrayの時点で、 「実行可能なコードがない」行は消えている 115 [013] L12 ZEND_INIT_FCALL [014] L12 ZEND_SEND_VAL

    [015] L12 ZEND_DO_ICALL [016] L14 ZEND_ECHO [017] L15 ZEND_INIT_FCALL 011 012 013 014 015 } else { var_dump(' } echo $x; \pcov\stop(); L13は処理を持たない
  55. 測定対象行の〜 116 op_array 結果データの初期化 コンパイル 測定対象行の抽出 カバレッジデータとの統合 PHPスクリプト 制御フローグラフ ZEND_APIの機能

    Block 0 (start=0 len=8) => REACHABLE Block 1 (start=8 len=1) => REACHABLE Block 2 (start=9 len=4) => UNREACHABLE Block 3 (start=13 len=3) => REACHABLE Block 4 (start=16 len=8) => REACHABLE
  56. 到達不能な行(ブロック)が区別される • op_arrayを分岐地点でブロックに区切る • 条件分岐やreturnがあるところが境目 118 006 007 008 009

    010 011 012 013 014 \pcov\start(); $x = add(1, 2); if (false) { return; var_dump(' } else { var_dump(' } echo $x; Block 0 [REACHABLE] start=0 len=8 000 L6 ZEND_INIT_FCALL op1=JMP->002 op2=CONST( 001 L6 ZEND_DO_ICALL 002 L7 ZEND_INIT_FCALL op1=JMP->[006] op2=CONS 003 L7 ZEND_SEND_VAL op1=CONST(1) op2=JMP->[ 004 L7 ZEND_SEND_VAL op1=CONST(2) op2=JMP->[ 005 L7 ZEND_DO_FCALL 006 L7 ZEND_ASSIGN op1=CV($x) op2=VAR(208) 007 L8 ZEND_JMPZ op1=CONST(false) op2=J
  57. 到達不能な行(ブロック)が区別される • op_arrayを分岐地点でブロックに区切る • 条件分岐やreturnがあるところが境目 119 006 007 008 009

    010 011 012 013 014 \pcov\start(); $x = add(1, 2); if (false) { return; var_dump(' } else { var_dump(' } echo $x; Block 1 [REACHABLE] start=8 len=1 008 L9 ZEND_RETURN op1=CONST(null)
  58. 到達不能な行(ブロック)が区別される • op_arrayを分岐地点でブロックに区切る • 条件分岐やreturnがあるところが境目 120 006 007 008 009

    010 011 012 013 014 \pcov\start(); $x = add(1, 2); if (false) { return; var_dump(' } else { var_dump(' } echo $x; Block 2 [UNREACHABLE] start=9 len=4 009 L10 ZEND_INIT_FCALL op1=JMP->[012] 010 L10 ZEND_SEND_VAL op1=CONST(" 011 L10 ZEND_DO_ICALL 012 L8 ZEND_JMP op1=JMP->[016
  59. 到達不能な行(ブロック)が区別される • pcovでは、「絶対に到達することのない」行は カバレッジの分母から消される • PHPスクリプトの10行目とはここでさようなら〜 121 008 009 010

    if (false) { return; var_dump(' Block 2 [UNREACHABLE] start=9 len=4 009 L10 ZEND_INIT_FCALL op1=JMP->[012] 010 L10 ZEND_SEND_VAL op1=CONST(" 011 L10 ZEND_DO_ICALL 012 L8 ZEND_JMP op1=JMP->[016
  60. 測定対象行の〜 122 op_array 制御フローグラフ コンパイル ZEND_APIの機能 測定対象行の抽出 PHPスクリプト 結果データの初期化 カバレッジデータとの統合

    /opt/pcov-demo.php: line 6:-1 line 7:-1 line 8:-1 line 9:-1 line 12: -1 line 14: -1 line 15: -1 line 18: -1 line 20: -1 line 23: -1 line 24: -1 line 25: -1 line 27: -1 全ての対象行に対する、 「行番号」と「結果」を 持つデータを構築 この時点では、 「-1 = 未実行」で 埋めておく
  61. 測定対象行の〜 128 pcov-demo.php line 6: not covered line 7: covered

    line 8: covered line 9: not covered line 12: covered line 14: covered line 15: covered line 18: not covered line 20: not covered line 23: not covered line 24: not covered line 25: not covered line 27: not covered 最終的な測定結果 結果データの初期化 測定対象行の抽出 カバレッジデータとの統合 PHP側に結果を返す
  62. PHP拡張の中身・開発 • Writing PHP Extensions - YouTube • https://www.youtube.com/playlist?list=PLg9Kjjye- m1hW4z0J-546qaFpysjlo27x

    • Xdebug作者のDerickさんのYoutube • Writing PHP Extensions | Zend • https://www.zend.com/resources/writing-php-extensions • 古いかつ英語ですが、基本的なコンセプトは通じるはず 143
  63. 最近のコミュニティでのPHP拡張関連の情報 • Deep Dive into Xdebug by nsfisis • @PHPカンファレンス小田原2026

    • https://fortee.jp/phpconodawara-2026/proposal/ 2ff16827-3893-480e-b73b-0ff88e65e555 • PHP7.4でもOpenTelemetryゼロコード計装がしたい! by Arthur • @PHPerKaigi 2026 • https://fortee.jp/phperkaigi-2026/proposal/8310bab7- a3d3-4b7a-8c98-8d6a97c4fc00 144
  64. PHPの開発 • Using CLion with php-src - DEV Community •

    https://dev.to/ramsey/using-clion-with-php-src-4me0 • 少し前の記事だが、基本的にこの通りにやればphp-srcからPHPのデ バッグ実行まで行けるようになる(簡単!) • エラーになったら、ログやスクショを生成AIさんに渡して乗り切る 145