diff --git a/NEWS b/NEWS index a7c1d4ef90593..184a787ea6598 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,8 @@ PDO_Firebird: logging. (Philip Prindeville) . Fixed bug #63217 (Constant numeric strings become integers when used as ArrayAccess offset). (Rudi Theunissen, Dmitry) + . Added user-defined object comparison. + (https://wiki.php.net/rfc/object-comparison) (Rudi Theunissen) - DOM: . Fixed bug #76285 (DOMDocument::formatOutput attribute sometimes ignored). diff --git a/UPGRADING b/UPGRADING index f616bf9cda308..fef4dcbba82d3 100644 --- a/UPGRADING +++ b/UPGRADING @@ -247,6 +247,30 @@ Core: are available under Linux, FreeBSD, Windows, Mac, SunOS, AIX and their derivatives. If no required timers are provided by a corresponding platform, the function returns false. + . Added two new magic methods: __compareTo and __equals. Classes may choose to + implement these to define relative natural ordering and equality, which + affects functions like in_array() and sort(), and comparison operators: + <, >, <=, >=, <=>, ==, !=. The behaviour of === and !== has not changed. + Classes that do not implement these methods will continue to be compared + according to the rules that were introduced in PHP 5. + (RFC: https://wiki.php.net/rfc/object-comparison) + + + +Changes to object comparison behaviour ++-------------------------------------- ++ ++* Classes may now implement a Comparable interface to override how objects are ++ compared to other values. If implemented, the class must implement a public ++ compareTo() method with the signature: ++ ++ public function compareTo($other) ++ ++ This method must return a negative integer to signal that the object is less ++ than $other, zero to signal that the object is equal to $other, or a positive ++ integer to signal that the object is greater than $other. ++ ++ Objects that don't implement Comparable will continue to be compared ++ according to the rules used in PHP 5. Date: . Added the DateTime::createFromImmutable() method, which mirrors diff --git a/Zend/tests/comparisons/__compareTo/Comparable.inc b/Zend/tests/comparisons/__compareTo/Comparable.inc new file mode 100644 index 0000000000000..453fa3ea7a051 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/Comparable.inc @@ -0,0 +1,31 @@ +decoy = $value * -1; + $this->value = $value; + } + + public function __compareTo($other) { + if ($other instanceof self) { + return $this->value <=> $other->value; + } + + /** + * Allow comparing to scalar numeric values. + */ + if (is_numeric($other)) { + return $this->value <=> $other; + } + + throw new Exception("Failed to compare"); + } +} diff --git a/Zend/tests/comparisons/__compareTo/compare-array-func-array-keys.phpt b/Zend/tests/comparisons/__compareTo/compare-array-func-array-keys.phpt new file mode 100644 index 0000000000000..8230009f14c1e --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-array-func-array-keys.phpt @@ -0,0 +1,121 @@ +--TEST-- +__compareTo: Called by array_keys which has equality semantics (searching) +--FILE-- + $a, + 'b' => $b, + 'c' => $c, +]; + +var_dump(array_keys($array, $a, $strict = false)); // a +var_dump(array_keys($array, $b, $strict = false)); // b +var_dump(array_keys($array, $c, $strict = false)); // c + +var_dump(array_keys($array, $a, $strict = true)); // a +var_dump(array_keys($array, $b, $strict = true)); // b +var_dump(array_keys($array, $c, $strict = true)); // c + +/* Found, because Comparable::__compareTo returned 0 */ +var_dump(array_keys($array, new Comparable(1), $strict = false)); // a +var_dump(array_keys($array, new Comparable(2), $strict = false)); // b +var_dump(array_keys($array, new Comparable(3), $strict = false)); // c + +/* Not found because strict comparison doesn't call __compareTo */ +var_dump(array_keys($array, new Comparable(1), $strict = true)); +var_dump(array_keys($array, new Comparable(2), $strict = true)); +var_dump(array_keys($array, new Comparable(3), $strict = true)); + +/* Found, because Comparable::__compareTo returned 0 and we haven't implemented __equals */ +var_dump(array_keys($array, 1, $strict = false)); // a +var_dump(array_keys($array, 2, $strict = false)); // b +var_dump(array_keys($array, 3, $strict = false)); // c + +/* Not found because strict comparison doesn't call __compareTo */ +var_dump(array_keys($array, 1, $strict = true)); +var_dump(array_keys($array, 2, $strict = true)); +var_dump(array_keys($array, 3, $strict = true)); + +/* Not found */ +var_dump(array_keys($array, new Comparable(4), $strict = true)); +var_dump(array_keys($array, new Comparable(5), $strict = true)); + +var_dump(array_keys($array, new Comparable(4), $strict = false)); +var_dump(array_keys($array, new Comparable(5), $strict = false)); + +?> +--EXPECT-- +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(0) { +} +array(0) { +} +array(0) { +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} diff --git a/Zend/tests/comparisons/__compareTo/compare-array-func-array-search.phpt b/Zend/tests/comparisons/__compareTo/compare-array-func-array-search.phpt new file mode 100644 index 0000000000000..8f22231f15694 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-array-func-array-search.phpt @@ -0,0 +1,71 @@ +--TEST-- +__compareTo: Called by array_search which has equality semantics (searching) +--FILE-- + +--EXPECT-- +int(0) +int(1) +int(2) +int(0) +int(1) +int(2) +int(0) +int(1) +int(2) +bool(false) +bool(false) +bool(false) +int(0) +int(1) +int(2) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/comparisons/__compareTo/compare-array-func-in-array.phpt b/Zend/tests/comparisons/__compareTo/compare-array-func-in-array.phpt new file mode 100644 index 0000000000000..01d1d8d742646 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-array-func-in-array.phpt @@ -0,0 +1,71 @@ +--TEST-- +__compareTo: Called by in_array which has equality semantics (searching) +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/comparisons/__compareTo/compare-array-sort.phpt b/Zend/tests/comparisons/__compareTo/compare-array-sort.phpt new file mode 100644 index 0000000000000..b1ef451f4a728 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-array-sort.phpt @@ -0,0 +1,38 @@ +--TEST-- +__compareTo: Called by array functions with ordering semantics (sorting) +--FILE-- + +--EXPECT-- +object(Comparable)#2 (2) { + ["decoy":protected]=> + int(-1) + ["value":protected]=> + int(1) +} +object(Comparable)#3 (2) { + ["decoy":protected]=> + int(-2) + ["value":protected]=> + int(2) +} +object(Comparable)#1 (2) { + ["decoy":protected]=> + int(-3) + ["value":protected]=> + int(3) +} diff --git a/Zend/tests/comparisons/__compareTo/compare-as-equality-fallback.phpt b/Zend/tests/comparisons/__compareTo/compare-as-equality-fallback.phpt new file mode 100644 index 0000000000000..63d8a8d0821bc --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-as-equality-fallback.phpt @@ -0,0 +1,49 @@ +--TEST-- +__compareTo: Should be called when comparing for equality +--FILE-- + +--EXPECT-- +A::__compareTo +A::__compareTo +B::__equals +B::__equals +B::__equals +B::__equals +B::__equals +B::__equals diff --git a/Zend/tests/comparisons/__compareTo/compare-inheritance.phpt b/Zend/tests/comparisons/__compareTo/compare-inheritance.phpt new file mode 100644 index 0000000000000..d19fb605c723c --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-inheritance.phpt @@ -0,0 +1,78 @@ +--TEST-- +__compareTo: Inheritance of magic method +--FILE-- + new InheritedComparable(0)); // 1 +var_dump(new Comparable(1) <=> new InheritedComparable(1)); // 0 +var_dump(new Comparable(1) <=> new InheritedComparable(2)); // -1 + +echo "\n"; + +var_dump(new InheritedComparable(1) <=> new Comparable(0)); // 1 +var_dump(new InheritedComparable(1) <=> new Comparable(1)); // 0 +var_dump(new InheritedComparable(1) <=> new Comparable(2)); // -1 + +echo "\n"; + +/** + * Compare against an extending object that overrides the base behaviour. + */ +var_dump(new Comparable(1) <=> new ReversedComparable(0)); // 1 +var_dump(new Comparable(1) <=> new ReversedComparable(1)); // 0 +var_dump(new Comparable(1) <=> new ReversedComparable(2)); // -1 + +echo "\n"; + +/** + * These will use ReversedComparable's __compareTo + */ +var_dump(new ReversedComparable(1) <=> new Comparable(0)); // -1 +var_dump(new ReversedComparable(1) <=> new Comparable(1)); // 0 +var_dump(new ReversedComparable(1) <=> new Comparable(2)); // 1 + + +?> +--EXPECT-- +int(1) +int(0) +int(-1) + +int(1) +int(0) +int(-1) + +int(1) +int(0) +int(-1) + +Reverse! +int(-1) +Reverse! +int(0) +Reverse! +int(1) diff --git a/Zend/tests/comparisons/__compareTo/compare-method-header.phpt b/Zend/tests/comparisons/__compareTo/compare-method-header.phpt new file mode 100644 index 0000000000000..134a1da231fb6 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-method-header.phpt @@ -0,0 +1,32 @@ +--TEST-- +__compareTo: Magic method must be public and non-static +--FILE-- + +--EXPECTF-- +Warning: The magic method __compareTo() must have public visibility and cannot be static in %s on line %d + +Warning: The magic method __compareTo() must have public visibility and cannot be static in %s on line %d + +Warning: The magic method __compareTo() must have public visibility and cannot be static in %s on line %d diff --git a/Zend/tests/comparisons/__compareTo/compare-normalize-to-int.phpt b/Zend/tests/comparisons/__compareTo/compare-normalize-to-int.phpt new file mode 100644 index 0000000000000..2af0ea88e0859 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-normalize-to-int.phpt @@ -0,0 +1,26 @@ +--TEST-- +__compareTo: Comparison always returns -1, 0, or 1 +--FILE-- + 1); +var_dump(new class { public function __compareTo($other) { return -2; } } <=> 1); +var_dump(new class { public function __compareTo($other) { return 0.5; } } <=> 1); // Not truncated +var_dump(new class { public function __compareTo($other) { return -0.5; } } <=> 1); // Not truncated +var_dump(new class { public function __compareTo($other) { return 'a'; } } <=> 1); +var_dump(new class { public function __compareTo($other) { return true; } } <=> 1); +var_dump(new class { public function __compareTo($other) { return new stdClass; } } <=> 1); // Attempt to convert +var_dump(new class { public function __compareTo($other) { } } <=> 1); + +?> +--EXPECTF-- +int(1) +int(-1) +int(1) +int(-1) +int(0) +int(1) + +Notice: Object of class stdClass could not be converted to int in %s on line %d +int(1) +int(0) diff --git a/Zend/tests/comparisons/__compareTo/compare-objects-handler.phpt b/Zend/tests/comparisons/__compareTo/compare-objects-handler.phpt new file mode 100644 index 0000000000000..fe51ad7195f4c --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-objects-handler.phpt @@ -0,0 +1,40 @@ +--TEST-- +__compareTo: Objects implementing compare_objects handler are unaffected +--SKIPIF-- + +--FILE-- + $a); // 0 +var_dump($a <=> $b); // 0 +var_dump($a <=> $c); // -1 + +var_dump($b <=> $a); // 0 +var_dump($b <=> $b); // 0 +var_dump($b <=> $c); // -1 + +var_dump($c <=> $a); // 1 +var_dump($c <=> $b); // 1 +var_dump($c <=> $c); // 0 + +?> +--EXPECT-- +int(0) +int(0) +int(-1) +int(0) +int(0) +int(-1) +int(1) +int(1) +int(0) diff --git a/Zend/tests/comparisons/__compareTo/compare-operators.phpt b/Zend/tests/comparisons/__compareTo/compare-operators.phpt new file mode 100644 index 0000000000000..1bb8cb262fb7f --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-operators.phpt @@ -0,0 +1,71 @@ +--TEST-- +__compareTo: Basic comparison operator behaviour +--FILE-- + new Comparable(0)); // true +var_dump(new Comparable(1) > new Comparable(1)); // false +var_dump(new Comparable(1) > new Comparable(2)); // false + +var_dump(new Comparable(1) <= new Comparable(0)); // false +var_dump(new Comparable(1) <= new Comparable(1)); // true +var_dump(new Comparable(1) <= new Comparable(2)); // true + +var_dump(new Comparable(1) >= new Comparable(0)); // true +var_dump(new Comparable(1) >= new Comparable(1)); // true +var_dump(new Comparable(1) >= new Comparable(2)); // false + +var_dump(new Comparable(1) == new Comparable(0)); // false +var_dump(new Comparable(1) == new Comparable(1)); // true +var_dump(new Comparable(1) == new Comparable(2)); // false + +var_dump(new Comparable(1) != new Comparable(0)); // true +var_dump(new Comparable(1) != new Comparable(1)); // false +var_dump(new Comparable(1) != new Comparable(2)); // true + +var_dump(new Comparable(1) === new Comparable(0)); // false +var_dump(new Comparable(1) === new Comparable(1)); // false +var_dump(new Comparable(1) === new Comparable(2)); // false + +var_dump(new Comparable(1) !== new Comparable(0)); // true +var_dump(new Comparable(1) !== new Comparable(1)); // true +var_dump(new Comparable(1) !== new Comparable(2)); // true + +var_dump(new Comparable(1) <=> new Comparable(0)); // 1 +var_dump(new Comparable(1) <=> new Comparable(1)); // 0 +var_dump(new Comparable(1) <=> new Comparable(2)); // -1 + +?> +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +int(1) +int(0) +int(-1) diff --git a/Zend/tests/comparisons/__compareTo/compare-rhs.phpt b/Zend/tests/comparisons/__compareTo/compare-rhs.phpt new file mode 100644 index 0000000000000..b476becc58f2c --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-rhs.phpt @@ -0,0 +1,28 @@ +--TEST-- +__compareTo: RHS operator should be considered even if LHS doesn't implement +--FILE-- + new B); +var_dump(new B <=> new A); + +?> +--EXPECT-- +Comparing! +int(-1) +Comparing! +int(1) diff --git a/Zend/tests/comparisons/__compareTo/compare-throw-exception.phpt b/Zend/tests/comparisons/__compareTo/compare-throw-exception.phpt new file mode 100644 index 0000000000000..d13141ba6f554 --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-throw-exception.phpt @@ -0,0 +1,26 @@ +--TEST-- +__compareTo: Throwing exception is caught +--FILE-- + new BrokenComparison; + +?> +--EXPECTF-- + +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s: BrokenComparison->__compareTo(Object(BrokenComparison)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/comparisons/__compareTo/compare-to-null.phpt b/Zend/tests/comparisons/__compareTo/compare-to-null.phpt new file mode 100644 index 0000000000000..1432d0ad2b4fe --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-to-null.phpt @@ -0,0 +1,82 @@ +--TEST-- +__compareTo: Compare against NULL +--FILE-- +value = $value; + } + + public function __compareTo($other) + { + echo "Comparing!\n"; + return $this->value <=> $other; + } +} + +/** + * This should call __compareTo. + */ +var_dump(new Comparable(1) <=> null); + +/** + * This should also call __compareTo even though we're using the RHS + */ +var_dump(null <=> new Comparable(1)); + +/** + * We're doing a non-strict comparison between 0 and NULL here. + */ +var_dump(null <=> new Comparable(0)); +var_dump(new Comparable(0) <=> null); + +echo "\n"; + +/** + * Check that default behaviour still works as expected. + */ +var_dump(new stdClass == null); +var_dump(new stdClass >= null); +var_dump(new stdClass <= null); +var_dump(new stdClass < null); +var_dump(new stdClass > null); +var_dump(new stdClass <=> null); + +echo "\n"; + +var_dump(null == new stdClass); +var_dump(null >= new stdClass); +var_dump(null <= new stdClass); +var_dump(null < new stdClass); +var_dump(null > new stdClass); +var_dump(null <=> new stdClass); + +?> +--EXPECT-- +Comparing! +int(1) +Comparing! +int(-1) +Comparing! +int(0) +Comparing! +int(0) + +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +int(1) + +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +int(-1) diff --git a/Zend/tests/comparisons/__compareTo/compare-to-scalar.phpt b/Zend/tests/comparisons/__compareTo/compare-to-scalar.phpt new file mode 100644 index 0000000000000..1d4cca41b45bb --- /dev/null +++ b/Zend/tests/comparisons/__compareTo/compare-to-scalar.phpt @@ -0,0 +1,33 @@ +--TEST-- +__compareTo: Compare against scalar values +--FILE-- + 0); // 1 +var_dump(new Comparable(1) <=> 1); // 0 +var_dump(new Comparable(1) <=> '1'); // 0 +var_dump(new Comparable(1) <=> 1.0); // 0 +var_dump(new Comparable(1) <=> 1.5); // -1 + +/** + * We didn't define comparison against non-numeric strings. + */ +var_dump(new Comparable(1) <=> 'a'); + +?> +--EXPECTF-- +int(1) +int(0) +int(0) +int(0) +int(-1) + +Fatal error: Uncaught Exception: Failed to compare in %s:%d +Stack trace: +#0 %s: Comparable->__compareTo('a') +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/comparisons/__equals/Equatable.inc b/Zend/tests/comparisons/__equals/Equatable.inc new file mode 100644 index 0000000000000..3c75f3cb0b53b --- /dev/null +++ b/Zend/tests/comparisons/__equals/Equatable.inc @@ -0,0 +1,28 @@ +decoy = $value * -1; + $this->value = $value; + } + + public function __equals($other): bool { + if ($other instanceof self) { + return $this->value == $other->value; + } + + if (is_numeric($other)) { + return $this->value == $other; + } + + throw new Exception("Failed to equate"); + } +} diff --git a/Zend/tests/comparisons/__equals/equals-array-func-array-keys.phpt b/Zend/tests/comparisons/__equals/equals-array-func-array-keys.phpt new file mode 100644 index 0000000000000..ff5502299f7f1 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-array-func-array-keys.phpt @@ -0,0 +1,121 @@ +--TEST-- +__equals: Called by array_keys which has equality semantics (searching) +--FILE-- + $a, + 'b' => $b, + 'c' => $c, +]; + +var_dump(array_keys($array, $a, $strict = false)); // a +var_dump(array_keys($array, $b, $strict = false)); // b +var_dump(array_keys($array, $c, $strict = false)); // c + +var_dump(array_keys($array, $a, $strict = true)); // a +var_dump(array_keys($array, $b, $strict = true)); // b +var_dump(array_keys($array, $c, $strict = true)); // c + +/* Found, because Equatable::__equals returned TRUE */ +var_dump(array_keys($array, new Equatable(1), $strict = false)); // a +var_dump(array_keys($array, new Equatable(2), $strict = false)); // b +var_dump(array_keys($array, new Equatable(3), $strict = false)); // c + +/* Not found because strict comparison doesn't call __equals */ +var_dump(array_keys($array, new Equatable(1), $strict = true)); +var_dump(array_keys($array, new Equatable(2), $strict = true)); +var_dump(array_keys($array, new Equatable(3), $strict = true)); + +/* Found, because Equatable::__equals returned TRUE */ +var_dump(array_keys($array, 1, $strict = false)); // a +var_dump(array_keys($array, 2, $strict = false)); // b +var_dump(array_keys($array, 3, $strict = false)); // c + +/* Not found because strict comparison doesn't call __equals */ +var_dump(array_keys($array, 1, $strict = true)); +var_dump(array_keys($array, 2, $strict = true)); +var_dump(array_keys($array, 3, $strict = true)); + +/* Not found */ +var_dump(array_keys($array, new Equatable(4), $strict = true)); +var_dump(array_keys($array, new Equatable(5), $strict = true)); + +var_dump(array_keys($array, new Equatable(4), $strict = false)); +var_dump(array_keys($array, new Equatable(5), $strict = false)); + +?> +--EXPECT-- +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(0) { +} +array(0) { +} +array(0) { +} +array(1) { + [0]=> + string(1) "a" +} +array(1) { + [0]=> + string(1) "b" +} +array(1) { + [0]=> + string(1) "c" +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} +array(0) { +} diff --git a/Zend/tests/comparisons/__equals/equals-array-func-array-search.phpt b/Zend/tests/comparisons/__equals/equals-array-func-array-search.phpt new file mode 100644 index 0000000000000..731e71949e497 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-array-func-array-search.phpt @@ -0,0 +1,71 @@ +--TEST-- +__equals: Called by array_search which has equality semantics (searching) +--FILE-- + +--EXPECT-- +int(0) +int(1) +int(2) +int(0) +int(1) +int(2) +int(0) +int(1) +int(2) +bool(false) +bool(false) +bool(false) +int(0) +int(1) +int(2) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/comparisons/__equals/equals-array-func-in-array.phpt b/Zend/tests/comparisons/__equals/equals-array-func-in-array.phpt new file mode 100644 index 0000000000000..f1dc424d2eefa --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-array-func-in-array.phpt @@ -0,0 +1,71 @@ +--TEST-- +__equals: Called by in_array which has equality semantics (searching) +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/comparisons/__equals/equals-array-sort.phpt b/Zend/tests/comparisons/__equals/equals-array-sort.phpt new file mode 100644 index 0000000000000..e0d3673d57704 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-array-sort.phpt @@ -0,0 +1,42 @@ +--TEST-- +__equals: Not called by array functions with ordering semantics (sorting) +--FILE-- + +--EXPECT-- +object(Equatable)#3 (2) { + ["decoy":protected]=> + int(-3) + ["value":protected]=> + int(3) +} +object(Equatable)#2 (2) { + ["decoy":protected]=> + int(-2) + ["value":protected]=> + int(2) +} +object(Equatable)#1 (2) { + ["decoy":protected]=> + int(-1) + ["value":protected]=> + int(1) +} diff --git a/Zend/tests/comparisons/__equals/equals-compare-objects-handler.phpt b/Zend/tests/comparisons/__equals/equals-compare-objects-handler.phpt new file mode 100644 index 0000000000000..fdc8de36a582a --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-compare-objects-handler.phpt @@ -0,0 +1,41 @@ +--TEST-- +__equals: Objects implementing compare_objects handler are unaffected +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) + diff --git a/Zend/tests/comparisons/__equals/equals-disallow-compare.phpt b/Zend/tests/comparisons/__equals/equals-disallow-compare.phpt new file mode 100644 index 0000000000000..0cc7771ec7e12 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-disallow-compare.phpt @@ -0,0 +1,50 @@ +--TEST-- +__equals: Disallow comparison with an exception, but allow equality checks +--FILE-- +value = $value; + } + + public function __compareTo($other) + { + throw new Exception("This object does not support comparison"); + } + + public function __equals($other) + { + return $other instanceof self && $this->value === $other->value; + } +} + +/** + * This should call __equals which returns FALSE. + */ +var_dump(new Test(1) == new Test(2)); + +/** + * This should call __equals which returns TRUE. + */ +var_dump(new Test(2) == new Test(2)); + +/** + * This should call __compareTo which is explicitly unsupported. + */ +new Test(1) <=> new Test(1); + +?> +--EXPECTF-- +bool(false) +bool(true) + +Fatal error: Uncaught Exception: %s:%d +Stack trace: +#0 %s: Test->__compareTo(Object(Test)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/comparisons/__equals/equals-inheritance.phpt b/Zend/tests/comparisons/__equals/equals-inheritance.phpt new file mode 100644 index 0000000000000..ae5861cada11e --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-inheritance.phpt @@ -0,0 +1,78 @@ +--TEST-- +__equals: Inheritance of magic method +--FILE-- + +--EXPECT-- +bool(false) +bool(true) +bool(false) + +bool(false) +bool(true) +bool(false) + +bool(false) +bool(true) +bool(false) + +Reverse! +bool(true) +Reverse! +bool(false) +Reverse! +bool(true) diff --git a/Zend/tests/comparisons/__equals/equals-method-header.phpt b/Zend/tests/comparisons/__equals/equals-method-header.phpt new file mode 100644 index 0000000000000..f640035ec2fd8 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-method-header.phpt @@ -0,0 +1,32 @@ +--TEST-- +__equals: Magic method must be public and non-static +--FILE-- + +--EXPECTF-- +Warning: The magic method __equals() must have public visibility and cannot be static in %s on line %d + +Warning: The magic method __equals() must have public visibility and cannot be static in %s on line %d + +Warning: The magic method __equals() must have public visibility and cannot be static in %s on line %d diff --git a/Zend/tests/comparisons/__equals/equals-normalize-to-bool.phpt b/Zend/tests/comparisons/__equals/equals-normalize-to-bool.phpt new file mode 100644 index 0000000000000..0184a2adfb3de --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-normalize-to-bool.phpt @@ -0,0 +1,26 @@ +--TEST-- +__equals: Converts to boolean on return +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) diff --git a/Zend/tests/comparisons/__equals/equals-not-called-for-ordering.phpt b/Zend/tests/comparisons/__equals/equals-not-called-for-ordering.phpt new file mode 100644 index 0000000000000..846e672412830 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-not-called-for-ordering.phpt @@ -0,0 +1,19 @@ +--TEST-- +__equals: Should not be called when comparing for ordering +--FILE-- + new A; +new A >= new A; +new A <= new A; +new A <=> new A; +?> +--EXPECT-- diff --git a/Zend/tests/comparisons/__equals/equals-operators.phpt b/Zend/tests/comparisons/__equals/equals-operators.phpt new file mode 100644 index 0000000000000..bde6c33eae286 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-operators.phpt @@ -0,0 +1,73 @@ +--TEST-- +__equals: Basic equality operator behaviour +--FILE-- + new Equatable(0)); // false +var_dump(new Equatable(1) > new Equatable(1)); // false +var_dump(new Equatable(1) > new Equatable(2)); // true + +var_dump(new Equatable(1) <= new Equatable(0)); // true +var_dump(new Equatable(1) <= new Equatable(1)); // true +var_dump(new Equatable(1) <= new Equatable(2)); // false + +var_dump(new Equatable(1) >= new Equatable(0)); // false +var_dump(new Equatable(1) >= new Equatable(1)); // true +var_dump(new Equatable(1) >= new Equatable(2)); // true + +/* These should use __equals */ +var_dump(new Equatable(1) == new Equatable(0)); // false +var_dump(new Equatable(1) == new Equatable(1)); // true +var_dump(new Equatable(1) == new Equatable(2)); // false + +var_dump(new Equatable(1) != new Equatable(0)); // true +var_dump(new Equatable(1) != new Equatable(1)); // false +var_dump(new Equatable(1) != new Equatable(2)); // true +/****/ + +var_dump(new Equatable(1) === new Equatable(0)); // false +var_dump(new Equatable(1) === new Equatable(1)); // false +var_dump(new Equatable(1) === new Equatable(2)); // false + +var_dump(new Equatable(1) !== new Equatable(0)); // true +var_dump(new Equatable(1) !== new Equatable(1)); // true +var_dump(new Equatable(1) !== new Equatable(2)); // true + +var_dump(new Equatable(1) <=> new Equatable(0)); // -1 +var_dump(new Equatable(1) <=> new Equatable(1)); // 0 +var_dump(new Equatable(1) <=> new Equatable(2)); // 1 + +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +int(-1) +int(0) +int(1) diff --git a/Zend/tests/comparisons/__equals/equals-rhs.phpt b/Zend/tests/comparisons/__equals/equals-rhs.phpt new file mode 100644 index 0000000000000..b959ac921c62b --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-rhs.phpt @@ -0,0 +1,28 @@ +--TEST-- +__equals: RHS operator should be considered even if LHS doesn't implement +--FILE-- + +--EXPECT-- +Comparing! +bool(true) +Comparing! +bool(true) diff --git a/Zend/tests/comparisons/__equals/equals-throw-exception.phpt b/Zend/tests/comparisons/__equals/equals-throw-exception.phpt new file mode 100644 index 0000000000000..0a451ad174ffc --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-throw-exception.phpt @@ -0,0 +1,26 @@ +--TEST-- +__equals: Throwing exception is caught +--FILE-- + +--EXPECTF-- + +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s: BrokenEquatable->__equals(Object(BrokenEquatable)) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/comparisons/__equals/equals-to-null.phpt b/Zend/tests/comparisons/__equals/equals-to-null.phpt new file mode 100644 index 0000000000000..10dd79837e3ea --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-to-null.phpt @@ -0,0 +1,62 @@ +--TEST-- +__equals: Compare against NULL +--FILE-- +value = $value; + } + + public function __equals($other) + { + echo "Comparing!\n"; + return $this->value == $other; + } +} + +/** + * This should call __equals. + */ +var_dump(new Equatable(1) == null); // false + +/** + * This should also call __equals even though we're using the RHS + */ +var_dump(null == new Equatable(1)); // false + +/** + * We're doing a non-strict comparison between 0 and NULL here. + */ +var_dump(null == new Equatable(0)); // true +var_dump(new Equatable(0) == null); // true + +echo "\n"; + +/** + * Check that default behaviour still works as expected. + */ +var_dump(new stdClass == null); +var_dump(new stdClass != null); +var_dump(null == new stdClass); +var_dump(null != new stdClass); + +?> +--EXPECT-- +Comparing! +bool(false) +Comparing! +bool(false) +Comparing! +bool(true) +Comparing! +bool(true) + +bool(false) +bool(true) +bool(false) +bool(true) diff --git a/Zend/tests/comparisons/__equals/equals-to-scalar.phpt b/Zend/tests/comparisons/__equals/equals-to-scalar.phpt new file mode 100644 index 0000000000000..13ccab65901f7 --- /dev/null +++ b/Zend/tests/comparisons/__equals/equals-to-scalar.phpt @@ -0,0 +1,33 @@ +--TEST-- +__equals: Compare against scalar values +--FILE-- + +--EXPECTF-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) + +Fatal error: Uncaught Exception: Failed to equate in %s:%d +Stack trace: +#0 %s: Equatable->__equals('a') +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/compare_001.phpt b/Zend/tests/comparisons/compare_001.phpt similarity index 100% rename from Zend/tests/compare_001.phpt rename to Zend/tests/comparisons/compare_001.phpt diff --git a/Zend/tests/compare_001_64bit.phpt b/Zend/tests/comparisons/compare_001_64bit.phpt similarity index 100% rename from Zend/tests/compare_001_64bit.phpt rename to Zend/tests/comparisons/compare_001_64bit.phpt diff --git a/Zend/tests/compare_002.phpt b/Zend/tests/comparisons/compare_002.phpt similarity index 100% rename from Zend/tests/compare_002.phpt rename to Zend/tests/comparisons/compare_002.phpt diff --git a/Zend/tests/compare_002_64bit.phpt b/Zend/tests/comparisons/compare_002_64bit.phpt similarity index 100% rename from Zend/tests/compare_002_64bit.phpt rename to Zend/tests/comparisons/compare_002_64bit.phpt diff --git a/Zend/tests/compare_003.phpt b/Zend/tests/comparisons/compare_003.phpt similarity index 100% rename from Zend/tests/compare_003.phpt rename to Zend/tests/comparisons/compare_003.phpt diff --git a/Zend/tests/compare_003_64bit.phpt b/Zend/tests/comparisons/compare_003_64bit.phpt similarity index 100% rename from Zend/tests/compare_003_64bit.phpt rename to Zend/tests/comparisons/compare_003_64bit.phpt diff --git a/Zend/tests/compare_004.phpt b/Zend/tests/comparisons/compare_004.phpt similarity index 100% rename from Zend/tests/compare_004.phpt rename to Zend/tests/comparisons/compare_004.phpt diff --git a/Zend/tests/compare_004_64bit.phpt b/Zend/tests/comparisons/compare_004_64bit.phpt similarity index 100% rename from Zend/tests/compare_004_64bit.phpt rename to Zend/tests/comparisons/compare_004_64bit.phpt diff --git a/Zend/tests/compare_005.phpt b/Zend/tests/comparisons/compare_005.phpt similarity index 100% rename from Zend/tests/compare_005.phpt rename to Zend/tests/comparisons/compare_005.phpt diff --git a/Zend/tests/compare_005_64bit.phpt b/Zend/tests/comparisons/compare_005_64bit.phpt similarity index 100% rename from Zend/tests/compare_005_64bit.phpt rename to Zend/tests/comparisons/compare_005_64bit.phpt diff --git a/Zend/tests/compare_006.phpt b/Zend/tests/comparisons/compare_006.phpt similarity index 100% rename from Zend/tests/compare_006.phpt rename to Zend/tests/comparisons/compare_006.phpt diff --git a/Zend/tests/compare_006_64bit.phpt b/Zend/tests/comparisons/compare_006_64bit.phpt similarity index 100% rename from Zend/tests/compare_006_64bit.phpt rename to Zend/tests/comparisons/compare_006_64bit.phpt diff --git a/Zend/tests/comparisons/compare_007.phpt b/Zend/tests/comparisons/compare_007.phpt new file mode 100644 index 0000000000000..991feeba4f087 --- /dev/null +++ b/Zend/tests/comparisons/compare_007.phpt @@ -0,0 +1,24 @@ +--TEST-- +comparing objects of different types +--FILE-- + new DateTime()); // false +var_dump(new stdClass() == new DateTime()); // false + +var_dump(new DateTime() < new stdClass()); // false +var_dump(new DateTime() > new stdClass()); // false +var_dump(new DateTime() == new stdClass()); // false + +var_dump(new stdClass() <=> new DateTime()); // 1 +var_dump(new DateTime() <=> new stdClass()); // 1 +?> +--EXPECTF-- +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +int(1) +int(1) diff --git a/Zend/zend.h b/Zend/zend.h index b1c1e66867338..feeaa2f79fb51 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -139,6 +139,8 @@ struct _zend_class_entry { union _zend_function *__callstatic; union _zend_function *__tostring; union _zend_function *__debugInfo; + union _zend_function *__compareTo; + union _zend_function *__equals; union _zend_function *serialize_func; union _zend_function *unserialize_func; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 1699f1b50a94b..c846533ef777c 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2163,7 +2163,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio int count=0, unload=0; HashTable *target_function_table = function_table; int error_type; - zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL; + zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL, *__compareTo = NULL, *__equals = NULL; zend_string *lowercase_name; size_t fname_len; const char *lc_class_name = NULL; @@ -2355,6 +2355,10 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio __callstatic = reg_function; } else if (zend_string_equals_literal(lowercase_name, ZEND_TOSTRING_FUNC_NAME)) { __tostring = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_COMPARETO_FUNC_NAME)) { + __compareTo = reg_function; + } else if (zend_string_equals_literal(lowercase_name, ZEND_EQUALS_FUNC_NAME)) { + __equals = reg_function; } else if (zend_string_equals_literal(lowercase_name, ZEND_GET_FUNC_NAME)) { __get = reg_function; scope->ce_flags |= ZEND_ACC_USE_GUARDS; @@ -2409,6 +2413,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio scope->__unset = __unset; scope->__isset = __isset; scope->__debugInfo = __debugInfo; + scope->__compareTo = __compareTo; + scope->__equals = __equals; if (ctor) { ctor->common.fn_flags |= ZEND_ACC_CTOR; if (ctor->common.fn_flags & ZEND_ACC_STATIC) { @@ -2476,7 +2482,16 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio zend_error(error_type, "Method %s::%s() cannot be static", ZSTR_VAL(scope->name), ZSTR_VAL(__debugInfo->common.function_name)); } } - + if (__compareTo) { + if (__compareTo->common.fn_flags & ZEND_ACC_STATIC) { + zend_error(error_type, "Method %s::%s() cannot be static", ZSTR_VAL(scope->name), ZSTR_VAL(__compareTo->common.function_name)); + } + } + if (__equals) { + if (__equals->common.fn_flags & ZEND_ACC_STATIC) { + zend_error(error_type, "Method %s::%s() cannot be static", ZSTR_VAL(scope->name), ZSTR_VAL(__equals->common.function_name)); + } + } if (ctor && ctor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE && ctor->common.fn_flags & ZEND_ACC_CTOR) { zend_error_noreturn(E_CORE_ERROR, "Constructor %s::%s() cannot declare a return type", ZSTR_VAL(scope->name), ZSTR_VAL(ctor->common.function_name)); } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 63c82a4b06ad9..bd8f0c9d7fe41 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -211,6 +211,8 @@ typedef struct _zend_fcall_info_cache { class_container.__unset = NULL; \ class_container.__isset = NULL; \ class_container.__debugInfo = NULL; \ + class_container.__compareTo = NULL; \ + class_container.__equals = NULL; \ class_container.serialize_func = NULL; \ class_container.unserialize_func = NULL; \ class_container.parent = NULL; \ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c050855ae8d42..9ea0a64c8c80d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1763,6 +1763,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify ce->serialize_func = NULL; ce->unserialize_func = NULL; ce->__debugInfo = NULL; + ce->__compareTo = NULL; + ce->__equals = NULL; if (ce->type == ZEND_INTERNAL_CLASS) { ce->info.internal.module = NULL; ce->info.internal.builtin_functions = NULL; @@ -5847,6 +5849,16 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo zend_error(E_WARNING, "The magic method __debugInfo() must have " "public visibility and cannot be static"); } + } else if (zend_string_equals_literal(lcname, ZEND_COMPARETO_FUNC_NAME)) { + if (!is_public || is_static) { + zend_error(E_WARNING, "The magic method __compareTo() must have " + "public visibility and cannot be static"); + } + } else if (zend_string_equals_literal(lcname, ZEND_EQUALS_FUNC_NAME)) { + if (!is_public || is_static) { + zend_error(E_WARNING, "The magic method __equals() must have " + "public visibility and cannot be static"); + } } } else { if (!in_trait && zend_string_equals_ci(lcname, ce->name)) { @@ -5920,6 +5932,18 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo "public visibility and cannot be static"); } ce->__debugInfo = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_COMPARETO_FUNC_NAME)) { + if (!is_public || is_static) { + zend_error(E_WARNING, "The magic method __compareTo() must have " + "public visibility and cannot be static"); + } + ce->__compareTo = (zend_function *) op_array; + } else if (zend_string_equals_literal(lcname, ZEND_EQUALS_FUNC_NAME)) { + if (!is_public || is_static) { + zend_error(E_WARNING, "The magic method __equals() must have " + "public visibility and cannot be static"); + } + ce->__equals = (zend_function *) op_array; } else if (!is_static) { op_array->fn_flags |= ZEND_ACC_ALLOW_STATIC; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 9ba1b54f33bd7..c1a2e56e37422 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -979,6 +979,8 @@ END_EXTERN_C() #define ZEND_AUTOLOAD_FUNC_NAME "__autoload" #define ZEND_INVOKE_FUNC_NAME "__invoke" #define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo" +#define ZEND_COMPARETO_FUNC_NAME "__compareto" +#define ZEND_EQUALS_FUNC_NAME "__equals" /* The following constants may be combined in CG(compiler_options) * to change the default compiler behavior */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b66464c37542a..b1073158e61a5 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -138,6 +138,12 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */ if (EXPECTED(!ce->__debugInfo)) { ce->__debugInfo = ce->parent->__debugInfo; } + if (EXPECTED(!ce->__compareTo)) { + ce->__compareTo = ce->parent->__compareTo; + } + if (EXPECTED(!ce->__equals)) { + ce->__equals = ce->parent->__equals; + } if (ce->constructor) { if (ce->parent->constructor && UNEXPECTED(ce->parent->constructor->common.fn_flags & ZEND_ACC_FINAL)) { @@ -1157,6 +1163,10 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen ce->__tostring = fe; } else if (zend_string_equals_literal(mname, ZEND_DEBUGINFO_FUNC_NAME)) { ce->__debugInfo = fe; + } else if (zend_string_equals_literal(mname, ZEND_COMPARETO_FUNC_NAME)) { + ce->__compareTo = fe; + } else if (zend_string_equals_literal(mname, ZEND_EQUALS_FUNC_NAME)) { + ce->__equals = fe; } } /* }}} */ diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 97a0dcd817040..45b39de48e0e3 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1551,6 +1551,40 @@ ZEND_API zend_function *zend_std_get_constructor(zend_object *zobj) /* {{{ */ } /* }}} */ +ZEND_API int zend_std_equals(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + if (Z_TYPE_P(op1) == IS_OBJECT) { + zend_class_entry *ce = Z_OBJCE_P(op1); + + if (ce->__equals) { + zend_call_method_with_1_params(op1, ce, &ce->__equals, ZEND_EQUALS_FUNC_NAME, result, op2); + + if (!Z_ISUNDEF_P(result)) { + return SUCCESS; + } + } + } + return FAILURE; +} +/* }}} */ + +ZEND_API int zend_std_compare(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + if (Z_TYPE_P(op1) == IS_OBJECT) { + zend_class_entry *ce = Z_OBJCE_P(op1); + + if (ce->__compareTo) { + zend_call_method_with_1_params(op1, ce, &ce->__compareTo, ZEND_COMPARETO_FUNC_NAME, result, op2); + + if (!Z_ISUNDEF_P(result)) { + return SUCCESS; + } + } + } + return FAILURE; +} +/* }}} */ + ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ { zend_object *zobj1, *zobj2; @@ -1863,7 +1897,8 @@ ZEND_API const zend_object_handlers std_object_handlers = { zend_std_get_closure, /* get_closure */ zend_std_get_gc, /* get_gc */ NULL, /* do_operation */ - NULL, /* compare */ + zend_std_compare, /* compare */ + zend_std_equals, /* equals */ }; /* diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 1dfed715f3535..1fd869c8496c3 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -112,8 +112,11 @@ typedef zend_object* (*zend_object_clone_obj_t)(zval *object); * Must be defined and must return a non-NULL value. */ typedef zend_string *(*zend_object_get_class_name_t)(const zend_object *object); +/* Comparison and equality + */ typedef int (*zend_object_compare_t)(zval *object1, zval *object2); -typedef int (*zend_object_compare_zvals_t)(zval *resul, zval *op1, zval *op2); +typedef int (*zend_object_compare_zvals_t)(zval *result, zval *op1, zval *op2); +typedef int (*zend_object_equals_t)(zval *result, zval *op1, zval *op2); /* Cast an object to some other type. * readobj and retval must point to distinct zvals. @@ -162,6 +165,7 @@ struct _zend_object_handlers { zend_object_get_gc_t get_gc; zend_object_do_operation_t do_operation; zend_object_compare_zvals_t compare; + zend_object_equals_t equals; }; BEGIN_EXTERN_C() diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 333bce5dfc73d..27ebf59672006 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -31,6 +31,7 @@ #include "zend_strtod.h" #include "zend_exceptions.h" #include "zend_closures.h" +#include "zend_interfaces.h" #if ZEND_USE_TOLOWER_L #include @@ -1951,6 +1952,7 @@ static void ZEND_FASTCALL convert_compare_result_to_long(zval *result) /* {{{ */ ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result))); } else { convert_to_long(result); + Z_LVAL_P(result) = ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)); } } /* }}} */ @@ -2024,10 +2026,25 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) return SUCCESS; case TYPE_PAIR(IS_OBJECT, IS_NULL): + if (Z_OBJ_HANDLER_P(op1, compare)) { + if (Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2) == SUCCESS) { + convert_compare_result_to_long(result); + return SUCCESS; + } + } + ZVAL_LONG(result, 1); return SUCCESS; case TYPE_PAIR(IS_NULL, IS_OBJECT): + if (Z_OBJ_HANDLER_P(op2, compare)) { + if (Z_OBJ_HANDLER_P(op2, compare)(result, op2, op1) == SUCCESS) { + convert_compare_result_to_long(result); + Z_LVAL_P(result) *= -1; + return SUCCESS; + } + } + ZVAL_LONG(result, -1); return SUCCESS; @@ -2040,18 +2057,20 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) continue; } - if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) { - ret = Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2); - if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) { - convert_compare_result_to_long(result); - } - return ret; - } else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, compare)) { - ret = Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2); - if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) { + /* compare handlers fall through if not successful */ + if (Z_TYPE_P(op1) == IS_OBJECT) { + if (Z_OBJ_HANDLER_P(op1, compare) && Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2) == SUCCESS) { + convert_compare_result_to_long(result); + return SUCCESS; + } + } + + if (Z_TYPE_P(op2) == IS_OBJECT) { + if (Z_OBJ_HANDLER_P(op2, compare) && Z_OBJ_HANDLER_P(op2, compare)(result, op2, op1) == SUCCESS) { convert_compare_result_to_long(result); - } - return ret; + Z_LVAL_P(result) *= -1; + return SUCCESS; + } } if (Z_TYPE_P(op1) == IS_OBJECT && Z_TYPE_P(op2) == IS_OBJECT) { @@ -2065,6 +2084,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) return SUCCESS; } } + if (Z_TYPE_P(op1) == IS_OBJECT) { if (Z_OBJ_HT_P(op1)->get) { zval rv; @@ -2084,6 +2104,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) return ret; } } + if (Z_TYPE_P(op2) == IS_OBJECT) { if (Z_OBJ_HT_P(op2)->get) { zval rv; @@ -2106,6 +2127,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) return SUCCESS; } } + if (!converted) { if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0); @@ -2214,9 +2236,24 @@ ZEND_API int ZEND_FASTCALL is_not_identical_function(zval *result, zval *op1, zv ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */ { + if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, equals)) { + if (Z_OBJ_HANDLER_P(op1, equals)(result, op1, op2) == SUCCESS) { + convert_to_boolean(result); + return SUCCESS; + } + } + + if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, equals)) { + if (Z_OBJ_HANDLER_P(op2, equals)(result, op2, op1) == SUCCESS) { + convert_to_boolean(result); + return SUCCESS; + } + } + if (compare_function(result, op1, op2) == FAILURE) { return FAILURE; } + ZVAL_BOOL(result, (Z_LVAL_P(result) == 0)); return SUCCESS; } @@ -2224,10 +2261,10 @@ ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2) ZEND_API int ZEND_FASTCALL is_not_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */ { - if (compare_function(result, op1, op2) == FAILURE) { + if (is_equal_function(result, op1, op2) == FAILURE) { return FAILURE; } - ZVAL_BOOL(result, (Z_LVAL_P(result) != 0)); + ZVAL_BOOL(result, Z_TYPE_P(result) == IS_TRUE ? 0 : 1); return SUCCESS; } /* }}} */ @@ -2252,6 +2289,26 @@ ZEND_API int ZEND_FASTCALL is_smaller_or_equal_function(zval *result, zval *op1, } /* }}} */ +ZEND_API int ZEND_FASTCALL is_greater_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + if (compare_function(result, op1, op2) == FAILURE) { + return FAILURE; + } + ZVAL_BOOL(result, (Z_LVAL_P(result) > 0)); + return SUCCESS; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL is_greater_or_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */ +{ + if (compare_function(result, op1, op2) == FAILURE) { + return FAILURE; + } + ZVAL_BOOL(result, (Z_LVAL_P(result) >= 0)); + return SUCCESS; +} +/* }}} */ + static zend_bool ZEND_FASTCALL instanceof_interface_only(const zend_class_entry *instance_ce, const zend_class_entry *ce) /* {{{ */ { uint32_t i; diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index b142bc94dd11f..23e468fa8f7af 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -67,6 +67,8 @@ ZEND_API int ZEND_FASTCALL is_not_identical_function(zval *result, zval *op1, zv ZEND_API int ZEND_FASTCALL is_not_equal_function(zval *result, zval *op1, zval *op2); ZEND_API int ZEND_FASTCALL is_smaller_function(zval *result, zval *op1, zval *op2); ZEND_API int ZEND_FASTCALL is_smaller_or_equal_function(zval *result, zval *op1, zval *op2); +ZEND_API int ZEND_FASTCALL is_greater_function(zval *result, zval *op1, zval *op2); +ZEND_API int ZEND_FASTCALL is_greater_or_equal_function(zval *result, zval *op1, zval *op2); ZEND_API zend_bool ZEND_FASTCALL instanceof_function_ex(const zend_class_entry *instance_ce, const zend_class_entry *ce, zend_bool interfaces_only); ZEND_API zend_bool ZEND_FASTCALL instanceof_function(const zend_class_entry *instance_ce, const zend_class_entry *ce); @@ -753,8 +755,8 @@ static zend_always_inline int fast_equal_check_function(zval *op1, zval *op2) return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2)); } } - compare_function(&result, op1, op2); - return Z_LVAL(result) == 0; + is_equal_function(&result, op1, op2); + return Z_TYPE(result) == IS_TRUE; } static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2) @@ -763,8 +765,8 @@ static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2) if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { return Z_LVAL_P(op1) == Z_LVAL_P(op2); } - compare_function(&result, op1, op2); - return Z_LVAL(result) == 0; + is_equal_function(&result, op1, op2); + return Z_TYPE(result) == IS_TRUE; } static zend_always_inline int fast_equal_check_string(zval *op1, zval *op2) @@ -773,8 +775,8 @@ static zend_always_inline int fast_equal_check_string(zval *op1, zval *op2) if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) { return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2)); } - compare_function(&result, op1, op2); - return Z_LVAL(result) == 0; + is_equal_function(&result, op1, op2); + return Z_TYPE(result) == IS_TRUE; } static zend_always_inline int fast_is_identical_function(zval *op1, zval *op2) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c5ff50cf852d6..7857b5081b74c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -438,8 +438,7 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(17, ZEND_IS_EQUAL, CONST|TMPVAR|CV, CONST|TMPVAR op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); FREE_OP1(); FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -496,8 +495,7 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(18, ZEND_IS_NOT_EQUAL, CONST|TMPVAR|CV, CONST|TM op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); FREE_OP1(); FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -546,8 +544,7 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(19, ZEND_IS_SMALLER, CONST|TMPVAR|CV, CONST|TMPV op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); FREE_OP1(); FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -596,8 +593,7 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(20, ZEND_IS_SMALLER_OR_EQUAL, CONST|TMPVAR|CV, C op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); FREE_OP1(); FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4883,8 +4879,7 @@ ZEND_VM_HANDLER(48, ZEND_CASE, TMPVAR, CONST|TMPVAR|CV) op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); FREE_OP2(); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -8038,8 +8033,8 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(189, ZEND_IN_ARRAY, CONST|TMP|VAR|CV, CONST, NUM result = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { result = 1; break; } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 2724bca69ce2f..7f84030a6157b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4273,8 +4273,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CON op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4331,8 +4330,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4381,8 +4379,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_C op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -4431,8 +4428,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQU op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -6120,8 +6116,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IN_ARRAY_SPEC_CON result = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { result = 1; break; } @@ -6703,8 +6699,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_TMPVAR_H op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -6753,8 +6748,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_CONST op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9796,8 +9790,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CONST_CV_HANDL op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -9846,8 +9839,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_CONST op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -13191,8 +13183,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_TMPVAR_CONST_HAN op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -13249,8 +13240,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC_TMPVAR_CONST op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -13299,8 +13289,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_TMPVAR_CONST_H op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -13349,8 +13338,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_TMPVA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -14145,8 +14133,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CASE_SPEC_TMPVAR_CONST_HANDLER op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -14920,8 +14907,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_TMPVAR_TMPVAR_HA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -14978,8 +14964,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC_TMPVAR_TMPVA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -15028,8 +15013,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_TMPVAR_TMPVAR_ op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -15078,8 +15062,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_TMPVA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -15750,8 +15733,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CASE_SPEC_TMPVAR_TMPVAR_HANDLE op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -16998,8 +16980,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_TMPVAR_CV_HAND op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -17048,8 +17029,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_TMPVA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -17622,8 +17602,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CASE_SPEC_TMPVAR_CV_HANDLER(ZE op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -18939,8 +18918,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IN_ARRAY_SPEC_TMP_CONST_HANDLE result = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { result = 1; break; } @@ -24204,8 +24183,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IN_ARRAY_SPEC_VAR_CONST_HANDLE result = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { result = 1; break; } @@ -38260,8 +38239,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CV_CONST_HANDLER op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -38318,8 +38296,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC_CV_CONST_HAN op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -38368,8 +38345,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CV_CONST_HANDL op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -38418,8 +38394,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_CV_CO op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -41652,8 +41627,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IN_ARRAY_SPEC_CV_CONST_HANDLER result = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { result = 1; break; } @@ -42138,8 +42113,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CV_TMPVAR_HANDLE op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -42196,8 +42170,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC_CV_TMPVAR_HA op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -42246,8 +42219,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CV_TMPVAR_HAND op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -42296,8 +42268,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_CV_TM op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); zval_ptr_dtor_nogc(free_op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -47697,8 +47668,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CV_CV_HANDLER(ZE op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) == 0); + is_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -47755,8 +47725,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_NOT_EQUAL_SPEC_CV_CV_HANDLE op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) != 0); + is_not_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -47805,8 +47774,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_SPEC_CV_CV_HANDLER( op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) < 0); + is_smaller_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); @@ -47855,8 +47823,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_SMALLER_OR_EQUAL_SPEC_CV_CV op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); - compare_function(result, op1, op2); - ZVAL_BOOL(result, Z_LVAL_P(result) <= 0); + is_smaller_or_equal_function(result, op1, op2); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index c9acb0498387c..52ca68d52d0f0 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -688,8 +688,8 @@ static inline int ct_eval_in_array(zval *result, uint32_t extended_value, zval * res = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); - compare_function(&result_tmp, op1, &key_tmp); - if (Z_LVAL(result_tmp) == 0) { + is_equal_function(&result_tmp, op1, &key_tmp); + if (Z_TYPE(result_tmp) == IS_TRUE) { res = 1; break; } diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 05708a7068d84..155b591ff383d 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -424,6 +424,10 @@ static void zend_class_copy_ctor(zend_class_entry **pce) zend_update_inherited_handler(__callstatic); zend_update_inherited_handler(__debugInfo); +/* Comparison methods */ + zend_update_inherited_handler(__compareTo); + zend_update_inherited_handler(__equals); + /* 5.4 traits */ if (ce->trait_aliases) { zend_trait_alias **trait_aliases; diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 36576d892ed9b..264ca23790317 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -723,6 +723,8 @@ static void zend_file_cache_serialize_class(zval *zv, SERIALIZE_PTR(ce->__tostring); SERIALIZE_PTR(ce->__callstatic); SERIALIZE_PTR(ce->__debugInfo); + SERIALIZE_PTR(ce->__compareTo); + SERIALIZE_PTR(ce->__equals); } static void zend_file_cache_serialize(zend_persistent_script *script, @@ -1347,6 +1349,8 @@ static void zend_file_cache_unserialize_class(zval *zv, UNSERIALIZE_PTR(ce->__tostring); UNSERIALIZE_PTR(ce->__callstatic); UNSERIALIZE_PTR(ce->__debugInfo); + UNSERIALIZE_PTR(ce->__compareTo); + UNSERIALIZE_PTR(ce->__equals); if (UNEXPECTED((ce->ce_flags & ZEND_ACC_ANON_CLASS))) { ce->serialize = zend_class_serialize_deny; diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index ecd0da516335e..3e99a4f42d9ec 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -884,6 +884,12 @@ static int zend_update_parent_ce(zval *zv) if (ce->__debugInfo) { ce->__debugInfo = zend_shared_alloc_get_xlat_entry(ce->__debugInfo); } + if (ce->__compareTo) { + ce->__compareTo = zend_shared_alloc_get_xlat_entry(ce->__compareTo); + } + if (ce->__equals) { + ce->__equals = zend_shared_alloc_get_xlat_entry(ce->__equals); + } // zend_hash_apply(&ce->properties_info, (apply_func_t) zend_update_property_info_ce); return 0; }