Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmMessenger.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmMessenger.h"
4
5
#include <algorithm>
6
#include <array>
7
#include <sstream>
8
#include <utility>
9
10
#include <cmext/string_view>
11
12
#include "cmDocumentationFormatter.h"
13
#include "cmMessageMetadata.h"
14
#include "cmMessageType.h"
15
#include "cmStateSnapshot.h"
16
#include "cmStdIoTerminal.h"
17
#include "cmStringAlgorithms.h"
18
#include "cmSystemTools.h"
19
20
#if !defined(CMAKE_BOOTSTRAP)
21
#  include "cmsys/SystemInformation.hxx"
22
#endif
23
24
#ifdef CMake_ENABLE_DEBUGGER
25
#  include "cmDebuggerAdapter.h"
26
#endif
27
28
namespace {
29
char const* getMessageTypeStr(MessageType t)
30
37.2k
{
31
37.2k
  switch (t) {
32
12.7k
    case MessageType::FATAL_ERROR:
33
12.7k
      return "Error";
34
0
    case MessageType::INTERNAL_ERROR:
35
0
      return "Internal Error (please report a bug)";
36
0
    case MessageType::LOG:
37
0
      return "Debug Log";
38
24.5k
    default:
39
24.5k
      break;
40
37.2k
  }
41
24.5k
  return "Warning";
42
37.2k
}
43
44
std::string getDiagnosticCategoryStr(cmDiagnosticCategory category)
45
0
{
46
0
  std::string out = cmSystemTools::LowerCase(
47
0
    cmDiagnostics::GetCategoryString(category).substr(4));
48
0
  std::replace(out.begin(), out.end(), '_', '-');
49
0
  return out;
50
0
}
51
52
cm::StdIo::TermAttr getMessageColor(MessageType t)
53
37.2k
{
54
37.2k
  switch (t) {
55
0
    case MessageType::INTERNAL_ERROR:
56
12.7k
    case MessageType::FATAL_ERROR:
57
12.7k
      return cm::StdIo::TermAttr::ForegroundRed;
58
24.5k
    case MessageType::WARNING:
59
24.5k
      return cm::StdIo::TermAttr::ForegroundYellow;
60
0
    default:
61
0
      return cm::StdIo::TermAttr::Normal;
62
37.2k
  }
63
37.2k
}
64
65
bool isAuthorDiagnostic(cmDiagnosticCategory category)
66
37.2k
{
67
37.2k
  while (category != cmDiagnostics::CMD_NONE) {
68
0
    if (category == cmDiagnostics::CMD_AUTHOR) {
69
0
      return true;
70
0
    }
71
0
    category = cmDiagnostics::CategoryInfo[category].Parent;
72
0
  }
73
37.2k
  return false;
74
37.2k
}
75
76
void printMessageText(std::ostream& msg, std::string const& text)
77
37.2k
{
78
37.2k
  msg << ":\n";
79
37.2k
  cmDocumentationFormatter formatter;
80
37.2k
  formatter.SetIndent(2u);
81
37.2k
  formatter.PrintFormatted(msg, text);
82
37.2k
}
83
84
void displayMessage(MessageType type, cmDiagnosticCategory category,
85
                    cmMessenger::SuppressionHint hint, std::ostringstream& msg)
86
37.2k
{
87
37.2k
  if (hint == cmMessenger::SuppressionHint::Show &&
88
37.2k
      isAuthorDiagnostic(category)) {
89
    // Add a note about warning suppression.
90
0
    std::ostringstream text;
91
0
    if (type == MessageType::WARNING) {
92
0
      text << "This warning is for project developers.  "
93
0
              "Use -Wno-author";
94
0
      if (category != cmDiagnostics::CMD_AUTHOR) {
95
0
        text << " or -Wno-" << getDiagnosticCategoryStr(category);
96
0
      }
97
0
    } else if (type == MessageType::FATAL_ERROR) {
98
0
      text << "This error is for project developers.  "
99
0
              "Use -Wno-error=author";
100
0
      if (category != cmDiagnostics::CMD_AUTHOR) {
101
0
        text << " or -Wno-error=" << getDiagnosticCategoryStr(category);
102
0
      }
103
0
    }
104
0
    text << " to suppress it.";
105
106
0
    cmDocumentationFormatter formatter;
107
0
    formatter.PrintFormatted(msg, text.str());
108
37.2k
  } else {
109
    // Add a terminating blank line.
110
37.2k
    msg << '\n';
111
37.2k
  }
112
113
37.2k
#if !defined(CMAKE_BOOTSTRAP)
114
  // Add a C++ stack trace to internal errors.
115
37.2k
  if (type == MessageType::INTERNAL_ERROR) {
116
0
    std::string stack = cmsys::SystemInformation::GetProgramStack(0, 0);
117
0
    if (!stack.empty()) {
118
0
      if (cmHasLiteralPrefix(stack, "WARNING:")) {
119
0
        stack = "Note:" + stack.substr(8);
120
0
      }
121
0
      msg << stack << '\n';
122
0
    }
123
0
  }
124
37.2k
#endif
125
126
  // Output the message.
127
37.2k
  cmMessageMetadata md;
128
37.2k
  md.attrs = getMessageColor(type);
129
37.2k
  if (type == MessageType::FATAL_ERROR ||
130
24.5k
      type == MessageType::INTERNAL_ERROR) {
131
12.7k
    cmSystemTools::SetErrorOccurred();
132
12.7k
    md.title = "Error";
133
24.5k
  } else {
134
24.5k
    md.title = "Warning";
135
24.5k
  }
136
37.2k
  cmSystemTools::Message(msg.str(), md);
137
37.2k
}
138
139
void PrintCallStack(std::ostream& out, cmListFileBacktrace bt,
140
                    cm::optional<std::string> const& topSource)
141
37.2k
{
142
  // The call stack exists only if we have at least two calls on top
143
  // of the bottom.
144
37.2k
  if (bt.Empty()) {
145
27.8k
    return;
146
27.8k
  }
147
9.41k
  std::string lastFilePath = bt.Top().FilePath;
148
9.41k
  bt = bt.Pop();
149
9.41k
  if (bt.Empty()) {
150
0
    return;
151
0
  }
152
153
9.41k
  bool first = true;
154
18.8k
  for (; !bt.Empty(); bt = bt.Pop()) {
155
9.41k
    cmListFileContext lfc = bt.Top();
156
9.41k
    if (lfc.Name.empty() &&
157
9.41k
        lfc.Line != cmListFileContext::DeferPlaceholderLine &&
158
9.41k
        lfc.FilePath == lastFilePath) {
159
      // An entry with no function name is frequently preceded (in the stack)
160
      // by a more specific entry.  When this happens (as verified by the
161
      // preceding entry referencing the same file path), skip the less
162
      // specific entry, as we have already printed the more specific one.
163
9.41k
      continue;
164
9.41k
    }
165
0
    if (first) {
166
0
      first = false;
167
0
      out << "Call Stack (most recent call first):\n";
168
0
    }
169
0
    lastFilePath = lfc.FilePath;
170
0
    if (topSource) {
171
0
      lfc.FilePath = cmSystemTools::RelativeIfUnder(*topSource, lfc.FilePath);
172
0
    }
173
0
    out << "  " << lfc << '\n';
174
0
  }
175
9.41k
}
176
177
} // anonymous namespace
178
179
void cmMessenger::IssueMessage(MessageType t, std::string const& text,
180
                               cmListFileBacktrace const& backtrace)
