Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Update UNIVERSAL::can() to accept a list of methods to check for
  • Loading branch information
Ovid committed Jul 15, 2024
commit 9784e88bb51f05de86f9cb7b63c411004084afa1
37 changes: 36 additions & 1 deletion t/uni/universal.t
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ BEGIN {
use utf8;
use open qw( :utf8 :std );

plan tests => 90;
plan tests => 103;

$a = {};
bless $a, "Bòb";
Expand Down Expand Up @@ -159,4 +159,39 @@ ok (!splàtt->isa('plòp'));
ok (!splàtt->isa('zlòpp'));
ok (splàtt->isa('plòp'));

#
# Check that ->can() can now accept a list of methods
#

package Parent {
sub new { return bless {}, shift }
sub foo { return __PACKAGE__ }
sub bar { return __PACKAGE__ }
}
package Child {
our @ISA = qw(Parent);
sub bar { return __PACKAGE__ }
sub baz { return __PACKAGE__ }
}

my $foo = \&Parent::foo;
my $parent_bar = \&Parent::bar;
my $child_bar = \&Child::bar;
my $baz = \&Child::baz;

ok( my @methods = Child->can(qw(foo bar baz)) );
is(scalar @methods, 3, 'should return 3 methods');
is( builtin::refaddr($methods[0]), builtin::refaddr($foo), 'foo should return our parent method' );
is( builtin::refaddr($methods[1]), builtin::refaddr($child_bar), 'bar should return our child method');
is( builtin::refaddr($methods[2]), builtin::refaddr($baz), 'baz should return our child method' );
ok( Child->can(qw(foo bar baz)), 'can(@methods) in scalar content should return a true value' );

my $child = Child->new;
ok( @methods = $child->can(qw(bar baz)) );
is(scalar @methods, 2, 'should return 2 methods');
is( builtin::refaddr($methods[0]), builtin::refaddr($child_bar), 'bar should return our child method' );
is( builtin::refaddr($methods[1]), builtin::refaddr($baz), 'baz should return our child method' );
ok( scalar $child->can(qw(bar baz)), 'can(@methods) in scalar content should return a true value' );

ok( !Child->can(qw(foo baz no_such_method)), 'can(@methods) with non-existent methods should already return nothing' );
ok( !scalar Child->can(qw(foo baz no_such_method)), 'can(@methods) with non-existent methods should return false in scalar context' );
28 changes: 16 additions & 12 deletions universal.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,21 +469,19 @@ XS(XS_UNIVERSAL_import_unimport)
XSRETURN_EMPTY;
}


XS(XS_UNIVERSAL_can); /* prototype to pass -Wmissing-prototypes */
XS(XS_UNIVERSAL_can)
{
dXSARGS;
SV *sv;
SV *rv;
HV *pkg = NULL;
GV *iogv;
int i;

if (items != 2)
croak_xs_usage(cv, "object-ref, method");
if (items < 2)
croak_xs_usage(cv, "object-ref, method, ...");

sv = ST(0);

SvGETMAGIC(sv);

/* Reject undef and empty string. Note that the string form takes
Expand All @@ -492,8 +490,6 @@ XS(XS_UNIVERSAL_can)
if (!SvOK(sv) || (SvPOK(sv) && !SvCUR(sv)))
XSRETURN_UNDEF;

rv = &PL_sv_undef;

if (SvROK(sv)) {
sv = MUTABLE_SV(SvRV(sv));
if (SvOBJECT(sv))
Expand All @@ -512,13 +508,21 @@ XS(XS_UNIVERSAL_can)
}

if (pkg) {
GV * const gv = gv_fetchmethod_sv_flags(pkg, ST(1), 0);
if (gv && isGV(gv))
rv = sv_2mortal(newRV(MUTABLE_SV(GvCV(gv))));
for (i = 1; i < items; i++) {
GV * const gv = gv_fetchmethod_sv_flags(pkg, ST(i), 0);
if (!gv || !isGV(gv) || !GvCV(gv))
XSRETURN_UNDEF;
}

EXTEND(SP, items - 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

You moved the writes to the stack before the EXTEND(), which may write beyond the end of the stack, the EXTEND should be moved above the loop.

for (i = 1; i < items; i++) {
GV * const gv = gv_fetchmethod_sv_flags(pkg, ST(i), 0);
ST(i-1) = sv_2mortal(newRV(MUTABLE_SV(GvCV(gv))));
}
XSRETURN(items - 1);
}

ST(0) = rv;
XSRETURN(1);
XSRETURN_UNDEF;
}

XS(XS_UNIVERSAL_DOES); /* prototype to pass -Wmissing-prototypes */
Expand Down