Permalink
Browse files

Fix crashes when trying to read from output stream (and vice versa)

  • Loading branch information...
benhoyt committed Nov 5, 2018
1 parent 159bb98 commit 59c931fa42e6bd436c64391fd743f6e259beabef
Showing with 33 additions and 25 deletions.
  1. +1 −2 goawk.go
  2. +7 −4 interp/functions.go
  3. +4 −2 interp/interp.go
  4. +5 −4 interp/interp_test.go
  5. +16 −13 interp/io.go
@@ -30,15 +30,14 @@ package main
/*
TODO:
- fix crash with: BEGIN { print |"1"; getline <"1" } # also print >"1"
- performance testing: I/O, allocations, CPU
+ PoC: is interpreting via a "heavy AST" faster?
i.e., with execute functions on the AST elements instead of the switch/case
+ buffer > and >> output (see TODO in getOutputStream)
+ benchmark against awk/gawk with some real awk scripts
- get goawk_test.go working in TravisCI
*/// This comment intentionally left blank
*/ // This comment intentionally left blank

import (
"bytes"
@@ -222,11 +222,14 @@ func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {

case F_CLOSE:
name := p.toString(args[0])
w, ok := p.streams[name]
if !ok {
return num(-1), nil
var c io.Closer = p.inputStreams[name]
if c == nil {
c = p.outputStreams[name]
if c == nil {
return num(-1), nil
}
}
err := w.Close()
err := c.Close()
if err != nil {
return num(-1), nil
}
@@ -71,7 +71,8 @@ type interp struct {
filenameIndex int
hadFiles bool
input io.Reader
streams map[string]io.Closer
inputStreams map[string]io.ReadCloser
outputStreams map[string]io.WriteCloser
commands map[string]*exec.Cmd

// Scalars and arrays
@@ -213,7 +214,8 @@ func ExecProgram(program *Program, config *Config) (int, error) {
p.errorOutput = bufio.NewWriterSize(os.Stderr, stderrBufSize)
p.flushError = true
}
p.streams = make(map[string]io.Closer)
p.inputStreams = make(map[string]io.ReadCloser)
p.outputStreams = make(map[string]io.WriteCloser)
p.commands = make(map[string]*exec.Cmd)
p.scanners = make(map[string]*bufio.Scanner)
defer p.closeAll()
@@ -463,10 +463,11 @@ BEGIN { early() }
"", "", `parse error at 1:56: can't pass array "a" as scalar param`, "array"},
{`{ f(z) } function f(x) { print NR }`, "abc", "1\n", "", ""},

// Redirected I/O
// TODO: these tests currently panic() due to bug with s.(io.Reader) in interp.go
// {`BEGIN { print >"out"; getline <"out" }`, "", "", "can't read from writer stream", ""},
// {`BEGIN { print |"out"; getline <"out" }`, "", "", "", ""},
// Redirected I/O (we give explicit errors, awk and gawk don't)
{`BEGIN { print >"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
{`BEGIN { print |"out"; getline <"out" } # !awk !gawk`, "", "", "can't read from writer stream", ""},
{`BEGIN { getline <"out"; print >"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},
{`BEGIN { getline <"out"; print |"out" } # !awk !gawk`, "", "", "can't write to reader stream", ""},

// Greater than operator requires parentheses in print statement,
// otherwise it's a redirection directive
@@ -39,12 +39,12 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
return nil, err
}
name := p.toString(destValue)
if s, ok := p.streams[name]; ok {
if w, ok := s.(io.Writer); ok {
return w, nil
}
if _, ok := p.inputStreams[name]; ok {
return nil, newError("can't write to reader stream")
}
if w, ok := p.outputStreams[name]; ok {
return w, nil
}

switch redirect {
case GREATER, APPEND:
@@ -60,7 +60,7 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
if err != nil {
return nil, newError("output redirection error: %s", err)
}
p.streams[name] = w
p.outputStreams[name] = w
return w, nil

case PIPE:
@@ -90,7 +90,7 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
io.Copy(p.errorOutput, stderr)
}()
p.commands[name] = cmd
p.streams[name] = w
p.outputStreams[name] = w
return w, nil

default:
@@ -102,20 +102,20 @@ func (p *interp) getOutputStream(redirect Token, dest Expr) (io.Writer, error) {
// Get input Scanner to use for "getline" based on file or pipe name
// TODO: this is basically two different functions switching on isFile -- split?
func (p *interp) getInputScanner(name string, isFile bool) (*bufio.Scanner, error) {
if s, ok := p.streams[name]; ok {
if _, ok := s.(io.Reader); ok {
return p.scanners[name], nil
}
if _, ok := p.outputStreams[name]; ok {
return nil, newError("can't read from writer stream")
}
if _, ok := p.inputStreams[name]; ok {
return p.scanners[name], nil
}
if isFile {
r, err := os.Open(name)
if err != nil {
return nil, newError("input redirection error: %s", err)
}
scanner := p.newScanner(r)
p.scanners[name] = scanner
p.streams[name] = r
p.inputStreams[name] = r
return scanner, nil
} else {
cmd := exec.Command("sh", "-c", name)
@@ -145,7 +145,7 @@ func (p *interp) getInputScanner(name string, isFile bool) (*bufio.Scanner, erro
}()
scanner := p.newScanner(r)
p.commands[name] = cmd
p.streams[name] = r
p.inputStreams[name] = r
p.scanners[name] = scanner
return scanner, nil
}
@@ -377,7 +377,10 @@ func (p *interp) closeAll() {
if prevInput, ok := p.input.(io.Closer); ok {
prevInput.Close()
}
for _, w := range p.streams {
for _, r := range p.inputStreams {
_ = r.Close()
}
for _, w := range p.outputStreams {
_ = w.Close()
}
for _, cmd := range p.commands {

0 comments on commit 59c931f

Please sign in to comment.