| //===- ErrorHandler.cpp ---------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lld/Common/ErrorHandler.h" | |
| #include "lld/Common/CommonLinkerContext.h" | |
| #include "llvm/ADT/Twine.h" | |
| #include "llvm/IR/DiagnosticInfo.h" | |
| #include "llvm/IR/DiagnosticPrinter.h" | |
| #include "llvm/Support/CrashRecoveryContext.h" | |
| #include "llvm/Support/ManagedStatic.h" | |
| #include "llvm/Support/Process.h" | |
| #include "llvm/Support/Program.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| #include <regex> | |
| using namespace llvm; | |
| using namespace lld; | |
| static StringRef getSeparator(const Twine &msg) { | |
| if (StringRef(msg.str()).contains('\n')) | |
| return "\n"; | |
| return ""; | |
| } | |
| ErrorHandler::~ErrorHandler() { | |
| if (cleanupCallback) | |
| cleanupCallback(); | |
| } | |
| void ErrorHandler::initialize(llvm::raw_ostream &stdoutOS, | |
| llvm::raw_ostream &stderrOS, bool exitEarly, | |
| bool disableOutput) { | |
| this->stdoutOS = &stdoutOS; | |
| this->stderrOS = &stderrOS; | |
| stderrOS.enable_colors(stderrOS.has_colors()); | |
| this->exitEarly = exitEarly; | |
| this->disableOutput = disableOutput; | |
| } | |
| void ErrorHandler::flushStreams() { | |
| std::lock_guard<std::mutex> lock(mu); | |
| outs().flush(); | |
| errs().flush(); | |
| } | |
| ErrorHandler &lld::errorHandler() { return context().e; } | |
| void lld::error(const Twine &msg) { errorHandler().error(msg); } | |
| void lld::error(const Twine &msg, ErrorTag tag, ArrayRef<StringRef> args) { | |
| errorHandler().error(msg, tag, args); | |
| } | |
| void lld::fatal(const Twine &msg) { errorHandler().fatal(msg); } | |
| void lld::log(const Twine &msg) { errorHandler().log(msg); } | |
| void lld::message(const Twine &msg, llvm::raw_ostream &s) { | |
| errorHandler().message(msg, s); | |
| } | |
| void lld::warn(const Twine &msg) { errorHandler().warn(msg); } | |
| uint64_t lld::errorCount() { return errorHandler().errorCount; } | |
| raw_ostream &lld::outs() { | |
| ErrorHandler &e = errorHandler(); | |
| return e.outs(); | |
| } | |
| raw_ostream &ErrorHandler::outs() { | |
| if (disableOutput) | |
| return llvm::nulls(); | |
| return stdoutOS ? *stdoutOS : llvm::outs(); | |
| } | |
| raw_ostream &ErrorHandler::errs() { | |
| if (disableOutput) | |
| return llvm::nulls(); | |
| return stderrOS ? *stderrOS : llvm::errs(); | |
| } | |
| void lld::exitLld(int val) { | |
| if (hasContext()) { | |
| ErrorHandler &e = errorHandler(); | |
| // Delete any temporary file, while keeping the memory mapping open. | |
| if (e.outputBuffer) | |
| e.outputBuffer->discard(); | |
| } | |
| // Re-throw a possible signal or exception once/if it was caught by | |
| // safeLldMain(). | |
| CrashRecoveryContext::throwIfCrash(val); | |
| // Dealloc/destroy ManagedStatic variables before calling _exit(). | |
| // In an LTO build, allows us to get the output of -time-passes. | |
| // Ensures that the thread pool for the parallel algorithms is stopped to | |
| // avoid intermittent crashes on Windows when exiting. | |
| if (!CrashRecoveryContext::GetCurrent()) | |
| llvm_shutdown(); | |
| if (hasContext()) | |
| lld::errorHandler().flushStreams(); | |
| // When running inside safeLldMain(), restore the control flow back to the | |
| // CrashRecoveryContext. Otherwise simply use _exit(), meanning no cleanup, | |
| // since we want to avoid further crashes on shutdown. | |
| llvm::sys::Process::Exit(val, /*NoCleanup=*/true); | |
| } | |
| void lld::diagnosticHandler(const DiagnosticInfo &di) { | |
| SmallString<128> s; | |
| raw_svector_ostream os(s); | |
| DiagnosticPrinterRawOStream dp(os); | |
| // For an inline asm diagnostic, prepend the module name to get something like | |
| // "$module <inline asm>:1:5: ". | |
| if (auto *dism = dyn_cast<DiagnosticInfoSrcMgr>(&di)) | |
| if (dism->isInlineAsmDiag()) | |
| os << dism->getModuleName() << ' '; | |
| di.print(dp); | |
| switch (di.getSeverity()) { | |
| case DS_Error: | |
| error(s); | |
| break; | |
| case DS_Warning: | |
| warn(s); | |
| break; | |
| case DS_Remark: | |
| case DS_Note: | |
| message(s); | |
| break; | |
| } | |
| } | |
| void lld::checkError(Error e) { | |
| handleAllErrors(std::move(e), | |
| [&](ErrorInfoBase &eib) { error(eib.message()); }); | |
| } | |
| void lld::checkError(ErrorHandler &eh, Error e) { | |
| handleAllErrors(std::move(e), | |
| [&](ErrorInfoBase &eib) { eh.error(eib.message()); }); | |
| } | |
| // This is for --vs-diagnostics. | |
| // | |
| // Normally, lld's error message starts with argv[0]. Therefore, it usually | |
| // looks like this: | |
| // | |
| // ld.lld: error: ... | |
| // | |
| // This error message style is unfortunately unfriendly to Visual Studio | |
| // IDE. VS interprets the first word of the first line as an error location | |
| // and make it clickable, thus "ld.lld" in the above message would become a | |
| // clickable text. When you click it, VS opens "ld.lld" executable file with | |
| // a binary editor. | |
| // | |
| // As a workaround, we print out an error location instead of "ld.lld" if | |
| // lld is running in VS diagnostics mode. As a result, error message will | |
| // look like this: | |
| // | |
| // src/foo.c(35): error: ... | |
| // | |
| // This function returns an error location string. An error location is | |
| // extracted from an error message using regexps. | |
| std::string ErrorHandler::getLocation(const Twine &msg) { | |
| if (!vsDiagnostics) | |
| return std::string(logName); | |
| static std::regex regexes[] = { | |
| std::regex( | |
| R"(^undefined (?:\S+ )?symbol:.*\n)" | |
| R"(>>> referenced by .+\((\S+):(\d+)\))"), | |
| std::regex( | |
| R"(^undefined (?:\S+ )?symbol:.*\n>>> referenced by (\S+):(\d+))"), | |
| std::regex(R"(^undefined symbol:.*\n>>> referenced by (.*):)"), | |
| std::regex( | |
| R"(^duplicate symbol: .*\n>>> defined in (\S+)\n>>> defined in.*)"), | |
| std::regex( | |
| R"(^duplicate symbol: .*\n>>> defined at .+\((\S+):(\d+)\))"), | |
| std::regex(R"(^duplicate symbol: .*\n>>> defined at (\S+):(\d+))"), | |
| std::regex( | |
| R"(.*\n>>> defined in .*\n>>> referenced by .+\((\S+):(\d+)\))"), | |
| std::regex(R"(.*\n>>> defined in .*\n>>> referenced by (\S+):(\d+))"), | |
| std::regex(R"((\S+):(\d+): unclosed quote)"), | |
| }; | |
| std::string str = msg.str(); | |
| for (std::regex &re : regexes) { | |
| std::smatch m; | |
| if (!std::regex_search(str, m, re)) | |
| continue; | |
| assert(m.size() == 2 || m.size() == 3); | |
| if (m.size() == 2) | |
| return m.str(1); | |
| return m.str(1) + "(" + m.str(2) + ")"; | |
| } | |
| return std::string(logName); | |
| } | |
| void ErrorHandler::reportDiagnostic(StringRef location, Colors c, | |
| StringRef diagKind, const Twine &msg) { | |
| SmallString<256> buf; | |
| raw_svector_ostream os(buf); | |
| os << sep << location << ": "; | |
| if (!diagKind.empty()) { | |
| if (errs().colors_enabled()) { | |
| os.enable_colors(true); | |
| os << c << diagKind << ": " << Colors::RESET; | |
| } else { | |
| os << diagKind << ": "; | |
| } | |
| } | |
| os << msg << '\n'; | |
| errs() << buf; | |
| // If msg contains a newline, ensure that the next diagnostic is preceded by | |
| // a blank line separator. | |
| sep = getSeparator(msg); | |
| } | |
| void ErrorHandler::log(const Twine &msg) { | |
| if (!verbose || disableOutput) | |
| return; | |
| std::lock_guard<std::mutex> lock(mu); | |
| reportDiagnostic(logName, Colors::RESET, "", msg); | |
| } | |
| void ErrorHandler::message(const Twine &msg, llvm::raw_ostream &s) { | |
| if (disableOutput) | |
| return; | |
| std::lock_guard<std::mutex> lock(mu); | |
| s << msg << "\n"; | |
| s.flush(); | |
| } | |
| void ErrorHandler::warn(const Twine &msg) { | |
| if (fatalWarnings) { | |
| error(msg); | |
| return; | |
| } | |
| if (suppressWarnings) | |
| return; | |
| std::lock_guard<std::mutex> lock(mu); | |
| reportDiagnostic(getLocation(msg), Colors::MAGENTA, "warning", msg); | |
| } | |
| void ErrorHandler::error(const Twine &msg) { | |
| // If Visual Studio-style error message mode is enabled, | |
| // this particular error is printed out as two errors. | |
| if (vsDiagnostics) { | |
| static std::regex re(R"(^(duplicate symbol: .*))" | |
| R"((\n>>> defined at \S+:\d+.*\n>>>.*))" | |
| R"((\n>>> defined at \S+:\d+.*\n>>>.*))"); | |
| std::string str = msg.str(); | |
| std::smatch m; | |
| if (std::regex_match(str, m, re)) { | |
| error(m.str(1) + m.str(2)); | |
| error(m.str(1) + m.str(3)); | |
| return; | |
| } | |
| } | |
| bool exit = false; | |
| { | |
| std::lock_guard<std::mutex> lock(mu); | |
| if (errorLimit == 0 || errorCount < errorLimit) { | |
| reportDiagnostic(getLocation(msg), Colors::RED, "error", msg); | |
| } else if (errorCount == errorLimit) { | |
| reportDiagnostic(logName, Colors::RED, "error", errorLimitExceededMsg); | |
| exit = exitEarly; | |
| } | |
| ++errorCount; | |
| } | |
| if (exit) | |
| exitLld(1); | |
| } | |
| void ErrorHandler::error(const Twine &msg, ErrorTag tag, | |
| ArrayRef<StringRef> args) { | |
| if (errorHandlingScript.empty() || disableOutput) { | |
| error(msg); | |
| return; | |
| } | |
| SmallVector<StringRef, 4> scriptArgs; | |
| scriptArgs.push_back(errorHandlingScript); | |
| switch (tag) { | |
| case ErrorTag::LibNotFound: | |
| scriptArgs.push_back("missing-lib"); | |
| break; | |
| case ErrorTag::SymbolNotFound: | |
| scriptArgs.push_back("undefined-symbol"); | |
| break; | |
| } | |
| scriptArgs.insert(scriptArgs.end(), args.begin(), args.end()); | |
| int res = llvm::sys::ExecuteAndWait(errorHandlingScript, scriptArgs); | |
| if (res == 0) { | |
| return error(msg); | |
| } else { | |
| // Temporarily disable error limit to make sure the two calls to error(...) | |
| // only count as one. | |
| uint64_t currentErrorLimit = errorLimit; | |
| errorLimit = 0; | |
| error(msg); | |
| errorLimit = currentErrorLimit; | |
| --errorCount; | |
| switch (res) { | |
| case -1: | |
| error("error handling script '" + errorHandlingScript + | |
| "' failed to execute"); | |
| break; | |
| case -2: | |
| error("error handling script '" + errorHandlingScript + | |
| "' crashed or timeout"); | |
| break; | |
| default: | |
| error("error handling script '" + errorHandlingScript + | |
| "' exited with code " + Twine(res)); | |
| } | |
| } | |
| } | |
| void ErrorHandler::fatal(const Twine &msg) { | |
| error(msg); | |
| exitLld(1); | |
| } | |
| SyncStream::~SyncStream() { | |
| switch (level) { | |
| case DiagLevel::None: | |
| break; | |
| case DiagLevel::Log: | |
| e.log(buf); | |
| break; | |
| case DiagLevel::Msg: | |
| e.message(buf, e.outs()); | |
| break; | |
| case DiagLevel::Warn: | |
| e.warn(buf); | |
| break; | |
| case DiagLevel::Err: | |
| e.error(buf); | |
| break; | |
| case DiagLevel::Fatal: | |
| e.fatal(buf); | |
| break; | |
| } | |
| } |