That's more or less what I was thinking.  I came up with this class
and test case based on a test that was already part of the test suite;
essentially the same bug but in a more elaborate class.  The test is
t/15basic.t in my github tests branch, or t/01basic.t in any of the
current CPAN versions.  The same bug must exist in several of
Inline::CPP's POD examples, and might even be an issue in one of
Inline::Struct's examples.

Essentially any time a char* is passed in as a parameter, and then
kept around as member data there's the issue of the pointer being
invalidated by the ref-count of the original SV passed in and
converted to a char* falling out to zero.  The solution in this case
is to copy the string within the class's constructor, and delete it in
a destructor.  It just caught me off-guard because the same sort of
code is common in Inline::CPP's original test suite (which I'm now in
the process of re-writing).



On Mon, Feb 6, 2012 at 5:46 AM, David Mertens <dcmertens.p...@gmail.com> wrote:
> David,
>
> I believe this is a bug in your class's interaction with Perl. The fact that
> the first set of tests passes is due to luck, I would guess. If you are
> referencing a Perl scalar, you must increment its reference count, and
> decrement the count on object destruction.
>
> There are also issues with guaranteeing that the string is ASCII, as a
> general use case could very well have UTF-8. A char pointer would gladly
> store that, of course, but the return value of the accessor will not
> properly mark the SV with the UTF-8 flag.
>
> David
>
> On Feb 6, 2012 3:00 AM, "David Oswald" <daosw...@gmail.com> wrote:
>>
>> I stumbled across a strange issue in Inline::CPP.  Here's a test to
>> illustrate it:
>>
>> # Break object instantiation such that accessors fetch bad data.
>>
>> use strict;
>> use warnings;
>>
>> use Test::More;
>>
>> use Inline 'C++' => <<END;
>>
>> class CStrTest {
>>    public:
>>        CStrTest( char* a );
>>        char* get_name();
>>    private:
>>        char* x;
>> };
>>
>> CStrTest::CStrTest( char* a ) {
>>   x = a;
>> }
>>
>> char* CStrTest::get_name() {
>>    return x;
>> }
>>
>> END
>>
>>
>> note( 'Subtest: Testing object instantiated by Class->new() syntax.' );
>>
>> subtest 'Object instantiated with Class->new() syntax.' => sub {
>>    plan tests => 4;
>>
>>    my $obj1 = CStrTest->new( 'Honest' );
>>    my $obj2 = CStrTest->new( 'Lucky'  );
>>    isa_ok( $obj1, 'CStrTest', '$obj1' );
>>    isa_ok( $obj2, 'CStrTest', '$obj2' );
>>    is( $obj1->get_name, 'Honest', "get_name on \$obj1 (Honest)" );
>>    is( $obj2->get_name, 'Lucky',  "get_name on \$obj2 (Lucky)"  );
>>
>> };
>>
>>
>> TODO: {
>>
>>    local $TODO = 'Tests on new_ok() objects fail. Still investigating
>> why.';
>>
>>    note(
>>        'Subtest: Testing object instantiated by ' .
>>        'Test::More::new_ok() syntax.'
>>    );
>>
>>    subtest 'Object instantiated with new_ok().' => sub {
>>        plan tests => 4;
>>
>>        my $obj1 = new_ok( 'CStrTest', [ 'Mickey' ], '$obj1' );
>>        my $obj2 = new_ok( 'CStrTest', [ 'Donald' ], '$obj2' );
>>        is( $obj1->get_name, 'Mickey', "get_name on \$obj1 (Mickey)" );
>>        is( $obj2->get_name, 'Donald', "get_name on \$obj2 (Donald)" );
>>    };
>>
>> }
>>
>> done_testing();
>>
>> __END__
>>
>> And the output.....
>>
>> # Subtest: Testing object instantiated by Class->new() syntax.
>>    1..4
>>    ok 1 - $obj1 isa CStrTest
>>    ok 2 - $obj2 isa CStrTest
>>    ok 3 - get_name on $obj1 (Honest)
>>    ok 4 - get_name on $obj2 (Lucky)
>> ok 1 - Object instantiated with Class->new() syntax.
>> # Subtest: Testing object instantiated by Test::More::new_ok() syntax.
>>    1..4
>>    ok 1 - $obj1 isa CStrTest
>>    ok 2 - $obj2 isa CStrTest
>>    not ok 3 - get_name on $obj1 (Mickey)
>>    #   Failed test 'get_name on $obj1 (Mickey)'
>>    #   at t/16charptr.t line 58.
>>    #          got: 'Donald'
>>    #     expected: 'Mickey'
>>    not ok 4 - get_name on $obj2 (Donald)
>>    #   Failed test 'get_name on $obj2 (Donald)'
>>    #   at t/16charptr.t line 59.
>>    #          got: ''
>>    #     expected: 'Donald'
>>    # Looks like you failed 2 tests of 4.
>> not ok 2 - Object instantiated with new_ok(). # TODO Tests on new_ok()
>> objects fail. Still investigating why.
>> #   Failed (TODO) test 'Object instantiated with new_ok().'
>> #   at t/16charptr.t line 60.
>> 1..2
>>
>> It looks to me like what's happening is that when the object is
>> instantiated using Test::More::use_ok(), the string passed in to the
>> constructor must be falling out of scope and being garbage-collected
>> so that the char* becomes invalid.  But this isn't happening when the
>> object is instantiated with Class->new() syntax.
>>
>> That's just a hunch.  Here are some additional facts:  There is no
>> failure if we pass a basic data type instead of a pointer to a
>> c-string.  That's because when passing basic data types copies are
>> made, whereas when passing pointers around there is no additional copy
>> of the data pointed to.
>>
>> I'm trying to decide if this is a Test::More::use_ok bug, a bug in my
>> test C++ code, a bug in Inline::CPP, a bug in the typemap for char*,
>> or a bug in Inline::C.
>>
>> If anyone wishes to play with it, you can find it in my github repo:
>> g...@github.com:daoswald/Inline-CPP.git
>>
>> Check out the 'cstr-test' branch.  The specific test is t/16charptr.t
>>
>> Dave
>>
>> --
>>
>> David Oswald
>> daosw...@gmail.com



-- 

David Oswald
daosw...@gmail.com

Reply via email to