Chapel: Pointer to unmanaged object invalidated when object is out of scope

Created on 18 Nov 2019  路  5Comments  路  Source: chapel-lang/chapel

Summary of Problem

Trying to dereference a pointer to an unmanaged object that was defined in a function scope gives an invalid address.

Some checks that were done:

  • If the object is globally defined, this issue does not occur
  • A print statement in a user-defined deinit indicates that the object is not deinitialized before the end of the program.

This can definitely be distilled further, such as removing the integer casts, for example. I might take a stab at doing this when I get a chance.

Steps to Reproduce

Source Code:

proc main() {
  writeln('(main) makeC');
  var ptr = makeC();

  writeln('(main) ptr: ', ptr);
  var cPtr =  __primitive("cast", c_ptr(unmanaged C), ptr);
  writeln('(main) cPtr: ', cPtr);
  var c = cPtr.deref();
  writeln('(main) c: ', c);

  writeln('(main) writeC');
  writeC(ptr);
}

proc makeC() : int {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = c_ptrTo(x):int;
  writeln('(makeC) ptr: ', ptr);
  return ptr;
}

proc writeC(ptr : int) {
  writeln('(writeC) ptr: ', ptr);
  var cPtr =  __primitive("cast", c_ptr(unmanaged C), ptr);
  writeln('(writeC) cPtr: ', cPtr);
  var c = cPtr.deref();
  writeln('(writeC) c: ', c);
}


class C {
  var x : int = 1;
  proc deinit() {
    writeln('C.deinit() called');
  }
}

Compile command:

chpl repro.chpl

Execution command:

Here is the local output on my machine, but note that running with valgrind on linux yields a seg fault at the first dereference outside of makeC:

(main) makeC
(makeC) x: {x = 2}
(makeC) ptr: 4513062368
(main) ptr: 4513062368
(main) cPtr: 0x10cffdde0
(main) c: {x = 2}              # <--- seg faults when running with valgrind
(main) writeC
(writeC) ptr: 4513062368
(writeC) cPtr: 0x10cffdde0
(writeC) c: nil

Configuration Information

  • Output of chpl --version: chpl version 1.21.0 pre-release (f3e74adea4)
Compiler Bug user issue

Most helpful comment

The code is wrong.

proc makeC() : int {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = c_ptrTo(x):int;
  writeln('(makeC) ptr: ', ptr);
  return ptr;
}

This returns a pointer to a local variable (as an integer). You might imagine in being like this C code:

long makeC() {
  void* x = malloc(16);
  return (long) &x;
}

Here is a corrected version of the example that runs correctly with valgrind:

proc main() {
  writeln('(main) makeC');
  var ptr = makeC();

  writeln('(main) ptr: ', ptr);
  var c = (ptr:unmanaged C?)!;
  writeln('(main) c: ', c);

  writeln('(main) writeC');
  writeC(ptr);
}

proc makeC() : c_void_ptr {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = x:c_void_ptr;
  writeln('(makeC) ptr: ', ptr);
  return ptr;
}

proc writeC(ptr : c_void_ptr) {
  writeln('(writeC) ptr: ', ptr);
  var c = (ptr:unmanaged C?)!;
  writeln('(writeC) c: ', c);
}


class C {
  var x : int = 1;
  proc deinit() {
    writeln('C.deinit() called');
  }
}

All 5 comments

Marked as user issue since @npadmana ran into this.

FYI @mppf - this is the issue we were chatting about offline.

The code is wrong.

proc makeC() : int {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = c_ptrTo(x):int;
  writeln('(makeC) ptr: ', ptr);
  return ptr;
}

This returns a pointer to a local variable (as an integer). You might imagine in being like this C code:

long makeC() {
  void* x = malloc(16);
  return (long) &x;
}

Here is a corrected version of the example that runs correctly with valgrind:

proc main() {
  writeln('(main) makeC');
  var ptr = makeC();

  writeln('(main) ptr: ', ptr);
  var c = (ptr:unmanaged C?)!;
  writeln('(main) c: ', c);

  writeln('(main) writeC');
  writeC(ptr);
}

proc makeC() : c_void_ptr {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = x:c_void_ptr;
  writeln('(makeC) ptr: ', ptr);
  return ptr;
}

proc writeC(ptr : c_void_ptr) {
  writeln('(writeC) ptr: ', ptr);
  var c = (ptr:unmanaged C?)!;
  writeln('(writeC) c: ', c);
}


class C {
  var x : int = 1;
  proc deinit() {
    writeln('C.deinit() called');
  }
}

Thanks @mppf!

@npadmana - I've ported Michael's example to the integer-as-pointer use-case you had in mind originally, and confirmed it works as expected:

proc main() {
  writeln('(main) makeC');
  var ptr = makeC();

  writeln('(main) writeC');
  writeC(ptr);
}

proc makeC() : int {
  var x = new unmanaged C(2);
  writeln('(makeC) x: ', x);
  var ptr = x:c_void_ptr;
  writeln('(makeC) ptr: ', ptr);
  return ptr:int;
}

proc writeC(intPtr : int) {
  var ptr = __primitive("cast", c_void_ptr, intPtr);
  writeln('(writeC) ptr: ', ptr);
  var x = (ptr:unmanaged C?)!;
  writeln('(writeC) x: ', x);
}


class C {
  var x : int = 1;
}
> ./pointer-as-int
(main) makeC
(makeC) x: {x = 2}
(makeC) ptr: 0x104010310
(main) writeC
(writeC) ptr: 0x104010310
(writeC) x: {x = 2}

I'm going to close this issue, but let me know if you run into any other problems related to this.

Thanks, Michael!!!

I wonder if this would be an interesting example to add either to SO, or to the technote on interoperability.

Was this page helpful?
0 / 5 - 0 ratings