diff --git a/.gitignore b/.gitignore index 1ad199c10..e3e12421b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.commit_hash.txt +.prerelease.txt + # Compiled Object files *.slo *.lo diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ad9c0610..f190a5075 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.1") +set(PROJECT_VERSION "0.4.2") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index 1ae186581..b5c0631d1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,16 @@ +### 0.4.2 (2016-09-17) + +Bugfixes: + + * Code Generator: Fix library functions being called from payable functions. + * Type Checker: Fixed a crash about invalid array types. + * Code Generator: Fixed a call gas bug that became visible after + version 0.4.0 for calls where the output is larger than the input. + +### 0.4.1 (2016-09-09) + + * Build System: Fixes to allow library compilation. + ### 0.4.0 (2016-09-08) This release deliberately breaks backwards compatibility mostly to diff --git a/cmake/scripts/buildinfo.cmake b/cmake/scripts/buildinfo.cmake index e2f8cb3eb..8e1615f6d 100644 --- a/cmake/scripts/buildinfo.cmake +++ b/cmake/scripts/buildinfo.cmake @@ -26,6 +26,7 @@ if (EXISTS ${ETH_SOURCE_DIR}/prerelease.txt) string(STRIP "${SOL_VERSION_PRERELEASE}" SOL_VERSION_PRERELEASE) else() string(TIMESTAMP SOL_VERSION_PRERELEASE "develop.%Y.%m.%d" UTC) + string(REPLACE .0 . SOL_VERSION_PRERELEASE "${SOL_VERSION_PRERELEASE}") endif() if (EXISTS ${ETH_SOURCE_DIR}/commit_hash.txt) @@ -33,7 +34,7 @@ if (EXISTS ${ETH_SOURCE_DIR}/commit_hash.txt) string(STRIP ${SOL_COMMIT_HASH} SOL_COMMIT_HASH) else() execute_process( - COMMAND git --git-dir=${ETH_SOURCE_DIR}/.git --work-tree=${ETH_SOURCE_DIR} rev-parse HEAD + COMMAND git --git-dir=${ETH_SOURCE_DIR}/.git --work-tree=${ETH_SOURCE_DIR} rev-parse --short=8 HEAD OUTPUT_VARIABLE SOL_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) execute_process( @@ -47,14 +48,17 @@ if (SOL_COMMIT_HASH) string(SUBSTRING ${SOL_COMMIT_HASH} 0 8 SOL_COMMIT_HASH) endif() -if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES) - set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod") -endif() - if (NOT SOL_COMMIT_HASH) message(FATAL_ERROR "Unable to determine commit hash. Either compile from within git repository or " "supply a file called commit_hash.txt") endif() +if (NOT SOL_COMMIT_HASH MATCHES [a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]) + message(FATAL_ERROR "Malformed commit hash \"${SOL_COMMIT_HASH}\". It has to consist of exactly 8 hex digits.") +endif() + +if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES) + set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod") +endif() set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}") diff --git a/docs/conf.py b/docs/conf.py index d0e263623..485184f2a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,9 +56,9 @@ copyright = '2016, Ethereum' # built documents. # # The short X.Y version. -version = '0.2.0' +version = '0.4.1' # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = '0.4.1-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index ad27e5286..ebb7537be 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -6,6 +6,15 @@ Installing Solidity ################### +Versioning +========== + +Solidity versions follow `semantic versioning ` and in addition to +releases, **nightly development builds** are also made available. The nightly builds +are not guaranteed to be working and despite best efforts they might contain undocumented +and/or broken changes. We recommend to use the latest release. Package installers below +will use the latest release. + Browser-Solidity ================ @@ -186,3 +195,20 @@ Alternatively, you can build for Windows on the command-line, like so: .. code:: bash cmake --build . --config RelWithDebInfo + +Important information about versioning +====================================== + +After a release is made, the patch version level is bumped, because we assume that only +patch level changes follow. When changes are merged, the version should be bumped according +to semver and the severity of the change. Finally, a release is always made with the version +of the current nightly build, but without the ``prerelease`` specifier. + +Example: +- 0) the 0.4.0 release is made +- 1) nightly build has a version of 0.4.1 from now on +- 2) non-breaking changes are introduced - no change in version +- 3) a breaking change is introduced - version is bumped to 0.5.0 +- 4) the 0.5.0 release is made + +This behaviour works well with the version pragma. diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h index 7b4a4a765..05e0b0e5c 100644 --- a/libdevcore/Assertions.h +++ b/libdevcore/Assertions.h @@ -73,7 +73,7 @@ inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char con /// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong."); /// Do NOT supply an exception object as the second parameter. #define assertThrow(_condition, _ExceptionType, _description) \ - ::dev::assertThrowAux<_ExceptionType>(_condition, _description, __LINE__, __FILE__, ETH_FUNC) + ::dev::assertThrowAux<_ExceptionType>(!!(_condition), _description, __LINE__, __FILE__, ETH_FUNC) using errinfo_comment = boost::error_info; @@ -96,16 +96,4 @@ inline void assertThrowAux( ); } -template -inline void assertThrowAux( - void const* _pointer, - ::std::string const& _errorDescription, - unsigned _line, - char const* _file, - char const* _function -) -{ - assertThrowAux<_ExceptionType>(_pointer != nullptr, _errorDescription, _line, _file, _function); -} - } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d9c54f755..ae7c13c88 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1438,7 +1438,7 @@ bool TypeChecker::visit(IndexAccess const& _access) length->literalValue(nullptr) )); else - typeError(index->location(), "Integer constant expected."); + fatalTypeError(index->location(), "Integer constant expected."); } break; } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 33571bc0f..18b42fce4 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -263,7 +263,9 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); m_context << callDataUnpackerEntryPoints.at(it.first); - if (!functionType->isPayable()) + // We have to allow this for libraries, because value of the previous + // call is still visible in the delegatecall. + if (!functionType->isPayable() && !_contract.isLibrary()) { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 96ca42966..26acd8a47 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1476,6 +1476,18 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().storeFreeMemoryPointer(); } + // Touch the end of the output area so that we do not pay for memory resize during the call + // (which we would have to subtract from the gas left) + // We could also just use MLOAD; POP right before the gas calculation, but the optimizer + // would remove that, so we use MSTORE here. + if (!_functionType.gasSet() && retSize > 0) + { + m_context << u256(0); + utils().fetchFreeMemoryPointer(); + // This touches too much, but that way we save some rounding arithmetics + m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + } + // Copy function identifier to memory. utils().fetchFreeMemoryPointer(); if (!_functionType.isBareCall() || manualFunctionId) @@ -1551,10 +1563,7 @@ void ExpressionCompiler::appendExternalFunctionCall( gasNeededByCaller += eth::GasCosts::callValueTransferGas; if (!isCallCode && !isDelegateCall && !existenceChecked) gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know - m_context << - gasNeededByCaller << - Instruction::GAS << - Instruction::SUB; + m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } if (isDelegateCall) m_context << Instruction::DELEGATECALL; diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index 834024fab..f3831b40f 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -36,6 +36,10 @@ bool Why3Translator::process(SourceUnit const& _source) appendPreface(); _source.accept(*this); } + catch (NoFormalType&) + { + solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions."); + } catch (FatalError& /*_e*/) { solAssert(m_errorOccured, ""); @@ -77,14 +81,30 @@ string Why3Translator::toFormalType(Type const& _type) const return "uint256"; } else if (auto type = dynamic_cast(&_type)) + { if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory)) { + // Not catching NoFormalType exception. Let the caller deal with it. string base = toFormalType(*type->baseType()); - if (!base.empty()) - return "array " + base; + return "array " + base; } + } + else if (auto mappingType = dynamic_cast(&_type)) + { + solAssert(mappingType->keyType(), "A mappingType misses a keyType."); + if (dynamic_cast(&*mappingType->keyType())) + { + //@TODO Use the information from the key type and specify the length of the array as an invariant. + // Also the constructor need to specify the length of the array. + solAssert(mappingType->valueType(), "A mappingType misses a valueType."); + // Not catching NoFormalType exception. Let the caller deal with it. + string valueTypeFormal = toFormalType(*mappingType->valueType()); + return "array " + valueTypeFormal; + } + } - return ""; + BOOST_THROW_EXCEPTION(NoFormalType() + << errinfo_noFormalTypeFrom(_type.toString(true))); } void Why3Translator::addLine(string const& _line) @@ -142,9 +162,17 @@ bool Why3Translator::visit(ContractDefinition const& _contract) m_currentContract.stateVariables = _contract.stateVariables(); for (VariableDeclaration const* variable: m_currentContract.stateVariables) { - string varType = toFormalType(*variable->annotation().type); - if (varType.empty()) - fatalError(*variable, "Type not supported for state variable."); + string varType; + try + { + varType = toFormalType(*variable->annotation().type); + } + catch (NoFormalType &err) + { + string const* typeNamePtr = boost::get_error_info(err); + string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : ""; + fatalError(*variable, "Type" + typeName + " not supported for state variable."); + } addLine("mutable _" + variable->name() + ": " + varType); } unindent(); @@ -218,9 +246,16 @@ bool Why3Translator::visit(FunctionDefinition const& _function) add(" (this: account)"); for (auto const& param: _function.parameters()) { - string paramType = toFormalType(*param->annotation().type); - if (paramType.empty()) - error(*param, "Parameter type not supported."); + string paramType; + try + { + paramType = toFormalType(*param->annotation().type); + } + catch (NoFormalType &err) + { + string const* typeName = boost::get_error_info(err); + error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported."); + } if (param->name().empty()) error(*param, "Anonymous function parameters not supported."); add(" (arg_" + param->name() + ": " + paramType + ")"); @@ -232,9 +267,16 @@ bool Why3Translator::visit(FunctionDefinition const& _function) string retString = "("; for (auto const& retParam: _function.returnParameters()) { - string paramType = toFormalType(*retParam->annotation().type); - if (paramType.empty()) - error(*retParam, "Parameter type not supported."); + string paramType; + try + { + paramType = toFormalType(*retParam->annotation().type); + } + catch (NoFormalType &err) + { + string const* typeName = boost::get_error_info(err); + error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported."); + } if (retString.size() != 1) retString += ", "; retString += paramType; @@ -264,14 +306,32 @@ bool Why3Translator::visit(FunctionDefinition const& _function) { if (variable->name().empty()) error(*variable, "Unnamed return variables not yet supported."); - string varType = toFormalType(*variable->annotation().type); + string varType; + try + { + varType = toFormalType(*variable->annotation().type); + } + catch (NoFormalType &err) + { + string const* typeNamePtr = boost::get_error_info(err); + error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported."); + } addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); } for (VariableDeclaration const* variable: _function.localVariables()) { if (variable->name().empty()) error(*variable, "Unnamed variables not yet supported."); - string varType = toFormalType(*variable->annotation().type); + string varType; + try + { + varType = toFormalType(*variable->annotation().type); + } + catch (NoFormalType &err) + { + string const* typeNamePtr = boost::get_error_info(err); + error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported."); + } addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); } addLine("try"); @@ -434,8 +494,15 @@ bool Why3Translator::visit(TupleExpression const& _node) bool Why3Translator::visit(UnaryOperation const& _unaryOperation) { - if (toFormalType(*_unaryOperation.annotation().type).empty()) - error(_unaryOperation, "Type not supported in unary operation."); + try + { + toFormalType(*_unaryOperation.annotation().type); + } + catch (NoFormalType &err) + { + string const* typeNamePtr = boost::get_error_info(err); + error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation."); + } switch (_unaryOperation.getOperator()) { @@ -798,5 +865,14 @@ module UInt256 type t = uint256, constant max = max_uint256 end + +module Address + use import mach.int.Unsigned + type address + constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *) + clone export mach.int.Unsigned with + type t = address, + constant max = max_address +end )", 0}); } diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h index 1b80ed613..22bfff895 100644 --- a/libsolidity/formal/Why3Translator.h +++ b/libsolidity/formal/Why3Translator.h @@ -60,9 +60,10 @@ private: /// Appends imports and constants use throughout the formal code. void appendPreface(); - /// @returns a string representation of the corresponding formal type or the empty string - /// if the type is not supported. + /// @returns a string representation of the corresponding formal type or throws NoFormalType exception. std::string toFormalType(Type const& _type) const; + using errinfo_noFormalTypeFrom = boost::error_info; + struct NoFormalType: virtual Exception {}; void indent() { newLine(); m_lines.back().indentation++; } void unindent(); diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index da2c7df32..fe7ea11d1 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -29,7 +29,7 @@ set -e if [[ "$OSTYPE" != "darwin"* ]]; then - date -u +"nightly.%Y.%m.%d" > prerelease.txt + date -u +"nightly.%Y.%-m.%-d" > prerelease.txt ./scripts/travis-emscripten/install_deps.sh docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh fi diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 7c8523a8b..334c62d4b 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -56,6 +56,8 @@ detect_linux_distro() { elif [ -f /etc/os-release ]; then # extract 'foo' from NAME=foo, only on the line with NAME=foo DISTRO=$(sed -n -e 's/^NAME="\(.*\)\"/\1/p' /etc/os-release) + elif [ -f /etc/centos-release ]; then + DISTRO=CentOS else DISTRO='' fi @@ -329,6 +331,51 @@ case $(uname -s) in sudo apt-get -y install eth ;; + +#------------------------------------------------------------------------------ +# CentOS +# CentOS needs some more testing. This is the general idea of packages +# needed, but some tweaking/improvements can definitely happen +#------------------------------------------------------------------------------ + CentOS) + read -p "This script will heavily modify your system in order to allow for compilation of Solidity. Are you sure? [Y/N]" -n 1 -r + if [[ $REPLY =~ ^[Yy]$ ]]; then + # Make Sure we have the EPEL repos + sudo yum -y install epel-release + # Get g++ 4.8 + sudo rpm --import http://ftp.scientificlinux.org/linux/scientific/5x/x86_64/RPM-GPG-KEYs/RPM-GPG-KEY-cern + wget -O /etc/yum.repos.d/slc6-devtoolset.repo http://linuxsoft.cern.ch/cern/devtoolset/slc6-devtoolset.repo + sudo yum -y install devtoolset-2-gcc devtoolset-2-gcc-c++ devtoolset-2-binutils + + # Enable the devtoolset2 usage so global gcc/g++ become the 4.8 one. + # As per https://gist.github.com/stephenturner/e3bc5cfacc2dc67eca8b, what you should do afterwards is + # to add this line: + # source /opt/rh/devtoolset-2/enable + # to your bashrc so that this happens automatically at login + scl enable devtoolset-2 bash + + # Get cmake + sudo yum -y remove cmake + sudo yum -y install cmake3 + sudo ln -s /usr/bin/cmake3 /usr/bin/cmake + + # Get latest boost thanks to this guy: http://vicendominguez.blogspot.de/2014/04/boost-c-library-rpm-packages-for-centos.html + sudo yum -y remove boost-devel + sudo wget http://repo.enetres.net/enetres.repo -O /etc/yum.repos.d/enetres.repo + sudo yum install boost-devel + + # And finally jsoncpp + sudo yum -y install jsoncpp-devel + else + echo "Aborted CentOS Solidity Dependency Installation"; + exit 1 + fi + + ;; + + + + *) #------------------------------------------------------------------------------ diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 8e9dc2822..6a30faf52 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -52,9 +52,9 @@ mv solidity solc # Determine version cd solc version=`grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? CMakeLists.txt` -commithash=`git rev-parse --short HEAD` +commithash=`git rev-parse --short=8 HEAD` committimestamp=`git show --format=%ci HEAD | head -n 1` -commitdate=`git show --format=%ci HEAD | head -n 1 | cut - -b1-10` +commitdate=`git show --format=%ci HEAD | head -n 1 | cut - -b1-10 | sed -e 's/-0?/./' | sed -e 's/-0?/./'` echo "$commithash" > commit_hash.txt if [ $branch = develop ] diff --git a/scripts/travis-emscripten/publish_binary.sh b/scripts/travis-emscripten/publish_binary.sh index e89e3ce3e..d372995cf 100755 --- a/scripts/travis-emscripten/publish_binary.sh +++ b/scripts/travis-emscripten/publish_binary.sh @@ -33,8 +33,11 @@ set -e VER=$(cat CMakeLists.txt | grep 'set(PROJECT_VERSION' | sed -e 's/.*set(PROJECT_VERSION "\(.*\)".*/\1/') test -n "$VER" VER="v$VER" -COMMIT=$(git rev-parse --short HEAD) -DATE=$(date --date="$(git log -1 --date=iso --format=%ad HEAD)" --utc +%Y.%m.%d) +COMMIT=$(git rev-parse --short=8 HEAD) +DATE=$(date --date="$(git log -1 --date=iso --format=%ad HEAD)" --utc +%Y.%-m.%-d) + +# remove leading zeros in components - they are not semver-compatible +COMMIT=$(echo "$COMMIT" | sed -e 's/^0*//') ENCRYPTED_KEY_VAR="encrypted_${ENCRYPTION_LABEL}_key" ENCRYPTED_IV_VAR="encrypted_${ENCRYPTION_LABEL}_iv" diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7ee5700c7..23bd2abcc 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7144,6 +7144,23 @@ BOOST_AUTO_TEST_CASE(payable_function) BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 27 + 27); } +BOOST_AUTO_TEST_CASE(payable_function_calls_library) +{ + char const* sourceCode = R"( + library L { + function f() returns (uint) { return 7; } + } + contract C { + function f() payable returns (uint) { + return L.f(); + } + } + )"; + compileAndRun(sourceCode, 0, "L"); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"L", m_contractAddress}}); + BOOST_CHECK(callContractFunctionWithValue("f()", 27) == encodeArgs(u256(7))); +} + BOOST_AUTO_TEST_CASE(non_payable_throw) { char const* sourceCode = R"( @@ -7186,6 +7203,33 @@ BOOST_AUTO_TEST_CASE(no_nonpayable_circumvention_by_modifier) BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 0); } +BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) +{ + // This tests that memory resize for return values is not paid during the call, which would + // make the gas calculation overly complex. We access the end of the output area before + // the call is made. + // Tests that this also survives the optimizer. + char const* sourceCode = R"( + contract C { + function f() returns (uint[200]) {} + } + contract D { + function f(C c) returns (uint) { c.f(); return 7; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + u160 cAddr = m_contractAddress; + compileAndRun(sourceCode, 0, "D"); + BOOST_CHECK(callContractFunction("f(address)", cAddr) == encodeArgs(u256(7))); + + m_optimize = true; + + compileAndRun(sourceCode, 0, "C"); + u160 cAddrOpt = m_contractAddress; + compileAndRun(sourceCode, 0, "D"); + BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 58736025b..b8c643360 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4009,6 +4009,16 @@ BOOST_AUTO_TEST_CASE(external_constructor) BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(invalid_array_as_statement) +{ + char const* text = R"( + contract test { + struct S { uint x; } + function test(uint k) { S[k]; } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} BOOST_AUTO_TEST_SUITE_END()