Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129201 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 791261A00BC for ; Tue, 11 Nov 2025 20:20:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1762892416; bh=tybDVjSQwUH0wE+qSNO50Wy4Qg8Q0rA+9fAJg7xARus=; h=Date:Subject:To:References:From:In-Reply-To:From; b=EyIOrDwgu+UQEGLG4LxbF3ESqPyJBgo96lZu+MgMLXtXODXSS98t5nbfHhXUNpnTM 8SncShpwId5BkKrid+gNay/9bNVAisQ0XdpM3FTKvJ9kg/9VspG1Uk+3uo6mshy3Sl 3uHUJVNfseSkKHHVnj5Ck5SfHA8nVnMMC9LeXtlMQahxY+N/F3U7GtnWI1J5Lr7EZL Qm52i5/x4XJeHsLF9AMUomtr5GQOHxi+rWGZ0WhJe/OGy2jH6Ek/8ylqnPNRC/K94j rLNPkBK/LdCy04Jw0AJbgA7ZmVRtmidkKUDL4m1A2QRjwzksNBXcGHUDGbD5cwbQdA 7nJjJDmxTI3/w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 6CCCF180055 for ; Tue, 11 Nov 2025 20:20:10 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-b6-smtp.messagingengine.com (fhigh-b6-smtp.messagingengine.com [202.12.124.157]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 11 Nov 2025 20:20:10 +0000 (UTC) Received: from phl-compute-04.internal (phl-compute-04.internal [10.202.2.44]) by mailfhigh.stl.internal (Postfix) with ESMTP id 846C37A00DA for ; Tue, 11 Nov 2025 15:20:04 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-04.internal (MEProxy); Tue, 11 Nov 2025 15:20:04 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rwec.co.uk; h=cc :content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1762892404; x=1762978804; bh=1a3+jW2HKK jlIrvLm4jCQJD08pXovt2B8yYBiJC3gIg=; b=lv18Ke+2ZsCMBBJ6p+5iSa35wJ wGqIt5hjiS/lUyuT8UNgnJ/l66wAZZ0ir6EArwg0hvk7lBGLQMh2GRHjjmpqfPLx 5FcTAhzm4UcoT/ACZfMrKfeYgiQ0mfxwEAgxEvHR/zwUrCEWOkDgSwLBqnEiclcc syeDd9ftAJixX1FKpuF7nPiXfQun+RohlPuF/adXUE5BLB9bMcLqVmW5ri3WpF3f OqV8wMC9Zmj7YOX66IIIxD0suGhIb1yWG2cbQlrcy1xZR3oSGOgOp03YEp21b/bk 0PXf8zBm5JEyxx/3ysdMdyI3W9VVolZ16OWdrhxzQifKG7LZ+Y7eBRZFV+tw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1762892404; x=1762978804; bh=1a3+jW2HKKjlIrvLm4jCQJD08pXovt2B8yY BiJC3gIg=; b=Xg/RAyLh3VWYS5aeVZ7aG+AzIL85Y0UqLy+yEmn9ocKUfa9JDCs oUoUBP8ZTw0voyKNDNFgPGWJEe7SF5+U+KhIl1sKge2+4njIYlk32XcW2oZEMbRX KaEUwHGXkXBuNGT5fzaubWg3dY3HsFekd3FpaYIrRI6tv+InbCxtpjjs+O3UhyFE Azzwg8PGwREV82kTmEXn0u5dedjToUQu715bXs4zbNN7pTpVz4kq4S/coqKzFN8K 2DUl3zLiPKJKymwnm5HcMAk+vy/yQOuYCTg5E/hEBsGYvxCPJolXNkHZQ4qvCZQy MAppRMfZ+315g99V9FaX2TXVb88AKH14jxQ== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddvtddvudegucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpegtkfffgggfuffvfhfhjgesrgdtreertd dvjeenucfhrhhomhepfdftohifrghnucfvohhmmhhinhhsucglkffoufhorfgnfdcuoehi mhhsohhprdhphhhpsehrfigvtgdrtghordhukheqnecuggftrfgrthhtvghrnhepveekhf ffuddtffeftdejheffjeeujeelfeeifeduffdvudfhfefhgfehueefudegnecuffhomhgr ihhnpehorhgrtghlvgdrtghomhdpvgigthgvrhhnrghlshdrihhonecuvehluhhsthgvrh fuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepihhmshhophdrphhhphesrhif vggtrdgtohdruhhkpdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpohhuthdprh gtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: id5114917:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA for ; Tue, 11 Nov 2025 15:20:03 -0500 (EST) Content-Type: multipart/alternative; boundary="------------S4FD3KUQ7oGOq0B30wGZD60D" Message-ID: Date: Tue, 11 Nov 2025 20:20:02 +0000 Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PHP-DEV] [RFC] Context Managers To: internals@lists.php.net References: Content-Language: en-GB In-Reply-To: From: imsop.php@rwec.co.uk ("Rowan Tommins [IMSoP]") This is a multi-part message in MIME format. --------------S4FD3KUQ7oGOq0B30wGZD60D Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 09/11/2025 21:46, Seifeddine Gmati wrote: > This is the exact reason why, in the `use()` thread, I suggested a > `Disposable` interface should **not** have an `enter()` method. The > language should just guarantee a single `dispose()` method is called > when the scope is exited (successfully or not). Remember, the Context Manager is not the same as the resource being managed, and exitContext() is not equivalent to dispose(). In fact, it occurs to me that a C#-style using() block can be written as a Context Manager with only a few lines of code: interface Disposable {     public function dispose(): void; } class Disposer implements ContextManager {     private function __construct(private Disposable $resource) {}     public static function of(Disposable $resource): ContextManager {         return new self($resource);     }     public function enterContext(): Disposable {         return $this->resource;     }     public function exitContext(): void {         $this->resource->dispose();     } } Then to use it, you just pass in whatever object you want: with(Disposer::of(new MyDisposableResource) as $foo) {     // ...     $foo->whatever();     // ... } // guaranteed call to $foo->dispose() here Which is pretty close to a direct port of C#: using($foo = new MyDisposableResource) {     // ...     $foo->whatever();     // ... } // guaranteed call to $foo->dispose() here > You're right, but the `use()` proposal has a clear path to solve this, > which is far superior IMO to the manual checks required by the > `ContextManager` design. As discussed in the `use()` thread, we could > later introduce: > > 1. A **`Resource`** marker interface: This would tell the engine to > handle the object specially (e.g., use weak references in backtraces > to prevent accidental refcount inflation). > > 2. A **`Disposable`** interface: This would have the `dispose()` > logic and could (and probably should) also be a `Resource`. Neither of these is specific to the use() block. #1 is just a general language feature - as someone else pointed out, it could be similar to #[SensitiveParameter]. #2 is, as shown above, trivial to implement using a context manager, without even needing additional language support. > Furthermore, a `Disposable` interface opens up the possibility of > being supported *outside* the `use()` construct entirely. The engine > could guarantee `dispose()` is called whenever the object goes out of > *any* scope, just like a destructor but with crucial exception > awareness: This is an interesting idea, but again doesn't seem to have any special relationship to the use() proposal. It's also not what C# or Hack mean by "Disposable", so that would probably be a poor choice of name. It sounds more like an extension of the current __destruct(), which could potentially be as simple as adding a parameter to that. > With these (future) additions, the `use()` construct could be enhanced > to **enforce a no-escape policy** specifically for `Resource`s. If > `use($r = get_resource())` finishes and `$r` still has references, the > engine could throw an error. > > This combination would *programmatically prevent* the "use after free" > pitfall you described, rather than relying on manual checks inside > every single method. Unless the error happens at compile time (probably impossible in PHP's current workflow), or crashes the application with an uncatchable error (yikes!), I don't think it's possible to make that guarantee. Consider this code: try {     use($r = get_resource()) {         SomeClass::$staticVar = $r;     } } catch ( Throwable $e ) {     log_and_continue($e); } SomeClass::$staticVar->doSomething(); What is the value of SomeClass::$staticVar? I can only see three options: 1. It is still the open resource from get_resource(); the call succeeds, but the resource has leaked 2. It is a closed resource, and the call to doSomething() throws an "object already disposed" Error 3. It has been forcibly unset, and the call to doSomething() throws a "method call on null" Error (I don't think #3 is actually possible in the current Engine, because we only track the reference *count*, not a reference *list*; but it's at least theoretically possible.) And that's leaving aside the fact that a lot of resources can end up in unusable states anyway, such as a network connection being closed from the other end. > All of those powerful safety checks can be added in the future. I > don't see why we need to cram them into the initial `use()` RFC. Requiring a "double opt-in" (the use() block and an interface) makes the feature less reliable - you can't see at a glance if the use() block is performing the extra cleanup, or just creating a variable scope. I think it's better to have a specific block that can *only* be used with objects implementing the appropriate interface, like using+IDisposable (C# and Hack) or try+AutoCloseable (Java https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html). If we want a general-purpose "block scoping" feature alongside that, we can discuss exactly how that should look and operate, as I replied to Tim here: https://externals.io/message/129059#129188 -- Rowan Tommins [IMSoP] --------------S4FD3KUQ7oGOq0B30wGZD60D Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 09/11/2025 21:46, Seifeddine Gmati wrote:
This is the exact reason why, in the `use()` thread, I suggested a
`Disposable` interface should **not** have an `enter()` method. The
language should just guarantee a single `dispose()` method is called
when the scope is exited (successfully or not). 


