Browse Source

variable_watch: Don't share memory for callbacks

The command itself is owned by the cmMakefile class, but the
cmVariableWatch which holds a pointer to the cmVariableWatchCommand via
the client_data for the callback outlives the cmMakefile class in the Qt
GUI. This means that when the cmMakefile is destroyed, the variable
watch is still in effect, but with a stale pointer.

To fix this, each callback is now a separate entity completely and
doesn't rely on the command which spawned it at all.

An example CMakeLists.txt which demonstrates the issue (only displayed
in cmake-gui, so no tests can be written for it):

    set(var 0)
    variable_watch(var)
Ben Boeckel 12 years ago
parent
commit
f9bb20fe2b
2 changed files with 87 additions and 67 deletions
  1. 83 54
      Source/cmVariableWatchCommand.cxx
  2. 4 13
      Source/cmVariableWatchCommand.h

+ 83 - 54
Source/cmVariableWatchCommand.cxx

@@ -14,63 +14,27 @@
 #include "cmVariableWatch.h"
 
 //----------------------------------------------------------------------------
-static void cmVariableWatchCommandVariableAccessed(
-  const std::string& variable, int access_type, void* client_data,
-  const char* newValue, const cmMakefile* mf)
+struct cmVariableWatchCallbackData
 {
-  cmVariableWatchCommand* command
-    = static_cast<cmVariableWatchCommand*>(client_data);
-  command->VariableAccessed(variable, access_type, newValue, mf);
-}
+  bool InCallback;
+  std::string Command;
+};
 
 //----------------------------------------------------------------------------
-cmVariableWatchCommand::cmVariableWatchCommand()
+static void cmVariableWatchCommandVariableAccessed(
+  const std::string& variable, int access_type, void* client_data,
+  const char* newValue, const cmMakefile* mf)
 {
-  this->InCallback = false;
-}
+  cmVariableWatchCallbackData* data
+    = static_cast<cmVariableWatchCallbackData*>(client_data);
 
