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 11, 2026

More Decks by hideki kinjyo

Other Decks in Programming

Transcript

  1. 自己紹介 • 金城秀樹 / きんじょうひでき • GitHub: @o0h / 𝕏

    : @o0h_ • 好きなFWはCakePHP • アイコンは美味しい鮭親子丼の写真です • 最近はPodcastをやっています • ハッシュタグ: #readlinefm 
  2. 測定実行コード pcovの関数を利用して カバレッジを測定する コード 32 <?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
  3. 測定実行コード 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 ここから測定開始
  4. 測定実行コード 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 ここで測定終了
  5. 測定実行コード 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 ここが測定対象
  6. 測定実行コード 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 測定結果の生成・取得
  7. 測定結果 37 ["/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したもの
  8. 測定結果 38 ["/opt/lib/functions.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=>

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

    int(-1) [11]=> int(1) } 1回でもその行を通った 1回もその行を通らなかった
  10. 測定結果 40 [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();
  11. pcov拡張が提供している関数 \pcov\start, \pcov\stop, \pcov\collect Ҏ֎ʹ΋ɾɾ • \pcov\enabled : pcov͕༗ޮʹͳ͍ͬͯΔ͔Λฦ͢ •

    \pcov\clear : ֨ೲ͞Ε͍ͯΔ৘ใΛΫϦΞ • \pcov\waiting : collect͞Ε͍ͯͳ͍ϑΝΠϧϦετΛฦ͢ • \pcov\memory : ֬อࡁΈͰ࢖ΘΕ͍ͯͳ͍ϝϞϦ༰ྔΛฦ͢ 42
  12. りある・おぺこーど <?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) 53
  13. PHPスクリプトが実行されるまで 54 本当の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
  14. ZendVMのメインループ 60 $_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
  15. ZendVMのメインループ 61 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
  16. ZendVMのメインループ 62 L10 00 INIT_FCALL 2 144 string("div") ɾ ɾ

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

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

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

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

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

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

     ZVAL_LONG(&php_pcov_uncovered, PHP_PCOV_UNCOVERED); 「実行されていない行」 を示す定数、「-1」 ZVAL_LONG(&php_pcov_covered, PHP_PCOV_COVERED); 「実行されている行」 を示す定数、「1」 pcov.c
  24. zend_execute_ex / _zend_execute_data zend_execute_exは_zend_execute_dataを受け取る 77 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
  25. zend_execute_ex / _zend_execute_data zend_execute_dataはzend_functionをもち、op_arrayを含む 1つのop_arrayが1つのフレームに相当(≒関数の中身のopcode) 78 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
  26. 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
  27. php_pcov_execute_ex (っぽいもの) function pcovExecuteEx($࣮ߦ͢Δσʔλ) { while (true) { $returnCode =

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

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

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

    pcovTrace($࣮ߦ͢Δσʔλ); if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } } }  if ($returnCode === CODE_શ෦ऴྃ) { return; } elseif ($returnCode !== CODE_ͦͷ··࣍΁) { $࣮ߦ͢Δσʔλ = ݱࡏͷϑϨʔϜऔಘ(); } 状態に応じて 離脱したり次に行ったり
  31. 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
  32. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

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

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

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

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); }  pcovCreate($࣮ߦ͢Δσʔλ); 「実行済み」行のデータ作成
  36. 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
  37. 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; } 
  38. 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; }  ファイル名、行番号を持つ 構造体 create->file create->line
  39. php_pcov_trace (っぽいもの) function pcovTrace($࣮ߦ͢Δσʔλ) { if (pcovଌఆελʔτࡁΈ()) { if (/**

    ଌఆσʔλΛ࡞ͬͯ௥Ճ͢Δ͔ */) { $ଌఆσʔλ = pcovCreate($࣮ߦ͢Δσʔλ); ଌఆσʔλ௥Ճ($ଌఆσʔλ); } } return zendVMͷඪ४Φϖίʔυॲཧ($࣮ߦ͢Δσʔλ); }  ଌఆσʔλ௥Ճ($ଌఆσʔλ); file, line情報を持つ 構造体を蓄積する
  40. 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
  41. pcovのオーバーライド PHP_RINIT_FUNCTION(pcov) { /** লུ */ /** লུ */ }

     pcov.c zend_compile_file = php_pcov_compile_file; phpスクリプト→opcode変換処理の オーバーライド
  42. 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; } 
  43. 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)が そのまま関数全体の戻り値になる
  44. 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) 測定/除外対象の 設定内容との照合
  45. 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) もうメモしてあるファイルだったらスキップ
  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; }  ଌఆର৅ϑΝΠϧʹ௥Ճ($op_array->filename); ココが肝!!
  47. \pcov\collect() のおさらい • 「実行された行」「されなかった行」のデータを返す関数 • この関数が呼ばれると、集めたデータの統合と整理を行う • コンパイル時に集めた 「対象ファイル」の一覧 •

    コード実行時に記録された 「実行された行」の一覧 111 // var_dump(\pcov\collect()); ["/opt/hoge.php"]=> array(4) { [13]=> int(-1) [7]=> int(1) [8]=> int(-1) [11]=> int(1) }
  48. 測定対象行の〜 115 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
  49. 測定対象行の〜 制御フローグラフ 結果データの初期化 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)
  50. 実行に関わりが無い行が消える op_arrayの時点で、 「実行可能なコードがない」行は消えている 117 [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は処理を持たない
  51. 測定対象行の〜 118 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
  52. 到達不能な行(ブロック)が区別される • 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 0 [REACHABLE] start=0 len=8 000 L6 ZEND_INIT_FCALL op1=JMP->002 001 L6 ZEND_DO_ICALL 002 L7 ZEND_INIT_FCALL op1=JMP->[00 // ... 006 L7 ZEND_ASSIGN op1=CV($x) o 007 L8 ZEND_JMPZ op1=CONST(fa
  53. 到達不能な行(ブロック)が区別される • op_arrayを分岐地点でブロックに区切る • 条件分岐やreturnがあるところが境目 121 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)
  54. 到達不能な行(ブロック)が区別される • op_arrayを分岐地点でブロックに区切る • 条件分岐やreturnがあるところが境目 122 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
  55. 到達不能な行(ブロック)が区別される • pcovでは、「絶対に到達することのない」行は カバレッジの分母から消される • PHPスクリプトの10行目とはここでさようなら〜 123 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
  56. 測定対象行の〜 124 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 = 未実行」で 埋めておく
  57. 測定対象行の〜 126 /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 最終的な測定結果 結果データの初期化 測定対象行の抽出 カバレッジデータとの統合 PHP側に結果を返す
  58. 測定対象行の〜 130 /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 最終的な測定結果 結果データの初期化 測定対象行の抽出 カバレッジデータとの統合 PHP側に結果を返す
  59. 何を可能にしているのか • 通常の処理をラップして、独自の仕事を付加している • ex: zend_execute_ex のオーバーライド • oplineの処理時に、「これがどこの行だったか」を記録 •

    独自関数内部で、ZendVMのAPIも利用してデータ作成 • ex: \pcov\collect() 内部での、zend_build_cfgの利用。 制御フローグラフの構築と、到達不可能ブロックの洗い出し 135
  60. 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 • 古いかつ英語ですが、基本的なコンセプトは通じるはず 149
  61. 最近のコミュニティでの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 150
  62. PHP(内部)入門的なもの • php-src 入門: ぼくらの画面に hello world が届くまで sji •

    @PHPカンファレンス福岡2024 • https://www.youtube.com/watch?v=fpRcZiTi5As 151
  63. PHPの開発 • Using CLion with php-src - DEV Community •

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