Skip to content

Commit d73b81a

Browse files
committed
XS: fix INTERFACE on strict C compilers
GH #23192 The INTERFACE keyword in XS allows the same XSUB to wrap multiple C library functions, by storing a function pointer to the C function in each CV. This has started failing on some strict C compilers, as the C code generated by the XS compiler does some suspect function pointer casts. This commit fixes the issue by providing a more correct cast in most cases (it still can't handle non-trivial C_ARGS values). Background: Before this commit, the presence of the INTERFACE keyword in this XSUB: char* foo(int a, int b, int c) INTERFACE: bar baz would cause these extra lines to be added to the XSUB's body: dXSFUNCTION(char*); XSFUNCTION = XSINTERFACE_FUNC(char*,cv,XSANY.any_dptr); RETVAL = XSFUNCTION(a, b, c); and lines like this to be added to the boot XSUB: XSINTERFACE_FUNC_SET(cv,bar); After macro expansion, these lines look roughly like the following: char* (*XSFUNCTION)(); XSFUNCTION = (char* (*)()))(XSANY.any_dptr)); RETVAL = XSFUNCTION(a, b, c); CvXSUBANY(cv).any_dxptr = (void (*) (pTHX_ void*))(bar); The issue: The specific error the C compiler is giving in the ticket is: error: too many arguments to function ‘XSFUNCTION’ which is caused by this line: RETVAL = XSFUNCTION(a, b, c); because XSFUNCTION has been declared as a pointer to a function which has no args, and is now being used as a pointer to a function which takes args. The fix: This commit fixes this issue by adding adding a cast: but only to the place where XSFUNCTION is used to call the function. The other places are left untouched (so the XSFUNCTION variable itself still has the "wrong" type). This has the advantage that the various dXSFUNCTION etc macros in XSUB.h don't need to be modified, and so portability to older Perls is less of an issue. It also means that INTERFACE_MACRO usage should be unaffected. After this commit, the code emitted to call XSFUNCTION now looks like: RETVAL = ((char* (*)(int,int,int))(XSFUNCTION))(a, b, c); This cast is generated based on the types of the XSUB's parameters and return values. Things are difficult in the presence of C_ARGS; for example, char* foo(int a, int b, int c) INTERFACE: bar baz C_ARGS: a, c In this case, the XS parser now splits the C_ARGS line and looks up the type of each corresponding parameter. If it can't be found, it uses 'void *' instead and hopes for the best. For more complex C_ARGS entries such as C_ARGS: foo(a), b+1 this doesn't work and it is likely that uncompilable C code will still be generated, This commit adds tests both to 001-basic.t (which just checks that the generated C code looks as expected), but also to 002-more.t, which actually compiles and executes the C code, and thus is more likely to catch failures due to picky C compilers.
1 parent 6be8ed2 commit d73b81a

File tree

4 files changed

+95
-21
lines changed

4 files changed

+95
-21
lines changed

