php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
randomizer.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Go Kudo <zeriyoshi@php.net> |
14 +----------------------------------------------------------------------+
15*/
16
17#ifdef HAVE_CONFIG_H
18# include "config.h"
19#endif
20
21#include "php.h"
22#include "php_random.h"
23
26
27#include "Zend/zend_enum.h"
29#include "zend_portability.h"
30
31static inline void randomizer_common_init(php_random_randomizer *randomizer, zend_object *engine_object) {
32 if (engine_object->ce->type == ZEND_INTERNAL_CLASS) {
33 /* Internal classes always php_random_engine struct */
34 php_random_engine *engine = php_random_engine_from_obj(engine_object);
35
36 /* Copy engine pointers */
37 randomizer->engine = engine->engine;
38 } else {
39 /* Self allocation */
41 randomizer->engine = (php_random_algo_with_state){
42 .algo = &php_random_algo_user,
43 .state = state,
44 };
45
46 zend_string *mname;
47 zend_function *generate_method;
48
49 mname = ZSTR_INIT_LITERAL("generate", 0);
50 generate_method = zend_hash_find_ptr(&engine_object->ce->function_table, mname);
51 zend_string_release(mname);
52
53 /* Create compatible state */
54 state->object = engine_object;
55 state->generate_method = generate_method;
56
57 /* Mark self-allocated for memory management */
58 randomizer->is_userland_algo = true;
59 }
60}
61
62/* {{{ Random\Randomizer::__construct() */
63PHP_METHOD(Random_Randomizer, __construct)
64{
66 zval engine;
67 zval *param_engine = NULL;
68
73
74 if (param_engine != NULL) {
75 ZVAL_COPY(&engine, param_engine);
76 } else {
77 /* Create default RNG instance */
79 }
80
82
83 OBJ_RELEASE(Z_OBJ_P(&engine));
84
85 if (EG(exception)) {
87 }
88
89 randomizer_common_init(randomizer, Z_OBJ_P(&engine));
90}
91/* }}} */
92
93/* {{{ Generate a float in [0, 1) */
94PHP_METHOD(Random_Randomizer, nextFloat)
95{
97 php_random_algo_with_state engine = randomizer->engine;
98
99 uint64_t result;
100 size_t total_size;
101
103
104 result = 0;
105 total_size = 0;
106 do {
107 php_random_result r = engine.algo->generate(engine.state);
108 result = result | (r.result << (total_size * 8));
109 total_size += r.size;
110 if (EG(exception)) {
112 }
113 } while (total_size < sizeof(uint64_t));
114
115 /* A double has 53 bits of precision, thus we must not
116 * use the full 64 bits of the uint64_t, because we would
117 * introduce a bias / rounding error.
118 */
119#if DBL_MANT_DIG != 53
120# error "Random_Randomizer::nextFloat(): Requires DBL_MANT_DIG == 53 to work."
121#endif
122 const double step_size = 1.0 / (1ULL << 53);
123
124 /* Use the upper 53 bits, because some engine's lower bits
125 * are of lower quality.
126 */
127 result = (result >> 11);
128
129 RETURN_DOUBLE(step_size * result);
130}
131/* }}} */
132
133/* {{{ Generates a random float within a configurable interval.
134 *
135 * This method uses the γ-section algorithm by Frédéric Goualard.
136 */
137PHP_METHOD(Random_Randomizer, getFloat)
138{
140 double min, max;
141 zend_object *bounds = NULL;
142 int bounds_type = 'C' + sizeof("ClosedOpen") - 1;
143
150
151 if (!zend_finite(min)) {
152 zend_argument_value_error(1, "must be finite");
154 }
155
156 if (!zend_finite(max)) {
157 zend_argument_value_error(2, "must be finite");
159 }
160
161 if (bounds) {
162 zval *case_name = zend_enum_fetch_case_name(bounds);
163 zend_string *bounds_name = Z_STR_P(case_name);
164
165 bounds_type = ZSTR_VAL(bounds_name)[0] + ZSTR_LEN(bounds_name);
166 }
167
168 switch (bounds_type) {
169 case 'C' + sizeof("ClosedOpen") - 1:
170 if (UNEXPECTED(max <= min)) {
171 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
173 }
174
176 case 'C' + sizeof("ClosedClosed") - 1:
177 if (UNEXPECTED(max < min)) {
178 zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
180 }
181
183 case 'O' + sizeof("OpenClosed") - 1:
184 if (UNEXPECTED(max <= min)) {
185 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
187 }
188
190 case 'O' + sizeof("OpenOpen") - 1:
191 if (UNEXPECTED(max <= min)) {
192 zend_argument_value_error(2, "must be greater than argument #1 ($min)");
194 }
195
197
198 if (UNEXPECTED(isnan(Z_DVAL_P(return_value)))) {
199 zend_value_error("The given interval is empty, there are no floats between argument #1 ($min) and argument #2 ($max).");
201 }
202
203 return;
204 default:
206 }
207}
208/* }}} */
209
210/* {{{ Generate positive random number */
211PHP_METHOD(Random_Randomizer, nextInt)
212{
214 php_random_algo_with_state engine = randomizer->engine;
215
217
218 php_random_result result = engine.algo->generate(engine.state);
219 if (EG(exception)) {
221 }
222 if (result.size > sizeof(zend_long)) {
223 zend_throw_exception(random_ce_Random_RandomException, "Generated value exceeds size of int", 0);
225 }
226
227 RETURN_LONG((zend_long) (result.result >> 1));
228}
229/* }}} */
230
231/* {{{ Generate random number in range */
232PHP_METHOD(Random_Randomizer, getInt)
233{
235 php_random_algo_with_state engine = randomizer->engine;
236
237 uint64_t result;
239
244
245 if (UNEXPECTED(max < min)) {
246 zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
248 }
249
250 if (UNEXPECTED(
251 engine.algo->range == php_random_algo_mt19937.range
252 && ((php_random_status_state_mt19937 *) engine.state)->mode != MT_RAND_MT19937
253 )) {
254 uint64_t r = php_random_algo_mt19937.generate(engine.state).result >> 1;
255
256 /* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
257 * (max - min) > ZEND_LONG_MAX.
258 */
259 zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
260
261 result = (zend_long) (offset + min);
262 } else {
263 result = engine.algo->range(engine.state, min, max);
264 }
265
266 if (EG(exception)) {
268 }
269
271}
272/* }}} */
273
274/* {{{ Generate random bytes string in ordered length */
275PHP_METHOD(Random_Randomizer, getBytes)
276{
278 php_random_algo_with_state engine = randomizer->engine;
279
281 zend_long user_length;
282 size_t total_size = 0;
283
285 Z_PARAM_LONG(user_length)
287
288 if (user_length < 1) {
289 zend_argument_value_error(1, "must be greater than 0");
291 }
292
293 size_t length = (size_t)user_length;
294 retval = zend_string_alloc(length, 0);
295
297 while (total_size + 8 <= length) {
298 result = engine.algo->generate(engine.state);
299 if (EG(exception)) {
300 zend_string_free(retval);
302 }
303
304 /* If the result is not 64 bits, we can't use the fast path and
305 * we don't attempt to use it in the future, because we don't
306 * expect engines to change their output size.
307 *
308 * While it would be possible to always memcpy() the entire output,
309 * using result.size as the length that would result in much worse
310 * assembly, because it will actually emit a call to memcpy()
311 * instead of just storing the 64 bit value at a memory offset.
312 */
313 if (result.size != 8) {
314 goto non_64;
315 }
316
317#ifdef WORDS_BIGENDIAN
318 uint64_t swapped = ZEND_BYTES_SWAP64(result.result);
319 memcpy(ZSTR_VAL(retval) + total_size, &swapped, 8);
320#else
321 memcpy(ZSTR_VAL(retval) + total_size, &result.result, 8);
322#endif
323 total_size += 8;
324 }
325
326 while (total_size < length) {
327 result = engine.algo->generate(engine.state);
328 if (EG(exception)) {
329 zend_string_free(retval);
331 }
332
333 non_64:
334
335 for (size_t i = 0; i < result.size; i++) {
336 ZSTR_VAL(retval)[total_size++] = result.result & 0xff;
337 result.result >>= 8;
338 if (total_size >= length) {
339 break;
340 }
341 }
342 }
343
344 ZSTR_VAL(retval)[length] = '\0';
346}
347/* }}} */
348
349/* {{{ Shuffling array */
350PHP_METHOD(Random_Randomizer, shuffleArray)
351{
353 zval *array;
354
356 Z_PARAM_ARRAY(array)
358
359 ZVAL_DUP(return_value, array);
360 if (!php_array_data_shuffle(randomizer->engine, return_value)) {
362 }
363}
364/* }}} */
365
366/* {{{ Shuffling binary */
367PHP_METHOD(Random_Randomizer, shuffleBytes)
368{
370 zend_string *bytes;
371
373 Z_PARAM_STR(bytes)
375
376 if (ZSTR_LEN(bytes) < 2) {
377 RETURN_STR_COPY(bytes);
378 }
379
380 RETVAL_STRINGL(ZSTR_VAL(bytes), ZSTR_LEN(bytes));
383 }
384}
385/* }}} */
386
387/* {{{ Pick keys */
388PHP_METHOD(Random_Randomizer, pickArrayKeys)
389{
391 zval *input, t;
392 zend_long num_req;
393
395 Z_PARAM_ARRAY(input)
396 Z_PARAM_LONG(num_req)
398
400 randomizer->engine,
401 input,
402 num_req,
404 false)
405 ) {
407 }
408
409 /* Keep compatibility, But the result is always an array */
414 }
415}
416/* }}} */
417
418/* {{{ Get Random Bytes for String */
419PHP_METHOD(Random_Randomizer, getBytesFromString)
420{
422 php_random_algo_with_state engine = randomizer->engine;
423
424 zend_long user_length;
425 zend_string *source, *retval;
426 size_t total_size = 0;
427
429 Z_PARAM_STR(source)
430 Z_PARAM_LONG(user_length)
432
433 const size_t source_length = ZSTR_LEN(source);
434 const size_t max_offset = source_length - 1;
435
436 if (source_length < 1) {
439 }
440
441 if (user_length < 1) {
442 zend_argument_value_error(2, "must be greater than 0");
444 }
445
446 size_t length = (size_t)user_length;
447 retval = zend_string_alloc(length, 0);
448
449 if (max_offset > 0xff) {
450 while (total_size < length) {
451 uint64_t offset = engine.algo->range(engine.state, 0, max_offset);
452
453 if (EG(exception)) {
454 zend_string_free(retval);
456 }
457
458 ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
459 }
460 } else {
461 uint64_t mask = max_offset;
462 // Copy the top-most bit into all lower bits.
463 // Shifting by 4 is sufficient, because max_offset
464 // is guaranteed to fit in an 8-bit integer at this
465 // point.
466 mask |= mask >> 1;
467 mask |= mask >> 2;
468 mask |= mask >> 4;
469 // Expand the lowest byte into all bytes.
470 mask *= 0x0101010101010101;
471
472 int failures = 0;
473 while (total_size < length) {
474 php_random_result result = engine.algo->generate(engine.state);
475 if (EG(exception)) {
476 zend_string_free(retval);
478 }
479
480 uint64_t offsets = result.result & mask;
481 for (size_t i = 0; i < result.size; i++) {
482 uint64_t offset = offsets & 0xff;
483 offsets >>= 8;
484
485 if (offset > max_offset) {
486 if (++failures > PHP_RANDOM_RANGE_ATTEMPTS) {
487 zend_string_free(retval);
488 zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", PHP_RANDOM_RANGE_ATTEMPTS);
490 }
491
492 continue;
493 }
494
495 failures = 0;
496
497 ZSTR_VAL(retval)[total_size++] = ZSTR_VAL(source)[offset];
498 if (total_size >= length) {
499 break;
500 }
501 }
502 }
503 }
504
505 ZSTR_VAL(retval)[length] = '\0';
507}
508/* }}} */
509
510/* {{{ Random\Randomizer::__serialize() */
511PHP_METHOD(Random_Randomizer, __serialize)
512{
514 zval t;
515
517
519 ZVAL_ARR(&t, zend_std_get_properties(&randomizer->std));
520 Z_TRY_ADDREF(t);
522}
523/* }}} */
524
525/* {{{ Random\Randomizer::__unserialize() */
526PHP_METHOD(Random_Randomizer, __unserialize)
527{
529 HashTable *d;
530 zval *members_zv;
531 zval *zengine;
532
536
537 /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
538 if (zend_hash_num_elements(d) != 1) {
539 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
541 }
542
543 members_zv = zend_hash_index_find(d, 0);
544 if (!members_zv || Z_TYPE_P(members_zv) != IS_ARRAY) {
545 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
547 }
548 object_properties_load(&randomizer->std, Z_ARRVAL_P(members_zv));
549 if (EG(exception)) {
550 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
552 }
553
554 zengine = zend_read_property(randomizer->std.ce, &randomizer->std, "engine", strlen("engine"), 1, NULL);
555 if (Z_TYPE_P(zengine) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(zengine), random_ce_Random_Engine)) {
556 zend_throw_exception(NULL, "Invalid serialization data for Random\\Randomizer object", 0);
558 }
559
560 randomizer_common_init(randomizer, Z_OBJ_P(zengine));
561}
562/* }}} */
bool exception
Definition assert.c:30
PHPAPI const php_random_algo php_random_algo_mt19937
PHPAPI const php_random_algo php_random_algo_user
Definition engine_user.c:76
#define max(a, b)
Definition exif.c:60
memcpy(ptr1, ptr2, size)
zend_long offset
PHPAPI double php_random_gammasection_closed_closed(php_random_algo_with_state engine, double min, double max)
PHPAPI double php_random_gammasection_open_closed(php_random_algo_with_state engine, double min, double max)
PHPAPI double php_random_gammasection_open_open(php_random_algo_with_state engine, double min, double max)
PHPAPI double php_random_gammasection_closed_open(php_random_algo_with_state engine, double min, double max)
#define NULL
Definition gdcache.h:45
#define PHP_METHOD
Definition php.h:365
#define min(a, b)
PHPAPI zend_class_entry * random_ce_Random_Engine
#define Z_RANDOM_RANDOMIZER_P(zval)
Definition php_random.h:149
PHPAPI zend_class_entry * random_ce_Random_Randomizer
PHPAPI void * php_random_status_alloc(const php_random_algo *algo, const bool persistent)
Definition random.c:238
struct _php_random_algo_with_state php_random_algo_with_state
PHPAPI zend_class_entry * random_ce_Random_RandomException
struct _php_random_status_state_mt19937 php_random_status_state_mt19937
PHPAPI zend_class_entry * random_ce_Random_IntervalBoundary
struct _php_random_status_state_user php_random_status_state_user
struct _php_random_engine php_random_engine
PHPAPI zend_class_entry * random_ce_Random_Engine_Secure
#define PHP_MT_RAND_MAX
Definition php_random.h:50
@ MT_RAND_MT19937
Definition php_random.h:53
#define PHP_RANDOM_RANGE_ATTEMPTS
Definition php_random.h:57
struct _php_random_randomizer php_random_randomizer
PHPAPI zend_class_entry * random_ce_Random_BrokenRandomEngineError
struct _php_random_result php_random_result
PHPAPI bool php_binary_string_shuffle(php_random_algo_with_state engine, char *str, zend_long len)
Definition string.c:5941
PHPAPI bool php_array_data_shuffle(php_random_algo_with_state engine, zval *array)
Definition array.c:3212
PHPAPI bool php_array_pick_keys(php_random_algo_with_state engine, zval *input, zend_long num_req, zval *retval, bool silent)
Definition array.c:6208
php_random_algo_with_state engine
Definition php_random.h:118
char type
Definition zend.h:148
HashTable function_table
Definition zend.h:163
zend_class_entry * ce
Definition zend_types.h:560
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format,...)
Definition zend.c:1772
ZEND_API ZEND_COLD void zend_value_error(const char *format,...)
Definition zend.c:1849
ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *class_type)
Definition zend_API.c:1849
ZEND_API zval * zend_read_property(zend_class_entry *scope, zend_object *object, const char *name, size_t name_length, bool silent, zval *rv)
Definition zend_API.c:5201
ZEND_API void zend_update_property(zend_class_entry *scope, zend_object *object, const char *name, size_t name_length, zval *value)
Definition zend_API.c:4991
ZEND_API ZEND_COLD void zend_argument_must_not_be_empty_error(uint32_t arg_num)
Definition zend_API.c:443
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format,...)
Definition zend_API.c:433
ZEND_API void object_properties_load(zend_object *object, HashTable *properties)
Definition zend_API.c:1728
#define ZEND_PARSE_PARAMETERS_END()
Definition zend_API.h:1641
#define RETURN_DOUBLE(d)
Definition zend_API.h:1038
#define ZEND_PARSE_PARAMETERS_NONE()
Definition zend_API.h:1623
#define RETVAL_DOUBLE(d)
Definition zend_API.h:1012
#define Z_PARAM_OPTIONAL
Definition zend_API.h:1667
#define Z_PARAM_STR(dest)
Definition zend_API.h:2086
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
Definition zend_API.h:1620
#define Z_PARAM_LONG(dest)
Definition zend_API.h:1896
#define RETURN_LONG(l)
Definition zend_API.h:1037
#define RETURN_THROWS()
Definition zend_API.h:1060
#define Z_PARAM_ARRAY_HT(dest)
Definition zend_API.h:1852
#define Z_PARAM_OBJ_OF_CLASS(dest, _ce)
Definition zend_API.h:1997
#define Z_PARAM_DOUBLE(dest)
Definition zend_API.h:1803
#define RETURN_STR(s)
Definition zend_API.h:1039
#define ZEND_THIS
Definition zend_API.h:523
#define Z_PARAM_OBJECT_OF_CLASS_OR_NULL(dest, _ce)
Definition zend_API.h:1979
#define Z_PARAM_ARRAY(dest)
Definition zend_API.h:1682
#define RETVAL_STRINGL(s, l)
Definition zend_API.h:1018
#define RETURN_STR_COPY(s)
Definition zend_API.h:1042
#define array_init(arg)
Definition zend_API.h:537
struct _zval_struct zval
strlen(string $string)
#define ZEND_INTERNAL_CLASS
ZEND_API ZEND_COLD zend_object * zend_throw_exception(zend_class_entry *exception_ce, const char *message, zend_long code)
union _zend_function zend_function
#define EG(v)
ZEND_API zval *ZEND_FASTCALL zend_hash_next_index_insert(HashTable *ht, zval *pData)
Definition zend_hash.c:1224
ZEND_API zval *ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h)
Definition zend_hash.c:2701
int32_t zend_long
Definition zend_long.h:42
uint32_t zend_ulong
Definition zend_long.h:43
struct _zend_string zend_string
ZEND_API HashTable * zend_std_get_properties(zend_object *zobj)
#define OBJ_RELEASE(obj)
#define zend_finite(a)
#define ZEND_UNREACHABLE()
#define UNEXPECTED(condition)
struct _zend_object zend_object
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_INIT_LITERAL(s, persistent)
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
#define Z_TYPE_P(zval_p)
Definition zend_types.h:660
#define ZVAL_DUP(z, v)
#define Z_STRVAL_P(zval_p)
Definition zend_types.h:975
#define Z_ARRVAL_P(zval_p)
Definition zend_types.h:987
struct _zend_array HashTable
Definition zend_types.h:386
#define Z_OBJ_P(zval_p)
Definition zend_types.h:990
#define IS_ARRAY
Definition zend_types.h:607
#define Z_STR_P(zval_p)
Definition zend_types.h:972
#define Z_STRLEN_P(zval_p)
Definition zend_types.h:978
#define Z_OBJCE_P(zval_p)
#define Z_TRY_ADDREF(z)
#define IS_OBJECT
Definition zend_types.h:608
#define ZVAL_ARR(z, a)
#define ZVAL_COPY(z, v)
#define Z_DVAL_P(zval_p)
Definition zend_types.h:969
#define ZVAL_COPY_VALUE(z, v)
zval retval
zval * return_value
bool result