Skip to content

Commit 98f51c8

Browse files
author
cpriest
committed
Implementation of static isset/unset code, added numerous tests.
ADDED: zend_compile_string_inline() - Compiles code straight into current op_array. ADDED: Automatic setting of function purpose for internal functions to ZEND_FNP_UNDEFINED ADDED: Handling of static self::$a accessor handling. CHANGE: Changed the way that automated implementations are coded. All automatic implementations are now embeded/built PHP strings run through zend_compile_string_inline(). Performance testing shows excellent performance and the code is 10x simpler. ADDED: Automatic implementation of isset/unset in the case of no declaration at all, when appropriate. ADDED: Implementation of static isset/unset code. CHANGE: pass_two() was modified to catch static gets where there is no getter. Static setters work by converting any class::$a into the getter, then the followup zend_do_assign() backpatches the getter into a setter call. When no zend_do_assign() is made and there is no getter, this is caught in pass_two() - pass_two() also allows for more accurate error file/line reporting now as well.
1 parent e7cc406 commit 98f51c8

30 files changed

+750
-116
lines changed

Zend/zend.c

+1
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ int zend_startup(zend_utility_functions *utility_functions, char **extensions TS
691691
zend_execute_internal = NULL;
692692
#endif /* HAVE_SYS_SDT_H */
693693
zend_compile_string = compile_string;
694+
zend_compile_string_inline = compile_string_inline;
694695
zend_throw_exception_hook = NULL;
695696

696697
zend_init_opcodes_handlers();

Zend/zend_API.c

+1
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
19681968
internal_function->function_name = (char*)ptr->fname;
19691969
internal_function->scope = scope;
19701970
internal_function->prototype = NULL;
1971+
internal_function->purpose = ZEND_FNP_UNDEFINED;
19711972
if (ptr->flags) {
19721973
if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
19731974
if (ptr->flags != ZEND_ACC_DEPRECATED || scope) {

Zend/zend_compile.c

+323-101
Large diffs are not rendered by default.

Zend/zend_compile.h

+19-6
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,22 @@
3636

3737
#define IS_ACCESSOR(purpose) ( purpose >= ZEND_FNP_PROP_GETTER && purpose <= ZEND_FNP_PROP_UNSETTER )
3838

39-
#define MAKE_ZNODE(zn, str) \
39+
#define INIT_ZNODE(zn) \
4040
{ \
4141
zn.op_type = IS_CONST; \
4242
INIT_PZVAL(&zn.u.constant); \
43-
ZVAL_STRINGL(&zn.u.constant, str, strlen(str), 1); \
4443
zn.EA = 0; \
4544
}
45+
46+
#define MAKE_ZNODE(zn, str) \
47+
{ \
48+
INIT_ZNODE(zn); \
49+
ZVAL_STRINGL(&zn.u.constant, str, strlen(str), 1); \
50+
}
4651
#define MAKE_ZNODEL(zn, str, l) \
4752
{ \
48-
zn.op_type = IS_CONST; \
49-
INIT_PZVAL(&zn.u.constant); \
53+
INIT_ZNODE(zn); \
5054
ZVAL_STRINGL(&zn.u.constant, str, l, 1); \
51-
zn.EA = 0; \
5255
}
5356

5457
#define SET_UNUSED(op) op ## _type = IS_UNUSED
@@ -378,7 +381,8 @@ typedef union _zend_function {
378381
zend_internal_function internal_function;
379382
} zend_function;
380383

381-
#define ZEND_ACC_NAME(func) &func->common.function_name[5]
384+
#define ZEND_ACC_NAME(func) zend_get_accessor_name_from_function(func TSRMLS_CC)
385+
#define ZEND_ACC_NAME_AI(ai) zend_get_accessor_name_from_accessor_info(ai TSRMLS_CC)
382386

383387
typedef struct _zend_accessor_info {
384388
zend_uint flags;
@@ -455,6 +459,7 @@ void zend_init_compiler_context(TSRMLS_D);
455459

456460
extern ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);
457461
extern ZEND_API zend_op_array *(*zend_compile_string)(zval *source_string, char *filename TSRMLS_DC);
462+
extern ZEND_API void (*zend_compile_string_inline)(zval *source_string, char *filename TSRMLS_DC);
458463

459464
ZEND_API int lex_scan(zval *zendlval TSRMLS_DC);
460465
void startup_scanner(TSRMLS_D);
@@ -545,6 +550,7 @@ void zend_do_handle_exception(TSRMLS_D);
545550
void zend_declare_accessor(znode *var_name TSRMLS_DC);
546551
void zend_do_begin_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, int return_reference TSRMLS_DC);
547552
void zend_do_end_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, const znode *body TSRMLS_DC);
553+
void zend_finalize_accessor(znode *var_name TSRMLS_DC);
548554

549555
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
550556
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
@@ -679,6 +685,12 @@ void zend_do_goto(const znode *label TSRMLS_DC);
679685
void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2 TSRMLS_DC);
680686
void zend_release_labels(TSRMLS_D);
681687

