Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-3231] Generated C string doesn't survive until called C function returns #45819

Closed
swift-ci opened this issue Nov 17, 2016 · 11 comments
Closed
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself crash Bug: A crash, i.e., an abnormal termination of software run-time crash Bug → crash: Swift code crashed during execution

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-3231
Radar rdar://problem/31325077
Original Reporter Mecki (JIRA User)
Type Bug
Status Resolved
Resolution Done
Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug, Miscompile, RunTimeCrash
Assignee None
Priority Medium

md5: afb3f0f08a5c97423951a93c429a0a5c

is duplicated by:

  • SR-4249 Result of String.cstring(using:) deallocated prematurely when passed into bridged C function

relates to:

  • SR-2814 Swift does not correctly pass in multiple optional strings to C function

Issue Description:

I have a C function that expects an optional string as argument (among other parameters, but let's ignore these for a moment). The C declaration looks like this:

OpaqueObj *_Nullable MyFunction (
    const char *_Nullable optString
);

and for Swift it looks like this:

public func MyFunction(
    _ optString: UnsafePointer<Int8>?
) -> UnsafeMutablePointer<OpaqueObj>?

What I'm doing now, I'm trying to feed a Swift string, imagine that code:

let test = "Hello World"
let data = MyFunction(test)

When I break into MyFunction, the string looks perfectly okay at the top of it but when it then calls another C function, the string is junk on return. I ran the app with guard malloc enabled and this even crashes when MyFunction is trying to use that string, so the string memory seems to have been released already at that moment, which would also explain why it becomes corrupted.

I do understand that the automatic generated C string has a very limited lifespan, so I don't expect it to stay alive beyond the call of MyFunction, because how would its memory be managed to not leak otherwise? But in my case it doesn't even seem to stay alive while MyFunction is still running and that's something I don't understand.

MyFunction adds the string to a C array of strings, like this:

const char * strings[] = {
    ..., optString, ...
};

and a reference to strings is passed around but that really shouldn't play a role. As long as the C string stays alive until MyFunction returns, everything should be okay, as after return nobody holds or uses a reference to optString any longer (its data will have been copied to internal buffer already).

@belkadan
Copy link
Contributor

The situation you've described should behave as expected, so I'm guessing there's something more complicated going on. Can you attach a full reproducing test case?

@swift-ci
Copy link
Collaborator Author

Comment by Markus Hanauska (JIRA)

I cannot reproduce that in a simple stripped down test case but I can reproduce that 100% in our application. Dangling pointers is something you can hardly ever reproduce with simple test cases as in a single threaded app I can usually access freed memory just fine as long as I perform no alloc operation that might wipe it (rarely this will fail in a very simple app).

What I don't understand is that I cannot even find the allocation of the C string when I run the project in Instruments. I added this line of code fprintf(stderr, "\n\n**** %p ****\n\n", optString); so I know the address of this string and when I then search for that address in Instruments, there is nothing, no allocation that would match. Not even one close to that address. Also when I make a breakpoint at the very top of the function, print the address and then open the Xcode 8 memory graph, this graph doesn't know anything about the address of optString

So I currently have no way to find out when this memory is created, by whom it is created and when and by whom it is freed again. I tried malloc history but when I query that address, I also just get an error and no history. I tried setting watchpoints on the string memory, no success either.

The only thing I've been able to find out so far is that between the last Swift line and the first C line, there's a call to _convertConstStringToUT8PointerArgument which is probably expected. Any debugging tips?

@swift-ci
Copy link
Collaborator Author

Comment by Markus Hanauska (JIRA)

By the way, I ended up to write code like this:

let tempString = optString as NSString?
let data = MyFunction(tempString?.utf8String)

And this codes works flawlessly and exactly as expected (also passes all instrument/guard malloc tests).

@belkadan
Copy link
Contributor

Are you able to attach the code that contains the call? I'm pretty sure that'll help figure out what's going on.

@swift-ci
Copy link
Collaborator Author

Comment by Markus Hanauska (JIRA)

Are you able to attach the code that contains the call?

Only if I clean it up first, which will be quite a bit of work; but I'm giving my best.

Let me just share some more details that could all play a role:

  • The code runs within a block.

  • The block is the only provider of an autorelease pool.

  • The block uses defer blocks for clean up.

  • The block is scheduled to run on a queue.

  • From what I've seen, though, this is currently always the queue of the main thread.

  • The string value comes from a property of a swift object which inherits of NSObject.

  • The property is set from Obj-C code, so initially the string is a NSString object.

  • The Swift string is alive and okay when the C function call returns.

  • The passed C string seems okay as long as nobody allocates memory or calls strlen on it.

  • The corruption seems to be always identical and not random (first string bytes becomes 0x02, rest 0x00)

@belkadan
Copy link
Contributor

Ooh, I wonder if the optional chaining is messing it up. What happens if you break that out into a separate assignment into a String? local? Or to go a step further, to unwrap the optional and make two calls depending on whether it's nil or not?

@swift-ci
Copy link
Collaborator Author

Comment by Markus Hanauska (JIRA)

@belkadan You could be on the right track here. While this code fails every time

let optString = someObj.someString
let data = MyFunction(optString)

this code so far has been working every time:

guard let optString = someObj.someString else { return }
let data = MyFunction(optString)

Just that optString is named that way for a reason. It is okay for that string to be nil and it is okay to call the C function with a NULL pointer, as it will still produce data I need in either case. In this example it may be easy as here there's only a single string parameter, so I could do

let data: // appropriate-type
if let string =  someObj.someString {
    data = MyFunction(string)
} else {
    data = MyFunction(nil)
}

but I have other C functions that take up to eight parameters of that kind and this solution would not work there.

I was considering writing it like that but that doesn't work at all:

let data = MyFunction( (optString != nil ? optString! : nil) )

Calling the function like that causes the first character of the string to be already corrupted when the C function enters.

@belkadan
Copy link
Contributor

@swift-ci create

@belkadan
Copy link
Contributor

belkadan commented May 5, 2017

I think John fixed this in #9033 ! If you want, you can test it by downloading a development snapshot from swift.org.

@swift-ci
Copy link
Collaborator Author

swift-ci commented May 8, 2017

Comment by Matt Deckard (JIRA)

If [SR-4249] is truly a duplicate of this issue, then as far as I can tell, it had already been fixed as of Xcode 8.3.2.

EDIT: Well actually I can't even reproduce it in 8.2.1 now, so I don't know what is going on...

@belkadan
Copy link
Contributor

belkadan commented May 8, 2017

This was definitely still broken in 8.3.2. It's the optional that throws things off.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@AnthonyLatsis AnthonyLatsis added the crash Bug: A crash, i.e., an abnormal termination of software label Dec 12, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself crash Bug: A crash, i.e., an abnormal termination of software run-time crash Bug → crash: Swift code crashed during execution
Projects
None yet
Development

No branches or pull requests

3 participants