Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmJSONState.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
4
#include "cmJSONState.h"
5
6
#include <istream>
7
#include <iterator>
8
#include <memory>
9
#include <sstream>
10
11
#include <cm3p/json/reader.h>
12
#include <cm3p/json/value.h>
13
#include <cm3p/json/version.h>
14
15
#include "cmsys/FStream.hxx"
16
17
#include "cmStringAlgorithms.h"
18
#include "cmSystemTools.h"
19
20
cmJSONState::cmJSONState(std::string jsonFile, Json::Value* root,
21
                         StrictMode strictMode)
22
64.4k
  : Filename(std::move(jsonFile))
23
64.4k
{
24
64.4k
  cmsys::ifstream fin(this->Filename.c_str(), std::ios::in | std::ios::binary);
25
64.4k
  if (!fin) {
26
375
    this->AddError(cmStrCat("File not found: ", this->Filename));
27
375
    return;
28
375
  }
29
30
64.1k
  this->ReadJSONStream(fin, root, strictMode);
31
64.1k
}
32
33
cmJSONState::cmJSONState(std::istream& jsonIStream, Json::Value* root,
34
                         StrictMode strictMode)
35
7.15k
{
36
7.15k
  this->ReadJSONStream(jsonIStream, root, strictMode);
37
7.15k
}
38
39
void cmJSONState::AddError(std::string const& errMsg)
40
30.0k
{
41
30.0k
  this->errors.emplace_back(errMsg);
42
30.0k
}
43
44
void cmJSONState::AddErrorAtValue(std::string const& errMsg,
45
                                  Json::Value const* value)
46
923k
{
47
923k
  if (value && !value->isNull()) {
48
908k
    this->AddErrorAtOffset(errMsg, value->getOffsetStart());
49
908k
  } else {
50
15.3k
    this->AddError(errMsg);
51
15.3k
  }
52
923k
}
53
54
void cmJSONState::AddErrorAtOffset(std::string const& errMsg,
55
                                   std::ptrdiff_t offset)
56
928k
{
57
928k
  if (doc.empty()) {
58
0
    this->AddError(errMsg);
59
928k
  } else {
60
928k
    Location loc = LocateInDocument(offset);
61
928k
    this->errors.emplace_back(loc, errMsg);
62
928k
  }
63
928k
}
64
65
std::string cmJSONState::GetErrorMessage(bool showContext)
66
0
{
67
0
  std::string message;
68
0
  std::string filenameName = cmSystemTools::GetFilenameName(this->Filename);
69
0
  for (auto const& error : this->errors) {
70
0
    Location loc = error.GetLocation();
71
0
    if (!filenameName.empty() && loc.line > 0) {
72
0
      message =
73
0
        cmStrCat(std::move(message), filenameName, ':', loc.line, ": ");
74
0
    }
75
0
    message = cmStrCat(std::move(message), error.GetErrorMessage(), '\n');
76
0
    if (showContext && loc.line > 0) {
77
0
      message = cmStrCat(std::move(message), GetJsonContext(loc), '\n');
78
0
    }
79
0
  }
80
0
  if (!message.empty()) {
81
0
    message.pop_back();
82
0
  }
83
0
  return message;
84
0
}
85
86
std::string cmJSONState::key()
87
334k
{
88
334k
  if (!this->parseStack.empty()) {
89
334k
    return this->parseStack.back().first;
90
334k
  }
91
0
  return "";
92
334k
}
93
94
std::string cmJSONState::key_after(std::string const& k)
95
51.3k
{
96
197k
  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
97
174k
       ++it) {
98
174k
    if (it->first == k) {
99
29.5k
      auto next = std::next(it);
100
29.5k
      if (next != this->parseStack.end()) {
101
28.6k
        return next->first;
102
28.6k
      }
103
29.5k
    }
104
174k
  }
105
22.6k
  return "";
106
51.3k
}
107
108
Json::Value const* cmJSONState::value_after(std::string const& k)
109
0
{
110
0
  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
111
0
       ++it) {
112
0
    if (it->first == k) {
113
0
      auto next = std::next(it);
114
0
      if (next != this->parseStack.end()) {
115
0
        return next->second;
116
0
      }
117
0
    }
118
0
  }
119
0
  return nullptr;
