diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d62ea44..629e391 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,13 +57,18 @@ jobs: test: runs-on: ubuntu-latest - name: "Test | PHP ${{ matrix.php-version }}" + name: "Test | PHP ${{ matrix.php-version }} ${{ matrix.debug }}" strategy: matrix: php-version: - "8.1" - "8.2" - "8.3" + debug: + - "" + include: + - php-version: "8.1" + debug: "debug" steps: - uses: actions/checkout@v4 @@ -84,7 +89,9 @@ jobs: - run: phpize - - run: ./configure --enable-compile-warnings=error + - run: ./configure --enable-compile-warnings=error "${EXTRA_FLAGS}" + env: + EXTRA_FLAGS: ${{ matrix.debug == 'debug' && '--enable-perfidious-debug' || '' }} - run: make diff --git a/README.md b/README.md index ce936e0..ecaf541 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Some notable generic perf events are: | Name | Default | Changeable | Description | | --------------------- | -------- | ----------- | ------------ | +| `perfidious.overflow_mode` | `0` | `PHP_INI_SYSTEM` | Sets the overflow behavior when casting counters from `uint64_t` to `zend_long`. See the constants `Perfidious\OVERFLOW_*` for other values. Note that when set to `Perfidious\OVERFLOW_WARN`, `read` and `readArray` may return `NULL`, despite their type signatures indicating otherwise. | | `perfidious.global.enable` | `0` | `PHP_INI_SYSTEM` | Set to `1` to enable the global handle. This handle is kept open between requests. You can read from this handle via e.g. `var_dump(Perfidious\global_handle()?->read());`. | | `perfidious.global.metrics` | `perf::PERF_COUNT_HW_CPU_CYCLES:u`, `perf::PERF_COUNT_HW_INSTRUCTIONS:u` | `PHP_INI_SYSTEM` | The metrics to monitor with the global handle. | | `perfidious.request.enable` | `0` | `PHP_INI_SYSTEM` | Set to `1` to enable the per-request handle. This handle is kept open between requests, but reset before and after. You can read from this handle via e.g. `var_dump(Perfidious\request_handle()?->read());` | diff --git a/flake.nix b/flake.nix index 80f266c..a972977 100644 --- a/flake.nix +++ b/flake.nix @@ -79,10 +79,12 @@ stdenv ? pkgs.stdenv, php ? pkgs.php, libpfm ? pkgs.libpfm, + debugSupport ? false, }: pkgs.callPackage ./nix/derivation.nix { inherit src; inherit stdenv php libpfm; + inherit debugSupport; buildPecl = pkgs.callPackage (nixpkgs + "/pkgs/build-support/php/build-pecl.nix") { inherit php stdenv; }; @@ -146,7 +148,7 @@ # in opcache and relies on mkWrapper to load extensions export TEST_PHP_ARGS='-c ${package.php.phpIni}' # php.unwrapped from the buildDeps is overwriting php - export PATH="${package.php}/bin:$PATH" + export PATH="${package.php}/bin:./vendor/bin:$PATH" ''; }; @@ -200,21 +202,31 @@ }; # @see https://github.com/NixOS/nixpkgs/pull/110787 - buildConfs = lib.cartesianProductOfSets { - php = ["php81" "php82" "php83"]; - stdenv = [ - "gcc" - "clang" - # totally broken - # "musl" + buildConfs = + (lib.cartesianProductOfSets { + php = ["php81" "php82" "php83"]; + stdenv = [ + "gcc" + "clang" + # totally broken + # "musl" + ]; + libpfm = ["libpfm" "libpfm-unstable"]; + }) + ++ [ + { + php = "php81"; + stdenv = "gcc"; + libpfm = "libpfm"; + debugSupport = true; + } ]; - libpfm = ["libpfm" "libpfm-unstable"]; - }; buildFn = { php, libpfm, stdenv, + debugSupport ? false, }: lib.nameValuePair (lib.concatStringsSep "-" (lib.filter (v: v != "") [ @@ -226,12 +238,18 @@ then "" else "${libpfm}" ) + ( + if debugSupport + then "debug" + else "" + ) ])) ( makePackage { php = matrix.php.${php}; libpfm = matrix.libpfm.${libpfm}; stdenv = matrix.stdenv.${stdenv}; + inherit debugSupport; } ); diff --git a/nix/derivation.nix b/nix/derivation.nix index 3612255..c163e75 100644 --- a/nix/derivation.nix +++ b/nix/derivation.nix @@ -10,6 +10,7 @@ buildPecl, src, checkSupport ? false, + debugSupport ? false, WerrorSupport ? checkSupport, valgrindSupport ? true, }: @@ -32,7 +33,8 @@ buildPecl rec { configureFlags = [] ++ lib.optional WerrorSupport "--enable-compile-warnings=error" - ++ lib.optionals (!WerrorSupport) ["--enable-compile-warnings=yes" "--disable-Werror"]; + ++ lib.optionals (!WerrorSupport) ["--enable-compile-warnings=yes" "--disable-Werror"] + ++ lib.optional debugSupport "--enable-perfidious-debug"; makeFlags = ["phpincludedir=$(dev)/include"]; outputs = ["out" "dev"]; diff --git a/perfidious.stub.php b/perfidious.stub.php index b247b78..21a565a 100644 --- a/perfidious.stub.php +++ b/perfidious.stub.php @@ -4,6 +4,10 @@ const VERSION = "0.1.0"; +const OVERFLOW_THROW = 0; +const OVERFLOW_WARN = 1; +const OVERFLOW_SATURATE = 2; +const OVERFLOW_WRAP = 3; /** * @throws PmuNotFoundException @@ -99,6 +103,7 @@ final public function disable(): self /** * Get a raw byte stream from the handle's file descriptor + * * @note closing this resource will cause subsequent calls to read to fail * @return resource */ @@ -107,6 +112,11 @@ final public function rawStream() } /** + * @note If perfidious.overflow_mode is set to Perfidious\OVERFLOW_WARN, this method can return null, despite its + * typehint. If perfidious.overflow_mode is set to any value other than Perfidious\OVERFLOW_THROW, this + * method will *not* throw an OverflowException. + * + * @return ReadResult * @throws OverflowException|IOException * * @phpstan-return ReadResult @@ -116,7 +126,11 @@ final public function read(): ReadResult } /** - * @return array + * @note If perfidious.overflow_mode is set to Perfidious\OVERFLOW_WARN, this method can return null, despite its + * typehint. If perfidious.overflow_mode is set to any value other than Perfidious\OVERFLOW_THROW, this + * method will *not* throw an OverflowException. + * + * @return array * @throws OverflowException|IOException * * @phpstan-return array, int> diff --git a/php_perfidious.h b/php_perfidious.h index 1cb448c..e35ef4c 100644 --- a/php_perfidious.h +++ b/php_perfidious.h @@ -83,6 +83,15 @@ enum perfidious_error_mode PERFIDIOUS_ERROR_MODE_WARNING = 1, }; +enum perfidious_overflow_mode +{ + PERFIDIOUS_OVERFLOW_THROW = 0, + PERFIDIOUS_OVERFLOW_WARN = 1, + PERFIDIOUS_OVERFLOW_SATURATE = 2, + PERFIDIOUS_OVERFLOW_WRAP = 3, + PERFIDIOUS_OVERFLOW_MAX = 3, +}; + PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_exception_interface_ce; PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_pmu_not_found_exception_ce; PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_pmu_event_not_found_exception_ce; @@ -103,6 +112,7 @@ ZEND_BEGIN_MODULE_GLOBALS(perfidious) struct perfidious_handle *request_handle; enum perfidious_error_mode error_mode; + enum perfidious_overflow_mode overflow_mode; ZEND_END_MODULE_GLOBALS(perfidious) ZEND_EXTERN_MODULE_GLOBALS(perfidious); @@ -171,6 +181,7 @@ zend_result perfidious_handle_read_to_array_with_times( ZEND_HOT PERFIDIOUS_PUBLIC PERFIDIOUS_ATTR_NONNULL_ALL +PERFIDIOUS_ATTR_WARN_UNUSED_RESULT zend_result perfidious_handle_read_to_result(const struct perfidious_handle *restrict handle, zval *restrict return_value); diff --git a/src/extension.c b/src/extension.c index 597bccb..f21e203 100644 --- a/src/extension.c +++ b/src/extension.c @@ -38,7 +38,6 @@ #include "ext/standard/php_string.h" #include "php_perfidious.h" -#include "functions.h" #include "handle.h" #include "private.h" @@ -63,6 +62,7 @@ static ZEND_INI_MH(OnUpdateStr) // clang-format off PHP_INI_BEGIN() + STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".overflow_mode", "0", PHP_INI_SYSTEM, OnUpdateLong, overflow_mode, zend_perfidious_globals, perfidious_globals) STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".global.enable", "0", PHP_INI_SYSTEM, OnUpdateBool, global_enable, zend_perfidious_globals, perfidious_globals) STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".global.metrics", DEFAULT_METRICS, PHP_INI_SYSTEM, OnUpdateStr, global_metrics, zend_perfidious_globals, perfidious_globals) STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".request.enable", "0", PHP_INI_SYSTEM, OnUpdateBool, request_enable, zend_perfidious_globals, perfidious_globals) @@ -152,10 +152,28 @@ static PHP_MINIT_FUNCTION(perfidious) return FAILURE; } - REGISTER_INI_ENTRIES(); - +#ifdef PERFIDIOUS_DEBUG + REGISTER_BOOL_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\DEBUG", (zend_bool) PERFIDIOUS_DEBUG, flags); +#else + REGISTER_BOOL_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\DEBUG", false, flags); +#endif REGISTER_STRING_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\VERSION", (char *) PHP_PERFIDIOUS_VERSION, flags); + REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_THROW", PERFIDIOUS_OVERFLOW_THROW, flags); + REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_WARN", PERFIDIOUS_OVERFLOW_WARN, flags); + REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_SATURATE", PERFIDIOUS_OVERFLOW_SATURATE, flags); + REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_WRAP", PERFIDIOUS_OVERFLOW_WRAP, flags); + +#ifdef PERFIDIOUS_DEBUG + do { + char buf[128]; + snprintf(buf, sizeof(buf), "%" PRIu64, UINT64_MAX); + REGISTER_STRING_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\UINT64_MAX", buf, flags); + } while (false); +#endif + + REGISTER_INI_ENTRIES(); + perfidious_exceptions_minit(); perfidious_handle_minit(); perfidious_pmu_event_info_minit(); @@ -284,25 +302,15 @@ static PHP_GINIT_FUNCTION(perfidious) perfidious_globals->error_mode = PERFIDIOUS_ERROR_MODE_THROW; } -// clang-format off -PERFIDIOUS_LOCAL -const zend_function_entry perfidious_functions[] = { - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\get_pmu_info", ZEND_FN(perfidious_get_pmu_info), perfidious_get_pmu_info_arginfo, 0) - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\global_handle", ZEND_FN(perfidious_global_handle), perfidious_global_handle_arginfo, 0) - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmus", ZEND_FN(perfidious_list_pmus), perfidious_list_pmus_arginfo, 0) - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmu_events", ZEND_FN(perfidious_list_pmu_events), perfidious_list_pmu_events_arginfo, 0) - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\open", ZEND_FN(perfidious_open), perfidious_open_arginfo, 0) - ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\request_handle", ZEND_FN(perfidious_request_handle), perfidious_request_handle_arginfo, 0) - PHP_FE_END -}; -// clang-format on - static const zend_module_dep perfidious_deps[] = { {"spl", NULL, NULL, MODULE_DEP_REQUIRED}, {"opcache", NULL, NULL, MODULE_DEP_OPTIONAL}, ZEND_MOD_END, }; +PERFIDIOUS_LOCAL +extern const zend_function_entry perfidious_functions[]; + zend_module_entry perfidious_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, diff --git a/src/functions.c b/src/functions.c index bab859f..6d87327 100644 --- a/src/functions.c +++ b/src/functions.c @@ -29,13 +29,15 @@ #include #include "main/php.h" #include "php_perfidious.h" -#include "functions.h" #include "handle.h" #include "private.h" +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_get_pmu_info_arginfo, false, 1, Perfidious\\PmuInfo, false) + ZEND_ARG_TYPE_INFO(false, pmu, IS_LONG, false) +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_get_pmu_info) +static PHP_FUNCTION(perfidious_get_pmu_info) { zend_long pmu; @@ -48,9 +50,11 @@ PHP_FUNCTION(perfidious_get_pmu_info) } } +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_global_handle_arginfo, false, 0, Perfidious\\Handle, false) +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_global_handle) +static PHP_FUNCTION(perfidious_global_handle) { ZEND_PARSE_PARAMETERS_NONE(); @@ -65,9 +69,11 @@ PHP_FUNCTION(perfidious_global_handle) } } +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(perfidious_list_pmus_arginfo, IS_ARRAY, false) +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_list_pmus) +static PHP_FUNCTION(perfidious_list_pmus) { zend_long index; zval tmp = {0}; @@ -84,9 +90,12 @@ PHP_FUNCTION(perfidious_list_pmus) } } +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(perfidious_list_pmu_events_arginfo, IS_ARRAY, false) + ZEND_ARG_TYPE_INFO(false, pmu, IS_LONG, false) +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_list_pmu_events) +static PHP_FUNCTION(perfidious_list_pmu_events) { zend_long pmu_id; @@ -125,9 +134,14 @@ PHP_FUNCTION(perfidious_list_pmu_events) } } +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_open_arginfo, false, 1, Perfidious\\Handle, false) + ZEND_ARG_TYPE_INFO(false, event_names, IS_ARRAY, false) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(false, pid, IS_LONG, true, "0") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(false, cpu, IS_LONG, true, "-1") +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_open) +static PHP_FUNCTION(perfidious_open) { HashTable *event_names_ht; zend_long pid_zl = 0; @@ -194,9 +208,11 @@ PHP_FUNCTION(perfidious_open) obj->handle = handle; } +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_request_handle_arginfo, false, 0, Perfidious\\Handle, true) +ZEND_END_ARG_INFO() + ZEND_COLD -PERFIDIOUS_LOCAL -PHP_FUNCTION(perfidious_request_handle) +static PHP_FUNCTION(perfidious_request_handle) { ZEND_PARSE_PARAMETERS_NONE(); @@ -210,3 +226,54 @@ PHP_FUNCTION(perfidious_request_handle) RETURN_NULL(); } } + +#ifdef PERFIDIOUS_DEBUG +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(perfidious_debug_uint64_overflow_arginfo, false, 0, IS_LONG, false) + ZEND_ARG_TYPE_INFO(false, overflow_mode, IS_LONG, false) +ZEND_END_ARG_INFO() + +ZEND_COLD +static ZEND_FUNCTION(perfidious_debug_uint64_overflow) +{ + zend_long overflow_mode_zl = -1; + enum perfidious_overflow_mode overflow_mode; + zend_long output; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(overflow_mode_zl) + ZEND_PARSE_PARAMETERS_END(); + + if (overflow_mode_zl == -1) { + overflow_mode = PERFIDIOUS_G(overflow_mode); + } else { + if (overflow_mode_zl > PERFIDIOUS_OVERFLOW_MAX || overflow_mode_zl < 0) { + zend_type_error("Overflow mode out-of-range"); + return; + } + overflow_mode = overflow_mode_zl; + } + + if (!perfidious_uint64_t_to_zend_long(UINT64_MAX, &output, overflow_mode)) { + RETURN_NULL(); + } + + RETURN_LONG(output); +} +#endif + +// clang-format off +PERFIDIOUS_LOCAL +const zend_function_entry perfidious_functions[] = { + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\get_pmu_info", ZEND_FN(perfidious_get_pmu_info), perfidious_get_pmu_info_arginfo, 0) + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\global_handle", ZEND_FN(perfidious_global_handle), perfidious_global_handle_arginfo, 0) + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmus", ZEND_FN(perfidious_list_pmus), perfidious_list_pmus_arginfo, 0) + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmu_events", ZEND_FN(perfidious_list_pmu_events), perfidious_list_pmu_events_arginfo, 0) + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\open", ZEND_FN(perfidious_open), perfidious_open_arginfo, 0) + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\request_handle", ZEND_FN(perfidious_request_handle), perfidious_request_handle_arginfo, 0) +#ifdef PERFIDIOUS_DEBUG + ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\debug_uint64_overflow", ZEND_FN(perfidious_debug_uint64_overflow), perfidious_debug_uint64_overflow_arginfo, 0) +#endif + PHP_FE_END +}; +// clang-format on diff --git a/src/functions.h b/src/functions.h deleted file mode 100644 index e9f91db..0000000 --- a/src/functions.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (C) 2024 John Boehr & contributors - * - * This file is part of php-perfidious. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#ifndef PERFIDIOUS_FUNCTIONS_H -#define PERFIDIOUS_FUNCTIONS_H - -#include -#include - -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_get_pmu_info_arginfo, false, 1, Perfidious\\PmuInfo, false) - ZEND_ARG_TYPE_INFO(false, pmu, IS_LONG, false) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_global_handle_arginfo, false, 0, Perfidious\\Handle, false) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(perfidious_list_pmus_arginfo, IS_ARRAY, false) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(perfidious_list_pmu_events_arginfo, IS_ARRAY, false) - ZEND_ARG_TYPE_INFO(false, pmu, IS_LONG, false) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_open_arginfo, false, 1, Perfidious\\Handle, false) - ZEND_ARG_TYPE_INFO(false, event_names, IS_ARRAY, false) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(false, pid, IS_LONG, true, "0") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(false, cpu, IS_LONG, true, "-1") -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(perfidious_request_handle_arginfo, false, 0, Perfidious\\Handle, true) -ZEND_END_ARG_INFO() - -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_get_pmu_info); -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_global_handle); -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_list_pmus); -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_list_pmu_events); -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_open); -PERFIDIOUS_LOCAL extern ZEND_FUNCTION(perfidious_request_handle); - -#endif /* PERFIDIOUS_FUNCTIONS_H */ diff --git a/src/handle.c b/src/handle.c index 5ac383f..3d3ff78 100644 --- a/src/handle.c +++ b/src/handle.c @@ -239,10 +239,13 @@ zend_result perfidious_handle_read_to_array_with_times( zend_long value_zl = 0; zval tmp = {0}; - PERFIDIOUS_ASSERT_RETURN_EX(perfidious_uint64_t_to_zend_long(value->value, &value_zl), { - zval_ptr_dtor(return_value); - ZVAL_UNDEF(return_value); - }); + PERFIDIOUS_ASSERT_RETURN_EX( + perfidious_uint64_t_to_zend_long(value->value, &value_zl, PERFIDIOUS_OVERFLOW_THROW), + { + zval_ptr_dtor(return_value); + ZVAL_UNDEF(return_value); + } + ); ZVAL_LONG(&tmp, value_zl); zend_symtable_update(Z_ARRVAL_P(return_value), metric->name, &tmp); @@ -255,7 +258,6 @@ zend_result perfidious_handle_read_to_array_with_times( ZEND_HOT PERFIDIOUS_PUBLIC PERFIDIOUS_ATTR_NONNULL_ALL -PERFIDIOUS_ATTR_WARN_UNUSED_RESULT zend_result perfidious_handle_read_to_array(const struct perfidious_handle *restrict handle, zval *restrict return_value) { @@ -279,14 +281,16 @@ perfidious_handle_read_to_result(const struct perfidious_handle *restrict handle PERFIDIOUS_ASSERT_RETURN(err == SUCCESS); zend_long time_enabled_zl = 0; - PERFIDIOUS_ASSERT_RETURN_EX(perfidious_uint64_t_to_zend_long(time_enabled, &time_enabled_zl), { - zval_ptr_dtor(&arr); - }); + PERFIDIOUS_ASSERT_RETURN_EX( + perfidious_uint64_t_to_zend_long(time_enabled, &time_enabled_zl, PERFIDIOUS_OVERFLOW_THROW), + { zval_ptr_dtor(&arr); } + ); zend_long time_running_zl = 0; - PERFIDIOUS_ASSERT_RETURN_EX(perfidious_uint64_t_to_zend_long(time_running, &time_running_zl), { - zval_ptr_dtor(&arr); - }); + PERFIDIOUS_ASSERT_RETURN_EX( + perfidious_uint64_t_to_zend_long(time_running, &time_running_zl, PERFIDIOUS_OVERFLOW_THROW), + { zval_ptr_dtor(&arr); } + ); object_init_ex(return_value, perfidious_read_result_ce); @@ -503,7 +507,9 @@ static PHP_METHOD(PerfidousHandle, read) perfidious_handle_disable(obj->handle); } - perfidious_handle_read_to_result(obj->handle, return_value); + if (UNEXPECTED(FAILURE == perfidious_handle_read_to_result(obj->handle, return_value))) { + RETVAL_NULL(); + } if (orig_enabled) { perfidious_handle_enable(obj->handle); @@ -524,7 +530,7 @@ static PHP_METHOD(PerfidousHandle, readArray) } if (UNEXPECTED(FAILURE == perfidious_handle_read_to_array(obj->handle, return_value))) { - RETURN_NULL(); + RETVAL_NULL(); } if (orig_enabled) { diff --git a/src/private.h b/src/private.h index 2bf5404..d159ca4 100644 --- a/src/private.h +++ b/src/private.h @@ -44,8 +44,6 @@ PERFIDIOUS_LOCAL extern zend_string *PERFIDIOUS_INTERNED_TIME_ENABLED; PERFIDIOUS_LOCAL extern zend_string *PERFIDIOUS_INTERNED_TIME_RUNNING; PERFIDIOUS_LOCAL extern zend_string *PERFIDIOUS_INTERNED_VALUES; -// interned strings for handle - #define PERFIDIOUS_ASSERT_RETURN(expr) \ if (UNEXPECTED(!(expr))) { \ return FAILURE; \ @@ -58,18 +56,37 @@ PERFIDIOUS_LOCAL extern zend_string *PERFIDIOUS_INTERNED_VALUES; return FAILURE; \ } -static inline bool perfidious_uint64_t_to_zend_long(uint64_t from, zend_long *restrict to) +static inline bool +perfidious_uint64_t_to_zend_long(uint64_t from, zend_long *restrict to, enum perfidious_overflow_mode overflow_mode) { #if SIZEOF_UINT64_T >= SIZEOF_ZEND_LONG if (UNEXPECTED(from > ZEND_LONG_MAX)) { - zend_throw_exception_ex( - perfidious_overflow_exception_ce, - 0, - "value too large: %" PRIu64 " > %" ZEND_LONG_FMT_SPEC, - from, - ZEND_LONG_MAX - ); - return false; + switch (overflow_mode) { + default: + case PERFIDIOUS_OVERFLOW_SATURATE: + from = ZEND_LONG_MAX; + break; + + case PERFIDIOUS_OVERFLOW_WRAP: + from = from % ZEND_LONG_MAX; + break; + + case PERFIDIOUS_OVERFLOW_THROW: + zend_throw_exception_ex( + perfidious_overflow_exception_ce, + 0, + "value too large: %" PRIu64 " > %" ZEND_LONG_FMT_SPEC, + from, + ZEND_LONG_MAX + ); + return false; + + case PERFIDIOUS_OVERFLOW_WARN: + php_error_docref( + NULL, E_WARNING, "value too large: %" PRIu64 " > %" ZEND_LONG_FMT_SPEC, from, ZEND_LONG_MAX + ); + return false; + } } #endif diff --git a/tests/info-both.phpt b/tests/info-both.phpt index e37a3de..1826fa5 100644 --- a/tests/info-both.phpt +++ b/tests/info-both.phpt @@ -21,6 +21,7 @@ Authors => %A Directive => Local Value => Master Value perfidious.global.enable => 1 => 1 perfidious.global.metrics => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u +perfidious.overflow_mode => 0 => 0 perfidious.request.enable => 1 => 1 perfidious.request.metrics => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u %A diff --git a/tests/info-global.phpt b/tests/info-global.phpt index 62c5d17..33bf3a8 100644 --- a/tests/info-global.phpt +++ b/tests/info-global.phpt @@ -19,6 +19,7 @@ Authors => %A Directive => Local Value => Master Value perfidious.global.enable => 1 => 1 perfidious.global.metrics => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u +perfidious.overflow_mode => 0 => 0 perfidious.request.enable => 0 => 0 perfidious.request.metrics => %s => %s %A diff --git a/tests/info-request.phpt b/tests/info-request.phpt index abb0c39..75685e2 100644 --- a/tests/info-request.phpt +++ b/tests/info-request.phpt @@ -19,6 +19,7 @@ Authors => %A Directive => Local Value => Master Value perfidious.global.enable => 0 => 0 perfidious.global.metrics => %s => %s +perfidious.overflow_mode => 0 => 0 perfidious.request.enable => 1 => 1 perfidious.request.metrics => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u => perf::PERF_COUNT_SW_CPU_CLOCK:u,perf::PERF_COUNT_SW_PAGE_FAULTS:u,perf::PERF_COUNT_SW_CONTEXT_SWITCHES:u %A diff --git a/tests/info.phpt b/tests/info.phpt index f5401ef..75cd0a2 100644 --- a/tests/info.phpt +++ b/tests/info.phpt @@ -16,6 +16,7 @@ Authors => %A Directive => Local Value => Master Value perfidious.global.enable => 0 => 0 perfidious.global.metrics => %s => %s +perfidious.overflow_mode => 0 => 0 perfidious.request.enable => 0 => 0 perfidious.request.metrics => %s => %s %A diff --git a/tests/overflow/saturate.phpt b/tests/overflow/saturate.phpt new file mode 100644 index 0000000..099151a --- /dev/null +++ b/tests/overflow/saturate.phpt @@ -0,0 +1,15 @@ +--TEST-- +overflow (saturate) +--EXTENSIONS-- +perfidious +--SKIPIF-- + +--INI-- +perfidious.overflow_mode=2 +--FILE-- + +--INI-- +perfidious.overflow_mode=0 +--FILE-- +getMessage()); +} +try { + var_dump(Perfidious\debug_uint64_overflow(Perfidious\OVERFLOW_THROW)); +} catch (Perfidious\OverflowException $e) { + var_dump($e->getMessage()); +} +--EXPECTF-- +string(%d) "value too large: %d > %d" +string(%d) "value too large: %d > %d" \ No newline at end of file diff --git a/tests/overflow/warn.phpt b/tests/overflow/warn.phpt new file mode 100644 index 0000000..8907371 --- /dev/null +++ b/tests/overflow/warn.phpt @@ -0,0 +1,18 @@ +--TEST-- +overflow (warn) +--EXTENSIONS-- +perfidious +--SKIPIF-- + +--INI-- +perfidious.overflow_mode=1 +--FILE-- + %d in %A +NULL + +Warning: Perfidious\debug_uint64_overflow(): value too large: %d > %d in %A +NULL \ No newline at end of file diff --git a/tests/overflow/wrap.phpt b/tests/overflow/wrap.phpt new file mode 100644 index 0000000..44c1573 --- /dev/null +++ b/tests/overflow/wrap.phpt @@ -0,0 +1,18 @@ +--TEST-- +overflow (wrap) +--EXTENSIONS-- +perfidious +gmp +--SKIPIF-- + +--INI-- +perfidious.overflow_mode=3 +--FILE-- +