diff --git a/clang/include/clang/Frontend/FrontendAction.h b/clang/include/clang/Frontend/FrontendAction.h --- a/clang/include/clang/Frontend/FrontendAction.h +++ b/clang/include/clang/Frontend/FrontendAction.h @@ -44,6 +44,9 @@ std::unique_ptr CreateWrappedASTConsumer(CompilerInstance &CI, StringRef InFile); + bool BeginSourceFileImpl(CompilerInstance &CI, const FrontendInputFile &Input, + bool &HasBegunSourceFile); + protected: /// @name Implementation Action Interface /// @{ diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -27,8 +27,8 @@ #include "clang/Serialization/ASTDeserializationListener.h" #include "clang/Serialization/ASTReader.h" #include "clang/Serialization/GlobalModuleIndex.h" -#include "llvm/ADT/ScopeExit.h" #include "llvm/Support/BuryPointer.h" +#include "llvm/Support/Cleanup.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -550,27 +550,33 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, const FrontendInputFile &RealInput) { + bool HasBegunSourceFile = false; + std::function Operation = [&]() { + return BeginSourceFileImpl(CI, RealInput, HasBegunSourceFile); + }; + std::function Cleanup = [&]() { + if (HasBegunSourceFile) + CI.getDiagnosticClient().EndSourceFile(); + CI.clearOutputFiles(/*EraseFiles=*/true); + CI.getLangOpts().setCompilingModule(LangOptions::CMK_None); + setCurrentInput(FrontendInputFile()); + setCompilerInstance(nullptr); + }; + return llvm::doWithCleanup(Operation, Cleanup); +} + +bool FrontendAction::BeginSourceFileImpl(CompilerInstance &CI, + const FrontendInputFile &RealInput, + bool &HasBegunSourceFile) { FrontendInputFile Input(RealInput); assert(!Instance && "Already processing a source file!"); assert(!Input.isEmpty() && "Unexpected empty filename!"); setCurrentInput(Input); setCompilerInstance(&CI); - bool HasBegunSourceFile = false; bool ReplayASTFile = Input.getKind().getFormat() == InputKind::Precompiled && usesPreprocessorOnly(); - // If we fail, reset state since the client will not end up calling the - // matching EndSourceFile(). All paths that return true should release this. - auto FailureCleanup = llvm::make_scope_exit([&]() { - if (HasBegunSourceFile) - CI.getDiagnosticClient().EndSourceFile(); - CI.clearOutputFiles(/*EraseFiles=*/true); - CI.getLangOpts().setCompilingModule(LangOptions::CMK_None); - setCurrentInput(FrontendInputFile()); - setCompilerInstance(nullptr); - }); - if (!BeginInvocation(CI)) return false; @@ -689,7 +695,6 @@ if (!CI.hasASTConsumer()) return false; - FailureCleanup.release(); return true; } @@ -730,7 +735,6 @@ if (!CI.InitializeSourceManager(CurrentInput)) return false; - FailureCleanup.release(); return true; } @@ -942,7 +946,6 @@ CI.getASTContext().setExternalSource(Override); } - FailureCleanup.release(); return true; } diff --git a/llvm/include/llvm/Support/Cleanup.h b/llvm/include/llvm/Support/Cleanup.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/Cleanup.h @@ -0,0 +1,67 @@ +//===- llvm/Support/Cleanup.h - Cleanup on failure abstraction --*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines helper abstractions for cleaning up resources on failure. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_CLEANUP_H +#define LLVM_SUPPORT_CLEANUP_H + +#include "llvm/Support/Error.h" + +#include +#include + +namespace llvm { + +/// Helper templates for determining if a result is a failure. +namespace detail { + +/// Evaluator for if an llvm::Error contains a failure. +template +typename std::enable_if::value, bool>::type +isFailure(ErrT &Err) { + return (bool)Err; +} + +/// Evaluator for c-style int error returns where success is 0. +template +typename std::enable_if::value, bool>::type +isFailure(ErrT &Err) { + return 0 != Err; +} + +/// Evaluator for boolean error returns where success is true. +template +typename std::enable_if::value || + std::is_same::value), + bool>::type +isFailure(ErrT &Err) { + return !(bool)Err; +} +} // namespace detail + +/// doWithCleanup abstraction performs an operation and cleanup on failure. +/// +/// @param Fn std::function to perform which returns the error type. +/// @param CleanupFn std::function to perform cleanup on failure result. +/// @param IsFailure (optional) std::function that takes a reference of the +/// error type, and returns true if the value is an error and false otherwise. +template +ErrT doWithCleanup( + std::function Fn, std::function CleanupFn, + std::function IsFailure = &detail::isFailure) { + ErrT Result = Fn(); + if (IsFailure(Result)) + CleanupFn(); + return Result; +} +} // namespace llvm + +#endif // LLVM_SUPPORT_CLEANUP_H diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -18,6 +18,7 @@ Casting.cpp CheckedArithmeticTest.cpp Chrono.cpp + CleanupTests.cpp CommandLineTest.cpp CompressionTest.cpp ConvertUTFTest.cpp diff --git a/llvm/unittests/Support/CleanupTests.cpp b/llvm/unittests/Support/CleanupTests.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/CleanupTests.cpp @@ -0,0 +1,86 @@ +//===----- unittests/CleanupTests.cpp - Cleanup.h tests -------------------===// +// +// 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 "llvm/Support/Cleanup.h" +#include "llvm/Support/Error.h" + +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(Error, DoWithCleanupBool) { + bool CleanupRun = false; + bool Result = + doWithCleanup([] { return true; }, [&] { CleanupRun = true; }); + EXPECT_TRUE(Result); + EXPECT_FALSE(CleanupRun); + + Result = + doWithCleanup([] { return false; }, [&] { CleanupRun = true; }); + EXPECT_FALSE(Result); + EXPECT_TRUE(CleanupRun); +} + +TEST(Error, DoWithCleanupError) { + bool CleanupRun = false; + llvm::consumeError(doWithCleanup( + [&] { return Error::success(); }, [&] { CleanupRun = true; })); + + EXPECT_FALSE(CleanupRun); + + llvm::consumeError(doWithCleanup( + [&] { return errorCodeToError(inconvertibleErrorCode()); }, + [&] { CleanupRun = true; })); + EXPECT_TRUE(CleanupRun); +} + +TEST(Error, DoWithCleanupInt) { + bool CleanupRun = false; + int Result = doWithCleanup([] { return 0; }, [&] { CleanupRun = true; }); + EXPECT_EQ(Result, 0); + EXPECT_FALSE(CleanupRun); + + Result = doWithCleanup([] { return 1; }, [&] { CleanupRun = true; }); + EXPECT_EQ(Result, 1); + EXPECT_TRUE(CleanupRun); +} + +enum class PetType { Doggo, Kitteh, Birb, WhoozieWhatsit }; + +TEST(Error, DoWithCleanupOverrideFailure) { + bool CleanupRun = false; + PetType Result = doWithCleanup( + [] { return PetType::Doggo; }, [&] { CleanupRun = true; }, + [](PetType &E) { return E == PetType::WhoozieWhatsit; }); + EXPECT_EQ(Result, PetType::Doggo); + EXPECT_FALSE(CleanupRun); + + Result = doWithCleanup( + [] { return PetType::WhoozieWhatsit; }, [&] { CleanupRun = true; }, + [](PetType &E) { return E == PetType::WhoozieWhatsit; }); + EXPECT_EQ(Result, PetType::WhoozieWhatsit); + EXPECT_TRUE(CleanupRun); +} + +enum class ResultTy { + Failure = 0, + Success, +}; + +TEST(Error, DoWithCleanupEnumNonZeroFailure) { + bool CleanupRun = false; + ResultTy Result = doWithCleanup([] { return ResultTy::Success; }, + [&] { CleanupRun = true; }); + EXPECT_EQ(Result, ResultTy::Success); + EXPECT_FALSE(CleanupRun); + + Result = doWithCleanup([] { return ResultTy::Failure; }, + [&] { CleanupRun = true; }); + EXPECT_EQ(Result, ResultTy::Failure); + EXPECT_TRUE(CleanupRun); +}