https://github.com/Kitware/CMake
Revision d89e10cd58e5f9e21cbd466e56a1890e2811bee0 authored by Brad King on 14 July 2017, 17:52:53 UTC, committed by Brad King on 14 July 2017, 18:05:22 UTC
The code

    add_library(A OBJECT a.c)
    target_sources(A PRIVATE $<TARGET_OBJECTS:A>)

used to crash CMake via infinite recursion while evaluating the
generator expression.  Then the change in commit v3.9.0-rc1~266^2~1
(cmGeneratorTarget: Replace source classifier implementation,
2017-04-07) avoided the infinite recursion because GetKindedSources now
creates a map entry and initializes it once.  If it is called again on
the same target during that initialization, the partially computed
results are returned.  This is still wrong but does not crash.
Detect and diagnose this case instead.

Co-Author: Ben Boeckel <ben.boeckel@kitware.com>
Fixes: #16578
1 parent 25b72e9
Raw File
Tip revision: d89e10cd58e5f9e21cbd466e56a1890e2811bee0 authored by Brad King on 14 July 2017, 17:52:53 UTC
Diagnose object library self-reference
Tip revision: d89e10c
cmGeneratorExpressionDAGChecker.cxx
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmGeneratorExpressionDAGChecker.h"

#include "cmAlgorithms.h"
#include "cmGeneratorExpressionContext.h"
#include "cmGeneratorExpressionEvaluator.h"
#include "cmGeneratorTarget.h"
#include "cmLocalGenerator.h"
#include "cmake.h"

#include <sstream>
#include <string.h>
#include <utility>

cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
  const cmListFileBacktrace& backtrace, const std::string& target,
  const std::string& property, const GeneratorExpressionContent* content,
  cmGeneratorExpressionDAGChecker* parent)
  : Parent(parent)
  , Target(target)
  , Property(property)
  , Content(content)
  , Backtrace(backtrace)
  , TransitivePropertiesOnly(false)
{
  Initialize();
}

cmGeneratorExpressionDAGChecker::cmGeneratorExpressionDAGChecker(
  const std::string& target, const std::string& property,
  const GeneratorExpressionContent* content,
  cmGeneratorExpressionDAGChecker* parent)
  : Parent(parent)
  , Target(target)
  , Property(property)
  , Content(content)
  , Backtrace()
  , TransitivePropertiesOnly(false)
{
  Initialize();
}

void cmGeneratorExpressionDAGChecker::Initialize()
{
  const cmGeneratorExpressionDAGChecker* top = this;
  const cmGeneratorExpressionDAGChecker* p = this->Parent;
  while (p) {
    top = p;
    p = p->Parent;
  }
  this->CheckResult = this->CheckGraph();

#define TEST_TRANSITIVE_PROPERTY_METHOD(METHOD) top->METHOD() ||

  if (CheckResult == DAG &&
      (CM_FOR_EACH_TRANSITIVE_PROPERTY_METHOD(
        TEST_TRANSITIVE_PROPERTY_METHOD) false)) // NOLINT(clang-tidy)
#undef TEST_TRANSITIVE_PROPERTY_METHOD
  {
    std::map<std::string, std::set<std::string> >::const_iterator it =
      top->Seen.find(this->Target);
    if (it != top->Seen.end()) {
      const std::set<std::string>& propSet = it->second;
      if (propSet.find(this->Property) != propSet.end()) {
        this->CheckResult = ALREADY_SEEN;
        return;
      }
    }
    const_cast<cmGeneratorExpressionDAGChecker*>(top)
      ->Seen[this->Target]
      .insert(this->Property);
  }
}

cmGeneratorExpressionDAGChecker::Result
cmGeneratorExpressionDAGChecker::Check() const
{
  return this->CheckResult;
}

