Skip to content

Commit dde65a0

Browse files
committed
Add RandomNumberGenerator interface.
1 parent 24a2c7b commit dde65a0

14 files changed

+195
-159
lines changed

ext/standard/php_random_class.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ PHP_MINIT_FUNCTION(random_class);
2121
PHP_MSHUTDOWN_FUNCTION(random_class);
2222
PHP_MINFO_FUNCTION(random_class);
2323

24-
extern PHPAPI zend_class_entry *php_ce_random;
24+
extern PHPAPI zend_class_entry *php_random_class_ce_RandomNumberGenerator;
25+
extern PHPAPI zend_class_entry *php_random_class_ce_Random;
2526

2627
typedef struct _php_random_class_algo {
2728
const char* ident;

ext/standard/random_class.c

Lines changed: 89 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,19 @@
1919
#include "php.h"
2020
#include "info.h"
2121

22+
#include "zend_exceptions.h"
23+
2224
#include "php_random.h"
2325
#include "php_random_class.h"
2426
#include "random_class_arginfo.h"
2527

26-
#include "zend_exceptions.h"
28+
PHPAPI zend_class_entry *php_random_class_ce_RandomNumberGenerator;
29+
PHPAPI zend_class_entry *php_random_class_ce_Random;
2730

2831
static zend_array php_random_class_algos;
29-
static zend_class_entry *php_random_class_ce;
3032
static zend_object_handlers php_random_class_object_handlers;
3133

34+
/* stolen from: mt_rand.c */
3235
static uint32_t range32(php_random_class *random_class, uint32_t umax) {
3336
uint32_t result, limit;
3437

@@ -59,6 +62,7 @@ static uint32_t range32(php_random_class *random_class, uint32_t umax) {
5962
}
6063

6164
#if ZEND_ULONG_MAX > UINT32_MAX
65+
/* stolen from: mt_rand.c */
6266
static uint64_t range64(php_random_class *random_class, uint64_t umax) {
6367
uint64_t result, limit;
6468

@@ -134,24 +138,32 @@ const php_random_class_algo* php_random_class_algo_find(const zend_string *ident
134138

135139
/* {{{ */
136140
uint64_t php_random_class_next(php_random_class *random_class)
137-
{
138-
zend_long ret;
139-
zend_function *function;
140-
zval function_name, retval;
141-
141+
{
142142
if (random_class->algo) {
143143
return random_class->algo->next(random_class->state);
144144
}
145145

146-
/* call user implementation. */
147-
ZVAL_STRING(&function_name, "next");
148-
function = zend_hash_find_ptr(&random_class->std.ce->function_table, Z_STR(function_name));
149-
zval_ptr_dtor(&function_name);
146+
zval retval, rv;
147+
zval *zrng = zend_read_property(random_class->std.ce, &random_class->std, "rng", sizeof("rng") - 1, 0, &rv);
148+
if (Z_ISNULL_P(zrng)) {
149+
return 0;
150+
}
151+
152+
zend_object *obj = Z_OBJ_P(zrng);
153+
if (!obj) {
154+
return 0;
155+
}
156+
157+
zend_string *method_name_generate = zend_string_init("generate", sizeof("generate") - 1, 0);
158+
zend_function *func = zend_hash_find_ptr(&obj->ce->function_table, method_name_generate);
159+
zend_string_release(method_name_generate);
160+
if (!func) {
161+
return 0;
162+
}
150163

151-
zend_call_known_instance_method_with_0_params(function, &random_class->std, &retval);
152-
ret = Z_LVAL(retval);
164+
zend_call_known_instance_method_with_0_params(func, obj, &retval);
153165

154-
return (uint64_t) ret;
166+
return (uint64_t) Z_LVAL(retval);
155167
}
156168
/* }}} */
157169

@@ -284,9 +296,13 @@ static zend_object *php_random_class_new(zend_class_entry *ce) {
284296
return &random_class->std;
285297
}
286298

287-
static void php_random_class_state_initialize(php_random_class *random_class) {
288-
if (random_class->algo && random_class->algo->state_size > 0) {
289-
random_class->state = ecalloc(1, random_class->algo->state_size);
299+
static void php_random_class_state_initialize(php_random_class *random_class, const php_random_class_algo *algo) {
300+
if (!algo) {
301+
return;
302+
}
303+
304+
if (algo->state_size > 0) {
305+
random_class->state = ecalloc(1, algo->state_size);
290306
}
291307
}
292308

@@ -311,7 +327,7 @@ static zend_object *php_random_class_clone_obj(zend_object *object) {
311327

312328
if (old->algo) {
313329
new->algo = old->algo;
314-
php_random_class_state_initialize(new);
330+
php_random_class_state_initialize(new, new->algo);
315331
memcpy(new->state, old->state, old->algo->state_size);
316332
}
317333

@@ -523,10 +539,6 @@ const php_random_class_algo php_random_class_algo_secure = {
523539
};
524540
/* secure END */
525541

526-
/* user BEGIN */
527-
#define PHP_RANDOM_CLASS_ALGO_USER_DEFINED "user"
528-
/* user END */
529-
530542
/* {{{ PHP_INI */
531543
PHP_INI_BEGIN()
532544
STD_PHP_INI_BOOLEAN("random.ignore_generated_size_exceeded", "0", PHP_INI_ALL, OnUpdateBool, random_class_ignore_generated_size_exceeded, php_core_globals, core_globals)
@@ -572,47 +584,61 @@ PHP_METHOD(Random, getAlgoInfo)
572584
/* {{{ */
573585
PHP_METHOD(Random, __construct)
574586
{
587+
php_random_class *random_class = Z_RANDOM_CLASS_P(ZEND_THIS);
575588
zend_string *algo_str = NULL;
589+
zend_object *rng = NULL;
576590
zend_long seed;
577591
bool seed_is_null = 1;
578-
const php_random_class_algo *algo;
579592

580593
ZEND_PARSE_PARAMETERS_START(0, 2)
581594
Z_PARAM_OPTIONAL
582-
Z_PARAM_STR(algo_str)
595+
Z_PARAM_OBJ_OF_CLASS_OR_STR(rng, php_random_class_ce_RandomNumberGenerator, algo_str)
583596
Z_PARAM_LONG_OR_NULL(seed, seed_is_null)
584597
ZEND_PARSE_PARAMETERS_END();
585598

586-
algo =
587-
algo_str == NULL
588-
? &php_random_class_algo_xorshift128plus
589-
: php_random_class_algo_find(algo_str);
590-
591-
if (algo) {
592-
php_random_class *random_class = Z_RANDOM_CLASS_P(ZEND_THIS);
593-
random_class->algo = algo;
594-
595-
if (!algo->seed && !seed_is_null) {
596-
zend_argument_value_error(2, "algorithm does not support seed with value");
599+
if (rng) {
600+
zval property_rng_value;
601+
602+
if (!seed_is_null) {
603+
zend_argument_value_error(2, "RandomNumberGenerator does not support seed with value");
597604
RETURN_THROWS();
598605
}
599606

600-
php_random_class_state_initialize(random_class);
601-
if (algo->seed) {
602-
if (seed_is_null) {
603-
seed = php_random_bytes_silent(&seed, sizeof(zend_long));
604-
}
607+
ZVAL_OBJ(&property_rng_value, rng);
608+
zend_string *property_rng_name = zend_string_init("rng", sizeof("rng") - 1, 0);
609+
zend_std_write_property(&random_class->std, property_rng_name, &property_rng_value, NULL);
610+
zend_string_release(property_rng_name);
611+
612+
return;
613+
}
605614

606-
algo->seed(random_class->state, seed);
607-
}
615+
const php_random_class_algo *algo;
608616

609-
} else if (! zend_string_equals_literal(algo_str, PHP_RANDOM_CLASS_ALGO_USER_DEFINED)) {
617+
algo = algo_str == NULL
618+
? &php_random_class_algo_xorshift128plus
619+
: php_random_class_algo_find(algo_str);
620+
621+
if (!algo) {
610622
zend_argument_value_error(1, "must be a valid random number generator algorithm");
611623
RETURN_THROWS();
612-
} else if (Z_OBJCE_P(ZEND_THIS)->type == ZEND_INTERNAL_CLASS) {
613-
zend_throw_exception(NULL, "User defined algorithm must be inherited", 0);
624+
}
625+
626+
random_class->algo = algo;
627+
628+
if (!algo->seed && !seed_is_null) {
629+
zend_argument_value_error(2, "algorithm does not support seed with value");
614630
RETURN_THROWS();
615631
}
632+
633+
php_random_class_state_initialize(random_class, algo);
634+
635+
if (algo->seed) {
636+
if (seed_is_null) {
637+
seed = php_random_bytes_silent(&seed, sizeof(zend_long));
638+
}
639+
640+
algo->seed(random_class->state, seed);
641+
}
616642
}
617643
/* }}} */
618644

@@ -744,11 +770,6 @@ PHP_METHOD(Random, __serialize)
744770

745771
ZEND_PARSE_PARAMETERS_NONE();
746772

747-
if (Z_OBJCE_P(ZEND_THIS)->ce_flags & ZEND_ACC_ANON_CLASS) {
748-
zend_throw_exception(NULL, "Anonymous class serialization is not allowed", 0);
749-
RETURN_THROWS();
750-
}
751-
752773
if (intern->algo && (!intern->algo->serialize || !intern->algo->unserialize)) {
753774
zend_throw_exception(NULL, "Algorithm does not support serialization", 0);
754775
RETURN_THROWS();
@@ -762,7 +783,11 @@ PHP_METHOD(Random, __serialize)
762783
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
763784

764785
/* algo */
765-
ZVAL_STRING(&tmp, intern->algo ? intern->algo->ident : PHP_RANDOM_CLASS_ALGO_USER_DEFINED);
786+
if (intern->algo) {
787+
ZVAL_STRING(&tmp, intern->algo->ident);
788+
} else {
789+
ZVAL_NULL(&tmp);
790+
}
766791
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &tmp);
767792

768793
/* state */
@@ -776,7 +801,6 @@ PHP_METHOD(Random, __serialize)
776801
PHP_METHOD(Random, __unserialize)
777802
{
778803
php_random_class *intern = Z_RANDOM_CLASS_P(ZEND_THIS);
779-
const php_random_class_algo *algo;
780804
HashTable *data;
781805
zval *tmp, *members_zv;
782806

@@ -794,15 +818,17 @@ PHP_METHOD(Random, __unserialize)
794818

795819
/* state */
796820
tmp = zend_hash_index_find(data, 1);
797-
if (! zend_string_equals_literal(Z_STR_P(tmp), PHP_RANDOM_CLASS_ALGO_USER_DEFINED)) {
821+
if (!Z_ISNULL_P(tmp)) {
822+
const php_random_class_algo *algo;
823+
798824
algo = php_random_class_algo_find(Z_STR_P(tmp));
799825
if (!algo) {
800826
zend_throw_exception(NULL, "Algorithm does not registered", 0);
801827
RETURN_THROWS();
802828
}
803829
intern->algo = algo;
804830

805-
php_random_class_state_initialize(intern);
831+
php_random_class_state_initialize(intern, algo);
806832

807833
if (!algo->serialize || !algo->unserialize) {
808834
zend_throw_exception(NULL, "Algorithm does not support serialization", 0);
@@ -817,40 +843,31 @@ PHP_METHOD(Random, __unserialize)
817843
}
818844
/* }}} */
819845

820-
/* {{{ */
821-
PHP_METHOD(Random, next)
822-
{
823-
zend_throw_exception(NULL, "Must be override the method next(): int", 0);
824-
RETURN_THROWS();
825-
}
826-
/* }}} */
827-
828846
PHP_MINIT_FUNCTION(random_class)
829847
{
830-
zend_class_entry ce;
831-
832848
REGISTER_INI_ENTRIES();
833849

834850
zend_hash_init(&php_random_class_algos, 1, NULL, ZVAL_PTR_DTOR, 1);
835851

836-
/* XorShift128+ */
852+
/* algo: XorShift128+ */
837853
if (FAILURE == php_random_class_algo_register(&php_random_class_algo_xorshift128plus)) { return FAILURE; }
838854
REGISTER_STRING_CONSTANT("RANDOM_XORSHIFT128PLUS", php_random_class_algo_xorshift128plus.ident, CONST_CS | CONST_PERSISTENT);
839855

840-
/* MT19937 */
856+
/* algo: MT19937 */
841857
if (FAILURE == php_random_class_algo_register(&php_random_class_algo_mt19937)) { return FAILURE; }
842858
REGISTER_STRING_CONSTANT("RANDOM_MT19937", php_random_class_algo_mt19937.ident, CONST_CS | CONST_PERSISTENT);
843859

844-
/* secure */
860+
/* algo: secure */
845861
if (FAILURE == php_random_class_algo_register(&php_random_class_algo_secure)) { return FAILURE; }
846862
REGISTER_STRING_CONSTANT("RANDOM_SECURE", php_random_class_algo_secure.ident, CONST_CS | CONST_PERSISTENT);
847863

848-
/* user */
849-
REGISTER_STRING_CONSTANT("RANDOM_USER", PHP_RANDOM_CLASS_ALGO_USER_DEFINED, CONST_CS | CONST_PERSISTENT);
864+
/* interface: RandomNumberGenerator */
865+
php_random_class_ce_RandomNumberGenerator = register_class_RandomNumberGenerator();
866+
867+
/* class:Random */
868+
php_random_class_ce_Random = register_class_Random();
850869

851-
INIT_CLASS_ENTRY(ce, "Random", class_Random_methods);
852-
php_random_class_ce = zend_register_internal_class(&ce);
853-
php_random_class_ce->create_object = php_random_class_new;
870+
php_random_class_ce_Random->create_object = php_random_class_new;
854871
memcpy(&php_random_class_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
855872
php_random_class_object_handlers.offset = XtOffsetOf(php_random_class, std);
856873
php_random_class_object_handlers.free_obj = php_random_class_free_obj;

ext/standard/random_class.stub.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
<?php
22

3+
/** @generate-class-entries */
34
/** @generate-function-entries */
45

5-
class Random
6+
interface RandomNumberGenerator
67
{
8+
public function generate(): int;
9+
}
10+
11+
final class Random
12+
{
13+
private ?RandomNumberGenerator $rng;
14+
715
public static function getAlgos(): array;
816
public static function getAlgoInfo(string $algo): ?array;
9-
public function __construct(string $algo = RANDOM_XORSHIFT128PLUS, ?int $seed = null) {}
17+
public function __construct(string|RandomNumberGenerator $algo = RANDOM_XORSHIFT128PLUS, ?int $seed = null) {}
1018
public function nextInt(): int {}
1119
public function getInt(int $min, int $max): int {}
1220
public function getBytes(int $length): string {}
1321
public function shuffleArray(array $array): array {}
1422
public function shuffleString(string $string): string {}
1523
public function __serialize(): array {}
1624
public function __unserialize(array $data): void {}
17-
protected function next(): int {}
1825
}

0 commit comments

Comments
 (0)