#println()ing any

1 messages · Page 1 of 1 (latest)

distant wagon
#

There is a bug in my code. In the process of debugging, I've come across something that I don't understand.

The last 2 lines of "main" produce what appears to be gibberish.

Any help/comments would be appreciated.

background: Main() wants to create a generic struct containing a string. The function "new_datum_strin()" appears to work and appears to display the copied string correctly. Main() can println() the result in whole, but, appears to print gibberish when println()ing the .data field of the result. [Itlooks like I've created a traditional C pointer problem of some sort.]

Whittled-down code...

package zd

import "core:strings"
import "core:mem"
import "core:os"
import "core:fmt"

Datum :: struct {
    data: any,
    clone: #type proc (^Datum) -> ^Datum,
    reclaim: #type proc (^Datum),
}

new_datum_string :: proc (s : string) -> ^Datum {
    p := new (Datum)
    p.data = strings.clone (s)
    p.clone = clone_datum_string
    p.reclaim = reclaim_datum_string
    fmt.println ("new_datum_string", p.data)
    return p
}

clone_datum_string :: proc (src: ^Datum) -> ^Datum {
    // don't care
    return src
}

reclaim_datum_string :: proc (src: ^Datum) {
    // don't care
}

main :: proc () {
    d := new_datum_string ("fr/find.md")
    fmt.println (d)
    fmt.println (d.data)
    fmt.println ("inject B", d.data)
}
small pendant
#

the problem is this

p.data = strings.clone (s)

is more like this

tmp := strings.clone(s)
p.data = tmp

which in turn makes p.data a pointer to tmp on the stack, instead of the string (this leads to memory corruption)

#

in other words if you imagine Datum as Datum :: struct { data: ^string }
then it is doing

tmp := strings.clone(s)
p.data = &tmp
#

a "workaround" is something like this

data := new(string) // heap storage for the *string's* ptr+len that will outlive this function
data^ = strings.clone(s)
p.data = data
#

or alternatively add a place to put the length field
then you can store the strings.clone pointer directly

small pendant
#

roughly, visually
the middle bit is what is violated here

(and in context of 0d implementations, part of the reason why i don't use any deep in the backend, its not really meant for this kind of stuff)

patent portal
#

holy shit this is next level explanation @small pendant

#

So the issue is that the clone is not allocating on the heap so the pointer is lost after leaving the scope?

small pendant
#

the clone is allocating on the heap
the issue is the p.data = assignment implicitly stores a pointer to the string metadata that sits on the stack of the current function

#

it's special behavior of any

patent portal
#

sorry would you mind explaining which metadata? I would have imagine the any pointer reference the string pointer that was created on the heap. I guess that's what is confusing

#

so leaving the scope the pointer should still point to the same (still existing in the heap) location

distant wagon
#

theory: my knowledge of C is interfering with my knowledge of Odin. Strings in C are char*, but not in Odin. In Odin, a string is a struct...

#

when strings.clone(...) is called, which parts get cloned? I currently think that only the green thing is cloned. The blue thing isn't touched. Correct?

#

... (I mean the right-most blue thing) ...

frank gyro
#

Both are cloned, the problem is that any has a pointer to the green thing, which is stored on the stack in your case.

#

You might want to reconsider the use of any, it's very rarely a good solution to problems like this

#

@small pendant how did you make that image btw? It looks really nice!

placid nacelle
placid nacelle
placid nacelle
# small pendant in other words if you imagine Datum as `Datum :: struct { data: ^string }` then ...

To expand on this a little, any's trick is indeed that it captures a pointer to the value you give it.
This means that x: any = value captures &value.
You can also do x: any = pointer^ to capture pointer instead.

But yes - I generally echo Jakub's advice; don't use any unless you're very fucking sure how it works, because it's easy to end up UAFs 😄
You can only really use it safely when you just pass one down the call stack - like a formatting proc. (Which is primarily why Odin has any.)

small pendant
rugged iris
distant wagon