Menu

#1134 C++ memory leak of temporaries on exception

None
open
nobody
perl (97)
5
2023-09-12
2011-02-23
Roy LeCates
No

Temporary C++ objects (created as "new local variables" in a typemap) do not get destroyed properly when SWIG_fail is used. In the generated wrapper, the "fail" label is incorrectly located in the same scope as the temporary variables. The SWIG_fail jumps to "fail:" which triggers SWIG_croak_null() which results in a Perl croak() which uses longjmp() to exit. Hence, the destructors for objects on the stack are never called and associated memory is leaked. A simple fix is to place the entire function contents (before the "fail" label) inside of a { } block, causing it to leave scope cleanly before croaking.

For example, use of the following typemap will cause temp_str to be leaked on a SWIG_fail.
%typemap(in) String (std::string temp_str) {
STRLEN temp_len = 0;
const char* temp_ptr;
temp_ptr = SvPV($input, temp_len);
temp_str.assign(temp_ptr, temp_len);
$1 = &temp_str;
}

Discussion

  • Olly Betts

    Olly Betts - 2022-03-19

    Reproduced with current SWIG git master. Simple interface file:

    %module test
    %typemap(in) String (std::string temp_str) {
    STRLEN temp_len = 0;
    const char* temp_ptr;
    temp_ptr = SvPV($input, temp_len);
    temp_str.assign(temp_ptr, temp_len);
    $1 = &temp_str;
    }
    %inline %{
    int xyzzy(String x) {
        return len(x);
    }
    %}
    

    Then:

    $ grep -A24 'XS(_wrap_xyzzy)' test_wrap.cxx 
    XS(_wrap_xyzzy) {
      {
        String arg1 ;
        std::string temp_str1 ;
        int argvi = 0;
        int result;
        dXSARGS;
    
        if ((items < 1) || (items > 1)) {
          SWIG_croak("Usage: xyzzy(x);");
        }
        {
          STRLEN temp_len = 0;
          const char* temp_ptr;
          temp_ptr = SvPV(ST(0), temp_len);
          temp_str1.assign(temp_ptr, temp_len);
          arg1 = &temp_str1;
        }
        result = (int)xyzzy(arg1);
        ST(argvi) = SWIG_From_int  SWIG_PERL_CALL_ARGS_1(static_cast< int >(result)); argvi++ ;
        XSRETURN(argvi);
      fail:
        SWIG_croak_null();
      }
    }
    
     
  • Olly Betts

    Olly Betts - 2022-03-19

    This issue is specific to languages where whatever SWIG does on "fail" calls longjmp. It's pretty common that we set an exception via the target language's C API and then return a dummy value from our wrapper function in which case the C++ object should get destroyed OK.

    A simple fix is to place the entire function contents (before the "fail" label) inside of a { } block, causing it to leave scope cleanly before croaking.

    Sounds good. I did a quick test and this indeed does the trick.

     
  • Olly Betts

    Olly Betts - 2023-09-12
    • Group: -->
     
  • Olly Betts

    Olly Betts - 2023-09-12

    I fixed this same issue for Lua in 9ab9c716239d2855bdd6c0771e2e980c0767fb57, and https://github.com/swig/swig/issues/1981 is the same but for Go.

    There's a testcase exception_memory_leak for it so writing a _runme.pl for that would provide a regression test.

     

Log in to post a comment.