688+
const char *zend_get_accessor_name_from_function(zend_function *func TSRMLS_DC);
689+
const char *zend_get_accessor_name_from_accessor_info(zend_accessor_info *ai TSRMLS_DC);
690+
zend_accessor_info *zend_get_accessor_info_from_function(zend_function *func TSRMLS_DC);
691+
zend_accessor_info *zend_get_accessor_from_init_static_method_call(zend_op_array *op_array, zend_op *opline, const char **context_name_out TSRMLS_DC);
692+
693+
682694
ZEND_API void function_add_ref(zend_function *function);
683695

684696
#define INITIAL_OP_ARRAY_SIZE 64
@@ -688,6 +700,7 @@ ZEND_API void function_add_ref(zend_function *function);
688700
/* helper functions in zend_language_scanner.l */
689701
ZEND_API zend_op_array *compile_file(zend_file_handle *file_handle, int type TSRMLS_DC);
690702
ZEND_API zend_op_array *compile_string(zval *source_string, char *filename TSRMLS_DC);
703+
ZEND_API void compile_string_inline(zval *source_string, char *filename TSRMLS_DC);
691704
ZEND_API zend_op_array *compile_filename(int type, zval *filename TSRMLS_DC);
692705
ZEND_API int zend_execute_scripts(int type TSRMLS_DC, zval **retval, int file_count, ...);
693706
ZEND_API int open_file_for_scanning(zend_file_handle *file_handle TSRMLS_DC);

Zend/zend_language_parser.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ class_variable_accessor_declarations:
756756
T_VARIABLE '{'
757757
{ zend_declare_accessor(&$1 TSRMLS_CC); CG(accessor_node) = &$1; }
758758
accessors
759-
{ efree($1.u.constant.value.str.val); }
759+
{ zend_finalize_accessor(&$1 TSRMLS_CC); efree($1.u.constant.value.str.val); }
760760
'}'
761761
| class_variable_declaration ';'
762762
;

Zend/zend_language_scanner.l

+43
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,49 @@ zend_op_array *compile_string(zval *source_string, char *filename TSRMLS_DC)
779779
}
780780

781781

