Coverage Report

Created: 2026-06-02 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/random/engine_mt19937.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
12
   |          Zeev Suraski <zeev@php.net>                                 |
13
   |          Pedro Melo <melo@ip.pt>                                     |
14
   |          Sterling Hughes <sterling@php.net>                          |
15
   |          Go Kudo <zeriyoshi@php.net>                                 |
16
   |                                                                      |
17
   | Based on code from: Richard J. Wagner <rjwagner@writeme.com>         |
18
   |                     Makoto Matsumoto <matumoto@math.keio.ac.jp>      |
19
   |                     Takuji Nishimura                                 |
20
   |                     Shawn Cokus <Cokus@math.washington.edu>          |
21
   +----------------------------------------------------------------------+
22
*/
23
24
#ifdef HAVE_CONFIG_H
25
# include "config.h"
26
#endif
27
28
#include "php.h"
29
#include "php_random.h"
30
#include "php_random_csprng.h"
31
32
#include "Zend/zend_exceptions.h"
33
34
/*
35
  The following mt19937 algorithms are based on a C++ class MTRand by
36
  Richard J. Wagner. For more information see the web page at
37
  http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/MersenneTwister.h
38
39
  Mersenne Twister random number generator -- a C++ class MTRand
40
  Based on code by Makoto Matsumoto, Takuji Nishimura, and Shawn Cokus
41
  Richard J. Wagner  v1.0  15 May 2003  rjwagner@writeme.com
42
43
  The Mersenne Twister is an algorithm for generating random numbers.  It
44
  was designed with consideration of the flaws in various other generators.
45
  The period, 2^19937-1, and the order of equidistribution, 623 dimensions,
46
  are far greater.  The generator is also fast; it avoids multiplication and
47
  division, and it benefits from caches and pipelines.  For more information
48
  see the inventors' web page at http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
49
50
  Reference
51
  M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-Dimensionally
52
  Equidistributed Uniform Pseudo-Random Number Generator", ACM Transactions on
53
  Modeling and Computer Simulation, Vol. 8, No. 1, January 1998, pp 3-30.
54
55
  Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
56
  Copyright (C) 2000 - 2003, Richard J. Wagner
57
  All rights reserved.
58
59
  Redistribution and use in source and binary forms, with or without
60
  modification, are permitted provided that the following conditions
61
  are met:
62
63
  1. Redistributions of source code must retain the above copyright
64
     notice, this list of conditions and the following disclaimer.
65
66
  2. Redistributions in binary form must reproduce the above copyright
67
     notice, this list of conditions and the following disclaimer in the
68
     documentation and/or other materials provided with the distribution.
69
70
  3. The names of its contributors may not be used to endorse or promote
71
     products derived from this software without specific prior written
72
     permission.
73
74
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
75
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
76
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
77
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
78
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
79
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
80
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
81
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
82
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
83
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
84
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
85
*/
86
87
13.4k
#define N             624                 /* length of state vector */
88
ZEND_STATIC_ASSERT(
89
  N == sizeof(((php_random_status_state_mt19937*)0)->state) / sizeof(((php_random_status_state_mt19937*)0)->state[0]),
90
  "Assumed length of Mt19937 state vector does not match actual size."
91
);
92
40
#define M             (397)                /* a period parameter */
93
12.4k
#define hiBit(u)      ((u) & 0x80000000U)  /* mask all but highest   bit of u */
94
12.4k
#define loBit(u)      ((u) & 0x00000001U)  /* mask all but lowest    bit of u */
95
12.4k
#define loBits(u)     ((u) & 0x7FFFFFFFU)  /* mask     the highest   bit of u */
96
12.4k
#define mixBits(u, v) (hiBit(u) | loBits(v)) /* move hi bit of u to hi bit of v */
97
98
12.4k
#define twist(m,u,v)  (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(v))) & 0x9908b0dfU))
99
0
#define twist_php(m,u,v)  (m ^ (mixBits(u,v) >> 1) ^ ((uint32_t)(-(int32_t)(loBit(u))) & 0x9908b0dfU))
100
101
static inline void mt19937_reload(php_random_status_state_mt19937 *state)
102
20
{
103
20
  uint32_t *p = state->state;
104
105
20
  if (state->mode == MT_RAND_MT19937) {
106
4.56k
    for (uint32_t i = N - M; i--; ++p) {
107
4.54k
      *p = twist(p[M], p[0], p[1]);
108
4.54k
    }
109
7.94k
    for (uint32_t i = M; --i; ++p) {
110
7.92k
      *p = twist(p[M-N], p[0], p[1]);
111
7.92k
    }
112
20
    *p = twist(p[M-N], p[0], state->state[0]);
113
20
  } else {
114
0
    for (uint32_t i = N - M; i--; ++p) {
115
0
      *p = twist_php(p[M], p[0], p[1]);
116
0
    }
117
0
    for (uint32_t i = M; --i; ++p) {
118
0
      *p = twist_php(p[M-N], p[0], p[1]);
119
0
    }
120
0
    *p = twist_php(p[M-N], p[0], state->state[0]);
121
0
  }
122
123
20
  state->count = 0;
124
20
}
125
126
PHPAPI inline void php_random_mt19937_seed32(php_random_status_state_mt19937 *state, uint32_t seed)
127
20
{
128
20
  uint32_t i, prev_state;
129
130
  /* Initialize generator state with seed
131
     See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier.
132
     In previous versions, most significant bits (MSBs) of the seed affect
133
     only MSBs of the state array.  Modified 9 Jan 2002 by Makoto Matsumoto. */
134
20
  state->state[0] = seed;
135
12.4k
  for (i = 1; i < N; i++) {
136
12.4k
    prev_state = state->state[i - 1];
137
12.4k
    state->state[i] = (1812433253U * (prev_state  ^ (prev_state  >> 30)) + i) & 0xffffffffU;
138
12.4k
  }
139
20
  state->count = i;
140
141
20
  mt19937_reload(state);
142
20
}
143
144
static php_random_result generate(void *state)
145
951
{
146
951
  php_random_status_state_mt19937 *s = state;
147
951
  uint32_t s1;
148
149
951
  if (s->count >= N) {
150
0
    mt19937_reload(s);
151
0
  }
152
153
951
  s1 = s->state[s->count++];
154
951
  s1 ^= (s1 >> 11);
155
951
  s1 ^= (s1 << 7) & 0x9d2c5680U;
156
951
  s1 ^= (s1 << 15) & 0xefc60000U;
157
158
951
  return (php_random_result){
159
951
    .size = sizeof(uint32_t),
160
951
    .result = (uint64_t) (s1 ^ (s1 >> 18)),
161
951
  };
162
951
}
163
164
static zend_long range(void *state, zend_long min, zend_long max)
165
951
{
166
951
  return php_random_range((php_random_algo_with_state){
167
951
    .algo = &php_random_algo_mt19937,
168
951
    .state = state,
169
951
  }, min, max);
170
951
}
171
172
static bool serialize(void *state, HashTable *data)
173
0
{
174
0
  php_random_status_state_mt19937 *s = state;
175
0
  zval t;
176
177
0
  for (uint32_t i = 0; i < N; i++) {
178
0
    ZVAL_STR(&t, php_random_bin2hex_le(&s->state[i], sizeof(uint32_t)));
179
0
    zend_hash_next_index_insert(data, &t);
180
0
  }
181
0
  ZVAL_LONG(&t, s->count);
182
0
  zend_hash_next_index_insert(data, &t);
183
0
  ZVAL_LONG(&t, s->mode);
184
0
  zend_hash_next_index_insert(data, &t);
185
186
0
  return true;
187
0
}
188
189
static bool unserialize(void *state, HashTable *data)
190
0
{
191
0
  php_random_status_state_mt19937 *s = state;
192
0
  zval *t;
193
194
  /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
195
0
  if (zend_hash_num_elements(data) != (N + 2)) {
196
0
    return false;
197
0
  }
198
199
0
  for (uint32_t i = 0; i < N; i++) {
200
0
    t = zend_hash_index_find(data, i);
201
0
    if (!t || Z_TYPE_P(t) != IS_STRING || Z_STRLEN_P(t) != (2 * sizeof(uint32_t))) {
202
0
      return false;
203
0
    }
204
0
    if (!php_random_hex2bin_le(Z_STR_P(t), &s->state[i])) {
205
0
      return false;
206
0
    }
207
0
  }
208
0
  t = zend_hash_index_find(data, N);
209
0
  if (!t || Z_TYPE_P(t) != IS_LONG) {
210
0
    return false;
211
0
  }
212
0
  s->count = Z_LVAL_P(t);
213
0
  if (s->count > N) {
214
0
    return false;
215
0
  }
216
217
0
  t = zend_hash_index_find(data, N + 1);
218
0
  if (!t || Z_TYPE_P(t) != IS_LONG) {
219
0
    return false;
220
0
  }
221
0
  s->mode = Z_LVAL_P(t);
222
0
  if (s->mode != MT_RAND_MT19937 && s->mode != MT_RAND_PHP) {
223
0
    return false;
224
0
  }
225
226
0
  return true;
227
0
}
228
229
PHPAPI const php_random_algo php_random_algo_mt19937 = {
230
  sizeof(php_random_status_state_mt19937),
231
  generate,
232
  range,
233
  serialize,
234
  unserialize
235
};
236
237
/* {{{ php_random_mt19937_seed_default */
238
PHPAPI void php_random_mt19937_seed_default(php_random_status_state_mt19937 *state)
239
15
{
240
15
  uint32_t seed = 0;
241
242
15
  if (php_random_bytes_silent(&seed, sizeof(seed)) == FAILURE) {
243
0
    seed = (uint32_t)php_random_generate_fallback_seed();
244
0
  }
245
246
15
  php_random_mt19937_seed32(state, seed);
247
15
}
248
/* }}} */
249
250
/* {{{ Random\Engine\Mt19937::__construct() */
251
PHP_METHOD(Random_Engine_Mt19937, __construct)
252
2
{
253
2
  php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine;
254
2
  php_random_status_state_mt19937 *state = engine.state;
255
2
  zend_long seed, mode = MT_RAND_MT19937;
256
2
  bool seed_is_null = true;
257
258
6
  ZEND_PARSE_PARAMETERS_START(0, 2)
259
6
    Z_PARAM_OPTIONAL;
260
6
    Z_PARAM_LONG_OR_NULL(seed, seed_is_null);
261
0
    Z_PARAM_LONG(mode);
262
2
  ZEND_PARSE_PARAMETERS_END();
263
264
2
  switch (mode) {
265
2
    case MT_RAND_MT19937:
266
2
      state->mode = MT_RAND_MT19937;
267
2
      break;
268
0
    case MT_RAND_PHP:
269
0
      zend_error(E_DEPRECATED, "The MT_RAND_PHP variant of Mt19937 is deprecated");
270
0
      state->mode = MT_RAND_PHP;
271
0
      break;
272
0
    default:
273
0
      zend_argument_value_error(2, "must be either MT_RAND_MT19937 or MT_RAND_PHP");
274
0
      RETURN_THROWS();
275
2
  }
276
277
2
  if (seed_is_null) {
278
    /* MT19937 has a very large state, uses CSPRNG for seeding only */
279
2
    if (php_random_bytes_throw(&seed, sizeof(seed)) == FAILURE) {
280
0
      zend_throw_exception(random_ce_Random_RandomException, "Failed to generate a random seed", 0);
281
0
      RETURN_THROWS();
282
0
    }
283
2
  }
284
285
2
  php_random_mt19937_seed32(state, seed);
286
2
}
287
/* }}} */
288
289
/* {{{ Random\Engine\Mt19937::generate() */
290
PHP_METHOD(Random_Engine_Mt19937, generate)
291
0
{
292
0
  php_random_algo_with_state engine = Z_RANDOM_ENGINE_P(ZEND_THIS)->engine;
293
0
  zend_string *bytes;
294
295
0
  ZEND_PARSE_PARAMETERS_NONE();
296
297
0
  php_random_result generated = engine.algo->generate(engine.state);
298
0
  if (EG(exception)) {
299
0
    RETURN_THROWS();
300
0
  }
301
302
0
  bytes = zend_string_alloc(generated.size, false);
303
304
  /* Endianness safe copy */
305
0
  for (size_t i = 0; i < generated.size; i++) {
306
0
    ZSTR_VAL(bytes)[i] = (generated.result >> (i * 8)) & 0xff;
307
0
  }
308
0
  ZSTR_VAL(bytes)[generated.size] = '\0';
309
310
0
  RETURN_STR(bytes);
311
0
}
312
/* }}} */
313
314
/* {{{ Random\Engine\Mt19937::__serialize() */
315
PHP_METHOD(Random_Engine_Mt19937, __serialize)
316
0
{
317
0
  php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
318
0
  zval t;
319
320
0
  ZEND_PARSE_PARAMETERS_NONE();
321
322
0
  array_init(return_value);
323
324
  /* members */
325
0
  ZVAL_EMPTY_ARRAY(&t);
326
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
327
328
  /* state */
329
0
  array_init(&t);
330
0
  zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &t);
