/src/solidity/libsolidity/interface/StandardCompiler.cpp
Line | Count | Source |
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 | | #include <libevmasm/Ethdebug.h> |
34 | | #include <libevmasm/EVMAssemblyStack.h> |
35 | | |
36 | | #include <libsmtutil/Exceptions.h> |
37 | | |
38 | | #include <liblangutil/SourceReferenceFormatter.h> |
39 | | |
40 | | #include <libsolutil/JSON.h> |
41 | | #include <libsolutil/Keccak256.h> |
42 | | #include <libsolutil/CommonData.h> |
43 | | |
44 | | #include <boost/algorithm/string/predicate.hpp> |
45 | | |
46 | | #include <algorithm> |
47 | | #include <optional> |
48 | | |
49 | | #include <range/v3/algorithm/contains.hpp> |
50 | | |
51 | | using namespace solidity; |
52 | | using namespace solidity::yul; |
53 | | using namespace solidity::frontend; |
54 | | using namespace solidity::langutil; |
55 | | using namespace solidity::util; |
56 | | using namespace std::string_literals; |
57 | | |
58 | | namespace |
59 | | { |
60 | | |
61 | | Json formatError( |
62 | | Error::Type _type, |
63 | | std::string const& _component, |
64 | | std::string const& _message, |
65 | | std::string const& _formattedMessage = "", |
66 | | Json const& _sourceLocation = Json(), |
67 | | Json const& _secondarySourceLocation = Json() |
68 | | ) |
69 | 0 | { |
70 | 0 | Json error; |
71 | 0 | error["type"] = Error::formatErrorType(_type); |
72 | 0 | error["component"] = _component; |
73 | 0 | error["severity"] = Error::formatErrorSeverityLowercase(Error::errorSeverity(_type)); |
74 | 0 | error["message"] = _message; |
75 | 0 | error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message; |
76 | 0 | if (_sourceLocation.is_object()) |
77 | 0 | error["sourceLocation"] = _sourceLocation; |
78 | 0 | if (_secondarySourceLocation.is_array()) |
79 | 0 | error["secondarySourceLocations"] = _secondarySourceLocation; |
80 | 0 | return error; |
81 | 0 | } |
82 | | |
83 | | Json formatFatalError(Error::Type _type, std::string const& _message) |
84 | 0 | { |
85 | 0 | Json output; |
86 | 0 | output["errors"] = Json::array(); |
87 | 0 | output["errors"].emplace_back(formatError(_type, "general", _message)); |
88 | 0 | return output; |
89 | 0 | } |
90 | | |
91 | | Json formatSourceLocation(SourceLocation const* location) |
92 | 0 | { |
93 | 0 | if (!location || !location->sourceName) |
94 | 0 | return Json(); |
95 | | |
96 | 0 | Json sourceLocation; |
97 | 0 | sourceLocation["file"] = *location->sourceName; |
98 | 0 | sourceLocation["start"] = location->start; |
99 | 0 | sourceLocation["end"] = location->end; |
100 | 0 | return sourceLocation; |
101 | 0 | } |
102 | | |
103 | | Json formatSecondarySourceLocation(SecondarySourceLocation const* _secondaryLocation) |
104 | 0 | { |
105 | 0 | if (!_secondaryLocation) |
106 | 0 | return Json(); |
107 | | |
108 | 0 | Json secondarySourceLocation = Json::array(); |
109 | 0 | for (auto const& location: _secondaryLocation->infos) |
110 | 0 | { |
111 | 0 | Json msg = formatSourceLocation(&location.second); |
112 | 0 | msg["message"] = location.first; |
113 | 0 | secondarySourceLocation.emplace_back(msg); |
114 | 0 | } |
115 | 0 | return secondarySourceLocation; |
116 | 0 | } |
117 | | |
118 | | Json formatErrorWithException( |
119 | | CharStreamProvider const& _charStreamProvider, |
120 | | util::Exception const& _exception, |
121 | | Error::Type _type, |
122 | | std::string const& _component, |
123 | | std::string const& _message, |
124 | | std::optional<ErrorId> _errorId = std::nullopt |
125 | | ) |
126 | 0 | { |
127 | 0 | std::string message; |
128 | | // TODO: consider enabling color |
129 | 0 | std::string formattedMessage = SourceReferenceFormatter::formatExceptionInformation( |
130 | 0 | _exception, |
131 | 0 | _type, |
132 | 0 | _charStreamProvider, |
133 | 0 | false // colored |
134 | 0 | ); |
135 | |
|
136 | 0 | if (std::string const* description = _exception.comment()) |
137 | 0 | message = ((_message.length() > 0) ? (_message + ": ") : "") + *description; |
138 | 0 | else |
139 | 0 | message = _message; |
140 | |
|
141 | 0 | Json error = formatError( |
142 | 0 | _type, |
143 | 0 | _component, |
144 | 0 | message, |
145 | 0 | formattedMessage, |
146 | 0 | formatSourceLocation(boost::get_error_info<errinfo_sourceLocation>(_exception)), |
147 | 0 | formatSecondarySourceLocation(boost::get_error_info<errinfo_secondarySourceLocation>(_exception)) |
148 | 0 | ); |
149 | |
|
150 | 0 | if (_errorId) |
151 | 0 | error["errorCode"] = std::to_string(_errorId.value().error); |
152 | |
|
153 | 0 | return error; |
154 | 0 | } |
155 | | |
156 | | /// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content. |
157 | | bool hashMatchesContent(std::string const& _hash, std::string const& _content) |
158 | 0 | { |
159 | 0 | try |
160 | 0 | { |
161 | 0 | return util::h256(_hash) == util::keccak256(_content); |
162 | 0 | } |
163 | 0 | catch (util::BadHexCharacter const&) |
164 | 0 | { |
165 | 0 | return false; |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | bool isArtifactRequested(Json const& _outputSelection, std::string const& _artifact, bool _wildcardMatchesExperimental) |
170 | 0 | { |
171 | 0 | static std::set<std::string> experimental{"ir", "irAst", "irOptimized", "irOptimizedAst", "ethdebug"}; |
172 | 0 | for (auto const& selectedArtifactJson: _outputSelection) |
173 | 0 | { |
174 | 0 | std::string const& selectedArtifact = selectedArtifactJson.get<std::string>(); |
175 | 0 | if ( |
176 | 0 | _artifact == selectedArtifact || |
177 | 0 | boost::algorithm::starts_with(_artifact, selectedArtifact + ".") |
178 | 0 | ) |
179 | 0 | { |
180 | 0 | if (_artifact.find("ethdebug") != std::string::npos) |
181 | | // only accept exact matches for ethdebug, e.g. evm.bytecode.ethdebug |
182 | 0 | return selectedArtifact == _artifact; |
183 | 0 | return true; |
184 | 0 | } |
185 | 0 | else if (selectedArtifact == "*") |
186 | 0 | { |
187 | | // TODO: everything ethdebug related is only experimental for now, so it should not be matched by "*". |
188 | 0 | if (_artifact.find("ethdebug") != std::string::npos) |
189 | 0 | return false; |
190 | | // "ir", "irOptimized" can only be matched by "*" if activated. |
191 | 0 | if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental) |
192 | 0 | return true; |
193 | 0 | } |
194 | 0 | } |
195 | 0 | return false; |
196 | 0 | } |
197 | | |
198 | | /// |
199 | | /// @a _outputSelection is a JSON object containing a two-level hashmap, where the first level is the filename, |
200 | | /// the second level is the contract name and the value is an array of artifact names to be requested for that contract. |
201 | | /// @a _file is the current file |
202 | | /// @a _contract is the current contract |
203 | | /// @a _artifact is the current artifact name |
204 | | /// |
205 | | /// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract. |
206 | | /// |
207 | | /// In @a _outputSelection the use of '*' as a wildcard is permitted. |
208 | | /// |
209 | | /// @TODO optimise this. Perhaps flatten the structure upfront. |
210 | | /// |
211 | | bool isArtifactRequested(Json const& _outputSelection, std::string const& _file, std::string const& _contract, std::string const& _artifact, bool _wildcardMatchesExperimental) |
212 | 0 | { |
213 | 0 | if (!_outputSelection.is_object()) |
214 | 0 | return false; |
215 | | |
216 | 0 | for (auto const& file: { _file, std::string("*") }) |
217 | 0 | if (_outputSelection.contains(file) && _outputSelection[file].is_object()) |
218 | 0 | { |
219 | | /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise |
220 | | /// for Contract-level targets try both contract name and wildcard |
221 | 0 | std::vector<std::string> contracts{ _contract }; |
222 | 0 | if (!_contract.empty()) |
223 | 0 | contracts.emplace_back("*"); |
224 | 0 | for (auto const& contract: contracts) |
225 | 0 | if ( |
226 | 0 | _outputSelection[file].contains(contract) && |
227 | 0 | _outputSelection[file][contract].is_array() && |
228 | 0 | isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesExperimental) |
229 | 0 | ) |
230 | 0 | return true; |
231 | 0 | } |
232 | | |
233 | 0 | return false; |
234 | 0 | } |
235 | | |
236 | | bool isArtifactRequested(Json const& _outputSelection, std::string const& _file, std::string const& _contract, std::vector<std::string> const& _artifacts, bool _wildcardMatchesExperimental) |
237 | 0 | { |
238 | 0 | for (auto const& artifact: _artifacts) |
239 | 0 | if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesExperimental)) |
240 | 0 | return true; |
241 | 0 | return false; |
242 | 0 | } |
243 | | |
244 | | /// @returns boolean indicating whether @_artifacts is contained in @_outputSelection. Helper function. |
245 | | template<ranges::range Range> |
246 | | bool areArtifactsRequested(Json const& _outputSelection, Range&& _artifacts) |
247 | 0 | { |
248 | 0 | for (auto const& fileRequests: _outputSelection) |
249 | 0 | for (auto const& requests: fileRequests) |
250 | 0 | for (auto const& request: requests) |
251 | 0 | if (ranges::contains(_artifacts, request.get<std::string>())) |
252 | 0 | return true; |
253 | 0 | return false; |
254 | 0 | } Unexecuted instantiation: StandardCompiler.cpp:_ZN12_GLOBAL__N_121areArtifactsRequestedITkN6ranges5rangeERKNSt3__15arrayIPKcLm4EEEEEbRKN8nlohmann16json_abi_v3_12_010basic_jsonINS2_3mapENS2_6vectorENS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEblmdSH_NSA_14adl_serializerENSD_IhNSH_IhEEEEvEEOT_ Unexecuted instantiation: StandardCompiler.cpp:_ZN12_GLOBAL__N_121areArtifactsRequestedITkN6ranges5rangeERKNSt3__15arrayIPKcLm2EEEEEbRKN8nlohmann16json_abi_v3_12_010basic_jsonINS2_3mapENS2_6vectorENS2_12basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEEblmdSH_NSA_14adl_serializerENSD_IhNSH_IhEEEEvEEOT_ |
255 | | |
256 | | /// @returns all artifact names of the EVM object, either for creation or deploy time. |
257 | | std::vector<std::string> evmObjectComponents(std::string const& _objectKind) |
258 | 0 | { |
259 | 0 | solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", ""); |
260 | 0 | std::vector<std::string> components{"", ".object", ".opcodes", ".sourceMap", ".functionDebugData", ".generatedSources", ".linkReferences", ".ethdebug"}; |
261 | 0 | if (_objectKind == "deployedBytecode") |
262 | 0 | components.push_back(".immutableReferences"); |
263 | 0 | return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; }); |
264 | 0 | } |
265 | | |
266 | | /// @returns true if any binary was requested, i.e. we actually have to perform compilation. |
267 | | bool isBinaryRequested(Json const& _outputSelection) |
268 | 0 | { |
269 | 0 | if (!_outputSelection.is_object()) |
270 | 0 | return false; |
271 | | |
272 | | // This does not include "evm.methodIdentifiers" on purpose! |
273 | 0 | static std::vector<std::string> const outputsThatRequireBinaries = std::vector<std::string>{ |
274 | 0 | "*", |
275 | 0 | "ir", "irAst", "irOptimized", "irOptimizedAst", |
276 | 0 | "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" |
277 | 0 | } + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode"); |
278 | |
|
279 | 0 | for (auto const& fileRequests: _outputSelection) |
280 | 0 | for (auto const& requests: fileRequests) |
281 | 0 | for (auto const& output: outputsThatRequireBinaries) |
282 | 0 | if (isArtifactRequested(requests, output, false)) |
283 | 0 | return true; |
284 | 0 | return false; |
285 | 0 | } |
286 | | |
287 | | /// @returns true if EVM bytecode was requested, i.e. we have to run the old code generator. |
288 | | bool isEvmBytecodeRequested(Json const& _outputSelection) |
289 | 0 | { |
290 | 0 | if (!_outputSelection.is_object()) |
291 | 0 | return false; |
292 | | |
293 | 0 | static std::vector<std::string> const outputsThatRequireEvmBinaries = std::vector<std::string>{ |
294 | 0 | "*", |
295 | 0 | "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" |
296 | 0 | } + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode"); |
297 | |
|
298 | 0 | for (auto const& fileRequests: _outputSelection) |
299 | 0 | for (auto const& requests: fileRequests) |
300 | 0 | for (auto const& output: outputsThatRequireEvmBinaries) |
301 | 0 | if (isArtifactRequested(requests, output, false)) |
302 | 0 | return true; |
303 | 0 | return false; |
304 | 0 | } |
305 | | |
306 | | /// @returns true if any per-contract ethdebug program output was requested. |
307 | | bool isEthdebugProgramRequested(Json const& _outputSelection) |
308 | 0 | { |
309 | 0 | if (!_outputSelection.is_object()) |
310 | 0 | return false; |
311 | | |
312 | 0 | static std::array constexpr ethdebugArtifacts{"evm.bytecode.ethdebug", "evm.deployedBytecode.ethdebug"}; |
313 | |
|
314 | 0 | return areArtifactsRequested(_outputSelection, ethdebugArtifacts); |
315 | 0 | } |
316 | | |
317 | | /// @returns true if any ethdebug output (global or per-contract) was requested. |
318 | | bool isAnyEthdebugRequested(Json const& _outputSelection) |
319 | 0 | { |
320 | 0 | if (!_outputSelection.is_object()) |
321 | 0 | return false; |
322 | | |
323 | 0 | static std::array constexpr ethdebugArtifacts{ |
324 | 0 | "evm.bytecode.ethdebug", "evm.deployedBytecode.ethdebug", |
325 | 0 | "ethdebug.resources", "ethdebug.compilation" |
326 | 0 | }; |
327 | |
|
328 | 0 | return areArtifactsRequested(_outputSelection, ethdebugArtifacts); |
329 | 0 | } |
330 | | |
331 | | /// @returns true if the given global ethdebug output was requested via */*. |
332 | | bool isEthdebugGlobalOutputRequested(Json const& _outputSelection, std::string const& _artifact) |
333 | 0 | { |
334 | 0 | if (!_outputSelection.is_object()) |
335 | 0 | return false; |
336 | | |
337 | 0 | static std::array constexpr globalEthdebugArtifacts{"ethdebug.resources", "ethdebug.compilation"}; |
338 | 0 | if (!ranges::contains(globalEthdebugArtifacts, _artifact)) |
339 | 0 | return false; |
340 | | |
341 | 0 | for (auto const& [file, contracts]: _outputSelection.items()) |
342 | 0 | if (file == "*" && contracts.is_object()) |
343 | 0 | for (auto const& [contract, requests]: contracts.items()) |
344 | 0 | if (contract == "*" && requests.is_array()) |
345 | 0 | for (auto const& request: requests) |
346 | 0 | if (request.get<std::string>() == _artifact) |
347 | 0 | return true; |
348 | | |
349 | 0 | return false; |
350 | 0 | } |
351 | | |
352 | | bool isExperimentalArtifactRequested(Json const& _outputSelection) |
353 | 0 | { |
354 | 0 | static std::array constexpr experimentalArtifacts{"irAst", "irOptimizedAst"}; |
355 | |
|
356 | 0 | if (isAnyEthdebugRequested(_outputSelection)) |
357 | 0 | return true; |
358 | | |
359 | 0 | return areArtifactsRequested(_outputSelection, experimentalArtifacts); |
360 | 0 | } |
361 | | |
362 | | /// @returns The set of selected contracts, along with their compiler pipeline configuration, based |
363 | | /// on outputs requested in the JSON. Translates wildcards to the ones understood by CompilerStack. |
364 | | /// Note that as an exception, '*' does not yet match "ir", "irAst", "irOptimized" or "irOptimizedAst". |
365 | | CompilerStack::ContractSelection pipelineConfig( |
366 | | Json const& _jsonOutputSelection |
367 | | ) |
368 | 0 | { |
369 | 0 | if (!_jsonOutputSelection.is_object()) |
370 | 0 | return {}; |
371 | | |
372 | 0 | CompilerStack::ContractSelection contractSelection; |
373 | 0 | for (auto const& [sourceUnitName, jsonOutputSelectionForSource]: _jsonOutputSelection.items()) |
374 | 0 | { |
375 | 0 | solAssert(jsonOutputSelectionForSource.is_object()); |
376 | 0 | for (auto const& [contractName, jsonOutputSelectionForContract]: jsonOutputSelectionForSource.items()) |
377 | 0 | { |
378 | 0 | solAssert(jsonOutputSelectionForContract.is_array()); |
379 | 0 | CompilerStack::PipelineConfig pipelineForContract; |
380 | 0 | for (Json const& request: jsonOutputSelectionForContract) |
381 | 0 | { |
382 | 0 | solAssert(request.is_string()); |
383 | 0 | pipelineForContract.irOptimization = |
384 | 0 | pipelineForContract.irOptimization || |
385 | 0 | request == "irOptimized" || |
386 | 0 | request == "irOptimizedAst"; |
387 | 0 | pipelineForContract.irCodegen = |
388 | 0 | pipelineForContract.irCodegen || |
389 | 0 | pipelineForContract.irOptimization || |
390 | 0 | request == "ir" || |
391 | 0 | request == "irAst"; |
392 | 0 | pipelineForContract.bytecode = isEvmBytecodeRequested(_jsonOutputSelection); |
393 | 0 | } |
394 | 0 | std::string key = (sourceUnitName == "*") ? "" : sourceUnitName; |
395 | 0 | std::string value = (contractName == "*") ? "" : contractName; |
396 | 0 | contractSelection[key][value] = pipelineForContract; |
397 | 0 | } |
398 | 0 | } |
399 | 0 | return contractSelection; |
400 | 0 | } |
401 | | |
402 | | Json formatLinkReferences(std::map<size_t, std::string> const& linkReferences) |
403 | 0 | { |
404 | 0 | Json ret = Json::object(); |
405 | |
|
406 | 0 | for (auto const& ref: linkReferences) |
407 | 0 | { |
408 | 0 | std::string const& fullname = ref.second; |
409 | | |
410 | | // If the link reference does not contain a colon, assume that the file name is missing and |
411 | | // the whole string represents the library name. |
412 | 0 | size_t colon = fullname.rfind(':'); |
413 | 0 | std::string file = (colon != std::string::npos ? fullname.substr(0, colon) : ""); |
414 | 0 | std::string name = (colon != std::string::npos ? fullname.substr(colon + 1) : fullname); |
415 | |
|
416 | 0 | Json fileObject = ret.value(file, Json::object()); |
417 | 0 | Json libraryArray = fileObject.value(name, Json::array()); |
418 | |
|
419 | 0 | Json entry; |
420 | 0 | entry["start"] = Json(ref.first); |
421 | 0 | entry["length"] = 20; |
422 | |
|
423 | 0 | libraryArray.emplace_back(entry); |
424 | 0 | fileObject[name] = libraryArray; |
425 | 0 | ret[file] = fileObject; |
426 | 0 | } |
427 | |
|
428 | 0 | return ret; |
429 | 0 | } |
430 | | |
431 | | Json formatImmutableReferences(std::map<u256, evmasm::LinkerObject::ImmutableRefs> const& _immutableReferences) |
432 | 0 | { |
433 | 0 | Json ret = Json::object(); |
434 | |
|
435 | 0 | for (auto const& immutableReference: _immutableReferences) |
436 | 0 | { |
437 | 0 | auto const& [identifier, byteOffsets] = immutableReference.second; |
438 | 0 | Json array = Json::array(); |
439 | 0 | for (size_t byteOffset: byteOffsets) |
440 | 0 | { |
441 | 0 | Json byteRange; |
442 | 0 | byteRange["start"] = Json::number_unsigned_t(byteOffset); |
443 | 0 | byteRange["length"] = Json::number_unsigned_t(32); // immutable references are currently always 32 bytes wide |
444 | 0 | array.emplace_back(byteRange); |
445 | 0 | } |
446 | 0 | ret[identifier] = array; |
447 | 0 | } |
448 | |
|
449 | 0 | return ret; |
450 | 0 | } |
451 | | |
452 | | std::optional<Json> checkKeys(Json const& _input, std::set<std::string> const& _keys, std::string const& _name) |
453 | 0 | { |
454 | 0 | if (!_input.empty() && !_input.is_object()) |
455 | 0 | return formatFatalError(Error::Type::JSONError, "\"" + _name + "\" must be an object"); |
456 | | |
457 | 0 | for (auto const& [member, _]: _input.items()) |
458 | 0 | if (!_keys.count(member)) |
459 | 0 | return formatFatalError(Error::Type::JSONError, "Unknown key \"" + member + "\""); |
460 | | |
461 | 0 | return std::nullopt; |
462 | 0 | } |
463 | | |
464 | | std::optional<Json> checkRootKeys(Json const& _input) |
465 | 0 | { |
466 | 0 | static std::set<std::string> keys{"auxiliaryInput", "language", "settings", "sources"}; |
467 | 0 | return checkKeys(_input, keys, "root"); |
468 | 0 | } |
469 | | |
470 | | std::optional<Json> checkSourceKeys(Json const& _input, std::string const& _name) |
471 | 0 | { |
472 | 0 | static std::set<std::string> keys{"content", "keccak256", "urls"}; |
473 | 0 | return checkKeys(_input, keys, "sources." + _name); |
474 | 0 | } |
475 | | |
476 | | std::optional<Json> checkAuxiliaryInputKeys(Json const& _input) |
477 | 0 | { |
478 | 0 | static std::set<std::string> keys{"smtlib2responses"}; |
479 | 0 | return checkKeys(_input, keys, "auxiliaryInput"); |
480 | 0 | } |
481 | | |
482 | | std::optional<Json> checkSettingsKeys(Json const& _input) |
483 | 0 | { |
484 | 0 | static std::set<std::string> keys{"debug", "evmVersion", "experimental", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR", "viaSSACFG"}; |
485 | 0 | return checkKeys(_input, keys, "settings"); |
486 | 0 | } |
487 | | |
488 | | std::optional<Json> checkModelCheckerSettingsKeys(Json const& _input) |
489 | 0 | { |
490 | 0 | static std::set<std::string> keys{"bmcLoopIterations", "contracts", "divModNoSlacks", "engine", "extCalls", "invariants", "printQuery", "showProvedSafe", "showUnproved", "showUnsupported", "solvers", "targets", "timeout"}; |
491 | 0 | return checkKeys(_input, keys, "modelChecker"); |
492 | 0 | } |
493 | | |
494 | | std::optional<Json> checkOptimizerKeys(Json const& _input) |
495 | 0 | { |
496 | 0 | static std::set<std::string> keys{"details", "enabled", "runs"}; |
497 | 0 | return checkKeys(_input, keys, "settings.optimizer"); |
498 | 0 | } |
499 | | |
500 | | std::optional<Json> checkOptimizerDetailsKeys(Json const& _input) |
501 | 0 | { |
502 | 0 | static std::set<std::string> keys{"peephole", "inliner", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails", "simpleCounterForLoopUncheckedIncrement"}; |
503 | 0 | return checkKeys(_input, keys, "settings.optimizer.details"); |
504 | 0 | } |
505 | | |
506 | | std::optional<Json> checkOptimizerDetail(Json const& _details, std::string const& _name, bool& _setting) |
507 | 0 | { |
508 | 0 | if (_details.contains(_name)) |
509 | 0 | { |
510 | 0 | if (!_details[_name].is_boolean()) |
511 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.optimizer.details." + _name + "\" must be Boolean"); |
512 | 0 | _setting = _details[_name].get<bool>(); |
513 | 0 | } |
514 | 0 | return {}; |
515 | 0 | } |
516 | | |
517 | | std::optional<Json> checkOptimizerDetailSteps(Json const& _details, std::string const& _name, std::string& _optimiserSetting, std::string& _cleanupSetting, bool _runYulOptimizer) |
518 | 0 | { |
519 | 0 | if (_details.contains(_name)) |
520 | 0 | { |
521 | 0 | if (_details[_name].is_string()) |
522 | 0 | { |
523 | 0 | std::string const fullSequence = _details[_name].get<std::string>(); |
524 | 0 | if (!_runYulOptimizer && !OptimiserSuite::isEmptyOptimizerSequence(fullSequence)) |
525 | 0 | { |
526 | 0 | std::string errorMessage = |
527 | 0 | "If Yul optimizer is disabled, only an empty optimizerSteps sequence is accepted." |
528 | 0 | " Note that the empty optimizer sequence is properly denoted by \":\"."; |
529 | 0 | return formatFatalError(Error::Type::JSONError, errorMessage); |
530 | 0 | } |
531 | | |
532 | 0 | try |
533 | 0 | { |
534 | 0 | yul::OptimiserSuite::validateSequence(_details[_name].get<std::string>()); |
535 | 0 | } |
536 | 0 | catch (yul::OptimizerException const& _exception) |
537 | 0 | { |
538 | 0 | return formatFatalError( |
539 | 0 | Error::Type::JSONError, |
540 | 0 | "Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what() |
541 | 0 | ); |
542 | 0 | } |
543 | | |
544 | 0 | auto const delimiterPos = fullSequence.find(":"); |
545 | 0 | _optimiserSetting = fullSequence.substr(0, delimiterPos); |
546 | |
|
547 | 0 | if (delimiterPos != std::string::npos) |
548 | 0 | _cleanupSetting = fullSequence.substr(delimiterPos + 1); |
549 | 0 | else |
550 | 0 | solAssert(_cleanupSetting == OptimiserSettings::DefaultYulOptimiserCleanupSteps); |
551 | 0 | } |
552 | 0 | else |
553 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.optimizer.details." + _name + "\" must be a string"); |
554 | |
|
555 | 0 | } |
556 | 0 | return {}; |
557 | 0 | } |
558 | | |
559 | | std::optional<Json> checkMetadataKeys(Json const& _input) |
560 | 0 | { |
561 | 0 | if (_input.is_object()) |
562 | 0 | { |
563 | 0 | if (_input.contains("appendCBOR") && !_input["appendCBOR"].is_boolean()) |
564 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.metadata.appendCBOR\" must be Boolean"); |
565 | 0 | if (_input.contains("useLiteralContent") && !_input["useLiteralContent"].is_boolean()) |
566 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.metadata.useLiteralContent\" must be Boolean"); |
567 | | |
568 | 0 | static std::set<std::string> hashes{"ipfs", "bzzr1", "none"}; |
569 | 0 | if (_input.contains("bytecodeHash") && !hashes.count(_input["bytecodeHash"].get<std::string>())) |
570 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.metadata.bytecodeHash\" must be \"ipfs\", \"bzzr1\" or \"none\""); |
571 | 0 | } |
572 | 0 | static std::set<std::string> keys{"appendCBOR", "useLiteralContent", "bytecodeHash"}; |
573 | 0 | return checkKeys(_input, keys, "settings.metadata"); |
574 | 0 | } |
575 | | |
576 | | std::optional<Json> checkOutputSelection(Json const& _outputSelection) |
577 | 0 | { |
578 | 0 | if (!_outputSelection.empty() && !_outputSelection.is_object()) |
579 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.outputSelection\" must be an object"); |
580 | | |
581 | 0 | for (auto const& [sourceName, sourceVal]: _outputSelection.items()) |
582 | 0 | { |
583 | 0 | if (!sourceVal.is_object()) |
584 | 0 | return formatFatalError( |
585 | 0 | Error::Type::JSONError, |
586 | 0 | "\"settings.outputSelection." + sourceName + "\" must be an object" |
587 | 0 | ); |
588 | | |
589 | 0 | for (auto const& [contractName, contractVal]: sourceVal.items()) |
590 | 0 | { |
591 | 0 | if (!contractVal.is_array()) |
592 | 0 | return formatFatalError( |
593 | 0 | Error::Type::JSONError, |
594 | 0 | "\"settings.outputSelection." + |
595 | 0 | sourceName + |
596 | 0 | "." + |
597 | 0 | contractName + |
598 | 0 | "\" must be a string array" |
599 | 0 | ); |
600 | | |
601 | 0 | for (auto const& output: contractVal) |
602 | 0 | if (!output.is_string()) |
603 | 0 | return formatFatalError( |
604 | 0 | Error::Type::JSONError, |
605 | 0 | "\"settings.outputSelection." + |
606 | 0 | sourceName + |
607 | 0 | "." + |
608 | 0 | contractName + |
609 | 0 | "\" must be a string array" |
610 | 0 | ); |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | 0 | return std::nullopt; |
615 | 0 | } |
616 | | |
617 | | /// Validates the optimizer settings and returns them in a parsed object. |
618 | | /// On error returns the json-formatted error message. |
619 | | std::variant<OptimiserSettings, Json> parseOptimizerSettings(std::string_view const _language, Json const& _jsonInput) |
620 | 0 | { |
621 | 0 | if (auto result = checkOptimizerKeys(_jsonInput)) |
622 | 0 | return *result; |
623 | | |
624 | 0 | OptimiserSettings settings = _language == "EVMAssembly" ? OptimiserSettings::none() : OptimiserSettings::minimal(); |
625 | |
|
626 | 0 | if (_jsonInput.contains("enabled")) |
627 | 0 | { |
628 | 0 | if (!_jsonInput["enabled"].is_boolean()) |
629 | 0 | return formatFatalError(Error::Type::JSONError, "The \"enabled\" setting must be a Boolean."); |
630 | | |
631 | 0 | if (_jsonInput["enabled"].get<bool>()) |
632 | 0 | settings = OptimiserSettings::standard(); |
633 | 0 | } |
634 | | |
635 | 0 | if (_jsonInput.contains("runs")) |
636 | 0 | { |
637 | 0 | if (!_jsonInput["runs"].is_number_unsigned()) |
638 | 0 | return formatFatalError(Error::Type::JSONError, "The \"runs\" setting must be an unsigned number."); |
639 | 0 | static_assert(std::is_same_v<decltype(settings.expectedExecutionsPerDeployment), Json::number_unsigned_t>); |
640 | 0 | settings.expectedExecutionsPerDeployment = _jsonInput["runs"].get<Json::number_unsigned_t>(); |
641 | 0 | } |
642 | | |
643 | 0 | if (_jsonInput.contains("details")) |
644 | 0 | { |
645 | 0 | Json const& details = _jsonInput["details"]; |
646 | 0 | if (auto result = checkOptimizerDetailsKeys(details)) |
647 | 0 | return *result; |
648 | | |
649 | 0 | if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) |
650 | 0 | return *error; |
651 | 0 | if (auto error = checkOptimizerDetail(details, "inliner", settings.runInliner)) |
652 | 0 | return *error; |
653 | 0 | if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) |
654 | 0 | return *error; |
655 | 0 | if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) |
656 | 0 | return *error; |
657 | 0 | if (auto error = checkOptimizerDetail(details, "deduplicate", settings.runDeduplicate)) |
658 | 0 | return *error; |
659 | 0 | if (auto error = checkOptimizerDetail(details, "cse", settings.runCSE)) |
660 | 0 | return *error; |
661 | 0 | if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) |
662 | 0 | return *error; |
663 | 0 | if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) |
664 | 0 | return *error; |
665 | 0 | if (auto error = checkOptimizerDetail(details, "simpleCounterForLoopUncheckedIncrement", settings.simpleCounterForLoopUncheckedIncrement)) |
666 | 0 | return *error; |
667 | 0 | settings.optimizeStackAllocation = settings.runYulOptimiser; |
668 | 0 | if (details.contains("yulDetails")) |
669 | 0 | { |
670 | 0 | if (!settings.runYulOptimiser) |
671 | 0 | { |
672 | 0 | if (checkKeys(details["yulDetails"], {"optimizerSteps"}, "settings.optimizer.details.yulDetails")) |
673 | 0 | return formatFatalError(Error::Type::JSONError, "Only optimizerSteps can be set in yulDetails when Yul optimizer is disabled."); |
674 | 0 | if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps, settings.yulOptimiserCleanupSteps, settings.runYulOptimiser)) |
675 | 0 | return *error; |
676 | 0 | return {std::move(settings)}; |
677 | 0 | } |
678 | | |
679 | 0 | if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails")) |
680 | 0 | return *result; |
681 | 0 | if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) |
682 | 0 | return *error; |
683 | 0 | if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps, settings.yulOptimiserCleanupSteps, settings.runYulOptimiser)) |
684 | 0 | return *error; |
685 | 0 | } |
686 | 0 | } |
687 | 0 | return {std::move(settings)}; |
688 | 0 | } |
689 | | |
690 | | } |
691 | | |
692 | | std::variant<StandardCompiler::InputsAndSettings, Json> StandardCompiler::parseInput(Json const& _input) |
693 | 0 | { |
694 | 0 | InputsAndSettings ret; |
695 | |
|
696 | 0 | if (!_input.is_object()) |
697 | 0 | return formatFatalError(Error::Type::JSONError, "Input is not a JSON object."); |
698 | | |
699 | 0 | if (auto result = checkRootKeys(_input)) |
700 | 0 | return *result; |
701 | | |
702 | 0 | ret.language = _input.value<std::string>("language", ""); |
703 | |
|
704 | 0 | Json const& sources = _input.value<Json>("sources", Json()); |
705 | |
|
706 | 0 | if (!sources.is_object() && !sources.is_null()) |
707 | 0 | return formatFatalError(Error::Type::JSONError, "\"sources\" is not a JSON object."); |
708 | | |
709 | 0 | if (sources.empty()) |
710 | 0 | return formatFatalError(Error::Type::JSONError, "No input sources specified."); |
711 | | |
712 | 0 | ret.errors = Json::array(); |
713 | |
|
714 | 0 | if (ret.language == "Solidity" || ret.language == "Yul") |
715 | 0 | { |
716 | 0 | for (auto const& [sourceName, sourceValue]: sources.items()) |
717 | 0 | { |
718 | 0 | std::string hash; |
719 | |
|
720 | 0 | if (auto result = checkSourceKeys(sourceValue, sourceName)) |
721 | 0 | return *result; |
722 | | |
723 | 0 | if (sourceValue.contains("keccak256") && sourceValue["keccak256"].is_string()) |
724 | 0 | hash = sourceValue["keccak256"].get<std::string>(); |
725 | |
|
726 | 0 | if (sourceValue.contains("content") && sourceValue["content"].is_string()) |
727 | 0 | { |
728 | 0 | std::string content = sourceValue["content"].get<std::string>(); |
729 | 0 | if (!hash.empty() && !hashMatchesContent(hash, content)) |
730 | 0 | ret.errors.emplace_back(formatError( |
731 | 0 | Error::Type::IOError, |
732 | 0 | "general", |
733 | 0 | "Mismatch between content and supplied hash for \"" + sourceName + "\"" |
734 | 0 | )); |
735 | 0 | else |
736 | 0 | ret.sources[sourceName] = content; |
737 | 0 | } |
738 | 0 | else if (sourceValue["urls"].is_array()) |
739 | 0 | { |
740 | 0 | if (!m_readFile) |
741 | 0 | return formatFatalError( |
742 | 0 | Error::Type::JSONError, "No import callback supplied, but URL is requested." |
743 | 0 | ); |
744 | | |
745 | 0 | std::vector<std::string> failures; |
746 | 0 | bool found = false; |
747 | |
|
748 | 0 | for (auto const& url: sourceValue["urls"]) |
749 | 0 | { |
750 | 0 | if (!url.is_string()) |
751 | 0 | return formatFatalError(Error::Type::JSONError, "URL must be a string."); |
752 | 0 | ReadCallback::Result result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), url.get<std::string>()); |
753 | 0 | if (result.success) |
754 | 0 | { |
755 | 0 | if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) |
756 | 0 | ret.errors.emplace_back(formatError( |
757 | 0 | Error::Type::IOError, |
758 | 0 | "general", |
759 | 0 | "Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.get<std::string>() + "\"" |
760 | 0 | )); |
761 | 0 | else |
762 | 0 | { |
763 | 0 | ret.sources[sourceName] = result.responseOrErrorMessage; |
764 | 0 | found = true; |
765 | 0 | break; |
766 | 0 | } |
767 | 0 | } |
768 | 0 | else |
769 | 0 | failures.push_back( |
770 | 0 | "Cannot import url (\"" + url.get<std::string>() + "\"): " + result.responseOrErrorMessage |
771 | 0 | ); |
772 | 0 | } |
773 | | |
774 | 0 | for (auto const& failure: failures) |
775 | 0 | { |
776 | | /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. |
777 | 0 | ret.errors.emplace_back(formatError( |
778 | 0 | found ? Error::Type::Warning : Error::Type::IOError, |
779 | 0 | "general", |
780 | 0 | failure |
781 | 0 | )); |
782 | 0 | } |
783 | 0 | } |
784 | 0 | else |
785 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid input source specified."); |
786 | 0 | } |
787 | 0 | } |
788 | 0 | else if (ret.language == "SolidityAST") |
789 | 0 | { |
790 | 0 | for (auto const& [sourceName, sourceValue]: sources.items()) |
791 | 0 | ret.sources[sourceName] = util::jsonCompactPrint(sourceValue); |
792 | 0 | } |
793 | 0 | else if (ret.language == "EVMAssembly") |
794 | 0 | { |
795 | 0 | for (auto const& [sourceName, sourceValue]: sources.items()) |
796 | 0 | { |
797 | 0 | solAssert(sources.contains(sourceName)); |
798 | 0 | if ( |
799 | 0 | !sourceValue.contains("assemblyJson") || |
800 | 0 | !sourceValue["assemblyJson"].is_object() || |
801 | 0 | sourceValue.size() != 1 |
802 | 0 | ) |
803 | 0 | return formatFatalError( |
804 | 0 | Error::Type::JSONError, |
805 | 0 | "Invalid input source specified. Expected exactly one object, named 'assemblyJson', inside $.sources." + sourceName |
806 | 0 | ); |
807 | | |
808 | 0 | ret.jsonSources[sourceName] = sourceValue["assemblyJson"]; |
809 | 0 | } |
810 | 0 | if (ret.jsonSources.size() != 1) |
811 | 0 | return formatFatalError( |
812 | 0 | Error::Type::JSONError, |
813 | 0 | "EVMAssembly import only supports exactly one input file." |
814 | 0 | ); |
815 | 0 | } |
816 | 0 | Json const& auxInputs = _input.value("auxiliaryInput", Json::object()); |
817 | |
|
818 | 0 | if (auto result = checkAuxiliaryInputKeys(auxInputs)) |
819 | 0 | return *result; |
820 | | |
821 | 0 | if (!auxInputs.empty()) |
822 | 0 | { |
823 | 0 | Json const& smtlib2Responses = auxInputs.value("smtlib2responses", Json::object()); |
824 | 0 | if (!smtlib2Responses.empty()) |
825 | 0 | { |
826 | 0 | if (!smtlib2Responses.is_object()) |
827 | 0 | return formatFatalError(Error::Type::JSONError, "\"auxiliaryInput.smtlib2responses\" must be an object."); |
828 | | |
829 | 0 | for (auto const& [hashString, response]: smtlib2Responses.items()) |
830 | 0 | { |
831 | 0 | util::h256 hash; |
832 | 0 | try |
833 | 0 | { |
834 | 0 | hash = util::h256(hashString); |
835 | 0 | } |
836 | 0 | catch (util::BadHexCharacter const&) |
837 | 0 | { |
838 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid hex encoding of SMTLib2 auxiliary input."); |
839 | 0 | } |
840 | | |
841 | 0 | if (!response.is_string()) |
842 | 0 | return formatFatalError( |
843 | 0 | Error::Type::JSONError, |
844 | 0 | "\"smtlib2Responses." + hashString + "\" must be a string." |
845 | 0 | ); |
846 | | |
847 | 0 | ret.smtLib2Responses[hash] = response.get<std::string>(); |
848 | 0 | } |
849 | 0 | } |
850 | 0 | } |
851 | | |
852 | 0 | Json const& settings = _input.value("settings", Json::object()); |
853 | |
|
854 | 0 | if (auto result = checkSettingsKeys(settings)) |
855 | 0 | return *result; |
856 | | |
857 | 0 | if (settings.contains("experimental")) |
858 | 0 | { |
859 | 0 | if (!settings["experimental"].is_boolean()) |
860 | 0 | return formatFatalError(Error::Type::JSONError, "'settings.experimental' must be a Boolean."); |
861 | 0 | ret.experimental = settings["experimental"].get<bool>(); |
862 | 0 | } |
863 | | |
864 | 0 | if (settings.contains("stopAfter")) |
865 | 0 | { |
866 | 0 | if (!settings["stopAfter"].is_string()) |
867 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.stopAfter\" must be a string."); |
868 | | |
869 | 0 | if (settings["stopAfter"].get<std::string>() != "parsing") |
870 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid value for \"settings.stopAfter\". Only valid value is \"parsing\"."); |
871 | | |
872 | 0 | ret.stopAfter = CompilerStack::State::Parsed; |
873 | 0 | } |
874 | | |
875 | 0 | if (settings.contains("viaIR")) |
876 | 0 | { |
877 | 0 | if (!settings["viaIR"].is_boolean()) |
878 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.viaIR\" must be a Boolean."); |
879 | 0 | ret.viaIR = settings["viaIR"].get<bool>(); |
880 | 0 | } |
881 | | |
882 | 0 | if (settings.contains("viaSSACFG")) |
883 | 0 | { |
884 | 0 | if (!settings["viaSSACFG"].is_boolean()) |
885 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.viaSSACFG\" must be a Boolean."); |
886 | 0 | ret.viaSSACFG = settings["viaSSACFG"].get<bool>(); |
887 | 0 | if (ret.viaSSACFG) |
888 | 0 | { |
889 | 0 | if (settings.contains("viaIR") && !ret.viaIR) |
890 | 0 | return formatFatalError( |
891 | 0 | Error::Type::JSONError, |
892 | 0 | "\"settings.viaSSACFG\" requires compilation via IR." |
893 | 0 | ); |
894 | 0 | ret.viaIR = true; |
895 | 0 | } |
896 | 0 | } |
897 | | |
898 | 0 | if (settings.contains("evmVersion")) |
899 | 0 | { |
900 | 0 | if (!settings["evmVersion"].is_string()) |
901 | 0 | return formatFatalError(Error::Type::JSONError, "evmVersion must be a string."); |
902 | 0 | std::optional<langutil::EVMVersion> version = langutil::EVMVersion::fromString(settings["evmVersion"].get<std::string>()); |
903 | 0 | if (!version) |
904 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid EVM version requested."); |
905 | 0 | if (version < EVMVersion::london()) |
906 | 0 | ret.errors.emplace_back(formatError( |
907 | 0 | Error::Type::Warning, |
908 | 0 | "general", |
909 | 0 | "Support for EVM versions older than london is deprecated and will be removed in the future." |
910 | 0 | )); |
911 | 0 | if (version->isExperimental() && !ret.experimental) |
912 | 0 | return formatFatalError( |
913 | 0 | Error::Type::JSONError, |
914 | 0 | fmt::format("EVM version '{}' is experimental and can only be used with the 'settings.experimental' option enabled.", version->name()) |
915 | 0 | ); |
916 | 0 | ret.evmVersion = *version; |
917 | 0 | } |
918 | | |
919 | 0 | if (settings.contains("debug")) |
920 | 0 | { |
921 | 0 | if (auto result = checkKeys(settings["debug"], {"revertStrings", "debugInfo"}, "settings.debug")) |
922 | 0 | return *result; |
923 | | |
924 | 0 | if (settings["debug"].contains("revertStrings")) |
925 | 0 | { |
926 | 0 | if (!settings["debug"]["revertStrings"].is_string()) |
927 | 0 | return formatFatalError(Error::Type::JSONError, "settings.debug.revertStrings must be a string."); |
928 | 0 | std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].get<std::string>()); |
929 | 0 | if (!revertStrings) |
930 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid value for settings.debug.revertStrings."); |
931 | 0 | if (*revertStrings == RevertStrings::VerboseDebug) |
932 | 0 | return formatFatalError( |
933 | 0 | Error::Type::UnimplementedFeatureError, |
934 | 0 | "Only \"default\", \"strip\" and \"debug\" are implemented for settings.debug.revertStrings for now." |
935 | 0 | ); |
936 | 0 | ret.revertStrings = *revertStrings; |
937 | 0 | } |
938 | | |
939 | 0 | if (settings["debug"].contains("debugInfo")) |
940 | 0 | { |
941 | 0 | if (!settings["debug"]["debugInfo"].is_array()) |
942 | 0 | return formatFatalError(Error::Type::JSONError, "settings.debug.debugInfo must be an array."); |
943 | | |
944 | 0 | std::vector<std::string> components; |
945 | 0 | for (Json const& arrayValue: settings["debug"]["debugInfo"]) |
946 | 0 | components.push_back(arrayValue.get<std::string>()); |
947 | |
|
948 | 0 | std::optional<DebugInfoSelection> debugInfoSelection = DebugInfoSelection::fromComponents( |
949 | 0 | components, |
950 | 0 | true /* _acceptWildcards */ |
951 | 0 | ); |
952 | 0 | if (!debugInfoSelection.has_value()) |
953 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid value in settings.debug.debugInfo."); |
954 | | |
955 | 0 | if (debugInfoSelection->snippet && !debugInfoSelection->location) |
956 | 0 | return formatFatalError( |
957 | 0 | Error::Type::JSONError, |
958 | 0 | "To use 'snippet' with settings.debug.debugInfo you must select also 'location'." |
959 | 0 | ); |
960 | 0 | if (debugInfoSelection->ethdebug) |
961 | 0 | { |
962 | 0 | if (!ret.experimental) |
963 | 0 | return formatFatalError(Error::Type::FatalError, "Ethdebug annotations are experimental and can only be included in 'settings.debug.debugInfo' by enabling the 'settings.experimental' option."); |
964 | 0 | if (!debugInfoSelection->astID) |
965 | 0 | return formatFatalError( |
966 | 0 | Error::Type::JSONError, |
967 | 0 | "To use 'ethdebug' with settings.debug.debugInfo you must select also 'ast-id'." |
968 | 0 | ); |
969 | 0 | } |
970 | 0 | ret.debugInfoSelection = debugInfoSelection.value(); |
971 | 0 | } |
972 | 0 | } |
973 | | |
974 | 0 | if (settings.contains("remappings") && !settings["remappings"].is_array()) |
975 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.remappings\" must be an array of strings."); |
976 | | |
977 | 0 | for (auto const& remapping: settings.value("remappings", Json::object())) |
978 | 0 | { |
979 | 0 | if (!remapping.is_string()) |
980 | 0 | return formatFatalError(Error::Type::JSONError, "\"settings.remappings\" must be an array of strings"); |
981 | 0 | if (auto r = ImportRemapper::parseRemapping(remapping.get<std::string>())) |
982 | 0 | ret.remappings.emplace_back(std::move(*r)); |
983 | 0 | else |
984 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid remapping: \"" + remapping.get<std::string>() + "\""); |
985 | 0 | } |
986 | | |
987 | 0 | if (settings.contains("optimizer")) |
988 | 0 | { |
989 | 0 | auto optimiserSettings = parseOptimizerSettings(ret.language, settings["optimizer"]); |
990 | 0 | if (std::holds_alternative<Json>(optimiserSettings)) |
991 | 0 | return std::get<Json>(std::move(optimiserSettings)); // was an error |
992 | 0 | else |
993 | 0 | ret.optimiserSettings = std::get<OptimiserSettings>(std::move(optimiserSettings)); |
994 | 0 | } |
995 | 0 | else if (ret.language == "EVMAssembly") |
996 | 0 | ret.optimiserSettings = OptimiserSettings::none(); |
997 | 0 | else |
998 | 0 | ret.optimiserSettings = OptimiserSettings::minimal(); |
999 | | |
1000 | 0 | Json const& jsonLibraries = settings.value("libraries", Json::object()); |
1001 | 0 | if (!jsonLibraries.is_object()) |
1002 | 0 | return formatFatalError(Error::Type::JSONError, "\"libraries\" is not a JSON object."); |
1003 | 0 | for (auto const& [sourceName, jsonSourceName]: jsonLibraries.items()) |
1004 | 0 | { |
1005 | 0 | if (!jsonSourceName.is_object()) |
1006 | 0 | return formatFatalError(Error::Type::JSONError, "Library entry is not a JSON object."); |
1007 | 0 | for (auto const& [library, libraryValue]: jsonSourceName.items()) |
1008 | 0 | { |
1009 | 0 | if (!libraryValue.is_string()) |
1010 | 0 | return formatFatalError(Error::Type::JSONError, "Library address must be a string."); |
1011 | 0 | std::string address = libraryValue.get<std::string>(); |
1012 | |
|
1013 | 0 | if (!boost::starts_with(address, "0x")) |
1014 | 0 | return formatFatalError( |
1015 | 0 | Error::Type::JSONError, |
1016 | 0 | "Library address is not prefixed with \"0x\"." |
1017 | 0 | ); |
1018 | | |
1019 | 0 | if (address.length() != 42) |
1020 | 0 | return formatFatalError( |
1021 | 0 | Error::Type::JSONError, |
1022 | 0 | "Library address is of invalid length." |
1023 | 0 | ); |
1024 | | |
1025 | 0 | try |
1026 | 0 | { |
1027 | 0 | ret.libraries[sourceName + ":" + library] = util::h160(address); |
1028 | 0 | } |
1029 | 0 | catch (util::BadHexCharacter const&) |
1030 | 0 | { |
1031 | 0 | return formatFatalError( |
1032 | 0 | Error::Type::JSONError, |
1033 | 0 | "Invalid library address (\"" + address + "\") supplied." |
1034 | 0 | ); |
1035 | 0 | } |
1036 | 0 | } |
1037 | 0 | } |
1038 | | |
1039 | 0 | Json const& metadataSettings = settings.value("metadata", Json::object()); |
1040 | |
|
1041 | 0 | if (auto result = checkMetadataKeys(metadataSettings)) |
1042 | 0 | return *result; |
1043 | | |
1044 | 0 | solAssert(CompilerStack::defaultMetadataFormat() != CompilerStack::MetadataFormat::NoMetadata, ""); |
1045 | 0 | ret.metadataFormat = |
1046 | 0 | metadataSettings.value("appendCBOR", Json(true)) ? |
1047 | 0 | CompilerStack::defaultMetadataFormat() : |
1048 | 0 | CompilerStack::MetadataFormat::NoMetadata; |
1049 | |
|
1050 | 0 | ret.metadataLiteralSources = |
1051 | 0 | metadataSettings.contains("useLiteralContent") && |
1052 | 0 | metadataSettings["useLiteralContent"].is_boolean() && |
1053 | 0 | metadataSettings["useLiteralContent"].get<bool>(); |
1054 | 0 | if (metadataSettings.contains("bytecodeHash")) |
1055 | 0 | { |
1056 | 0 | auto metadataHash = metadataSettings["bytecodeHash"].get<std::string>(); |
1057 | 0 | ret.metadataHash = |
1058 | 0 | metadataHash == "ipfs" ? |
1059 | 0 | CompilerStack::MetadataHash::IPFS : |
1060 | 0 | metadataHash == "bzzr1" ? |
1061 | 0 | CompilerStack::MetadataHash::Bzzr1 : |
1062 | 0 | CompilerStack::MetadataHash::None; |
1063 | 0 | if (ret.metadataFormat == CompilerStack::MetadataFormat::NoMetadata && ret.metadataHash != CompilerStack::MetadataHash::None) |
1064 | 0 | return formatFatalError( |
1065 | 0 | Error::Type::JSONError, |
1066 | 0 | "When the parameter \"appendCBOR\" is set to false, the parameter \"bytecodeHash\" cannot be set to \"" + |
1067 | 0 | metadataHash + |
1068 | 0 | "\". The parameter \"bytecodeHash\" should either be skipped, or set to \"none\"." |
1069 | 0 | ); |
1070 | 0 | } |
1071 | | |
1072 | 0 | Json const& outputSelection = settings.value("outputSelection", Json::object()); |
1073 | |
|
1074 | 0 | if (auto jsonError = checkOutputSelection(outputSelection)) |
1075 | 0 | return *jsonError; |
1076 | | |
1077 | 0 | ret.outputSelection = outputSelection; |
1078 | |
|
1079 | 0 | if (ret.stopAfter != CompilerStack::State::CompilationSuccessful && isBinaryRequested(ret.outputSelection)) |
1080 | 0 | return formatFatalError( |
1081 | 0 | Error::Type::JSONError, |
1082 | 0 | "Requested output selection conflicts with \"settings.stopAfter\"." |
1083 | 0 | ); |
1084 | | |
1085 | 0 | Json const& modelCheckerSettings = settings.value("modelChecker", Json::object()); |
1086 | |
|
1087 | 0 | if (auto result = checkModelCheckerSettingsKeys(modelCheckerSettings)) |
1088 | 0 | return *result; |
1089 | | |
1090 | 0 | if (modelCheckerSettings.contains("contracts")) |
1091 | 0 | { |
1092 | 0 | auto const& sources = modelCheckerSettings["contracts"]; |
1093 | 0 | if (!sources.is_object() && !sources.is_null()) |
1094 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.contracts is not a JSON object."); |
1095 | | |
1096 | 0 | std::map<std::string, std::set<std::string>> sourceContracts; |
1097 | 0 | for (auto const& [source, contracts]: sources.items()) |
1098 | 0 | { |
1099 | 0 | if (source.empty()) |
1100 | 0 | return formatFatalError(Error::Type::JSONError, "Source name cannot be empty."); |
1101 | | |
1102 | 0 | if (!contracts.is_array()) |
1103 | 0 | return formatFatalError(Error::Type::JSONError, "Source contracts must be an array."); |
1104 | | |
1105 | 0 | for (auto const& contract: contracts) |
1106 | 0 | { |
1107 | 0 | if (!contract.is_string()) |
1108 | 0 | return formatFatalError(Error::Type::JSONError, "Every contract in settings.modelChecker.contracts must be a string."); |
1109 | 0 | if (contract.get<std::string>().empty()) |
1110 | 0 | return formatFatalError(Error::Type::JSONError, "Contract name cannot be empty."); |
1111 | 0 | sourceContracts[source].insert(contract.get<std::string>()); |
1112 | 0 | } |
1113 | | |
1114 | 0 | if (sourceContracts[source].empty()) |
1115 | 0 | return formatFatalError(Error::Type::JSONError, "Source contracts must be a non-empty array."); |
1116 | 0 | } |
1117 | 0 | ret.modelCheckerSettings.contracts = {std::move(sourceContracts)}; |
1118 | 0 | } |
1119 | | |
1120 | 0 | if (modelCheckerSettings.contains("divModNoSlacks")) |
1121 | 0 | { |
1122 | 0 | auto const& divModNoSlacks = modelCheckerSettings["divModNoSlacks"]; |
1123 | 0 | if (!divModNoSlacks.is_boolean()) |
1124 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.divModNoSlacks must be a Boolean."); |
1125 | 0 | ret.modelCheckerSettings.divModNoSlacks = divModNoSlacks.get<bool>(); |
1126 | 0 | } |
1127 | | |
1128 | 0 | if (modelCheckerSettings.contains("engine")) |
1129 | 0 | { |
1130 | 0 | if (!modelCheckerSettings["engine"].is_string()) |
1131 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.engine must be a string."); |
1132 | 0 | std::optional<ModelCheckerEngine> engine = ModelCheckerEngine::fromString(modelCheckerSettings["engine"].get<std::string>()); |
1133 | 0 | if (!engine) |
1134 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid model checker engine requested."); |
1135 | 0 | ret.modelCheckerSettings.engine = *engine; |
1136 | 0 | } |
1137 | | |
1138 | 0 | if (modelCheckerSettings.contains("bmcLoopIterations")) |
1139 | 0 | { |
1140 | 0 | if (!ret.modelCheckerSettings.engine.bmc) |
1141 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.bmcLoopIterations requires the BMC engine to be enabled."); |
1142 | 0 | if (modelCheckerSettings["bmcLoopIterations"].is_number_unsigned()) |
1143 | 0 | ret.modelCheckerSettings.bmcLoopIterations = modelCheckerSettings["bmcLoopIterations"].get<unsigned>(); |
1144 | 0 | else |
1145 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.bmcLoopIterations must be an unsigned integer."); |
1146 | 0 | } |
1147 | | |
1148 | 0 | if (modelCheckerSettings.contains("extCalls")) |
1149 | 0 | { |
1150 | 0 | if (!modelCheckerSettings["extCalls"].is_string()) |
1151 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.extCalls must be a string."); |
1152 | 0 | std::optional<ModelCheckerExtCalls> extCalls = ModelCheckerExtCalls::fromString(modelCheckerSettings["extCalls"].get<std::string>()); |
1153 | 0 | if (!extCalls) |
1154 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid model checker extCalls requested."); |
1155 | 0 | ret.modelCheckerSettings.externalCalls = *extCalls; |
1156 | 0 | } |
1157 | | |
1158 | 0 | if (modelCheckerSettings.contains("invariants")) |
1159 | 0 | { |
1160 | 0 | auto const& invariantsArray = modelCheckerSettings["invariants"]; |
1161 | 0 | if (!invariantsArray.is_array()) |
1162 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.invariants must be an array."); |
1163 | | |
1164 | 0 | ModelCheckerInvariants invariants; |
1165 | 0 | for (auto const& i: invariantsArray) |
1166 | 0 | { |
1167 | 0 | if (!i.is_string()) |
1168 | 0 | return formatFatalError(Error::Type::JSONError, "Every invariant type in settings.modelChecker.invariants must be a string."); |
1169 | 0 | if (!invariants.setFromString(i.get<std::string>())) |
1170 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid model checker invariants requested."); |
1171 | 0 | } |
1172 | | |
1173 | 0 | if (invariants.invariants.empty()) |
1174 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.invariants must be a non-empty array."); |
1175 | | |
1176 | 0 | ret.modelCheckerSettings.invariants = invariants; |
1177 | 0 | } |
1178 | | |
1179 | 0 | if (modelCheckerSettings.contains("showProvedSafe")) |
1180 | 0 | { |
1181 | 0 | auto const& showProvedSafe = modelCheckerSettings["showProvedSafe"]; |
1182 | 0 | if (!showProvedSafe.is_boolean()) |
1183 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.showProvedSafe must be a Boolean value."); |
1184 | 0 | ret.modelCheckerSettings.showProvedSafe = showProvedSafe.get<bool>(); |
1185 | 0 | } |
1186 | | |
1187 | 0 | if (modelCheckerSettings.contains("showUnproved")) |
1188 | 0 | { |
1189 | 0 | auto const& showUnproved = modelCheckerSettings["showUnproved"]; |
1190 | 0 | if (!showUnproved.is_boolean()) |
1191 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.showUnproved must be a Boolean value."); |
1192 | 0 | ret.modelCheckerSettings.showUnproved = showUnproved.get<bool>(); |
1193 | 0 | } |
1194 | | |
1195 | 0 | if (modelCheckerSettings.contains("showUnsupported")) |
1196 | 0 | { |
1197 | 0 | auto const& showUnsupported = modelCheckerSettings["showUnsupported"]; |
1198 | 0 | if (!showUnsupported.is_boolean()) |
1199 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.showUnsupported must be a Boolean value."); |
1200 | 0 | ret.modelCheckerSettings.showUnsupported = showUnsupported.get<bool>(); |
1201 | 0 | } |
1202 | | |
1203 | 0 | if (modelCheckerSettings.contains("solvers")) |
1204 | 0 | { |
1205 | 0 | auto const& solversArray = modelCheckerSettings["solvers"]; |
1206 | 0 | if (!solversArray.is_array()) |
1207 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.solvers must be an array."); |
1208 | | |
1209 | 0 | smtutil::SMTSolverChoice solvers; |
1210 | 0 | for (auto const& s: solversArray) |
1211 | 0 | { |
1212 | 0 | if (!s.is_string()) |
1213 | 0 | return formatFatalError(Error::Type::JSONError, "Every target in settings.modelChecker.solvers must be a string."); |
1214 | 0 | if (!solvers.setSolver(s.get<std::string>())) |
1215 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid model checker solvers requested."); |
1216 | 0 | } |
1217 | | |
1218 | 0 | ret.modelCheckerSettings.solvers = solvers; |
1219 | 0 | } |
1220 | | |
1221 | 0 | if (modelCheckerSettings.contains("printQuery")) |
1222 | 0 | { |
1223 | 0 | auto const& printQuery = modelCheckerSettings["printQuery"]; |
1224 | 0 | if (!printQuery.is_boolean()) |
1225 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.printQuery must be a Boolean value."); |
1226 | | |
1227 | 0 | ret.modelCheckerSettings.printQuery = printQuery.get<bool>(); |
1228 | 0 | } |
1229 | | |
1230 | 0 | if (modelCheckerSettings.contains("targets")) |
1231 | 0 | { |
1232 | 0 | auto const& targetsArray = modelCheckerSettings["targets"]; |
1233 | 0 | if (!targetsArray.is_array()) |
1234 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.targets must be an array."); |
1235 | | |
1236 | 0 | ModelCheckerTargets targets; |
1237 | 0 | for (auto const& t: targetsArray) |
1238 | 0 | { |
1239 | 0 | if (!t.is_string()) |
1240 | 0 | return formatFatalError(Error::Type::JSONError, "Every target in settings.modelChecker.targets must be a string."); |
1241 | 0 | if (!targets.setFromString(t.get<std::string>())) |
1242 | 0 | return formatFatalError(Error::Type::JSONError, "Invalid model checker targets requested."); |
1243 | 0 | } |
1244 | | |
1245 | 0 | if (targets.targets.empty()) |
1246 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.targets must be a non-empty array."); |
1247 | | |
1248 | 0 | ret.modelCheckerSettings.targets = targets; |
1249 | 0 | } |
1250 | | |
1251 | 0 | if (modelCheckerSettings.contains("timeout")) |
1252 | 0 | { |
1253 | 0 | if (!modelCheckerSettings["timeout"].is_number_unsigned()) |
1254 | 0 | return formatFatalError(Error::Type::JSONError, "settings.modelChecker.timeout must be an unsigned integer."); |
1255 | 0 | ret.modelCheckerSettings.timeout = modelCheckerSettings["timeout"].get<Json::number_unsigned_t>(); |
1256 | 0 | } |
1257 | | |
1258 | 0 | if ((ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug) || isAnyEthdebugRequested(ret.outputSelection)) |
1259 | 0 | { |
1260 | 0 | if (ret.language != "Solidity" && ret.language != "Yul") |
1261 | 0 | return formatFatalError(Error::Type::FatalError, "'settings.debug.debugInfo' 'ethdebug' is only supported for languages 'Solidity' and 'Yul'."); |
1262 | 0 | } |
1263 | | |
1264 | 0 | if (isEthdebugProgramRequested(ret.outputSelection) && ret.language == "Solidity" && !ret.viaIR) |
1265 | 0 | return formatFatalError(Error::Type::FatalError, "'evm.bytecode.ethdebug' or 'evm.deployedBytecode.ethdebug' can only be selected as output, if 'viaIR' was set."); |
1266 | | |
1267 | 0 | bool const ethdebugSelected = ret.debugInfoSelection.has_value() && ret.debugInfoSelection->ethdebug; |
1268 | 0 | if (ethdebugSelected && ret.optimiserSettings.runYulOptimiser) |
1269 | 0 | solUnimplemented("Optimization is not yet supported with ethdebug."); |
1270 | | // The SSA CFG code generator attaches no source locations, which the |
1271 | | // program output consists of, so the output is gated as well. |
1272 | 0 | if ((ethdebugSelected || isEthdebugProgramRequested(ret.outputSelection)) && ret.viaSSACFG) |
1273 | 0 | solUnimplemented("SSA CFG codegen does not yet support ethdebug."); |
1274 | |
|
1275 | 0 | if (!ret.experimental) |
1276 | 0 | { |
1277 | 0 | if (ret.language == "SolidityAST" || ret.language == "EVMAssembly") |
1278 | 0 | return formatFatalError(Error::Type::FatalError, "'SolidityAST' and 'EVMAssembly' inputs are experimental and can only be used with the 'settings.experimental' option enabled."); |
1279 | | |
1280 | 0 | if (ret.evmVersion.isExperimental()) |
1281 | | // TODO: Cover with test when the Amsterdam version is introduced |
1282 | 0 | return formatFatalError(Error::Type::FatalError, fmt::format("EVM version '{}' is experimental and can only be used with the 'settings.experimental' option enabled.", ret.evmVersion.name())); |
1283 | | |
1284 | 0 | if (isExperimentalArtifactRequested(ret.outputSelection)) |
1285 | 0 | return formatFatalError(Error::Type::FatalError, "'irAst', 'irOptimizedAst', and 'ethdebug' outputs are experimental and can only be used with the 'settings.experimental' option enabled."); |
1286 | | |
1287 | 0 | if (ret.viaSSACFG) |
1288 | 0 | return formatFatalError(Error::Type::FatalError, "'viaSSACFG' setting is experimental and can only be used with the 'settings.experimental' option enabled."); |
1289 | 0 | } |
1290 | | |
1291 | 0 | return {std::move(ret)}; |
1292 | 0 | } |
1293 | | |
1294 | | std::map<std::string, Json> StandardCompiler::parseAstFromInput(StringMap const& _sources) |
1295 | 0 | { |
1296 | 0 | std::map<std::string, Json> sourceJsons; |
1297 | 0 | for (auto const& [sourceName, sourceCode]: _sources) |
1298 | 0 | { |
1299 | 0 | Json ast; |
1300 | 0 | astAssert(util::jsonParseStrict(sourceCode, ast), "Input file could not be parsed to JSON"); |
1301 | 0 | std::string astKey = ast.contains("ast") ? "ast" : "AST"; |
1302 | |
|
1303 | 0 | astAssert(ast.contains(astKey), "astkey is not member"); |
1304 | 0 | astAssert(ast[astKey]["nodeType"].get<std::string>() == "SourceUnit", "Top-level node should be a 'SourceUnit'"); |
1305 | 0 | astAssert(sourceJsons.count(sourceName) == 0, "All sources must have unique names"); |
1306 | 0 | sourceJsons.emplace(sourceName, std::move(ast[astKey])); |
1307 | 0 | } |
1308 | 0 | return sourceJsons; |
1309 | 0 | } |
1310 | | |
1311 | | Json StandardCompiler::importEVMAssembly(StandardCompiler::InputsAndSettings _inputsAndSettings) |
1312 | 0 | { |
1313 | 0 | solAssert(_inputsAndSettings.language == "EVMAssembly"); |
1314 | 0 | solAssert(_inputsAndSettings.sources.empty()); |
1315 | 0 | solAssert(_inputsAndSettings.jsonSources.size() == 1); |
1316 | 0 | solAssert(_inputsAndSettings.experimental); |
1317 | |
|
1318 | 0 | if (!isBinaryRequested(_inputsAndSettings.outputSelection)) |
1319 | 0 | return Json::object(); |
1320 | | |
1321 | 0 | evmasm::EVMAssemblyStack stack( |
1322 | 0 | _inputsAndSettings.evmVersion, |
1323 | 0 | evmasm::Assembly::OptimiserSettings::translateSettings( |
1324 | 0 | _inputsAndSettings.optimiserSettings |
1325 | 0 | ) |
1326 | 0 | ); |
1327 | 0 | std::string const& sourceName = _inputsAndSettings.jsonSources.begin()->first; // result of structured binding can only be used within lambda from C++20 on. |
1328 | 0 | Json const& sourceJson = _inputsAndSettings.jsonSources.begin()->second; |
1329 | 0 | try |
1330 | 0 | { |
1331 | 0 | stack.analyze(sourceName, sourceJson); |
1332 | 0 | stack.assemble(); |
1333 | 0 | } |
1334 | 0 | catch (evmasm::AssemblyImportException const& e) |
1335 | 0 | { |
1336 | 0 | return formatFatalError(Error::Type::Exception, "Assembly import error: " + std::string(e.what())); |
1337 | 0 | } |
1338 | 0 | catch (...) |
1339 | 0 | { |
1340 | 0 | return formatError( |
1341 | 0 | Error::Type::Exception, |
1342 | 0 | "general", |
1343 | 0 | "Unknown exception during assembly import: " + boost::current_exception_diagnostic_information() |
1344 | 0 | ); |
1345 | 0 | } |
1346 | 0 | if (!stack.compilationSuccessful()) |
1347 | 0 | return Json::object(); |
1348 | | |
1349 | | // EVM |
1350 | 0 | bool const wildcardMatchesExperimental = false; |
1351 | 0 | Json evmData; |
1352 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.assembly", wildcardMatchesExperimental)) |
1353 | 0 | evmData["assembly"] = stack.assemblyString(sourceName, {}); |
1354 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.legacyAssembly", wildcardMatchesExperimental)) |
1355 | 0 | evmData["legacyAssembly"] = stack.assemblyJSON(sourceName); |
1356 | |
|
1357 | 0 | if (isArtifactRequested( |
1358 | 0 | _inputsAndSettings.outputSelection, |
1359 | 0 | sourceName, |
1360 | 0 | "", |
1361 | 0 | evmObjectComponents("bytecode"), |
1362 | 0 | wildcardMatchesExperimental |
1363 | 0 | )) |
1364 | 0 | { |
1365 | 0 | auto const evmCreationArtifactRequested = [&](std::string const& _element) { |
1366 | 0 | return isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.bytecode." + _element, wildcardMatchesExperimental); |
1367 | 0 | }; |
1368 | |
|
1369 | 0 | Json creationJSON; |
1370 | 0 | if (evmCreationArtifactRequested("object")) |
1371 | 0 | creationJSON["object"] = stack.object(sourceName).toHex(); |
1372 | 0 | if (evmCreationArtifactRequested("opcodes")) |
1373 | 0 | creationJSON["opcodes"] = evmasm::disassemble(stack.object(sourceName).bytecode, _inputsAndSettings.evmVersion); |
1374 | 0 | if (evmCreationArtifactRequested("sourceMap")) |
1375 | 0 | creationJSON["sourceMap"] = stack.sourceMapping(sourceName) ? *stack.sourceMapping(sourceName) : ""; |
1376 | 0 | if (evmCreationArtifactRequested("functionDebugData")) |
1377 | 0 | creationJSON["functionDebugData"] = formatFunctionDebugData(stack.object(sourceName).functionDebugData); |
1378 | 0 | if (evmCreationArtifactRequested("linkReferences")) |
1379 | 0 | creationJSON["linkReferences"] = formatLinkReferences(stack.object(sourceName).linkReferences); |
1380 | 0 | if (evmCreationArtifactRequested("ethdebug")) |
1381 | 0 | creationJSON["ethdebug"] = stack.ethdebug(sourceName); |
1382 | 0 | evmData["bytecode"] = creationJSON; |
1383 | 0 | } |
1384 | |
|
1385 | 0 | if (isArtifactRequested( |
1386 | 0 | _inputsAndSettings.outputSelection, |
1387 | 0 | sourceName, |
1388 | 0 | "", |
1389 | 0 | evmObjectComponents("deployedBytecode"), |
1390 | 0 | wildcardMatchesExperimental |
1391 | 0 | )) |
1392 | 0 | { |
1393 | 0 | auto const evmDeployedArtifactRequested = [&](std::string const& _element) { |
1394 | 0 | return isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "evm.deployedBytecode." + _element, wildcardMatchesExperimental); |
1395 | 0 | }; |
1396 | |
|
1397 | 0 | Json deployedJSON; |
1398 | 0 | if (evmDeployedArtifactRequested("object")) |
1399 | 0 | deployedJSON["object"] = stack.runtimeObject(sourceName).toHex(); |
1400 | 0 | if (evmDeployedArtifactRequested("opcodes")) |
1401 | 0 | deployedJSON["opcodes"] = evmasm::disassemble(stack.runtimeObject(sourceName).bytecode, _inputsAndSettings.evmVersion); |
1402 | 0 | if (evmDeployedArtifactRequested("sourceMap")) |
1403 | 0 | deployedJSON["sourceMap"] = stack.runtimeSourceMapping(sourceName) ? *stack.runtimeSourceMapping(sourceName) : ""; |
1404 | 0 | if (evmDeployedArtifactRequested("functionDebugData")) |
1405 | 0 | deployedJSON["functionDebugData"] = formatFunctionDebugData(stack.runtimeObject(sourceName).functionDebugData); |
1406 | 0 | if (evmDeployedArtifactRequested("linkReferences")) |
1407 | 0 | deployedJSON["linkReferences"] = formatLinkReferences(stack.runtimeObject(sourceName).linkReferences); |
1408 | 0 | if (evmDeployedArtifactRequested("immutableReferences")) |
1409 | 0 | deployedJSON["immutableReferences"] = formatImmutableReferences(stack.runtimeObject(sourceName).immutableReferences); |
1410 | 0 | if (evmDeployedArtifactRequested("ethdebug")) |
1411 | 0 | deployedJSON["ethdebug"] = stack.ethdebugRuntime(sourceName); |
1412 | 0 | evmData["deployedBytecode"] = deployedJSON; |
1413 | 0 | } |
1414 | |
|
1415 | 0 | Json contractData; |
1416 | 0 | if (!evmData.empty()) |
1417 | 0 | contractData["evm"] = evmData; |
1418 | |
|
1419 | 0 | Json contractsOutput; |
1420 | 0 | contractsOutput[sourceName][""] = contractData; |
1421 | 0 | Json output; |
1422 | 0 | output["contracts"] = contractsOutput; |
1423 | 0 | return util::removeNullMembers(output); |
1424 | 0 | } |
1425 | | |
1426 | | Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) |
1427 | 0 | { |
1428 | 0 | solAssert(_inputsAndSettings.jsonSources.empty()); |
1429 | |
|
1430 | 0 | CompilerStack compilerStack(m_readFile); |
1431 | |
|
1432 | 0 | StringMap sourceList = std::move(_inputsAndSettings.sources); |
1433 | 0 | if (_inputsAndSettings.language == "Solidity") |
1434 | 0 | compilerStack.setSources(sourceList); |
1435 | 0 | for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) |
1436 | 0 | compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); |
1437 | 0 | compilerStack.setViaIR(_inputsAndSettings.viaIR); |
1438 | 0 | compilerStack.setViaSSACFG(_inputsAndSettings.viaSSACFG); |
1439 | 0 | compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); |
1440 | 0 | compilerStack.setRemappings(std::move(_inputsAndSettings.remappings)); |
1441 | 0 | compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); |
1442 | 0 | compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); |
1443 | 0 | if (_inputsAndSettings.debugInfoSelection.has_value()) |
1444 | 0 | compilerStack.selectDebugInfo(_inputsAndSettings.debugInfoSelection.value()); |
1445 | 0 | compilerStack.setLibraries(_inputsAndSettings.libraries); |
1446 | 0 | compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); |
1447 | 0 | compilerStack.setMetadataFormat(_inputsAndSettings.metadataFormat); |
1448 | 0 | compilerStack.setMetadataHash(_inputsAndSettings.metadataHash); |
1449 | 0 | compilerStack.selectContracts(pipelineConfig(_inputsAndSettings.outputSelection)); |
1450 | 0 | compilerStack.setModelCheckerSettings(_inputsAndSettings.modelCheckerSettings); |
1451 | 0 | compilerStack.setExperimental(_inputsAndSettings.experimental); |
1452 | |
|
1453 | 0 | Json errors = std::move(_inputsAndSettings.errors); |
1454 | |
|
1455 | 0 | bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); |
1456 | |
|
1457 | 0 | try |
1458 | 0 | { |
1459 | 0 | if (_inputsAndSettings.language == "SolidityAST") |
1460 | 0 | { |
1461 | 0 | try |
1462 | 0 | { |
1463 | 0 | compilerStack.importASTs(parseAstFromInput(sourceList)); |
1464 | 0 | if (!compilerStack.analyze()) |
1465 | 0 | errors.emplace_back(formatError(Error::Type::FatalError, "general", "Analysis of the AST failed.")); |
1466 | 0 | if (binariesRequested) |
1467 | 0 | compilerStack.compile(); |
1468 | 0 | } |
1469 | 0 | catch (util::Exception const& _exc) |
1470 | 0 | { |
1471 | 0 | solThrow(util::Exception, "Failed to import AST: "s + _exc.what()); |
1472 | 0 | } |
1473 | 0 | } |
1474 | 0 | else |
1475 | 0 | { |
1476 | 0 | if (binariesRequested) |
1477 | 0 | compilerStack.compile(); |
1478 | 0 | else |
1479 | 0 | compilerStack.parseAndAnalyze(_inputsAndSettings.stopAfter); |
1480 | |
|
1481 | 0 | for (auto const& error: compilerStack.errors()) |
1482 | 0 | errors.emplace_back(formatErrorWithException( |
1483 | 0 | compilerStack, |
1484 | 0 | *error, |
1485 | 0 | error->type(), |
1486 | 0 | "general", |
1487 | 0 | "", |
1488 | 0 | error->errorId() |
1489 | 0 | )); |
1490 | 0 | } |
1491 | 0 | } |
1492 | | // NOTE: This includes langutil::StackTooDeepError. |
1493 | 0 | catch (CompilerError const& _exception) |
1494 | 0 | { |
1495 | 0 | errors.emplace_back(formatErrorWithException( |
1496 | 0 | compilerStack, |
1497 | 0 | _exception, |
1498 | 0 | Error::Type::CompilerError, |
1499 | 0 | "general", |
1500 | 0 | "Compiler error (" + _exception.lineInfo() + ")" |
1501 | 0 | )); |
1502 | 0 | } |
1503 | 0 | catch (yul::StackTooDeepError const& _exception) |
1504 | 0 | { |
1505 | 0 | errors.emplace_back(formatErrorWithException( |
1506 | 0 | compilerStack, |
1507 | 0 | _exception, |
1508 | 0 | Error::Type::YulException, |
1509 | 0 | "general", |
1510 | 0 | "" // No prefix needed. These messages already say it's a "stack too deep" error. |
1511 | 0 | )); |
1512 | 0 | } |
1513 | 0 | catch (InternalCompilerError const&) |
1514 | 0 | { |
1515 | 0 | errors.emplace_back(formatError( |
1516 | 0 | Error::Type::InternalCompilerError, |
1517 | 0 | "general", |
1518 | 0 | "Internal compiler error:\n" + boost::current_exception_diagnostic_information() |
1519 | 0 | )); |
1520 | 0 | } |
1521 | 0 | catch (UnimplementedFeatureError const& _exception) |
1522 | 0 | { |
1523 | | // let StandardCompiler::compile handle this |
1524 | 0 | throw _exception; |
1525 | 0 | } |
1526 | 0 | catch (YulAssertion const&) |
1527 | 0 | { |
1528 | 0 | errors.emplace_back(formatError( |
1529 | 0 | Error::Type::YulException, |
1530 | 0 | "general", |
1531 | 0 | "Yul assertion failed:\n" + boost::current_exception_diagnostic_information() |
1532 | 0 | )); |
1533 | 0 | } |
1534 | 0 | catch (smtutil::SMTLogicError const&) |
1535 | 0 | { |
1536 | 0 | errors.emplace_back(formatError( |
1537 | 0 | Error::Type::SMTLogicException, |
1538 | 0 | "general", |
1539 | 0 | "SMT logic error:\n" + boost::current_exception_diagnostic_information() |
1540 | 0 | )); |
1541 | 0 | } |
1542 | 0 | catch (...) |
1543 | 0 | { |
1544 | 0 | errors.emplace_back(formatError( |
1545 | 0 | Error::Type::Exception, |
1546 | 0 | "general", |
1547 | 0 | "Unknown exception during compilation: " + boost::current_exception_diagnostic_information() |
1548 | 0 | )); |
1549 | 0 | } |
1550 | | |
1551 | 0 | bool parsingSuccess = compilerStack.state() >= CompilerStack::State::Parsed; |
1552 | 0 | bool analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; |
1553 | 0 | bool compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; |
1554 | | |
1555 | | // If analysis fails, the artifacts inside CompilerStack are potentially incomplete and must not be returned. |
1556 | | // Note that not completing analysis due to stopAfter does not count as a failure. It's neither failure nor success. |
1557 | 0 | bool analysisFailed = !analysisSuccess && _inputsAndSettings.stopAfter >= CompilerStack::State::AnalysisSuccessful; |
1558 | 0 | bool compilationFailed = !compilationSuccess && binariesRequested; |
1559 | 0 | if (compilationFailed || analysisFailed || !parsingSuccess) |
1560 | 0 | solAssert(!errors.empty(), "No error reported, but compilation failed."); |
1561 | |
|
1562 | 0 | Json output; |
1563 | |
|
1564 | 0 | if (errors.size() > 0) |
1565 | 0 | output["errors"] = std::move(errors); |
1566 | |
|
1567 | 0 | if (!compilerStack.unhandledSMTLib2Queries().empty()) |
1568 | 0 | for (std::string const& query: compilerStack.unhandledSMTLib2Queries()) |
1569 | 0 | output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + util::keccak256(query).hex()] = query; |
1570 | |
|
1571 | 0 | bool const wildcardMatchesExperimental = false; |
1572 | |
|
1573 | 0 | output["sources"] = Json::object(); |
1574 | 0 | unsigned sourceIndex = 0; |
1575 | | // NOTE: A case that will pass `parsingSuccess && !analysisFailed` but not `analysisSuccess` is |
1576 | | // stopAfter: parsing with no parsing errors. |
1577 | 0 | if (parsingSuccess && !analysisFailed) |
1578 | 0 | for (std::string const& sourceName: compilerStack.sourceNames()) |
1579 | 0 | { |
1580 | 0 | Json sourceResult; |
1581 | 0 | sourceResult["id"] = sourceIndex++; |
1582 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesExperimental)) |
1583 | 0 | sourceResult["ast"] = ASTJsonExporter(compilerStack.state(), compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); |
1584 | 0 | output["sources"][sourceName] = sourceResult; |
1585 | 0 | } |
1586 | |
|
1587 | 0 | Json contractsOutput; |
1588 | 0 | for (std::string const& contractName: analysisSuccess ? compilerStack.contractNames() : std::vector<std::string>()) |
1589 | 0 | { |
1590 | 0 | size_t colon = contractName.rfind(':'); |
1591 | 0 | solAssert(colon != std::string::npos, ""); |
1592 | 0 | std::string file = contractName.substr(0, colon); |
1593 | 0 | std::string name = contractName.substr(colon + 1); |
1594 | | |
1595 | | // ABI, storage layout, documentation and metadata |
1596 | 0 | Json contractData; |
1597 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesExperimental)) |
1598 | 0 | contractData["abi"] = compilerStack.contractABI(contractName); |
1599 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "storageLayout", false)) |
1600 | 0 | contractData["storageLayout"] = compilerStack.storageLayout(contractName); |
1601 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "transientStorageLayout", false)) |
1602 | 0 | contractData["transientStorageLayout"] = compilerStack.transientStorageLayout(contractName); |
1603 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesExperimental)) |
1604 | 0 | contractData["metadata"] = compilerStack.metadata(contractName); |
1605 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesExperimental)) |
1606 | 0 | contractData["userdoc"] = compilerStack.natspecUser(contractName); |
1607 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental)) |
1608 | 0 | contractData["devdoc"] = compilerStack.natspecDev(contractName); |
1609 | | |
1610 | | // IR |
1611 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental)) |
1612 | 0 | contractData["ir"] = compilerStack.yulIR(contractName).value_or(""); |
1613 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irAst", wildcardMatchesExperimental)) |
1614 | 0 | contractData["irAst"] = compilerStack.yulIRAst(contractName).value_or(Json{}); |
1615 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental)) |
1616 | 0 | contractData["irOptimized"] = compilerStack.yulIROptimized(contractName).value_or(""); |
1617 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimizedAst", wildcardMatchesExperimental)) |
1618 | 0 | contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName).value_or(Json{}); |
1619 | | |
1620 | | // EVM |
1621 | 0 | Json evmData; |
1622 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental)) |
1623 | 0 | evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); |
1624 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental)) |
1625 | 0 | evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName); |
1626 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental)) |
1627 | 0 | evmData["methodIdentifiers"] = compilerStack.interfaceSymbols(contractName)["methods"]; |
1628 | 0 | if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) |
1629 | 0 | evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); |
1630 | |
|
1631 | 0 | if (compilationSuccess && isArtifactRequested( |
1632 | 0 | _inputsAndSettings.outputSelection, |
1633 | 0 | file, |
1634 | 0 | name, |
1635 | 0 | evmObjectComponents("bytecode"), |
1636 | 0 | wildcardMatchesExperimental |
1637 | 0 | )) |
1638 | 0 | { |
1639 | 0 | auto const evmCreationArtifactRequested = [&](std::string const& _element) { |
1640 | 0 | return isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.bytecode." + _element, wildcardMatchesExperimental); |
1641 | 0 | }; |
1642 | |
|
1643 | 0 | Json creationJSON; |
1644 | 0 | if (evmCreationArtifactRequested("object")) |
1645 | 0 | creationJSON["object"] = compilerStack.object(contractName).toHex(); |
1646 | 0 | if (evmCreationArtifactRequested("opcodes")) |
1647 | 0 | creationJSON["opcodes"] = evmasm::disassemble(compilerStack.object(contractName).bytecode, _inputsAndSettings.evmVersion); |
1648 | 0 | if (evmCreationArtifactRequested("sourceMap")) |
1649 | 0 | creationJSON["sourceMap"] = compilerStack.sourceMapping(contractName) ? *compilerStack.sourceMapping(contractName) : ""; |
1650 | 0 | if (evmCreationArtifactRequested("functionDebugData")) |
1651 | 0 | creationJSON["functionDebugData"] = formatFunctionDebugData(compilerStack.object(contractName).functionDebugData); |
1652 | 0 | if (evmCreationArtifactRequested("linkReferences")) |
1653 | 0 | creationJSON["linkReferences"] = formatLinkReferences(compilerStack.object(contractName).linkReferences); |
1654 | 0 | if (evmCreationArtifactRequested("generatedSources")) |
1655 | 0 | creationJSON["generatedSources"] = compilerStack.generatedSources(contractName, /* _runtime */ false); |
1656 | 0 | if (evmCreationArtifactRequested("ethdebug")) |
1657 | 0 | creationJSON["ethdebug"] = compilerStack.ethdebug(contractName); |
1658 | 0 | evmData["bytecode"] = creationJSON; |
1659 | 0 | } |
1660 | |
|
1661 | 0 | if (compilationSuccess && isArtifactRequested( |
1662 | 0 | _inputsAndSettings.outputSelection, |
1663 | 0 | file, |
1664 | 0 | name, |
1665 | 0 | evmObjectComponents("deployedBytecode"), |
1666 | 0 | wildcardMatchesExperimental |
1667 | 0 | )) |
1668 | 0 | { |
1669 | 0 | auto const evmDeployedArtifactRequested = [&](std::string const& _element) { |
1670 | 0 | return isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.deployedBytecode." + _element, wildcardMatchesExperimental); |
1671 | 0 | }; |
1672 | |
|
1673 | 0 | Json deployedJSON; |
1674 | 0 | if (evmDeployedArtifactRequested("object")) |
1675 | 0 | deployedJSON["object"] = compilerStack.runtimeObject(contractName).toHex(); |
1676 | 0 | if (evmDeployedArtifactRequested("opcodes")) |
1677 | 0 | deployedJSON["opcodes"] = evmasm::disassemble(compilerStack.runtimeObject(contractName).bytecode, _inputsAndSettings.evmVersion); |
1678 | 0 | if (evmDeployedArtifactRequested("sourceMap")) |
1679 | 0 | deployedJSON["sourceMap"] = compilerStack.runtimeSourceMapping(contractName) ? *compilerStack.runtimeSourceMapping(contractName) : ""; |
1680 | 0 | if (evmDeployedArtifactRequested("functionDebugData")) |
1681 | 0 | deployedJSON["functionDebugData"] = formatFunctionDebugData(compilerStack.runtimeObject(contractName).functionDebugData); |
1682 | 0 | if (evmDeployedArtifactRequested("linkReferences")) |
1683 | 0 | deployedJSON["linkReferences"] = formatLinkReferences(compilerStack.runtimeObject(contractName).linkReferences); |
1684 | 0 | if (evmDeployedArtifactRequested("immutableReferences")) |
1685 | 0 | deployedJSON["immutableReferences"] = formatImmutableReferences(compilerStack.runtimeObject(contractName).immutableReferences); |
1686 | 0 | if (evmDeployedArtifactRequested("generatedSources")) |
1687 | 0 | deployedJSON["generatedSources"] = compilerStack.generatedSources(contractName, /* _runtime */ true); |
1688 | 0 | if (evmDeployedArtifactRequested("ethdebug")) |
1689 | 0 | deployedJSON["ethdebug"] = compilerStack.ethdebugRuntime(contractName); |
1690 | 0 | evmData["deployedBytecode"] = deployedJSON; |
1691 | 0 | } |
1692 | |
|
1693 | 0 | if (!evmData.empty()) |
1694 | 0 | contractData["evm"] = evmData; |
1695 | |
|
1696 | 0 | if (!contractData.empty()) |
1697 | 0 | { |
1698 | 0 | if (!contractsOutput.contains(file)) |
1699 | 0 | contractsOutput[file] = Json::object(); |
1700 | 0 | contractsOutput[file][name] = contractData; |
1701 | 0 | } |
1702 | 0 | } |
1703 | |
|
1704 | 0 | if (analysisSuccess) |
1705 | 0 | { |
1706 | 0 | if (isEthdebugGlobalOutputRequested(_inputsAndSettings.outputSelection, "ethdebug.resources")) |
1707 | 0 | output["ethdebug"]["resources"] = compilerStack.ethdebug(); |
1708 | 0 | if (isEthdebugGlobalOutputRequested(_inputsAndSettings.outputSelection, "ethdebug.compilation")) |
1709 | 0 | output["ethdebug"]["compilation"] = compilerStack.ethdebugCompilation(); |
1710 | 0 | } |
1711 | |
|
1712 | 0 | if (!contractsOutput.empty()) |
1713 | 0 | output["contracts"] = contractsOutput; |
1714 | |
|
1715 | 0 | return output; |
1716 | 0 | } |
1717 | | |
1718 | | |
1719 | | Json StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) |
1720 | 0 | { |
1721 | 0 | solAssert(_inputsAndSettings.jsonSources.empty()); |
1722 | |
|
1723 | 0 | Json output; |
1724 | 0 | output["errors"] = std::move(_inputsAndSettings.errors); |
1725 | |
|
1726 | 0 | if (_inputsAndSettings.sources.size() != 1) |
1727 | 0 | { |
1728 | 0 | output["errors"].emplace_back(formatError( |
1729 | 0 | Error::Type::JSONError, |
1730 | 0 | "general", |
1731 | 0 | "Yul mode only supports exactly one input file." |
1732 | 0 | )); |
1733 | 0 | return output; |
1734 | 0 | } |
1735 | 0 | if (!_inputsAndSettings.smtLib2Responses.empty()) |
1736 | 0 | { |
1737 | 0 | output["errors"].emplace_back(formatError( |
1738 | 0 | Error::Type::JSONError, |
1739 | 0 | "general", |
1740 | 0 | "Yul mode does not support smtlib2responses." |
1741 | 0 | )); |
1742 | 0 | return output; |
1743 | 0 | } |
1744 | 0 | if (!_inputsAndSettings.remappings.empty()) |
1745 | 0 | { |
1746 | 0 | output["errors"].emplace_back(formatError( |
1747 | 0 | Error::Type::JSONError, |
1748 | 0 | "general", |
1749 | 0 | "Field \"settings.remappings\" cannot be used for Yul." |
1750 | 0 | )); |
1751 | 0 | return output; |
1752 | 0 | } |
1753 | 0 | if (_inputsAndSettings.revertStrings != RevertStrings::Default) |
1754 | 0 | { |
1755 | 0 | output["errors"].emplace_back(formatError( |
1756 | 0 | Error::Type::JSONError, |
1757 | 0 | "general", |
1758 | 0 | "Field \"settings.debug.revertStrings\" cannot be used for Yul." |
1759 | 0 | )); |
1760 | 0 | return output; |
1761 | 0 | } |
1762 | | |
1763 | 0 | YulStack stack( |
1764 | 0 | _inputsAndSettings.evmVersion, |
1765 | 0 | _inputsAndSettings.optimiserSettings, |
1766 | 0 | _inputsAndSettings.debugInfoSelection.has_value() ? |
1767 | 0 | _inputsAndSettings.debugInfoSelection.value() : |
1768 | 0 | DebugInfoSelection::Default() |
1769 | 0 | ); |
1770 | 0 | std::string const& sourceName = _inputsAndSettings.sources.begin()->first; |
1771 | 0 | std::string const& sourceContents = _inputsAndSettings.sources.begin()->second; |
1772 | |
|
1773 | 0 | std::string contractName; |
1774 | 0 | bool const wildcardMatchesExperimental = true; |
1775 | 0 | MachineAssemblyObject object; |
1776 | 0 | MachineAssemblyObject deployedObject; |
1777 | |
|
1778 | 0 | bool successful = stack.parseAndAnalyze(sourceName, sourceContents); |
1779 | 0 | if (!successful) |
1780 | | // Inconsistent state - stop here to receive error reports from users |
1781 | 0 | solAssert(stack.hasErrors(), "No error reported, but parsing/analysis failed."); |
1782 | 0 | else |
1783 | 0 | { |
1784 | 0 | contractName = stack.parserResult()->name; |
1785 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental)) |
1786 | 0 | output["contracts"][sourceName][contractName]["ir"] = stack.print(); |
1787 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ast", wildcardMatchesExperimental)) |
1788 | 0 | { |
1789 | 0 | Json sourceResult; |
1790 | 0 | sourceResult["id"] = 0; |
1791 | 0 | sourceResult["ast"] = stack.astJson(); |
1792 | 0 | output["sources"][sourceName] = sourceResult; |
1793 | 0 | } |
1794 | 0 | stack.optimize(_inputsAndSettings.viaSSACFG); |
1795 | 0 | std::tie(object, deployedObject) = stack.assembleWithDeployed({}, _inputsAndSettings.viaSSACFG); |
1796 | 0 | if (object.bytecode) |
1797 | 0 | object.bytecode->link(_inputsAndSettings.libraries); |
1798 | 0 | if (deployedObject.bytecode) |
1799 | 0 | deployedObject.bytecode->link(_inputsAndSettings.libraries); |
1800 | 0 | } |
1801 | |
|
1802 | 0 | for (auto const& error: stack.errors()) |
1803 | 0 | { |
1804 | 0 | auto err = std::dynamic_pointer_cast<Error const>(error); |
1805 | |
|
1806 | 0 | output["errors"].emplace_back(formatErrorWithException( |
1807 | 0 | stack, |
1808 | 0 | *error, |
1809 | 0 | err->type(), |
1810 | 0 | "general", |
1811 | 0 | "" |
1812 | 0 | )); |
1813 | 0 | } |
1814 | 0 | if (stack.hasErrors()) |
1815 | 0 | return output; |
1816 | | |
1817 | 0 | for (auto&& [kind, isDeployed]: {make_pair("bytecode"s, false), make_pair("deployedBytecode"s, true)}) |
1818 | 0 | if (isArtifactRequested( |
1819 | 0 | _inputsAndSettings.outputSelection, |
1820 | 0 | sourceName, |
1821 | 0 | contractName, |
1822 | 0 | evmObjectComponents(kind), |
1823 | 0 | wildcardMatchesExperimental |
1824 | 0 | )) |
1825 | 0 | { |
1826 | 0 | auto const evmArtifactRequested = [&](std::string const& _kind, std::string const& _element) { |
1827 | 0 | return isArtifactRequested( |
1828 | 0 | _inputsAndSettings.outputSelection, |
1829 | 0 | sourceName, |
1830 | 0 | contractName, |
1831 | 0 | "evm." + _kind + "." + _element, |
1832 | 0 | wildcardMatchesExperimental |
1833 | 0 | ); |
1834 | 0 | }; |
1835 | |
|
1836 | 0 | MachineAssemblyObject const& selectedObject = isDeployed ? deployedObject : object; |
1837 | 0 | if (selectedObject.bytecode) |
1838 | 0 | { |
1839 | 0 | Json bytecodeJSON; |
1840 | 0 | if (evmArtifactRequested(kind, "object")) |
1841 | 0 | bytecodeJSON["object"] = selectedObject.bytecode->toHex(); |
1842 | 0 | if (evmArtifactRequested(kind, "opcodes")) |
1843 | 0 | bytecodeJSON["opcodes"] = evmasm::disassemble(selectedObject.bytecode->bytecode, _inputsAndSettings.evmVersion); |
1844 | 0 | if (evmArtifactRequested(kind, "sourceMap")) |
1845 | 0 | bytecodeJSON["sourceMap"] = selectedObject.sourceMappings ? *selectedObject.sourceMappings : ""; |
1846 | 0 | if (evmArtifactRequested(kind, "functionDebugData")) |
1847 | 0 | bytecodeJSON["functionDebugData"] = formatFunctionDebugData(selectedObject.bytecode->functionDebugData); |
1848 | 0 | if (evmArtifactRequested(kind, "linkReferences")) |
1849 | 0 | bytecodeJSON["linkReferences"] = formatLinkReferences(selectedObject.bytecode->linkReferences); |
1850 | 0 | if (evmArtifactRequested(kind, "ethdebug")) |
1851 | 0 | { |
1852 | 0 | solAssert(_inputsAndSettings.experimental); |
1853 | 0 | bytecodeJSON["ethdebug"] = selectedObject.ethdebug; |
1854 | 0 | } |
1855 | 0 | if (isDeployed && evmArtifactRequested(kind, "immutableReferences")) |
1856 | 0 | bytecodeJSON["immutableReferences"] = formatImmutableReferences(selectedObject.bytecode->immutableReferences); |
1857 | 0 | output["contracts"][sourceName][contractName]["evm"][kind] = bytecodeJSON; |
1858 | 0 | } |
1859 | 0 | } |
1860 | |
|
1861 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental)) |
1862 | 0 | output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); |
1863 | 0 | if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental)) |
1864 | 0 | output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly->assemblyString(stack.debugInfoSelection()); |
1865 | |
|
1866 | 0 | if (isEthdebugGlobalOutputRequested(_inputsAndSettings.outputSelection, "ethdebug.resources")) |
1867 | 0 | { |
1868 | 0 | solAssert(_inputsAndSettings.experimental, ""); |
1869 | 0 | output["ethdebug"]["resources"] = evmasm::ethdebug::resources( |
1870 | 0 | {{.id = 0, .path = sourceName, .contents = sourceContents, .language = "Yul"}}, |
1871 | 0 | VersionString |
1872 | 0 | ); |
1873 | 0 | } |
1874 | 0 | if (isEthdebugGlobalOutputRequested(_inputsAndSettings.outputSelection, "ethdebug.compilation")) |
1875 | 0 | { |
1876 | 0 | solAssert(_inputsAndSettings.experimental, ""); |
1877 | 0 | output["ethdebug"]["compilation"] = evmasm::ethdebug::compilation( |
1878 | 0 | {{.id = 0, .path = sourceName, .contents = sourceContents, .language = "Yul"}}, |
1879 | 0 | VersionString |
1880 | 0 | ); |
1881 | 0 | } |
1882 | 0 | return output; |
1883 | 0 | } |
1884 | | |
1885 | | Json StandardCompiler::compile(Json const& _input) noexcept |
1886 | 0 | { |
1887 | 0 | YulStringRepository::reset(); |
1888 | |
|
1889 | 0 | try |
1890 | 0 | { |
1891 | 0 | auto parsed = parseInput(_input); |
1892 | 0 | if (std::holds_alternative<Json>(parsed)) |
1893 | 0 | return std::get<Json>(std::move(parsed)); |
1894 | 0 | InputsAndSettings settings = std::get<InputsAndSettings>(std::move(parsed)); |
1895 | 0 | if (settings.language == "Solidity") |
1896 | 0 | return compileSolidity(std::move(settings)); |
1897 | 0 | else if (settings.language == "Yul") |
1898 | 0 | return compileYul(std::move(settings)); |
1899 | 0 | else if (settings.language == "SolidityAST") |
1900 | 0 | return compileSolidity(std::move(settings)); |
1901 | 0 | else if (settings.language == "EVMAssembly") |
1902 | 0 | return importEVMAssembly(std::move(settings)); |
1903 | 0 | else |
1904 | 0 | return formatFatalError(Error::Type::JSONError, "Only \"Solidity\", \"Yul\", \"SolidityAST\" or \"EVMAssembly\" is supported as a language."); |
1905 | 0 | } |
1906 | 0 | catch (UnimplementedFeatureError const& _exception) |
1907 | 0 | { |
1908 | 0 | solAssert(_exception.comment(), "Unimplemented feature errors must include a message for the user"); |
1909 | 0 | return formatFatalError(Error::Type::UnimplementedFeatureError, stringOrDefault(_exception.comment())); |
1910 | 0 | } |
1911 | 0 | catch (...) |
1912 | 0 | { |
1913 | 0 | return formatFatalError( |
1914 | 0 | Error::Type::InternalCompilerError, |
1915 | 0 | "Uncaught exception:\n" + boost::current_exception_diagnostic_information() |
1916 | 0 | ); |
1917 | 0 | } |
1918 | 0 | } |
1919 | | |
1920 | | std::string StandardCompiler::compile(std::string const& _input) noexcept |
1921 | 0 | { |
1922 | 0 | Json input; |
1923 | 0 | std::string errors; |
1924 | 0 | try |
1925 | 0 | { |
1926 | 0 | if (!util::jsonParseStrict(_input, input, &errors)) |
1927 | 0 | return util::jsonPrint(formatFatalError(Error::Type::JSONError, errors), m_jsonPrintingFormat); |
1928 | 0 | } |
1929 | 0 | catch (...) |
1930 | 0 | { |
1931 | 0 | if (errors.empty()) |
1932 | 0 | return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}"; |
1933 | 0 | else |
1934 | 0 | return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON: " + errors + "\"}]}"; |
1935 | 0 | } |
1936 | | |
1937 | | // std::cout << "Input: " << solidity::util::jsonPrettyPrint(input) << std::endl; |
1938 | 0 | Json output = compile(input); |
1939 | | // std::cout << "Output: " << solidity::util::jsonPrettyPrint(output) << std::endl; |
1940 | |
|
1941 | 0 | try |
1942 | 0 | { |
1943 | 0 | return util::jsonPrint(output, m_jsonPrintingFormat); |
1944 | 0 | } |
1945 | 0 | catch (...) |
1946 | 0 | { |
1947 | 0 | return "{\"errors\":[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}"; |
1948 | 0 | } |
1949 | 0 | } |
1950 | | |
1951 | | Json StandardCompiler::formatFunctionDebugData( |
1952 | | std::map<std::string, evmasm::LinkerObject::FunctionDebugData> const& _debugInfo |
1953 | | ) |
1954 | 0 | { |
1955 | 0 | static_assert(std::is_same_v<Json::number_unsigned_t, uint64_t>); |
1956 | 0 | Json ret = Json::object(); |
1957 | 0 | for (auto const& [name, info]: _debugInfo) |
1958 | 0 | { |
1959 | 0 | Json fun = Json::object(); |
1960 | 0 | if (info.sourceID) |
1961 | 0 | fun["id"] = Json::number_unsigned_t(*info.sourceID); |
1962 | 0 | else |
1963 | 0 | fun["id"] = Json(); |
1964 | 0 | if (info.bytecodeOffset) |
1965 | 0 | fun["entryPoint"] = Json::number_unsigned_t(*info.bytecodeOffset); |
1966 | 0 | else |
1967 | 0 | fun["entryPoint"] = Json(); |
1968 | 0 | fun["parameterSlots"] = Json::number_unsigned_t(info.params); |
1969 | 0 | fun["returnSlots"] = Json::number_unsigned_t(info.returns); |
1970 | 0 | ret[name] = std::move(fun); |
1971 | 0 | } |
1972 | |
|
1973 | 0 | return ret; |
1974 | 0 | } |