[Utility] Simplify and generalize the CleanUp helper, NFC
authorVedant Kumar <vsk@apple.com>
Fri, 23 Feb 2018 22:08:38 +0000 (22:08 +0000)
committerVedant Kumar <vsk@apple.com>
Fri, 23 Feb 2018 22:08:38 +0000 (22:08 +0000)
Removing the template arguments and most of the mutating methods from
CleanUp makes it easier to understand and reuse.

In its present state, CleanUp would be too cumbersome to adapt to cases
where multiple objects need to be released. Take for example this change
in swift-lldb:

  https://github.com/apple/swift-lldb/pull/334/files#diff-6f474df750f75c8ba675f2a8408a5629R219

This change is simple to express with the new CleanUp, but not so simple
with the old version.

Differential Revision: https://reviews.llvm.org/D43662

llvm-svn: 325964

lldb/include/lldb/Utility/CleanUp.h
lldb/lldb.xcodeproj/project.pbxproj
lldb/source/Host/macosx/Host.mm
lldb/source/Host/macosx/Symbols.cpp
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/unittests/Utility/CMakeLists.txt
lldb/unittests/Utility/CleanUpTest.cpp [new file with mode: 0644]

index 0d7bc8d..ef460dd 100644 (file)
 #include "lldb/lldb-public.h"
 #include <functional>
 
