wvxvw
wvxvw

Reputation: 9464

Spot Undefined Behavior in this code snippet

I'm 90% sure the bizarre result I'm seeing here is some kind of UB triggered by my code, however, I cannot find it:

cdef char** to_cstring_array(strings):
    cdef char** result = <char**>malloc(len(strings) * sizeof(char*))
    cdef bytes item
    nelements = len(strings)
    i = 0

    while i < nelements:
        s = str(strings[i]).encode("utf-8")
        item = s
        result[i] = item
        i += 1
    return result


cpdef object pykubectl_get(object items, object options=None):
    cdef size_t nargs = len(items);
    cdef bytes message
    cdef char** args = to_cstring_array(items);
    # message = args[0]
    # print("1 items encoded: {}".format(message))
    json_opts = json.dumps(options or {}).encode("utf-8")
    cdef char* opts = json_opts
    print("items: {}".format(items))
    message = args[0]
    print("2 items encoded: {}".format(message))
    cdef ResourceGet_return result = kubectl_get(
        opts,
        len(json_opts),
        <const char**>args,
        nargs
    )
    free(args)

    if result.r0.n == 0:
        message = result.r1.p
        raise Exception("kubectl failed: '{}'".format(message.decode("utf-8")))
    message = result.r0.p
    return json.loads(message.decode("utf-8"))

If I uncomment the lines assigning first argument to message, the contents of args changes. My guess is that compiler is trying to optimize something here, and that it could be related to how memory allocation is done, but I cannot see any problems with it. I also know that, in principle, I shouldn't need to cast to const char**, but I'm getting a warning for not casting, so I added it. It changes nothing if I ignore the warning and don't cast.

I'm not posting the generated C code because it's huge... but if this will prove inevitable, I'll post it elsewhere.

Upvotes: 0

Views: 139

Answers (1)

DavidW
DavidW

Reputation: 30930

result[i] = item

This creates a pointer to the first element of your Python bytes object item. It does not cause item to be kept alive. As soon as item is freed (probably on the next iteration of the loop) then the pointer is immediately invalid.

You need to actually copy the memory - something like:

cdef char* item_as_charp
    # ....
    # then in the loop
    item_as_charp = <char*>malloc(sizeof(char)*len(item))
    memcpy(item_as_charp,item, len(item))

(You possibly need to use len(item)+1 to give room for the null terminator)

You need to free this memory once you're done with it, of course

Upvotes: 2

Related Questions