Skip to content

Loading…

Fixed Bug #61025 __invoke() visibility not honored #298

Closed
wants to merge 3 commits into from

5 participants

@reeze

See bug https://bugs.php.net/bug.php?id=61025

This patch make __invoke() visibility consist with other magic methods.

reeze added some commits
@reeze reeze Fixed bug #61025 (__invoke() visibility not honored)
- check visibility of __invoke when calling
- make is_callable consist
- Add the ZEND_ACC_CLOSURE flag to the closure created with: zend_create_closure()
be42661
@reeze reeze __invoke() magic method should not be declared as static
Related to bug #61025
Consist with similar magic methods which will raise warning
when modified with improper modifiers
e60ee2d
@reeze reeze Add missing test file for bug #61025 cd55a59
@lstrojny lstrojny commented on the diff
Zend/zend_API.c
@@ -3100,6 +3100,17 @@ ZEND_API zend_bool zend_is_callable_ex(zval *callable, zval *object_ptr, uint ch
case IS_OBJECT:
if (Z_OBJ_HANDLER_P(callable, get_closure) && Z_OBJ_HANDLER_P(callable, get_closure)(callable, &fcc->calling_scope, &fcc->function_handler, &fcc->object_ptr TSRMLS_CC) == SUCCESS) {
fcc->called_scope = fcc->calling_scope;
+ if (!(fcc->function_handler->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(fcc->called_scope != EG(scope))) {
+ return 0;
+ }
+ } else if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fcc->function_handler), EG(scope)))) {
+ return 0;
+ }
+ }
@lstrojny
lstrojny added a note

Don’t we miss a return 0 here?

@reeze
reeze added a note

Hmm, I only see four conditions, they are closure function and other three possible ZEND_ACC_PPP* flags.
or else it's common function, but that wouldn't happen when get the function with get_closure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@laruence
php.net member

I don't think this need to be fixed in this way, like __call:

<?php

class Bar {
    private function __call($name, $value) {
        return __CLASS__;
    }
}

$b = new Bar;
$b->__call("name", NULL);

works well, but with an warning:

Warning: The magic method __call() must have public visibility and cannot be static 

I am not sure whether this is a bug, or just need document.

@jpauli
php.net member

This patch is inconsistent with what actually exist.
Actually, __magics() signature are checked at compile time, not at run-time. This patch adds runtime checks for __invoke() , which IMO is wrong.

I think it's better to stay consistent and add the checks in the zend_compile.c cases.
Visibility is never checked at runtime for __magics() (but used to be).

@php-pulls

Comment on behalf of laruence at php.net:

wrong fix, closed

@php-pulls php-pulls closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 6, 2013
  1. @reeze

    Fixed bug #61025 (__invoke() visibility not honored)

    reeze committed
    - check visibility of __invoke when calling
    - make is_callable consist
    - Add the ZEND_ACC_CLOSURE flag to the closure created with: zend_create_closure()
  2. @reeze

    __invoke() magic method should not be declared as static

    reeze committed
    Related to bug #61025
    Consist with similar magic methods which will raise warning
    when modified with improper modifiers
  3. @reeze
