-
Notifications
You must be signed in to change notification settings - Fork 12
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
refactor: captures unsafe casting from TSQueryMatch #24
Conversation
can you actually explain the captures "not working correctly" and what "unsafe rules" it broke? I also would rather not change the layout of C-compatible structs, they're intentionally designed like that. |
I can't really explain why this first doesn't work other than showing you an actual example: They both print different things for some reason. from https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go
I can keep that 👍🏻 |
The unsafe rules are enumerated under https://pkg.go.dev/[email protected]#Pointer. In particular, since
The fact that it violates that rule is my best guess for why adding an explicit loop to build the slice of Go types fixes the problem. The Go compiler optimizes rather aggressively around uses of package unsafe, which means that even seemingly innocent rule violations can have surprising consequences. An unsafe conversion from |
Okay, I did some debugging of your highlighting code and the problem is you're trying to "copy" over each capture when iterating them. This is my bad on not clarifying this in the docs of the code, but This was a problem upstream too, because in Rust we implemented That being said, the current code without this PR does work, provided that you understand that you have to copy/take out what you want during iteration of a To also add, the go "unsafe" rule is not being violated, the large size is just a trick to say to the go compiler "an array at least this big, but then we set the length and capacity to the exact same size". A thread on it can be found here https://groups.google.com/g/golang-nuts/c/sV_f0VkjZTA |
Memory reuse is certainly a cleaner explanation than unsafe magic. However, it is still a fact that converting to a slice through a large array type violates unsafe rules. The thread you linked is over ten years old; Go has changed since then. This kind of pattern violating unsafe rules was a major motivation for adding unsafe.Slice in the first place. See golang/go#19367, especially around mentions of -d=checkptr (which is notably enabled implicitly when building with -race). |
Oh nice, thanks for that thread - but for some reason I couldn't get any warning/error to come up when running Is it possible we can use |
There's a CL linked from that issue which disabled flagging specifically the cast-through-array pattern on -d=checkptr, but the commit message says it "should be revisited" after unsafe.Slice would be added (and also mentions that the pattern is a violation of unsafe rule 1). I wasn't sure whether that special case had ever been removed, but it must not have been if the code works under -race. Assuming the C and Go types remain layout-compatible (which in particular should imply structs.HostLayout), the correct-est pattern to convert from the former to the latter without copying is to use unsafe.Slice to create the slice of []C.struct_TSQueryCapture (or whatever is the correct type name, I don't know where to find the C in question) and then convert that slice through unsafe.Pointer to []QueryCapture: cc := unsafe.Slice(m.captures, m.capture_count)
captures = *(*[]QueryCapture)(unsafe.Pointer(&cc)) |
Co-authored-by: Branden J Brown <[email protected]>
I went ahead & applied the suggested code from @zephyrtronium & added the suggested documentation. Thank your for patience with this issue and debugging it. |
maybe not exactly pretty, but this then resolves the issue in my code, thanks again for your time gopad-dev/go-tree-sitter-highlight@aa913ce captures := make([]_queryCapture, 0)
queryCaptures := cursor.Captures(config.Query, tree.RootNode(), source)
for {
match, i := queryCaptures.Next()
if match == nil {
break
}
+ match.Captures = slices.Clone(match.Captures)
captures = append(captures, _queryCapture{
Match: *match,
Index: i,
})
} |
No worries, thank you to you both 🙂 I'm no Go expert so it's nice to receive improvements like this. Just a few minor comments and then we can get this in |
Co-authored-by: Amaan Qureshi <[email protected]>
thank you both! |
Thank you too, love the work in this |
I ran into the issue that captures didn't work correctly and apparently this code was breaking unsafe rules. I fixed it with some help of a friend.
I also changed QueryCapture.Index to
uint
which does not make the struct C-compatible anymore but nicer to use since you don't need to do that manually anymore