Coverage Report

Created: 2026-06-15 07:03

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