From a3c92fceecbf5ea1e4bf866040351b22e0c58508 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Tue, 27 Oct 2026 14:40:34 +0100 Subject: [PATCH] fix: preserve overloaded arithmetic result lifetime Use reference-counted scalar assignment for overloaded increment and decrement fallbacks. Add a regression test for pre/post operations preserving referent state. Refs #1901 Generated with [OpenAI Codex](https://openai.com/codex/) Co-Authored-By: OpenAI Codex --- docs/about/changelog.md | 1 + .../runtime/runtimetypes/RuntimeScalar.java | 12 ++--- ..._autoincrement_preserves_result_lifetime.t | 48 +++++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/test/resources/unit/overload_autoincrement_preserves_result_lifetime.t diff --git a/docs/about/changelog.md b/docs/about/changelog.md index 15bf39ba71..e6acc22e15 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -6,6 +6,7 @@ priorities and future plans. ## Work in progress +- Preserve reference lifetime when overloaded `++` and `--` fall back to arithmetic (#1901). - Parse forward-declared qualified classes as indirect constructors inside `new` methods (#1899). - Match Perl `Encode` aliases for ANSI X3.4-1968, HZ-GB-2312, and ISO-8859-8-I, and include the BOM when encoding generic UTF-32 (#1897). diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 09a0f43989..afac592022 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -5722,8 +5722,7 @@ private RuntimeScalar preAutoIncrementWithoutWatcherNotification() { result = ctx.tryOverload("(+", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { // For fallback, + should NOT modify operand, so we handle assignment - this.type = result.type; - this.value = copyScalarPayload(result.type, result.value); + set(result); return this; } @@ -6005,8 +6004,7 @@ private RuntimeScalar postAutoIncrementLarge() { result = ctx.tryOverload("(+", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { // For fallback, + should NOT modify operand, so we handle assignment - this.type = result.type; - this.value = copyScalarPayload(result.type, result.value); + set(result); return old; } @@ -6157,8 +6155,7 @@ private RuntimeScalar preAutoDecrementWithoutWatcherNotification() { result = ctx.tryOverload("(-", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { // For fallback, - should NOT modify operand, so we handle assignment - this.type = result.type; - this.value = copyScalarPayload(result.type, result.value); + set(result); return this; } } @@ -6316,8 +6313,7 @@ private RuntimeScalar postAutoDecrementWithoutWatcherNotification() { result = ctx.tryOverload("(-", new RuntimeArray(this, scalarOne, scalarUndef)); if (result != null) { // For fallback, - should NOT modify operand, so we handle assignment - this.type = result.type; - this.value = copyScalarPayload(result.type, result.value); + set(result); return old; } } diff --git a/src/test/resources/unit/overload_autoincrement_preserves_result_lifetime.t b/src/test/resources/unit/overload_autoincrement_preserves_result_lifetime.t new file mode 100644 index 0000000000..fc46ccc401 --- /dev/null +++ b/src/test/resources/unit/overload_autoincrement_preserves_result_lifetime.t @@ -0,0 +1,48 @@ +use strict; +use warnings; +no warnings 'numeric'; +use Test::More; + +{ + package Local::FormattedDate; + use Scalar::Util qw(refaddr); + our %FORMATS; + + use overload + '+' => sub { _offset($_[0], $_[1]) }, + '-' => sub { _offset($_[0], -$_[1]) }, + '""' => sub { + my ($self) = @_; + return sprintf $FORMATS{refaddr($self)}, $$self; + }, + fallback => 1; + + sub _offset { + my ($self, $delta) = @_; + my $result = bless \($$self + $delta), ref($self); + $FORMATS{refaddr($result)} = $FORMATS{refaddr($self)}; + return $result; + } + + sub set_format { $FORMATS{refaddr($_[0])} = $_[1] } + + sub DESTROY { delete $FORMATS{refaddr($_[0])} } +} + +my $value = 20261101; +my $date = bless \$value, 'Local::FormattedDate'; +$date->set_format('custom:%d'); + +++ $date; +is("$date", 'custom:20261102', 'preincrement keeps state on the overloaded result'); + +-- $date; +is("$date", 'custom:20261101', 'predecrement keeps state on the overloaded result'); + +$date++; +is("$date", 'custom:20261102', 'postincrement keeps state on the overloaded result'); + +$date--; +is("$date", 'custom:20261101', 'postdecrement keeps state on the overloaded result'); + +done_testing();