181
37.2k
{
182
37.2k
  this->DisplayMessage(t, cmDiagnostics::CMD_NONE, text, backtrace);
183
37.2k
}
184
185
void cmMessenger::IssueDiagnostic(cmDiagnosticCategory category,
186
                                  std::string const& text,
187
                                  cmStateSnapshot const& fallbackContext,
188
                                  cmDiagnosticContext const& context)
189
0
{
190
0
  cmDiagnosticAction const action = [&] {
191
0
    if (context.HasState) {
192
0
      cmDiagnosticAction const ca = context.DiagnosticState[category];
193
0
      if (ca != cmDiagnostics::Undefined) {
194
0
        return ca;
195
0
      }
196
197
      // If the context has recorded states, but not the state we want, this
198
      // implies that we had the opportunity to record the state and failed to
199
      // do so. Ask users to report this.
200
0
      std::string msg =
201
0
        cmStrCat("Stored diagnostic context did not record state for "_s,
202
0
                 cmDiagnostics::GetCategoryString(category),
203
0
                 ".  Please report this as a bug.\n"_s);
204
0
      this->IssueMessage(MessageType::LOG, msg, context.GetBacktrace());
205
0
    }
206
207
0
    return fallbackContext.GetDiagnostic(category);
208
0
  }();
209
210
0
  switch (action) {
211
0
    case cmDiagnostics::FatalError:
212
0
      cmSystemTools::SetFatalErrorOccurred();
213
0
      CM_FALLTHROUGH;
214
0
    case cmDiagnostics::SendError:
215
0
      cmSystemTools::SetErrorOccurred();
216
0
      this->DisplayMessage(MessageType::FATAL_ERROR, category, text,
217
0
                           context.GetBacktrace());
218
0
      break;
219
0
    case cmDiagnostics::Warn:
220
0
      this->DisplayMessage(MessageType::WARNING, category, text,
221
0
                           context.GetBacktrace());
222
0
      break;
223
0
    default:
224
0
      return;
225
0
  }
226
0
}
227
228
void cmMessenger::DisplayMessage(MessageType type,
229
                                 cmDiagnosticCategory category,
230
                                 std::string const& text,
231
                                 cmListFileBacktrace const& backtrace)
