Skip to content

Commit ee7df97

Browse files
committed
Add an INI setting to control uint64_t -> zend_long overflow behavior
1 parent 0e7dede commit ee7df97

19 files changed

+296
-123
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,18 @@ jobs:
5757

5858
test:
5959
runs-on: ubuntu-latest
60-
name: "Test | PHP ${{ matrix.php-version }}"
60+
name: "Test | PHP ${{ matrix.php-version }} ${{ matrix.debug }}"
6161
strategy:
6262
matrix:
6363
php-version:
6464
- "8.1"
6565
- "8.2"
6666
- "8.3"
67+
debug:
68+
- ""
69+
include:
70+
- php-version: "8.1"
71+
debug: "debug"
6772
steps:
6873
- uses: actions/checkout@v4
6974

@@ -84,7 +89,9 @@ jobs:
8489

8590
- run: phpize
8691

87-
- run: ./configure --enable-compile-warnings=error
92+
- run: ./configure --enable-compile-warnings=error "${EXTRA_FLAGS}"
93+
env:
94+
EXTRA_FLAGS: ${{ matrix.debug == 'debug' && '--enable-perfidious-debug' || '' }}
8895

8996
- run: make
9097

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Some notable generic perf events are:
115115

116116
| Name | Default | Changeable | Description |
117117
| --------------------- | -------- | ----------- | ------------ |
118+
| `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. |
118119
| `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());`. |
119120
| `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. |
120121
| `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());` |

flake.nix

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@
7979
stdenv ? pkgs.stdenv,
8080
php ? pkgs.php,
8181
libpfm ? pkgs.libpfm,
82+
debugSupport ? false,
8283
}:
8384
pkgs.callPackage ./nix/derivation.nix {
8485
inherit src;
8586
inherit stdenv php libpfm;
87+
inherit debugSupport;
8688
buildPecl = pkgs.callPackage (nixpkgs + "/pkgs/build-support/php/build-pecl.nix") {
8789
inherit php stdenv;
8890
};
@@ -146,7 +148,7 @@
146148
# in opcache and relies on mkWrapper to load extensions
147149
export TEST_PHP_ARGS='-c ${package.php.phpIni}'
148150
# php.unwrapped from the buildDeps is overwriting php
149-
export PATH="${package.php}/bin:$PATH"
151+
export PATH="${package.php}/bin:./vendor/bin:$PATH"
150152
'';
151153
};
152154

@@ -200,21 +202,31 @@
200202
};
201203

202204
# @see https://github.com/NixOS/nixpkgs/pull/110787
203-
buildConfs = lib.cartesianProductOfSets {
204-
php = ["php81" "php82" "php83"];
205-
stdenv = [
206-
"gcc"
207-
"clang"
208-
# totally broken
209-
# "musl"
205+
buildConfs =
206+
(lib.cartesianProductOfSets {
207+
php = ["php81" "php82" "php83"];
208+
stdenv = [
209+
"gcc"
210+
"clang"
211+
# totally broken
212+
# "musl"
213+
];
214+
libpfm = ["libpfm" "libpfm-unstable"];
215+
})
216+
++ [
217+
{
218+
php = "php81";
219+
stdenv = "gcc";
220+
libpfm = "libpfm";
221+
debugSupport = true;
222+
}
210223
];
211-
libpfm = ["libpfm" "libpfm-unstable"];
212-
};
213224

214225
buildFn = {
215226
php,
216227
libpfm,
217228
stdenv,
229+
debugSupport ? false,
218230
}:
219231
lib.nameValuePair
220232
(lib.concatStringsSep "-" (lib.filter (v: v != "") [
@@ -226,12 +238,18 @@
226238
then ""
227239
else "${libpfm}"
228240
)
241+
(
242+
if debugSupport
243+
then "debug"
244+
else ""
245+
)
229246
]))
230247
(
231248
makePackage {
232249
php = matrix.php.${php};
233250
libpfm = matrix.libpfm.${libpfm};
234251
stdenv = matrix.stdenv.${stdenv};
252+
inherit debugSupport;
235253
}
236254
);
237255

