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}