232
37.2k
{
233
37.2k
  std::ostringstream msg;
234
235
  // Print the message preamble.
236
37.2k
  msg << "CMake " << getMessageTypeStr(type);
237
37.2k
  if (category != cmDiagnostics::CMD_NONE) {
238
0
    msg << " (" << getDiagnosticCategoryStr(category) << ')';
239
0
  }
240
241
  // Add the immediate context.
242
37.2k
  this->PrintBacktraceTitle(msg, backtrace);
243
244
37.2k
  printMessageText(msg, text);
245
246
  // Add the rest of the context.
247
37.2k
  PrintCallStack(msg, backtrace, this->TopSource);
248
249
37.2k
  displayMessage(type, category, this->SuppressionHintMode, msg);
250
251
  // Add message to logs
252
37.2k
  this->DisplayedMessages.emplace_back(
253
37.2k
    Message{ type, category, backtrace, text });
254
255
37.2k
#ifdef CMake_ENABLE_DEBUGGER
256
37.2k
  if (DebuggerAdapter) {
257
0
    DebuggerAdapter->OnMessageOutput(type, msg.str());
258
0
  }
259
37.2k
#endif
260
37.2k
}
261
262
void cmMessenger::PrintBacktraceTitle(std::ostream& out,
263
                                      cmListFileBacktrace const& bt) const
264
37.2k
{
265
  // The title exists only if we have a call on top of the bottom.
266
37.2k
  if (bt.Empty()) {
267
27.8k
    return;
268
27.8k
  }
269
9.41k
  cmListFileContext lfc = bt.Top();
270
9.41k
  if (this->TopSource) {
271
9.41k
    lfc.FilePath =
272
9.41k
      cmSystemTools::RelativeIfUnder(*this->TopSource, lfc.FilePath);
273
9.41k
  }
274
9.41k
  out << (lfc.Line ? " at " : " in ") << lfc;
275
9.41k
}
276
277
void cmMessenger::SetTopSource(cm::optional<std::string> topSource)
278
19.9k
{
279
19.9k
  this->TopSource = std::move(topSource);
280
19.9k
}