331
0
  if (!engine->engine.algo->serialize(engine->engine.state, Z_ARRVAL(t))) {
332
0
    zend_throw_exception(NULL, "Engine serialize failed", 0);
333
0
    RETURN_THROWS();
334
0
  }
335
0
}
336
/* }}} */
337
338
/* {{{ Random\Engine\Mt19937::__unserialize() */
339
PHP_METHOD(Random_Engine_Mt19937, __unserialize)
340
0
{
341
0
  php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
342
0
  HashTable *d;
343
0
  zval *t;
344
345
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
346
0
    Z_PARAM_ARRAY_HT(d);
347
0
  ZEND_PARSE_PARAMETERS_END();
348
349
  /* Verify the expected number of elements, this implicitly ensures that no additional elements are present. */
350
0
  if (zend_hash_num_elements(d) != 2) {
351
0
    zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name));
352
0
    RETURN_THROWS();
353
0
  }
354
355
  /* members */
356
0
  t = zend_hash_index_find(d, 0);
357
0
  if (!t || Z_TYPE_P(t) != IS_ARRAY) {
358
0
    zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name));
359
0
    RETURN_THROWS();
360
0
  }
361
0
  object_properties_load(&engine->std, Z_ARRVAL_P(t));
362
0
  if (EG(exception)) {
363
0
    zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name));
