Skip to content

Instantly share code, notes, and snippets.

@thekid
Last active August 29, 2015 14:07
Show Gist options
  • Select an option

  • Save thekid/4f68e84e44773a88cd99 to your computer and use it in GitHub Desktop.

Select an option

Save thekid/4f68e84e44773a88cd99 to your computer and use it in GitHub Desktop.
Patch for PHP PR #847 - Catchable "Call to a member function bar() on a non-object"
diff --git a/NEWS b/NEWS
index c348efe..2f8883b 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,8 @@ PHP NEWS
. Update the MIME type list from the one shipped by Apache HTTPD. (Adam)
- Core:
+ . Implemented the RFC `Catchable "Call to a member function bar() on a
+ non-object"` (Timm)
. Added PHP_INT_MIN constant. (Andrea)
. Added Closure::apply() method. (Andrea)
. Implemented FR #38409 (parse_ini_file() looses the type of booleans). (Tjerk)
diff --git a/Zend/tests/dereference_002.phpt b/Zend/tests/dereference_002.phpt
index da13dec..cc0f27d 100644
--- a/Zend/tests/dereference_002.phpt
+++ b/Zend/tests/dereference_002.phpt
@@ -76,4 +76,4 @@ NULL
Notice: Undefined offset: 3 in %s on line %d
-Fatal error: Call to a member function bar() on null in %s on line %d
+Catchable fatal error: Call to a member function bar() on null in %s on line %d
diff --git a/Zend/tests/methods-on-non-objects-args-catch.phpt b/Zend/tests/methods-on-non-objects-args-catch.phpt
new file mode 100644
index 0000000..853d2d5
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-args-catch.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Catch method calls on non-objects raise recoverable errors
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+var_dump($x->method(1, 2, 3));
+echo "Alive\n";
+?>
+--EXPECTF--
+
+int(4096)
+string(%d) "Call to a member function method() on null"
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-array-access.phpt b/Zend/tests/methods-on-non-objects-array-access.phpt
new file mode 100755
index 0000000..be87457
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-array-access.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Catch method calls on non-objects inside array access
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+$a= [null => 'OK'];
+var_dump($a[$x->method()]);
+echo "Alive\n";
+?>
+--EXPECTF--
+int(4096)
+string(%d) "Call to a member function method() on null"
+string(2) "OK"
+Alive
\ No newline at end of file
diff --git a/Zend/tests/methods-on-non-objects-array-creation.phpt b/Zend/tests/methods-on-non-objects-array-creation.phpt
new file mode 100755
index 0000000..74cbb9c
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-array-creation.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Catch method calls on non-objects inside array creation
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+var_dump([$x->method() => 'OK']);
+var_dump([$x->method(), $x->method(), $x->method()]);
+echo "Alive\n";
+?>
+--EXPECTF--
+int(4096)
+string(%d) "Call to a member function method() on null"
+array(1) {
+ [""]=>
+ string(2) "OK"
+}
+int(4096)
+string(%d) "Call to a member function method() on null"
+int(4096)
+string(%d) "Call to a member function method() on null"
+int(4096)
+string(%d) "Call to a member function method() on null"
+array(3) {
+ [0]=>
+ NULL
+ [1]=>
+ NULL
+ [2]=>
+ NULL
+}
+Alive
\ No newline at end of file
diff --git a/Zend/tests/methods-on-non-objects-as-arg.phpt b/Zend/tests/methods-on-non-objects-as-arg.phpt
new file mode 100644
index 0000000..13b83cb
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-as-arg.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Catch method calls on non-objects as argument
+--FILE--
+<?php
+function nesting() {
+ return func_get_args();
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+var_dump(nesting($x->method()));
+var_dump(nesting(nesting($x->method())));
+var_dump(nesting($x->method(nesting($x->method()))));
+var_dump(nesting($x->method(), $x->method()));
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+array(1) {
+ [0]=>
+ NULL
+}
+Called #2
+array(1) {
+ [0]=>
+ array(1) {
+ [0]=>
+ NULL
+ }
+}
+Called #3
+array(1) {
+ [0]=>
+ NULL
+}
+Called #4
+Called #5
+array(2) {
+ [0]=>
+ NULL
+ [1]=>
+ NULL
+}
+Alive
diff --git a/Zend/tests/methods-on-non-objects-call-user-func.phpt b/Zend/tests/methods-on-non-objects-call-user-func.phpt
new file mode 100644
index 0000000..f76b7d4
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-call-user-func.phpt
@@ -0,0 +1,13 @@
+--TEST--
+call_user_func() in combination with "Call to a member function method() on a non-object"
+--FILE--
+<?php
+$comparator= null;
+var_dump(call_user_func([$comparator, 'compare'], 1, 2));
+echo "Alive\n";
+?>
+--EXPECTF--
+Warning: call_user_func() expects parameter 1 to be a valid callback, first array member is not a valid class name or object in %s on line %d
+NULL
+Alive
+
diff --git a/Zend/tests/methods-on-non-objects-catch.phpt b/Zend/tests/methods-on-non-objects-catch.phpt
new file mode 100644
index 0000000..bbfadac
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-catch.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Catch method calls on non-objects raise recoverable errors
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+var_dump($x->method());
+echo "Alive\n";
+?>
+--EXPECTF--
+
+int(4096)
+string(%d) "Call to a member function method() on null"
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-chain.phpt b/Zend/tests/methods-on-non-objects-chain.phpt
new file mode 100644
index 0000000..30da254
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-chain.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Catch chained method calls on non-objects raise recoverable errors
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+var_dump($x->method()->chained()->invocations());
+echo "Alive\n";
+?>
+--EXPECTF--
+
+int(4096)
+string(%d) "Call to a member function method() on null"
+int(4096)
+string(%d) "Call to a member function chained() on null"
+int(4096)
+string(%d) "Call to a member function invocations() on null"
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-concat.phpt b/Zend/tests/methods-on-non-objects-concat.phpt
new file mode 100755
index 0000000..4ff47aa
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-concat.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Catch method calls on non-objects inside concatenation
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+echo "Before\n".$x->method()."After\n";
+echo "Alive\n";
+?>
+--EXPECTF--
+int(4096)
+string(%d) "Call to a member function method() on null"
+Before
+After
+Alive
\ No newline at end of file
diff --git a/Zend/tests/methods-on-non-objects-dynamic.phpt b/Zend/tests/methods-on-non-objects-dynamic.phpt
new file mode 100644
index 0000000..11c5c9f
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-dynamic.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Catch method calls on non-objects with dynamic lookups
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$arr= [null, 'method'];
+var_dump($arr[0]->{$arr[1]}());
+
+$fun= function() { return null; };
+var_dump($fun()->{'method'}());
+
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-eval.phpt b/Zend/tests/methods-on-non-objects-eval.phpt
new file mode 100644
index 0000000..8ee494c
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-eval.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Indirect call inside eval to member function on non-object
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+var_dump(eval('$x->method(1, 2, 3);'));
+echo "Alive\n";
+?>
+--EXPECTF--
+
+int(4096)
+string(%d) "Call to a member function method() on null"
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-in-echo.phpt b/Zend/tests/methods-on-non-objects-in-echo.phpt
new file mode 100755
index 0000000..a0267c0
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-in-echo.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Catch method calls on non-objects inside echo
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$x= null;
+echo "Before\n", $x->method(), "After\n";
+echo "Alive\n";
+?>
+--EXPECTF--
+Before
+int(4096)
+string(%d) "Call to a member function method() on null"
+After
+Alive
\ No newline at end of file
diff --git a/Zend/tests/methods-on-non-objects-nested-calls-dyn.phpt b/Zend/tests/methods-on-non-objects-nested-calls-dyn.phpt
new file mode 100644
index 0000000..267104f
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls-dyn.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Catch method calls on non-objects with nested dynamic calls
+--FILE--
+<?php
+function nested() {
+ throw new LogicException('Should not be called');
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+
+$closure= function() { return nested(); };
+var_dump($x->method($closure()));
+
+$lambda= create_function('', 'return nested();');
+var_dump($x->method($lambda()));
+
+$func= 'nested';
+var_dump($x->method($func()));
+
+var_dump($x->method(call_user_func('nested')));
+
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Called #4
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested-calls-new.phpt b/Zend/tests/methods-on-non-objects-nested-calls-new.phpt
new file mode 100644
index 0000000..d8e3dd2
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls-new.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Catch method calls on non-objects with nested calls to new
+--FILE--
+<?php
+class Nesting {
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+var_dump($x->method(new Nesting()));
+var_dump($x->method(new Nesting(), new Nesting()));
+var_dump($x->method(new Nesting(new Nesting())));
+var_dump($x->method(new Nesting($x->nested())));
+var_dump($x->method(new Nesting($x->nested(new Nesting()))));
+var_dump($x->method($x->nested(new Nesting($x->deep()))));
+var_dump($x->method([new Nesting()]));
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Called #4
+NULL
+Called #5
+NULL
+Called #6
+NULL
+Called #7
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested-calls-nonct.phpt b/Zend/tests/methods-on-non-objects-nested-calls-nonct.phpt
new file mode 100644
index 0000000..a4529ee
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls-nonct.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Catch method calls on non-objects with nested non-compile-time-resolveable calls
+--FILE--
+<?php
+require('methods-on-non-objects-nested.inc');
+
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+
+var_dump($x->method(nested()));
+
+$closure= function() { return nested(); };
+var_dump($x->method($closure()));
+
+$lambda= create_function('', 'return nested();');
+var_dump($x->method($lambda()));
+
+$func= 'nested';
+var_dump($x->method($func()));
+
+var_dump($x->method(call_user_func('nested')));
+var_dump($x->method(call_user_func_array('nested', [])));
+
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Called #4
+NULL
+Called #5
+NULL
+Called #6
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested-calls-ns.phpt b/Zend/tests/methods-on-non-objects-nested-calls-ns.phpt
new file mode 100644
index 0000000..b16f579
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls-ns.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Catch method calls on non-objects with nested calls to namespaced functions with core counterparts
+--FILE--
+<?php namespace test;
+function strlen($str) {
+ throw new LogicException('Should not be called');
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+var_dump($x->method(strlen('Test')));
+var_dump($x->method(strlen('Test'), strlen('Test')));
+var_dump($x->method([strlen('Test')]));
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested-calls-static.phpt b/Zend/tests/methods-on-non-objects-nested-calls-static.phpt
new file mode 100644
index 0000000..64972ee
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls-static.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Catch method calls on non-objects with nested calls to static methods
+--FILE--
+<?php
+class Nesting {
+ static function nested() {
+ throw new LogicException('Should not be called');
+ }
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+$class= 'Nesting';
+$method= 'nested';
+var_dump($x->method(Nesting::nested()));
+var_dump($x->method($class::nested()));
+var_dump($x->method($class::{$method}()));
+var_dump($x->method([Nesting::nested()]));
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Called #4
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested-calls.phpt b/Zend/tests/methods-on-non-objects-nested-calls.phpt
new file mode 100644
index 0000000..b25aeaf
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested-calls.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Catch method calls on non-objects with nested function and method calls
+--FILE--
+<?php
+function nested() {
+ throw new LogicException('Should not be called');
+}
+set_error_handler(function($code, $message) {
+ static $i= 0;
+ echo 'Called #'.(++$i)."\n";
+});
+
+$x= null;
+var_dump($x->method(nested()));
+var_dump($x->method(nested(), nested()));
+var_dump($x->method(nested(nested())));
+var_dump($x->method($x->nested()));
+var_dump($x->method($x->nested(), $x->nested()));
+var_dump($x->method($x->nested(nested())));
+var_dump($x->method($x->nested($x->deep())));
+var_dump($x->method($x->nested(nested($x->deep()))));
+var_dump($x->method(nested(nested($x->nested()))));
+var_dump($x->method([nested()]));
+echo "Alive\n";
+?>
+--EXPECTF--
+Called #1
+NULL
+Called #2
+NULL
+Called #3
+NULL
+Called #4
+NULL
+Called #5
+NULL
+Called #6
+NULL
+Called #7
+NULL
+Called #8
+NULL
+Called #9
+NULL
+Called #10
+NULL
+Alive
diff --git a/Zend/tests/methods-on-non-objects-nested.inc b/Zend/tests/methods-on-non-objects-nested.inc
new file mode 100644
index 0000000..8511414
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-nested.inc
@@ -0,0 +1,4 @@
+<?php
+function nested() {
+ throw new LogicException('Should not be called');
+}
\ No newline at end of file
diff --git a/Zend/tests/methods-on-non-objects-return-unused.phpt b/Zend/tests/methods-on-non-objects-return-unused.phpt
new file mode 100644
index 0000000..ab2951f
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-return-unused.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Catch method calls on non-objects without using return value
+--INI--
+report_memleaks=1
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ echo "Caught\n";
+});
+
+$x= null;
+$x->method();
+echo "Alive\n";
+?>
+--EXPECTF--
+Caught
+Alive
diff --git a/Zend/tests/methods-on-non-objects-throw.phpt b/Zend/tests/methods-on-non-objects-throw.phpt
new file mode 100644
index 0000000..874f57c
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-throw.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Convert errors to exceptions from method calls on non-objects raise recoverable errors
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ echo "Raising...\n";
+ if (0 === strncmp('Call', $message, 4)) {
+ throw new BadMethodCallException($message);
+ } else if (0 === strncmp('Argument', $message, 8)) {
+ throw new InvalidArgumentException($message);
+ } else {
+ trigger_error($message, E_USER_ERROR);
+ }
+}, E_RECOVERABLE_ERROR);
+
+$x= null;
+echo "Calling...\n";
+try {
+ $x->method();
+} catch (BadMethodCallException $e) {
+ echo "Caught expected ", $e->getMessage(), "!\n";
+}
+echo "Alive\n";
+?>
+--EXPECTF--
+Calling...
+Raising...
+Caught expected Call to a member function method() on null!
+Alive
diff --git a/Zend/tests/methods-on-non-objects-usort.phpt b/Zend/tests/methods-on-non-objects-usort.phpt
new file mode 100644
index 0000000..760d481
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects-usort.phpt
@@ -0,0 +1,43 @@
+--TEST--
+usort() in combination with "Call to a member function method() on null"
+--FILE--
+<?php
+set_error_handler(function($code, $message) {
+ var_dump($code, $message);
+});
+
+$comparator= null;
+$list= [1, 4, 2, 3, -1];
+usort($list, function($a, $b) use ($comparator) {
+ return $comparator->compare($a, $b);
+});
+var_dump($list);
+echo "Alive\n";
+?>
+--EXPECTF--
+int(4096)
+string(43) "Call to a member function compare() on null"
+int(4096)
+string(43) "Call to a member function compare() on null"
+int(4096)
+string(43) "Call to a member function compare() on null"
+int(4096)
+string(43) "Call to a member function compare() on null"
+int(4096)
+string(43) "Call to a member function compare() on null"
+int(4096)
+string(43) "Call to a member function compare() on null"
+array(5) {
+ [0]=>
+ int(-1)
+ [1]=>
+ int(3)
+ [2]=>
+ int(2)
+ [3]=>
+ int(4)
+ [4]=>
+ int(1)
+}
+Alive
+
diff --git a/Zend/tests/methods-on-non-objects.phpt b/Zend/tests/methods-on-non-objects.phpt
new file mode 100644
index 0000000..01031b8
--- /dev/null
+++ b/Zend/tests/methods-on-non-objects.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Method calls on non-objects raise recoverable errors
+--FILE--
+<?php
+
+$x= null;
+$x->method();
+echo "Should not get here!\n";
+?>
+--EXPECTF--
+
+Catchable fatal error: Call to a member function method() on null in %s on line %d
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 6122429..fd0e11d 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -2139,11 +2139,48 @@ ZEND_VM_HANDLER(112, ZEND_INIT_METHOD_CALL, TMP|VAR|UNUSED|CV, CONST|TMP|VAR|CV)
object = GET_OP1_OBJ_ZVAL_PTR_DEREF(BP_VAR_R);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
FREE_OP2();
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ FREE_OP2();
+ FREE_OP1_IF_VAR();
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 54d2bb2..d932701 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -11003,11 +11003,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMP_CONST_HANDLER(ZEND_OPCO
object = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -12135,11 +12171,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMP_TMP_HANDLER(ZEND_OPCODE
object = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -13266,11 +13338,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMP_VAR_HANDLER(ZEND_OPCODE
object = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -14990,11 +15098,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_TMP_CV_HANDLER(ZEND_OPCODE_
object = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -18568,11 +18712,48 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_VAR_CONST_HANDLER(ZEND_OPCO
object = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zval_ptr_dtor_nogc(free_op1.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -20790,11 +20971,48 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_VAR_TMP_HANDLER(ZEND_OPCODE
object = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+ zval_ptr_dtor_nogc(free_op1.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -22979,11 +23197,48 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_VAR_VAR_HANDLER(ZEND_OPCODE
object = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+ zval_ptr_dtor_nogc(free_op1.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -26364,11 +26619,48 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_VAR_CV_HANDLER(ZEND_OPCODE_
object = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zval_ptr_dtor_nogc(free_op1.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -27945,11 +28237,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CONST_HANDLER(ZEND_O
object = _get_obj_zval_ptr_unused(execute_data TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -29308,11 +29636,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_TMP_HANDLER(ZEND_OPC
object = _get_obj_zval_ptr_unused(execute_data TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -30578,11 +30942,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_VAR_HANDLER(ZEND_OPC
object = _get_obj_zval_ptr_unused(execute_data TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -32358,11 +32758,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_UNUSED_CV_HANDLER(ZEND_OPCO
object = _get_obj_zval_ptr_unused(execute_data TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -35674,11 +36110,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CONST_HANDLER(ZEND_OPCOD
object = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -37729,11 +38201,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_
object = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -39789,11 +40297,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_VAR_HANDLER(ZEND_OPCODE_
object = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
zval_ptr_dtor_nogc(free_op2.var);
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+ zval_ptr_dtor_nogc(free_op2.var);
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
@@ -42900,11 +43444,47 @@ static int ZEND_FASTCALL ZEND_INIT_METHOD_CALL_SPEC_CV_CV_HANDLER(ZEND_OPCODE_H
object = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC);
if (UNEXPECTED(Z_TYPE_P(object) != IS_OBJECT)) {
+ uint32_t nesting = 1;
+
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
}
- zend_error_noreturn(E_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+ zend_error(E_RECOVERABLE_ERROR, "Call to a member function %s() on %s", Z_STRVAL_P(function_name), zend_get_type_by_const(Z_TYPE_P(object)));
+
+
+ if (EG(exception) != NULL) {
+ HANDLE_EXCEPTION();
+ }
+
+ /* No exception raised: Skip over arguments until fcall opcode with correct
+ * nesting level. Return NULL (except when return value unused) */
+ do {
+ opline++;
+ if (opline->opcode == ZEND_INIT_FCALL ||
+ opline->opcode == ZEND_INIT_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME ||
+ opline->opcode == ZEND_INIT_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
+ opline->opcode == ZEND_INIT_USER_CALL ||
+ opline->opcode == ZEND_NEW
+ ) {
+ nesting++;
+ } else if (opline->opcode == ZEND_DO_FCALL) {
+ nesting--;
+ }
+ } while (nesting);
+
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_NULL(EX_VAR(opline->result.var));
+ }
+
+ /* We've skipped EXT_FCALL_BEGIND, so also skip the ending opcode */
+ if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
+ opline++;
+ }
+ ZEND_VM_JMP(++opline);
}
obj = Z_OBJ_P(object);
diff --git a/ext/mysqli/tests/bug33491.phpt b/ext/mysqli/tests/bug33491.phpt
index 7e994bc..c83e126 100644
--- a/ext/mysqli/tests/bug33491.phpt
+++ b/ext/mysqli/tests/bug33491.phpt
@@ -1,7 +1,7 @@
--TEST--
Bug #33491 (extended mysqli class crashes when result is not object)
--INI--
-error_reporting=4095
+error_reporting=4096
--SKIPIF--
<?php
require_once('skipif.inc');
@@ -26,4 +26,4 @@ $DB->query_single('SELECT DATE()');
?>
--EXPECTF--
-Fatal error: Call to a member function fetch_row() on boolean in %sbug33491.php on line %d
+Catchable fatal error: Call to a member function fetch_row() on boolean in %sbug33491.php on line %d
diff --git a/ext/mysqli/tests/mysqli_change_user_new.phpt b/ext/mysqli/tests/mysqli_change_user_new.phpt
index e168957..06c721ac 100644
--- a/ext/mysqli/tests/mysqli_change_user_new.phpt
+++ b/ext/mysqli/tests/mysqli_change_user_new.phpt
@@ -41,4 +41,4 @@ Warning: mysqli_query(): MySQL server has gone away in %s on line %d
Warning: mysqli_query(): Error reading result set's header in %s on line %d
[003] [2006] MySQL server has gone away
-Fatal error: Call to a member function fetch_assoc() on %s in %s on line %d
\ No newline at end of file
+Catchable fatal error: Call to a member function fetch_assoc() on %s in %s on line %d
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt
index 224684b..0beca90 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_clear_error.phpt
@@ -93,4 +93,4 @@ array(1) {
Warning: PDO::prepare(): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_column' in 'field list' in %s on line %d
-Fatal error: Call to a member function execute() on boolean in %s on line %d
+Catchable fatal error: Call to a member function execute() on boolean in %s on line %d
diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt
index b7b0ab6..fc55547 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_prepare_native_mixed_style.phpt
@@ -36,4 +36,4 @@ Warning: PDO::prepare(): SQLSTATE[HY093]: Invalid parameter number: mixed named
Warning: PDO::prepare(): SQLSTATE[HY093]: Invalid parameter number in %s on line %d
-Fatal error: Call to a member function execute() on boolean in %s on line %d
+Catchable fatal error: Call to a member function execute() on boolean in %s on line %d
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt
index c16ce5d..36295e9 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_stmt_errorcode.phpt
@@ -56,4 +56,4 @@ Testing native PS...
Warning: PDO::prepare(): SQLSTATE[42S02]: Base table or view not found: 1146 Table '%s.ihopeitdoesnotexist' doesn't exist in %s on line %d
-Fatal error: Call to a member function execute() on boolean in %s on line %d
+Catchable fatal error: Call to a member function execute() on boolean in %s on line %d
diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt
index 52ecc91..278cd4e 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_stmt_multiquery.phpt
@@ -99,4 +99,4 @@ Native Prepared Statements...
Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your %s server version for the right syntax to use near '%SSELECT label FROM test ORDER BY id ASC LIMIT 1' at line %d in %s on line %d
-Fatal error: Call to a member function errorInfo() on boolean in %s on line %d
+Catchable fatal error: Call to a member function errorInfo() on boolean in %s on line %d
\ No newline at end of file
@thekid
Copy link
Author

thekid commented Oct 5, 2014

Commit message

Implemented the RFC `Catchable "Call to a member function bar()..."` 

Changed ZEND_INIT_METHOD_CALL to raise an E_RECOVERABLE_ERROR and handle
error handler outcome, returning NULL if error handler doesn't terminate
the script.

Adjusted outcome in MySQL / PDO_MySQL
Added various examples and tests
Added tests for indirect calls - see http://news.php.net/php.internals/73823
Added tests verifying method calls on non-objects work inside eval()
Added tests verifying calls work inside echo, concatenation and array access
Originally developed inside php/php-src#647

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment