/src/solidity/libsolidity/interface/StandardCompiler.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * @author Alex Beregszaszi |
20 | | * @date 2016 |
21 | | * Standard JSON compiler interface. |
22 | | */ |
23 | | |
24 | | #include <libsolidity/interface/StandardCompiler.h> |
25 | | #include <libsolidity/interface/ImportRemapper.h> |
26 | | |
27 | | #include <libsolidity/ast/ASTJsonExporter.h> |
28 | | #include <libyul/YulStack.h> |
29 | | #include <libyul/Exceptions.h> |
30 | | #include <libyul/optimiser/Suite.h> |
31 | | |
32 | | #include <libevmasm/Disassemble.h> |
33 | | |
34 | | #include <libsmtutil/Exceptions.h> |
35 | | |
36 | | #include <liblangutil/SourceReferenceFormatter.h> |
37 | | |
38 | | #include <libsolutil/JSON.h> |
39 | | #include <libsolutil/Keccak256.h> |
40 | | #include <libsolutil/CommonData.h> |
41 | | |
42 | | #include <boost/algorithm/string/predicate.hpp> |
43 | | |
44 | | #include <algorithm> |
45 | | #include <optional> |
46 | | |
47 | | using namespace std; |
48 | | using namespace solidity; |
49 | | using namespace solidity::yul; |
50 | | using namespace solidity::frontend; |
51 | | using namespace solidity::langutil; |
52 | | |
53 | | namespace |
54 | | { |
55 | | |
56 | | Json::Value formatError( |
57 | | Error::Severity _severity, |
58 | | string const& _type, |
59 | | string const& _component, |
60 | | string const& _message, |
61 | | string const& _formattedMessage = "", |
62 | | Json::Value const& _sourceLocation = Json::Value(), |
63 | | Json::Value const& _secondarySourceLocation = Json::Value() |
64 | | ) |
65 | 0 | { |
66 | 0 | Json::Value error{Json::objectValue}; |
67 | 0 | error["type"] = _type; |
68 | 0 | error["component"] = _component; |
69 | 0 | error["severity"] = Error::formatErrorSeverityLowercase(_severity); |
70 | 0 | error["message"] = _message; |
71 | 0 | error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message; |
72 | 0 | if (_sourceLocation.isObject()) |
73 | 0 | error["sourceLocation"] = _sourceLocation; |
74 | 0 | if (_secondarySourceLocation.isArray()) |
75 | 0 | error["secondarySourceLocations"] = _secondarySourceLocation; |
76 | 0 | return error; |
77 | 0 | } |
78 | | |
79 | | Json::Value formatFatalError(string const& _type, string const& _message) |
80 | 0 | { |
81 | 0 | Json::Value output{Json::objectValue}; |
82 | 0 | output["errors"] = Json::arrayValue; |
83 | 0 | output["errors"].append(formatError(Error::Severity::Error, _type, "general", _message)); |
84 | 0 | return output; |
85 | 0 | } |
86 | | |
87 | | Json::Value formatSourceLocation(SourceLocation const* location) |
88 | 0 | { |
89 | 0 | if (!location || !location->sourceName) |
90 | 0 | return Json::nullValue; |
91 | | |
92 | 0 | Json::Value sourceLocation{Json::objectValue}; |
93 | 0 | sourceLocation["file"] = *location->sourceName; |
94 | 0 | sourceLocation["start"] = location->start; |
95 | 0 | sourceLocation["end"] = location->end; |
96 | 0 | return sourceLocation; |
97 | 0 | } |
98 | | |
99 | | Json::Value formatSecondarySourceLocation(SecondarySourceLocation const* _secondaryLocation) |
100 | 0 | { |
101 | 0 | if (!_secondaryLocation) |
102 | 0 | return Json::nullValue; |
103 | | |
104 | 0 | Json::Value secondarySourceLocation{Json::arrayValue}; |
105 | 0 | for (auto const& location: _secondaryLocation->infos) |
106 | 0 | { |
107 | 0 | Json::Value msg = formatSourceLocation(&location.second); |
108 | 0 | msg["message"] = location.first; |
109 | 0 | secondarySourceLocation.append(msg); |
110 | 0 | } |
111 | 0 | return secondarySourceLocation; |
112 | 0 | } |
113 | | |
114 | | Json::Value formatErrorWithException( |
115 | | CharStreamProvider const& _charStreamProvider, |
116 | | util::Exception const& _exception, |
117 | | Error::Severity _severity, |
118 | | string const& _type, |
119 | | string const& _component, |
120 | | string const& _message, |
121 | | optional<ErrorId> _errorId = nullopt |
122 | | ) |
123 | 0 | { |
124 | 0 | string message; |
125 | | // TODO: consider enabling color |
126 | 0 | string formattedMessage = SourceReferenceFormatter::formatExceptionInformation( |
127 | 0 | _exception, |
128 | 0 | _type, |
129 | 0 | _charStreamProvider |
130 | 0 | ); |
131 | |
|
132 | 0 | if (string const* description = _exception.comment()) |
133 | 0 | message = ((_message.length() > 0) ? (_message + ":") : "") + *description; |
134 | 0 | else |
135 | 0 | message = _message; |
136 | |
|
137 | 0 | Json::Value error = formatError( |
138 | 0 | _severity, |
139 | 0 | _type, |
140 | 0 | _component, |
141 | 0 | message, |
142 | 0 | formattedMessage, |
143 | 0 | formatSourceLocation(boost::get_error_info<errinfo_sourceLocation>(_exception)), |
144 | 0 | formatSecondarySourceLocation(boost::get_error_info<errinfo_secondarySourceLocation>(_exception)) |
145 | 0 | ); |
146 | |
|
147 | 0 | if (_errorId) |
148 | 0 | error["errorCode"] = to_string(_errorId.value().error); |
149 | |
|
150 | 0 | return error; |
151 | 0 | } |
152 | | |
153 | | map<string, set<string>> requestedContractNames(Json::Value const& _outputSelection) |
154 | 0 | { |
155 | 0 | map<string, set<string>> contracts; |
156 | 0 | for (auto const& sourceName: _outputSelection.getMemberNames()) |
157 | 0 | { |
158 | 0 | string key = (sourceName == "*") ? "" : sourceName; |
159 | 0 | for (auto const& contractName: _outputSelection[sourceName].getMemberNames()) |
160 | 0 | { |
161 | 0 | string value = (contractName == "*") ? "" : contractName; |
162 | 0 | contracts[key].insert(value); |
163 | 0 | } |
164 | 0 | } |
165 | 0 | return contracts; |
166 | 0 | } |
167 | | |
168 | | /// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content. |
169 | | bool hashMatchesContent(string const& _hash, string const& _content) |
170 | 0 | { |
171 | 0 | try |
172 | 0 | { |
173 | 0 | return util::h256(_hash) == util::keccak256(_content); |
174 | 0 | } |
175 | 0 | catch (util::BadHexCharacter const&) |
176 | 0 | { |
177 | 0 | return false; |
178 | 0 | } |
179 | 0 | } |
180 | | |
181 | | bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesExperimental) |
182 | 0 | { |
183 | 0 | static set<string> experimental{"ir", "irOptimized", "wast", "ewasm", "ewasm.wast"}; |
184 | 0 | for (auto const& selectedArtifactJson: _outputSelection) |
185 | 0 | { |
186 | 0 | string const& selectedArtifact = selectedArtifactJson.asString(); |
187 | 0 | if ( |
188 | 0 | _artifact == selectedArtifact || |
189 | 0 | boost::algorithm::starts_with(_artifact, selectedArtifact + ".") |
190 | 0 | ) |
191 | 0 | return true; |
192 | 0 | else if (selectedArtifact == "*") |
193 | 0 | { |
194 | | // "ir", "irOptimized", "wast" and "ewasm.wast" can only be matched by "*" if activated. |
195 | 0 | if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental) |
196 | 0 | return true; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | return false; |
200 | 0 | } |
201 | | |
202 | | /// |
203 | | /// @a _outputSelection is a JSON object containing a two-level hashmap, where the first level is the filename, |
204 | | /// the second level is the contract name and the value is an array of artifact names to be requested for that contract. |
205 | | /// @a _file is the current file |
206 | | /// @a _contract is the current contract |
207 | | /// @a _artifact is the current artifact name |
208 | | /// |
209 | | /// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract. |
210 | | /// |
211 | | /// In @a _outputSelection the use of '*' as a wildcard is permitted. |
212 | | /// |
213 | | /// @TODO optimise this. Perhaps flatten the structure upfront. |
214 | | /// |
215 | | bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesExperimental) |
216 | 0 | { |
217 | 0 | if (!_outputSelection.isObject()) |
218 | 0 | return false; |
219 | | |
220 | 0 | for (auto const& file: { _file, string("*") }) |
221 | 0 | if (_outputSelection.isMember(file) && _outputSelection[file].isObject()) |
222 | 0 | { |
223 | | /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise |
224 | | /// for Contract-level targets try both contract name and wildcard |
225 | 0 | vector<string> contracts{ _contract }; |
226 | 0 | if (!_contract.empty()) |
227 | 0 | contracts.emplace_back("*"); |
228 | 0 | for (auto const& contract: contracts) |
229 | 0 | if ( |
230 | 0 | _outputSelection[file].isMember(contract) && |
231 | 0 | _outputSelection[file][contract].isArray() && |
232 | 0 | isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesExperimental) |
233 | 0 | ) |
234 | 0 | return true; |
235 | 0 | } |
236 | | |
237 | 0 | return false; |
238 | 0 | } |
239 | | |
240 | | bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts, bool _wildcardMatchesExperimental) |
241 | 0 | { |
242 | 0 | for (auto const& artifact: _artifacts) |
243 | 0 | if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesExperimental)) |
244 | 0 | return true; |
245 | 0 | return false; |
246 | 0 | } |
247 | | |
248 | | /// @returns all artifact names of the EVM object, either for creation or deploy time. |
249 | | vector<string> evmObjectComponents(string const& _objectKind) |
250 | 0 | { |
251 | 0 | solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", ""); |
252 | 0 | vector<string> components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences"}; |
253 | 0 | if (_objectKind == "deployedBytecode") |
254 | 0 | components.push_back(".immutableReferences"); |
255 | 0 | return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; }); |
256 | 0 | } |
257 | | |
258 | | /// @returns true if any binary was requested, i.e. we actually have to perform compilation. |
259 | | bool isBinaryRequested(Json::Value const& _outputSelection) |
260 | 0 | { |
261 | 0 | if (!_outputSelection.isObject()) |
262 | 0 | return false; |
263 | | |
264 | | // This does not include "evm.methodIdentifiers" on purpose! |
265 | 0 | static vector<string> const outputsThatRequireBinaries = vector<string>{ |
266 | 0 | "*", |
267 | 0 | "ir", "irOptimized", |
268 | 0 | "wast", "wasm", "ewasm.wast", "ewasm.wasm", |
269 | 0 | "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" |
270 | 0 | } + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode"); |
271 | |
|
272 | 0 | for (auto const& fileRequests: _outputSelection) |
273 | 0 | for (auto const& requests: fileRequests) |
274 | 0 | for (auto const& output: outputsThatRequireBinaries) |
275 | 0 | if (isArtifactRequested(requests, output, false)) |
276 | 0 | return true; |
277 | 0 | return false; |
278 | 0 | } |
279 | | |
280 | | /// @returns true if EVM bytecode was requested, i.e. we have to run the old code generator. |
281 | | bool isEvmBytecodeRequested(Json::Value const& _outputSelection) |
282 | 0 | { |
283 | 0 | if (!_outputSelection.isObject()) |
284 | 0 | return false; |
285 | | |
286 | 0 | static vector<string> const outputsThatRequireEvmBinaries = vector<string>{ |
287 | 0 | "*", |
288 | 0 | "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" |
289 | 0 | } + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode"); |
290 | |
|
291 | 0 | for (auto const& fileRequests: _outputSelection) |
292 | 0 | for (auto const& requests: fileRequests) |
293 | 0 | for (auto const& output: outputsThatRequireEvmBinaries) |
294 | 0 | if (isArtifactRequested(requests, output, false)) |
295 | 0 | return true; |
296 | 0 | return false; |
297 | 0 | } |
298 | | |
299 | | /// @returns true if any Ewasm code was requested. Note that as an exception, '*' does not |
300 | | /// yet match "ewasm.wast" or "ewasm" |
301 | | bool isEwasmRequested(Json::Value const& _outputSelection) |
302 | 0 | { |
303 | 0 | if (!_outputSelection.isObject()) |
304 | 0 | return false; |
305 | | |
306 | 0 | for (auto const& fileRequests: _outputSelection) |
307 | 0 | for (auto const& requests: fileRequests) |
308 | 0 | for (auto const& request: requests) |
309 | 0 | if (request == "ewasm" || request == "ewasm.wast") |
310 | 0 | return true; |
311 | | |
312 | 0 | return false; |
313 | 0 | } |
314 | | |
315 | | /// @returns true if any Yul IR was requested. Note that as an exception, '*' does not |
316 | | /// yet match "ir" or "irOptimized" |
317 | | bool isIRRequested(Json::Value const& _outputSelection) |
318 | 0 | { |
319 | 0 | if (isEwasmRequested(_outputSelection)) |
320 | 0 | return true; |
321 | | |
322 | 0 | if (!_outputSelection.isObject()) |
323 | 0 | return false; |
324 | | |
325 | 0 | for (auto const& fileRequests: _outputSelection) |
326 | 0 | for (auto const& requests: fileRequests) |
327 | 0 | for (auto const& request: requests) |
328 | 0 | if (request == "ir" || request == "irOptimized") |
329 | 0 | return true; |
330 | | |
331 | 0 | return false; |
332 | 0 | } |
333 | | |
334 | | Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences) |
335 | 0 | { |
336 | 0 | Json::Value ret{Json::objectValue}; |
337 | |
|
338 | 0 | for (auto const& ref: linkReferences) |
339 | 0 | { |
340 | 0 | string const& fullname = ref.second; |
341 | | |
342 | | // If the link reference does not contain a colon, assume that the file name is missing and |
343 | | // the whole string represents the library name. |
344 | 0 | size_t colon = fullname.rfind(':'); |
345 | 0 | string file = (colon != string::npos ? fullname.substr(0, colon) : ""); |
346 | 0 | string name = (colon != string::npos ? fullname.substr(colon + 1) : fullname); |
347 | |
|
348 | 0 | Json::Value fileObject = ret.get(file, Json::objectValue); |
349 | 0 | Json::Value libraryArray = fileObject.get(name, Json::arrayValue); |
350 | |
|
351 | 0 | Json::Value entry{Json::objectValue}; |
352 | 0 | entry["start"] = Json::UInt(ref.first); |
353 | 0 | entry["length"] = 20; |
354 | |
|
355 | 0 | libraryArray.append(entry); |
356 | 0 | fileObject[name] = libraryArray; |
357 | 0 | ret[file] = fileObject; |
358 | 0 | } |
359 | |
|
360 | 0 | return ret; |
361 | 0 | } |
362 | | |
363 | | Json::Value formatImmutableReferences(map<u256, pair<string, vector<size_t>>> const& _immutableReferences) |
364 | 0 | { |
365 | 0 | Json::Value ret{Json::objectValue}; |
366 | |
|
367 | 0 | for (auto const& immutableReference: _immutableReferences) |
368 | 0 | { |
369 | 0 | auto const& [identifier, byteOffsets] = immutableReference.second; |
370 | 0 | Json::Value array(Json::arrayValue); |
371 | 0 | for (size_t byteOffset: byteOffsets) |
372 | 0 | { |
373 | 0 | Json::Value byteRange{Json::objectValue}; |
374 | 0 | byteRange["start"] = Json::UInt(byteOffset); |
375 | 0 | byteRange["length"] = Json::UInt(32); // immutable references are currently always 32 bytes wide |
376 | 0 | array.append(byteRange); |
377 | 0 | } |
378 | 0 | ret[identifier] = array; |
379 | 0 | } |
380 | |
|
381 | 0 | return ret; |
382 | 0 | } |
383 | | |
384 | | Json::Value collectEVMObject( |
385 | | evmasm::LinkerObject const& _object, |
386 | | string const* _sourceMap, |
387 | | Json::Value _generatedSources, |
388 | | bool _runtimeObject, |
389 | | function<bool(string)> const& _artifactRequested |
390 | | ) |
391 | 0 | { |
392 | 0 | Json::Value output{Json::objectValue}; |
393 | 0 | if (_artifactRequested("object")) |
394 | 0 | output["object"] = _object.toHex(); |
395 | 0 | if (_artifactRequested("opcodes")) |
396 | 0 | output["opcodes"] = evmasm::disassemble(_object.bytecode); |
397 | 0 | if (_artifactRequested("sourceMap")) |
398 | 0 | output["sourceMap"] = _sourceMap ? *_sourceMap : ""; |
399 | 0 | if (_artifactRequested("functionDebugData")) |
400 | 0 | output["functionDebugData"] = StandardCompiler::formatFunctionDebugData(_object.functionDebugData); |
401 | 0 | if (_artifactRequested("linkReferences")) |
402 | 0 | output["linkReferences"] = formatLinkReferences(_object.linkReferences); |
403 | 0 | if (_runtimeObject && _artifactRequested("immutableReferences")) |
404 | 0 | output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences); |
405 | 0 | if (_artifactRequested("generatedSources")) |
406 | 0 | output["generatedSources"] = move(_generatedSources); |
407 | 0 | return output; |
408 | 0 | } |
409 | | |
410 | | std::optional<Json::Value> checkKeys(Json::Value const& _input, set<string> const& _keys, string const& _name) |
411 | 0 | { |
412 | 0 | if (!!_input && !_input.isObject()) |
413 | 0 | return formatFatalError("JSONError", "\"" + _name + "\" must be an object"); |
414 | | |
415 | 0 | for (auto const& member: _input.getMemberNames()) |
416 | 0 | if (!_keys.count(member)) |
417 | 0 | return formatFatalError("JSONError", "Unknown key \"" + member + "\""); |
418 | | |
419 | 0 | return std::nullopt; |
420 | 0 | } |
421 | | |
422 | | std::optional<Json::Value> checkRootKeys(Json::Value const& _input) |
423 | 0 | { |
424 | 0 | static set<string> keys{"auxiliaryInput", "language", "settings", "sources"}; |
425 | 0 | return checkKeys(_input, keys, "root"); |
426 | 0 | } |
427 | | |
428 | | std::optional<Json::Value> checkSourceKeys(Json::Value const& _input, string const& _name) |
429 | 0 | { |
430 | 0 | static set<string> keys{"content", "keccak256", "urls"}; |
431 | 0 | return checkKeys(_input, keys, "sources." + _name); |
432 | 0 | } |
433 | | |
434 | | std::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input) |
435 | 0 | { |
436 | 0 | static set<string> keys{"smtlib2responses"}; |
437 | 0 | return checkKeys(_input, keys, "auxiliaryInput"); |
438 | 0 | } |
439 | | |
440 | | std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input) |
441 | 0 | { |
442 | 0 | static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; |
443 | 0 | return checkKeys(_input, keys, "settings"); |
444 | 0 | } |
445 | | |
446 | | std::optional<Json::Value> checkModelCheckerSettingsKeys(Json::Value const& _input) |
447 | 0 | { |
448 | 0 | static set<string> keys{"contracts", "divModNoSlacks", "engine", "invariants", "showUnproved", "solvers", "targets", "timeout"}; |
449 | 0 | return checkKeys(_input, keys, "modelChecker"); |
450 | 0 | } |
451 | | |
452 | | std::optional<Json::Value> checkOptimizerKeys(Json::Value const& _input) |
453 | 0 | { |
454 | 0 | static set<string> keys{"details", "enabled", "runs"}; |
455 | 0 | return checkKeys(_input, keys, "settings.optimizer"); |
456 | 0 | } |
457 | | |
458 | | std::optional<Json::Value> checkOptimizerDetailsKeys(Json::Value const& _input) |
459 | 0 | { |
460 | 0 | static set<string> keys{"peephole", "inliner", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails"}; |
461 | 0 | return checkKeys(_input, keys, "settings.optimizer.details"); |
462 | 0 | } |
463 | | |
464 | | std::optional<Json::Value> checkOptimizerDetail(Json::Value const& _details, std::string const& _name, bool& _setting) |
465 | 0 | { |
466 | 0 | if (_details.isMember(_name)) |
467 | 0 | { |
468 | 0 | if (!_details[_name].isBool()) |
469 | 0 | return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be Boolean"); |
470 | 0 | _setting = _details[_name].asBool(); |
471 | 0 | } |
472 | 0 | return {}; |
473 | 0 | } |
474 | | |
475 | | std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting) |
476 | 0 | { |
477 | 0 | if (_details.isMember(_name)) |
478 | 0 | { |
479 | 0 | if (_details[_name].isString()) |
480 | 0 | { |
481 | 0 | try |
482 | 0 | { |
483 | 0 | yul::OptimiserSuite::validateSequence(_details[_name].asString()); |
484 | 0 | } |
485 | 0 | catch (yul::OptimizerException const& _exception) |
486 | 0 | { |
487 | 0 | return formatFatalError( |
488 | 0 | "JSONError", |
489 | 0 | "Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what() |
490 | 0 | ); |
491 | 0 | } |
492 | | |
493 | 0 | _setting = _details[_name].asString(); |
494 | 0 | } |
495 | 0 | else |
496 | 0 | return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string"); |
497 | |
|
498 | 0 | } |
499 | 0 | return {}; |
500 | 0 | } |
501 | | |
502 | | std::optional<Json::Value> checkMetadataKeys(Json::Value const& _input) |
503 | 0 | { |
504 | 0 | if (_input.isObject()) |
505 | 0 | { |
506 | 0 | if (_input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) |
507 | 0 | return formatFatalError("JSONError", "\"settings.metadata.useLiteralContent\" must be Boolean"); |
508 | | |
509 | 0 | static set<string> hashes{"ipfs", "bzzr1", "none"}; |
510 | 0 | if (_input.isMember("bytecodeHash") && !hashes.count(_input["bytecodeHash"].asString())) |
511 | 0 | return formatFatalError("JSONError", "\"settings.metadata.bytecodeHash\" must be \"ipfs\", \"bzzr1\" or \"none\""); |
512 | 0 | } |
513 | 0 | static set<string> keys{"useLiteralContent", "bytecodeHash"}; |
514 | 0 | return checkKeys(_input, keys, "settings.metadata"); |
515 | 0 | } |
516 | | |
517 | | std::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSelection) |
518 | 0 | { |
519 | 0 | if (!!_outputSelection && !_outputSelection.isObject()) |
520 | 0 | return formatFatalError("JSONError", "\"settings.outputSelection\" must be an object"); |
521 | | |
522 | 0 | for (auto const& sourceName: _outputSelection.getMemberNames()) |
523 | 0 | { |
524 | 0 | auto const& sourceVal = _outputSelection[sourceName]; |
525 | |
|
526 | 0 | if (!sourceVal.isObject()) |
527 | 0 | return formatFatalError( |
528 | 0 | "JSONError", |
529 | 0 | "\"settings.outputSelection." + sourceName + "\" must be an object" |
530 | 0 | ); |
531 | | |
532 | 0 | for (auto const& contractName: sourceVal.getMemberNames()) |
533 | 0 | { |
534 | 0 | auto const& contractVal = sourceVal[contractName]; |
535 | |
|
536 | 0 | if (!contractVal.isArray()) |
537 | 0 | return formatFatalError( |
538 | 0 | "JSONError", |
539 | 0 | "\"settings.outputSelection." + |
540 | 0 | sourceName + |
541 | 0 | "." + |
542 | 0 | contractName + |
543 | 0 | "\" must be a string array" |
544 | 0 | ); |
545 | | |
546 | 0 | for (auto const& output: contractVal) |
547 | 0 | if (!output.isString()) |
548 | 0 | return formatFatalError( |
549 | 0 | "JSONError", |
550 | 0 | "\"settings.outputSelection." + |
551 | 0 | sourceName + |
552 | 0 | "." + |
553 | 0 | contractName + |
554 | 0 | "\" must be a string array" |
555 | 0 | ); |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | 0 | return std::nullopt; |
560 | 0 | } |
561 | | |
562 | | /// Validates the optimizer settings and returns them in a parsed object. |
563 | | /// On error returns the json-formatted error message. |
564 | | std::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput) |
565 | 0 | { |
566 | 0 | if (auto result = checkOptimizerKeys(_jsonInput)) |
567 | 0 | return *result; |
568 | | |
569 | 0 | OptimiserSettings settings = OptimiserSettings::minimal(); |
570 | |
|
571 | 0 | if (_jsonInput.isMember("enabled")) |
572 | 0 | { |
573 | 0 | if (!_jsonInput["enabled"].isBool()) |
574 | 0 | return formatFatalError("JSONError", "The \"enabled\" setting must be a Boolean."); |
575 | | |
576 | 0 | if (_jsonInput["enabled"].asBool()) |
577 | 0 | settings = OptimiserSettings::standard(); |
578 | 0 | } |
579 | | |
580 | 0 | if (_jsonInput.isMember("runs")) |
581 | 0 | { |
582 | 0 | if (!_jsonInput["runs"].isUInt()) |
583 | 0 | return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); |
584 | 0 | settings.expectedExecutionsPerDeployment = _jsonInput["runs"].asUInt(); |
585 | 0 | } |
586 | | |
587 | 0 | if (_jsonInput.isMember("details")) |
588 | 0 | { |
589 | 0 | Json::Value const& details = _jsonInput["details"]; |
590 | 0 | if (auto result = checkOptimizerDetailsKeys(details)) |
591 | 0 | return *result; |
592 | | |
593 | 0 | if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) |
594 | 0 | return *error; |
595 | 0 | if (auto error = checkOptimizerDetail(details, "inliner", settings.runInliner)) |
596 | 0 | return *error; |
597 | 0 | if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) |
598 | 0 | return *error; |
599 | 0 | if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) |
600 | 0 | return *error; |
601 | 0 | if (auto error = checkOptimizerDetail(details, "deduplicate", settings.runDeduplicate)) |
602 | 0 | return *error; |
603 | 0 | if (auto error = checkOptimizerDetail(details, "cse", settings.runCSE)) |
604 | 0 | return *error; |
605 | 0 | if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) |
606 | 0 | return *error; |
607 | 0 | if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) |
608 | 0 | return *error; |
609 | 0 | settings.optimizeStackAllocation = settings.runYulOptimiser; |
610 | 0 | if (details.isMember("yulDetails")) |
611 | 0 | { |
612 | 0 | if (!settings.runYulOptimiser) |
613 | 0 | return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled."); |
614 | | |
615 | 0 | if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails")) |
616 | 0 | return *result; |
617 | 0 | if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) |
618 | 0 | return *error; |
619 | 0 | if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps)) |
620 | 0 | return *error; |
621 | 0 | } |
622 | 0 | } |
623 | 0 | return { std::move(settings) }; |
624 | 0 | } |
625 | | |
626 | | } |
627 | | |
628 | | |
629 | | std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler::parseInput(Json::Value const& _input) |
630 | 0 | { |
631 | 0 | InputsAndSettings ret; |
632 | |
|
633 | 0 | if (!_input.isObject()) |
634 | 0 | return formatFatalError("JSONError", "Input is not a JSON object."); |
635 | | |
636 | 0 | if (auto result = checkRootKeys(_input)) |
637 | 0 | return *result; |
638 | | |
639 | 0 | ret.language = _input["language"].asString(); |
640 | |
|
641 | 0 | Json::Value const& sources = _input["sources"]; |
642 | |
|
643 | 0 | if (!sources.isObject() && !sources.isNull()) |
644 | 0 | return formatFatalError("JSONError", "\"sources\" is not a JSON object."); |
645 | | |
646 | 0 | if (sources.empty()) |
647 | 0 | return formatFatalError("JSONError", "No input sources specified."); |
648 | | |
649 | 0 | ret.errors = Json::arrayValue; |
650 | |
|
651 | 0 | for (auto const& sourceName: sources.getMemberNames()) |
652 | 0 | { |
653 | 0 | string hash; |
654 | |
|
655 | 0 | if (auto result = checkSourceKeys(sources[sourceName], sourceName)) |
656 | 0 | return *result; |
657 | | |
658 | 0 | if (sources[sourceName]["keccak256"].isString()) |
659 | 0 | hash = sources[sourceName]["keccak256"].asString(); |
660 | |
|
661 | 0 | if (sources[sourceName]["content"].isString()) |
662 | 0 | { |
663 | 0 | string content = sources[sourceName]["content"].asString(); |
664 | 0 | if (!hash.empty() && !hashMatchesContent(hash, content)) |
665 | 0 | ret.errors.append(formatError( |
666 | 0 | Error::Severity::Error, |
667 | 0 | "IOError", |
668 | 0 | "general", |
669 | 0 | "Mismatch between content and supplied hash for \"" + sourceName + "\"" |
670 | 0 | )); |
671 | 0 | else |
672 | 0 | ret.sources[sourceName] = content; |
673 | 0 | } |
674 | 0 | else if (sources[sourceName]["urls"].isArray()) |
675 | 0 | { |
676 | 0 | if (!m_readFile) |
677 | 0 | return formatFatalError("JSONError", "No import callback supplied, but URL is requested."); |
678 | | |
679 | 0 | bool found = false; |
680 | 0 | vector<string> failures; |
681 | |
|
682 | 0 | for (auto const& url: sources[sourceName]["urls"]) |
683 | 0 | { |
684 | 0 | if (!url.isString()) |
685 | 0 | return formatFatalError("JSONError", "URL must be a string."); |
686 | 0 | ReadCallback::Result result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), url.asString()); |
687 | 0 | if (result.success) |
688 | 0 | { |
689 | 0 | if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) |
690 | 0 | ret.errors.append(formatError( |
691 | 0 | Error::Severity::Error, |
692 | 0 | "IOError", |
693 | 0 | "general", |
694 | 0 | "Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.asString() + "\"" |
695 | 0 | )); |
696 | 0 | else |
697 | 0 | { |
698 | 0 | ret.sources[sourceName] = result.responseOrErrorMessage; |
699 | 0 | found = true; |
700 | 0 | break; |
701 | 0 | } |
702 | 0 | } |
703 | 0 | else |
704 | 0 | failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.responseOrErrorMessage); |
705 | 0 | } |
706 | | |
707 | 0 | for (auto const& failure: failures) |
708 | 0 | { |
709 | | /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. |
710 | 0 | ret.errors.append(formatError( |
711 | 0 | found ? Error::Severity::Warning : Error::Severity::Error, |
712 | 0 | "IOError", |
713 | 0 | "general", |
714 | 0 | failure |
715 | 0 | )); |
716 | 0 | } |
717 | 0 | } |
718 | 0 | else |
719 | 0 | return formatFatalError("JSONError", "Invalid input source specified."); |
720 | 0 | } |
721 | | |
722 | 0 | Json::Value const& auxInputs = _input["auxiliaryInput"]; |
723 | |
|
724 | 0 | if (auto result = checkAuxiliaryInputKeys(auxInputs)) |
725 | 0 | return *result; |
726 | | |
727 | 0 | if (!!auxInputs) |
728 | 0 | { |
729 | 0 | Json::Value const& smtlib2Responses = auxInputs["smtlib2responses"]; |
730 | 0 | if (!!smtlib2Responses) |
731 | 0 | { |
732 | 0 | if (!smtlib2Responses.isObject()) |
733 | 0 | return formatFatalError("JSONError", "\"auxiliaryInput.smtlib2responses\" must be an object."); |
734 | | |
735 | 0 | for (auto const& hashString: smtlib2Responses.getMemberNames()) |
736 | 0 | { |
737 | 0 | util::h256 hash; |
738 | 0 | try |
739 | 0 | { |
740 | 0 | hash = util::h256(hashString); |
741 | 0 | } |
742 | 0 | catch (util::BadHexCharacter const&) |
743 | 0 | { |
744 | 0 | return formatFatalError("JSONError", "Invalid hex encoding of SMTLib2 auxiliary input."); |
745 | 0 | } |
746 | | |
747 | 0 | if (!smtlib2Responses[hashString].isString()) |
748 | 0 | return formatFatalError( |
749 | 0 | "JSONError", |
750 | 0 | "\"smtlib2Responses." + hashString + "\" must be a string." |
751 | 0 | ); |
752 | | |
753 | 0 | ret.smtLib2Responses[hash] = smtlib2Responses[hashString].asString(); |
754 | 0 | } |
755 | 0 | } |
756 | 0 | } |
757 | | |
758 | 0 | Json::Value const& settings = _input.get("settings", Json::Value()); |
759 | |
|
760 | 0 | if (auto result = checkSettingsKeys(settings)) |
761 | 0 | return *result; |
762 | | |
763 | 0 | if (settings.isMember("stopAfter")) |
764 | 0 | { |
765 | 0 | if (!settings["stopAfter"].isString()) |
766 | 0 | return formatFatalError("JSONError", "\"settings.stopAfter\" must be a string."); |
767 | | |
768 | 0 | if (settings["stopAfter"].asString() != "parsing") |
769 | 0 | return formatFatalError("JSONError", "Invalid value for \"settings.stopAfter\". Only valid value is \"parsing\"."); |
770 | | |
771 | 0 | ret.stopAfter = CompilerStack::State::Parsed; |
772 | 0 | } |
773 | | |
774 | 0 | if (settings.isMember("parserErrorRecovery")) |
775 | 0 | { |
776 | 0 | if (!settings["parserErrorRecovery"].isBool()) |
777 | 0 | return formatFatalError("JSONError", "\"settings.parserErrorRecovery\" must be a Boolean."); |
778 | 0 | ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool(); |
779 | 0 | } |
780 | | |
781 | 0 | if (settings.isMember("viaIR")) |
782 | 0 | { |
783 | 0 | if (!settings["viaIR"].isBool()) |
784 | 0 | return formatFatalError("JSONError", "\"settings.viaIR\" must be a Boolean."); |
785 | 0 | ret.viaIR = settings["viaIR"].asBool(); |
786 | 0 | } |
787 | | |
788 | 0 | if (settings.isMember("evmVersion")) |
789 | 0 | { |
790 | 0 | if (!settings["evmVersion"].isString()) |
791 | 0 | return formatFatalError("JSONError", "evmVersion must be a string."); |
792 | 0 | std::optional<langutil::EVMVersion> version = langutil::EVMVersion::fromString(settings["evmVersion"].asString()); |
793 | 0 | if (!version) |
794 | 0 | return formatFatalError("JSONError", "Invalid EVM version requested."); |
795 | 0 | ret.evmVersion = *version; |
796 | 0 | } |
797 | | |
798 | 0 | if (settings.isMember("debug")) |
799 | 0 | { |
800 | 0 | if (auto result = checkKeys(settings["debug"], {"revertStrings", "debugInfo"}, "settings.debug")) |
801 | 0 | return *result; |
802 | | |
803 | 0 | if (settings["debug"].isMember("revertStrings")) |
804 | 0 | { |
805 | 0 | if (!settings["debug"]["revertStrings"].isString()) |
806 | 0 | return formatFatalError("JSONError", "settings.debug.revertStrings must be a string."); |
807 | 0 | std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString()); |
808 | 0 | if (!revertStrings) |
809 | 0 | return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings."); |
810 | 0 | if (*revertStrings == RevertStrings::VerboseDebug) |
811 | 0 | return formatFatalError( |
812 | 0 | "UnimplementedFeatureError", |
813 | 0 | "Only \"default\", \"strip\" and \"debug\" are implemented for settings.debug.revertStrings for now." |
814 | 0 | ); |
815 | 0 | ret.revertStrings = *revertStrings; |
816 | 0 | } |
817 | | |
818 | 0 | if (settings["debug"].isMember("debugInfo")) |
819 | 0 | { |
820 | 0 | if (!settings["debug"]["debugInfo"].isArray()) |
821 | 0 | return formatFatalError("JSONError", "settings.debug.debugInfo must be an array."); |
822 | | |
823 | 0 | vector<string> components; |
824 | 0 | for (Json::Value const& arrayValue: settings["debug"]["debugInfo"]) |
825 | 0 | components.push_back(arrayValue.asString()); |
826 | |
|
827 | 0 | optional<DebugInfoSelection> debugInfoSelection = DebugInfoSelection::fromComponents( |
828 | 0 | components, |
829 | 0 | true /* _acceptWildcards */ |
830 | 0 | ); |
831 | 0 | if (!debugInfoSelection.has_value()) |
832 | 0 | return formatFatalError("JSONError", "Invalid value in settings.debug.debugInfo."); |
833 | | |
834 | 0 | if (debugInfoSelection->snippet && !debugInfoSelection->location) |
835 | 0 | return formatFatalError( |
836 | 0 | "JSONError", |
837 | 0 | "To use 'snippet' with settings.debug.debugInfo you must select also 'location'." |
838 | 0 | ); |
839 | | |
840 | 0 | ret.debugInfoSelection = debugInfoSelection.value(); |
841 | 0 | } |
842 | 0 | } |
843 | | |
844 | 0 | if (settings.isMember("remappings") && !settings["remappings"].isArray()) |
845 | 0 | return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); |
846 | | |
847 | 0 | for (auto const& remapping: settings.get("remappings", Json::Value())) |
848 | 0 | { |
849 | 0 | if (!remapping.isString()) |
850 | 0 | return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings"); |
851 | 0 | if (auto r = ImportRemapper::parseRemapping(remapping.asString())) |
852 | 0 | ret.remappings.emplace_back(std::move(*r)); |
853 | 0 | else |
854 | 0 | return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); |
855 | 0 | } |
856 | | |
857 | 0 | if (settings.isMember("optimizer")) |
858 | 0 | { |
859 | 0 | auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]); |
860 | 0 | if (std::holds_alternative<Json::Value>(optimiserSettings)) |
861 | 0 | return std::get<Json::Value>(std::move(optimiserSettings)); // was an error |
862 | 0 | else |
863 | 0 | ret.optimiserSettings = std::get<OptimiserSettings>(std::move(optimiserSettings)); |
864 | 0 | } |
865 | | |
866 | 0 | Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); |
867 | 0 | if (!jsonLibraries.isObject()) |
868 | 0 | return formatFatalError("JSONError", "\"libraries\" is not a JSON object."); |
869 | 0 | for (auto const& sourceName: jsonLibraries.getMemberNames()) |
870 | 0 | { |
871 | 0 | auto const& jsonSourceName = jsonLibraries[sourceName]; |
872 | 0 | if (!jsonSourceName.isObject()) |
873 | 0 | return formatFatalError("JSONError", "Library entry is not a JSON object."); |
874 | 0 | for (auto const& library: jsonSourceName.getMemberNames()) |
875 | 0 | { |
876 | 0 | if (!jsonSourceName[library].isString()) |
877 | 0 | return formatFatalError("JSONError", "Library address must be a string."); |
878 | 0 | string address = jsonSourceName[library].asString(); |
879 | |
|
880 | 0 | if (!boost::starts_with(address, "0x")) |
881 | 0 | return formatFatalError( |
882 | 0 | "JSONError", |
883 | 0 | "Library address is not prefixed with \"0x\"." |
884 | 0 | ); |
885 | | |
886 | 0 | if (address.length() != 42) |
887 | 0 | return formatFatalError( |
888 | 0 | "JSONError", |
889 | 0 | "Library address is of invalid length." |
890 | 0 | ); |
891 | | |
892 | 0 | try |
893 | 0 | { |
894 | 0 | ret.libraries[sourceName + ":" + library] = util::h160(address); |
895 | 0 | } |
896 | 0 | catch (util::BadHexCharacter const&) |
897 | 0 | { |
898 | 0 | return formatFatalError( |
899 | 0 | "JSONError", |
900 | 0 | "Invalid library address (\"" + address + "\") supplied." |
901 | 0 | ); |
902 | 0 | } |
903 | 0 | } |
904 | 0 | } |
905 | | |
906 | 0 | Json::Value metadataSettings = settings.get("metadata", Json::Value()); |
907 | |
|
908 | 0 | if (auto result = checkMetadataKeys(metadataSettings)) |
909 | 0 | return *result; |
910 | | |
911 | 0 | ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool(); |
912 | 0 | if (metadataSettings.isMember("bytecodeHash")) |
913 | 0 | { |
914 | 0 | auto metadataHash = metadataSettings["bytecodeHash"].asString(); |
915 | 0 | ret.metadataHash = |
916 | 0 | metadataHash == "ipfs" ? |
917 | 0 | CompilerStack::MetadataHash::IPFS : |
918 | 0 | metadataHash == "bzzr1" ? |
919 | 0 | CompilerStack::MetadataHash::Bzzr1 : |
920 | 0 | CompilerStack::MetadataHash::None; |
921 | 0 | } |
922 | |
|
923 | 0 | Json::Value outputSelection = settings.get("outputSelection", Json::Value()); |
924 | |
|
925 | 0 | if (auto jsonError = checkOutputSelection(outputSelection)) |
926 | 0 | return *jsonError; |
927 | | |
928 | 0 | ret.outputSelection = std::move(outputSelection); |
929 | |
|
930 | 0 | if (ret.stopAfter != CompilerStack::State::CompilationSuccessful && isBinaryRequested(ret.outputSelection)) |
931 | 0 | return formatFatalError( |
932 | 0 | "JSONError", |
933 | 0 | "Requested output selection conflicts with \"settings.stopAfter\"." |
934 | 0 | ); |
935 | | |
936 | 0 | Json::Value const& modelCheckerSettings = settings.get("modelChecker", Json::Value()); |
937 | |
|
938 | 0 | if (auto result = checkModelCheckerSettingsKeys(modelCheckerSettings)) |
939 | 0 | return *result; |
940 | | |
941 | 0 | if (modelCheckerSettings.isMember("contracts")) |
942 | 0 | { |
943 | 0 | auto const& sources = modelCheckerSettings["contracts"]; |
944 | 0 | if (!sources.isObject() && !sources.isNull()) |
945 | 0 | return formatFatalError("JSONError", "settings.modelChecker.contracts is not a JSON object."); |
946 | | |
947 | 0 | map<string, set<string>> sourceContracts; |
948 | 0 | for (auto const& source: sources.getMemberNames()) |
949 | 0 | { |
950 | 0 | if (source.empty()) |
951 | 0 | return formatFatalError("JSONError", "Source name cannot be empty."); |
952 | | |
953 | 0 | auto const& contracts = sources[source]; |
954 | 0 | if (!contracts.isArray()) |
955 | 0 | return formatFatalError("JSONError", "Source contracts must be an array."); |
956 | | |
957 | 0 | for (auto const& contract: contracts) |
958 | 0 | { |
959 | 0 | if (!contract.isString()) |
960 | 0 | return formatFatalError("JSONError", "Every contract in settings.modelChecker.contracts must be a string."); |
961 | 0 | if (contract.asString().empty()) |
962 | 0 | return formatFatalError("JSONError", "Contract name cannot be empty."); |
963 | 0 | sourceContracts[source].insert(contract.asString()); |
964 | 0 | } |
965 | | |
966 | 0 | if (sourceContracts[source].empty()) |
967 | 0 | return formatFatalError("JSONError", "Source contracts must be a non-empty array."); |
968 | 0 | } |
969 | 0 | ret.modelCheckerSettings.contracts = {move(sourceContracts)}; |
970 | 0 | } |
971 | | |
972 | 0 | if (modelCheckerSettings.isMember("divModNoSlacks")) |
973 | 0 | { |
974 | 0 | auto const& divModNoSlacks = modelCheckerSettings["divModNoSlacks"]; |
975 | 0 | if (!divModNoSlacks.isBool()) |
976 | 0 | return formatFatalError("JSONError", "settings.modelChecker.divModNoSlacks must be a Boolean."); |
977 | 0 | ret.modelCheckerSettings.divModNoSlacks = divModNoSlacks.asBool(); |
978 | 0 | } |
979 | | |
980 | 0 | if (modelCheckerSettings.isMember("engine")) |
981 | 0 | { |
982 | 0 | if (!modelCheckerSettings["engine"].isString()) |
983 | 0 | return formatFatalError("JSONError", "settings.modelChecker.engine must be a string."); |
984 | 0 | std::optional<ModelCheckerEngine> engine = ModelCheckerEngine::fromString(modelCheckerSettings["engine"].asString()); |
985 | 0 | if (!engine) |
986 | 0 | return formatFatalError("JSONError", "Invalid model checker engine requested."); |
987 | 0 | ret.modelCheckerSettings.engine = *engine; |
988 | 0 | } |
989 | | |
990 | 0 | if (modelCheckerSettings.isMember("invariants")) |
991 | 0 | { |
992 | 0 | auto const& invariantsArray = modelCheckerSettings["invariants"]; |
993 | 0 | if (!invariantsArray.isArray()) |
994 | 0 | return formatFatalError("JSONError", "settings.modelChecker.invariants must be an array."); |
995 | | |
996 | 0 | ModelCheckerInvariants invariants; |
997 | 0 | for (auto const& i: invariantsArray) |
998 | 0 | { |
999 | 0 | if (!i.isString()) |
1000 | 0 | return formatFatalError("JSONError", "Every invariant type in settings.modelChecker.invariants must be a string."); |
1001 | 0 | if (!invariants.setFromString(i.asString())) |
1002 | 0 | return formatFatalError("JSONError", "Invalid model checker invariants requested."); |
1003 | 0 | } |
1004 | | |
1005 | 0 | if (invariants.invariants.empty()) |
1006 | 0 | return formatFatalError("JSONError", "settings.modelChecker.invariants must be a non-empty array."); |
1007 | | |
1008 | 0 | ret.modelCheckerSettings.invariants = invariants; |
1009 | 0 | } |
1010 | | |
1011 | 0 | if (modelCheckerSettings.isMember("showUnproved")) |
1012 | 0 | { |
1013 | 0 | auto const& showUnproved = modelCheckerSettings["showUnproved"]; |
1014 | 0 | if (!showUnproved.isBool()) |
1015 | 0 | return formatFatalError("JSONError", "settings.modelChecker.showUnproved must be a Boolean value."); |
1016 | 0 | ret.modelCheckerSettings.showUnproved = showUnproved.asBool(); |
1017 | 0 | } |
1018 | | |
1019 | 0 | if (modelCheckerSettings.isMember("solvers")) |
1020 | 0 | { |
1021 | 0 | auto const& solversArray = modelCheckerSettings["solvers"]; |
1022 | 0 | if (!solversArray.isArray()) |
1023 | 0 | return formatFatalError("JSONError", "settings.modelChecker.solvers must be an array."); |
1024 | | |
1025 | 0 | smtutil::SMTSolverChoice solvers; |
1026 | 0 | for (auto const& s: solversArray) |
1027 | 0 | { |
1028 | 0 | if (!s.isString()) |
1029 | 0 | return formatFatalError("JSONError", "Every target in settings.modelChecker.solvers must be a string."); |
1030 | 0 | if (!solvers.setSolver(s.asString())) |
1031 | 0 | return formatFatalError("JSONError", "Invalid model checker solvers requested."); |
1032 | 0 | } |
1033 | | |
1034 | 0 | ret.modelCheckerSettings.solvers = solvers; |
1035 | 0 | } |
1036 | | |
1037 | 0 | if (modelCheckerSettings.isMember("targets")) |
1038 | 0 | { |
1039 | 0 | auto const& targetsArray = modelCheckerSettings["targets"]; |
1040 | 0 | if (!targetsArray.isArray()) |
1041 | 0 | return formatFatalError("JSONError", "settings.modelChecker.targets must be an array."); |
1042 | | |
1043 | 0 | ModelCheckerTargets targets; |
1044 | 0 | for (auto const& t: targetsArray) |
1045 | 0 | { |
1046 | 0 | if (!t.isString()) |
1047 | 0 | return formatFatalError("JSONError", "Every target in settings.modelChecker.targets must be a string."); |
1048 | 0 | if (!targets.setFromString(t.asString())) |
1049 | 0 | return formatFatalError("JSONError", "Invalid model checker targets requested."); |
1050 | 0 | } |
1051 | | |
1052 | 0 | if (targets.targets.empty()) |
1053 | 0 | return formatFatalError("JSONError", "settings.modelChecker.targets must be a non-empty array."); |
1054 | | |
1055 | 0 | ret.modelCheckerSettings.targets = targets; |
1056 | 0 | } |
1057 | | |
1058 | 0 | if (modelCheckerSettings.isMember("timeout")) |
1059 | 0 | { |
1060 | 0 | if (!modelCheckerSettings["timeout"].isUInt()) |
1061 | 0 | return formatFatalError("JSONError", "settings.modelChecker.timeout must be an unsigned integer."); |
1062 | 0 | ret.modelCheckerSettings.timeout = modelCheckerSettings["timeout"].asUInt(); |
1063 | 0 | } |
1064 | | |
1065 | 0 | return { std::move(ret) }; |
1066 | 0 | } |
1067 | | |
1068 | | Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) |
1069 | 0 | { |
1070 | 0 | CompilerStack compilerStack(m_readFile); |
1071 | |
|
1072 | 0 | StringMap sourceList = std::move(_inputsAndSettings.sources); |
1073 | 0 | compilerStack.setSources(sourceList); |
1074 | 0 | for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) |
1075 | 0 | compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); |
1076 | 0 | compilerStack.setViaIR(_inputsAndSettings.viaIR); |
1077 | 0 | compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); |
1078 | 0 | compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery); |
1079 | 0 | compilerStack.setRemappings(move(_inputsAndSettings.remappings)); |
1080 | 0 | compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); |
1081 | 0 | compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); |
1082 | 0 | if (_inputsAndSettings.debugInfoSelection.has_value()) |
1083 | 0 | compilerStack.selectDebugInfo(_inputsAndSettings.debugInfoSelection.value()); |
1084 | 0 | compilerStack.setLibraries(_inputsAndSettings.libraries); |
1085 | 0 | compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); |
1086 | 0 | compilerStack.setMetadataHash(_inputsAndSettings.metadataHash); |
1087 | 0 | compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); |
1088 | 0 | compilerStack.setModelCheckerSettings(_inputsAndSettings.modelCheckerSettings); |
1089 | |
|
1090 | 0 | compilerStack.enableEvmBytecodeGeneration(isEvmBytecodeRequested(_inputsAndSettings.outputSelection)); |
1091 | 0 | compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection)); |
1092 | 0 | compilerStack.enableEwasmGeneration(isEwasmRequested(_inputsAndSettings.outputSelection)); |
1093 | |
|
1094 | 0 | Json::Value errors = std::move(_inputsAndSettings.errors); |
1095 | |
|
1096 | 0 | bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); |
1097 | |
|
1098 | 0 | try |
1099 | 0 | { |
1100 | 0 | if (binariesRequested) |
1101 | 0 | compilerStack.compile(); |
1102 | 0 | else |
1103 | 0 | compilerStack.parseAndAnalyze(_inputsAndSettings.stopAfter); |
1104 | |
|
1105 | 0 | for (auto const& error: compilerStack.errors()) |
1106 | 0 | { |
1107 | 0 | Error const& err = dynamic_cast<Error const&>(*error); |
1108 | |
|
1109 | 0 | errors.append(formatErrorWithException( |
1110 | 0 | compilerStack, |
1111 | 0 | *error, |
1112 | 0 | Error::errorSeverity(err.type()), |
1113 | 0 | err.typeName(), |
1114 | 0 | "general", |
1115 | 0 | "", |
1116 | 0 | err.errorId() |
1117 | 0 | )); |
1118 | 0 | } |
1119 | 0 | } |
1120 | | /// This is only thrown in a very few locations. |
1121 | 0 | catch (Error const& _error) |
1122 | 0 | { |
1123 | 0 | errors.append(formatErrorWithException( |
1124 | 0 | compilerStack, |
1125 | 0 | _error, |
1126 | 0 | Error::Severity::Error, |
1127 | 0 | _error.typeName(), |
1128 | 0 | "general", |
1129 | 0 | "Uncaught error: " |
1130 | 0 | )); |
1131 | 0 | } |
1132 | | /// This should not be leaked from compile(). |
1133 | 0 | catch (FatalError const& _exception) |
1134 | 0 | { |
1135 | 0 | errors.append(formatError( |
1136 | 0 | Error::Severity::Error, |
1137 | 0 | "FatalError", |
1138 | 0 | "general", |
1139 | 0 | "Uncaught fatal error: " + boost::diagnostic_information(_exception) |
1140 | 0 | )); |
1141 | 0 | } |
1142 | 0 | catch (CompilerError const& _exception) |
1143 | 0 | { |
1144 | 0 | errors.append(formatErrorWithException( |
1145 | 0 | compilerStack, |
1146 | 0 | _exception, |
1147 | 0 | Error::Severity::Error, |
1148 | 0 | "CompilerError", |
1149 | 0 | "general", |
1150 | 0 | "Compiler error (" + _exception.lineInfo() + ")" |
1151 | 0 | )); |
1152 | 0 | } |
1153 | 0 | catch (InternalCompilerError const& _exception) |
1154 | 0 | { |
1155 | 0 | errors.append(formatErrorWithException( |
1156 | 0 | compilerStack, |
1157 | 0 | _exception, |
1158 | 0 | Error::Severity::Error, |
1159 | 0 | "InternalCompilerError", |
1160 | 0 | "general", |
1161 | 0 | "Internal compiler error (" + _exception.lineInfo() + ")" |
1162 | 0 | )); |
1163 | 0 | } |
1164 | 0 | catch (UnimplementedFeatureError const& _exception) |
1165 | 0 | { |
1166 | 0 | errors.append(formatErrorWithException( |
1167 | 0 | compilerStack, |
1168 | 0 | _exception, |
1169 | 0 | Error::Severity::Error, |
1170 | 0 | "UnimplementedFeatureError", |
1171 | 0 | "general", |
1172 | 0 | "Unimplemented feature (" + _exception.lineInfo() + ")" |
1173 | 0 | )); |
1174 | 0 | } |
1175 | 0 | catch (yul::YulException const& _exception) |
1176 | 0 | { |
1177 | 0 | errors.append(formatErrorWithException( |
1178 | 0 | compilerStack, |
1179 | 0 | _exception, |
1180 | 0 | Error::Severity::Error, |
1181 | 0 | "YulException", |
1182 | 0 | "general", |
1183 | 0 | "Yul exception" |
1184 | 0 | )); |
1185 | 0 | } |
1186 | 0 | catch (smtutil::SMTLogicError const& _exception) |
1187 | 0 | { |
1188 | 0 | errors.append(formatErrorWithException( |
1189 | 0 | compilerStack, |
1190 | 0 | _exception, |
1191 | 0 | Error::Severity::Error, |
1192 | 0 | "SMTLogicException", |
1193 | 0 | "general", |
1194 | 0 | "SMT logic exception" |
1195 | 0 | )); |
1196 | 0 | } |
1197 | 0 | catch (util::Exception const& _exception) |
1198 | 0 | { |
1199 | 0 | errors.append(formatError( |
1200 | 0 | Error::Severity::Error, |
1201 | 0 | "Exception", |
1202 | 0 | "general", |
1203 | 0 | "Exception during compilation: " + boost::diagnostic_information(_exception) |
1204 | 0 | )); |
1205 | 0 | } |
1206 | 0 | catch (std::exception const& _exception) |
1207 | 0 | { |
1208 | 0 | errors.append(formatError( |
1209 | 0 | Error::Severity::Error, |
1210 | 0 | "Exception", |
1211 | 0 | "general", |
1212 | 0 | "Unknown exception during compilation: " + boost::diagnostic_information(_exception) |
1213 | 0 | )); |
1214 | 0 | } |
1215 | 0 | catch (...) |
1216 | 0 | { |
1217 | 0 | errors.append(formatError( |
1218 | 0 | Error::Severity::Error, |
1219 | 0 | "Exception", |
1220 | 0 | "general", |
1221 | 0 | "Unknown exception during compilation: " + boost::current_exception_diagnostic_information() |
1222 | 0 | )); |
1223 | 0 | } |
1224 | |
|
1225 | 0 | bool analysisPerformed = compilerStack.state() >= CompilerStack::State::AnalysisPerformed; |
1226 | 0 | bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; |
1227 | |
|
1228 | 0 | if (compilerStack.hasError() && !_inputsAndSettings.parserErrorRecovery) |
1229 | 0 | analysisPerformed = false; |
1230 | | |
1231 | | /// Inconsistent state - stop here to receive error reports from users |
1232 | 0 | if ( |
1233 | 0 | ((binariesRequested && !compilationSuccess) || !analysisPerformed) && |
1234 | 0 | (errors.empty() && _inputsAndSettings.stopAfter >= CompilerStack::State::AnalysisPerformed) |
1235 | 0 | ) |
1236 | 0 | return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); |
1237 | | |
1238 | 0 | Json::Value output = Json::objectValue; |
1239 | |
|
1240 | 0 | if (errors.size() > 0) |
1241 | 0 | output["errors"] = std::move(errors); |
1242 | |
|
1243 | 0 | if (!compilerStack.unhandledSMTLib2Queries().empty()) |
1244 | 0 | for (string const& query: compilerStack.unhandledSMTLib2Queries()) |
1245 | 0 | output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + util::keccak256(query).hex()] = query; |
1246 | |
|
1247 | 0 | bool const wildcardMatchesExperimental = false; |
1248 | |
|
1249 | 0 | output["sources"] = Json::objectValue; |
1250 | 0 | unsigned sourceIndex = 0; |
1251 | 0 | if (compilerStack.state() >= CompilerStack::State::Parsed && (!compilerStack.hasError() || _inputsAndSettings.parserErrorRecovery)) |
1252 | 0 | for (string const& sourceName: compilerStack.sourceNames()) |
1253 | 0 | { |
1254 | 0 | Json::Value sourceResult = Json::objectValue; |
1255 | 0 | sourceResult["id"] = sourceIndex++; |
1256 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesExperimental)) |
1257 | 0 | sourceResult["ast"] = ASTJsonExporter(compilerStack.state(), compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); |
1258 | 0 | output["sources"][sourceName] = sourceResult; |
1259 | 0 | } |
1260 | |
|
1261 | 0 | Json::Value contractsOutput = Json::objectValue; |
1262 | 0 | for (string const& contractName: analysisPerformed ? compilerStack.contractNames() : vector<string>()) |
1263 | 0 | { |
1264 | 0 | size_t colon = contractName.rfind(':'); |
1265 | 0 | solAssert(colon != string::npos, ""); |
1266 | 0 | string file = contractName.substr(0, colon); |
1267 | 0 | string name = contractName.substr(colon + 1); |
1268 | | |
1269 | | // ABI, storage layout, documentation and metadata |
1270 | 0 | Json::Value contractData(Json::objectValue); |
1271 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesExperimental)) |
1272 | 0 | contractData["abi"] = compilerStack.contractABI(contractName); |
1273 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "storageLayout", false)) |
1274 | 0 | contractData["storageLayout"] = compilerStack.storageLayout(contractName); |
1275 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesExperimental)) |
1276 | 0 | contractData["metadata"] = compilerStack.metadata(contractName); |
1277 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesExperimental)) |
1278 | 0 | contractData["userdoc"] = compilerStack.natspecUser(contractName); |
1279 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental)) |
1280 | 0 | contractData["devdoc"] = compilerStack.natspecDev(contractName); |
1281 | | |
1282 | | // IR |
1283 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental)) |
1284 | 0 | contractData["ir"] = compilerStack.yulIR(contractName); |
1285 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental)) |
1286 | 0 | contractData["irOptimized"] = compilerStack.yulIROptimized(contractName); |
1287 | | |
1288 | | // Ewasm |
1289 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wast", wildcardMatchesExperimental)) |
1290 | 0 | contractData["ewasm"]["wast"] = compilerStack.ewasm(contractName); |
1291 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wasm", wildcardMatchesExperimental)) |
1292 | 0 | contractData["ewasm"]["wasm"] = compilerStack.ewasmObject(contractName).toHex(); |
1293 | | |
1294 | | // EVM |
1295 | 0 | Json::Value evmData(Json::objectValue); |
1296 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental)) |
1297 | 0 | evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); |
1298 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental)) |
1299 | 0 | evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName); |
1300 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental)) |
1301 | 0 | evmData["methodIdentifiers"] = compilerStack.interfaceSymbols(contractName)["methods"]; |
1302 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) |
1303 | 0 | evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); |
1304 | |
|
1305 | 0 | if (compilationSuccess && isArtifactRequested( |
1306 | 0 | _inputsAndSettings.outputSelection, |
1307 | 0 | file, |
1308 | 0 | name, |
1309 | 0 | evmObjectComponents("bytecode"), |
1310 | 0 | wildcardMatchesExperimental |
1311 | 0 | )) |
1312 | 0 | evmData["bytecode"] = collectEVMObject( |
1313 | 0 | compilerStack.object(contractName), |
1314 | 0 | compilerStack.sourceMapping(contractName), |
1315 | 0 | compilerStack.generatedSources(contractName), |
1316 | 0 | false, |
1317 | 0 | [&](string const& _element) { return isArtifactRequested( |
1318 | 0 | _inputsAndSettings.outputSelection, |
1319 | 0 | file, |
1320 | 0 | name, |
1321 | 0 | "evm.bytecode." + _element, |
1322 | 0 | wildcardMatchesExperimental |
1323 | 0 | ); } |
1324 | 0 | ); |
1325 | |
|
1326 | 0 | if (compilationSuccess && isArtifactRequested( |
1327 | 0 | _inputsAndSettings.outputSelection, |
1328 | 0 | file, |
1329 | 0 | name, |
1330 | 0 | evmObjectComponents("deployedBytecode"), |
1331 | 0 | wildcardMatchesExperimental |
1332 | 0 | )) |
1333 | 0 | evmData["deployedBytecode"] = collectEVMObject( |
1334 | 0 | compilerStack.runtimeObject(contractName), |
1335 | 0 | compilerStack.runtimeSourceMapping(contractName), |
1336 | 0 | compilerStack.generatedSources(contractName, true), |
1337 | 0 | true, |
1338 | 0 | [&](string const& _element) { return isArtifactRequested( |
1339 | 0 | _inputsAndSettings.outputSelection, |
1340 | 0 | file, |
1341 | 0 | name, |
1342 | 0 | "evm.deployedBytecode." + _element, |
1343 | 0 | wildcardMatchesExperimental |
1344 | 0 | ); } |
1345 | 0 | ); |
1346 | |
|
1347 | 0 | if (!evmData.empty()) |
1348 | 0 | contractData["evm"] = evmData; |
1349 | |
|
1350 | 0 | if (!contractData.empty()) |
1351 | 0 | { |
1352 | 0 | if (!contractsOutput.isMember(file)) |
1353 | 0 | contractsOutput[file] = Json::objectValue; |
1354 | 0 | contractsOutput[file][name] = contractData; |
1355 | 0 | } |
1356 | 0 | } |
1357 | 0 | if (!contractsOutput.empty()) |
1358 | 0 | output["contracts"] = contractsOutput; |
1359 | |
|
1360 | 0 | return output; |
1361 | 0 | } |
1362 | | |
1363 | | |
1364 | | Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) |
1365 | 0 | { |
1366 | 0 | Json::Value output = Json::objectValue; |
1367 | 0 | output["errors"] = std::move(_inputsAndSettings.errors); |
1368 | |
|
1369 | 0 | if (_inputsAndSettings.sources.size() != 1) |
1370 | 0 | { |
1371 | 0 | output["errors"].append(formatError( |
1372 | 0 | Error::Severity::Error, |
1373 | 0 | "JSONError", |
1374 | 0 | "general", |
1375 | 0 | "Yul mode only supports exactly one input file." |
1376 | 0 | )); |
1377 | 0 | return output; |
1378 | 0 | } |
1379 | 0 | if (!_inputsAndSettings.smtLib2Responses.empty()) |
1380 | 0 | { |
1381 | 0 | output["errors"].append(formatError( |
1382 | 0 | Error::Severity::Error, |
1383 | 0 | "JSONError", |
1384 | 0 | "general", |
1385 | 0 | "Yul mode does not support smtlib2responses." |
1386 | 0 | )); |
1387 | 0 | return output; |
1388 | 0 | } |
1389 | 0 | if (!_inputsAndSettings.remappings.empty()) |
1390 | 0 | { |
1391 | 0 | output["errors"].append(formatError( |
1392 | 0 | Error::Severity::Error, |
1393 | 0 | "JSONError", |
1394 | 0 | "general", |
1395 | 0 | "Field \"settings.remappings\" cannot be used for Yul." |
1396 | 0 | )); |
1397 | 0 | return output; |
1398 | 0 | } |
1399 | 0 | if (_inputsAndSettings.revertStrings != RevertStrings::Default) |
1400 | 0 | { |
1401 | 0 | output["errors"].append(formatError( |
1402 | 0 | Error::Severity::Error, |
1403 | 0 | "JSONError", |
1404 | 0 | "general", |
1405 | 0 | "Field \"settings.debug.revertStrings\" cannot be used for Yul." |
1406 | 0 | )); |
1407 | 0 | return output; |
1408 | 0 | } |
1409 | | |
1410 | 0 | YulStack stack( |
1411 | 0 | _inputsAndSettings.evmVersion, |
1412 | 0 | YulStack::Language::StrictAssembly, |
1413 | 0 | _inputsAndSettings.optimiserSettings, |
1414 | 0 | _inputsAndSettings.debugInfoSelection.has_value() ? |
1415 | 0 | _inputsAndSettings.debugInfoSelection.value() : |
1416 | 0 | DebugInfoSelection::Default() |
1417 | 0 | ); |
1418 | 0 | string const& sourceName = _inputsAndSettings.sources.begin()->first; |
1419 | 0 | string const& sourceContents = _inputsAndSettings.sources.begin()->second; |
1420 | | |
1421 | | // Inconsistent state - stop here to receive error reports from users |
1422 | 0 | if (!stack.parseAndAnalyze(sourceName, sourceContents) && stack.errors().empty()) |
1423 | 0 | { |
1424 | 0 | output["errors"].append(formatError( |
1425 | 0 | Error::Severity::Error, |
1426 | 0 | "InternalCompilerError", |
1427 | 0 | "general", |
1428 | 0 | "No error reported, but compilation failed." |
1429 | 0 | )); |
1430 | 0 | return output; |
1431 | 0 | } |
1432 | | |
1433 | 0 | if (!stack.errors().empty()) |
1434 | 0 | { |
1435 | 0 | for (auto const& error: stack.errors()) |
1436 | 0 | { |
1437 | 0 | auto err = dynamic_pointer_cast<Error const>(error); |
1438 | |
|
1439 | 0 | output["errors"].append(formatErrorWithException( |
1440 | 0 | stack, |
1441 | 0 | *error, |
1442 | 0 | Error::errorSeverity(err->type()), |
1443 | 0 | err->typeName(), |
1444 | 0 | "general", |
1445 | 0 | "" |
1446 | 0 | )); |
1447 | 0 | } |
1448 | 0 | return output; |
1449 | 0 | } |
1450 | | |
1451 | 0 | string contractName = stack.parserResult()->name.str(); |
1452 | |
|
1453 | 0 | bool const wildcardMatchesExperimental = true; |
1454 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental)) |
1455 | 0 | output["contracts"][sourceName][contractName]["ir"] = stack.print(); |
1456 | |
|
1457 | 0 | stack.optimize(); |
1458 | |
|
1459 | 0 | MachineAssemblyObject object; |
1460 | 0 | MachineAssemblyObject deployedObject; |
1461 | 0 | tie(object, deployedObject) = stack.assembleWithDeployed(); |
1462 | |
|
1463 | 0 | if (object.bytecode) |
1464 | 0 | object.bytecode->link(_inputsAndSettings.libraries); |
1465 | 0 | if (deployedObject.bytecode) |
1466 | 0 | deployedObject.bytecode->link(_inputsAndSettings.libraries); |
1467 | |
|
1468 | 0 | for (auto&& [kind, isDeployed]: {make_pair("bytecode"s, false), make_pair("deployedBytecode"s, true)}) |
1469 | 0 | if (isArtifactRequested( |
1470 | 0 | _inputsAndSettings.outputSelection, |
1471 | 0 | sourceName, |
1472 | 0 | contractName, |
1473 | 0 | evmObjectComponents(kind), |
1474 | 0 | wildcardMatchesExperimental |
1475 | 0 | )) |
1476 | 0 | { |
1477 | 0 | MachineAssemblyObject const& o = isDeployed ? deployedObject : object; |
1478 | 0 | if (o.bytecode) |
1479 | 0 | output["contracts"][sourceName][contractName]["evm"][kind] = |
1480 | 0 | collectEVMObject( |
1481 | 0 | *o.bytecode, |
1482 | 0 | o.sourceMappings.get(), |
1483 | 0 | Json::arrayValue, |
1484 | 0 | isDeployed, |
1485 | 0 | [&, kind = kind](string const& _element) { return isArtifactRequested( |
1486 | 0 | _inputsAndSettings.outputSelection, |
1487 | 0 | sourceName, |
1488 | 0 | contractName, |
1489 | 0 | "evm." + kind + "." + _element, |
1490 | 0 | wildcardMatchesExperimental |
1491 | 0 | ); } |
1492 | 0 | ); |
1493 | 0 | } |
1494 | |
|
1495 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental)) |
1496 | 0 | output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); |
1497 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental)) |
1498 | 0 | output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly; |
1499 | |
|
1500 | 0 | return output; |
1501 | 0 | } |
1502 | | |
1503 | | |
1504 | | Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept |
1505 | 0 | { |
1506 | 0 | YulStringRepository::reset(); |
1507 | |
|
1508 | 0 | try |
1509 | 0 | { |
1510 | 0 | auto parsed = parseInput(_input); |
1511 | 0 | if (std::holds_alternative<Json::Value>(parsed)) |
1512 | 0 | return std::get<Json::Value>(std::move(parsed)); |
1513 | 0 | InputsAndSettings settings = std::get<InputsAndSettings>(std::move(parsed)); |
1514 | 0 | if (settings.language == "Solidity") |
1515 | 0 | return compileSolidity(std::move(settings)); |
1516 | 0 | else if (settings.language == "Yul") |
1517 | 0 | return compileYul(std::move(settings)); |
1518 | 0 | else |
1519 | 0 | return formatFatalError("JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language."); |
1520 | 0 | } |
1521 | 0 | catch (Json::LogicError const& _exception) |
1522 | 0 | { |
1523 | 0 | return formatFatalError("InternalCompilerError", string("JSON logic exception: ") + _exception.what()); |
1524 | 0 | } |
1525 | 0 | catch (Json::RuntimeError const& _exception) |
1526 | 0 | { |
1527 | 0 | return formatFatalError("InternalCompilerError", string("JSON runtime exception: ") + _exception.what()); |
1528 | 0 | } |
1529 | 0 | catch (util::Exception const& _exception) |
1530 | 0 | { |
1531 | 0 | return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::diagnostic_information(_exception)); |
1532 | 0 | } |
1533 | 0 | catch (...) |
1534 | 0 | { |
1535 | 0 | return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::current_exception_diagnostic_information()); |
1536 | 0 | } |
1537 | 0 | } |
1538 | | |
1539 | | string StandardCompiler::compile(string const& _input) noexcept |
1540 | 0 | { |
1541 | 0 | Json::Value input; |
1542 | 0 | string errors; |
1543 | 0 | try |
1544 | 0 | { |
1545 | 0 | if (!util::jsonParseStrict(_input, input, &errors)) |
1546 | 0 | return util::jsonPrint(formatFatalError("JSONError", errors), m_jsonPrintingFormat); |
1547 | 0 | } |
1548 | 0 | catch (...) |
1549 | 0 | { |
1550 | 0 | return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}"; |
1551 | 0 | } |
1552 | | |
1553 | | // cout << "Input: " << input.toStyledString() << endl; |
1554 | 0 | Json::Value output = compile(input); |
1555 | | // cout << "Output: " << output.toStyledString() << endl; |
1556 | |
|
1557 | 0 | try |
1558 | 0 | { |
1559 | 0 | return util::jsonPrint(output, m_jsonPrintingFormat); |
1560 | 0 | } |
1561 | 0 | catch (...) |
1562 | 0 | { |
1563 | 0 | return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}"; |
1564 | 0 | } |
1565 | 0 | } |
1566 | | |
1567 | | Json::Value StandardCompiler::formatFunctionDebugData( |
1568 | | map<string, evmasm::LinkerObject::FunctionDebugData> const& _debugInfo |
1569 | | ) |
1570 | 0 | { |
1571 | 0 | Json::Value ret(Json::objectValue); |
1572 | 0 | for (auto const& [name, info]: _debugInfo) |
1573 | 0 | { |
1574 | 0 | Json::Value fun; |
1575 | 0 | if (info.sourceID) |
1576 | 0 | fun["id"] = Json::UInt64(*info.sourceID); |
1577 | 0 | else |
1578 | 0 | fun["id"] = Json::nullValue; |
1579 | 0 | if (info.bytecodeOffset) |
1580 | 0 | fun["entryPoint"] = Json::UInt64(*info.bytecodeOffset); |
1581 | 0 | else |
1582 | 0 | fun["entryPoint"] = Json::nullValue; |
1583 | 0 | fun["parameterSlots"] = Json::UInt64(info.params); |
1584 | 0 | fun["returnSlots"] = Json::UInt64(info.returns); |
1585 | 0 | ret[name] = move(fun); |
1586 | 0 | } |
1587 | |
|
1588 | 0 | return ret; |
1589 | 0 | } |