2022-04-13

[php-src読書録]その13: プロパティのデフォルト値, static method

php-srcのコードリーディングした内容をコツコツ残すテスト。その13

引き続きclass。今日はプロパティのデフォルト値, static method

<?php

class Hoge
{
  public $hoge = 1;

  public static function call() {
    echo 123;
  }
}
echo (new Hoge)->hoge;
echo Hoge::call();

opcode

$_main:
     ; (lines=8, args=0, vars=0, tmps=4)
     ; (before optimizer)
     ; /path/to/13.php:1-14
     ; return  [] RANGE[0..0]
0000 V0 = NEW 0 string("Hoge")
0001 DO_FCALL
0002 T2 = FETCH_OBJ_R V0 string("hoge")
0003 ECHO T2
0004 INIT_STATIC_METHOD_CALL 0 string("Hoge") string("call")
0005 V3 = DO_UCALL
0006 ECHO V3
0007 RETURN int(1)
LIVE RANGES:
     0: 0001 - 0002 (new)

Hoge::call:
     ; (lines=2, args=0, vars=0, tmps=0)
     ; (before optimizer)
     ; /path/to/13.php:7-9
     ; return  [] RANGE[0..0]
0000 ECHO int(123)
0001 RETURN null

プロパティのデフォルト値

zend_compile_prop_decl()zend_const_expr_to_zval() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7352:L7352

zend_const_expr_to_zval()ZVAL_COPY でresultにastのzvalをコピー
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L9914

ce->default_properties_table にデフォルトのプロパティを設定。offsetは property_info->offset を利用。オブジェクトのプロパティ配置と同じになる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L4148-L4149

ZEND_NEW_SPEC_CONST_UNUSED_HANDLER のハンドラで object_init_ex() => _object_and_properties_init() => _object_properties_init() と呼び出していき、プロパティをセットしているところはこの辺
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L1509-L1527

Static Method Call

zend_compile_static_call() でコンパイルされる

op1にクラス名、op2にメソッド名がセットされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4616-L4625

あとは zend_compile_call_common() などで引数や呼び出しのコンパイルを行う。

INIT_STATIC_METHOD_CALL opcodeは ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_CONST_HANDLER でハンドリングされる。 まとめるとざっとこんな感じ

ce = zend_fetch_class_by_name(Z_STR_P(RT_CONSTANT(opline, opline->op1)), Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1), ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
function_name = RT_CONSTANT(opline, opline->op2);
fbc = zend_std_get_static_method(ce, Z_STR_P(function_name), ((IS_CONST == IS_CONST) ? (RT_CONSTANT(opline, opline->op2) + 1) : NULL));
call = zend_vm_stack_push_call_frame(call_info,
	fbc, opline->extended_value, ce);
call->prev_execute_data = EX(call);
EX(call) = call;

zend_class_entryを取得してstatic methodなzend_functionを取得して、zend_class_entryのコンテキストで関数を実行している。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L1383:L1383

インスタンスメソッドなのにstatic関数呼び出しするとエラーにしている。

	if (!(fbc->common.fn_flags & ZEND_ACC_STATIC)) {
		if (Z_TYPE(EX(This)) == IS_OBJECT && instanceof_function(Z_OBJCE(EX(This)), ce)) {
			ce = (zend_class_entry*)Z_OBJ(EX(This));
			call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS;
		} else {
			zend_non_static_method_call(fbc);
			HANDLE_EXCEPTION();
		}

インスタンスメソッドを呼び出す場合、EX(This)のタイプはIS_OBJECTかつEX(This)のオブジェクトは対象クラスの関数である必要がある、という分岐。

また、 staticコンテキストで $this->xxx という感じで呼び出そうとするとエラーになる。 ZEND_FETCH_THIS_SPEC_UNUSED_UNUSED_HANDLER にはこんな感じな処理が入っている。

if (EXPECTED(Z_TYPE(EX(This)) == IS_OBJECT)) {
	zval *result = EX_VAR(opline->result.var);

	ZVAL_OBJ(result, Z_OBJ(EX(This)));
	Z_ADDREF_P(result);
	ZEND_VM_NEXT_OPCODE();
} else {
	ZEND_VM_TAIL_CALL(zend_this_not_in_object_context_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}

staticメソッド呼び出しではEX(This)にクラスが入るが、インスタンスメソッド呼び出しではオブジェクトが入っている。

このエントリーをはてなブックマークに追加