Coverage Report

Created: 2026-04-29 07:01

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