Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ListUtil.xs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,8 +1369,10 @@ CODE:
/* coerce to integer */
#if PERL_VERSION >= 8
/* int_amg only appeared in perl 5.8.0 */
if(SvAMAGIC(arg) && (arg = AMG_CALLun(arg, int)))
; /* nothing to do */
if(SvAMAGIC(arg)) {
if(!(arg = AMG_CALLun(arg, int)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This adds a new exception condition where there didn't used to be one. I wonder if that change in behaviour would break too much.

I'd prefer to preserve the existing condition, but without trashing the arg value. Instead, perhaps have:

SV *mgret;
if(SvAMAGIC(arg) && (mgret = AMG_CALLun(arg, int)))
    arg = mgret;
else ...

that way, it keeps the existing condition, but does not trash the value in arg when there is some magic but no int_amg.

Copy link
Author

@zhmylove zhmylove Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
Yes, I actually suggested this particular change in #137

But after that I thought a little bit more about that and decided to write this with croak just like int() does in this situation. Mostly due to uniqint() means literally: "remove subsequent duplicates based on int() values of the list supplied to it" and in turn int() does croak() for such objects.

This change will definitely not break any existing code as it croaks only instead of Segmentation Violation error which causes exit even inside eval

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A croak seems appropriate to me, since that is what a missing overload does anywhere else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahyes. In that case, does the message match what would happen normally?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message does not match.

But looking at the code for amagic_call, it isn't obvious why the previous code isn't already working correctly. It seems like it should already be producing an appropriate croak if the overload doesn't exist.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leonerd > what would happen normally

In 5.42.0, just like in the older versions, it still results into a crash with segmentation violation.

Typical error of missing "int" overload looks like:

Operation "+": no method found,
	left argument has no overloaded magic,
	right argument in overloaded package OverloadedPackageWithoutInt at -e line 1.

To me, this does not look meaningful in case of uniqint().

Maybe we should merge this PR to avoid segmentation faults in Perl core modules?
In my solution the message tells No "int" method found in overloaded package and highlights the line with uniqint() call.

croak("No \"int\" method found in overloaded package");
}
else
#endif
if(!SvOK(arg) || SvNOK(arg) || SvPOK(arg))
Expand Down
28 changes: 27 additions & 1 deletion t/uniq.t
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use strict;
use warnings;
use Config; # to determine ivsize
use Test::More tests => 31;
use Test::More tests => 35;
use List::Util qw( uniqstr uniqint uniq );

use Tie::Array;
Expand Down Expand Up @@ -98,6 +98,32 @@ is_deeply( [ uniqint 6.1, 6.2, 6.3 ],
'uniqint on undef coerces to zero' );
}

{
use Math::BigInt;
my ($obj1, $obj2, $obj3) = map { Math::BigInt->new($_) } 1, 1, 2;

is_deeply( [ uniqint $obj1, $obj1 ],
[ $obj1 ],
'uniqint removes repeated Math::BigInt objects' );

is_deeply( [ uniqint $obj1, $obj2 ],
[ $obj1 ],
'uniqint removes subsequent Math::BigInt objects' );

is_deeply( [ uniqint $obj1, $obj2, $obj3 ],
[ $obj1, $obj3 ],
'uniqint removes multiple subsequent Math::BigInt objects' );
}

{
{ package OverloadedPackageWithoutInt; use overload "+" => sub {} }

my $obj1 = bless {}, "OverloadedPackageWithoutInt";

ok( !defined eval { uniqint $obj1 },
'package with no "int" overload throws exception' );
}

SKIP: {
skip('UVs are not reliable on this perl version', 2) unless "$]" >= 5.008000;

Expand Down
Loading