diff --git a/docs/RULES.md b/docs/RULES.md index 67d1b919..82b1184e 100644 --- a/docs/RULES.md +++ b/docs/RULES.md @@ -126,6 +126,9 @@ only the filesystem-accessing *functions* are forbidden. - Commands MUST NOT exhaust file descriptors or other system resources - Commands MUST handle interruption (context cancellation) gracefully +### Goroutine Context Propagation +- Goroutines spawned during command or interpreter execution MUST propagate and respect context cancellation. Before any blocking I/O in a goroutine, check `ctx.Err()`. For multi-chunk writes, check `ctx.Err()` between chunks. Close the pipe write end (`pw.Close()`) on cancellation to unblock the reader and propagate termination. + ### Integer Safety - Commands MUST check for integer overflow in all arithmetic operations - Commands MUST validate numeric conversions (string to int) and handle errors diff --git a/interp/runner_redir.go b/interp/runner_redir.go index 16bd8776..28cb0568 100644 --- a/interp/runner_redir.go +++ b/interp/runner_redir.go @@ -69,7 +69,7 @@ func hdocLiteralPart(buf *strings.Builder, part syntax.WordPart) { } } -func (r *Runner) hdocReader(rd *syntax.Redirect) (*os.File, error) { +func (r *Runner) hdocReader(ctx context.Context, rd *syntax.Redirect) (*os.File, error) { pr, pw, err := os.Pipe() if err != nil { return nil, err @@ -94,8 +94,22 @@ func (r *Runner) hdocReader(rd *syntax.Redirect) (*os.File, error) { return nil, fmt.Errorf("heredoc: content exceeds maximum size (%d bytes)", MaxHeredocBytes) } go func() { - pw.WriteString(hdoc) - pw.Close() + defer pw.Close() + const chunkSize = 32 * 1024 + data := []byte(hdoc) + for len(data) > 0 { + if ctx.Err() != nil { + return + } + n := chunkSize + if n > len(data) { + n = len(data) + } + if _, err := pw.Write(data[:n]); err != nil { + return + } + data = data[n:] + } }() return pr, nil } @@ -144,15 +158,29 @@ func (r *Runner) hdocReader(rd *syntax.Redirect) (*os.File, error) { return nil, hdocErr } go func() { - pw.Write(buf.Bytes()) - pw.Close() + defer pw.Close() + const chunkSize = 32 * 1024 + data := buf.Bytes() + for len(data) > 0 { + if ctx.Err() != nil { + return + } + n := chunkSize + if n > len(data) { + n = len(data) + } + if _, err := pw.Write(data[:n]); err != nil { + return + } + data = data[n:] + } }() return pr, nil } func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, error) { if rd.Hdoc != nil { - pr, err := r.hdocReader(rd) + pr, err := r.hdocReader(ctx, rd) if err != nil { return nil, err }