nix/derivation.nix

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
buildPecl,
1111
src,
1212
checkSupport ? false,
13+
debugSupport ? false,
1314
WerrorSupport ? checkSupport,
1415
valgrindSupport ? true,
1516
}:
@@ -32,7 +33,8 @@ buildPecl rec {
3233
configureFlags =
3334
[]
3435
++ lib.optional WerrorSupport "--enable-compile-warnings=error"
35-
++ lib.optionals (!WerrorSupport) ["--enable-compile-warnings=yes" "--disable-Werror"];
36+
++ lib.optionals (!WerrorSupport) ["--enable-compile-warnings=yes" "--disable-Werror"]
37+
++ lib.optional debugSupport "--enable-perfidious-debug";
3638

3739
makeFlags = ["phpincludedir=$(dev)/include"];
3840
outputs = ["out" "dev"];

perfidious.stub.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
const VERSION = "0.1.0";
66

7+
const OVERFLOW_THROW = 0;
8+
const OVERFLOW_WARN = 1;
9+
const OVERFLOW_SATURATE = 2;
10+
const OVERFLOW_WRAP = 3;
711

812
/**
913
* @throws PmuNotFoundException
@@ -99,6 +103,7 @@ final public function disable(): self
99103

100104
/**
101105
* Get a raw byte stream from the handle's file descriptor
106+
*
102107
* @note closing this resource will cause subsequent calls to read to fail
103108
* @return resource
104109
*/
@@ -107,6 +112,11 @@ final public function rawStream()
107112
}
108113

