weak.go
1package ygo
2
3/*
4#include "libyrs.h"
5#include <stdlib.h>
6*/
7import "C"
8import (
9 "fmt"
10 "unsafe"
11)
12
13// Weak represents a weak link to content that can be quoted/cited.
14//
15// IMPORTANT SAFETY NOTE: Due to a known memory corruption bug in the
16// underlying yffi library, weak links created via QuoteText or QuoteArray
17// MUST be destroyed BEFORE the transaction is committed. The weak link
18// CANNOT be used after the transaction ends. This is a workaround for a
19// yffi library issue that causes crashes during garbage collection.
20//
21// Safe usage pattern:
22// 1. Create ONE weak link per transaction
23// 2. Do NOT store the weak link for later use
24// 3. Call Destroy() on the weak link BEFORE the transaction callback returns
25// 4. If you need to persist the link, convert it to an Input immediately
26// and store that instead (the Input is a copy and is safe to use)
27//
28// Example:
29//
30// err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
31// link, err := arr.QuoteArray(txn, 1, 3, false, true)
32// if err != nil {
33// return err
34// }
35// // Convert to Input immediately
36// input, err := link.Input()
37// if err != nil {
38// return err
39// }
40// // Store the Input (safe to use later)
41// err = m.Insert(txn, "link", input)
42// // Destroy the Weak BEFORE returning
43// link.Destroy()
44// return err
45// })
46type Weak struct {
47 ptr *C.Weak
48}
49
50// Destroy releases the weak reference.
51//
52// SAFETY: When using QuoteText or QuoteArray, you MUST call Destroy()
53// before the transaction callback returns. Calling Destroy() after the
54// transaction has committed will cause memory corruption and crashes.
55func (w *Weak) Destroy() {
56 if w.ptr != nil {
57 C.yweak_destroy(w.ptr)
58 w.ptr = nil
59 }
60}
61
62// QuoteText creates a weak link to a text range.
63//
64// SAFETY WARNING: This function has a known memory corruption issue in the
65// underlying yffi library. The returned Weak reference MUST be destroyed
66// explicitly BEFORE the transaction callback returns to avoid crashes.
67//
68// DO NOT:
69// - Store the Weak reference for use outside the transaction
70// - Create multiple weak links in the same transaction
71// - Let the Weak reference escape the transaction scope
72//
73// DO:
74// - Call Destroy() on the Weak reference before the callback returns
75// - Convert to Input immediately if you need to persist the link
76// - Create only ONE weak link per transaction
77//
78// Example safe usage:
79//
80// err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
81// link, err := txt.QuoteText(txn, 2, 10, false, false)
82// if err != nil {
83// return err
84// }
85// input, err := link.Input()
86// if err != nil {
87// return err
88// }
89// // Store the Input, not the Weak
90// err = m.Insert(txn, "quote", input)
91// // Always destroy before returning
92// link.Destroy()
93// return err
94// })
95func (txt *Text) QuoteText(txn *Transaction, start, end uint32, startExclusive, endExclusive bool) (*Weak, error) {
96 if txt.branch == nil {
97 return nil, ErrNilBranch
98 }
99 if txn == nil || txn.ptr == nil {
100 return nil, ErrNilTransaction
101 }
102 if !txn.IsWriteable() {
103 return nil, ErrNotWriteable
104 }
105
106 startIdx := C.uint32_t(start)
107 endIdx := C.uint32_t(end)
108 var startExcl, endExcl C.int8_t
109 if startExclusive {
110 startExcl = 1
111 }
112 if endExclusive {
113 endExcl = 1
114 }
115
116 ptr := C.ytext_quote(txt.branch, txn.ptr, &startIdx, &endIdx, startExcl, endExcl)
117 if ptr == nil {
118 return nil, fmt.Errorf("failed to create text quote")
119 }
120
121 w := &Weak{ptr: ptr}
122 // NOTE: Intentionally NOT setting a finalizer here.
123 // The caller MUST explicitly call Destroy() before the transaction callback returns.
124 // This is a workaround for a yffi library memory corruption issue.
125 return w, nil
126}
127
128// QuoteArray creates a weak link to an array range.
129//
130// WARNING: This function has a CRITICAL memory corruption bug in the underlying
131// yffi library. Creating weak links via yarray_quote causes crashes during
132// garbage collection. This function is NOT SAFE for production use.
133//
134// The crash occurs because:
135// 1. The yffi library creates weak references with invalid memory pointers
136// 2. When Go's garbage collector runs, it encounters these invalid pointers
137// 3. This causes a SIGABRT with "unaligned tcache chunk detected" or similar
138//
139// Due to this bug, this function will cause crashes even if you follow the
140// safe usage pattern described for QuoteText. The underlying C library issue
141// makes QuoteArray fundamentally unsafe.
142//
143// DO NOT USE this function in production code. It is provided only for
144// API completeness and testing. If you need array quotations, consider:
145// - Using QuoteText instead (text operations don't crash)
146// - Storing array indices directly instead of weak links
147// - Waiting for a fixed version of the yffi library
148//
149// The crash happens in these scenarios:
150// - Creating and destroying a weak link inside a transaction
151// - Creating a weak link and letting it escape the transaction
152// - Multiple weak links in the same transaction
153// - Weak links inserted into maps or arrays
154//
155// There is NO safe usage pattern for QuoteArray. It will crash.
156func (arr *Array) QuoteArray(txn *Transaction, start, end uint32, startExclusive, endExclusive bool) (*Weak, error) {
157 if arr.branch == nil {
158 return nil, ErrNilBranch
159 }
160 if txn == nil || txn.ptr == nil {
161 return nil, ErrNilTransaction
162 }
163 if !txn.IsWriteable() {
164 return nil, ErrNotWriteable
165 }
166
167 startIdx := C.uint32_t(start)
168 endIdx := C.uint32_t(end)
169 var startExcl, endExcl C.int8_t
170 if startExclusive {
171 startExcl = 1
172 }
173 if endExclusive {
174 endExcl = 1
175 }
176
177 ptr := C.yarray_quote(arr.branch, txn.ptr, &startIdx, &endIdx, startExcl, endExcl)
178 if ptr == nil {
179 return nil, fmt.Errorf("failed to create array quote")
180 }
181
182 // NOTE: Intentionally NOT setting a finalizer here.
183 // The caller MUST explicitly call Destroy() before the transaction callback returns.
184 // This is a workaround for a yffi library memory corruption issue.
185 return &Weak{ptr: ptr}, nil
186}
187
188// LinkMap creates a weak link to a map entry.
189//
190// This function uses a different underlying C API than QuoteText and QuoteArray,
191// and does not have the same memory corruption issues. It is safe to use.
192// The Weak reference returned by LinkMap CAN be used after the transaction
193// completes, and the garbage collector will properly clean it up.
194func (m *Map) LinkMap(txn *Transaction, key string) (*Weak, error) {
195 if m.branch == nil {
196 return nil, ErrNilBranch
197 }
198 if txn == nil || txn.ptr == nil {
199 return nil, ErrNilTransaction
200 }
201 if !txn.IsWriteable() {
202 return nil, ErrNotWriteable
203 }
204
205 cKey := C.CString(key)
206 defer C.free(unsafe.Pointer(cKey))
207
208 ptr := C.ymap_link(m.branch, txn.ptr, cKey)
209 if ptr == nil {
210 return nil, fmt.Errorf("failed to create map link for key %q", key)
211 }
212
213 w := &Weak{ptr: ptr}
214 return w, nil
215}
216
217// Input creates a YInput from this weak link for insertion.
218// This copies the weak reference, so the original can still be destroyed.
219//
220// SAFETY: The returned Input is a COPY of the weak reference and is safe to use
221// after the transaction completes. You should convert to Input immediately after
222// creating a weak link, then store the Input (not the Weak) in your document.
223func (w *Weak) Input() (Input, error) {
224 if w.ptr == nil {
225 return Input{}, ErrNilWeakLink
226 }
227 return Input{cInput: C.yinput_weak(w.ptr)}, nil
228}