Remember, the Context Manager is not the same as the resource being managed, and exitContext() is not equivalent to dispose().

In fact, it occurs to me that a C#-style using() block can be written as a Context Manager with only a few lines of code:

interface Disposable {
    public function dispose(): void;
}
class Disposer implements ContextManager
{
    private function __construct(private Disposable $resource) {}
    public static function of(Disposable $resource): ContextManager {
        return new self($resource);
    }
    public function enterContext(): Disposable {
        return $this->resource;
    }
    public function exitContext(): void {
        $this->resource->dispose();
    }
}


Then to use it, you just pass in whatever object you want:

with(Disposer::of(new MyDisposableResource) as $foo) {
    // ...
    $foo->whatever();
    // ...
} // guaranteed call to $foo->dispose() here


Which is pretty close to a direct port of C#:

using($foo = new MyDisposableResource) {
    // ...
    $foo->whatever();
    // ...
} // guaranteed call to $foo->dispose() here


You're right, but the `use()` proposal has a clear path to solve this,
which is far superior IMO to the manual checks required by the
`ContextManager` design. As discussed in the `use()` thread, we could
later introduce:

1.  A **`Resource`** marker interface: This would tell the engine to
handle the object specially (e.g., use weak references in backtraces
to prevent accidental refcount inflation).

