Add support for is_greater(_or_equal) back

Support still requires a patch to the runtime,
but it's a really small patch now.
This commit is contained in:
Sara Golemon
2017-03-09 14:21:32 -08:00
parent 063bf71387
commit d270470099
5 changed files with 92 additions and 13 deletions

View File

@@ -44,6 +44,8 @@ The following overload methods are supported:
| $o != $arg | `__is_not_equal($arg)` |
| $o < $arg | `__is_smaller($arg)` |
| $o <= $arg | `__is_smaller_or_equal($arg)` |
| $o > $arg | `__is_greater($arg)` &#42; |
| $o >= $arg | `__is_greater_or_equal($arg)` &#42; |
| $o <=> $arg | `__cmp($arg)` |
| ++$o | `__pre_inc()` |
| $o++ | `__post_inc()` |
@@ -63,4 +65,4 @@ The following overload methods are supported:
| $o &= $arg | `__assign_bw_and($arg)` |
| $o ^= $arg | `__assign_bw_xor($arg)` |
&#42; - `__is_greater()` and `__is_greater_or_equal()` require a rebuild of the main PHP runtime using the [included patch](php7-is_greater.diff). Withtout this patch, `$a &gt; $b` is automatically remapped to `$b &lt; $a` by the engine.

View File

@@ -78,8 +78,16 @@ zval *get_zval_ptr_undef(zend_uchar op_type, znode_op op, zend_free_op *free_op,
UNARY_ASSIGN_OPS(X) \
BINARY_ASSIGN_OPS(X)
/* greater/greater-equal are encoded as smaller/smaller-equal
* so they require special handling
*/
#define GREATER_OPS(X) \
X(IS_SMALLER, __is_greater) \
X(IS_SMALLER_OR_EQUAL, __is_greater_or_equal)
#define X(op, meth) static zend_string *s_##meth;
ALL_OPS(X)
GREATER_OPS(X)
#undef X
/* {{{ operator_method_name */
@@ -96,13 +104,13 @@ ALL_OPS(X)
/* }}} */
/* {{{ operator_get_method */
static zend_bool operator_get_method(zend_uchar opcode, zval *obj,
static zend_bool operator_get_method(zend_string *method, zval *obj,
zend_fcall_info *fci,
zend_fcall_info_cache *fcc) {
memset(fci, 0, sizeof(zend_fcall_info));
fci->size = sizeof(zend_fcall_info);
fci->object = Z_OBJ_P(obj);
ZVAL_STR(&(fci->function_name), operator_method_name(opcode));
ZVAL_STR(&(fci->function_name), method);
if (!zend_is_callable_ex(&(fci->function_name), fci->object,
IS_CALLABLE_CHECK_SILENT | IS_CALLABLE_STRICT,
@@ -122,13 +130,28 @@ static zend_bool operator_get_method(zend_uchar opcode, zval *obj,
}
/* }}} */
/* {{{ operator_is_greater_op */
static inline zend_bool operator_is_greater_op(const zend_op *opline, zend_string **pmethod) {
if (opline->extended_value == 1) {
switch (opline->opcode) {
#define X(op, meth) \
case ZEND_##op: *pmethod = s_##meth; return 1;
GREATER_OPS(X)
#undef X
}
}
return 0;
}
/* }}} */
/* {{{ op_handler */
static int op_handler(zend_execute_data *execute_data) {
USE_OPLINE
zend_free_op free_op1 = NULL, free_op2 = NULL;
zval *op1, *op2;
zval *op1, *op2 = NULL;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
zend_string *method = operator_method_name(opline->opcode);
if (opline->op1_type == IS_UNUSED) {
/* Assign op */
@@ -137,23 +160,32 @@ static int op_handler(zend_execute_data *execute_data) {
op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
}
ZVAL_DEREF(op1);
if ((Z_TYPE_P(op1) != IS_OBJECT) ||
!operator_get_method(opline->opcode, op1, &fci, &fcc)) {
/* Not an overloaded call */
return ZEND_USER_OPCODE_DISPATCH;
}
fci.retval = EX_VAR(opline->result.var);
switch (opline->opcode) {
#define X(op, meth) \
case ZEND_##op:
BINARY_OPS(X)
BINARY_ASSIGN_OPS(X)
#undef X
fci.params = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
fci.param_count = 1;
op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
}
if (operator_is_greater_op(opline, &method)) {
zval *tmp = op1;
zend_free_op free_tmp = free_op1;
op1 = op2; op2 = tmp;
free_op1 = free_op2; free_op2 = free_tmp;
}
if ((Z_TYPE_P(op1) != IS_OBJECT) ||
!operator_get_method(method, op1, &fci, &fcc)) {
/* Not an overloaded call */
return ZEND_USER_OPCODE_DISPATCH;
}
fci.retval = EX_VAR(opline->result.var);
fci.params = op2;
fci.param_count = op2 ? 1 : 0;
if (FAILURE == zend_call_function(&fci, &fcc)) {
php_error(E_WARNING, "Failed calling %s::%s()", Z_OBJCE_P(op1)->name, Z_STRVAL(fci.function_name));
ZVAL_NULL(fci.retval);
@@ -168,6 +200,11 @@ BINARY_ASSIGN_OPS(X)
/* {{{ MINIT */
static PHP_MINIT_FUNCTION(operator) {
#define X(op, meth) \
s_##meth = zend_string_init(#meth, strlen(#meth), 1);
GREATER_OPS(X)
#undef X
#define X(op, meth) \
s_##meth = zend_string_init(#meth, strlen(#meth), 1); \
zend_set_user_opcode_handler(ZEND_##op, op_handler);

View File

@@ -30,7 +30,6 @@ http://pear.php.net/dtd/package-2.0.xsd">
<license uri="http://www.php.net/license">PHP</license>
<notes>
* Rewrote for PHP7, dropped PHP5 support
* Removed is_greater(_or_equal) support
* Removed pre/post inc/dec support for objects in object properties (this will come back eventually)
* Added pow, assign-pow, and spaceship operators
</notes>

13
php7-is_greater.diff Normal file
View File

@@ -0,0 +1,13 @@
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 88d7192..b797939 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -6716,7 +6716,7 @@ void zend_compile_greater(znode *result, zend_ast *ast) /* {{{ */
zend_emit_op_tmp(result,
ast->kind == ZEND_AST_GREATER ? ZEND_IS_SMALLER : ZEND_IS_SMALLER_OR_EQUAL,
- &right_node, &left_node);
+ &right_node, &left_node)->extended_value = 1;
}
/* }}} */

28
tests/compare2.phpt Normal file
View File

@@ -0,0 +1,28 @@
--TEST--
Extended comparison ops
--SKIPIF--
<?php if(!extension_loaded("operator")) print "skip";
--FILE--
<?php
class foo {
private $value;
function __is_greater($val) {
return $this->value > $val;
}
function __is_greater_or_equal($val) {
return $this->value >= $val;
}
function __construct($init) {
$this->value = $init;
}
}
$c = new foo(5);
var_dump($c > 5);
var_dump($c > 4);
var_dump($c >= 5);
var_dump($c >= 6);
--EXPECT--
bool(false)
bool(true)
bool(true)
bool(false)