-//----------------------------------------------------------------------------
-bool cmVariableWatchCommand
-::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
-{
-  if ( args.size() < 1 )
-    {
-    this->SetError("must be called with at least one argument.");
-    return false;
-    }
-  std::string variable = args[0];
-  if ( args.size() > 1 )
-    {
-    std::string command = args[1];
-    this->Handlers[variable].Commands.push_back(args[1]);
-    }
-  if ( variable == "CMAKE_CURRENT_LIST_FILE" )
-    {
-    cmOStringStream ostr;
-    ostr << "cannot be set on the variable: " << variable.c_str();
-    this->SetError(ostr.str().c_str());
-    return false;
-    }
-
-  this->Makefile->GetCMakeInstance()->GetVariableWatch()->AddWatch(
-    variable, cmVariableWatchCommandVariableAccessed, this);
-
-  return true;
-}
-
-//----------------------------------------------------------------------------
-void cmVariableWatchCommand::VariableAccessed(const std::string& variable,
-  int access_type, const char* newValue, const cmMakefile* mf)
-{
-  if ( this->InCallback )
+  if ( data->InCallback )
     {
     return;
     }
-  this->InCallback = true;
+  data->InCallback = true;
 
   cmListFileFunction newLFF;
-  cmVariableWatchCommandHandler *handler = &this->Handlers[variable];
-  cmVariableWatchCommandHandler::VectorOfCommands::iterator it;
   cmListFileArgument arg;
   bool processed = false;
   const char* accessString = cmVariableWatch::GetAccessAsString(access_type);
@@ -80,10 +44,8 @@ void cmVariableWatchCommand::VariableAccessed(const std::string& variable,
   cmMakefile* makefile = const_cast<cmMakefile*>(mf);
 
   std::string stack = makefile->GetProperty("LISTFILE_STACK");
-  for ( it = handler->Commands.begin(); it != handler->Commands.end();
-    ++ it )
+  if ( !data->Command.empty() )
     {
-    std::string command = *it;
     newLFF.Arguments.clear();
     newLFF.Arguments.push_back(
       cmListFileArgument(variable, true, "unknown", 9999));
@@ -95,7 +57,7 @@ void cmVariableWatchCommand::VariableAccessed(const std::string& variable,
       cmListFileArgument(currentListFile, true, "unknown", 9999));
     newLFF.Arguments.push_back(
       cmListFileArgument(stack, true, "unknown", 9999));
-    newLFF.Name = command;
+    newLFF.Name = data->Command;
     newLFF.FilePath = "Some weird path";
     newLFF.Line = 9999;
     cmExecutionStatus status;
@@ -107,9 +69,9 @@ void cmVariableWatchCommand::VariableAccessed(const std::string& variable,
       error << "Error in cmake code at\n"
         << arg.FilePath << ":" << arg.Line << ":\n"
         << "A command failed during the invocation of callback \""
-        << command << "\".";
+        << data->Command << "\".";
       cmSystemTools::Error(error.str().c_str());
-      this->InCallback = false;
+      data->InCallback = false;
       return;
       }
     processed = true;
@@ -121,5 +83,72 @@ void cmVariableWatchCommand::VariableAccessed(const std::string& variable,
         << accessString << " with value \"" << newValue << "\".";
     makefile->IssueMessage(cmake::LOG, msg.str());
     }
-  this->InCallback = false;
+
+  data->InCallback = false;
+}
+
+//----------------------------------------------------------------------------
+static void deleteVariableWatchCallbackData(void* client_data)
+{
+  cmVariableWatchCallbackData* data
+    = static_cast<cmVariableWatchCallbackData*>(client_data);
+  delete data;
+}
+
+//----------------------------------------------------------------------------
+cmVariableWatchCommand::cmVariableWatchCommand()
+{
+}
+
+//----------------------------------------------------------------------------
+cmVariableWatchCommand::~cmVariableWatchCommand()
+{
+  std::set<std::string>::const_iterator it;
+  for ( it = this->WatchedVariables.begin();
+        it != this->WatchedVariables.end();
+        ++it )
+    {
+    this->Makefile->GetCMakeInstance()->GetVariableWatch()->RemoveWatch(
+      *it, cmVariableWatchCommandVariableAccessed);
+    }
+}
+
+//----------------------------------------------------------------------------
+bool cmVariableWatchCommand
+::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
+{
+  if ( args.size() < 1 )
+    {
+    this->SetError("must be called with at least one argument.");
+    return false;
+    }
+  std::string variable = args[0];
+  std::string command;
+  if ( args.size() > 1 )
+    {
+    command = args[1];
+    }
+  if ( variable == "CMAKE_CURRENT_LIST_FILE" )
+    {
+    cmOStringStream ostr;
+    ostr << "cannot be set on the variable: " << variable.c_str();
+    this->SetError(ostr.str().c_str());
+    return false;
+    }
+
+  cmVariableWatchCallbackData* data = new cmVariableWatchCallbackData;
+
+  data->InCallback = false;
+  data->Command = command;
+
+  this->WatchedVariables.insert(variable);
+  if ( !this->Makefile->GetCMakeInstance()->GetVariableWatch()->AddWatch(
+          variable, cmVariableWatchCommandVariableAccessed,
+          data, deleteVariableWatchCallbackData) )
+    {
+    deleteVariableWatchCallbackData(data);
+    return false;
+    }
+
+  return true;
 }

+ 4 - 13
Source/cmVariableWatchCommand.h

@@ -14,13 +14,6 @@
 
 #include "cmCommand.h"
 
-class cmVariableWatchCommandHandler
-{
-public:
-  typedef std::vector<std::string> VectorOfCommands;
-  VectorOfCommands Commands;
-};
-
 /** \class cmVariableWatchCommand
  * \brief Watch when the variable changes and invoke command
  *
@@ -39,6 +32,9 @@ public:
   //! Default constructor
   cmVariableWatchCommand();
 
+  //! Destructor.
+  ~cmVariableWatchCommand();
+
   /**
    * This is called when the command is first encountered in
    * the CMakeLists.txt file.
@@ -83,13 +79,8 @@ public:
 
   cmTypeMacro(cmVariableWatchCommand, cmCommand);
 
-  void VariableAccessed(const std::string& variable, int access_type,
-    const char* newValue, const cmMakefile* mf);
-
 protected:
-  std::map<std::string, cmVariableWatchCommandHandler> Handlers;
-
-  bool InCallback;
+  std::set<std::string> WatchedVariables;
 };