120
0
}
121
122
void cmJSONState::push_stack(std::string const& k, Json::Value const* value)
123
11.5M
{
124
11.5M
  this->parseStack.emplace_back(k, value);
125
11.5M
}
126
127
void cmJSONState::pop_stack()
128
11.5M
{
129
11.5M
  this->parseStack.pop_back();
130
11.5M
}
131
132
void cmJSONState::ReadJSONStream(std::istream& jsonIStream, Json::Value* root,
133
                                 StrictMode strictMode)
134
71.2k
{
135
  // If there's a BOM, toss it.
136
71.2k
  cmsys::FStream::ReadBOM(jsonIStream);
137
138
  // Save the entire document.
139
71.2k
  std::streampos inBegin = jsonIStream.tellg();
140
71.2k
  this->doc = std::string(std::istreambuf_iterator<char>(jsonIStream),
141
71.2k
                          std::istreambuf_iterator<char>());
142
71.2k
  if (this->doc.empty()) {
143
97
    this->AddError("A JSON document cannot be empty");
144
97
    return;
145
97
  }
146
71.1k
  jsonIStream.seekg(inBegin);
147
148
71.1k
  Json::CharReaderBuilder builder;
149
71.1k
  if (strictMode == StrictMode::Strict) {
150
49.3k
    Json::CharReaderBuilder::strictMode(&builder.settings_);
151
49.3k
  }
152
71.1k
  std::string errMsg;
153
154
71.1k
#if JSONCPP_VERSION_HEXA >= 0x01090600
155
  // Has StructuredError
156
71.1k
  std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
157
71.1k
#  if JSON_USE_EXCEPTION
158
  // Only this CharReader-based parse() can throw, and only when built with
159
  // support for exceptions. Older jsoncpp aborts on bad inputs instead.
160
71.1k
  try {
161
71.1k
#  endif
162
71.1k
    reader->parse(this->doc.data(), this->doc.data() + this->doc.size(), root,
163
71.1k
                  &errMsg);
164
71.1k
    std::vector<Json::CharReader::StructuredError> structuredErrors =
165
71.1k
      reader->getStructuredErrors();
166
71.1k
    for (auto const& structuredError : structuredErrors) {
167
20.8k
      this->AddErrorAtOffset(structuredError.message,
168
20.8k
                             structuredError.offset_start);
169
20.8k
    }
170
71.1k
#  if JSON_USE_EXCEPTION
171
71.1k
  } catch (Json::Exception const& e) {
172
165
    if (this->Filename.empty()) {
173
51
      errMsg = cmStrCat("JSON Parse Error:\n ", e.what());
174
114
    } else {
175
114
      errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", e.what());
176
114
    }
177
165
    this->AddError(errMsg);
178
165
  }
179
71.1k
#  endif
180
#else
181
  // No StructuredError Available, Use error string from jsonCpp
182
  if (!Json::parseFromStream(builder, jsonIStream, root, &errMsg)) {
183
    if (this->Filename.empty()) {
184
      errMsg = cmStrCat("JSON Parse Error:\n ", std::move(errMsg));
185
    } else {
186
      errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n",
187
                        std::move(errMsg));
188
    }
189
    this->AddError(errMsg);
190
  }
191
#endif
192
71.1k
}
193
194
std::string cmJSONState::GetJsonContext(Location loc)
195
0
{
196
0
  std::string line;
197
0
  std::stringstream sstream(doc);
198
0
  for (int i = 0; i < loc.line; ++i) {
199
0
    std::getline(sstream, line, '\n');
200
0
  }
201
0
  return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^');
202
0
}
203
204
cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset)
205
928k
{
206
928k
  int line = 1;
207
928k
  int col = 1;
208
928k
  char const* beginDoc = doc.data();
209
928k
  char const* last = beginDoc + offset;
210
8.78G
  for (; beginDoc != last; ++beginDoc) {
211
8.77G
    switch (*beginDoc) {
212
11.7M
      case '\r':
213
11.7M
        if (beginDoc + 1 != last && beginDoc[1] == '\n') {
214
1.20M
          continue; // consume CRLF as a single token.
215
1.20M
        }
216
10.5M
        CM_FALLTHROUGH; // CR without a following LF is same as LF
217
74.0M
      case '\n':
218
74.0M
        col = 1;
219
74.0M
        ++line;
220
74.0M
        break;
221
8.70G
      default:
222
8.70G
        ++col;
223
8.70G
        break;
224
8.77G
    }
225
8.77G
  }
226
928k
  return { line, col };
227
928k
}