109114
/**
115+
* @note If perfidious.overflow_mode is set to Perfidious\OVERFLOW_WARN, this method can return null, despite its
116+
* typehint. If perfidious.overflow_mode is set to any value other than Perfidious\OVERFLOW_THROW, this
117+
* method will *not* throw an OverflowException.
118+
*
119+
* @return ReadResult
110120
* @throws OverflowException|IOException
111121
*
112122
* @phpstan-return ReadResult<T>
@@ -116,7 +126,11 @@ final public function read(): ReadResult
116126
}
117127

118128
/**
119-
* @return array<string, int>
129+
* @note If perfidious.overflow_mode is set to Perfidious\OVERFLOW_WARN, this method can return null, despite its
130+
* typehint. If perfidious.overflow_mode is set to any value other than Perfidious\OVERFLOW_THROW, this
131+
* method will *not* throw an OverflowException.
132+
*
133+
* @return array
120134
* @throws OverflowException|IOException
121135
*
122136
* @phpstan-return array<value-of<T>, int>

php_perfidious.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ enum perfidious_error_mode
8383
PERFIDIOUS_ERROR_MODE_WARNING = 1,
8484
};
8585

86+
enum perfidious_overflow_mode
87+
{
88+
PERFIDIOUS_OVERFLOW_THROW = 0,
89+
PERFIDIOUS_OVERFLOW_WARN = 1,
90+
PERFIDIOUS_OVERFLOW_SATURATE = 2,
91+
PERFIDIOUS_OVERFLOW_WRAP = 3,
92+
PERFIDIOUS_OVERFLOW_MAX = 3,
93+
};
94+
8695
PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_exception_interface_ce;
8796
PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_pmu_not_found_exception_ce;
8897
PERFIDIOUS_PUBLIC extern zend_class_entry *perfidious_pmu_event_not_found_exception_ce;
@@ -103,6 +112,7 @@ ZEND_BEGIN_MODULE_GLOBALS(perfidious)
103112
struct perfidious_handle *request_handle;
104113

105114
enum perfidious_error_mode error_mode;
115+
enum perfidious_overflow_mode overflow_mode;
106116
ZEND_END_MODULE_GLOBALS(perfidious)
107117

108118
ZEND_EXTERN_MODULE_GLOBALS(perfidious);
@@ -171,6 +181,7 @@ zend_result perfidious_handle_read_to_array_with_times(
171181
ZEND_HOT
172182
PERFIDIOUS_PUBLIC
173183
PERFIDIOUS_ATTR_NONNULL_ALL
184+
PERFIDIOUS_ATTR_WARN_UNUSED_RESULT
174185
zend_result
175186
perfidious_handle_read_to_result(const struct perfidious_handle *restrict handle, zval *restrict return_value);
176187

src/extension.c

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
#include "ext/standard/php_string.h"
3939

4040
#include "php_perfidious.h"
41-
#include "functions.h"
4241
#include "handle.h"
4342
#include "private.h"
4443

@@ -63,6 +62,7 @@ static ZEND_INI_MH(OnUpdateStr)
6362

6463
// clang-format off
6564
PHP_INI_BEGIN()
65+
STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".overflow_mode", "0", PHP_INI_SYSTEM, OnUpdateLong, overflow_mode, zend_perfidious_globals, perfidious_globals)
6666
STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".global.enable", "0", PHP_INI_SYSTEM, OnUpdateBool, global_enable, zend_perfidious_globals, perfidious_globals)
6767
STD_PHP_INI_ENTRY(PHP_PERFIDIOUS_NAME ".global.metrics", DEFAULT_METRICS, PHP_INI_SYSTEM, OnUpdateStr, global_metrics, zend_perfidious_globals, perfidious_globals)
6868
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)
152152
return FAILURE;
153153
}
154154

155-
REGISTER_INI_ENTRIES();
156-
155+
#ifdef PERFIDIOUS_DEBUG
156+
REGISTER_BOOL_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\DEBUG", (zend_bool) PERFIDIOUS_DEBUG, flags);
157+
#else
158+
REGISTER_BOOL_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\DEBUG", false, flags);
159+
#endif
157160
REGISTER_STRING_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\VERSION", (char *) PHP_PERFIDIOUS_VERSION, flags);
158161

162+
REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_THROW", PERFIDIOUS_OVERFLOW_THROW, flags);
163+
REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_WARN", PERFIDIOUS_OVERFLOW_WARN, flags);
164+
REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_SATURATE", PERFIDIOUS_OVERFLOW_SATURATE, flags);
165+
REGISTER_LONG_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\OVERFLOW_WRAP", PERFIDIOUS_OVERFLOW_WRAP, flags);
166+
167+
#ifdef PERFIDIOUS_DEBUG
168+
do {
169+
char buf[128];
170+
snprintf(buf, sizeof(buf), "%" PRIu64, UINT64_MAX);
171+
REGISTER_STRING_CONSTANT(PHP_PERFIDIOUS_NAMESPACE "\\UINT64_MAX", buf, flags);
172+
} while (false);
173+
#endif
174+
175+
REGISTER_INI_ENTRIES();
176+
159177
perfidious_exceptions_minit();
160178
perfidious_handle_minit();
161179
perfidious_pmu_event_info_minit();
@@ -284,25 +302,15 @@ static PHP_GINIT_FUNCTION(perfidious)
284302
perfidious_globals->error_mode = PERFIDIOUS_ERROR_MODE_THROW;
285303
}
286304

287-
// clang-format off
288-
PERFIDIOUS_LOCAL
289-
const zend_function_entry perfidious_functions[] = {
290-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\get_pmu_info", ZEND_FN(perfidious_get_pmu_info), perfidious_get_pmu_info_arginfo, 0)
291-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\global_handle", ZEND_FN(perfidious_global_handle), perfidious_global_handle_arginfo, 0)
292-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmus", ZEND_FN(perfidious_list_pmus), perfidious_list_pmus_arginfo, 0)
293-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\list_pmu_events", ZEND_FN(perfidious_list_pmu_events), perfidious_list_pmu_events_arginfo, 0)
294-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\open", ZEND_FN(perfidious_open), perfidious_open_arginfo, 0)
295-
ZEND_RAW_FENTRY(PHP_PERFIDIOUS_NAMESPACE "\\request_handle", ZEND_FN(perfidious_request_handle), perfidious_request_handle_arginfo, 0)
296-
PHP_FE_END
297-
};
298-
// clang-format on
299-
300305
static const zend_module_dep perfidious_deps[] = {
301306
{"spl", NULL, NULL, MODULE_DEP_REQUIRED},
302307
{"opcache", NULL, NULL, MODULE_DEP_OPTIONAL},
303308
ZEND_MOD_END,
304309
};
305310

311+
PERFIDIOUS_LOCAL
312+
extern const zend_function_entry perfidious_functions[];
313+
306314
zend_module_entry perfidious_module_entry = {
307315
STANDARD_MODULE_HEADER_EX,
308316
NULL,

0 commit comments

Comments
 (0)