Storage access from inline assembly.

This commit is contained in:
chriseth 2017-04-21 19:13:46 +02:00
parent 5f4b68e211
commit 478f2997ea
5 changed files with 108 additions and 20 deletions

View File

@ -28,6 +28,8 @@
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -166,10 +168,26 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
assembly::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
if (isSlot || isOffset)
{
// special mode to access storage variables
if (!declarations.empty())
// the special identifier exists itself, we should not allow that.
return size_t(-1);
string realName = _identifier.name.substr(0,
_identifier.name.size() - isSlot ?
string("_slot").size() :
string("_offset").size()
);
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() != 1)
return size_t(-1);
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
// At this stage we do not yet know the stack size of the identifier, so we just return 1.
return size_t(1);
};
assembly::AsmAnalyzer::Scopes scopes;

View File

@ -642,22 +642,35 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration, "");
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (!var->isLocalVariable())
if (ref->second.isSlot || ref->second.isOffset)
{
typeError(_identifier.location, "Only local variables are supported.");
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
return size_t(-1);
}
else if (_context != assembly::IdentifierContext::RValue)
{
typeError(_identifier.location, "Storage variables cannot be assigned to.");
return size_t(-1);
}
}
else if (!var->isLocalVariable())
{
typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
return size_t(-1);
}
if (var->type()->dataStoredIn(DataLocation::Storage))
else if (var->type()->dataStoredIn(DataLocation::Storage))
{
typeError(_identifier.location, "Storage reference variables are not supported.");
typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
return size_t(-1);
}
if (var->type()->sizeOnStack() != 1)
else if (var->type()->sizeOnStack() != 1)
{
typeError(_identifier.location, "Only types that use one stack slot are supported.");
return size_t(-1);
}
if (var->isConstant())
else if (var->isConstant())
{
typeError(_identifier.location, "Constant variables not supported by inline assembly.");
return size_t(-1);

View File

@ -122,6 +122,8 @@ struct InlineAssemblyAnnotation: StatementAnnotation
struct ExternalIdentifierInfo
{
Declaration const* declaration = nullptr;
bool isSlot = false; ///< Whether the storage slot of a variable is queried.
bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
size_t valueSize = size_t(-1);
};

View File

@ -542,6 +542,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
// If there is a runtime context, we have to merge both labels into the same
@ -557,19 +558,42 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
{
solAssert(!variable->isConstant(), "");
solAssert(m_context.isLocalVariable(variable), "");
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff));
if (m_context.isStateVariable(decl))
{
auto const& location = m_context.storageLocationOfVariable(*decl);
if (ref->second.isSlot)
m_context << location.first;
else if (ref->second.isOffset)
m_context << u256(location.second);
else
solAssert(false, "");
}
else if (m_context.isLocalVariable(decl))
{
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(variable->type()->sizeOnStack() == 2, "");
if (ref->second.isOffset)
stackDiff--;
}
else
solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff));
}
else
solAssert(false, "");
}
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName());
}
@ -580,6 +604,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else
{
// lvalue context
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
solAssert(
!!variable && m_context.isLocalVariable(variable),

View File

@ -7427,10 +7427,38 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
uint16 public y;
uint public z;
function f() returns (bool) {
uint off1;
uint off2;
assembly {
sstore(z_slot, 7)
off1 := z_offset
off2 := y_offset
}
assert(off1 == 0);
assert(off2 == 2);
return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer)
{
char const* sourceCode = R"(
contract C {
struct Data { uint contents; }
uint public separator;
Data public a;
uint public separator2;
function f() returns (bool) {
Data x = a;
uint off;
assembly {
sstore(z$slot, 7)
off := z$offset
sstore(x_slot, 7)
off := x_offset
}
assert(off == 0);
return true;
@ -7439,7 +7467,9 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
)";
compileAndRun(sourceCode, 0, "C");
BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7)));
BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0)));
BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0)));
}
BOOST_AUTO_TEST_CASE(inline_assembly_jumps)