diff options
-rw-r--r-- | Changelog.md | 2 | ||||
-rw-r--r-- | cmake/UseDev.cmake | 1 | ||||
-rw-r--r-- | docs/assembly.rst | 2 | ||||
-rw-r--r-- | docs/contributing.rst | 19 | ||||
-rw-r--r-- | docs/grammar.txt | 5 | ||||
-rw-r--r-- | libdevcore/Whiskers.cpp | 127 | ||||
-rw-r--r-- | libdevcore/Whiskers.h | 87 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 9 | ||||
-rw-r--r-- | test/libdevcore/MiniMoustache.cpp | 127 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 20 |
10 files changed, 397 insertions, 2 deletions
diff --git a/Changelog.md b/Changelog.md index e87cfa8e..6f0d76d6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Features: * Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias. * Inline Assembly: ``for`` and ``switch`` statements. * Inline Assembly: function definitions and function calls. + * Code Generator: Added the Whiskers template system. Bugfixes: * Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences. @@ -17,6 +18,7 @@ Bugfixes: * Unused variable warnings no longer issued for variables used inside inline assembly. * Code Generator: Fix ABI encoding of empty literal string. * Inline Assembly: Enforce function arguments when parsing functional instructions. + * Fixed segfault with constant function parameters ### 0.4.11 (2017-05-03) diff --git a/cmake/UseDev.cmake b/cmake/UseDev.cmake index 4461a8a0..68df691a 100644 --- a/cmake/UseDev.cmake +++ b/cmake/UseDev.cmake @@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE) target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES}) + target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES}) if (DEFINED MSVC) target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES}) diff --git a/docs/assembly.rst b/docs/assembly.rst index 7ef41483..83643634 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -13,6 +13,8 @@ TODO: Write about how scoping rules of inline assembly are a bit different and the complications that arise when for example using internal functions of libraries. Furthermore, write about the symbols defined by the compiler. +.. _inline-assembly: + Inline Assembly =============== diff --git a/docs/contributing.rst b/docs/contributing.rst index 1f869dbb..559f9f6a 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -74,3 +74,22 @@ To run a subset of tests, filters can be used: ``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests. + +Whiskers +======== + +*Whiskers* is a templating system similar to `Moustache <https://mustache.github.io>`_. It is used by the +compiler in various places to aid readability, and thus maintainability and verifiability, of the code. + +The syntax comes with a substantial difference to Moustache: the template markers ``{{`` and ``}}`` are +replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly` +(The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks). +Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future. + +A rough specification is the following: + +Any occurrence of ``<name>`` is replaced by the string-value of the supplied variable ``name`` without any +escaping and without iterated replacements. An area can be delimited by ``<#name>...</name>``. It is replaced +by as many concatenations of its contents as there were sets of variables supplied to the template system, +each time replacing any ``<inner>`` items by their respective value. Top-level variales can also be used +inside such areas.
\ No newline at end of file diff --git a/docs/grammar.txt b/docs/grammar.txt index 09e492f3..b38b7ffa 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -88,7 +88,6 @@ Expression = | Expression '||' Expression | Expression '?' Expression ':' Expression | Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression - | Expression? (',' Expression) | PrimaryExpression PrimaryExpression = Identifier @@ -96,6 +95,7 @@ PrimaryExpression = Identifier | NumberLiteral | HexLiteral | StringLiteral + | TupleExpression | ElementaryTypeNameExpression ExpressionList = Expression ( ',' Expression )* @@ -120,6 +120,9 @@ Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ +TupleExpression = '(' ( Expression ( ',' Expression )* )? ')' + | '[' ( Expression ( ',' Expression )* )? ']' + ElementaryTypeNameExpression = ElementaryTypeName ElementaryTypeName = 'address' | 'bool' | 'string' | 'var' diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp new file mode 100644 index 00000000..4bad8476 --- /dev/null +++ b/libdevcore/Whiskers.cpp @@ -0,0 +1,127 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** @file Whiskers.cpp + * @author Chris <chis@ethereum.org> + * @date 2017 + * + * Moustache-like templates. + */ + +#include <libdevcore/Whiskers.h> + +#include <libdevcore/Assertions.h> + +#include <boost/regex.hpp> + +using namespace std; +using namespace dev; + +Whiskers::Whiskers(string const& _template): +m_template(_template) +{ +} + +Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) +{ + assertThrow( + m_parameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set." + ); + assertThrow( + m_listParameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set as list parameter." + ); + m_parameters[_parameter] = _value; + + return *this; +} + +Whiskers& Whiskers::operator ()( + string const& _listParameter, + vector<map<string, string>> const& _values +) +{ + assertThrow( + m_listParameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set." + ); + assertThrow( + m_parameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set as value parameter." + ); + m_listParameters[_listParameter] = _values; + + return *this; +} + +string Whiskers::render() const +{ + return replace(m_template, m_parameters, m_listParameters); +} + +string Whiskers::replace( + string const& _template, + StringMap const& _parameters, + map<string, vector<StringMap>> const& _listParameters +) +{ + using namespace boost; + static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>"); + return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string + { + string tagName(_match[1]); + if (!tagName.empty()) + { + assertThrow(_parameters.count(tagName), WhiskersError, "Tag " + tagName + " not found."); + return _parameters.at(tagName); + } + else + { + string listName(_match[2]); + string templ(_match[3]); + assertThrow(!listName.empty(), WhiskersError, ""); + assertThrow( + _listParameters.count(listName), + WhiskersError, "List parameter " + listName + " not set." + ); + string replacement; + for (auto const& parameters: _listParameters.at(listName)) + replacement += replace(templ, joinMaps(_parameters, parameters)); + return replacement; + } + }); +} + +Whiskers::StringMap Whiskers::joinMaps( + Whiskers::StringMap const& _a, + Whiskers::StringMap const& _b +) +{ + Whiskers::StringMap ret = _a; + for (auto const& x: _b) + assertThrow( + ret.insert(x).second, + WhiskersError, + "Parameter collision" + ); + return ret; +} + diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h new file mode 100644 index 00000000..21d46af4 --- /dev/null +++ b/libdevcore/Whiskers.h @@ -0,0 +1,87 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** @file Whiskers.h + * @author Chris <chis@ethereum.org> + * @date 2017 + * + * Moustache-like templates. + */ + +#pragma once + +#include <libdevcore/Exceptions.h> + +#include <string> +#include <map> +#include <vector> + +namespace dev +{ + +DEV_SIMPLE_EXCEPTION(WhiskersError); + +/// +/// Moustache-like templates. +/// +/// Usage: +/// std::vector<std::map<std::string, std::string>> listValues(2); +/// listValues[0]["k"] = "key1"; +/// listValues[0]["v"] = "value1"; +/// listValues[1]["k"] = "key2"; +/// listValues[1]["v"] = "value2"; +/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>") +/// ("p1", "HEAD") +/// ("list", listValues) +/// .render(); +/// +/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" +/// +/// Note that lists cannot themselves contain lists - this would be a future feature. +class Whiskers +{ +public: + using StringMap = std::map<std::string, std::string>; + using StringListMap = std::map<std::string, std::vector<StringMap>>; + + explicit Whiskers(std::string const& _template); + + /// Sets a single parameter, <paramName>. + Whiskers& operator()(std::string const& _parameter, std::string const& _value); + /// Sets a list parameter, <#listName> </listName>. + Whiskers& operator()( + std::string const& _listParameter, + std::vector<StringMap> const& _values + ); + + std::string render() const; + +private: + static std::string replace( + std::string const& _template, + StringMap const& _parameters, + StringListMap const& _listParameters = StringListMap() + ); + + /// Joins the two maps throwing an exception if two keys are equal. + static StringMap joinMaps(StringMap const& _a, StringMap const& _b); + + std::string m_template; + StringMap m_parameters; + StringListMap m_listParameters; +}; + +} diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2a8d1ff6..4194e1c2 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -463,6 +463,8 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions."); + + var->accept(*this); } for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers()) visitManually( @@ -487,7 +489,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + // Forbid any variable declarations inside interfaces unless they are part of + // a function's input/output parameters. + if ( + m_scope->contractKind() == ContractDefinition::ContractKind::Interface + && !_variable.isCallableParameter() + ) m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); // Variables can be declared without type (with "var"), in which case the first assignment diff --git a/test/libdevcore/MiniMoustache.cpp b/test/libdevcore/MiniMoustache.cpp new file mode 100644 index 00000000..84149173 --- /dev/null +++ b/test/libdevcore/MiniMoustache.cpp @@ -0,0 +1,127 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for the mini moustache class. + */ + +#include <libdevcore/Whiskers.h> + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(WhiskersTest) + +BOOST_AUTO_TEST_CASE(no_templates) +{ + string templ = "this text does not contain templates"; + BOOST_CHECK_EQUAL(Whiskers(templ).render(), templ); +} + +BOOST_AUTO_TEST_CASE(basic_replacement) +{ + string templ = "a <b> x <c> -> <d>."; + string result = Whiskers(templ) + ("b", "BE") + ("c", "CE") + ("d", "DE") + .render(); + BOOST_CHECK_EQUAL(result, "a BE x CE -> DE."); +} + +BOOST_AUTO_TEST_CASE(tag_unavailable) +{ + string templ = "<b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(complicated_replacement) +{ + string templ = "a <b> x <complicated> \n <nes<ted>>."; + string result = Whiskers(templ) + ("b", "BE") + ("complicated", "CO<M>PL") + ("nes<ted", "NEST") + .render(); + BOOST_CHECK_EQUAL(result, "a BE x CO<M>PL \n NEST>."); +} + +BOOST_AUTO_TEST_CASE(non_existing_list) +{ + string templ = "a <#b></b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(empty_list) +{ + string templ = "a <#b></b>x"; + string result = Whiskers(templ)("b", vector<Whiskers::StringMap>{}).render(); + BOOST_CHECK_EQUAL(result, "a x"); +} + +BOOST_AUTO_TEST_CASE(list) +{ + string templ = "a<#b>( <g> - <h> )</b>x"; + vector<map<string, string>> list(2); + list[0]["g"] = "GE"; + list[0]["h"] = "H"; + list[1]["g"] = "2GE"; + list[1]["h"] = "2H"; + string result = Whiskers(templ)("b", list).render(); + BOOST_CHECK_EQUAL(result, "a( GE - H )( 2GE - 2H )x"); +} + +BOOST_AUTO_TEST_CASE(recursive_list) +{ + // Check that templates resulting from lists are not expanded again + string templ = "a<#b> 1<g>3 </b><x>"; + vector<map<string, string>> list(1); + list[0]["g"] = "<x>"; + string result = Whiskers(templ)("x", "X")("b", list).render(); + BOOST_CHECK_EQUAL(result, "a 1<x>3 X"); +} + +BOOST_AUTO_TEST_CASE(list_can_access_upper) +{ + string templ = "<#b>(<a>)</b>"; + vector<map<string, string>> list(2); + Whiskers m(templ); + string result = m("a", "A")("b", list).render(); + BOOST_CHECK_EQUAL(result, "(A)(A)"); +} + +BOOST_AUTO_TEST_CASE(parameter_collision) +{ + string templ = "a <#b></b>"; + vector<map<string, string>> list(1); + list[0]["a"] = "x"; + Whiskers m(templ); + m("a", "X")("b", list); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 0b3cb481..c4b1250f 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1597,6 +1597,16 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(constant_input_parameter) +{ + char const* text = R"( + contract test { + function f(uint[] constant a) { } + } + )"; + CHECK_ERROR_ALLOW_MULTI(text, TypeError, "Illegal use of \"constant\" specifier."); +} + BOOST_AUTO_TEST_CASE(empty_name_return_parameter) { char const* text = R"( @@ -5582,6 +5592,16 @@ BOOST_AUTO_TEST_CASE(interface_variables) CHECK_ERROR(text, TypeError, "Variables cannot be declared in interfaces"); } +BOOST_AUTO_TEST_CASE(interface_function_parameters) +{ + char const* text = R"( + interface I { + function f(uint a) returns(bool); + } + )"; + success(text); +} + BOOST_AUTO_TEST_CASE(interface_enums) { char const* text = R"( |