-namespace lldb_utility {
+namespace lldb_private {
 
-//----------------------------------------------------------------------
-// Templated class that guarantees that a cleanup callback function will
-// be called. The cleanup function will be called once under the
-// following conditions:
-// - when the object goes out of scope
-// - when the user explicitly calls clean.
-// - the current value will be cleaned up when a new value is set using
-//   set(T value) as long as the current value hasn't already been cleaned.
-//
-// This class is designed to be used with simple types for type T (like
-// file descriptors, opaque handles, pointers, etc). If more complex
-// type T objects are desired, we need to probably specialize this class
-// to take "const T&" for all input T parameters. Yet if a type T is
-// complex already it might be better to build the cleanup functionality
-// into T.
-//
-// The cleanup function must take one argument that is of type T.
-// The calback function return type is R. The return value is currently
-// needed for "CallbackType". If there is an easy way to get around the
-// need for the return value we can change this class.
-//
-// The two template parameters are:
-//    T - The variable type of value that will be stored and used as the
-//      sole argument for the cleanup callback.
-//    R - The return type for the cleanup function.
-//
-// EXAMPLES
-//  // Use with file handles that get opened where you want to close
-//  // them. Below we use "int open(const char *path, int oflag, ...)"
-//  // which returns an integer file descriptor. -1 is the invalid file
-//  // descriptor so to make an object that will call "int close(int fd)"
-//  // automatically we can use:
-//
-//  CleanUp <int, int> fd(open("/tmp/a.txt", O_RDONLY, 0), -1, close);
-//
-//  // malloc/free example
-//  CleanUp <void *, void> malloced_bytes(malloc(32), NULL, free);
-//----------------------------------------------------------------------
-template <typename T, typename R = void> class CleanUp {
-public:
-  typedef T value_type;
-  typedef std::function<R(value_type)> CallbackType;
-
-  //----------------------------------------------------------------------
-  // Constructor that sets the current value only. No values are
-  // considered to be invalid and the cleanup function will be called
-  // regardless of the value of m_current_value.
-  //----------------------------------------------------------------------
-  CleanUp(value_type value, CallbackType callback)
-      : m_current_value(value), m_invalid_value(), m_callback(callback),
-        m_callback_called(false), m_invalid_value_is_valid(false) {}
-
-  //----------------------------------------------------------------------
-  // Constructor that sets the current value and also the invalid value.
-  // The cleanup function will be called on "m_value" as long as it isn't
-  // equal to "m_invalid_value".
-  //----------------------------------------------------------------------
-  CleanUp(value_type value, value_type invalid, CallbackType callback)
-      : m_current_value(value), m_invalid_value(invalid), m_callback(callback),
-        m_callback_called(false), m_invalid_value_is_valid(true) {}
-
-  //----------------------------------------------------------------------
-  // Automatically cleanup when this object goes out of scope.
-  //----------------------------------------------------------------------
-  ~CleanUp() { clean(); }
-
-  //----------------------------------------------------------------------
-  // Access the value stored in this class
-  //----------------------------------------------------------------------
-  value_type get() { return m_current_value; }
-
-  //----------------------------------------------------------------------
-  // Access the value stored in this class
-  //----------------------------------------------------------------------
-  const value_type get() const { return m_current_value; }
-
-  //----------------------------------------------------------------------
-  // Reset the owned value to "value". If a current value is valid and
-  // the cleanup callback hasn't been called, the previous value will
-  // be cleaned up (see void CleanUp::clean()).
-  //----------------------------------------------------------------------
-  void set(const value_type value) {
-    // Cleanup the current value if needed
-    clean();
-    // Now set the new value and mark our callback as not called
-    m_callback_called = false;
-    m_current_value = value;
-  }
-
-  //----------------------------------------------------------------------
-  // Checks is "m_current_value" is valid. The value is considered valid
-  // no invalid value was supplied during construction of this object or
-  // if an invalid value was supplied and "m_current_value" is not equal
-  // to "m_invalid_value".
-  //
-  // Returns true if "m_current_value" is valid, false otherwise.
-  //----------------------------------------------------------------------
-  bool is_valid() const {
-    if (m_invalid_value_is_valid)
-      return m_current_value != m_invalid_value;
-    return true;
-  }
-
-  //----------------------------------------------------------------------
-  // This function will call the cleanup callback provided in the
-  // constructor one time if the value is considered valid (See is_valid()).
-  // This function sets m_callback_called to true so we don't call the
-  // cleanup callback multiple times on the same value.
-  //----------------------------------------------------------------------
-  void clean() {
-    if (m_callback && !m_callback_called) {
-      m_callback_called = true;
-      if (is_valid())
-        m_callback(m_current_value);
-    }
-  }
+/// Run a cleanup function on scope exit unless it's explicitly disabled.
+class CleanUp {
+  std::function<void()> Clean;
 
-  //----------------------------------------------------------------------
-  // Cancels the cleanup that would have been called on "m_current_value"
-  // if it was valid. This function can be used to release the value
-  // contained in this object so ownership can be transferred to the caller.
-  //----------------------------------------------------------------------
-  value_type release() {
-    m_callback_called = true;
-    return m_current_value;
-  }
-
-private:
-  value_type m_current_value;
-  const value_type m_invalid_value;
-  CallbackType m_callback;
-  bool m_callback_called;
-  bool m_invalid_value_is_valid;
-
-  // Outlaw default constructor, copy constructor and the assignment operator
-  DISALLOW_COPY_AND_ASSIGN(CleanUp);
-};
-
-template <typename T, typename R, typename A0> class CleanUp2 {
 public:
-  typedef T value_type;
-  typedef std::function<R(value_type, A0)> CallbackType;
-
-  //----------------------------------------------------------------------
-  // Constructor that sets the current value only. No values are
-  // considered to be invalid and the cleanup function will be called
-  // regardless of the value of m_current_value.
-  //----------------------------------------------------------------------
-  CleanUp2(value_type value, CallbackType callback, A0 arg)
-      : m_current_value(value), m_invalid_value(), m_callback(callback),
-        m_callback_called(false), m_invalid_value_is_valid(false),
-        m_argument(arg) {}
-
-  //----------------------------------------------------------------------
-  // Constructor that sets the current value and also the invalid value.
-  // The cleanup function will be called on "m_value" as long as it isn't
-  // equal to "m_invalid_value".
-  //----------------------------------------------------------------------
-  CleanUp2(value_type value, value_type invalid, CallbackType callback, A0 arg)
-      : m_current_value(value), m_invalid_value(invalid), m_callback(callback),
-        m_callback_called(false), m_invalid_value_is_valid(true),
-        m_argument(arg) {}
-
-  //----------------------------------------------------------------------
-  // Automatically cleanup when this object goes out of scope.
-  //----------------------------------------------------------------------
-  ~CleanUp2() { clean(); }
+  /// Register a cleanup function which applies \p Func to a list of arguments.
+  /// Use caution with arguments which are references: they will be copied.
+  template <typename F, typename... Args>
+  CleanUp(F &&Func, Args &&... args)
+      : Clean(std::bind(std::forward<F>(Func), std::forward<Args>(args)...)) {}
 
-  //----------------------------------------------------------------------
-  // Access the value stored in this class
-  //----------------------------------------------------------------------
-  value_type get() { return m_current_value; }
-
-  //----------------------------------------------------------------------
-  // Access the value stored in this class
-  //----------------------------------------------------------------------
-  const value_type get() const { return m_current_value; }
-
-  //----------------------------------------------------------------------
-  // Reset the owned value to "value". If a current value is valid and
-  // the cleanup callback hasn't been called, the previous value will
-  // be cleaned up (see void CleanUp::clean()).
-  //----------------------------------------------------------------------
-  void set(const value_type value) {
-    // Cleanup the current value if needed
-    clean();
-    // Now set the new value and mark our callback as not called
-    m_callback_called = false;
-    m_current_value = value;
+  ~CleanUp() {
+    if (Clean)
+      Clean();
   }
 
-  //----------------------------------------------------------------------
-  // Checks is "m_current_value" is valid. The value is considered valid
-  // no invalid value was supplied during construction of this object or
-  // if an invalid value was supplied and "m_current_value" is not equal
-  // to "m_invalid_value".
-  //
-  // Returns true if "m_current_value" is valid, false otherwise.
-  //----------------------------------------------------------------------
-  bool is_valid() const {
-    if (m_invalid_value_is_valid)
-      return m_current_value != m_invalid_value;
-    return true;
-  }
-
-  //----------------------------------------------------------------------
-  // This function will call the cleanup callback provided in the
-  // constructor one time if the value is considered valid (See is_valid()).
-  // This function sets m_callback_called to true so we don't call the
-  // cleanup callback multiple times on the same value.
-  //----------------------------------------------------------------------
-  void clean() {
-    if (m_callback && !m_callback_called) {
-      m_callback_called = true;
-      if (is_valid())
-        m_callback(m_current_value, m_argument);
-    }
-  }
+  /// Disable the cleanup.
+  void disable() { Clean = nullptr; }
 
-  //----------------------------------------------------------------------
-  // Cancels the cleanup that would have been called on "m_current_value"
-  // if it was valid. This function can be used to release the value
-  // contained in this object so ownership can be transferred to the caller.
-  //----------------------------------------------------------------------
-  value_type release() {
-    m_callback_called = true;
-    return m_current_value;
-  }
-
-private:
-  value_type m_current_value;
-  const value_type m_invalid_value;
-  CallbackType m_callback;
-  bool m_callback_called;
-  bool m_invalid_value_is_valid;
-  A0 m_argument;
-
-  // Outlaw default constructor, copy constructor and the assignment operator
-  DISALLOW_COPY_AND_ASSIGN(CleanUp2);
+  // Prevent cleanups from being run more than once.
+  DISALLOW_COPY_AND_ASSIGN(CleanUp);
 };
 
-} // namespace lldb_utility
+} // namespace lldb_private
 
 #endif // #ifndef liblldb_CleanUp_h_
index 4bf0f0b..1c98634 100644 (file)
                6D99A3631BBC2F3200979793 /* ArmUnwindInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D99A3621BBC2F3200979793 /* ArmUnwindInfo.cpp */; };
                6D9AB3DD1BB2B74E003F2289 /* TypeMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D9AB3DC1BB2B74E003F2289 /* TypeMap.cpp */; };
                6DEC6F391BD66D750091ABA6 /* TaskPool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */; };
