Coverage Report

Created: 2026-09-14 06:43

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
35.1k
  : Filename(std::move(jsonFile))
23
35.1k
{
24
35.1k
  cmsys::ifstream fin(this->Filename.c_str(), std::ios::in | std::ios::binary);
25
35.1k
  if (!fin) {
26
225
    this->AddError(cmStrCat("File not found: ", this->Filename));
27
225
    return;
28
225
  }
29
30
34.9k
  this->ReadJSONStream(fin, root, strictMode);
31
34.9k
}
32
33
cmJSONState::cmJSONState(std::istream& jsonIStream, Json::Value* root,
34
                         StrictMode strictMode)
35
7.03k
{
36
7.03k
  this->ReadJSONStream(jsonIStream, root, strictMode);
37
7.03k
}
38
39
void cmJSONState::AddError(std::string const& errMsg)
40
13.3k
{
41
13.3k
  this->errors.emplace_back(errMsg);
42
13.3k
}
43
44
void cmJSONState::AddErrorAtValue(std::string const& errMsg,
45
                                  Json::Value const* value)
46
413k
{
47
413k
  if (value && !value->isNull()) {
48
413k
    this->AddErrorAtOffset(errMsg, value->getOffsetStart());
49
413k
  } else {
50
837
    this->AddError(errMsg);
51
837
  }
52
413k
}
53
54
void cmJSONState::AddErrorAtOffset(std::string const& errMsg,
55
                                   std::ptrdiff_t offset)
56
429k
{
57
429k
  if (doc.empty()) {
58
0
    this->AddError(errMsg);
59
429k
  } else {
60
429k
    Location loc = LocateInDocument(offset);
61
429k
    this->errors.emplace_back(loc, errMsg);
62
429k
  }
63
429k
}
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 = cmStrCat(message, filenameName, ':', loc.line, ": ");
73
0
    }
74
0
    message = cmStrCat(message, error.GetErrorMessage(), '\n');
75
0
    if (showContext && loc.line > 0) {
76
0
      message = cmStrCat(message, GetJsonContext(loc), '\n');
77
0
    }
78
0
  }
79
0
  if (!message.empty()) {
80
0
    message.pop_back();
81
0
  }
82
0
  return message;
83
0
}
84
85
std::string cmJSONState::key()
86
138k
{
87
138k
  if (!this->parseStack.empty()) {
88
138k
    return this->parseStack.back().first;
89
138k
  }
90
0
  return "";
91
138k
}
92
93
std::string cmJSONState::key_after(std::string const& k)
94
6.28k
{
95
19.2k
  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
96
18.8k
       ++it) {
97
18.8k
    if (it->first == k && (++it) != this->parseStack.end()) {
98
5.91k
      return it->first;
99
5.91k
    }
100
18.8k
  }
101
372
  return "";
102
6.28k
}
103
104
Json::Value const* cmJSONState::value_after(std::string const& k)
105
0
{
106
0
  for (auto it = this->parseStack.begin(); it != this->parseStack.end();
107
0
       ++it) {
108
0
    if (it->first == k && (++it) != this->parseStack.end()) {
109
0
      return it->second;
110
0
    }
111
0
  }
112
0
  return nullptr;
113
0
}
114
115
void cmJSONState::push_stack(std::string const& k, Json::Value const* value)
116
8.81M
{
117
8.81M
  this->parseStack.emplace_back(k, value);
118
8.81M
}
119
120
void cmJSONState::pop_stack()
121
8.81M
{
122
8.81M
  this->parseStack.pop_back();
123
8.81M
}
124
125
void cmJSONState::ReadJSONStream(std::istream& jsonIStream, Json::Value* root,
126
                                 StrictMode strictMode)