dist/ExtUtils-ParseXS/lib/ExtUtils/ParseXS/Node.pm

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,14 +2569,18 @@ sub usage_string {
25692569

25702570
# $self->C_func_signature():
25712571
#
2572-
# return a string containing the arguments to pass to an autocall C
2573-
# function, e.g. 'a, &b, c'.
2572+
# return two arrays
2573+
# the first contains the arguments to pass to an autocall C
2574+
# function, e.g. ['a', '&b', 'c'];
2575+
# the second contains the types of those args, for use in declaring
2576+
# a function pointer type, e.g. ['int', 'char*', 'long'].
25742577

25752578
sub C_func_signature {
25762579
my __PACKAGE__ $self = shift;
25772580
my ExtUtils::ParseXS $pxs = shift;
25782581

25792582
my @args;
2583+
my @types;
25802584
for my $param (@{$self->{kids}}) {
25812585
next if $param->{is_synthetic} # THIS/CLASS/RETVAL
25822586
# if a synthetic RETVAL has acquired an arg_num, then
@@ -2586,6 +2590,7 @@ sub C_func_signature {
25862590

25872591
if ($param->{is_length}) {
25882592
push @args, "XSauto_length_of_$param->{len_name}";
2593+
push @types, $param->{type};
25892594
next;
25902595
}
25912596

@@ -2606,9 +2611,11 @@ sub C_func_signature {
26062611
my $a = $param->{var};
26072612
$a = "&$a" if $param->{is_addr} or $io =~ /OUT/;
26082613
push @args, $a;
2614+
my $t = $param->{type};
2615+
push @types, defined $t ? $t : 'void*';
26092616
}
26102617

2611-
return join(", ", @args);
2618+
return \@args, \@types;
26122619
}
26132620

26142621

@@ -3325,7 +3332,8 @@ package ExtUtils::ParseXS::Node::autocall;
33253332
# name
33263333

33273334
BEGIN { $build_subclass->(
3328-
'args', # Str: text to use for auto function call arguments
3335+
'args', # Str: text to use for auto function call arguments
3336+
'types', # Str: text to use for auto function type declaration
33293337
)};
33303338

33313339

@@ -3340,10 +3348,35 @@ sub parse {
33403348
$xbody->{seen_autocall} = 1;
33413349

33423350
my $ioparams = $xbody->{ioparams};
3343-
my $args = $ioparams->{auto_function_sig_override}; # C_ARGS
3344-
$args = $ioparams->C_func_signature($pxs)
3345-
unless defined $args;
3346-
$self->{args} = $args;
3351+
my ($args, $types);
3352+
$args = $ioparams->{auto_function_sig_override}; # C_ARGS
3353+
if (defined $args) {
3354+
# Try to determine the C_ARGS types; for example, with
3355+
#
3356+
# foo(short s, int i, long l)
3357+
# C_ARGS: s, l
3358+
#
3359+
# set $types to ['short', 'long']. May give the wrong results if
3360+
# C_ARGS isn't just a simple list of parameter names
3361+
for my $var (split /,/, $args) {
3362+
$var =~ s/^\s+//;
3363+
$var =~ s/\s+$//;
3364+
my $param = $ioparams->{names}{$var};
3365+
# 'void*' is a desperate guess if no such parameter
3366+
push @$types, ($param && defined $param->{type})
3367+
? $param->{type} : 'void*';
3368+
}
3369+
$self->{args} = $args;
3370+
}
3371+
else {
3372+
($args, $types) = $ioparams->C_func_signature($pxs);
3373+
$self->{args} = join ', ', @$args;
3374+
}
3375+
3376+
unless ($pxs->{config_RetainCplusplusHierarchicalTypes}) {
3377+
s/:/_/g for @$types;
3378+
}
3379+
$self->{types} = join ', ', @$types;
33473380

33483381
1;
33493382
}
@@ -3375,7 +3408,8 @@ sub as_code {
33753408

33763409
print "\n\t";
33773410

3378-
if ($xsub->{decl}{return_type}{type} ne "void") {
3411+
my $ret_type = $xsub->{decl}{return_type}{type};
3412+
if ($ret_type ne "void") {
33793413
print "RETVAL = ";
33803414
}
33813415

@@ -3404,9 +3438,13 @@ sub as_code {
34043438
$name =~ s/^\Q$strip//
34053439
if defined $strip;
34063440

3407-
$name = 'XSFUNCTION'
3408-
if $xsub->{seen_INTERFACE}
3409-
or $xsub->{seen_INTERFACE_MACRO};
3441+
if ( $xsub->{seen_INTERFACE}
3442+
or $xsub->{seen_INTERFACE_MACRO})
3443+
{
3444+
$ret_type =~ s/:/_/g
3445+
unless $pxs->{config_RetainCplusplusHierarchicalTypes};
3446+
$name = "(($ret_type (*)($self->{types}))(XSFUNCTION))";
3447+
}
34103448

34113449
print "$name($self->{args});\n";
34123450

dist/ExtUtils-ParseXS/t/001-basic.t

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4811,6 +4811,10 @@ EOF
48114811
|
48124812
|PROTOTYPES: DISABLE
48134813
|
4814+
|TYPEMAP: <<EOTM
4815+
|X::Y T_IV
4816+
|EOTM
4817+
|
48144818
EOF
48154819

48164820
my @test_fns = (
@@ -4825,25 +4829,36 @@ EOF
48254829
"got XSFUNCTION declaration" ],
48264830
[ 0, 0, qr{\QXSFUNCTION = XSINTERFACE_FUNC(void,cv,XSANY.any_dptr);},
48274831
"got XSFUNCTION assign" ],
4828-
[ 0, 0, qr{\bXSFUNCTION\(\)},
4832+
[ 0, 0, qr{\Q((void (*)())(XSFUNCTION))();},
48294833
"got XSFUNCTION call" ],
48304834
],
48314835
[
48324836
'INTERFACE with perl package name',
48334837
[ Q(<<'EOF') ],
4834-
|TYPEMAP: <<EOTM
4835-
|X::Y T_IV
4836-
|EOTM
4837-
|
48384838
|X::Y
4839-
|foo()
4839+
|foo(X::Y a, char *b)
48404840
| INTERFACE: f1
48414841
EOF
48424842
[ 0, 0, qr{\b\QdXSFUNCTION(X__Y)},
48434843
"got XSFUNCTION declaration" ],
48444844
[ 0, 0, qr{\QXSFUNCTION = XSINTERFACE_FUNC(X__Y,cv,XSANY.any_dptr);},
48454845
"got XSFUNCTION assign" ],
4846-
[ 0, 0, qr{\bXSFUNCTION\(\)},
4846+
[ 0, 0, qr{\QRETVAL = ((X__Y (*)(X__Y, char *))(XSFUNCTION))(a, b);},
4847+
"got XSFUNCTION call" ],
4848+
],
4849+
[
4850+
'INTERFACE with C_ARGS',
4851+
[ Q(<<'EOF') ],
4852+
|char *
4853+
|foo(X::Y a, int b, char *c)
4854+
| INTERFACE: f1
4855+
| C_ARGS: a, c
4856+
EOF
4857+
[ 0, 0, qr{\b\QdXSFUNCTION(char *)},
4858+
"got XSFUNCTION declaration" ],
4859+
[ 0, 0, qr{\QXSFUNCTION = XSINTERFACE_FUNC(char *,cv,XSANY.any_dptr);},
4860+
"got XSFUNCTION assign" ],
4861+
[ 0, 0, qr{\QRETVAL = ((char * (*)(X__Y, char *))(XSFUNCTION))(a, c);},
48474862
"got XSFUNCTION call" ],
48484863
],
48494864
);

dist/ExtUtils-ParseXS/t/002-more.t

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ExtUtils::CBuilder;
99
use attributes;
1010
use overload;
1111

12-
plan tests => 33;
12+
plan tests => 35;
1313

1414
my ($source_file, $obj_file, $lib_file);
1515

@@ -47,7 +47,7 @@ SKIP: {
4747
}
4848

4949
SKIP: {
50-
skip "no dynamic loading", 29
50+
skip "no dynamic loading", 31
5151
if !$b->have_compiler || !$Config{usedl};
5252
my $module = 'XSMore';
5353
$lib_file = $b->link( objects => $obj_file, module_name => $module );
@@ -113,6 +113,9 @@ SKIP: {
113113
is XSMore::typemaptest3(12, 13, 14), 12, 'Simple embedded typemap works for input, too';
114114
is XSMore::typemaptest6(5), 5, '<<END; (with semicolon) matches delimiter "END"';
115115

116+
is XSMore::myadd1(5,7), 12, 'INTERFACE: myadd1';
117+
is XSMore::myadd2(5,99,13), 18, 'INTERFACE: myadd2, C_ARGS';
118+
116119
# Win32 needs to close the DLL before it can unlink it, but unfortunately
117120
# dl_unload_file was missing on Win32 prior to perl change #24679!
118121
if ($^O eq 'MSWin32' and defined &DynaLoader::dl_unload_file) {

dist/ExtUtils-ParseXS/t/XSMore.xs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ len(const char* const s, int const l){
7878
return l;
7979
}
8080

81+
STATIC int
82+
myadd1(int a, int b)
83+
{ return a + b; }
84+
85+
STATIC int
86+
myadd2(int a, int b)
87+
{ return a + b; }
88+
89+
8190
MODULE = XSMore PACKAGE = XSMore
8291

8392
=for testing
@@ -244,6 +253,15 @@ outlist_int(const char *a, const char *b, OUTLIST char *c)
244253
int
245254
len(char* s, int length(s))
246255

256+
int
257+
interface1(int a, int b)
258+
INTERFACE: myadd1
259+
260+
int
261+
interface2(int a, int b, int c)
262+
INTERFACE: myadd2
263+
C_ARGS: a, c
264+
247265
INCLUDE_COMMAND: $^X -Ilib -It/lib -MIncludeTester -e IncludeTester::print_xs
248266

249267
#if 1

0 commit comments

Comments
 (0)