782+
void compile_string_inline(zval *source_string, char *filename TSRMLS_DC)
783+
{
784+
zend_lex_state original_lex_state;
785+
zval tmp;
786+
int compiler_result;
787+
zend_bool original_in_compilation = CG(in_compilation);
788+
789+
if (Z_STRLEN_P(source_string) == 0) {
790+
return;
791+
}
792+
793+
CG(in_compilation) = 1;
794+
795+
tmp = *source_string;
796+
zval_copy_ctor(&tmp);
797+
convert_to_string(&tmp);
798+
source_string = &tmp;
799+
800+
zend_save_lexical_state(&original_lex_state TSRMLS_CC);
801+
if (zend_prepare_string_for_scanning(source_string, filename TSRMLS_CC)==SUCCESS) {
802+
CG(zend_lineno) = original_lex_state.lineno;
803+
zend_bool orig_interactive = CG(interactive);
804+
805+
CG(interactive) = 0;
806+
CG(interactive) = orig_interactive;
807+
BEGIN(ST_IN_SCRIPTING);
808+
compiler_result = zendparse(TSRMLS_C);
809+
810+
if (SCNG(script_filtered)) {
811+
efree(SCNG(script_filtered));
812+
SCNG(script_filtered) = NULL;
813+
}
814+
815+
if (compiler_result==1) {
816+
CG(unclean_shutdown)=1;
817+
}
818+
}
819+
zend_restore_lexical_state(&original_lex_state TSRMLS_CC);
820+
zval_dtor(&tmp);
821+
CG(in_compilation) = original_in_compilation;
822+
}
823+
824+
782825
BEGIN_EXTERN_C()
783826
int highlight_file(char *filename, zend_syntax_highlighter_ini *syntax_highlighter_ini TSRMLS_DC)
784827
{

Zend/zend_opcode.c

+35-1
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,14 @@ static void zend_check_finally_breakout(zend_op_array *op_array, zend_op *opline
530530
ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC)
531531
{
532532
zend_op *opline, *end;
533+
zend_bool original_in_compilation = CG(in_compilation);
534+
char *original_compiled_filename = CG(compiled_filename);
535+
int original_zend_lineno = CG(zend_lineno);
533536

534-
if (op_array->type!=ZEND_USER_FUNCTION && op_array->type!=ZEND_EVAL_CODE) {
537+
if( (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) ) {
538+
return 0;
539+
}
540+
if (op_array->type != ZEND_USER_FUNCTION && op_array->type != ZEND_EVAL_CODE) {
535541
return 0;
536542
}
537543
if (CG(compiler_options) & ZEND_COMPILE_EXTENDED_INFO) {
@@ -554,9 +560,13 @@ ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC)
554560
CG(context).literals_size = op_array->last_literal;
555561
}
556562

563+
CG(in_compilation) = 1;
564+
CG(compiled_filename) = (char*)op_array->filename;
565+
557566
opline = op_array->opcodes;
558567
end = opline + op_array->last;
559568
while (opline < end) {
569+
CG(zend_lineno) = opline->lineno;
560570
if (opline->op1_type == IS_CONST) {
561571
opline->op1.zv = &op_array->literals[opline->op1.constant].constant;
562572
}
@@ -613,15 +623,39 @@ ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC)
613623
opline->opcode = ZEND_GENERATOR_RETURN;
614624
}
615625
break;
626+
case ZEND_INIT_STATIC_METHOD_CALL: {
627+
/** Is this a call to a static getter, static setters are dependent on static getters (which get backpatched to setter calls), so during compilation
628+
* a static getter call may be in the op_array and if no zend_do_assign() has been called to backpatch it, then it's an illegal getter call, this
629+
* checks to see that by this point any getter that has not been backpatched, actually exists as a getter, or errors. */
630+
const char *context_name; /* Does not need to be free'd */
631+
632+
zend_accessor_info *ai = zend_get_accessor_from_init_static_method_call(op_array, opline, &context_name TSRMLS_CC);
633+
if(ai) {
634+
if(Z_STRVAL_P(opline->op2.zv)[2] == 'g') {
635+
if(!ai->getter) {
636+
zend_error_noreturn(E_ERROR, "Cannot get property %s::$%s, no getter defined.", context_name, Z_STRVAL_P(opline->op2.zv)+5);
637+
}
638+
} else if (Z_STRVAL_P(opline->op2.zv)[2] == 's') {
639+
if(!ai->setter) {
640+
zend_error_noreturn(E_ERROR, "Cannot set property %s::$%s, no setter defined.", context_name, Z_STRVAL_P(opline->op2.zv)+5);
641+
}
642+
}
643+
}
644+
}
645+
break;
616646
}
617647
ZEND_VM_SET_OPCODE_HANDLER(opline);
618648
opline++;
619649
}
620650

621651
op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO;
652+
CG(in_compilation) = original_in_compilation;
653+
CG(compiled_filename) = original_compiled_filename;
654+
CG(zend_lineno) = original_zend_lineno;
622655
return 0;
623656
}
624657

