This is an archive of the discontinued LLVM Phabricator instance.

Fix backend crash on multiple close of stdout.
AbandonedPublic

Authored by ABataev on Sep 24 2015, 5:20 AM.

Details

Reviewers
chandlerc
Summary

If stdout is used as an output file for several outputs (like dep file, output file, etc.) , it causes a crash in llvm::raw_fd_ostream::~raw_fd_ostream(), when destructor tries to close file descriptor, which already is closed in another stream.
Here is the part of the code where it happens:

raw_fd_ostream::~raw_fd_ostream() {
  if (FD >= 0) {
    flush();
    if (ShouldClose && sys::Process::SafelyCloseFileDescriptor(FD))
      error_detected();
  }
...
  if (has_error())
    report_fatal_error("IO failure on output stream.", /*GenCrashDiag=*/false);
}

if stdout is already closed sys::Process::SafelyCloseFileDescriptor() cannot close stdout for the second time and an error flag is set for the stream. It makes the destructor to emit IO failure on output stream. fatal error message.

Diff Detail

Event Timeline

ABataev updated this revision to Diff 35609.Sep 24 2015, 5:20 AM
ABataev retitled this revision from to Fix backend crash on multiple close of stdout..
ABataev updated this object.
ABataev added a reviewer: chandlerc.
ABataev added a subscriber: cfe-commits.

When stdout goes elsewhere the console, the shell creates the the output file (pipe) and will close it when clang terminates so so why clang should close it at all ? it did not open it.

Practically, we have been running locally

    Error(false), UseAtomicWrites(false) {
if (FD < 0 ) {
  ShouldClose = false;
  return;
}
if (FD <= STDERR_FILENO)
  ShouldClose = false;

and passing regression tests on Windows 7 and Linux, maybe this is required on other usage scenarios or OS.

When stdout goes elsewhere the console, the shell creates the the output file (pipe) and will close it when clang terminates so so why clang should close it at all ? it did not open it.

Practically, we have been running locally

    Error(false), UseAtomicWrites(false) {
if (FD < 0 ) {
  ShouldClose = false;
  return;
}
if (FD <= STDERR_FILENO)
  ShouldClose = false;

and passing regression tests on Windows 7 and Linux, maybe this is required on other usage scenarios or OS.

I thought about it. The problem is that few lines above there is a comment (in getFD()):

// Handle "-" as stdout. Note that when we do this, we consider ourself
// the owner of stdout. This means that we can do things like close the
// file descriptor when we're done and set the "binary" flag globally.

and all such stream are created with ShouldClose flag explicitly set to true. Should we remove all this stuff?

The original commit http://lists.llvm.org/pipermail/llvm-commits/Week-of-Mon-20100816/106268.html by Dan Ghoman says:

"Make raw_fd_ostream consider itself the owner of STDOUT_FILENO when
constructed with an output filename of "-". In particular, allow the
file descriptor to be closed, and close the file descriptor in the
destructor if it hasn't been explicitly closed already, to ensure
that any write errors are detected."

Closing stdout causes not only this problem in the 2nd close (which may be worked around) but potentially losing text output depending on who gets to close the stream first. Is it normal behaviour for console programs to close their stdout?

ABataev updated this revision to Diff 35698.Sep 24 2015, 10:17 PM
ABataev updated this object.

Reworked patch after some discussion

This code has been questioned in this way before, and every time it comes up, it turns out that there's a bug somewhere else that really ought to be fixed anyway.

In previous iterations of this discussion, when clang is found attempting to print something like a dep file to the same stream as the normal compiler output file, this is not actually desirable behavior because it mixes two different kinds of output. Besides the fact that this typically isn't what a user actually wants, it can cause serious problems if one of the stream needs to use "binary" mode and the other doesn't, for example.

The resolution has been to have clang issue an error if the dep file and the output file are both using stdout, which is nice to do, because if a user is asking for this it's likely not intentional. With this done, there is no longer a way to crash the compiler, and there's no need to change how raw_fd_ostream works.

Hi Dan, it makes sense that output streams should not usually be mixed together, especially if one is binary as you write.
This may or may not be a problem depending on what the user really wants. He may want to mix the outputs for whatever purposes or it may usually be a user error.
In any case, that's not how clang deal with usage or even internal unexpected errors. It asserts, print error messages but does not crash on purpose. Having clang crash here does not seem like a good solution and will result in this issue continue to surface again.

The bad thing is that the same problem can be reproduced not only in frontend, but also in opt tool. The test I wrote (test/Other/empty.ll) also failed with the same message.
And Yaron is right, I need to mix several outputs in stdout. What should I do in this case?

In any case, that's not how clang deal with usage or even internal unexpected errors. It asserts, print error messages but does not crash on purpose. Having clang crash here does not seem like a good solution and will result in this issue continue to surface again.

When clang has bugs, it is common for it to crash unceremoniously. That said, if you know of a reasonable way to assert here before the crash, that'd be great.

The bad thing is that the same problem can be reproduced not only in frontend, but also in opt tool. The test I wrote (test/Other/empty.ll) also failed with the same message.

This could also be interpreted as opt failing to check for incompatible command-line options. One of the command-line options in this case is a hidden option, so this isn't especially surprising.

And Yaron is right, I need to mix several outputs in stdout. What should I do in this case?

What's the context? It may be worth investigating whether there are other ways to address your need.

If you want to use -rewrite-map-file and -info-output-file in a testcase at the same time, see the documentation for '%t' in the TestingGuide for creating multiple temporary output files.

ABataev abandoned this revision.Sep 29 2015, 2:50 AM