2.  A **`Disposable`** interface: This would have the `dispose()`
logic and could (and probably should) also be a `Resource`.


Neither of these is specific to the use() block. #1 is just a general language feature - as someone else pointed out, it could be similar to #[SensitiveParameter]. #2 is, as shown above, trivial to implement using a context manager, without even needing additional language support.


Furthermore, a `Disposable` interface opens up the possibility of
being supported *outside* the `use()` construct entirely. The engine
could guarantee `dispose()` is called whenever the object goes out of
*any* scope, just like a destructor but with crucial exception
awareness:


This is an interesting idea, but again doesn't seem to have any special relationship to the use() proposal. It's also not what C# or Hack mean by "Disposable", so that would probably be a poor choice of name. It sounds more like an extension of the current __destruct(), which could potentially be as simple as adding a parameter to that.


With these (future) additions, the `use()` construct could be enhanced
to **enforce a no-escape policy** specifically for `Resource`s. If
`use($r = get_resource())` finishes and `$r` still has references, the
engine could throw an error.

This combination would *programmatically prevent* the "use after free"
pitfall you described, rather than relying on manual checks inside
every single method.


Unless the error happens at compile time (probably impossible in PHP's current workflow), or crashes the application with an uncatchable error (yikes!), I don't think it's possible to make that guarantee. Consider this code:

try {
    use($r = get_resource()) {
        SomeClass::$staticVar = $r;
    }
}
catch ( Throwable $e ) {
    log_and_continue($e);
}
SomeClass::$staticVar->doSomething();


What is the value of SomeClass::$staticVar? I can only see three options:

1. It is still the open resource from get_resource(); the call succeeds, but the resource has leaked

2. It is a closed resource, and the call to doSomething() throws an "object already disposed" Error

3. It has been forcibly unset, and the call to doSomething() throws a "method call on null" Error

(I don't think #3 is actually possible in the current Engine, because we only track the reference *count*, not a reference *list*; but it's at least theoretically possible.)

And that's leaving aside the fact that a lot of resources can end up in unusable states anyway, such as a network connection being closed from the other end.

All of those powerful safety checks can be added in the future. I
don't see why we need to cram them into the initial `use()` RFC.


Requiring a "double opt-in" (the use() block and an interface) makes the feature less reliable - you can't see at a glance if the use() block is performing the extra cleanup, or just creating a variable scope.

I think it's better to have a specific block that can *only* be used with objects implementing the appropriate interface, like using+IDisposable (C# and Hack) or try+AutoCloseable (Java https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html).

If we want a general-purpose "block scoping" feature alongside that, we can discuss exactly how that should look and operate, as I replied to Tim here: https://externals.io/message/129059#129188


-- 
Rowan Tommins
[IMSoP]
--------------S4FD3KUQ7oGOq0B30wGZD60D--