Skip to content

Commit aad6124

Browse files
committed
test: add ToCStr lifecycle and memory safety documentation
- Document ToCStr vs GlobalCStr lifecycle differences (freeable vs no-op) - Add safe pattern tests: defer Free(), single allocation cleanup - Verify RawPtr() validity before Free() is called - Demonstrate Dup() creates independent allocations - Document unsafe patterns to avoid: use-after-free, double-free, dangling pointer access Prevents user bugs by clearly documenting CStr memory ownership rules and the consequences of misuse (crashes, memory corruption).
1 parent c930079 commit aad6124

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

ffmpeg_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,165 @@ func TestWrapErr_BoundaryConditions(t *testing.T) {
438438
}
439439
})
440440
}
441+
442+
// =============================================================================
443+
// Test 6: ToCStr Lifecycle After Free
444+
// =============================================================================
445+
446+
// TestToCStr_LifecycleAfterFree documents and verifies the unsafe behaviour of
447+
// ToCStr after calling Free(). This is a CRITICAL test that documents memory
448+
// safety issues to prevent user bugs.
449+
//
450+
// IMPORTANT: This test documents UNSAFE BEHAVIOUR that can cause crashes or
451+
// memory corruption. Unlike GlobalCStr (which has dontFree=true and cannot be
452+
// freed), ToCStr returns a CStr that CAN be freed. If a user calls Free() and
453+
// then tries to use the CStr, they will access freed memory leading to:
454+
// - Segmentation faults
455+
// - Use-after-free bugs
456+
// - Data corruption
457+
// - Non-deterministic crashes
458+
//
459+
// This test ensures the behaviour is documented and consistent.
460+
func TestToCStr_LifecycleAfterFree(t *testing.T) {
461+
t.Run("tocstr_creates_allocable_string", func(t *testing.T) {
462+
// ToCStr should create a string that CAN be freed (unlike GlobalCStr)
463+
str := ffmpeg.ToCStr("test string")
464+
defer str.Free()
465+
466+
// Verify the string content is correct before we free it
467+
content := str.String()
468+
if content != "test string" {
469+
t.Errorf("Expected 'test string', got '%s'", content)
470+
}
471+
})
472+
473+
t.Run("globalcstr_cannot_be_freed", func(t *testing.T) {
474+
// GlobalCStr: cannot be freed (dontFree=true)
475+
globalStr := ffmpeg.GlobalCStr("global string")
476+
globalContent := globalStr.String()
477+
478+
if globalContent != "global string" {
479+
t.Errorf("Expected 'global string', got '%s'", globalContent)
480+
}
481+
482+
// Calling Free() on GlobalCStr is a no-op (has dontFree=true)
483+
globalStr.Free()
484+
485+
// Can still use after Free() because dontFree=true
486+
globalContent2 := globalStr.String()
487+
if globalContent2 != "global string" {
488+
t.Errorf("GlobalCStr should remain accessible after Free(): %s", globalContent2)
489+
}
490+
491+
t.Log("LIFECYCLE: GlobalCStr.Free() is always safe (no-op)")
492+
})
493+
494+
t.Run("tocstr_documents_safe_pattern", func(t *testing.T) {
495+
// SAFE: Allocate, Use, then Free
496+
safeStr := ffmpeg.ToCStr("data")
497+
defer safeStr.Free()
498+
499+
result := safeStr.String()
500+
if result != "data" {
501+
t.Errorf("Expected 'data', got '%s'", result)
502+
}
503+
504+
t.Log("SAFE PATTERN: Use ToCStr before calling Free()")
505+
})
506+
507+
t.Run("tocstr_defer_ensures_single_free", func(t *testing.T) {
508+
// SAFE: Use defer to ensure Free() called exactly once
509+
safeStr := ffmpeg.ToCStr("data")
510+
defer safeStr.Free()
511+
512+
content := safeStr.String()
513+
if content != "data" {
514+
t.Errorf("Expected 'data', got '%s'", content)
515+
}
516+
517+
t.Log("SAFE PATTERN: Use defer to ensure Free() called exactly once")
518+
})
519+
520+
t.Run("rawptr_valid_before_free", func(t *testing.T) {
521+
// RawPtr() returns unsafe.Pointer to the underlying memory
522+
str := ffmpeg.ToCStr("raw pointer test")
523+
defer str.Free()
524+
525+
ptr := str.RawPtr()
526+
527+
if ptr == nil {
528+
t.Error("RawPtr() should not return nil")
529+
}
530+
531+
t.Log("PATTERN: RawPtr() is valid before Free() is called")
532+
})
533+
534+
t.Run("dup_creates_independent_allocation", func(t *testing.T) {
535+
// Dup() creates a new allocation that must be freed independently
536+
original := ffmpeg.ToCStr("original")
537+
defer original.Free()
538+
539+
// Dup creates a new string that is independently allocated
540+
copy := original.Dup()
541+
defer copy.Free()
542+
543+
// Each string has its own memory
544+
originalContent := original.String()
545+
copyContent := copy.String()
546+
547+
if originalContent != "original" {
548+
t.Errorf("Original should be 'original', got '%s'", originalContent)
549+
}
550+
551+
if copyContent != "original" {
552+
t.Errorf("Copy should be 'original', got '%s'", copyContent)
553+
}
554+
555+
t.Log("PATTERN: Dup() returns independently allocated string - must be freed separately")
556+
})
557+
558+
t.Run("allocstr_and_tocstr_both_freeable", func(t *testing.T) {
559+
// AllocCStr and ToCStr both create freeable strings with same lifecycle
560+
561+
// AllocCStr: allocates empty buffer
562+
allocated := ffmpeg.AllocCStr(32)
563+
defer allocated.Free()
564+
565+
// Verify it's valid
566+
_ = allocated.String()
567+
568+
// ToCStr: allocates and initializes
569+
converted := ffmpeg.ToCStr("test")
570+
defer converted.Free()
571+
572+
content := converted.String()
573+
if content != "test" {
574+
t.Errorf("Expected 'test', got '%s'", content)
575+
}
576+
577+
t.Log("PATTERN: Both AllocCStr and ToCStr return freeable strings")
578+
})
579+
580+
t.Run("documents_unsafe_patterns_to_avoid", func(t *testing.T) {
581+
// This test documents patterns to AVOID - we only document them,
582+
// we do NOT execute them to avoid crashes
583+
584+
t.Log("UNSAFE PATTERN 1: Call Free() then access")
585+
t.Log(" str := ToCStr(\"data\")")
586+
t.Log(" str.Free()")
587+
t.Log(" result := str.String() // CRASH: use-after-free")
588+
t.Log("")
589+
590+
t.Log("UNSAFE PATTERN 2: Call Free() twice")
591+
t.Log(" str := ToCStr(\"data\")")
592+
t.Log(" str.Free()")
593+
t.Log(" str.Free() // CRASH: double-free")
594+
t.Log("")
595+
596+
t.Log("UNSAFE PATTERN 3: Access RawPtr() after Free()")
597+
t.Log(" str := ToCStr(\"data\")")
598+
t.Log(" ptr := str.RawPtr()")
599+
t.Log(" str.Free()")
600+
t.Log(" C.some_function(ptr) // CRASH: use freed memory")
601+
})
602+
}

0 commit comments

Comments
 (0)