658+
625659
int print_class(zend_class_entry *class_entry TSRMLS_DC)
626660
{
627661
printf("Class %s:\n", class_entry->name);

tests/classes/accessor_interface_error.phpt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ class TimePeriod implements Hours {
1616

1717
?>
1818
--EXPECTF--
19-
Fatal error: Class TimePeriod contains 2 abstract accessors and must be declared abstract or implement the remaining accessors (get Hours::$Hours, set Hours::$Hours) in %s on line %d
19+
Fatal error: Class TimePeriod contains 4 abstract accessors and must be declared abstract or implement the remaining accessors (get Hours::$Hours, set Hours::$Hours, isset Hours::$Hours, ...) in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
ZE2 Tests that a read-only defined getter produces an error if unset() is attempted
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public read-only $b {
8+
get {
9+
echo "get AccessorTest::\$b Called.\n";
10+
return 5;
11+
}
12+
}
13+
}
14+
15+
$o = new AccessorTest();
16+
unset($o->b);
17+
?>
18+
--EXPECTF--
19+
Fatal error: Cannot unset read-only property AccessorTest::$b. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
ZE2 Tests that a undefined getter produces an error upon isset()
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public $b {
8+
set;
9+
}
10+
}
11+
12+
$o = new AccessorTest();
13+
14+
echo "isset(\$o->b) = ".(int)isset($o->b)."\n";
15+
?>
16+
--EXPECTF--
17+
Fatal error: Cannot get property AccessorTest::$b, no getter defined. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
ZE2 Tests that a accessor declared as write-only cannot declare isset
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public write-only $b {
8+
set;
9+
isset;
10+
}
11+
}
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Cannot define isset for write-only property $b in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
ZE2 Tests that access to a undefined parent getter produces an error
3+
--FILE--
4+
<?php
5+
6+
class ParentAccessorTest {
7+
public $a {
8+
set {
9+
echo "set ParentAccessorTest::\$a Called \$value = {$value}.\n";
10+
}
11+
}
12+
}
13+
14+
class AccessorTest extends ParentAccessorTest {
15+
public $b {
16+
get {
17+
echo "get AccessorTest::\$b Called.\n";
18+
return parent::$a;
19+
}
20+
set {
21+
echo "Setter called.\n";
22+
}
23+
}
24+
}
25+
26+
$o = new AccessorTest();
27+
echo $o->b;
28+
?>
29+
--EXPECTF--
30+
Fatal error: Cannot get property parent::$a, no getter defined. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
ZE2 Tests that access to a undefined self getter produces an error
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public $a {
8+
set;
9+
}
10+
public $b {
11+
get {
12+
echo "get AccessorTest::\$b Called.\n";
13+
return self::$a;
14+
}
15+
set {
16+
echo "Setter called.\n";
17+
}
18+
}
19+
}
20+
21+
$o = new AccessorTest();
22+
echo $o->b;
23+
?>
24+
--EXPECTF--
25+
Fatal error: Cannot get property self::$a, no getter defined. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
ZE2 Tests that an undefined setter produces an error on unset()
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public $b {
8+
get;
9+
}
10+
}
11+
12+
$o = new AccessorTest();
13+
14+
unset($o->b);
15+
?>
16+
--EXPECTF--
17+
Fatal error: Cannot set property AccessorTest::$b, no setter defined. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
ZE2 Tests that a write-only defined setter produces an error if isset() is attempted
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public write-only $b {
8+
set {
9+
echo "set AccessorTest::\$b Called \$value = {$value}.\n";
10+
}
11+
}
12+
}
13+
14+
$o = new AccessorTest();
15+
16+
echo "isset(\$o->b) = ".(int)isset($o->b)."\n";
17+
?>
18+
--EXPECTF--
19+
Fatal error: Cannot isset write-only property AccessorTest::$b. in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
ZE2 Tests that a read-only accessor cannot have unset defined
3+
--FILE--
4+
<?php
5+
6+
class AccessorTest {
7+
public read-only $b {
8+
get;
9+
unset;
10+
}
11+
}
12+
?>
13+
--EXPECTF--
14+
Fatal error: Cannot define unset for read-only property $b in %s on line %d

0 commit comments

Comments
 (0)