Skip to content

Commit fefa41d

Browse files
fix: prevent crash when writing to a closed wrap writer
Closing a WrapWriter returns its ANSI parser to a pool and clears the reference. When nested writer chains tear down out of order, a trailing style or link reset can be flushed back through a writer that was already closed, dereferencing a nil parser and crashing the program. Treat writes after close as a no-op so this teardown ordering is safe rather than fatal.
1 parent 40ec0e6 commit fefa41d

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

‎wrap.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ func (w *WrapWriter) Link() uv.Link {
6363

6464
// Write writes to the buffer.
6565
func (w *WrapWriter) Write(p []byte) (int, error) {
66+
if w.p == nil {
67+
// The writer has been closed and its parser returned to the pool.
68+
// Writing after close can happen during out-of-order teardown of
69+
// nested writer chains; treat it as a no-op rather than panicking.
70+
return len(p), nil
71+
}
6672
for i := range p {
6773
b := p[i]
6874
w.p.Advance(b)

‎wrap_test.go‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package lipgloss
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
// TestWrapWriterWriteAfterClose verifies that writing to a WrapWriter after it
9+
// has been closed is a safe no-op rather than a nil-pointer panic. Close()
10+
// returns the internal ANSI parser to a sync.Pool and nils it out; out-of-order
11+
// teardown of nested writer chains can route a trailing style/link reset back
12+
// through an already-closed writer, so Write must tolerate a nil parser.
13+
func TestWrapWriterWriteAfterClose(t *testing.T) {
14+
var buf bytes.Buffer
15+
w := NewWrapWriter(&buf)
16+
17+
if err := w.Close(); err != nil {
18+
t.Fatalf("close: %v", err)
19+
}
20+
21+
n, err := w.Write([]byte("after close"))
22+
if err != nil {
23+
t.Fatalf("write after close: %v", err)
24+
}
25+
if n != len("after close") {
26+
t.Fatalf("write after close: got n=%d, want %d", n, len("after close"))
27+
}
28+
}

0 commit comments

Comments
 (0)