Skip to content

Commit 0ee3fa2

Browse files
committed
Properly duplicate PerlIO::encoding objects
PerlIO::encoding objects are usually initialized by calling Perl methods, essentially from the pushed() and getarg() callbacks. During cloning, the PerlIO API will by default call these methods to initialize the duplicate struct when the PerlIOBase parent struct is itself duplicated. This does not behave so well because the perl interpreter is not ready to call methods at this point, for the stacks are not set up yet. The proper way to duplicate the PerlIO::encoding object is to call sv_dup() on its members from the dup() PerlIO callback. So the only catch is to make the getarg() and pushed() calls implied by the duplication of the underlying PerlIOBase object aware that they are called during cloning, and make them wait that the control flow returns to the dup() callback. Fortunately, getarg() knows since its param argument is then non-null, and its return value is passed immediately to pushed(), so it is enough to tag this returned value with a custom magic so that pushed() can see it is being called during cloning. This fixes [RT #31923].
1 parent fa7a1e4 commit 0ee3fa2

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

MANIFEST

+1
Original file line numberDiff line numberDiff line change
@@ -3753,6 +3753,7 @@ ext/PerlIO-encoding/encoding.xs PerlIO::encoding
37533753
ext/PerlIO-encoding/t/encoding.t See if PerlIO encoding conversion works
37543754
ext/PerlIO-encoding/t/fallback.t See if PerlIO fallbacks work
37553755
ext/PerlIO-encoding/t/nolooping.t Tests for PerlIO::encoding
3756+
ext/PerlIO-encoding/t/threads.t Tests PerlIO::encoding and threads
37563757
ext/PerlIO-mmap/mmap.pm PerlIO layer for memory maps
37573758
ext/PerlIO-mmap/mmap.xs PerlIO layer for memory maps
37583759
ext/PerlIO-scalar/scalar.pm PerlIO layer for scalars

ext/PerlIO-encoding/encoding.pm

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package PerlIO::encoding;
22

33
use strict;
4-
our $VERSION = '0.21';
4+
our $VERSION = '0.22';
55
our $DEBUG = 0;
66
$DEBUG and warn __PACKAGE__, " called by ", join(", ", caller), "\n";
77

ext/PerlIO-encoding/encoding.xs

+23-2
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,23 @@ typedef struct {
4949

5050
#define NEEDS_LINES 1
5151

52+
static const MGVTBL PerlIOEncode_tag = { 0, 0, 0, 0, 0, 0, 0, 0 };
53+
5254
SV *
5355
PerlIOEncode_getarg(pTHX_ PerlIO * f, CLONE_PARAMS * param, int flags)
5456
{
5557
PerlIOEncode *e = PerlIOSelf(f, PerlIOEncode);
56-
SV *sv = &PL_sv_undef;
57-
PERL_UNUSED_ARG(param);
58+
SV *sv;
5859
PERL_UNUSED_ARG(flags);
60+
/* During cloning, return an undef token object so that _pushed() knows
61+
* that it should not call methods and wait for _dup() to actually dup the
62+
* encoding object. */
63+
if (param) {
64+
sv = newSV(0);
65+
sv_magicext(sv, NULL, PERL_MAGIC_ext, &PerlIOEncode_tag, 0, 0);
66+
return sv;
67+
}
68+
sv = &PL_sv_undef;
5969
if (e->enc) {
6070
dSP;
6171
/* Not 100% sure stack swap is right thing to do during dup ... */
@@ -85,6 +95,14 @@ PerlIOEncode_pushed(pTHX_ PerlIO * f, const char *mode, SV * arg, PerlIO_funcs *
8595
IV code = PerlIOBuf_pushed(aTHX_ f, mode, Nullsv,tab);
8696
SV *result = Nullsv;
8797

98+
if (SvTYPE(arg) >= SVt_PVMG
99+
&& mg_findext(arg, PERL_MAGIC_ext, &PerlIOEncode_tag)) {
100+
e->enc = NULL;
101+
e->chk = NULL;
102+
e->inEncodeCall = 0;
103+
return code;
104+
}
105+
88106
PUSHSTACKi(PERLSI_MAGIC);
89107
ENTER;
90108
SAVETMPS;
@@ -566,6 +584,9 @@ PerlIOEncode_dup(pTHX_ PerlIO * f, PerlIO * o,
566584
if (oe->enc) {
567585
fe->enc = PerlIO_sv_dup(aTHX_ oe->enc, params);
568586
}
587+
if (oe->chk) {
588+
fe->chk = PerlIO_sv_dup(aTHX_ oe->chk, params);
589+
}
569590
}
570591
return f;
571592
}

ext/PerlIO-encoding/t/threads.t

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!perl
2+
3+
use strict;
4+
use warnings;
5+
6+
BEGIN {
7+
use Config;
8+
if ($Config{extensions} !~ /\bEncode\b/) {
9+
print "1..0 # Skip: no Encode\n";
10+
exit 0;
11+
}
12+
unless ($Config{useithreads}) {
13+
print "1..0 # Skip: no threads\n";
14+
exit 0;
15+
}
16+
}
17+
18+
use threads;
19+
20+
use Test::More tests => 3 + 1;
21+
22+
binmode *STDOUT, ':encoding(UTF-8)';
23+
24+
SKIP: {
25+
local $@;
26+
my $ret = eval {
27+
my $thread = threads->create(sub { pass 'in thread'; return 1 });
28+
skip 'test thread could not be spawned' => 3 unless $thread;
29+
$thread->join;
30+
};
31+
is $@, '', 'thread did not croak';
32+
is $ret, 1, 'thread returned the right value';
33+
}
34+
35+
pass 'passes at least one test';

0 commit comments

Comments
 (0)