void cmGeneratorExpressionDAGChecker::ReportError(
  cmGeneratorExpressionContext* context, const std::string& expr)
{
  if (this->CheckResult == DAG) {
    return;
  }

  context->HadError = true;
  if (context->Quiet) {
    return;
  }

  const cmGeneratorExpressionDAGChecker* parent = this->Parent;

  if (parent && !parent->Parent) {
    std::ostringstream e;
    e << "Error evaluating generator expression:\n"
      << "  " << expr << "\n"
      << "Self reference on target \"" << context->HeadTarget->GetName()
      << "\".\n";
    context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(),
                                                  parent->Backtrace);
    return;
  }

  {
    std::ostringstream e;
    /* clang-format off */
  e << "Error evaluating generator expression:\n"
    << "  " << expr << "\n"
    << "Dependency loop found.";
    /* clang-format on */
    context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(),
                                                  context->Backtrace);
  }

  int loopStep = 1;
  while (parent) {
    std::ostringstream e;
    e << "Loop step " << loopStep << "\n"
      << "  "
      << (parent->Content ? parent->Content->GetOriginalExpression() : expr)
      << "\n";
    context->LG->GetCMakeInstance()->IssueMessage(cmake::FATAL_ERROR, e.str(),
                                                  parent->Backtrace);
    parent = parent->Parent;
    ++loopStep;
  }
}

cmGeneratorExpressionDAGChecker::Result
cmGeneratorExpressionDAGChecker::CheckGraph() const
{
  const cmGeneratorExpressionDAGChecker* parent = this->Parent;
  while (parent) {
    if (this->Target == parent->Target && this->Property == parent->Property) {
      return (parent == this->Parent) ? SELF_REFERENCE : CYCLIC_REFERENCE;
    }
    parent = parent->Parent;
  }
  return DAG;
}

bool cmGeneratorExpressionDAGChecker::GetTransitivePropertiesOnly()
{
  const cmGeneratorExpressionDAGChecker* top = this;
  const cmGeneratorExpressionDAGChecker* parent = this->Parent;
  while (parent) {
    top = parent;
    parent = parent->Parent;
  }

  return top->TransitivePropertiesOnly;
}

bool cmGeneratorExpressionDAGChecker::EvaluatingLinkLibraries(const char* tgt)
{
  const cmGeneratorExpressionDAGChecker* top = this;
  const cmGeneratorExpressionDAGChecker* parent = this->Parent;
  while (parent) {
    top = parent;
    parent = parent->Parent;
  }

  const char* prop = top->Property.c_str();

  if (tgt) {
    return top->Target == tgt && strcmp(prop, "LINK_LIBRARIES") == 0;
  }

  return (strcmp(prop, "LINK_LIBRARIES") == 0 ||
          strcmp(prop, "LINK_INTERFACE_LIBRARIES") == 0 ||
          strcmp(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES") == 0 ||
          cmHasLiteralPrefix(prop, "LINK_INTERFACE_LIBRARIES_") ||
          cmHasLiteralPrefix(prop, "IMPORTED_LINK_INTERFACE_LIBRARIES_")) ||
    strcmp(prop, "INTERFACE_LINK_LIBRARIES") == 0;
}

std::string cmGeneratorExpressionDAGChecker::TopTarget() const
{
  const cmGeneratorExpressionDAGChecker* top = this;
  const cmGeneratorExpressionDAGChecker* parent = this->Parent;
  while (parent) {
    top = parent;
    parent = parent->Parent;
  }
  return top->Target;
}

enum TransitiveProperty
{
#define DEFINE_ENUM_ENTRY(NAME) NAME,
  CM_FOR_EACH_TRANSITIVE_PROPERTY_NAME(DEFINE_ENUM_ENTRY)
#undef DEFINE_ENUM_ENTRY
    TransitivePropertyTerminal
};

template <TransitiveProperty>
bool additionalTest(const char* const /*unused*/)
{
  return false;
}

template <>
bool additionalTest<COMPILE_DEFINITIONS>(const char* const prop)
{
  return cmHasLiteralPrefix(prop, "COMPILE_DEFINITIONS_");
}

#define DEFINE_TRANSITIVE_PROPERTY_METHOD(METHOD, PROPERTY)                   \
  bool cmGeneratorExpressionDAGChecker::METHOD() const                        \
  {                                                                           \
    const char* const prop = this->Property.c_str();                          \
    if (strcmp(prop, #PROPERTY) == 0 ||                                       \
        strcmp(prop, "INTERFACE_" #PROPERTY) == 0) {                          \
      return true;                                                            \
    }                                                                         \
    return additionalTest<PROPERTY>(prop);                                    \
  }

CM_FOR_EACH_TRANSITIVE_PROPERTY(DEFINE_TRANSITIVE_PROPERTY_METHOD)

#undef DEFINE_TRANSITIVE_PROPERTY_METHOD
back to top