Skip to content

Commit

Permalink
Add support of 'random_int' and 'random_bytes' builtins (#977)
Browse files Browse the repository at this point in the history
  • Loading branch information
apolyakov authored Apr 10, 2024
1 parent 82f3514 commit e80556b
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 11 deletions.
2 changes: 2 additions & 0 deletions builtin-functions/_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ function getrandmax() ::: int;
function mt_srand ($seed ::: int = PHP_INT_MIN) ::: void;
function mt_rand ($l ::: int = TODO_OVERLOAD, $r ::: int = TODO_OVERLOAD) ::: int;
function mt_getrandmax() ::: int;
function random_int($l ::: int, $r ::: int) ::: int | false;
function random_bytes($length ::: int) ::: string | false;

function hash_algos () ::: string[];
function hash_hmac_algos () ::: string[];
Expand Down
2 changes: 2 additions & 0 deletions builtin-functions/spl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class RangeException extends RuntimeException {}
class UnderflowException extends RuntimeException {}
class UnexpectedValueException extends RuntimeException {}

class Random\RandomException extends Exception {}

// https://www.php.net/manual/en/class.arrayiterator.php
// TODO: make it work with T[] arrays when generic classes are available.
// For now, it only supports mixed[].
Expand Down
1 change: 0 additions & 1 deletion runtime/exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ Exception new_Exception(const string &file, int64_t line, const string &message,
return f$_exception_set_location(f$Exception$$__construct(Exception().alloc(), message, code), file, line);
}


Exception f$err(const string &file, int64_t line, const string &code, const string &desc) {
return new_Exception(file, line, (static_SB.clean() << "ERR_" << code << ": " << desc).str(), 0);
}
Expand Down
22 changes: 22 additions & 0 deletions runtime/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#pragma once

#include <type_traits>

#include "common/algorithms/hashes.h"
#include "common/wrappers/string_view.h"
#include "runtime/dummy-visitor-methods.h"
Expand Down Expand Up @@ -157,6 +159,22 @@ Exception new_Exception(const string &file, int64_t line, const string &message

Exception f$err(const string &file, int64_t line, const string &code, const string &desc = string());

template <typename T>
inline class_instance<T> make_throwable(const string &file, int64_t line, int64_t code, const string &desc) noexcept {
static_assert(
std::is_base_of_v<C$Throwable, T>,
"Template argument must be a subtype of C$Throwable");

auto ci = make_instance<T>();

auto *ins_ptr = ci.get();
ins_ptr->$file = file;
ins_ptr->$line = line;
ins_ptr->$code = code;
ins_ptr->$message = desc;

return ci;
}

string f$Exception$$getMessage(const Exception &e);
string f$Error$$getMessage(const Error &e);
Expand Down Expand Up @@ -265,3 +283,7 @@ struct C$UnderflowException : public C$RuntimeException {
struct C$UnexpectedValueException : public C$RuntimeException {
const char *get_class() const noexcept override { return "UnexpectedValueException"; }
};

struct C$Random$RandomException : public C$Exception {
const char *get_class() const noexcept override { return "Random\\RandomException"; }
};
60 changes: 58 additions & 2 deletions runtime/math_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
// Copyright (c) 2020 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#include "runtime/math_functions.h"

#include <chrono>
#include <random>
#include <cstring>
#include <cerrno>
#include <sys/time.h>

#if defined(__APPLE__)
#include <stdlib.h>
#else
#include <sys/random.h>
#endif

#include "common/cycleclock.h"
#include "runtime/math_functions.h"
#include "runtime/exception.h"
#include "runtime/critical_section.h"
#include "runtime/string_functions.h"
#include "server/php-engine-vars.h"
Expand All @@ -20,6 +28,15 @@ namespace {
overflow = overflow || r < x || r > static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
return r;
}

int64_t secure_rand_buf(char * const buf, int64_t length) noexcept {
#if defined(__APPLE__)
arc4random_buf(static_cast<void*>(buf), static_cast<size_t>(length));
return 0;
#else
return getrandom(buf, static_cast<size_t>(length), 0x0);
#endif
}
} // namespace

int64_t f$bindec(const string &number) noexcept {
Expand Down Expand Up @@ -209,6 +226,45 @@ int64_t f$getrandmax() noexcept {
return f$mt_getrandmax();
}

Optional<int64_t> f$random_int(int64_t l, int64_t r) noexcept {
if (unlikely(l > r)) {
php_warning("Argument #1 ($min) must be less than or equal to argument #2 ($max)");
return false;
}

if (unlikely(l == r)) {
return l;
}

try {
std::random_device rd{"/dev/urandom"};
std::uniform_int_distribution dist{l, r};

return dist(rd);
} catch (const std::exception &e) {
php_warning("Source of randomness cannot be found: %s", e.what());
return false;
} catch (...) {
php_critical_error("Unhandled exception");
}
}

Optional<string> f$random_bytes(int64_t length) noexcept {
if (unlikely(length < 1)) {
php_warning("Argument #1 ($length) must be greater than 0");
return false;
}

string str{static_cast<string::size_type>(length), false};

if (secure_rand_buf(str.buffer(), static_cast<size_t>(length)) == -1) {
php_warning("Source of randomness cannot be found: %s", std::strerror(errno));
return false;
}

return str;
}

mixed f$abs(const mixed &v) {
mixed num = v.to_numeric();
if (num.is_int()) {
Expand Down
4 changes: 4 additions & 0 deletions runtime/math_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ int64_t f$rand() noexcept;

int64_t f$getrandmax() noexcept;

Optional<int64_t> f$random_int(int64_t l, int64_t r) noexcept;

Optional<string> f$random_bytes(int64_t length) noexcept;


template<class T>
inline T f$min(const array<T> &a);
Expand Down
5 changes: 3 additions & 2 deletions tests/kphp_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def __init__(self, file_path, test_tmp_dir, tags, env_vars: dict, out_regexps=No
self.env_vars = env_vars
self.out_regexps = out_regexps
self.forbidden_regexps = forbidden_regexps
self.php_version = next((tag for tag in tags if tag.startswith("php")), "php7.4")

def is_ok(self):
return "ok" in self.tags
Expand All @@ -43,15 +44,15 @@ def is_kphp_runtime_should_not_warn(self):
return "kphp_runtime_should_not_warn" in self.tags

def is_php8(self):
return "php8" in self.tags
return self.php_version.startswith("php8")

def make_kphp_once_runner(self, use_nocc, cxx_name):
tester_dir = os.path.abspath(os.path.dirname(__file__))
return KphpRunOnce(
php_script_path=self.file_path,
working_dir=os.path.abspath(os.path.join(self.test_tmp_dir, "working_dir")),
artifacts_dir=os.path.abspath(os.path.join(self.test_tmp_dir, "artifacts")),
php_bin=search_php_bin(php8_require=self.is_php8()),
php_bin=search_php_bin(php_version=self.php_version),
extra_include_dirs=[os.path.join(tester_dir, "php_include")],
vkext_dir=os.path.abspath(os.path.join(tester_dir, os.path.pardir, "objs", "vkext")),
use_nocc=use_nocc,
Expand Down
10 changes: 10 additions & 0 deletions tests/phpt/dl/385_random_bytes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@ok php8
<?php

function test_random_bytes() {
for ($i = 1; $i < 50; $i += 1) {
var_dump(strlen(random_bytes($i)));
}
}

test_random_bytes();
33 changes: 33 additions & 0 deletions tests/phpt/dl/386_random_int.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@ok php8
<?php

function test_random_int() {
$x = random_int(100, 500);
var_dump($x >= 100 && $x <= 500);

$x = random_int(-450, -200);
var_dump($x >= -450 && $x <= -200);

$x = random_int(-124, 107);
var_dump($x >= -124 && $x <= 107);

$x = random_int(0, PHP_INT_MAX);
var_dump($x >= 0 && $x <= PHP_INT_MAX);

$x = random_int(-PHP_INT_MAX - 1, 0);
var_dump($x >= -PHP_INT_MAX - 1 && $x <= 0);

$x = random_int(-PHP_INT_MAX - 1, PHP_INT_MAX);
var_dump($x >= (-PHP_INT_MAX - 1) && $x <= PHP_INT_MAX);

var_dump(random_int(0, 0));
var_dump(random_int(1, 1));
var_dump(random_int(-4, -4));
var_dump(random_int(9287167122323, 9287167122323));
var_dump(random_int(-8548276162, -8548276162));

var_dump(random_int(PHP_INT_MAX, PHP_INT_MAX));
var_dump(random_int(-PHP_INT_MAX - 1, -PHP_INT_MAX - 1));
}

test_random_int();
35 changes: 35 additions & 0 deletions tests/phpt/exceptions/spl/02_builtin_exceptions_1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@ok php8.2
KPHP_REQUIRE_FUNCTIONS_TYPING=1
<?php

/*
Random\RandomException (extends Exception)
*/

function fmt_throwable(Throwable $e): string {
return get_class($e) . ":{$e->getLine()}:{$e->getMessage()}:{$e->getCode()}";
}

function test_random_exception() {
$to_throw = new Random\RandomException(__FUNCTION__, __LINE__ + 1);

try {
throw $to_throw;
} catch (LogicException $e) {
var_dump([__LINE__ => 'unreachable']);
} catch (RuntimeException $e) {
var_dump([__LINE__ => 'unreachable']);
} catch (Exception $e) {
var_dump([__LINE__ => fmt_throwable($e)]);
var_dump([__LINE__ => $e->getMessage()]);
}

try {
throw $to_throw;
} catch (Random\RandomException $e) {
var_dump([__LINE__ => fmt_throwable($e)]);
var_dump([__LINE__ => $e->getMessage()]);
}
}

test_random_exception();
18 changes: 14 additions & 4 deletions tests/python/lib/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import sys
import shutil

_SUPPORTED_PHP_VERSIONS = ["php7.4", "php8", "php8.1", "php8.2", "php8.3"]


def _check_file(file_name, file_dir, file_checker):
file_path = os.path.join(file_dir, file_name)
Expand Down Expand Up @@ -103,9 +105,17 @@ def can_ignore_sanitizer_log(sanitizer_log_file):
return ignore_sanitizer


def search_php_bin(php8_require=False):
def search_php_bin(php_version: str):
if sys.platform == "darwin":
return shutil.which("php")
if php8_require:
return shutil.which("php8.1") or shutil.which("php8")
return shutil.which("php7.4")

# checking from oldest to newest versions
for spv in sorted(_SUPPORTED_PHP_VERSIONS):
if spv < php_version:
continue

exe_path = shutil.which(spv)
if exe_path is not None:
return exe_path

return None
4 changes: 2 additions & 2 deletions tests/python/lib/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class KphpCompilerAutoTestCase(BaseTestCase):

def __init__(self, method_name):
super().__init__(method_name)
self.require_php8 = False
self.php_version = "php7.4"

@classmethod
def extra_class_setup(cls):
Expand Down Expand Up @@ -318,7 +318,7 @@ def make_kphp_once_runner(self, php_script_path):
php_script_path=os.path.join(self.test_dir, php_script_path),
artifacts_dir=self.kphp_server_working_dir,
working_dir=self.kphp_build_working_dir,
php_bin=search_php_bin(php8_require=self.require_php8),
php_bin=search_php_bin(php_version=self.php_version),
use_nocc=self.should_use_nocc(),
)
self.once_runner_trash_bin.append(once_runner)
Expand Down

0 comments on commit e80556b

Please sign in to comment.