diff --git a/Changelog.md b/Changelog.md index 21e0e91f5..cd71d617f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: * General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon). + * General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing. * Yul Optimizer: Apply penalty when trying to rematerialize into loops. Bugfixes: diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index 63c9de39d..6a7663d46 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -16,6 +16,7 @@ set(sources SemVerHandler.cpp SemVerHandler.h SourceLocation.h + SourceLocation.cpp SourceReferenceExtractor.cpp SourceReferenceExtractor.h SourceReferenceFormatter.cpp diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index de72eba37..575847ed9 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -40,6 +40,7 @@ struct CompilerError: virtual util::Exception {}; struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {}; +struct InvalidAstError: virtual util::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ @@ -51,6 +52,9 @@ struct UnimplementedFeatureError: virtual util::Exception {}; #define solUnimplemented(DESCRIPTION) \ solUnimplementedAssert(false, DESCRIPTION) +#define astAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION) + class Error: virtual public util::Exception { public: diff --git a/liblangutil/SourceLocation.cpp b/liblangutil/SourceLocation.cpp new file mode 100644 index 000000000..8c531c625 --- /dev/null +++ b/liblangutil/SourceLocation.cpp @@ -0,0 +1,51 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +using namespace solidity; +namespace solidity::langutil +{ + +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex) +{ + // Expected input: "start:length:sourceindex" + enum SrcElem : size_t { Start, Length, Index }; + + std::vector pos; + + boost::algorithm::split(pos, _input, boost::is_any_of(":")); + + astAssert( + pos.size() == 3 && + _maxIndex >= static_cast(stoi(pos[Index])), + "'src'-field ill-formatted or src-index too high" + ); + + int start = stoi(pos[Start]); + int end = start + stoi(pos[Length]); + + // ASSUMPTION: only the name of source is used from here on, the m_source of the CharStream-Object can be empty + std::shared_ptr source = std::make_shared("", _sourceName); + + return SourceLocation{start, end, source}; +} + +} diff --git a/liblangutil/SourceLocation.h b/liblangutil/SourceLocation.h index d86dd206a..46808f8e0 100644 --- a/liblangutil/SourceLocation.h +++ b/liblangutil/SourceLocation.h @@ -23,13 +23,12 @@ #pragma once #include -#include // defines noexcept macro for MSVC #include + #include + #include #include -#include -#include namespace solidity::langutil { @@ -46,9 +45,28 @@ struct SourceLocation return source.get() == _other.source.get() && start == _other.start && end == _other.end; } bool operator!=(SourceLocation const& _other) const { return !operator==(_other); } - inline bool operator<(SourceLocation const& _other) const; - inline bool contains(SourceLocation const& _other) const; - inline bool intersects(SourceLocation const& _other) const; + + inline bool operator<(SourceLocation const& _other) const + { + if (!source|| !_other.source) + return std::make_tuple(int(!!source), start, end) < std::make_tuple(int(!!_other.source), _other.start, _other.end); + else + return std::make_tuple(source->name(), start, end) < std::make_tuple(_other.source->name(), _other.start, _other.end); + } + + inline bool contains(SourceLocation const& _other) const + { + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return start <= _other.start && _other.end <= end; + } + + inline bool intersects(SourceLocation const& _other) const + { + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return _other.start < end && start < _other.end; + } bool isEmpty() const { return start == -1 && end == -1; } @@ -86,6 +104,8 @@ struct SourceLocation std::shared_ptr source; }; +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex = -1); + /// Stream output for Location (used e.g. in boost exceptions). inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) { @@ -100,26 +120,4 @@ inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _locat return _out; } -bool SourceLocation::operator<(SourceLocation const& _other) const -{ - if (!source|| !_other.source) - return std::make_tuple(int(!!source), start, end) < std::make_tuple(int(!!_other.source), _other.start, _other.end); - else - return std::make_tuple(source->name(), start, end) < std::make_tuple(_other.source->name(), _other.start, _other.end); -} - -bool SourceLocation::contains(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) - return false; - return start <= _other.start && _other.end <= end; -} - -bool SourceLocation::intersects(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) - return false; - return _other.start < end && start < _other.end; -} - } diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 87eb0e9b7..242f26c35 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -39,10 +39,14 @@ set(sources ast/ASTAnnotations.h ast/ASTEnums.h ast/ASTForward.h + ast/AsmJsonImporter.cpp + ast/AsmJsonImporter.h ast/ASTJsonConverter.cpp ast/ASTJsonConverter.h ast/ASTUtils.cpp ast/ASTUtils.h + ast/ASTJsonImporter.cpp + ast/ASTJsonImporter.h ast/ASTVisitor.h ast/ExperimentalFeatures.h ast/Types.cpp diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 2a4ce374b..a0764edba 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -33,10 +33,44 @@ using namespace std; namespace solidity::frontend { +/// Magic variables get negative ids for easy differentiation +int magicVariableToID(std::string const& _name) +{ + if (_name == "abi") return -1; + else if (_name == "addmod") return -2; + else if (_name == "assert") return -3; + else if (_name == "block") return -4; + else if (_name == "blockhash") return -5; + else if (_name == "ecrecover") return -6; + else if (_name == "gasleft") return -7; + else if (_name == "keccak256") return -8; + else if (_name == "log0") return -10; + else if (_name == "log1") return -11; + else if (_name == "log2") return -12; + else if (_name == "log3") return -13; + else if (_name == "log4") return -14; + else if (_name == "msg") return -15; + else if (_name == "mulmod") return -16; + else if (_name == "now") return -17; + else if (_name == "require") return -18; + else if (_name == "revert") return -19; + else if (_name == "ripemd160") return -20; + else if (_name == "selfdestruct") return -21; + else if (_name == "sha256") return -22; + else if (_name == "sha3") return -23; + else if (_name == "suicide") return -24; + else if (_name == "super") return -25; + else if (_name == "tx") return -26; + else if (_name == "type") return -27; + else if (_name == "this") return -28; + else + solAssert(false, "Unknown magic variable: \"" + _name + "\"."); +} + inline vector> constructMagicVariables() { static auto const magicVarDecl = [](string const& _name, Type const* _type) { - return make_shared(_name, _type); + return make_shared(magicVariableToID(_name), _name, _type); }; return { @@ -97,7 +131,7 @@ vector GlobalContext::declarations() const MagicVariableDeclaration const* GlobalContext::currentThis() const { if (!m_thisPointer[m_currentContract]) - m_thisPointer[m_currentContract] = make_shared("this", TypeProvider::contract(*m_currentContract)); + m_thisPointer[m_currentContract] = make_shared(magicVariableToID("this"), "this", TypeProvider::contract(*m_currentContract)); return m_thisPointer[m_currentContract].get(); } @@ -105,7 +139,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const MagicVariableDeclaration const* GlobalContext::currentSuper() const { if (!m_superPointer[m_currentContract]) - m_superPointer[m_currentContract] = make_shared("super", TypeProvider::contract(*m_currentContract, true)); + m_superPointer[m_currentContract] = make_shared(magicVariableToID("super"), "super", TypeProvider::contract(*m_currentContract, true)); return m_superPointer[m_currentContract].get(); } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 9f8e1471d..74b042852 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -35,31 +35,12 @@ using namespace std; using namespace solidity; using namespace solidity::frontend; -class IDDispenser -{ -public: - static size_t next() { return ++instance(); } - static void reset() { instance() = 0; } -private: - static size_t& instance() - { - static IDDispenser dispenser; - return dispenser.id; - } - size_t id = 0; -}; - -ASTNode::ASTNode(SourceLocation const& _location): - m_id(IDDispenser::next()), +ASTNode::ASTNode(int64_t _id, SourceLocation const& _location): + m_id(_id), m_location(_location) { } -void ASTNode::resetID() -{ - IDDispenser::reset(); -} - ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index fc0b3a5be..a603a93fb 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -64,13 +64,11 @@ class ASTNode: private boost::noncopyable public: using SourceLocation = langutil::SourceLocation; - explicit ASTNode(SourceLocation const& _location); + explicit ASTNode(int64_t _id, SourceLocation const& _location); virtual ~ASTNode() {} /// @returns an identifier of this AST node that is unique for a single compilation run. - size_t id() const { return m_id; } - /// Resets the global ID counter. This invalidates all previous IDs. - static void resetID(); + int64_t id() const { return m_id; } virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; @@ -139,8 +137,8 @@ std::vector<_T const*> ASTNode::filteredNodes(std::vector> c class SourceUnit: public ASTNode { public: - SourceUnit(SourceLocation const& _location, std::vector> const& _nodes): - ASTNode(_location), m_nodes(_nodes) {} + SourceUnit(int64_t _id, SourceLocation const& _location, std::vector> const& _nodes): + ASTNode(_id, _location), m_nodes(_nodes) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -206,11 +204,12 @@ public: } Declaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility = Visibility::Default ): - ASTNode(_location), m_name(_name), m_visibility(_visibility) {} + ASTNode(_id, _location), m_name(_name), m_visibility(_visibility) {} /// @returns the declared name. ASTString const& name() const { return *m_name; } @@ -253,10 +252,11 @@ class PragmaDirective: public ASTNode { public: PragmaDirective( + int64_t _id, SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals - ): ASTNode(_location), m_tokens(_tokens), m_literals(_literals) + ): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals) {} void accept(ASTVisitor& _visitor) override; @@ -295,12 +295,13 @@ public: using SymbolAliasList = std::vector; ImportDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _path, ASTPointer const& _unitAlias, SymbolAliasList _symbolAliases ): - Declaration(_location, _unitAlias), + Declaration(_id, _location, _unitAlias), m_path(_path), m_symbolAliases(move(_symbolAliases)) { } @@ -385,6 +386,7 @@ class ContractDefinition: public Declaration, public Documented { public: ContractDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, @@ -393,7 +395,7 @@ public: ContractKind _contractKind = ContractKind::Contract, bool _abstract = false ): - Declaration(_location, _name), + Declaration(_id, _location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), @@ -465,11 +467,12 @@ class InheritanceSpecifier: public ASTNode { public: InheritanceSpecifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseName, std::unique_ptr>> _arguments ): - ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -494,11 +497,12 @@ class UsingForDirective: public ASTNode { public: UsingForDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _libraryName, ASTPointer const& _typeName ): - ASTNode(_location), m_libraryName(_libraryName), m_typeName(_typeName) {} + ASTNode(_id, _location), m_libraryName(_libraryName), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -516,11 +520,12 @@ class StructDefinition: public Declaration { public: StructDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -539,11 +544,12 @@ class EnumDefinition: public Declaration { public: EnumDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -563,8 +569,8 @@ private: class EnumValue: public Declaration { public: - EnumValue(SourceLocation const& _location, ASTPointer const& _name): - Declaration(_location, _name) {} + EnumValue(int64_t _id, SourceLocation const& _location, ASTPointer const& _name): + Declaration(_id, _location, _name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -581,10 +587,11 @@ class ParameterList: public ASTNode { public: ParameterList( + int64_t _id, SourceLocation const& _location, std::vector> const& _parameters ): - ASTNode(_location), m_parameters(_parameters) {} + ASTNode(_id, _location), m_parameters(_parameters) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -602,6 +609,7 @@ class CallableDeclaration: public Declaration, public VariableScope { public: CallableDeclaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -610,7 +618,7 @@ public: ASTPointer const& _overrides = nullptr, ASTPointer const& _returnParameters = ASTPointer() ): - Declaration(_location, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_parameters(_parameters), m_overrides(_overrides), m_returnParameters(_returnParameters), @@ -643,10 +651,11 @@ class OverrideSpecifier: public ASTNode { public: OverrideSpecifier( + int64_t _id, SourceLocation const& _location, std::vector> const& _overrides ): - ASTNode(_location), + ASTNode(_id, _location), m_overrides(_overrides) { } @@ -665,6 +674,7 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public { public: FunctionDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -678,7 +688,7 @@ public: ASTPointer const& _returnParameters, ASTPointer const& _body ): - CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), + CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), Documented(_documentation), ImplementationOptional(_body != nullptr), m_stateMutability(_stateMutability), @@ -748,7 +758,8 @@ public: enum Location { Unspecified, Storage, Memory, CallData }; VariableDeclaration( - SourceLocation const& _sourceLocation, + int64_t _id, + SourceLocation const& _location, ASTPointer const& _type, ASTPointer const& _name, ASTPointer _value, @@ -759,7 +770,7 @@ public: ASTPointer const& _overrides = nullptr, Location _referenceLocation = Location::Unspecified ): - Declaration(_sourceLocation, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_typeName(_type), m_value(_value), m_isStateVariable(_isStateVar), @@ -847,6 +858,7 @@ class ModifierDefinition: public CallableDeclaration, public Documented { public: ModifierDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, @@ -855,7 +867,7 @@ public: ASTPointer const& _overrides, ASTPointer const& _body ): - CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), + CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), Documented(_documentation), m_body(_body) { @@ -881,11 +893,12 @@ class ModifierInvocation: public ASTNode { public: ModifierInvocation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::unique_ptr>> _arguments ): - ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -908,13 +921,14 @@ class EventDefinition: public CallableDeclaration, public Documented { public: EventDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, ASTPointer const& _documentation, ASTPointer const& _parameters, bool _anonymous = false ): - CallableDeclaration(_location, _name, Visibility::Default, _parameters), + CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters), Documented(_documentation), m_anonymous(_anonymous) { @@ -936,13 +950,13 @@ private: /** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global - * functions when such an identifier is encountered. Will never have a valid location in the source code. + * functions when such an identifier is encountered. Will never have a valid location in the source code */ class MagicVariableDeclaration: public Declaration { public: - MagicVariableDeclaration(ASTString const& _name, Type const* _type): - Declaration(SourceLocation(), std::make_shared(_name)), m_type(_type) {} + MagicVariableDeclaration(int _id, ASTString const& _name, Type const* _type): + Declaration(_id, SourceLocation(), std::make_shared(_name)), m_type(_type) { } void accept(ASTVisitor&) override { @@ -973,7 +987,7 @@ private: class TypeName: public ASTNode { protected: - explicit TypeName(SourceLocation const& _location): ASTNode(_location) {} + explicit TypeName(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} public: TypeNameAnnotation& annotation() const override; @@ -987,10 +1001,11 @@ class ElementaryTypeName: public TypeName { public: ElementaryTypeName( + int64_t _id, SourceLocation const& _location, ElementaryTypeNameToken const& _elem, std::optional _stateMutability = {} - ): TypeName(_location), m_type(_elem), m_stateMutability(_stateMutability) + ): TypeName(_id, _location), m_type(_elem), m_stateMutability(_stateMutability) { solAssert(!_stateMutability.has_value() || _elem.token() == Token::Address, ""); } @@ -1013,8 +1028,8 @@ private: class UserDefinedTypeName: public TypeName { public: - UserDefinedTypeName(SourceLocation const& _location, std::vector const& _namePath): - TypeName(_location), m_namePath(_namePath) {} + UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector const& _namePath): + TypeName(_id, _location), m_namePath(_namePath) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1033,13 +1048,14 @@ class FunctionTypeName: public TypeName { public: FunctionTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _parameterTypes, ASTPointer const& _returnTypes, Visibility _visibility, StateMutability _stateMutability ): - TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + TypeName(_id, _location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), m_visibility(_visibility), m_stateMutability(_stateMutability) {} void accept(ASTVisitor& _visitor) override; @@ -1071,11 +1087,12 @@ class Mapping: public TypeName { public: Mapping( + int64_t _id, SourceLocation const& _location, ASTPointer const& _keyType, ASTPointer const& _valueType ): - TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} + TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1094,11 +1111,12 @@ class ArrayTypeName: public TypeName { public: ArrayTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseType, ASTPointer const& _length ): - TypeName(_location), m_baseType(_baseType), m_length(_length) {} + TypeName(_id, _location), m_baseType(_baseType), m_length(_length) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1123,9 +1141,10 @@ class Statement: public ASTNode, public Documented { public: explicit Statement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): ASTNode(_location), Documented(_docString) {} + ): ASTNode(_id, _location), Documented(_docString) {} StatementAnnotation& annotation() const override; }; @@ -1137,12 +1156,13 @@ class InlineAssembly: public Statement { public: InlineAssembly( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, yul::Dialect const& _dialect, std::shared_ptr const& _operations ): - Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {} + Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1163,11 +1183,12 @@ class Block: public Statement, public Scopable { public: Block( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _statements ): - Statement(_location, _docString), m_statements(_statements) {} + Statement(_id, _location, _docString), m_statements(_statements) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1187,9 +1208,10 @@ class PlaceholderStatement: public Statement { public: explicit PlaceholderStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1203,13 +1225,14 @@ class IfStatement: public Statement { public: IfStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _trueBody, ASTPointer const& _falseBody ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_condition(_condition), m_trueBody(_trueBody), m_falseBody(_falseBody) @@ -1237,12 +1260,13 @@ class TryCatchClause: public ASTNode, public Scopable { public: TryCatchClause( + int64_t _id, SourceLocation const& _location, ASTPointer const& _errorName, ASTPointer const& _parameters, ASTPointer const& _block ): - ASTNode(_location), + ASTNode(_id, _location), m_errorName(_errorName), m_parameters(_parameters), m_block(_block) @@ -1280,12 +1304,13 @@ class TryStatement: public Statement { public: TryStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _externalCall, std::vector> const& _clauses ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_externalCall(_externalCall), m_clauses(_clauses) {} @@ -1307,22 +1332,24 @@ class BreakableStatement: public Statement { public: explicit BreakableStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} }; class WhileStatement: public BreakableStatement { public: WhileStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _body, bool _isDoWhile ): - BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body), + BreakableStatement(_id, _location, _docString), m_condition(_condition), m_body(_body), m_isDoWhile(_isDoWhile) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1344,6 +1371,7 @@ class ForStatement: public BreakableStatement, public Scopable { public: ForStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _initExpression, @@ -1351,7 +1379,7 @@ public: ASTPointer const& _loopExpression, ASTPointer const& _body ): - BreakableStatement(_location, _docString), + BreakableStatement(_id, _location, _docString), m_initExpression(_initExpression), m_condExpression(_conditionExpression), m_loopExpression(_loopExpression), @@ -1381,8 +1409,8 @@ private: class Continue: public Statement { public: - explicit Continue(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Continue(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1390,8 +1418,8 @@ public: class Break: public Statement { public: - explicit Break(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Break(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1400,10 +1428,11 @@ class Return: public Statement { public: Return( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression - ): Statement(_location, _docString), m_expression(_expression) {} + ): Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1421,8 +1450,8 @@ private: class Throw: public Statement { public: - explicit Throw(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Throw(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1434,11 +1463,12 @@ class EmitStatement: public Statement { public: explicit EmitStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _functionCall ): - Statement(_location, _docString), m_eventCall(_functionCall) {} + Statement(_id, _location, _docString), m_eventCall(_functionCall) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1460,12 +1490,13 @@ class VariableDeclarationStatement: public Statement { public: VariableDeclarationStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _variables, ASTPointer const& _initialValue ): - Statement(_location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} + Statement(_id, _location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1489,11 +1520,12 @@ class ExpressionStatement: public Statement { public: ExpressionStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression ): - Statement(_location, _docString), m_expression(_expression) {} + Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1516,7 +1548,7 @@ private: class Expression: public ASTNode { public: - explicit Expression(SourceLocation const& _location): ASTNode(_location) {} + explicit Expression(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} ExpressionAnnotation& annotation() const override; }; @@ -1525,12 +1557,13 @@ class Conditional: public Expression { public: Conditional( + int64_t _id, SourceLocation const& _location, ASTPointer const& _condition, ASTPointer const& _trueExpression, ASTPointer const& _falseExpression ): - Expression(_location), + Expression(_id, _location), m_condition(_condition), m_trueExpression(_trueExpression), m_falseExpression(_falseExpression) @@ -1554,12 +1587,13 @@ class Assignment: public Expression { public: Assignment( + int64_t _id, SourceLocation const& _location, ASTPointer const& _leftHandSide, Token _assignmentOperator, ASTPointer const& _rightHandSide ): - Expression(_location), + Expression(_id, _location), m_leftHandSide(_leftHandSide), m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) @@ -1591,11 +1625,12 @@ class TupleExpression: public Expression { public: TupleExpression( + int64_t _id, SourceLocation const& _location, std::vector> const& _components, bool _isArray ): - Expression(_location), + Expression(_id, _location), m_components(_components), m_isArray(_isArray) {} void accept(ASTVisitor& _visitor) override; @@ -1617,12 +1652,13 @@ class UnaryOperation: public Expression { public: UnaryOperation( + int64_t _id, SourceLocation const& _location, Token _operator, ASTPointer const& _subExpression, bool _isPrefix ): - Expression(_location), + Expression(_id, _location), m_operator(_operator), m_subExpression(_subExpression), m_isPrefix(_isPrefix) @@ -1650,12 +1686,13 @@ class BinaryOperation: public Expression { public: BinaryOperation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _left, Token _operator, ASTPointer const& _right ): - Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) + Expression(_id, _location), m_left(_left), m_operator(_operator), m_right(_right) { solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); } @@ -1681,12 +1718,13 @@ class FunctionCall: public Expression { public: FunctionCall( + int64_t _id, SourceLocation const& _location, ASTPointer const& _expression, std::vector> const& _arguments, std::vector> const& _names ): - Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} + Expression(_id, _location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1710,10 +1748,11 @@ class NewExpression: public Expression { public: NewExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _typeName ): - Expression(_location), m_typeName(_typeName) {} + Expression(_id, _location), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1730,11 +1769,12 @@ class MemberAccess: public Expression { public: MemberAccess( + int64_t _id, SourceLocation const& _location, ASTPointer _expression, ASTPointer const& _memberName ): - Expression(_location), m_expression(_expression), m_memberName(_memberName) {} + Expression(_id, _location), m_expression(_expression), m_memberName(_memberName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } @@ -1754,11 +1794,12 @@ class IndexAccess: public Expression { public: IndexAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _index ): - Expression(_location), m_base(_base), m_index(_index) {} + Expression(_id, _location), m_base(_base), m_index(_index) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1777,12 +1818,13 @@ class IndexRangeAccess: public Expression { public: IndexRangeAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _start, ASTPointer const& _end ): - Expression(_location), m_base(_base), m_start(_start), m_end(_end) {} + Expression(_id, _location), m_base(_base), m_start(_start), m_end(_end) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1803,7 +1845,7 @@ private: class PrimaryExpression: public Expression { public: - PrimaryExpression(SourceLocation const& _location): Expression(_location) {} + PrimaryExpression(int64_t _id, SourceLocation const& _location): Expression(_id, _location) {} }; /** @@ -1813,10 +1855,11 @@ class Identifier: public PrimaryExpression { public: Identifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name ): - PrimaryExpression(_location), m_name(_name) {} + PrimaryExpression(_id, _location), m_name(_name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1837,10 +1880,11 @@ class ElementaryTypeNameExpression: public PrimaryExpression { public: ElementaryTypeNameExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _type ): - PrimaryExpression(_location), + PrimaryExpression(_id, _location), m_type(_type) { } @@ -1874,12 +1918,13 @@ public: Year = static_cast(Token::SubYear) }; Literal( + int64_t _id, SourceLocation const& _location, Token _token, ASTPointer const& _value, SubDenomination _sub = SubDenomination::None ): - PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} + PrimaryExpression(_id, _location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp new file mode 100644 index 000000000..6b1005777 --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -0,0 +1,982 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + *Component that imports an AST from json format to the internal format + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +template +ASTPointer ASTJsonImporter::nullOrCast(Json::Value const& _json) +{ + if (_json.isNull()) + return nullptr; + else + return dynamic_pointer_cast(convertJsonToASTNode(_json)); +} + + +// ============ public =========================== + +map> ASTJsonImporter::jsonToSourceUnit(map const& _sourceList) +{ + m_sourceList = _sourceList; + for (auto const& src: _sourceList) + m_sourceLocations.emplace_back(make_shared(src.first)); + for (auto const& srcPair: m_sourceList) + { + astAssert(!srcPair.second.isNull(), ""); + astAssert(member(srcPair.second,"nodeType") == "SourceUnit", "The 'nodeType' of the highest node must be 'SourceUnit'."); + m_currentSourceName = srcPair.first; + m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first); + } + return m_sourceUnits; +} + +// ============ private =========================== + +// =========== general creation functions ============== +template +ASTPointer ASTJsonImporter::createASTNode(Json::Value const& _node, Args&&... _args) +{ + astAssert(member(_node, "id").isInt64(), "'id'-field must be 64bit integer."); + + int64_t id = _node["id"].asInt64(); + + astAssert(m_usedIDs.insert(id).second, "Found duplicate node ID!"); + + auto n = make_shared( + id, + createSourceLocation(_node), + forward(_args)... + ); + return n; +} + +SourceLocation const ASTJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_currentSourceName, int(m_sourceLocations.size())); +} + +template +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _node) +{ + ASTPointer ret = dynamic_pointer_cast(convertJsonToASTNode(_node)); + astAssert(ret, "cast of converted json-node must not be nullptr"); + return ret; +} + + +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _json) +{ + astAssert(_json["nodeType"].isString() && _json.isMember("id"), "JSON-Node needs to have 'nodeType' and 'id' fields."); + string nodeType = _json["nodeType"].asString(); + if (nodeType == "PragmaDirective") + return createPragmaDirective(_json); + if (nodeType == "ImportDirective") + return createImportDirective(_json); + if (nodeType == "ContractDefinition") + return createContractDefinition(_json); + if (nodeType == "InheritanceSpecifier") + return createInheritanceSpecifier(_json); + if (nodeType == "UsingForDirective") + return createUsingForDirective(_json); + if (nodeType == "StructDefinition") + return createStructDefinition(_json); + if (nodeType == "EnumDefinition") + return createEnumDefinition(_json); + if (nodeType == "EnumValue") + return createEnumValue(_json); + if (nodeType == "ParameterList") + return createParameterList(_json); + if (nodeType == "OverrideSpecifier") + return createOverrideSpecifier(_json); + if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_json); + if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_json); + if (nodeType == "ModifierDefinition") + return createModifierDefinition(_json); + if (nodeType == "ModifierInvocation") + return createModifierInvocation(_json); + if (nodeType == "EventDefinition") + return createEventDefinition(_json); + if (nodeType == "ElementaryTypeName") + return createElementaryTypeName(_json); + if (nodeType == "UserDefinedTypeName") + return createUserDefinedTypeName(_json); + if (nodeType == "FunctionTypeName") + return createFunctionTypeName(_json); + if (nodeType == "Mapping") + return createMapping(_json); + if (nodeType == "ArrayTypeName") + return createArrayTypeName(_json); + if (nodeType == "InlineAssembly") + return createInlineAssembly(_json); + if (nodeType == "Block") + return createBlock(_json); + if (nodeType == "PlaceholderStatement") + return createPlaceholderStatement(_json); + if (nodeType == "IfStatement") + return createIfStatement(_json); + if (nodeType == "TryCatchClause") + return createTryCatchClause(_json); + if (nodeType == "TryStatement") + return createTryStatement(_json); + if (nodeType == "WhileStatement") + return createWhileStatement(_json, false); + if (nodeType == "DoWhileStatement") + return createWhileStatement(_json, true); + if (nodeType == "ForStatement") + return createForStatement(_json); + if (nodeType == "Continue") + return createContinue(_json); + if (nodeType == "Break") + return createBreak(_json); + if (nodeType == "Return") + return createReturn(_json); + if (nodeType == "EmitStatement") + return createEmitStatement(_json); + if (nodeType == "Throw") + return createThrow(_json); + if (nodeType == "VariableDeclarationStatement") + return createVariableDeclarationStatement(_json); + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_json); + if (nodeType == "Conditional") + return createConditional(_json); + if (nodeType == "Assignment") + return createAssignment(_json); + if (nodeType == "TupleExpression") + return createTupleExpression(_json); + if (nodeType == "UnaryOperation") + return createUnaryOperation(_json); + if (nodeType == "BinaryOperation") + return createBinaryOperation(_json); + if (nodeType == "FunctionCall") + return createFunctionCall(_json); + if (nodeType == "NewExpression") + return createNewExpression(_json); + if (nodeType == "MemberAccess") + return createMemberAccess(_json); + if (nodeType == "IndexAccess") + return createIndexAccess(_json); + if (nodeType == "IndexRangeAccess") + return createIndexRangeAccess(_json); + if (nodeType == "Identifier") + return createIdentifier(_json); + if (nodeType == "ElementaryTypeNameExpression") + return createElementaryTypeNameExpression(_json); + if (nodeType == "Literal") + return createLiteral(_json); + else + astAssert(false, "Unknown type of ASTNode: " + nodeType); +} + +// ============ functions to instantiate the AST-Nodes from Json-Nodes ============== + +ASTPointer ASTJsonImporter::createSourceUnit(Json::Value const& _node, string const& _srcName) +{ + vector> nodes; + for (auto& child: member(_node, "nodes")) + nodes.emplace_back(convertJsonToASTNode(child)); + ASTPointer tmp = createASTNode(_node, nodes); + tmp->annotation().path = _srcName; + return tmp; +} + +ASTPointer ASTJsonImporter::createPragmaDirective(Json::Value const& _node) +{ + vector tokens; + vector literals; + for (auto const& lit: member(_node, "literals")) + { + string l = lit.asString(); + literals.push_back(l); + tokens.push_back(scanSingleToken(l)); + } + return createASTNode(_node, tokens, literals); +} + +ASTPointer ASTJsonImporter::createImportDirective(Json::Value const& _node) +{ + ASTPointer unitAlias = memberAsASTString(_node, "unitAlias"); + ASTPointer path = memberAsASTString(_node, "file"); + ImportDirective::SymbolAliasList symbolAliases; + + for (auto& tuple: member(_node, "symbolAliases")) + { + astAssert(tuple["local"].isNull() || tuple["local"].isString(), "expected 'local' to be a string or null!"); + + symbolAliases.push_back({ + createIdentifier(tuple["foreign"]), + tuple["local"].isNull() ? nullptr : make_shared(tuple["local"].asString()), + createSourceLocation(tuple["foreign"])} + ); + } + ASTPointer tmp = createASTNode( + _node, + path, + unitAlias, + move(symbolAliases) + ); + + astAssert(_node["absolutePath"].isString(), "Expected 'absolutePath' to be a string!"); + + tmp->annotation().absolutePath = _node["absolutePath"].asString(); + return tmp; +} + +ASTPointer ASTJsonImporter::createContractDefinition(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + std::vector> baseContracts; + + for (auto& base: _node["baseContracts"]) + baseContracts.push_back(createInheritanceSpecifier(base)); + + std::vector> subNodes; + + for (auto& subnode: _node["nodes"]) + subNodes.push_back(convertJsonToASTNode(subnode)); + + return createASTNode( + _node, + make_shared(_node["name"].asString()), + nullOrASTString(_node, "documentation"), + baseContracts, + subNodes, + contractKind(_node), + memberAsBool(_node, "abstract") + ); +} + +ASTPointer ASTJsonImporter::createInheritanceSpecifier(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "baseName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createUsingForDirective(Json::Value const& _node) +{ + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "libraryName")), + _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]) + ); +} + +ASTPointer ASTJsonImporter::createStructDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createVariableDeclaration(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createEnumValue(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumValue(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name") + ); +} + +ASTPointer ASTJsonImporter::createParameterList(Json::Value const& _node) +{ + std::vector> parameters; + for (auto& param: _node["parameters"]) + parameters.push_back(createVariableDeclaration(param)); + return createASTNode( + _node, + parameters + ); +} + +ASTPointer ASTJsonImporter::createOverrideSpecifier(Json::Value const& _node) +{ + std::vector> overrides; + + for (auto& param: _node["overrides"]) + overrides.push_back(createUserDefinedTypeName(param)); + + return createASTNode( + _node, + overrides + ); +} + +ASTPointer ASTJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + astAssert(_node["kind"].isString(), "Expected 'kind' to be a string!"); + + Token kind; + string kindStr = member(_node, "kind").asString(); + + if (kindStr == "constructor") + kind = Token::Constructor; + else if (kindStr == "function") + kind = Token::Function; + else if (kindStr == "fallback") + kind = Token::Fallback; + else if (kindStr == "receive") + kind = Token::Receive; + else + astAssert(false, "Expected 'kind' to be one of [constructor, function, fallback, receive]"); + + std::vector> modifiers; + for (auto& mod: member(_node, "modifiers")) + modifiers.push_back(createModifierInvocation(mod)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + visibility(_node), + stateMutability(_node), + kind, + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + nullOrASTString(_node, "documentation"), + createParameterList(member(_node, "parameters")), + modifiers, + createParameterList(member(_node, "returnParameters")), + memberAsBool(_node, "implemented") ? createBlock(member(_node, "body")) : nullptr + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + return createASTNode( + _node, + nullOrCast(member(_node, "typeName")), + make_shared(member(_node, "name").asString()), + nullOrCast(member(_node, "value")), + visibility(_node), + memberAsBool(_node, "stateVariable"), + _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, + memberAsBool(_node, "constant"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + location(_node) + ); +} + +ASTPointer ASTJsonImporter::createModifierDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + nullOrASTString(_node,"documentation"), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + createBlock(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createModifierInvocation(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createIdentifier(member(_node, "modifierName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createEventDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + nullOrASTString(_node, "documentation"), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "anonymous") + ); +} + +ASTPointer ASTJsonImporter::createElementaryTypeName(Json::Value const& _node) +{ + unsigned short firstNum; + unsigned short secondNum; + + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + string name = member(_node, "name").asString(); + Token token; + tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(name); + ElementaryTypeNameToken elem(token, firstNum, secondNum); + + std::optional mutability = {}; + if (_node.isMember("stateMutability")) + mutability = stateMutability(_node); + + return createASTNode(_node, elem, mutability); +} + +ASTPointer ASTJsonImporter::createUserDefinedTypeName(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + vector namePath; + vector strs; + string nameString = member(_node, "name").asString(); + boost::algorithm::split(strs, nameString, boost::is_any_of(".")); + for (string s: strs) + namePath.push_back(ASTString(s)); + return createASTNode( + _node, + namePath + ); +} + +ASTPointer ASTJsonImporter::createFunctionTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + createParameterList(member(_node, "parameterTypes")), + createParameterList(member(_node, "returnParameterTypes")), + visibility(_node), + stateMutability(_node) + ); +} + +ASTPointer ASTJsonImporter::createMapping(Json::Value const& _node) +{ + return createASTNode( + _node, + createElementaryTypeName(member(_node, "keyType")), + convertJsonToASTNode(member(_node, "valueType")) + ); +} + +ASTPointer ASTJsonImporter::createArrayTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseType")), + nullOrCast(member(_node, "length")) + ); +} + +ASTPointer ASTJsonImporter::createInlineAssembly(Json::Value const& _node) +{ + astAssert(_node["evmVersion"].isString(), "Expected evmVersion to be a string!"); + auto evmVersion = langutil::EVMVersion::fromString(_node["evmVersion"].asString()); + astAssert(evmVersion.has_value(), "Invalid EVM version!"); + astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!"); + + yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value()); + shared_ptr operations = make_shared(AsmJsonImporter(m_currentSourceName).createBlock(member(_node, "AST"))); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + dialect, + operations + ); +} + +ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) +{ + std::vector> statements; + for (auto& stat: member(_node, "statements")) + statements.push_back(convertJsonToASTNode(stat)); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + statements + ); +} + +ASTPointer ASTJsonImporter::createPlaceholderStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createIfStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueBody")), + nullOrCast(member(_node, "falseBody")) + ); +} + +ASTPointer ASTJsonImporter::createTryCatchClause(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "errorName"), + nullOrCast(member(_node, "parameters")), + convertJsonToASTNode(member(_node, "block")) + ); +} + +ASTPointer ASTJsonImporter::createTryStatement(Json::Value const& _node) +{ + vector> clauses; + + for (auto& param: _node["clauses"]) + clauses.emplace_back(createTryCatchClause(param)); + + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "externalCall")), + clauses + ); +} + +ASTPointer ASTJsonImporter::createWhileStatement(Json::Value const& _node, bool _isDoWhile=false) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "body")), + _isDoWhile + ); +} + +ASTPointer ASTJsonImporter::createForStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "initializationExpression")), + nullOrCast(member(_node, "condition")), + nullOrCast(member(_node, "loopExpression")), + convertJsonToASTNode(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createContinue(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createBreak(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createReturn(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createThrow(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createEmitStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + createFunctionCall(member(_node, "eventCall")) + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclarationStatement(Json::Value const& _node) +{ + std::vector> variables; + for (auto& var: member(_node, "declarations")) + variables.push_back(var.isNull() ? nullptr : createVariableDeclaration(var)); //unnamed components are empty pointers + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + variables, + nullOrCast(member(_node, "initialValue")) + ); +} + +ASTPointer ASTJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createConditional(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueExpression")), + convertJsonToASTNode(member(_node, "falseExpression")) + ); +} + +ASTPointer ASTJsonImporter::createAssignment(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftHandSide")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightHandSide")) + ); +} + +ASTPointer ASTJsonImporter::createTupleExpression(Json::Value const& _node) +{ + std::vector> components; + for (auto& comp: member(_node, "components")) + components.push_back(nullOrCast(comp)); + return createASTNode( + _node, + components, + memberAsBool(_node, "isInlineArray") + ); +} + +ASTPointer ASTJsonImporter::createUnaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "subExpression")), + memberAsBool(_node, "prefix") + ); +} + +ASTPointer ASTJsonImporter::createBinaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftExpression")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightExpression")) + ); +} + +ASTPointer ASTJsonImporter::createFunctionCall(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + std::vector> names; + for (auto& name: member(_node, "names")) + { + astAssert(name.isString(), "Expected 'names' members to be strings!"); + names.push_back(make_shared(name.asString())); + } + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + arguments, + names + ); +} + +ASTPointer ASTJsonImporter::createNewExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createMemberAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + memberAsASTString(_node, "memberName") + ); +} + +ASTPointer ASTJsonImporter::createIndexAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "indexExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIndexRangeAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "startExpression")), + nullOrCast(member(_node, "endExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIdentifier(Json::Value const& _node) +{ + return createASTNode(_node, memberAsASTString(_node, "name")); +} + +ASTPointer ASTJsonImporter::createElementaryTypeNameExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + createElementaryTypeName(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createLiteral(Json::Value const& _node) +{ + static string const valStr = "value"; + static string const hexValStr = "hexValue"; + + astAssert(member(_node, valStr).isString() || member(_node, hexValStr).isString(), "Literal-value is unset."); + + ASTPointer value = _node.isMember(hexValStr) ? + make_shared(util::asString(util::fromHex(_node[hexValStr].asString()))) : + make_shared(_node[valStr].asString()); + + return createASTNode( + _node, + literalTokenKind(_node), + value, + member(_node, "subdenomination").isNull() ? Literal::SubDenomination::None : subdenomination(_node) + ); +} + +// ===== helper functions ========== + +Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node '" + _node["nodeType"].asString() + "' (id " + _node["id"].asString() + ") is missing field '" + _name + "'."); + return _node[_name]; +} + +Token ASTJsonImporter::scanSingleToken(Json::Value const& _node) +{ + langutil::Scanner scanner{langutil::CharStream(_node.asString(), "")}; + astAssert(scanner.peekNextToken() == Token::EOS, "Token string is too long."); + return scanner.currentToken(); +} + +ASTPointer ASTJsonImporter::nullOrASTString(Json::Value const& _json, string const& _name) +{ + return _json[_name].isString() ? memberAsASTString(_json, _name) : nullptr; +} + +ASTPointer ASTJsonImporter::memberAsASTString(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isString(), "field " + _name + " must be of type string."); + return make_shared(_node[_name].asString()); +} + +bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isBool(), "field " + _name + " must be of type boolean."); + return _node[_name].asBool(); +} + + +// =========== JSON to definition helpers ======================= + +ContractKind ASTJsonImporter::contractKind(Json::Value const& _node) +{ + ContractKind kind; + astAssert(!member(_node, "contractKind").isNull(), "'Contract-kind' can not be null."); + if (_node["contractKind"].asString() == "interface") + kind = ContractKind::Interface; + else if (_node["contractKind"].asString() == "contract") + kind = ContractKind::Contract; + else if (_node["contractKind"].asString() == "library") + kind = ContractKind::Library; + else + astAssert(false, "Unknown ContractKind"); + return kind; +} + +Token ASTJsonImporter::literalTokenKind(Json::Value const& _node) +{ + astAssert(member(_node, "kind").isString(), "Token-'kind' expected to be a string."); + Token tok; + if (_node["kind"].asString() == "number") + tok = Token::Number; + else if (_node["kind"].asString() == "string") + tok = Token::StringLiteral; + else if (_node["kind"].asString() == "bool") + tok = (member(_node, "value").asString() == "true") ? Token::TrueLiteral : Token::FalseLiteral; + else + astAssert(false, "Unknown kind of literalString"); + return tok; +} + +Visibility ASTJsonImporter::visibility(Json::Value const& _node) +{ + Json::Value visibility = member(_node, "visibility"); + astAssert(visibility.isString(), "'visibility' expected to be a string."); + + string const visibilityStr = visibility.asString(); + + if (visibilityStr == "default") + return Visibility::Default; + else if (visibilityStr == "private") + return Visibility::Private; + else if ( visibilityStr == "internal") + return Visibility::Internal; + else if (visibilityStr == "public") + return Visibility::Public; + else if (visibilityStr == "external") + return Visibility::External; + else + astAssert(false, "Unknown visibility declaration"); +} + +VariableDeclaration::Location ASTJsonImporter::location(Json::Value const& _node) +{ + Json::Value storageLoc = member(_node, "storageLocation"); + astAssert(storageLoc.isString(), "'storageLocation' expected to be a string."); + + string const storageLocStr = storageLoc.asString(); + + if (storageLocStr == "default") + return VariableDeclaration::Location::Unspecified; + else if (storageLocStr == "storage") + return VariableDeclaration::Location::Storage; + else if (storageLocStr == "memory") + return VariableDeclaration::Location::Memory; + else if (storageLocStr == "calldata") + return VariableDeclaration::Location::CallData; + else + astAssert(false, "Unknown location declaration"); +} + +Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _node) +{ + Json::Value subDen = member(_node, "subdenomination"); + + if (subDen.isNull()) + return Literal::SubDenomination::None; + + astAssert(subDen.isString(), "'subDenomination' expected to be string."); + + string const subDenStr = subDen.asString(); + + if (subDenStr == "wei") + return Literal::SubDenomination::Wei; + else if (subDenStr == "szabo") + return Literal::SubDenomination::Szabo; + else if (subDenStr == "finney") + return Literal::SubDenomination::Finney; + else if (subDenStr == "ether") + return Literal::SubDenomination::Ether; + else if (subDenStr == "seconds") + return Literal::SubDenomination::Second; + else if (subDenStr == "minutes") + return Literal::SubDenomination::Minute; + else if (subDenStr == "hours") + return Literal::SubDenomination::Hour; + else if (subDenStr == "days") + return Literal::SubDenomination::Day; + else if (subDenStr == "weeks") + return Literal::SubDenomination::Week; + else if (subDenStr == "years") + return Literal::SubDenomination::Year; + else + astAssert(false, "Unknown subdenomination"); +} + +StateMutability ASTJsonImporter::stateMutability(Json::Value const& _node) +{ + astAssert(member(_node, "stateMutability").isString(), "StateMutability' expected to be string."); + string const mutabilityStr = member(_node, "stateMutability").asString(); + + if (mutabilityStr == "pure") + return StateMutability::Pure; + else if (mutabilityStr == "view") + return StateMutability::View; + else if (mutabilityStr == "nonpayable") + return StateMutability::NonPayable; + else if (mutabilityStr == "payable") + return StateMutability::Payable; + else + astAssert(false, "Unknown stateMutability"); +} + +} diff --git a/libsolidity/ast/ASTJsonImporter.h b/libsolidity/ast/ASTJsonImporter.h new file mode 100644 index 000000000..28786961a --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.h @@ -0,0 +1,161 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts the AST from JSON format to ASTNode + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class ASTJsonImporter +{ +public: + ASTJsonImporter(langutil::EVMVersion _evmVersion) + :m_evmVersion(_evmVersion) + {} + + /// Converts the AST from JSON-format to ASTPointer + /// @a _sourceList used to provide source names for the ASTs + /// @returns map of sourcenames to their respective ASTs + std::map> jsonToSourceUnit(std::map const& _sourceList); + +private: + + // =========== general creation functions ============== + + /// Sets the source location and nodeID + /// @returns the ASTNode Object class of the respective JSON node, + template + ASTPointer createASTNode(Json::Value const& _node, Args&&... _args); + /// @returns the sourceLocation-object created from the string in the JSON node + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + /// Creates an ASTNode for a given JSON-ast of unknown type + /// @returns Pointer to a new created ASTNode + ASTPointer convertJsonToASTNode(Json::Value const& _ast); + /// @returns a pointer to the more specific subclass of ASTNode + /// as indicated by the nodeType field of the json + template + ASTPointer convertJsonToASTNode(Json::Value const& _node); + + + /// \defgroup nodeCreators JSON to AST-Nodes + ///@{ + ASTPointer createSourceUnit(Json::Value const& _node, std::string const& _srcName); + ASTPointer createPragmaDirective(Json::Value const& _node); + ASTPointer createImportDirective(Json::Value const& _node); + ASTPointer createContractDefinition(Json::Value const& _node); + ASTPointer createInheritanceSpecifier(Json::Value const& _node); + ASTPointer createUsingForDirective(Json::Value const& _node); + ASTPointer createStructDefinition(Json::Value const& _node); + ASTPointer createEnumDefinition(Json::Value const& _node); + ASTPointer createEnumValue(Json::Value const& _node); + ASTPointer createParameterList(Json::Value const& _node); + ASTPointer createOverrideSpecifier(Json::Value const& _node); + ASTPointer createFunctionDefinition(Json::Value const& _node); + ASTPointer createVariableDeclaration(Json::Value const& _node); + ASTPointer createModifierDefinition(Json::Value const& _node); + ASTPointer createModifierInvocation(Json::Value const& _node); + ASTPointer createEventDefinition(Json::Value const& _node); + ASTPointer createElementaryTypeName(Json::Value const& _node); + ASTPointer createUserDefinedTypeName(Json::Value const& _node); + ASTPointer createFunctionTypeName(Json::Value const& _node); + ASTPointer createMapping(Json::Value const& _node); + ASTPointer createArrayTypeName(Json::Value const& _node); + ASTPointer createInlineAssembly(Json::Value const& _node); + ASTPointer createBlock(Json::Value const& _node); + ASTPointer createPlaceholderStatement(Json::Value const& _node); + ASTPointer createIfStatement(Json::Value const& _node); + ASTPointer createTryCatchClause(Json::Value const& _node); + ASTPointer createTryStatement(Json::Value const& _node); + ASTPointer createWhileStatement(Json::Value const& _node, bool _isDoWhile); + ASTPointer createForStatement(Json::Value const& _node); + ASTPointer createContinue(Json::Value const& _node); + ASTPointer createBreak(Json::Value const& _node); + ASTPointer createReturn(Json::Value const& _node); + ASTPointer createThrow(Json::Value const& _node); + ASTPointer createEmitStatement(Json::Value const& _node); + ASTPointer createVariableDeclarationStatement(Json::Value const& _node); + ASTPointer createExpressionStatement(Json::Value const& _node); + ASTPointer createConditional(Json::Value const& _node); + ASTPointer createAssignment(Json::Value const& _node); + ASTPointer createTupleExpression(Json::Value const& _node); + ASTPointer createUnaryOperation(Json::Value const& _node); + ASTPointer createBinaryOperation(Json::Value const& _node); + ASTPointer createFunctionCall(Json::Value const& _node); + ASTPointer createNewExpression(Json::Value const& _node); + ASTPointer createMemberAccess(Json::Value const& _node); + ASTPointer createIndexAccess(Json::Value const& _node); + ASTPointer createIndexRangeAccess(Json::Value const& _node); + ASTPointer createIdentifier(Json::Value const& _node); + ASTPointer createElementaryTypeNameExpression(Json::Value const& _node); + ASTPointer createLiteral(Json::Value const& _node); + ///@} + + // =============== general helper functions =================== + /// @returns the member of a given JSON object, throws if member does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + /// @returns the appropriate TokenObject used in parsed Strings (pragma directive or operator) + Token scanSingleToken(Json::Value const& _node); + template + ///@returns nullptr or an ASTPointer cast to a specific Class + ASTPointer nullOrCast(Json::Value const& _json); + /// @returns nullptr or ASTString, given an JSON string or an empty field + ASTPointer nullOrASTString(Json::Value const& _json, std::string const& _name); + + // ============== JSON to definition helpers =============== + /// \defgroup typeHelpers Json to ast-datatype helpers + /// {@ + ASTPointer memberAsASTString(Json::Value const& _node, std::string const& _name); + bool memberAsBool(Json::Value const& _node, std::string const& _name); + Visibility visibility(Json::Value const& _node); + StateMutability stateMutability(Json::Value const& _node); + VariableDeclaration::Location location(Json::Value const& _node); + ContractKind contractKind(Json::Value const& _node); + Token literalTokenKind(Json::Value const& _node); + Literal::SubDenomination subdenomination(Json::Value const& _node); + ///@} + + // =========== member variables =============== + /// Stores filepath as sourcenames to AST in JSON format + std::map m_sourceList; + /// list of filepaths (used as sourcenames) + std::vector> m_sourceLocations; + /// filepath to AST + std::map> m_sourceUnits; + std::string m_currentSourceName; + /// IDs already used by the nodes + std::set m_usedIDs; + /// Configured EVM version + langutil::EVMVersion m_evmVersion; +}; + +} diff --git a/libsolidity/ast/AsmJsonImporter.cpp b/libsolidity/ast/AsmJsonImporter.cpp new file mode 100644 index 000000000..8f4559a55 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.cpp @@ -0,0 +1,303 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +using namespace std; +using namespace solidity::yul; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +SourceLocation const AsmJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_sourceName); +} + +template +T AsmJsonImporter::createAsmNode(Json::Value const& _node) +{ + T r; + r.location = createSourceLocation(_node); + astAssert(!r.location.isEmpty() || !r.location.source, "Invalid source location in Asm AST"); + return r; + +} + +Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node is missing field '" + _name + "'."); + return _node[_name]; +} + +yul::TypedName AsmJsonImporter::createTypedName(Json::Value const& _node) +{ + auto typedName = createAsmNode(_node); + typedName.type = YulString{member(_node, "type").asString()}; + typedName.name = YulString{member(_node, "name").asString()}; + return typedName; +} + +yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_node); + else if (nodeType == "Assignment") + return createAssignment(_node); + else if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_node); + else if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_node); + else if (nodeType == "If") + return createIf(_node); + else if (nodeType == "Switch") + return createSwitch(_node); + else if (nodeType == "ForLoop") + return createForLoop(_node); + else if (nodeType == "Break") + return createBreak(_node); + else if (nodeType == "Continue") + return createContinue(_node); + else if (nodeType == "Leave") + return createLeave(_node); + else + astAssert(false, "Invalid nodeType as statement"); +} + +yul::Expression AsmJsonImporter::createExpression(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "FunctionCall") + return createFunctionCall(_node); + else if (nodeType == "Identifier") + return createIdentifier(_node); + else if (nodeType == "Literal") + return createLiteral(_node); + else + astAssert(false, "Invalid nodeType as expression"); +} + +vector AsmJsonImporter::createExpressionVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createExpression(var)); + return ret; +} + +vector AsmJsonImporter::createStatementVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createStatement(var)); + return ret; +} + +yul::Block AsmJsonImporter::createBlock(Json::Value const& _node) +{ + auto block = createAsmNode(_node); + block.statements = createStatementVector(_node["statements"]); + return block; +} + +yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node) +{ + auto lit = createAsmNode(_node); + string kind = member(_node, "kind").asString(); + + lit.value = YulString{member(_node, "value").asString()}; + lit.type= YulString{member(_node, "type").asString()}; + + langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")}; + + if (kind == "number") + { + lit.kind = yul::LiteralKind::Number; + astAssert( + scanner.currentToken() == Token::Number, + "Expected number but got " + langutil::TokenTraits::friendlyName(scanner.currentToken()) + string(" while scanning ") + lit.value.str() + ); + } + else if (kind == "bool") + { + lit.kind = yul::LiteralKind::Boolean; + astAssert( + scanner.currentToken() == Token::TrueLiteral || + scanner.currentToken() == Token::FalseLiteral, + "Expected true/false literal!" + ); + } + else if (kind == "string") + { + lit.kind = yul::LiteralKind::String; + astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!"); + } + else + solAssert(false, "unknown type of literal"); + + return lit; +} + +yul::Leave AsmJsonImporter::createLeave(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Identifier AsmJsonImporter::createIdentifier(Json::Value const& _node) +{ + auto identifier = createAsmNode(_node); + identifier.name = YulString(member(_node, "name").asString()); + return identifier; +} + +yul::Assignment AsmJsonImporter::createAssignment(Json::Value const& _node) +{ + auto assignment = createAsmNode(_node); + + if (_node.isMember("variableNames")) + for (auto const& var: member(_node, "variableNames")) + assignment.variableNames.emplace_back(createIdentifier(var)); + + assignment.value = make_unique(createExpression(member(_node, "value"))); + return assignment; +} + +yul::FunctionCall AsmJsonImporter::createFunctionCall(Json::Value const& _node) +{ + auto functionCall = createAsmNode(_node); + + for (auto const& var: member(_node, "arguments")) + functionCall.arguments.emplace_back(createExpression(var)); + + functionCall.functionName = createIdentifier(member(_node, "functionName")); + + return functionCall; +} + +yul::ExpressionStatement AsmJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + auto statement = createAsmNode(_node); + statement.expression = createExpression(member(_node, "expression")); + return statement; +} + +yul::VariableDeclaration AsmJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + auto varDec = createAsmNode(_node); + for (auto const& var: member(_node, "variables")) + varDec.variables.emplace_back(createTypedName(var)); + varDec.value = make_unique(createExpression(member(_node, "value"))); + return varDec; +} + +yul::FunctionDefinition AsmJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + auto funcDef = createAsmNode(_node); + funcDef.name = YulString{member(_node, "name").asString()}; + + if (_node.isMember("parameters")) + for (auto const& var: member(_node, "parameters")) + funcDef.parameters.emplace_back(createTypedName(var)); + + if (_node.isMember("returnVariables")) + for (auto const& var: member(_node, "returnVariables")) + funcDef.returnVariables.emplace_back(createTypedName(var)); + + funcDef.body = createBlock(member(_node, "body")); + return funcDef; +} + +yul::If AsmJsonImporter::createIf(Json::Value const& _node) +{ + auto ifStatement = createAsmNode(_node); + ifStatement.condition = make_unique(createExpression(member(_node, "condition"))); + ifStatement.body = createBlock(member(_node, "body")); + return ifStatement; +} + +yul::Case AsmJsonImporter::createCase(Json::Value const& _node) +{ + auto caseStatement = createAsmNode(_node); + caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique(createLiteral(member(_node, "value"))); + caseStatement.body = createBlock(member(_node, "body")); + return caseStatement; +} + +yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node) +{ + auto switchStatement = createAsmNode(_node); + switchStatement.expression = make_unique(createExpression(member(_node, "value"))); + for (auto const& var: member(_node, "cases")) + switchStatement.cases.emplace_back(createCase(var)); + return switchStatement; +} + +yul::ForLoop AsmJsonImporter::createForLoop(Json::Value const& _node) +{ + auto forLoop = createAsmNode(_node); + forLoop.pre = createBlock(member(_node, "pre")); + forLoop.condition = make_unique(createExpression(member(_node, "condition"))); + forLoop.post = createBlock(member(_node, "post")); + forLoop.body = createBlock(member(_node, "body")); + return forLoop; +} + +yul::Break AsmJsonImporter::createBreak(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Continue AsmJsonImporter::createContinue(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +} diff --git a/libsolidity/ast/AsmJsonImporter.h b/libsolidity/ast/AsmJsonImporter.h new file mode 100644 index 000000000..e7f4821c2 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.h @@ -0,0 +1,74 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + */ + +#pragma once + +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class AsmJsonImporter +{ +public: + explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(_sourceName) {} + yul::Block createBlock(Json::Value const& _node); + +private: + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + template + T createAsmNode(Json::Value const& _node); + /// helper function to access member functions of the JSON + /// and throw an error if it does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + + yul::Statement createStatement(Json::Value const& _node); + yul::Expression createExpression(Json::Value const& _node); + std::vector createStatementVector(Json::Value const& _array); + std::vector createExpressionVector(Json::Value const& _array); + + yul::TypedName createTypedName(Json::Value const& _node); + yul::Literal createLiteral(Json::Value const& _node); + yul::Leave createLeave(Json::Value const& _node); + yul::Identifier createIdentifier(Json::Value const& _node); + yul::Assignment createAssignment(Json::Value const& _node); + yul::FunctionCall createFunctionCall(Json::Value const& _node); + yul::ExpressionStatement createExpressionStatement(Json::Value const& _node); + yul::VariableDeclaration createVariableDeclaration(Json::Value const& _node); + yul::FunctionDefinition createFunctionDefinition(Json::Value const& _node); + yul::If createIf(Json::Value const& _node); + yul::Case createCase(Json::Value const& _node); + yul::Switch createSwitch(Json::Value const& _node); + yul::ForLoop createForLoop(Json::Value const& _node); + yul::Break createBreak(Json::Value const& _node); + yul::Continue createContinue(Json::Value const& _node); + + std::string m_sourceName; + +}; + +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 1474b2826..bbc94e031 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -233,11 +234,12 @@ bool CompilerStack::parse() if (m_stackState != SourcesSet) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call parse only after the SourcesSet state.")); m_errorReporter.clear(); - ASTNode::resetID(); if (SemVerVersion{string(VersionString)}.isPrerelease()) m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production."); + Parser parser{m_errorReporter, m_evmVersion, m_parserErrorRecovery}; + vector sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); @@ -246,7 +248,7 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner); + source.ast = parser.parse(source.scanner); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); else @@ -268,6 +270,26 @@ bool CompilerStack::parse() return !m_hasError; } +void CompilerStack::importASTs(map const& _sources) +{ + if (m_stackState != Empty) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call importASTs only before the SourcesSet state.")); + m_sourceJsons = _sources; + map> reconstructedSources = ASTJsonImporter(m_evmVersion).jsonToSourceUnit(m_sourceJsons); + for (auto& src: reconstructedSources) + { + string const& path = src.first; + Source source; + source.ast = src.second; + string srcString = util::jsonCompactPrint(m_sourceJsons[src.first]); + ASTPointer scanner = make_shared(langutil::CharStream(srcString, src.first)); + source.scanner = scanner; + m_sources[path] = source; + } + m_stackState = ParsingPerformed; + m_importedSources = true; +} + bool CompilerStack::analyze() { if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed) @@ -1158,7 +1180,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const { Json::Value meta; meta["version"] = 1; - meta["language"] = "Solidity"; + meta["language"] = m_importedSources ? "SolidityAST" : "Solidity"; meta["compiler"]["version"] = VersionStringStrict; /// All the source files (including self), which should be included in the metadata. diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 0e9ffaff1..42279a499 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -199,6 +199,10 @@ public: /// @returns false on error. bool parse(); + /// Imports given SourceUnits so they can be analyzed. Leads to the same internal state as parse(). + /// Will throw errors if the import fails + void importASTs(std::map const& _sources); + /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, /// typechecking, staticAnalysis) on previously parsed sources. /// @returns false on error. @@ -440,6 +444,8 @@ private: /// "context:prefix=target" std::vector m_remappings; std::map m_sources; + // if imported, store AST-JSONS for each filename + std::map m_sourceJsons; std::vector m_unhandledSMTLib2Queries; std::map m_smtlib2Responses; std::shared_ptr m_globalContext; @@ -453,6 +459,7 @@ private: MetadataHash m_metadataHash = MetadataHash::IPFS; bool m_parserErrorRecovery = false; State m_stackState = Empty; + bool m_importedSources = false; /// Whether or not there has been an error during processing. /// If this is true, the stack will refuse to generate code. bool m_hasError = false; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 7991e1aaa..406226da4 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -44,9 +44,9 @@ namespace solidity::frontend class Parser::ASTNodeFactory { public: - explicit ASTNodeFactory(Parser const& _parser): + explicit ASTNodeFactory(Parser& _parser): m_parser(_parser), m_location{_parser.position(), -1, _parser.source()} {} - ASTNodeFactory(Parser const& _parser, ASTPointer const& _childNode): + ASTNodeFactory(Parser& _parser, ASTPointer const& _childNode): m_parser(_parser), m_location{_childNode->location()} {} void markEndPosition() { m_location.end = m_parser.endPosition(); } @@ -61,18 +61,19 @@ public: solAssert(m_location.source, ""); if (m_location.end < 0) markEndPosition(); - return make_shared(m_location, std::forward(_args)...); + return make_shared(m_parser.nextID(), m_location, std::forward(_args)...); } SourceLocation const& location() const noexcept { return m_location; } private: - Parser const& m_parser; + Parser& m_parser; SourceLocation m_location; }; ASTPointer Parser::parse(shared_ptr const& _scanner) { + solAssert(!m_insideModifier, ""); try { m_recursionDepth = 0; @@ -1193,7 +1194,7 @@ ASTPointer Parser::parseInlineAssembly(ASTPointer con BOOST_THROW_EXCEPTION(FatalError()); location.end = block->location.end; - return make_shared(location, _docString, dialect, block); + return make_shared(nextID(), location, _docString, dialect, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 2314e59ce..d066b7f36 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -173,6 +173,9 @@ private: bool empty() const; }; + /// Returns the next AST node ID + int64_t nextID() { return ++m_currentNodeID; } + std::pair tryParseIndexAccessedPath(); /// Performs limited look-ahead to distinguish between variable declaration and expression statement. /// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to @@ -198,6 +201,8 @@ private: /// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier. bool m_insideModifier = false; langutil::EVMVersion m_evmVersion; + /// Counter for the next AST node ID + int64_t m_currentNodeID = 0; }; } diff --git a/scripts/ASTImportTest.sh b/scripts/ASTImportTest.sh new file mode 100755 index 000000000..7b34b14b0 --- /dev/null +++ b/scripts/ASTImportTest.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +# Bash script to test the ast-import option of the compiler by +# first exporting a .sol file to JSON, then loading it into the compiler +# and exporting it again. The second JSON should be identical to the first + +REPO_ROOT=$(realpath "$(dirname "$0")"/..) +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} +SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc +SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py + +SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" +NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)" + +# DEV_DIR="${REPO_ROOT}/../tmp/contracts/" +# NSOURCES="$(find $DEV_DIR -type f | wc -l)" #TODO use find command + +FAILED=0 +UNCOMPILABLE=0 +TESTED=0 + +if [ $(ls | wc -l) -ne 0 ]; then + echo "Test directory not empty. Skipping!" + exit -1 +fi + +# function tests whether exporting and importing again leaves the JSON ast unchanged +# Results are recorded by adding to FAILED or UNCOMPILABLE. +# Also, in case of a mismatch a diff and the respective ASTs are printed +# Expected parameters: +# $1 name of the file to be exported and imported +# $2 any files needed to do so that might be in parent directories +function testImportExportEquivalence { + if $SOLC $1 $2 > /dev/null 2>&1 + then + # save exported json as expected result (silently) + $SOLC --combined-json ast,compact-format --pretty-json $1 $2> expected.json 2> /dev/null + # import it, and export it again as obtained result (silently) + $SOLC --import-ast --combined-json ast,compact-format --pretty-json expected.json > obtained.json 2> /dev/null + if [ $? -ne 0 ] + then + # For investigating, use exit 1 here so the script stops at the + # first failing test + # exit 1 + FAILED=$((FAILED + 1)) + return 1 + fi + DIFF="$(diff expected.json obtained.json)" + if [ "$DIFF" != "" ] + then + if [ "$DIFFVIEW" == "" ] + then + echo -e "ERROR: JSONS differ for $1: \n $DIFF \n" + echo "Expected:" + echo "$(cat ./expected.json)" + echo "Obtained:" + echo "$(cat ./obtained.json)" + else + # Use user supplied diff view binary + $DIFFVIEW expected.json obtained.json + fi + FAILED=$((FAILED + 1)) + return 2 + fi + TESTED=$((TESTED + 1)) + rm expected.json obtained.json + else + # echo "contract $solfile could not be compiled " + UNCOMPILABLE=$((UNCOMPILABLE + 1)) + fi + # return 0 +} +echo "Looking at $NSOURCES .sol files..." + +WORKINGDIR=$PWD + +# for solfile in $(find $DEV_DIR -name *.sol) +for solfile in $(find $SYNTAXTESTS_DIR -name *.sol) +do + echo -n "." + # create a temporary sub-directory + FILETMP=$(mktemp -d -p $WORKINGDIR) + cd $FILETMP + + OUTPUT=$($SPLITSOURCES $solfile) + if [ $? != 1 ] + then + # echo $OUTPUT + NSOURCES=$((NSOURCES - 1)) + for i in $OUTPUT; + do + testImportExportEquivalence $i $OUTPUT + NSOURCES=$((NSOURCES + 1)) + done + + else + testImportExportEquivalence $solfile + fi + + cd $WORKINGDIR + # Delete temporary files + rm -rf $FILETMP +done + +echo "" + +if [ "$FAILED" = 0 ] +then + echo "SUCCESS: $TESTED syntaxTests passed, $FAILED failed, $UNCOMPILABLE could not be compiled ($NSOURCES sources total)." +else + echo "FAILURE: Out of $NSOURCES sources, $FAILED failed, ($UNCOMPILABLE could not be compiled)." + exit 1 +fi diff --git a/scripts/splitSources.py b/scripts/splitSources.py new file mode 100755 index 000000000..0f6f1f86d --- /dev/null +++ b/scripts/splitSources.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python2 +# +# This script reads a syntaxTest file and writes all +# sources into their own files. If one source-name specifies subdirectories +# those will be created too. + +# Usage: scripts/splitSources.py pathToTestfile +# as a result prints +# - string of created files separated by whitespaces +# - 'false' if the file only had one source + +import sys +import os + +hasMultipleSources = False +createdSources = [] + +def extractSourceName(line): + if line.find("/") > -1: + filePath = line[13: line.rindex("/")] + # fileName = line[line.rindex("/")+1: line.find(" ====")] + srcName = line[line.find(":")+2: line.find(" ====")] + return filePath, srcName + return False, line[line.find(":")+2 : line.find(" ====")] + +# expects the first line of lines to be "==== Source: sourceName ====" +# writes the following source into a file named sourceName +def writeSourceToFile(lines): + filePath, srcName = extractSourceName(lines[0]) + # print "sourceName is", srcName + # print "filePath is", filePath + if filePath != False: + os.system("mkdir -p " + filePath) + f = open(srcName, 'a+') + createdSources.append(srcName) + i = 0 + for idx, line in enumerate(lines[1:]): + + # write to file + if line[:12] != "==== Source:": + f.write(line) + + # recursive call if there is another source + else: + writeSourceToFile(lines[1+idx:]) + break + +if __name__ == '__main__': + filePath = sys.argv[1] + # decide if file has multiple sources + lines = open(filePath, 'rb').read().splitlines() + if lines[0][:12] == "==== Source:": + hasMultipleSources = True + writeSourceToFile(lines) + + if hasMultipleSources: + srcString = "" + for src in createdSources: + srcString += src + ' ' + print srcString + else: + sys.exit(1) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index af5d45179..de7814785 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,7 @@ static string const g_strEVMVersion = "evm-version"; static string const g_strEwasm = "ewasm"; static string const g_strGas = "gas"; static string const g_strHelp = "help"; +static string const g_strImportAst = "import-ast"; static string const g_strInputFile = "input-file"; static string const g_strInterface = "interface"; static string const g_strYul = "yul"; @@ -184,6 +186,7 @@ static string const g_argCompactJSON = g_strCompactJSON; static string const g_argErrorRecovery = g_strErrorRecovery; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; +static string const g_argImportAst = g_strImportAst; static string const g_argInputFile = g_strInputFile; static string const g_argYul = g_strYul; static string const g_argIR = g_strIR; @@ -623,6 +626,33 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) return true; } +map CommandLineInterface::parseAstFromInput() +{ + map sourceJsons; + map tmpSources; + + for (auto const& srcPair: m_sourceCodes) + { + Json::Value ast; + astAssert(jsonParseStrict(srcPair.second, ast), "Input file could not be parsed to JSON"); + astAssert(ast.isMember("sources"), "Invalid Format for import-JSON: Must have 'sources'-object"); + + for (auto& src: ast["sources"].getMemberNames()) + { + std::string astKey = ast["sources"][src].isMember("ast") ? "ast" : "AST"; + + astAssert(ast["sources"][src].isMember(astKey), "astkey is not member"); + astAssert(ast["sources"][src][astKey]["nodeType"].asString() == "SourceUnit", "Top-level node should be a 'SourceUnit'"); + astAssert(sourceJsons.count(src) == 0, "All sources must have unique names"); + sourceJsons.emplace(src, move(ast["sources"][src][astKey])); + tmpSources[src] = util::jsonCompactPrint(ast); + } + } + + m_sourceCodes = std::move(tmpSources); + return sourceJsons; +} + void CommandLineInterface::createFile(string const& _fileName, string const& _data) { namespace fs = boost::filesystem; @@ -722,6 +752,12 @@ Allowed options)", "Switch to Standard JSON input / output mode, ignoring all options. " "It reads from standard input and provides the result on the standard output." ) + ( + g_argImportAst.c_str(), + "Import ASTs to be compiled, assumes input holds the AST in compact JSON format." + " Supported Inputs is the output of the standard-json or the one produced by --combined-json ast,compact-format" + ) + ( g_argAssemble.c_str(), "Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly." @@ -1065,10 +1101,9 @@ bool CommandLineInterface::processInput() m_compiler->setMetadataHash(m_metadataHash); if (m_args.count(g_argInputFile)) m_compiler->setRemappings(m_remappings); - m_compiler->setSources(m_sourceCodes); + if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); - m_compiler->setParserErrorRecovery(m_args.count(g_argErrorRecovery)); m_compiler->setEVMVersion(m_evmVersion); m_compiler->setRevertStringBehaviour(m_revertStrings); // TODO: Perhaps we should not compile unless requested @@ -1082,6 +1117,32 @@ bool CommandLineInterface::processInput() settings.optimizeStackAllocation = settings.runYulOptimiser; m_compiler->setOptimiserSettings(settings); + if (m_args.count(g_argImportAst)) + { + try + { + m_compiler->importASTs(parseAstFromInput()); + + if (!m_compiler->analyze()) + { + for (auto const& error: m_compiler->errors()) + formatter->printErrorInformation(*error); + astAssert(false, "Analysis of the AST failed"); + } + } + catch (Exception const& _exc) + { + serr() << string("Failed to import AST: ") << _exc.what() << endl; + return false; + } + } + else + { + m_compiler->setSources(m_sourceCodes); + if (m_args.count(g_argErrorRecovery)) + m_compiler->setParserErrorRecovery(true); + } + bool successful = m_compiler->compile(); for (auto const& error: m_compiler->errors()) diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index fd19fd8a7..553f9e69b 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -80,6 +80,12 @@ private: /// It then tries to parse the contents and appends to m_libraries. bool parseLibraryOption(std::string const& _input); + /// Tries to read @ m_sourceCodes as a JSONs holding ASTs + /// such that they can be imported into the compiler (importASTs()) + /// (produced by --combined-json ast,compact-format + /// or standard-json output + std::map parseAstFromInput(); + /// Create a file in the given directory /// @arg _fileName the name of the file /// @arg _data to be written diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 068f02d4c..95e7bb315 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -437,6 +437,19 @@ SOLTMPDIR=$(mktemp -d) fi ) +printTask "Testing AST import..." +SOLTMPDIR=$(mktemp -d) +( + cd "$SOLTMPDIR" + $REPO_ROOT/scripts/ASTImportTest.sh + if [ $? -ne 0 ] + then + rm -rf "$SOLTMPDIR" + exit 1 + fi +) +rm -rf "$SOLTMPDIR" + printTask "Testing soljson via the fuzzer..." SOLTMPDIR=$(mktemp -d) ( diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index fbab520af..fbf2f3e59 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -282,7 +282,7 @@ "name": "this", "nodeType": "Identifier", "overloadedDeclarations": [], - "referencedDeclaration": 68, + "referencedDeclaration": -28, "src": "217:4:1", "typeDescriptions": { diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index b5f4cf2ff..c45e75c4a 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -446,7 +446,7 @@ [ null ], - "referencedDeclaration": 68, + "referencedDeclaration": -28, "type": "contract C", "value": "this" }, diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 584c18ffe..c6cd25c56 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -145,7 +145,8 @@ BOOST_AUTO_TEST_CASE(type_identifier_escaping) BOOST_AUTO_TEST_CASE(type_identifiers) { - ASTNode::resetID(); + int64_t id = 0; + BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("uint128")->identifier(), "t_uint128"); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("int128")->identifier(), "t_int128"); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("address")->identifier(), "t_address"); @@ -157,7 +158,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77"); BOOST_CHECK_EQUAL(RationalNumberType(rational(-2 * 200, 2 * 77)).identifier(), "t_rational_minus_200_by_77"); BOOST_CHECK_EQUAL( - StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), + StringLiteralType(Literal(++id, SourceLocation{}, Token::StringLiteral, make_shared("abc - def"))).identifier(), "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4" ); BOOST_CHECK_EQUAL(TypeProvider::fromElementaryTypeName("byte")->identifier(), "t_bytes1"); @@ -178,14 +179,14 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TypePointer multiArray = TypeProvider::array(DataLocation::Storage, stringArray); BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); - ContractDefinition c(SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, ContractKind::Contract); + ContractDefinition c(++id, SourceLocation{}, make_shared("MyContract$"), {}, {}, {}, ContractKind::Contract); BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$"); BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); - StructDefinition s({}, make_shared("Struct"), {}); + StructDefinition s(++id, {}, make_shared("Struct"), {}); BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$"); - EnumDefinition e({}, make_shared("Enum"), {}); + EnumDefinition e(++id, {}, make_shared("Enum"), {}); BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type$_t_enum$_Enum_$4_$"); TupleType t({e.type(), s.type(), stringArray, nullptr}); @@ -203,11 +204,11 @@ BOOST_AUTO_TEST_CASE(type_identifiers) // TypeType is tested with contract - auto emptyParams = make_shared(SourceLocation(), std::vector>()); - ModifierDefinition mod(SourceLocation{}, make_shared("modif"), {}, emptyParams, {}, {}, {}); + auto emptyParams = make_shared(++id, SourceLocation(), std::vector>()); + ModifierDefinition mod(++id, SourceLocation{}, make_shared("modif"), {}, emptyParams, {}, {}, {}); BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$"); - SourceUnit su({}, {}); + SourceUnit su(++id, {}, {}); BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7"); BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block"); BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message");