Commit graph

2435 commits

Author SHA1 Message Date
Tim Düsterhus
7f4076bae0
RFC: Clone with v2 (#18747)
RFC: https://wiki.php.net/rfc/clone_with_v2

Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
2025-07-17 21:13:42 +02:00
Niels Dossche
224f95f442
Merge branch 'PHP-8.4'
* PHP-8.4:
  Update NEWS for GH-19068
  ext/gd: Drop useless and doubtful MSVC specific code (libgd/libgd@f1480ab)
  Zend: fix undefined symbol 'execute_ex' on Windows ARM64 #19064; ext/gd: fix emmintrin.h not found on Windows ARM64
2025-07-10 22:15:43 +02:00
Demon
2be3aa86f0
Zend: fix undefined symbol 'execute_ex' on Windows ARM64 #19064; ext/gd: fix emmintrin.h not found on Windows ARM64 2025-07-10 22:13:29 +02:00
Tim Düsterhus
45d948f2da
Zend: Add zend_check_method_accessible() to DRY method visibility checks (#18995)
* Zend: Add `zend_check_method_accessible()` to DRY method visibility checks

* Zend: Add assertions verifying flags didn't change before `zend_check_method_accessible()`

* Try `zend_always_inline` for `zend_check_method_accessible`
2025-07-07 21:30:13 +02:00
Tim Düsterhus
59dd0f8a48
Zend: Use zend_bad_method_call() when cloning from the wrong scope (#18999) 2025-07-01 20:24:11 +02:00
Niels Dossche
4a18c895ca
Fix OSS-Fuzz #428053935 (#18969)
Registering the constant may happen under another name due to
lowercasing. This will cause the lookup to the constant to fail.
Instead of looking it up, just change the Zend API to return a pointer
instead.
2025-06-30 09:09:55 +02:00
Tim Düsterhus
ca49a7bec2
RFC: Turn clone() into a function (#18919)
RFC: https://wiki.php.net/rfc/clone_with_v2

Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
2025-06-24 20:14:40 +02:00
Niels Dossche
36891a6775
Move VM exception checks (#18730)
Checking the non-exception path without arguments first, this avoids a redundant check in the case without arguments. The exception path may become more expensive, but we don't optimize for exception flow, rather we optimize for the happy flow. The other paths are unaffected.
2025-06-02 23:35:42 +02:00
Daniel Scherzer
04522cd1c4
Merge branch 'PHP-8.4'
* PHP-8.4:
  Reapply GH-17712 with a fix for internal class constants (#18464)
2025-05-25 16:51:18 -07:00
DanielEScherzer
cd751f98cb
Reapply GH-17712 with a fix for internal class constants (#18464)
Add recursion protection when emitting deprecation warnings for class
constants, since the deprecation message can come from an attribute that is
using the same constant for the message, or otherwise result in recursion.

But, internal constants are persisted, and thus cannot have recursion
protection. Otherwise, if a user error handler triggers bailout before the
recursion flag is removed then a subsequent request (e.g. with `--repeat 2`)
would start with that flag already applied. Internal constants can presumably
be trusted not to use deprecation messages that come from recursive attributes.

Fixes GH-18463
Fixes GH-17711
2025-05-25 16:43:36 -07:00
Niels Dossche
0b48e2a267
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix OSS-Fuzz #418106144
  Fix OSS-Fuzz #417078295
  fix: dangling opline in ZEND_INIT_ARRAY (#18578)
2025-05-19 19:08:29 +02:00
Niels Dossche
08cba2dcc1
Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3:
  Fix OSS-Fuzz #418106144
  Fix OSS-Fuzz #417078295
2025-05-19 19:07:17 +02:00
Niels Dossche
46ac878f6a
Fix OSS-Fuzz #417078295
If the variable_ptr and fetched value are the same or overlap, then we
get a UAF. Prevent this by delaying destruction.

Closes GH-18588.
2025-05-19 19:04:58 +02:00
Levi Morrison
59e6165fa6
Merge branch 'PHP-8.3' into PHP-8.4 2025-05-19 09:53:15 -06:00
Levi Morrison
35455b17be
fix: dangling opline in ZEND_INIT_ARRAY (#18578)
This causes problems if an allocation profiler decides to walk the
stack, or if the engine itself OOMs on this opcode, and it tries to
print file and line information.
2025-05-19 09:45:28 -06:00
Tim Düsterhus
2d6b86945f
zend_vm: Add OPcode specialization for === [] (#18571)
* zend_vm: Add OPcode specialization for `=== []`

Checking whether an array is empty with a strict comparison against the empty
array is a common pattern in PHP. A GitHub search for `"=== []" language:PHP`
reveals 44k hits. From the set of `!$a`, `count($a) === 0`, `empty($a)` and
`$a === []` it however is also the slowest option.

A test script:

    <?php

    $variable = array_fill(0, 10, random_int(1, 2));

    $f = true;
    for ($i = 0; $i < 50_000_000; $i++) {
    	$isEmpty = $variable === [];
    	$f = $f && $isEmpty;
    }

    var_dump($f);

with the `$isEmpty = …;` statement appropriately replaced results in:

    Benchmark 1: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php
      Time (mean ± σ):     467.6 ms ±   2.3 ms    [User: 463.3 ms, System: 3.4 ms]
      Range (min … max):   464.6 ms … 473.4 ms    10 runs

    Benchmark 2: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php
      Time (mean ± σ):     305.3 ms ±   0.3 ms    [User: 302.0 ms, System: 3.1 ms]
      Range (min … max):   304.9 ms … 305.7 ms    10 runs

    Benchmark 3: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php
      Time (mean ± σ):     630.3 ms ±   3.9 ms    [User: 624.8 ms, System: 3.8 ms]
      Range (min … max):   627.4 ms … 637.6 ms    10 runs

    Benchmark 4: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php
      Time (mean ± σ):     311.8 ms ±   3.4 ms    [User: 307.9 ms, System: 3.6 ms]
      Range (min … max):   308.7 ms … 320.7 ms    10 runs

    Summary
      sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php ran
        1.02 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php
        1.53 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php
        2.06 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php

This patch adds another OPcode specialization for `ZEND_IS_IDENTICAL` that
specifically matches a comparison against the empty array. With this
specialization the `=== []` check becomes the fastest of them all, which is not
surprising given how specific it is:

    Benchmark 1: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php
      Time (mean ± σ):     384.1 ms ±   2.3 ms    [User: 379.3 ms, System: 3.8 ms]
      Range (min … max):   382.2 ms … 389.8 ms    10 runs

    Benchmark 2: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php
      Time (mean ± σ):     305.8 ms ±   3.2 ms    [User: 301.7 ms, System: 3.8 ms]
      Range (min … max):   304.4 ms … 314.9 ms    10 runs

      Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

    Benchmark 3: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php
      Time (mean ± σ):     293.9 ms ±   2.9 ms    [User: 289.7 ms, System: 3.3 ms]
      Range (min … max):   291.5 ms … 299.4 ms    10 runs

    Benchmark 4: sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php
      Time (mean ± σ):     306.8 ms ±   0.4 ms    [User: 303.8 ms, System: 2.7 ms]
      Range (min … max):   306.3 ms … 307.3 ms    10 runs

    Summary
      sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 identical.php ran
        1.04 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 empty.php
        1.04 ± 0.01 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 not.php
        1.31 ± 0.02 times faster than sapi/cli/php -d zend_extension=modules/opcache.so -d opcache.enable_cli=1 count.php

As a follow-up optimization it might be possible to transform the other
emptiness checks, such as `count($arr) === 0` into `$arr === []` if `$arr` is
known to be `MAY_BE_ARRAY` only.

* zend_vm: Add OPcode specialization for `!== []`

* UPGRADING
2025-05-19 09:36:30 +02:00
Ilija Tovilo
386ab1dad2
Revert "Fix infinite recursion on deprecated attribute evaluation"
This reverts commit 272f7f75e2.

Reverts GH-17712 for the PHP-8.4 branch. This will be reapplied later
with a fix for GH-18463 (GH-18464).
2025-04-30 20:52:56 +02:00
DanielEScherzer
3f03f7ed3d
[RFC] Add support for attributes on compile-time constants
https://wiki.php.net/rfc/attributes-on-constants
2025-04-29 11:53:09 -07:00
Niels Dossche
864ad1b5bb
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-18136: tracing JIT floating point register clobbering on Windows and ARM64
2025-04-21 13:15:50 +02:00
Niels Dossche
1a1a83f1fc
Fix GH-18136: tracing JIT floating point register clobbering on Windows and ARM64
On win64, xmm6-xmm15 are preserved registers, but the prologues and
epilogues of JITted code don't handle these. The issue occurs when
calling into the JIT code again via an internal handler
(like call_user_func). Therefore, we want to save/restore xmm registers
upon entering/leaving execute_ex. Since MSVC x64 does not support inline
assembly, we create an assembly wrapper around the real execute_ex
function.
The alternative is to always save/restore these xmm registers into the
fixed call frame, but this causes unnecessary overhead.
The same issue occurs for ARM64 platforms for floating point register
8 to 15. However, there we can use inline asm to fix this.

Closes GH-18352.
2025-04-21 13:15:43 +02:00
Arnaud Le Blanc
76d7c616bb
Pass opline as argument to opcode handlers in CALL VM
This changes the signature of opcode handlers in the CALL VM so that the opline
is passed directly via arguments. This reduces the number of memory operations
on EX(opline), and makes the CALL VM considerably faster.

Additionally, this unifies the CALL and HYBRID VMs a bit, as EX(opline) is now
handled in the same way in both VMs.

This is a part of GH-17849.

Currently we have two VMs:

 * HYBRID: Used when compiling with GCC. execute_data and opline are global
   register variables
 * CALL: Used when compiling with something else. execute_data is passed as
   opcode handler arg, but opline is passed via execute_data->opline
   (EX(opline)).

The Call VM looks like this:

    while (1) {
        ret = execute_data->opline->handler(execute_data);
        if (UNEXPECTED(ret != 0)) {
            if (ret > 0) { // returned by ZEND_VM_ENTER() / ZEND_VM_LEAVE()
                execute_data = EG(current_execute_data);
            } else {       // returned by ZEND_VM_RETURN()
                return;
            }
        }
    }

    // example op handler
    int ZEND_INIT_FCALL_SPEC_CONST_HANDLER(zend_execute_data *execute_data) {
        // load opline
        const zend_op *opline = execute_data->opline;

        // instruction execution

        // dispatch
        // ZEND_VM_NEXT_OPCODE():
        execute_data->opline++;
        return 0; // ZEND_VM_CONTINUE()
    }

Opcode handlers return a positive value to signal that the loop must load a
new execute_data from EG(current_execute_data), typically when entering
or leaving a function.

Here I make the following changes:

 * Pass opline as opcode handler argument
 * Return next opline from opcode handlers
 * ZEND_VM_ENTER / ZEND_VM_LEAVE return opline|(1<<0) to signal that
   execute_data must be reloaded from EG(current_execute_data)

This gives us:

    while (1) {
        opline = opline->handler(execute_data, opline);
        if (UNEXPECTED((uintptr_t) opline & ZEND_VM_ENTER_BIT) {
            opline = opline & ~ZEND_VM_ENTER_BIT;
            if (opline != 0) { // ZEND_VM_ENTER() / ZEND_VM_LEAVE()
                execute_data = EG(current_execute_data);
            } else {           // ZEND_VM_RETURN()
                return;
            }
        }
    }

    // example op handler
    const zend_op * ZEND_INIT_FCALL_SPEC_CONST_HANDLER(zend_execute_data *execute_data, const zend_op *opline) {
        // opline already loaded

        // instruction execution

        // dispatch
        // ZEND_VM_NEXT_OPCODE():
        return ++opline;
    }

bench.php is 23% faster on Linux / x86_64, 18% faster on MacOS / M1.

Symfony Demo is 2.8% faster.

When using the HYBRID VM, JIT'ed code stores execute_data/opline in two fixed
callee-saved registers and rarely touches EX(opline), just like the VM.

Since the registers are callee-saved, the JIT'ed code doesn't have to
save them before calling other functions, and can assume they always
contain execute_data/opline. The code also avoids saving/restoring them in
prologue/epilogue, as execute_ex takes care of that (JIT'ed code is called
exclusively from there).

The CALL VM can now use a fixed register for execute_data/opline as well, but
we can't rely on execute_ex to save the registers for us as it may use these
registers itself. So we have to save/restore the two registers in JIT'ed code
prologue/epilogue.

Closes GH-17952
2025-04-15 18:51:54 +02:00
Niels Dossche
2f6c069296
Implement GH-18261: Allow cast to be used in constant expressions (#18264) 2025-04-11 17:53:43 +02:00
Niels Dossche
a32f491855
Remove cache slot from ZEND_VERIFY_TYPE and arg RECV opcodes (#18258) 2025-04-07 19:50:48 +02:00
Ilija Tovilo
1f6fdde646
Implement asymmetric visibility for static properties
https://wiki.php.net/rfc/static-aviz

Optimally, this would be moved to zend_fetch_static_property_address(). However,
this isn't currently effective for opcache, because R and RW/W/UNSET cache slots
are merged. This will circumvent the visibility check if the cache is primed by
a R instruction.

Closes GH-16486
2025-04-02 16:56:11 +02:00
Tim Düsterhus
5544be7018
RFC: Marking return values as important (#[\NoDiscard]) (#17599)
RFC: https://wiki.php.net/rfc/marking_return_value_as_important

Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
2025-04-02 09:35:29 +02:00
Ilija Tovilo
99f72fa499
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix infinite recursion on deprecated attribute evaluation
2025-03-26 23:40:58 +01:00
Ilija Tovilo
272f7f75e2
Fix infinite recursion on deprecated attribute evaluation
Fixes GH-17711
Fixes GH-18022
Closes GH-17712
2025-03-26 23:39:38 +01:00
Ilija Tovilo
9fd62733b9
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix skipped lazy init on primed SIMPLE_WRITE
2025-03-08 12:33:20 +01:00
Ilija Tovilo
9acfe6e11c
Fix skipped lazy init on primed SIMPLE_WRITE
Go through the normal assignment path, which includes an IS_UNDEF check.

Fixes GH-17998
Closes GH-17999
2025-03-08 12:32:18 +01:00
Niels Dossche
4c88bfb96d
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-16799: Assertion failure at Zend/zend_vm_execute.h:7469
2024-11-15 20:05:40 +01:00
Niels Dossche
4a5854ee62
Merge branch 'PHP-8.3' into PHP-8.4
* PHP-8.3:
  Fix GH-16799: Assertion failure at Zend/zend_vm_execute.h:7469
2024-11-15 20:03:26 +01:00
Niels Dossche
ed59c00661
Merge branch 'PHP-8.2' into PHP-8.3
* PHP-8.2:
  Fix GH-16799: Assertion failure at Zend/zend_vm_execute.h:7469
2024-11-15 20:03:19 +01:00
Niels Dossche
553d79c709
Fix GH-16799: Assertion failure at Zend/zend_vm_execute.h:7469
zend_is_callable_ex() can unfortunately emit a deprecation, and then
a user error handler can throw an exception. This causes an assert
failure at ZEND_VM_NEXT_OPCODE(). We fix this by checking if there's an
exception after zend_is_callable_ex().

Closes GH-16803.
2024-11-15 20:02:26 +01:00
Niels Dossche
0b3684c48e
Merge branch 'PHP-8.4'
* PHP-8.4:
  Fix GH-16574: Incorrect error "undefined method" messages
2024-10-25 18:33:36 +02:00
Niels Dossche
e9283c0819
Fix GH-16574: Incorrect error "undefined method" messages
The `get_method` object handler may change the object pointer. SPL does
this in its iterator implementations. This causes the error message
to change to another class which is confusing to the user. JIT handles
this correctly. This patch aligns behaviour with JIT.

Closes GH-16576.
2024-10-25 18:33:24 +02:00
Arnaud Le Blanc
8b41bca615
Merge branch 'PHP-8.4'
* PHP-8.4:
  [ci skip] NEWS for GH-15960
  Deny resetting an object as lazy during property iteration
  Ensure to initialize lazy object in foreach
  Do not null out obj->properties when resetting object
  Fix handling of undef property during foreach by ref on hooked class
2024-10-03 15:14:08 +02:00
Arnaud Le Blanc
3151117987
Ensure to initialize lazy object in foreach
foreach() by-passes the get_properties() handler and did not always trigger
initialization.
2024-10-03 15:12:21 +02:00
Dmitry Stogov
3f913c123a
Implement JIT for ZEND_FETCH_STATIC_PROP_* and improve interpretation (#16157)
* Implement JIT for ZEND_FETCH_STATIC_PROP_* and improve interpretation

* Revert incorrect change

* Use FASTCALL calling convention

* Use EMPTY_SWITCH_DEFAULT_CASE

* Move the loading of the property info into zend_jit_uninit_static_prop()
2024-10-02 21:02:33 +03:00
Arnaud Le Blanc
c65e042c0b
Fix zend_get_property_info_for_slot() for lazy objects (#15855)
zend_get_property_info_for_slot(obj, slot) assumes that 'slot' belongs to 'obj', but that may not be the case for lazy proxies.

Fortunately, the property info is often already available in path when it is needed.

For other cases, I make zend_get_property_info_for_slot() aware of lazy objects, and add zend_get_property_info_for_slot_self() for cases where the 'slot' is known to belong to the object itself.

Fixes oss-fuzz #71446
2024-09-16 16:58:12 +02:00
Levi Morrison
6435bb5ae1
Interrupt while internal frame is on the stack (#14627)
* Check VM interrupt while internal frame is on top

* Use tab instead of spaces

* fix frame used in interrupt and refactor

* remove unused failures for zend_jit_check_timeout

* Fix JIT support

Co-authored-by: Bob Weinand <bobwei9@hotmail.com>

* Fix the missing store to vm_interrupt

* Rename new functions

* Special case zend_interrupt_function in JIT code

* refactor to use ZEND_VM_SET_OPCODE_NO_INTERRUPT

* Split atomic exchange into load + store

It is difficult to determine performance of atomics sometimes. In this
case, the separate load+store is still correct, and a load does not
cause a modification, and might be faster for some platforms than an
exchange. A load+store is slower than an exchange, but we're fine
trading the penalty to the slow path and keeping the happy path faster.

---------

Co-authored-by: Bob Weinand <bobwei9@hotmail.com>
2024-09-05 00:35:55 +02:00
Jorg Adam Sowa
2a30f2ffc4
Add type indicator to array/arg unpack error messages (GH-15448) 2024-09-01 22:57:12 +02:00
Arnaud Le Blanc
58aa6fc830
Lazy objects
RFC: https://wiki.php.net/rfc/lazy-objects

Closes GH-15019
2024-08-30 17:30:03 +02:00
Gina Peter Banyard
a79c70f574
[RFC] Convert exit (and die) from language constructs to functions (#13483)
RFC: https://wiki.php.net/rfc/exit-as-function
2024-08-14 12:44:12 +01:00
Arnaud Le Blanc
c02c1d4474
Change YIELD/YIELD_FROM to do not increment opline (#15328)
YIELD and YIELD_FROM increment opline before returning, but in most places
we need the opline to point to the YIELD and YIELD_FROM.

Here I change YIELD / YIELD_FROM to not increment opline. This simplifies the
code and fixes GH-15275 in a better way.

Closes GH-15328
2024-08-10 16:09:47 +02:00
Ilija Tovilo
50217b35ea
Remove IS_STATIC_VAR_UNINITIALIZED (#15227)
This flag was never necessary. We know a static variable is uninitialized (i.e.
the initializer has never been called) iff the zval in the static variable array
does not contain a reference.

Prompted by a related issue in ext-uopz reported by Christoph.
2024-08-05 11:19:13 +02:00
Arnaud Le Blanc
1fbb666545
Use zend_std_build_properties() to access zend_object.properties
The zend_object.properties HashTable needs to be built just in time by calling
rebuild_object_properties() on the object before accessing it. Normally this is
done automatically in zend_std_get_properties(), but we do it manually in a few
places.

In this change I introduce an inline variant of zend_std_build_properties(), and
refactor these places to use it instead of calling rebuild_object_properties()
manually.

rebuild_object_properties() renamed as rebuild_object_properties_internal(), to
enforce usage of zend_std_get_properties() or zend_std_build_properties_ex().

Closes GH-14996
2024-07-18 22:18:38 +02:00
Ilija Tovilo
780a8280d2
[RFC] Property hooks (#13455)
RFC: https://wiki.php.net/rfc/property-hooks

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
2024-07-14 11:55:03 +02:00
Benjamin Eberlei
72c874691b
RFC: Add #[\Deprecated] Attribute (#11293)
see https://wiki.php.net/rfc/deprecated_attribute

Co-authored-by: Tim Düsterhus <tim@tideways-gmbh.com>
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
2024-07-02 09:44:25 +02:00
Bob Weinand
6a2c5318f9
Optimize observers (#13649)
Inline the lookup whether a function is observed at all.
This strategy is also used for FRAMELESS calls. If the frameless call is observed, we instead allocate a call frame and push the arguments, to call the the function afterwards.
Doing so is still a performance benefit as opposed to executing individual INIT_FCALL+SEND_VAL ops. Thus, even if the frameless call turns out to be observed, the call overhead is slightly lower than before.
If the internal function is not observed at all, the unavoidable overhead is fetching the FLF zend_function pointer and the run-time cache needs to be inspected.

As part of this work, it turned out to be most viable to put the result operand on the ZEND_OP_DATA instead of ZEND_FRAMELESS_ICALL_3, allowing seamless interoperability with the DO_ICALL opcode.
This is a bit unusual in comparison to all other ZEND_OP_DATA usages, but seems to not pose problems overall.

There is also a small issue resolved: trampolines would always use the ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER function due to zend_observer_fcall_op_array_extension being set to -1 too late.
2024-06-15 14:42:27 +02:00
Levi Morrison
c461b60060
refactor: change zend_is_true to return bool (#14301)
Previously this returned `int`. Many functions actually take advantage
of the fact this returns exactly 0 or 1. For instance,
`main/streams/xp_socket.c` does:

    sockopts |= STREAM_SOCKOP_IPV6_V6ONLY_ENABLED * zend_is_true(tmpzval);

And `Zend/zend_compile.c` does:

    child = &ast->child[2 - zend_is_true(zend_ast_get_zval(ast->child[0]))];

I changed a few places trivially from `int` to `bool`, but there are
still many places such as the object handlers which return `int` that
should eventually be `bool`.
2024-05-24 15:16:36 -06:00