Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Passing NULL pointer through XS without "Use of uninitialized value in subroutine entry"

by kscaldef (Pilgrim)
on Aug 21, 2006 at 23:03 UTC ( [id://568707]=perlquestion: print w/replies, xml ) Need Help??

kscaldef has asked for the wisdom of the Perl Monks concerning the following question:

I have some XS to interface with a C API where one of the arguments is a pointer which may be null. However, calling it this way causes it to spit out the warning "Use of uninitialized value in subroutine entry". Example usage (names changes to protect the innocent):
my $foo = undef; my $bar = Foo::create_bar (\$foo);
The XS is:
struct bar* create_bar(foo) struct type_foo* foo CODE: RETVAL = my_c_create_bar(foo); OUTPUT: RETVAL
And, there's a type map:
TYPEMAP struct bar* T_OBJECT struct type_foo* T_OBJECT
I read Source of warning message: "Use of uninitialized value in subroutine entry" and that suggested that this warning could be encountered passing NULL to functions in the perl API, but in this case it's an external C library where, as I said, NULL is a perfectly valid value. Is there something I should be doing to communicate that NULL is okay?
  • Comment on Passing NULL pointer through XS without "Use of uninitialized value in subroutine entry"
  • Select or Download Code

Replies are listed 'Best First'.
Re: Passing NULL pointer through XS without "Use of uninitialized value in subroutine entry"
by ikegami (Patriarch) on Aug 22, 2006 at 05:22 UTC

    First, keep in mind I've never used XS. I can't guarantee the following works or even its soundness.

    If you check the system typemap (lib/ExtUtils/typemap), you'll see the definition of T_PTROBJ. (What is T_OBJECT?) We can create a new rule based on T_PTROBJ to suit our needs.

    # typedef struct bar* Bar; # typedef struct type_foo* Foo; TYPEMAP ####### Bar T_NULLABLE_PTROBJ Foo T_NULLABLE_PTROBJ INPUT ##### T_NULLABLE_PTROBJ if ( !SvOK( $arg ) ) { /* if not defined */ $var = NULL; } else if ( sv_derived_from( $arg, \"${ntype}\" ) ) { IV tmp = SvIV( (SV*)SvRV( $arg ) ); $var = INT2PTR( $type, tmp ); } else { Perl_croak( aTHX_ \"$var is not of type ${ntype}\" ) } OUTPUT ###### T_NULLABLE_PTROBJ if ( $var ) /* if defined */ sv_setref_pv( $arg, \"${ntype}\", (void*)$var ); else $arg = &PL_sv_undef;

    The usage would be

    my $foo = undef; my $bar = Foo::create_bar( $foo );

    Update: I don't think addressing the problem in the typemap is the best method. This makes pointers of that type always nullable, whereas that should be decided on a per-function (and even per-argument) basis. That means we should addresses the issue in the XS stub.

    # typedef struct bar* Bar; # typedef struct type_foo* Foo; # In typemap: # Bar T_PTROBJ # Foo T_PTROBJ struct bar* create_bar(foo_sv = 0) SV* foo_sv CODE: struct type_foo* foo = NULL; if ( !foo_sv ) { /* Use default of NULL. */ } else if ( !SvOK( foo_sv ) ) { /* Use default of NULL. */ } else if ( sv_derived_from( foo_sv, 'type_foo' ) ) { IV tmp = SvIV( (SV*)SvRV( foo_sv ) ); foo = INT2PTR( struct type_foo*, tmp ); } else { Perl_croak( aTHX_ "\$foo is not of type foo_type" ) } RETVAL = my_c_create_bar(foo); OUTPUT: RETVAL

    Bonus: The = 0 and the if ( !foo_sv ) allows the parameter to be omitted. Feel free to remove this.

    Update: Now uses tabs in typemap, as required.

    Update: struct bar* and struct type_foo* are no good for type T_PTROBJ — search for T_PTROBJ_SPECIAL in perlxs — so I added typedefs.

      Somewhat inspired by your suggestion, and taking some things from the original generated C code, I've currently got something like this:
      struct bar* create_event(foo_sv) SV* foo_sv struct type_foo* foo = NULL; CODE: if ( SvROK(foo_sv)) { SV *tmp = (SV*) SvRV(foo_sv); if ( SvOK (tmp)) { foo = (struct type_foo*) SvIV(tmp); } } else { warn( "Foo::create_bar() -- foo is not an SV reference" ); XSRETURN_UNDEF; } RETVAL = my_c_create_bar(foo); OUTPUT: RETVAL
      which successfully avoids the warning message I was getting originally. Unfortunately, this is one of those bits of code someone else wrote originally and didn't write tests for, so now I need to go and actually write some tests to make sure this works right.

        What's the problem with what I provided? You've introduced so many bugs!

        • Your function now accepts any scalar as an argument.
          create_bar(123); is accepted.
          create_bar('abc'); is accepted.
          create_bar(['a', 'b']); is accepted.
          create_bar({key => 'val'}); is accepted.

        • If the argument is a reference, it will unconditionally be treated as an object of type type_foo, even if it isn't even an object.

        • warn gets executed when you pass undef.

        • Do you even know what XSRETURN_UNDEF does? I don't, but I'm pretty sure it's not used properly.

        You should be using type T_PTROBJ if you didn't accept undef, so you should base your code on T_PTROBJ.

        Updated

Re: Passing NULL pointer through XS without "Use of uninitialized value in subroutine entry"
by samtregar (Abbot) on Aug 21, 2006 at 23:18 UTC
    Does it help if you change your code to not pass a reference to undef, but instead just pass undef?

       my $bar = Foo::create_bar($foo);

    That seems like a more straight-forward representation of C's NULL to me.

    -sam

      If I do that, with no other changes, I get the error: "create_bar() -- foo is not an SV reference".
Re: Passing NULL pointer through XS without "Use of uninitialized value in subroutine entry"
by syphilis (Archbishop) on Aug 22, 2006 at 09:56 UTC
    Perhaps pass "\0' rather than undef:
    my $foo = undef; my $bar = wrap_create_bar(\$foo); sub wrap_create_bar { if(defined(${$_[0]}) {create_bar($_[0]} else {create_bar(\"\0")} }

    Update: I've had a couple of goes at correcting the above code (which is, of course, untested). I hope I've got it right ... or at least made my intention clear.

    I'm basing that on the following Inline::C script, for which the foo1() sub produces the warning, but the foo2() sub does not. (Plus the fact that C seems to regard "\0" as equivalent to NULL.)
    use warnings; use Inline C => Config => BUILD_NOISY => 1; use Inline C => <<'EOC'; void foo1(SV *x) { printf("%s\n", SvPV_nolen(x)); } EOC $x = undef; foo1($x); foo2($x); sub foo2 { if(defined($x)) {foo1($x)} else {foo1("\0")} }

    The problem is that as soon as a perl API function detects that it has been passed an undef, you'll get that warning (iff warnings are enabled). Simplest solution is to therefore pass a value that is not regarded as undef, but still has the desired effect - which means modifying the undef argument appropriately before it gets passed to the XSub. At least that's the only approach I could get to work for me ... though there may be others.

    Cheers,
    Rob

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://568707]
Approved by Joost
Front-paged by ikegami
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (7)
As of 2024-04-23 12:34 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found