+               7F94D7182040A13A006EE3EA /* CleanUpTest.cpp in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7F94D7172040A13A006EE3EA /* CleanUpTest.cpp */; };
                8C26C4261C3EA5F90031DF7C /* TSanRuntime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C26C4241C3EA4340031DF7C /* TSanRuntime.cpp */; };
                8C2D6A53197A1EAF006989C9 /* MemoryHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C2D6A52197A1EAF006989C9 /* MemoryHistory.cpp */; };
                8C2D6A5E197A250F006989C9 /* MemoryHistoryASan.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C2D6A5A197A1FDC006989C9 /* MemoryHistoryASan.cpp */; };
                        dstPath = "$(DEVELOPER_INSTALL_DIR)/usr/share/man/man1";
                        dstSubfolderSpec = 0;
                        files = (
+                               7F94D7182040A13A006EE3EA /* CleanUpTest.cpp in CopyFiles */,
                                AF90106515AB7D3600FF120D /* lldb.1 in CopyFiles */,
                        );
                        runOnlyForDeploymentPostprocessing = 1;
                6D9AB3DE1BB2B76B003F2289 /* TypeMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TypeMap.h; path = include/lldb/Symbol/TypeMap.h; sourceTree = "<group>"; };
                6DEC6F381BD66D750091ABA6 /* TaskPool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TaskPool.cpp; path = source/Host/common/TaskPool.cpp; sourceTree = "<group>"; };
                6DEC6F3A1BD66D950091ABA6 /* TaskPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TaskPool.h; path = include/lldb/Host/TaskPool.h; sourceTree = "<group>"; };
+               7F94D7172040A13A006EE3EA /* CleanUpTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CleanUpTest.cpp; sourceTree = "<group>"; };
                8C26C4241C3EA4340031DF7C /* TSanRuntime.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TSanRuntime.cpp; path = TSan/TSanRuntime.cpp; sourceTree = "<group>"; };
                8C26C4251C3EA4340031DF7C /* TSanRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TSanRuntime.h; path = TSan/TSanRuntime.h; sourceTree = "<group>"; };
                8C2D6A52197A1EAF006989C9 /* MemoryHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MemoryHistory.cpp; path = source/Target/MemoryHistory.cpp; sourceTree = "<group>"; };
                2321F9421BDD343A00BA9A93 /* Utility */ = {
                        isa = PBXGroup;
                        children = (
+                               7F94D7172040A13A006EE3EA /* CleanUpTest.cpp */,
                                23E2E5161D903689006F38BB /* ArchSpecTest.cpp */,
                                9A3D43C81F3150D200EB767C /* ConstStringTest.cpp */,
                                9A3D43C71F3150D200EB767C /* LogTest.cpp */,
index 4e23bc2..4cf0177 100644 (file)
@@ -1277,10 +1277,8 @@ static Status LaunchProcessPosixSpawn(const char *exe_path,
     return error;
   }
 
-  // Make a quick class that will cleanup the posix spawn attributes in case
-  // we return in the middle of this function.
-  lldb_utility::CleanUp<posix_spawnattr_t *, int> posix_spawnattr_cleanup(
-      &attr, posix_spawnattr_destroy);
+  // Make sure we clean up the posix spawn attributes before exiting this scope.
+  CleanUp cleanup_attr(posix_spawnattr_destroy, &attr);
 
   sigset_t no_signals;
   sigset_t all_signals;
@@ -1382,11 +1380,8 @@ static Status LaunchProcessPosixSpawn(const char *exe_path,
       return error;
     }
 
-    // Make a quick class that will cleanup the posix spawn attributes in case
-    // we return in the middle of this function.
-    lldb_utility::CleanUp<posix_spawn_file_actions_t *, int>
-        posix_spawn_file_actions_cleanup(&file_actions,
-                                         posix_spawn_file_actions_destroy);
+    // Make sure we clean up the posix file actions before exiting this scope.
+    CleanUp cleanup_fileact(posix_spawn_file_actions_destroy, &file_actions);
 
     for (size_t i = 0; i < num_file_actions; ++i) {
       const FileAction *launch_file_action =
index c7a7069..a0b3ece 100644 (file)
@@ -240,52 +240,53 @@ FileSpec Symbols::FindSymbolFileInBundle(const FileSpec &dsym_bundle_fspec,
                                          const lldb_private::UUID *uuid,
                                          const ArchSpec *arch) {
   char path[PATH_MAX];
+  if (dsym_bundle_fspec.GetPath(path, sizeof(path)) == 0)
+    return {};
 
-  FileSpec dsym_fspec;
+  ::strncat(path, "/Contents/Resources/DWARF", sizeof(path) - strlen(path) - 1);
 
-  if (dsym_bundle_fspec.GetPath(path, sizeof(path))) {
-    ::strncat(path, "/Contents/Resources/DWARF",
-              sizeof(path) - strlen(path) - 1);
-
-    lldb_utility::CleanUp<DIR *, int> dirp(opendir(path), NULL, closedir);
-    if (dirp.is_valid()) {
-      dsym_fspec.GetDirectory().SetCString(path);
-      struct dirent *dp;
-      while ((dp = readdir(dirp.get())) != NULL) {
-        // Only search directories
-        if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN) {
-          if (dp->d_namlen == 1 && dp->d_name[0] == '.')
-            continue;
-
-          if (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.')
-            continue;
-        }
+  DIR *dirp = opendir(path);
+  if (!dirp)
+    return {};
 
-        if (dp->d_type == DT_REG || dp->d_type == DT_UNKNOWN) {
-          dsym_fspec.GetFilename().SetCString(dp->d_name);
-          ModuleSpecList module_specs;
-          if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0,
-                                                  module_specs)) {
-            ModuleSpec spec;
-            for (size_t i = 0; i < module_specs.GetSize(); ++i) {
-              bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
-              UNUSED_IF_ASSERT_DISABLED(got_spec);
-              assert(got_spec);
-              if ((uuid == NULL ||
-                   (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
-                  (arch == NULL ||
-                   (spec.GetArchitecturePtr() &&
-                    spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
-                return dsym_fspec;
-              }
-            }
+  // Make sure we close the directory before exiting this scope.
+  CleanUp cleanup_dir(closedir, dirp);
+
+  FileSpec dsym_fspec;
+  dsym_fspec.GetDirectory().SetCString(path);
+  struct dirent *dp;
+  while ((dp = readdir(dirp)) != NULL) {
+    // Only search directories
+    if (dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN) {
+      if (dp->d_namlen == 1 && dp->d_name[0] == '.')
+        continue;
+
+      if (dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.')
+        continue;
+    }
+
+    if (dp->d_type == DT_REG || dp->d_type == DT_UNKNOWN) {
+      dsym_fspec.GetFilename().SetCString(dp->d_name);
+      ModuleSpecList module_specs;
+      if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) {
+        ModuleSpec spec;
+        for (size_t i = 0; i < module_specs.GetSize(); ++i) {
+          bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
+          UNUSED_IF_ASSERT_DISABLED(got_spec);
+          assert(got_spec);
+          if ((uuid == NULL ||
+               (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
+              (arch == NULL ||
+               (spec.GetArchitecturePtr() &&
+                spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
+            return dsym_fspec;
           }
         }
       }
     }
   }
-  dsym_fspec.Clear();
-  return dsym_fspec;
+
+  return {};
 }
 
 static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,
index 3605265..0823add 100644 (file)
@@ -3316,27 +3316,22 @@ Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
 
     int communication_fd = -1;
 #ifdef USE_SOCKETPAIR_FOR_LOCAL_CONNECTION
-    // Auto close the sockets we might open up unless everything goes OK. This
-    // helps us not leak file descriptors when things go wrong.
-    lldb_utility::CleanUp<int, int> our_socket(-1, -1, close);
-    lldb_utility::CleanUp<int, int> gdb_socket(-1, -1, close);
-
     // Use a socketpair on non-Windows systems for security and performance
     // reasons.
-    {
-      int sockets[2]; /* the pair of socket descriptors */
-      if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
-        error.SetErrorToErrno();
-        return error;
-      }
-
-      our_socket.set(sockets[0]);
-      gdb_socket.set(sockets[1]);
+    int sockets[2]; /* the pair of socket descriptors */
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
+      error.SetErrorToErrno();
+      return error;
     }
 
+    int our_socket = sockets[0];
+    int gdb_socket = sockets[1];
+    CleanUp cleanup_our(close, our_socket);
+    CleanUp cleanup_gdb(close, gdb_socket);
+
     // Don't let any child processes inherit our communication socket
-    SetCloexecFlag(our_socket.get());
-    communication_fd = gdb_socket.get();
+    SetCloexecFlag(our_socket);
+    communication_fd = gdb_socket;
 #endif
 
     error = m_gdb_comm.StartDebugserverProcess(
@@ -3352,8 +3347,8 @@ Status ProcessGDBRemote::LaunchAndConnectToDebugserver(
 #ifdef USE_SOCKETPAIR_FOR_LOCAL_CONNECTION
       // Our process spawned correctly, we can now set our connection to use our
       // end of the socket pair
-      m_gdb_comm.SetConnection(
-          new ConnectionFileDescriptor(our_socket.release(), true));
+      cleanup_our.disable();
+      m_gdb_comm.SetConnection(new ConnectionFileDescriptor(our_socket, true));
 #endif
       StartAsyncThread();
     }
index 00a63ef..e05084a 100644 (file)
@@ -1,5 +1,6 @@
 add_lldb_unittest(UtilityTests
   ArchSpecTest.cpp
+  CleanUpTest.cpp
   ConstStringTest.cpp
   EnvironmentTest.cpp
   JSONTest.cpp
diff --git a/lldb/unittests/Utility/CleanUpTest.cpp b/lldb/unittests/Utility/CleanUpTest.cpp
new file mode 100644 (file)
index 0000000..164dc32
--- /dev/null
@@ -0,0 +1,47 @@
+//===-- CleanUpTest.cpp -----------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Utility/CleanUp.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+TEST(CleanUpTest, no_args) {
+  bool f = false;
+  {
+    CleanUp cleanup([&] { f = true; });
+  }
+  ASSERT_TRUE(f);
+}
+
+TEST(CleanUpTest, multiple_args) {
+  bool f1 = false;
+  bool f2 = false;
+  bool f3 = false;
+  {
+    CleanUp cleanup(
+        [](bool arg1, bool *arg2, bool &arg3) {
+          ASSERT_FALSE(arg1);
+          *arg2 = true;
+          arg3 = true;
+        },
+        f1, &f2, f3);
+  }
+  ASSERT_TRUE(f2);
+  ASSERT_FALSE(f3);
+}
+
+TEST(CleanUpTest, disable) {
+  bool f = false;
+  {
+    CleanUp cleanup([&] { f = true; });
+    cleanup.disable();
+  }
+  ASSERT_FALSE(f);
+}