View
17 Zend/tests/bug61025-1.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Bug #61025 (__invoke() visibility not honored) invoke private
+--FILE--
+<?php
+
+class FooPrivate {
+ private function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+$foo_private = new FooPrivate();
+
+$foo_private();
+?>
+===DONE===
+--EXPECTF--
+Fatal error: Call to private FooPrivate::__invoke() from context '' in %s/bug61025-1.php on line %d
View
67 Zend/tests/bug61025-2.phpt
@@ -0,0 +1,67 @@
+--TEST--
+Bug #61025 (__invoke() visibility not honored) is_callable consist
+--FILE--
+<?php
+
+class FooPublic {
+ public function __construct() {
+ echo "Check in class: " . __CLASS__ . " \$this is callable: ";
+ echo is_callable($this) ? 'true' : 'false';
+ echo "\n";
+ }
+ public function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+
+class FooProtected {
+ public function __construct() {
+ echo "Check in class: " . __CLASS__ . " \$this is callable: ";
+ echo is_callable($this) ? 'true' : 'false';
+ echo "\n";
+ }
+
+ protected function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+
+class FooPrivate {
+ public function __construct() {
+ echo "Check in class: " . __CLASS__ . " \$this is callable: ";
+ echo is_callable($this) ? 'true' : 'false';
+ echo "\n";
+ }
+
+ private function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+
+$foo_public = new FooPublic();
+echo "Check outter is callable: ";
+echo is_callable($foo_public) ? 'true' : 'false';
+echo "\n\n";
+
+$foo_protected = new FooProtected();
+echo "Check outter is callable: ";
+echo is_callable($foo_protected) ? 'true' : 'false';
+echo "\n\n";
+
+$foo_private = new FooPrivate();
+echo "Check outter is callable: ";
+echo is_callable($foo_private) ? 'true' : 'false';
+echo "\n\n";
+?>
+===DONE===
+--EXPECT--
+Check in class: FooPublic $this is callable: true
+Check outter is callable: true
+
+Check in class: FooProtected $this is callable: true
+Check outter is callable: false
+
+Check in class: FooPrivate $this is callable: true
+Check outter is callable: false
+
+===DONE===
View
21 Zend/tests/bug61025-3.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Bug #61025 (__invoke() visibility not honored) raise warning when using static
+--FILE--
+<?php
+
+interface IInvoke {
+ public static function __invoke();
+}
+
+class Foo {
+ public static function __invoke() {
+ echo __CLASS__;
+ }
+}
+?>
+===DONE===
+--EXPECTF--
+Warning: The magic method __invoke() cannot be static in %s/bug61025-3.php on line %d
+
+Warning: The magic method __invoke() cannot be static in %s/bug61025-3.php on line %d
+===DONE===
View
34 Zend/tests/bug61025.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Bug #61025 (__invoke() visibility not honored)
+--FILE--
+<?php
+
+class FooPublic {
+ public function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+
+class FooProtected {
+ protected function __invoke() {
+ echo __CLASS__ . "::" . __FUNCTION__ . "()\n";
+ }
+}
+
+$closure = function () {
+ echo "Closure\n";
+};
+
+$foo_public = new FooPublic();
+$foo_protected = new FooProtected();
+
+$closure();
+$foo_public();
+$foo_protected();
+?>
+===DONE===
+--EXPECTF--
+Closure
+FooPublic::__invoke()
+
+Fatal error: Call to protected FooProtected::__invoke() from context '' in %s/bug61025.php on line %d
View
11 Zend/zend_API.c
@@ -3100,6 +3100,17 @@ ZEND_API zend_bool zend_is_callable_ex(zval *callable, zval *object_ptr, uint ch
case IS_OBJECT:
if (Z_OBJ_HANDLER_P(callable, get_closure) && Z_OBJ_HANDLER_P(callable, get_closure)(callable, &fcc->calling_scope, &fcc->function_handler, &fcc->object_ptr TSRMLS_CC) == SUCCESS) {
fcc->called_scope = fcc->calling_scope;
+ if (!(fcc->function_handler->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (fcc->function_handler->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(fcc->called_scope != EG(scope))) {
+ return 0;
+ }
+ } else if ((fcc->function_handler->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fcc->function_handler), EG(scope)))) {
+ return 0;
+ }
+ }
@lstrojny
lstrojny added a note

Don’t we miss a return 0 here?

@reeze
reeze added a note

Hmm, I only see four conditions, they are closure function and other three possible ZEND_ACC_PPP* flags.
or else it's common function, but that wouldn't happen when get the function with get_closure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
if (callable_name) {
zend_class_entry *ce = Z_OBJCE_P(callable); /* TBFixed: what if it's overloaded? */
View
2 Zend/zend_closures.c
@@ -28,6 +28,7 @@
#include "zend_objects.h"
#include "zend_objects_API.h"
#include "zend_globals.h"
+#include "zend_compile.h"
#define ZEND_CLOSURE_PRINT_NAME "Closure object"
@@ -450,6 +451,7 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
closure->func = *func;
closure->func.common.prototype = NULL;
+ closure->func.common.fn_flags |= ZEND_ACC_CLOSURE;
if ((scope == NULL) && (this_ptr != NULL)) {
/* use dummy scope if we're binding an object without specifying a scope */
View
2 Zend/zend_closures.h
@@ -24,8 +24,6 @@
BEGIN_EXTERN_C()
-#define ZEND_INVOKE_FUNC_NAME "__invoke"
-
void zend_register_closure_ce(TSRMLS_D);
extern ZEND_API zend_class_entry *zend_ce_closure;
View
9 Zend/zend_compile.c
@@ -29,6 +29,7 @@
#include "tsrm_virtual_cwd.h"
#include "zend_multibyte.h"
#include "zend_language_scanner.h"
+#include "zend_closures.h"
#define CONSTANT_EX(op_array, op) \
(op_array)->literals[op].constant
@@ -1621,6 +1622,10 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
zend_error(E_WARNING, "The magic method __toString() must have public visibility and cannot be static");
}
+ } else if ((name_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1))) {
+ if (fn_flags & ZEND_ACC_STATIC) {
+ zend_error(E_WARNING, "The magic method __invoke() cannot be static");
+ }
}
} else {
char *class_lcname;
@@ -1677,6 +1682,10 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n
zend_error(E_WARNING, "The magic method __toString() must have public visibility and cannot be static");
}
CG(active_class_entry)->__tostring = (zend_function *) CG(active_op_array);
+ } else if ((name_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1))) {
+ if (fn_flags & ZEND_ACC_STATIC) {
+ zend_error(E_WARNING, "The magic method __invoke() cannot be static");
+ }
} else if (!(fn_flags & ZEND_ACC_STATIC)) {
CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC;
}
View
1 Zend/zend_compile.h
@@ -856,6 +856,7 @@ END_EXTERN_C()
#define ZEND_CALLSTATIC_FUNC_NAME "__callstatic"
#define ZEND_TOSTRING_FUNC_NAME "__tostring"
#define ZEND_AUTOLOAD_FUNC_NAME "__autoload"
+#define ZEND_INVOKE_FUNC_NAME "__invoke"
/* The following constants may be combined in CG(compiler_options)
* to change the default compiler behavior */
View
11 Zend/zend_vm_def.h
@@ -2673,6 +2673,17 @@ ZEND_VM_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST|TMP|VAR|CV)
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
Z_OBJ_HANDLER_P(function_name, get_closure)(function_name, &call->called_scope, &call->fbc, &call->object TSRMLS_CC) == SUCCESS) {
+ if (!(call->fbc->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (call->fbc->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(call->called_scope != EG(scope))) {
+ zend_error_noreturn(E_ERROR, "Call to private %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ } else if ((call->fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(call->fbc), EG(scope)))) {
+ zend_error_noreturn(E_ERROR, "Call to protected %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ }
+ }
if (call->object) {
Z_ADDREF_P(call->object);
}
View
44 Zend/zend_vm_execute.h
@@ -1256,6 +1256,17 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
Z_OBJ_HANDLER_P(function_name, get_closure)(function_name, &call->called_scope, &call->fbc, &call->object TSRMLS_CC) == SUCCESS) {
+ if (!(call->fbc->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (call->fbc->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(call->called_scope != EG(scope))) {
+ zend_error_noreturn(E_ERROR, "Call to private %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ } else if ((call->fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(call->fbc), EG(scope)))) {
+ zend_error_noreturn(E_ERROR, "Call to protected %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ }
+ }
if (call->object) {
Z_ADDREF_P(call->object);
}
@@ -1583,6 +1594,17 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_TMP_HANDLER(ZEND_OPCODE_H
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
Z_OBJ_HANDLER_P(function_name, get_closure)(function_name, &call->called_scope, &call->fbc, &call->object TSRMLS_CC) == SUCCESS) {
+ if (!(call->fbc->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (call->fbc->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(call->called_scope != EG(scope))) {
+ zend_error_noreturn(E_ERROR, "Call to private %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ } else if ((call->fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(call->fbc), EG(scope)))) {
+ zend_error_noreturn(E_ERROR, "Call to protected %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ }
+ }
if (call->object) {
Z_ADDREF_P(call->object);
}
@@ -1770,6 +1792,17 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_VAR_HANDLER(ZEND_OPCODE_H
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
Z_OBJ_HANDLER_P(function_name, get_closure)(function_name, &call->called_scope, &call->fbc, &call->object TSRMLS_CC) == SUCCESS) {
+ if (!(call->fbc->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (call->fbc->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(call->called_scope != EG(scope))) {
+ zend_error_noreturn(E_ERROR, "Call to private %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ } else if ((call->fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(call->fbc), EG(scope)))) {
+ zend_error_noreturn(E_ERROR, "Call to protected %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ }
+ }
if (call->object) {
Z_ADDREF_P(call->object);
}
@@ -1995,6 +2028,17 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_CV_HANDLER(ZEND_OPCODE_HA
EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT) &&
Z_OBJ_HANDLER_P(function_name, get_closure) &&
Z_OBJ_HANDLER_P(function_name, get_closure)(function_name, &call->called_scope, &call->fbc, &call->object TSRMLS_CC) == SUCCESS) {
+ if (!(call->fbc->common.fn_flags & (ZEND_ACC_PUBLIC | ZEND_ACC_CLOSURE))) {
+ if (call->fbc->common.fn_flags & ZEND_ACC_PRIVATE) {
+ if (UNEXPECTED(call->called_scope != EG(scope))) {
+ zend_error_noreturn(E_ERROR, "Call to private %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ } else if ((call->fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
+ if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(call->fbc), EG(scope)))) {
+ zend_error_noreturn(E_ERROR, "Call to protected %s::__invoke() from context '%s'", Z_OBJCE_P(function_name)->name, EG(scope) ? EG(scope)->name : "");
+ }
+ }
+ }
if (call->object) {
Z_ADDREF_P(call->object);
}
Something went wrong with that request. Please try again.