mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #222 from chriseth/newArrays
Dynamically create memory arrays.
This commit is contained in:
commit
c498dcce22
@ -49,7 +49,7 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit)
|
||||
{
|
||||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors);
|
||||
}
|
||||
catch (FatalError const& _e)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
if (m_errors.empty())
|
||||
throw; // Something is weird here, rather throw again.
|
||||
@ -146,7 +146,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
|
||||
if (!success)
|
||||
return false;
|
||||
}
|
||||
catch (FatalError const& _e)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
if (m_errors.empty())
|
||||
throw; // Something is weird here, rather throw again.
|
||||
@ -162,7 +162,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
|
||||
m_scopes[nullptr].registerDeclaration(_declaration, false, true);
|
||||
solAssert(_declaration.scope() == nullptr, "Updated declaration outside global scope.");
|
||||
}
|
||||
catch (FatalError const& _error)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
if (m_errors.empty())
|
||||
throw; // Something is weird here, rather throw again.
|
||||
|
@ -37,6 +37,11 @@ bool ReferencesResolver::visit(Return const& _return)
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(NewExpression const& _new)
|
||||
{
|
||||
typeFor(_new.typeName());
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(UserDefinedTypeName const& _typeName)
|
||||
{
|
||||
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
|
||||
@ -44,6 +49,8 @@ bool ReferencesResolver::visit(UserDefinedTypeName const& _typeName)
|
||||
fatalDeclarationError(_typeName.location(), "Identifier not found or not unique.");
|
||||
|
||||
_typeName.annotation().referencedDeclaration = declaration;
|
||||
|
||||
_typeName.annotation().contractScope = m_currentContract;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -53,7 +60,7 @@ bool ReferencesResolver::resolve(ASTNode& _root)
|
||||
{
|
||||
_root.accept(*this);
|
||||
}
|
||||
catch (FatalError const& e)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
solAssert(m_errorOccurred, "");
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ private:
|
||||
virtual bool visit(Identifier const& _identifier) override;
|
||||
virtual bool visit(UserDefinedTypeName const& _typeName) override;
|
||||
virtual bool visit(Return const& _return) override;
|
||||
virtual void endVisit(NewExpression const& _new) override;
|
||||
virtual void endVisit(VariableDeclaration const& _variable) override;
|
||||
|
||||
TypePointer typeFor(TypeName const& _typeName);
|
||||
|
@ -1045,14 +1045,19 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
|
||||
void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
{
|
||||
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(_newExpression.contractName()));
|
||||
TypePointer type = _newExpression.typeName().annotation().type;
|
||||
solAssert(!!type, "Type name not resolved.");
|
||||
|
||||
if (auto contractName = dynamic_cast<UserDefinedTypeName const*>(&_newExpression.typeName()))
|
||||
{
|
||||
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
|
||||
|
||||
if (!contract)
|
||||
fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
|
||||
if (!contract->annotation().isFullyImplemented)
|
||||
typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
|
||||
|
||||
auto scopeContract = _newExpression.contractName().annotation().contractScope;
|
||||
auto scopeContract = contractName->annotation().contractScope;
|
||||
scopeContract->annotation().contractDependencies.insert(contract);
|
||||
solAssert(
|
||||
!contract->annotation().linearizedBaseContracts.empty(),
|
||||
@ -1073,6 +1078,30 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
strings(),
|
||||
FunctionType::Location::Creation
|
||||
);
|
||||
}
|
||||
else if (type->category() == Type::Category::Array)
|
||||
{
|
||||
if (!type->canLiveOutsideStorage())
|
||||
fatalTypeError(
|
||||
_newExpression.typeName().location(),
|
||||
"Type cannot live outside storage."
|
||||
);
|
||||
if (!type->isDynamicallySized())
|
||||
typeError(
|
||||
_newExpression.typeName().location(),
|
||||
"Length has to be placed in parentheses after the array type for new expression."
|
||||
);
|
||||
type = ReferenceType::copyForLocationIfReference(DataLocation::Memory, type);
|
||||
_newExpression.annotation().type = make_shared<FunctionType>(
|
||||
TypePointers{make_shared<IntegerType>(256)},
|
||||
TypePointers{type},
|
||||
strings(),
|
||||
strings(),
|
||||
FunctionType::Location::ObjectCreation
|
||||
);
|
||||
}
|
||||
else
|
||||
fatalTypeError(_newExpression.location(), "Contract or array type expected.");
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
@ -1282,12 +1311,18 @@ bool TypeChecker::contractDependenciesAreCyclic(
|
||||
return false;
|
||||
}
|
||||
|
||||
Declaration const& TypeChecker::dereference(Identifier const& _identifier)
|
||||
Declaration const& TypeChecker::dereference(Identifier const& _identifier) const
|
||||
{
|
||||
solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored.");
|
||||
return *_identifier.annotation().referencedDeclaration;
|
||||
}
|
||||
|
||||
Declaration const& TypeChecker::dereference(UserDefinedTypeName const& _typeName) const
|
||||
{
|
||||
solAssert(!!_typeName.annotation().referencedDeclaration, "Declaration not stored.");
|
||||
return *_typeName.annotation().referencedDeclaration;
|
||||
}
|
||||
|
||||
void TypeChecker::expectType(Expression const& _expression, Type const& _expectedType)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
|
@ -107,7 +107,9 @@ private:
|
||||
) const;
|
||||
|
||||
/// @returns the referenced declaration and throws on error.
|
||||
Declaration const& dereference(Identifier const& _identifier);
|
||||
Declaration const& dereference(Identifier const& _identifier) const;
|
||||
/// @returns the referenced declaration and throws on error.
|
||||
Declaration const& dereference(UserDefinedTypeName const& _typeName) const;
|
||||
|
||||
/// Runs type checks on @a _expression to infer its type and then checks that it is implicitly
|
||||
/// convertible to @a _expectedType.
|
||||
|
@ -1216,23 +1216,24 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
* Expression that creates a new contract, e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
|
||||
* Expression that creates a new contract or memory-array,
|
||||
* e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
|
||||
*/
|
||||
class NewExpression: public Expression
|
||||
{
|
||||
public:
|
||||
NewExpression(
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Identifier> const& _contractName
|
||||
ASTPointer<TypeName> const& _typeName
|
||||
):
|
||||
Expression(_location), m_contractName(_contractName) {}
|
||||
Expression(_location), m_typeName(_typeName) {}
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
Identifier const& contractName() const { return *m_contractName; }
|
||||
TypeName const& typeName() const { return *m_typeName; }
|
||||
|
||||
private:
|
||||
ASTPointer<Identifier> m_contractName;
|
||||
ASTPointer<TypeName> m_typeName;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -111,6 +111,9 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation
|
||||
{
|
||||
/// Referenced declaration, set during reference resolution stage.
|
||||
Declaration const* referencedDeclaration = nullptr;
|
||||
/// Stores a reference to the current contract.
|
||||
/// This is needed because types of base contracts change depending on the context.
|
||||
ContractDefinition const* contractScope = nullptr;
|
||||
};
|
||||
|
||||
struct VariableDeclarationStatementAnnotation: StatementAnnotation
|
||||
|
@ -634,14 +634,14 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
|
||||
void NewExpression::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
m_contractName->accept(_visitor);
|
||||
m_typeName->accept(_visitor);
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void NewExpression::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
m_contractName->accept(_visitor);
|
||||
m_typeName->accept(_visitor);
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
|
@ -141,9 +141,6 @@ public:
|
||||
/// Factory functions that convert an AST @ref TypeName to a Type.
|
||||
static TypePointer fromElementaryTypeName(Token::Value _typeToken);
|
||||
static TypePointer fromElementaryTypeName(std::string const& _name);
|
||||
static TypePointer fromUserDefinedTypeName(UserDefinedTypeName const& _typeName);
|
||||
static TypePointer fromMapping(ElementaryTypeName& _keyType, TypeName& _valueType);
|
||||
static TypePointer fromArrayTypeName(TypeName& _baseTypeName, Expression* _length);
|
||||
/// @}
|
||||
|
||||
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
||||
@ -751,7 +748,8 @@ public:
|
||||
AddMod, ///< ADDMOD
|
||||
MulMod, ///< MULMOD
|
||||
ArrayPush, ///< .push() to a dynamically sized array in storage
|
||||
ByteArrayPush ///< .push() to a dynamically sized byte array in storage
|
||||
ByteArrayPush, ///< .push() to a dynamically sized byte array in storage
|
||||
ObjectCreation ///< array creation using new
|
||||
};
|
||||
|
||||
virtual Category category() const override { return Category::Function; }
|
||||
|
@ -266,6 +266,19 @@ void CompilerUtils::encodeToMemory(
|
||||
popStackSlots(argSize + dynPointers + 1);
|
||||
}
|
||||
|
||||
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
{
|
||||
auto repeat = m_context.newTag();
|
||||
m_context << repeat;
|
||||
pushZeroValue(*_type.baseType());
|
||||
storeInMemoryDynamic(*_type.baseType());
|
||||
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1;
|
||||
m_context << eth::Instruction::SUB << eth::Instruction::SWAP1;
|
||||
m_context << eth::Instruction::DUP2;
|
||||
m_context.appendConditionalJumpTo(repeat);
|
||||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
|
||||
}
|
||||
|
||||
void CompilerUtils::memoryCopy()
|
||||
{
|
||||
// Stack here: size target source
|
||||
@ -646,15 +659,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
{
|
||||
m_context << arrayType->length() << eth::Instruction::SWAP1;
|
||||
// stack: items_to_do memory_pos
|
||||
auto repeat = m_context.newTag();
|
||||
m_context << repeat;
|
||||
pushZeroValue(*arrayType->baseType());
|
||||
storeInMemoryDynamic(*arrayType->baseType());
|
||||
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1;
|
||||
m_context << eth::Instruction::SUB << eth::Instruction::SWAP1;
|
||||
m_context << eth::Instruction::DUP2;
|
||||
m_context.appendConditionalJumpTo(repeat);
|
||||
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
|
||||
zeroInitialiseMemoryArray(*arrayType);
|
||||
// stack: updated_memory_pos
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -103,6 +103,11 @@ public:
|
||||
bool _encodeAsLibraryTypes = false
|
||||
);
|
||||
|
||||
/// Zero-initialises (the data part of) an already allocated memory array.
|
||||
/// Stack pre: <length> <memptr>
|
||||
/// Stack post: <updated_memptr>
|
||||
void zeroInitialiseMemoryArray(ArrayType const& _type);
|
||||
|
||||
/// Uses a CALL to the identity contract to perform a memory-to-memory copy.
|
||||
/// Stack pre: <size> <target> <source>
|
||||
/// Stack post:
|
||||
|
@ -703,6 +703,53 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
|
||||
break;
|
||||
}
|
||||
case Location::ObjectCreation:
|
||||
{
|
||||
// Will allocate at the end of memory (MSIZE) and not write at all unless the base
|
||||
// type is dynamically sized.
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
|
||||
_functionCall.expression().accept(*this);
|
||||
solAssert(arguments.size() == 1, "");
|
||||
|
||||
// Fetch requested length.
|
||||
arguments[0]->accept(*this);
|
||||
utils().convertType(*arguments[0]->annotation().type, IntegerType(256));
|
||||
|
||||
// Stack: requested_length
|
||||
// Allocate at max(MSIZE, freeMemoryPointer)
|
||||
utils().fetchFreeMemoryPointer();
|
||||
m_context << eth::Instruction::DUP1 << eth::Instruction::MSIZE;
|
||||
m_context << eth::Instruction::LT;
|
||||
auto initialise = m_context.appendConditionalJump();
|
||||
// Free memory pointer does not point to empty memory, use MSIZE.
|
||||
m_context << eth::Instruction::POP;
|
||||
m_context << eth::Instruction::MSIZE;
|
||||
m_context << initialise;
|
||||
|
||||
// Stack: requested_length memptr
|
||||
m_context << eth::Instruction::SWAP1;
|
||||
// Stack: memptr requested_length
|
||||
// store length
|
||||
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::MSTORE;
|
||||
// Stack: memptr requested_length
|
||||
// update free memory pointer
|
||||
m_context << eth::Instruction::DUP1 << arrayType.baseType()->memoryHeadSize();
|
||||
m_context << eth::Instruction::MUL << u256(32) << eth::Instruction::ADD;
|
||||
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
|
||||
utils().storeFreeMemoryPointer();
|
||||
// Stack: memptr requested_length
|
||||
|
||||
// We only have to initialise if the base type is a not a value type.
|
||||
if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get()))
|
||||
{
|
||||
m_context << eth::Instruction::DUP2 << u256(32) << eth::Instruction::ADD;
|
||||
utils().zeroInitialiseMemoryArray(arrayType);
|
||||
m_context << eth::Instruction::POP;
|
||||
}
|
||||
else
|
||||
m_context << eth::Instruction::POP;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type."));
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class SourceUnit;
|
||||
class Why3Translator: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
Why3Translator(ErrorList& _errors): m_lines{{std::string(), 0}}, m_errors(_errors) {}
|
||||
Why3Translator(ErrorList& _errors): m_lines(std::vector<Line>{{std::string(), 0}}), m_errors(_errors) {}
|
||||
|
||||
/// Appends formalisation of the given source unit to the output.
|
||||
/// @returns false on error.
|
||||
|
@ -88,7 +88,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
|
||||
}
|
||||
return nodeFactory.createNode<SourceUnit>(nodes);
|
||||
}
|
||||
catch (FatalError const& _error)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
if (m_errors.empty())
|
||||
throw; // Something is weird here, rather throw again.
|
||||
@ -939,7 +939,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
|
||||
else if (m_scanner->currentToken() == Token::New)
|
||||
{
|
||||
expectToken(Token::New);
|
||||
ASTPointer<Identifier> contractName(parseIdentifier());
|
||||
ASTPointer<TypeName> contractName(parseTypeName(false));
|
||||
nodeFactory.setEndPositionFromNode(contractName);
|
||||
expression = nodeFactory.createNode<NewExpression>(contractName);
|
||||
}
|
||||
|
@ -1955,7 +1955,7 @@ BOOST_AUTO_TEST_CASE(value_for_constructor)
|
||||
contract Main {
|
||||
Helper h;
|
||||
function Main() {
|
||||
h = new Helper.value(10)("abc", true);
|
||||
h = (new Helper).value(10)("abc", true);
|
||||
}
|
||||
function getFlag() returns (bool ret) { return h.getFlag(); }
|
||||
function getName() returns (bytes3 ret) { return h.getName(); }
|
||||
@ -5816,6 +5816,50 @@ BOOST_AUTO_TEST_CASE(lone_struct_array_type)
|
||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(3)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(create_memory_array)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
struct S { uint[2] a; bytes b; }
|
||||
function f() returns (byte, uint, uint, byte) {
|
||||
var x = new bytes(200);
|
||||
x[199] = 'A';
|
||||
var y = new uint[2][](300);
|
||||
y[203][1] = 8;
|
||||
var z = new S[](180);
|
||||
z[170].a[1] = 4;
|
||||
z[170].b = new bytes(102);
|
||||
z[170].b[99] = 'B';
|
||||
return (x[199], y[203][1], z[170].a[1], z[170].b[99]);
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(string("A"), u256(8), u256(4), string("B")));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(memory_arrays_of_various_sizes)
|
||||
{
|
||||
// Computes binomial coefficients the chinese way
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
function f(uint n, uint k) returns (uint) {
|
||||
uint[][] memory rows = new uint[][](n + 1);
|
||||
for (uint i = 1; i <= n; i++) {
|
||||
rows[i] = new uint[](i);
|
||||
rows[i][0] = rows[i][rows[i].length - 1] = 1;
|
||||
for (uint j = 1; j < i - 1; j++)
|
||||
rows[i][j] = rows[i - 1][j - 1] + rows[i - 1][j];
|
||||
}
|
||||
return rows[n][k - 1];
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
BOOST_CHECK(callContractFunction("f(uint256,uint256)", encodeArgs(u256(3), u256(1))) == encodeArgs(u256(1)));
|
||||
BOOST_CHECK(callContractFunction("f(uint256,uint256)", encodeArgs(u256(9), u256(5))) == encodeArgs(u256(70)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(memory_overwrite)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
|
@ -2529,6 +2529,60 @@ BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity)
|
||||
BOOST_CHECK(success(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(create_memory_arrays)
|
||||
{
|
||||
char const* text = R"(
|
||||
library L {
|
||||
struct R { uint[10][10] y; }
|
||||
struct S { uint a; uint b; uint[20][20][20] c; R d; }
|
||||
}
|
||||
contract C {
|
||||
function f(uint size) {
|
||||
L.S[][] memory x = new L.S[][](10);
|
||||
var y = new uint[](20);
|
||||
var z = new bytes(size);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(success(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_in_memory_array)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f(uint size) {
|
||||
var x = new mapping(uint => uint)[](4);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(new_for_non_array)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f(uint size) {
|
||||
var x = new uint(7);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(invalid_args_creating_memory_array)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f(uint size) {
|
||||
var x = new uint[]();
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user