Version: ooRexx 5.3.0 r13237, 64-bit, Linux
When the ERROR target of an ADDRESS ... WITH instruction is the same as the INPUT source, the error target is not buffered: it is initialised (emptied / truncated) before the input is read. Depending on the case this gives wrong results, a crash or a hang.
Case 1: wrong result
s.0 = 2; s.1 = "one"; s.2 = "two"
address system "cat; echo err >&2" with input stem s. output stem o. error stem s.
say "rc="rc "o.0="o.0 "o.1="o.1 "o.2="o.2
say "s.0="s.0 "s.1="s.1
Expected: rc=0 o.0=2 o.1=one o.2=two and s.0=1 s.1=err
Actual: rc=0 o.0=2 o.1=S. o.2=S. and s.0=1 s.1=err
The ERROR target empties s. before the input is read, so cat receives the stem's default value (its name) instead of the data.
Case 2: segmentation fault
s.0 = 2; s.1 = "one"; s.2 = "two"
address system "cat; echo err >&2" with input stem s. error stem s.
say "rc="rc "s.0="s.0 "s.1="s.1
Actual: the interpreter crashes with SIGSEGV (exit code 139) and prints nothing.
Case 3: hang
/* f.txt contains "one\ntwo\n" */
address system "cat; echo err >&2" with input stream "f.txt" output stem o. error stream "f.txt"
say "rc="rc "o.0="o.0 "o.1="o.1
Actual: hangs. The ERROR target opens f.txt WRITE REPLACE, which truncates it before the input is read. The input is then empty, which triggers bug #NNN (the stdin pipe is never closed when the input is empty).
Control: the same conflict between OUTPUT and INPUT is handled correctly:
s.0 = 2; s.1 = "one"; s.2 = "two"
address system "cat; echo err >&2" with input stem s. output stem s.
gives s.0=2 s.1=one s.2=two, as expected.
Cause
interpreter/instructions/CommandIOContext.cpp, CommandIOContext::resolveConflicts():
// now we still could have a conflict beteen the input ane error, so check that too
else if (error != OREF_NULL && error->needsBuffering(input))
{
// make this buffered until the command returns
output = new BufferingOutputTarget(output);
}
The branch that detects the INPUT/ERROR conflict wraps output instead of error. As a result:
error is not buffered, so its init() empties the stem, or truncates the file, before the input is read (cases 1 and 3);BufferingOutputTarget(NULL) is created, and its cleanup() calls target->init() on a NULL pointer (case 2).Suggested fix
else if (error != OREF_NULL && error->needsBuffering(input))
{
error = new BufferingOutputTarget(error);
}
This branch is only reached when OUTPUT and ERROR are not the same target (if they were, the previous branch would already have buffered both), so wrapping error alone is enough.
Anonymous
Committed the suggested code fix and added a simple test with revision [r13262]
Related
Commit: [r13262]