364
0
    RETURN_THROWS();
365
0
  }
366
367
  /* state */
368
0
  t = zend_hash_index_find(d, 1);
369
0
  if (!t || Z_TYPE_P(t) != IS_ARRAY) {
370
0
    zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name));
371
0
    RETURN_THROWS();
372
0
  }
373
0
  if (!engine->engine.algo->unserialize(engine->engine.state, Z_ARRVAL_P(t))) {
374
0
    zend_throw_exception_ex(NULL, 0, "Invalid serialization data for %s object", ZSTR_VAL(engine->std.ce->name));
375
0
    RETURN_THROWS();
376
0
  }
377
0
}
378
/* }}} */
379
380
/* {{{ Random\Engine\Mt19937::__debugInfo() */
381
PHP_METHOD(Random_Engine_Mt19937, __debugInfo)
382
0
{
383
0
  php_random_engine *engine = Z_RANDOM_ENGINE_P(ZEND_THIS);
384
0
  zval t;
385
386
0
  ZEND_PARSE_PARAMETERS_NONE();
387
388
0
  ZVAL_ARR(return_value, zend_array_dup(zend_std_get_properties_ex(&engine->std)));
389
390
0
  if (engine->engine.algo->serialize) {
391
0
    array_init(&t);
392
0
    zend_hash_str_add(Z_ARR_P(return_value), "__states", strlen("__states"), &t);
393
0
    if (!engine->engine.algo->serialize(engine->engine.state, Z_ARRVAL(t))) {
394
0
      zend_throw_exception(NULL, "Engine serialize failed", 0);
395
      RETURN_THROWS();
396
0
    }
397
0
  }
398
0
}
399
/* }}} */