127
41.9k
{
128
  // If there's a BOM, toss it.
129
41.9k
  cmsys::FStream::ReadBOM(jsonIStream);
130
131
  // Save the entire document.
132
41.9k
  std::streampos inBegin = jsonIStream.tellg();
133
41.9k
  this->doc = std::string(std::istreambuf_iterator<char>(jsonIStream),
134
41.9k
                          std::istreambuf_iterator<char>());
135
41.9k
  if (this->doc.empty()) {
136
42
    this->AddError("A JSON document cannot be empty");
137
42
    return;
138
42
  }
139
41.8k
  jsonIStream.seekg(inBegin);
140
141
41.8k
  Json::CharReaderBuilder builder;
142
41.8k
  if (strictMode == StrictMode::Strict) {
143
38.3k
    Json::CharReaderBuilder::strictMode(&builder.settings_);
144
38.3k
  }
145
41.8k
  std::string errMsg;
146
147
41.8k
#if JSONCPP_VERSION_HEXA >= 0x01090600
148
  // Has StructuredError
149
41.8k
  std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
150
41.8k
#  if JSON_USE_EXCEPTION
151
  // Only this CharReader-based parse() can throw, and only when built with
152
  // support for exceptions. Older jsoncpp aborts on bad inputs instead.
153
41.8k
  try {
154
41.8k
#  endif
155
41.8k
    reader->parse(this->doc.data(), this->doc.data() + this->doc.size(), root,
156
41.8k
                  &errMsg);
157
41.8k
    std::vector<Json::CharReader::StructuredError> structuredErrors =
158
41.8k
      reader->getStructuredErrors();
159
41.8k
    for (auto const& structuredError : structuredErrors) {
160
16.7k
      this->AddErrorAtOffset(structuredError.message,
161
16.7k
                             structuredError.offset_start);
162
16.7k
    }
163
41.8k
#  if JSON_USE_EXCEPTION
164
41.8k
  } catch (Json::Exception const& e) {
165
117
    if (this->Filename.empty()) {
166
45
      errMsg = cmStrCat("JSON Parse Error:\n ", e.what());
167
72
    } else {
168
72
      errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", e.what());
169
72
    }
170
117
    this->AddError(errMsg);
171
117
  }
172
41.8k
#  endif
173
#else
174
  // No StructuredError Available, Use error string from jsonCpp
175
  if (!Json::parseFromStream(builder, jsonIStream, root, &errMsg)) {
176
    if (this->Filename.empty()) {
177
      errMsg = cmStrCat("JSON Parse Error:\n ", errMsg);
178
    } else {
179
      errMsg = cmStrCat("JSON Parse Error: ", this->Filename, ":\n", errMsg);
180
    }
181
    this->AddError(errMsg);
182
  }
183
#endif
184
41.8k
}
185
186
std::string cmJSONState::GetJsonContext(Location loc)
187
0
{
188
0
  std::string line;
189
0
  std::stringstream sstream(doc);
190
0
  for (int i = 0; i < loc.line; ++i) {
191
0
    std::getline(sstream, line, '\n');
192
0
  }
193
0
  return cmStrCat(line, '\n', std::string(loc.column - 1, ' '), '^');
194
0
}
195
196
cmJSONState::Location cmJSONState::LocateInDocument(ptrdiff_t offset)
197
429k
{
198
429k
  int line = 1;
199
429k
  int col = 1;
200
429k
  char const* beginDoc = doc.data();
201
429k
  char const* last = beginDoc + offset;
202
5.56G
  for (; beginDoc != last; ++beginDoc) {
203
5.56G
    switch (*beginDoc) {
204
48.0M
      case '\r':
205
48.0M
        if (beginDoc + 1 != last && beginDoc[1] == '\n') {
206
846k
          continue; // consume CRLF as a single token.
207
846k
        }
208
47.2M
        CM_FALLTHROUGH; // CR without a following LF is same as LF
209
83.1M
      case '\n':
210
83.1M
        col = 1;
211
83.1M
        ++line;
212
83.1M
        break;
213
5.48G
      default:
214
5.48G
        ++col;
215
5.48G
        break;
216
5.56G
    }
217
5.56G
  }
218
429k
  return { line, col };
219
429k
}