/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 | } |