diff --git a/Zend/tests/protocols/basic_errors.phpt b/Zend/tests/protocols/basic_errors.phpt new file mode 100644 index 0000000000000..d302694c43c32 --- /dev/null +++ b/Zend/tests/protocols/basic_errors.phpt @@ -0,0 +1,20 @@ +--TEST-- +Protocol Basic Error +--FILE-- + $bar) { + var_dump($bar); +} + +foo(new Foo); +?> +--EXPECTF-- +Catchable fatal error: Argument 1 passed to foo() must look like Bar, instance of Foo given, called in %s/basic_errors.php on line %d and defined in %s/basic_errors.php on line %d diff --git a/Zend/tests/protocols/basic_functionality.phpt b/Zend/tests/protocols/basic_functionality.phpt new file mode 100644 index 0000000000000..aad98375ad236 --- /dev/null +++ b/Zend/tests/protocols/basic_functionality.phpt @@ -0,0 +1,27 @@ +--TEST-- +Protocol Basic Functionality +--FILE-- + $bar) { + var_dump($bar); +} + +foo(new Foo); +foo(new Baz); +?> +--EXPECT-- +object(Foo)#1 (0) { +} +object(Baz)#1 (0) { +} diff --git a/Zend/tests/protocols/protected_private_methods_ignored.phpt b/Zend/tests/protocols/protected_private_methods_ignored.phpt new file mode 100644 index 0000000000000..f5335f10c31f1 --- /dev/null +++ b/Zend/tests/protocols/protected_private_methods_ignored.phpt @@ -0,0 +1,23 @@ +--TEST-- +Protocol Test That Protected And Private Methods Are Ignored +--FILE-- + $bar) { + var_dump($bar); +} + +foo(new Foo); +?> +--EXPECT-- +object(Foo)#1 (0) { +} diff --git a/Zend/zend.c b/Zend/zend.c index aad6165e408a3..5745748429fb2 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -593,6 +593,11 @@ static void executor_globals_dtor(zend_executor_globals *executor_globals TSRMLS zend_hash_destroy(executor_globals->zend_constants); free(executor_globals->zend_constants); } + if (executor_globals->protocol_cache) { + zend_hash_destroy(executor_globals->protocol_cache); + free(executor_globals->protocol_cache); + } + } /* }}} */ diff --git a/Zend/zend.h b/Zend/zend.h index 1377fd566594b..5b43b6643c1ee 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -481,6 +481,7 @@ struct _zend_class_entry { int refcount; zend_uint ce_flags; + HashTable *protocol_cache; HashTable function_table; HashTable properties_info; zval **default_properties_table; @@ -589,6 +590,9 @@ typedef int (*zend_write_func_t)(const char *str, uint str_length); #define IS_CONSTANT_ARRAY 9 #define IS_CALLABLE 10 +/* Ugly hack to support protocol parsing */ +#define IS_PROTOCOL 11 + /* Ugly hack to support constants as static array indices */ #define IS_CONSTANT_TYPE_MASK 0x00f #define IS_CONSTANT_UNQUALIFIED 0x010 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index faef3a9197d0b..ba526a67b1144 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1911,7 +1911,12 @@ void zend_do_receive_arg(zend_uchar op, znode *varname, const znode *offset, con } } } else { - cur_arg_info->type_hint = IS_OBJECT; + if (class_type->u.constant.type == IS_PROTOCOL) { + cur_arg_info->type_hint = IS_PROTOCOL; + class_type->u.constant.type = IS_STRING; + } else { + cur_arg_info->type_hint = IS_OBJECT; + } if (ZEND_FETCH_CLASS_DEFAULT == zend_get_class_fetch_type(Z_STRVAL(class_type->u.constant), Z_STRLEN(class_type->u.constant))) { zend_resolve_class_name(class_type, opline->extended_value, 1 TSRMLS_CC); } @@ -3113,7 +3118,7 @@ static void do_inherit_method(zend_function *function) /* {{{ */ } /* }}} */ -static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto TSRMLS_DC) /* {{{ */ +ZEND_API zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto TSRMLS_DC) /* {{{ */ { zend_uint i; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 9c55b5ebe8812..2ff3dbf420b8d 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -694,6 +694,7 @@ ZEND_API zend_bool zend_is_compiling(TSRMLS_D); ZEND_API char *zend_make_compiled_string_description(const char *name TSRMLS_DC); ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify_handlers TSRMLS_DC); int zend_get_class_fetch_type(const char *class_name, uint class_name_len); +ZEND_API zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto TSRMLS_DC); typedef zend_bool (*zend_auto_global_callback)(const char *name, uint name_len TSRMLS_DC); typedef struct _zend_auto_global { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 3c3dd8e3b0b57..c09fa0ba1a6b9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -630,11 +630,16 @@ static inline int zend_verify_arg_type(zend_function *zf, zend_uint arg_num, zva need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, &class_name, &ce TSRMLS_CC); return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, need_msg, class_name, "none", "" TSRMLS_CC); } - if (Z_TYPE_P(arg) == IS_OBJECT) { + if (cur_arg_info->type_hint == IS_OBJECT && Z_TYPE_P(arg) == IS_OBJECT) { need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, &class_name, &ce TSRMLS_CC); if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce TSRMLS_CC)) { return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name TSRMLS_CC); } + } else if (cur_arg_info->type_hint == IS_PROTOCOL && Z_TYPE_P(arg) == IS_OBJECT) { + ce = zend_fetch_class(cur_arg_info->class_name, cur_arg_info->class_name_len, (fetch_type | ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD) TSRMLS_CC); + if (!ce || !protocol_check_function(Z_OBJCE_P(arg), ce TSRMLS_CC)) { + return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, "look like ", (ce ? ce->name : cur_arg_info->class_name), "instance of ", Z_OBJCE_P(arg)->name TSRMLS_CC); + } } else if (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null) { need_msg = zend_verify_arg_class_kind(cur_arg_info, fetch_type, &class_name, &ce TSRMLS_CC); return zend_verify_arg_error(E_RECOVERABLE_ERROR, zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "" TSRMLS_CC); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 800bdc7f66bcf..e2cddc0c493a2 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -187,6 +187,8 @@ struct _zend_executor_globals { zend_op_array *active_op_array; + HashTable *protocol_cache; /* protocol cache hash table */ + HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ HashTable *zend_constants; /* constants table */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 6a9a24a87ea72..b930247e22275 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -538,6 +538,7 @@ optional_class_type: /* empty */ { $$.op_type = IS_UNUSED; } | T_ARRAY { $$.op_type = IS_CONST; Z_TYPE($$.u.constant)=IS_ARRAY; } | T_CALLABLE { $$.op_type = IS_CONST; Z_TYPE($$.u.constant)=IS_CALLABLE; } + | '<' fully_qualified_class_name '>' { $$ = $2; Z_TYPE($$.u.constant) = IS_PROTOCOL; } | fully_qualified_class_name { $$ = $1; } ; diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 60730121882c5..95bd863fa0b77 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -30,6 +30,8 @@ #include "zend_strtod.h" #include "zend_exceptions.h" #include "zend_closures.h" +#include "zend_compile.h" +#include "zend_hash.h" #if ZEND_USE_TOLOWER_L #include @@ -1800,6 +1802,99 @@ ZEND_API int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSR } /* }}} */ +static int protocol_check_function_implementation(void *function_entry TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key); + +static void protocol_cache_dtor(void *data) /* {{{ */ +{ + zend_hash_destroy((HashTable *) data); +} +/* }}} */ + +ZEND_API zend_bool protocol_check_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC) /* {{{ */ +{ + zend_bool result = 1; + HashTable *cache_bucket = NULL; + zend_bool *cache_result = NULL; + + if (!instance_ce->protocol_cache) { + HashTable **cache_bucket_ext = NULL; + + if (!EG(protocol_cache)) { + EG(protocol_cache) = (HashTable*) malloc(sizeof(HashTable)); + zend_hash_init(EG(protocol_cache), 16, NULL, protocol_cache_dtor, 1); + } + + if (zend_hash_find(EG(protocol_cache), instance_ce->name, instance_ce->name_length, (void **) &cache_bucket_ext) == FAILURE) { + cache_bucket = (HashTable*) malloc(sizeof(HashTable)); + zend_hash_init(cache_bucket, 16, NULL, NULL, 1); + zend_hash_add(EG(protocol_cache), instance_ce->name, instance_ce->name_length, (void *) &cache_bucket, sizeof(void *), NULL); + } else { + cache_bucket = *cache_bucket_ext; + } + instance_ce->protocol_cache = cache_bucket; + } else { + cache_bucket = instance_ce->protocol_cache; + } + + if (zend_hash_find(cache_bucket, ce->name, ce->name_length, (void **) &cache_result) != FAILURE) { + return *cache_result; + } + + if (0 == instanceof_function(instance_ce, ce)) { + /* Short-circuit if types match */ + zend_hash_apply_with_arguments(&(ce->function_table), protocol_check_function_implementation, 2, instance_ce, &result); + } + + zend_hash_add(cache_bucket, ce->name, ce->name_length, (void *) &result, sizeof(zend_bool), NULL); + + return result; +} +/* }}} */ + +static int protocol_check_function_implementation(void *function_entry TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ +{ + zend_function *child; + zend_bool *result; + zend_class_entry *ce; + zend_uint child_flags; + zend_uint protocol_flags = ((zend_function*) function_entry)->common.fn_flags; + + if (protocol_flags & (ZEND_ACC_PRIVATE | ZEND_ACC_PROTECTED)) { + /* Skip the non-public API */ + return ZEND_HASH_APPLY_KEEP; + } + + TSRMLS_FETCH(); + ce = va_arg(args, zend_class_entry*); + result = va_arg(args, zend_bool*); + + if (*result == 0) { + return ZEND_HASH_APPLY_STOP; + } + + if (zend_hash_quick_find(&(ce->function_table), hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void **) &child) == FAILURE) { + *result = 0; + return ZEND_HASH_APPLY_STOP; + } + child_flags = child->common.fn_flags; + + if ((child_flags & ZEND_ACC_STATIC) != (protocol_flags & ZEND_ACC_STATIC)) { + *result = 0; + return ZEND_HASH_APPLY_STOP; + } + if ((child_flags & ZEND_ACC_ABSTRACT)) { + /* This shouldn't be possible, as it requires an instance, bail! */ + *result = 0; + return ZEND_HASH_APPLY_STOP; + } + if (zend_do_perform_implementation_check(child, function_entry TSRMLS_DC) == 0) { + *result = 0; + return ZEND_HASH_APPLY_STOP; + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + ZEND_API zend_bool instanceof_function_ex(const zend_class_entry *instance_ce, const zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC) /* {{{ */ { zend_uint i; diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index e7ab9bb3fe9e4..1c78e344d6736 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -68,6 +68,7 @@ ZEND_API int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSR ZEND_API zend_bool instanceof_function_ex(const zend_class_entry *instance_ce, const zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC); ZEND_API zend_bool instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce TSRMLS_DC); +ZEND_API zend_bool protocol_check_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC); END_EXTERN_C() #if ZEND_DVAL_TO_LVAL_CAST_OK