Browse Source

Merge branch 'develop' into network

cl-refactor
subtly 10 years ago
parent
commit
b54f79b3e2
  1. 1
      libdevcore/FixedHash.h
  2. 9
      libdevcrypto/Common.cpp
  3. 10
      libdevcrypto/Common.h
  4. 1
      libethcore/Exceptions.h
  5. 10
      libethereum/State.cpp
  6. 4
      libethereum/Transaction.cpp
  7. 20
      libevm/VM.h
  8. 93
      libsolidity/Compiler.cpp
  9. 13
      libsolidity/Compiler.h
  10. 164
      libsolidity/Scanner.cpp
  11. 59
      libsolidity/Scanner.h
  12. 1
      libsolidity/Token.h
  13. 1
      test/TestHelper.cpp
  14. 35
      test/recursiveCreateFiller.json
  15. 22
      test/solidityEndToEndTest.cpp
  16. 40
      test/solidityScanner.cpp
  17. 77
      test/vm.cpp

1
libdevcore/FixedHash.h

@ -83,6 +83,7 @@ public:
bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; }
bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; }
bool operator<(FixedHash const& _c) const { return m_data < _c.m_data; }
bool operator>=(FixedHash const& _c) const { return m_data >= _c.m_data; }
// The obvious binary operators.
FixedHash& operator^=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; }

9
libdevcrypto/Common.cpp

@ -33,6 +33,15 @@ using namespace dev::crypto;
static Secp256k1 s_secp256k1;
bool dev::SignatureStruct::isValid()
{
if (this->v > 1 ||
this->r >= h256("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") ||
this->s >= h256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"))
return false;
return true;
}
Public dev::toPublic(Secret const& _secret)
{
Public p;

10
libdevcrypto/Common.h

@ -43,7 +43,15 @@ using Public = h512;
/// @NOTE This is not endian-specific; it's just a bunch of bytes.
using Signature = h520;
struct SignatureStruct { h256 r; h256 s; byte v; };
struct SignatureStruct
{
/// @returns true if r,s,v values are valid, otherwise false
bool isValid();
h256 r;
h256 s;
byte v;
};
/// An Ethereum address: 20 bytes.
/// @NOTE This is not endian-specific; it's just a bunch of bytes.

1
libethcore/Exceptions.h

@ -60,6 +60,7 @@ struct InvalidTransactionGasUsed: virtual dev::Exception {};
struct InvalidTransactionsStateRoot: virtual dev::Exception {};
struct InvalidReceiptsStateRoot: virtual dev::Exception {};
struct InvalidTimestamp: virtual dev::Exception {};
struct InvalidLogBloom: virtual dev::Exception {};
class InvalidNonce: virtual public dev::Exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; virtual const char* what() const noexcept; };
class InvalidBlockNonce: virtual public dev::Exception { public: InvalidBlockNonce(h256 _h = h256(), h256 _n = h256(), u256 _d = 0): h(_h), n(_n), d(_d) {} h256 h; h256 n; u256 d; virtual const char* what() const noexcept; };
struct InvalidParentHash: virtual dev::Exception {};

10
libethereum/State.cpp

@ -55,6 +55,10 @@ void ecrecoverCode(bytesConstRef _in, bytesRef _out)
memcpy(&in, _in.data(), min(_in.size(), sizeof(in)));
SignatureStruct sig{in.r, in.s, (byte)((int)(u256)in.v - 27)};
if (!sig.isValid() || in.v > 28)
return;
byte pubkey[65];
int pubkeylen = 65;
secp256k1_start();
@ -661,6 +665,12 @@ u256 State::enact(bytesConstRef _block, BlockChain const* _bc, bool _checkNonce)
BOOST_THROW_EXCEPTION(InvalidReceiptsStateRoot());
}
if (m_currentBlock.logBloom != logBloom())
{
cwarn << "Bad log bloom!";
BOOST_THROW_EXCEPTION(InvalidLogBloom());
}
// Initialise total difficulty calculation.
u256 tdIncrease = m_currentBlock.difficulty;

4
libethereum/Transaction.cpp

@ -82,7 +82,9 @@ Address Transaction::sender() const
void Transaction::sign(Secret _priv)
{
auto sig = dev::sign(_priv, sha3(WithoutSignature));
m_vrs = *(SignatureStruct const*)&sig;
SignatureStruct sigStruct = *(SignatureStruct const*)&sig;
if (sigStruct.isValid())
m_vrs = sigStruct;
}
void Transaction::streamRLP(RLPStream& _s, IncludeSignature _sig) const

20
libevm/VM.h

@ -71,8 +71,9 @@ public:
template <class Ext>
bytesConstRef go(Ext& _ext, OnOpFunc const& _onOp = OnOpFunc(), uint64_t _steps = (uint64_t)-1);
void require(u256 _n) { if (m_stack.size() < _n) BOOST_THROW_EXCEPTION(StackTooSmall() << RequirementError((bigint)_n, (bigint)m_stack.size())); }
void require(u256 _n) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackTooSmall() << RequirementError((bigint)_n, (bigint)m_stack.size())); } }
void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } }
u256 gas() const { return m_gas; }
u256 curPC() const { return m_curPC; }
@ -85,6 +86,7 @@ private:
bytes m_temp;
u256s m_stack;
std::set<u256> m_jumpDests;
std::function<void()> m_onFail;
};
}
@ -92,7 +94,7 @@ private:
// INLINE:
template <class Ext> dev::bytesConstRef dev::eth::VM::go(Ext& _ext, OnOpFunc const& _onOp, uint64_t _steps)
{
auto memNeed = [](dev::u256 _offset, dev::u256 _size) { return _size ? _offset + _size : 0; };
auto memNeed = [](dev::u256 _offset, dev::u256 _size) { return _size ? (bigint)_offset + _size : (bigint)0; };
if (m_jumpDests.empty())
{
@ -129,6 +131,15 @@ template <class Ext> dev::bytesConstRef dev::eth::VM::go(Ext& _ext, OnOpFunc con
// FEES...
bigint runGas = c_stepGas;
bigint newTempSize = m_temp.size();
auto onOperation = [&]()
{
if (_onOp)
_onOp(osteps - _steps - 1, inst, newTempSize > m_temp.size() ? (newTempSize - m_temp.size()) / 32 : bigint(0), runGas, this, &_ext);
};
// should work, but just seems to result in immediate errorless exit on initial execution. yeah. weird.
//m_onFail = std::function<void()>(onOperation);
switch (inst)
{
case Instruction::STOP:
@ -355,8 +366,9 @@ template <class Ext> dev::bytesConstRef dev::eth::VM::go(Ext& _ext, OnOpFunc con
if (newTempSize > m_temp.size())
runGas += c_memoryGas * (newTempSize - m_temp.size()) / 32;
if (_onOp)
_onOp(osteps - _steps - 1, inst, newTempSize > m_temp.size() ? (newTempSize - m_temp.size()) / 32 : bigint(0), runGas, this, &_ext);
onOperation();
// if (_onOp)
// _onOp(osteps - _steps - 1, inst, newTempSize > m_temp.size() ? (newTempSize - m_temp.size()) / 32 : bigint(0), runGas, this, &_ext);
if (m_gas < runGas)
{

93
libsolidity/Compiler.cpp

@ -43,42 +43,59 @@ void Compiler::compileContract(ContractDefinition& _contract)
{
m_context = CompilerContext(); // clear it just in case
//@todo constructor
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
m_context.addFunction(*function);
//@todo sort them?
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
m_context.addStateVariable(*variable);
if (function->getName() != _contract.getName()) // don't add the constructor here
m_context.addFunction(*function);
registerStateVariables(_contract);
appendFunctionSelector(_contract.getDefinedFunctions());
appendFunctionSelector(_contract);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
function->accept(*this);
if (function->getName() != _contract.getName()) // don't add the constructor here
function->accept(*this);
packIntoContractCreator();
packIntoContractCreator(_contract);
}
void Compiler::packIntoContractCreator()
void Compiler::packIntoContractCreator(ContractDefinition const& _contract)
{
CompilerContext creatorContext;
eth::AssemblyItem sub = creatorContext.addSubroutine(m_context.getAssembly());
CompilerContext runtimeContext;
swap(m_context, runtimeContext);
registerStateVariables(_contract);
FunctionDefinition* constructor = nullptr;
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
if (function->getName() == _contract.getName())
{
constructor = function.get();
break;
}
if (constructor)
{
eth::AssemblyItem returnTag = m_context.pushNewTag();
m_context.addFunction(*constructor); // note that it cannot be called due to syntactic reasons
//@todo copy constructor arguments from calldata to memory prior to this
//@todo calling other functions inside the constructor should either trigger a parse error
//or we should copy them here (register them above and call "accept") - detecting which
// functions are referenced / called needs to be done in a recursive way.
appendCalldataUnpacker(*constructor, true);
m_context.appendJumpTo(m_context.getFunctionEntryLabel(*constructor));
constructor->accept(*this);
m_context << returnTag;
}
eth::AssemblyItem sub = m_context.addSubroutine(runtimeContext.getAssembly());
// stack contains sub size
creatorContext << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY;
creatorContext << u256(0) << eth::Instruction::RETURN;
swap(m_context, creatorContext);
m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY;
m_context << u256(0) << eth::Instruction::RETURN;
}
void Compiler::appendFunctionSelector(vector<ASTPointer<FunctionDefinition>> const& _functions)
void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
{
// sort all public functions and store them together with a tag for their argument decoding section
map<string, pair<FunctionDefinition const*, eth::AssemblyItem>> publicFunctions;
for (ASTPointer<FunctionDefinition> const& f: _functions)
if (f->isPublic())
publicFunctions.insert(make_pair(f->getName(), make_pair(f.get(), m_context.newTag())));
vector<FunctionDefinition const*> interfaceFunctions = _contract.getInterfaceFunctions();
vector<eth::AssemblyItem> callDataUnpackerEntryPoints;
//@todo remove constructor
if (publicFunctions.size() > 255)
if (interfaceFunctions.size() > 255)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract."));
// retrieve the first byte of the call data, which determines the called function
@ -90,21 +107,20 @@ void Compiler::appendFunctionSelector(vector<ASTPointer<FunctionDefinition>> con
<< eth::dupInstruction(2);
// stack here: 1 0 <funid> 0, stack top will be counted up until it matches funid
for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
for (unsigned funid = 0; funid < interfaceFunctions.size(); ++funid)
{
eth::AssemblyItem const& callDataUnpackerEntry = f.second.second;
callDataUnpackerEntryPoints.push_back(m_context.newTag());
m_context << eth::dupInstruction(2) << eth::dupInstruction(2) << eth::Instruction::EQ;
m_context.appendConditionalJumpTo(callDataUnpackerEntry);
m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.back());
m_context << eth::dupInstruction(4) << eth::Instruction::ADD;
//@todo avoid the last ADD (or remove it in the optimizer)
}
m_context << eth::Instruction::STOP; // function not found
for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
for (unsigned funid = 0; funid < interfaceFunctions.size(); ++funid)
{
FunctionDefinition const& function = *f.second.first;
eth::AssemblyItem const& callDataUnpackerEntry = f.second.second;
m_context << callDataUnpackerEntry;
FunctionDefinition const& function = *interfaceFunctions[funid];
m_context << callDataUnpackerEntryPoints[funid];
eth::AssemblyItem returnTag = m_context.pushNewTag();
appendCalldataUnpacker(function);
m_context.appendJumpTo(m_context.getFunctionEntryLabel(function));
@ -113,10 +129,11 @@ void Compiler::appendFunctionSelector(vector<ASTPointer<FunctionDefinition>> con
}
}
void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function)
unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory)
{
// We do not check the calldata size, everything is zero-padded.
unsigned dataOffset = 1;
eth::Instruction load = _fromMemory ? eth::Instruction::MLOAD : eth::Instruction::CALLDATALOAD;
//@todo this can be done more efficiently, saving some CALLDATALOAD calls
for (ASTPointer<VariableDeclaration> const& var: _function.getParameters())
@ -127,12 +144,13 @@ void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function)
<< errinfo_sourceLocation(var->getLocation())
<< errinfo_comment("Type " + var->getType()->toString() + " not yet supported."));
if (numBytes == 32)
m_context << u256(dataOffset) << eth::Instruction::CALLDATALOAD;
m_context << u256(dataOffset) << load;
else
m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset)
<< eth::Instruction::CALLDATALOAD << eth::Instruction::DIV;
<< load << eth::Instruction::DIV;
dataOffset += numBytes;
}
return dataOffset;
}
void Compiler::appendReturnValuePacker(FunctionDefinition const& _function)
@ -158,6 +176,13 @@ void Compiler::appendReturnValuePacker(FunctionDefinition const& _function)
m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN;
}
void Compiler::registerStateVariables(ContractDefinition const& _contract)
{
//@todo sort them?
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
m_context.addStateVariable(*variable);
}
bool Compiler::visit(FunctionDefinition& _function)
{
//@todo to simplify this, the calling convention could by changed such that

13
libsolidity/Compiler.h

@ -40,12 +40,17 @@ public:
static bytes compile(ContractDefinition& _contract, bool _optimize);
private:
/// Creates a new compiler context / assembly and packs the current code into the data part.
void packIntoContractCreator();
void appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition> > const& _functions);
void appendCalldataUnpacker(FunctionDefinition const& _function);
/// Creates a new compiler context / assembly, packs the current code into the data part and
/// adds the constructor code.
void packIntoContractCreator(ContractDefinition const& _contract);
void appendFunctionSelector(ContractDefinition const& _contract);
/// Creates code that unpacks the arguments for the given function, from memory if
/// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes.
unsigned appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory = false);
void appendReturnValuePacker(FunctionDefinition const& _function);
void registerStateVariables(ContractDefinition const& _contract);
virtual bool visit(FunctionDefinition& _function) override;
virtual bool visit(IfStatement& _ifStatement) override;
virtual bool visit(WhileStatement& _whileStatement) override;

164
libsolidity/Scanner.cpp

@ -63,34 +63,34 @@ namespace solidity
namespace
{
bool IsDecimalDigit(char c)
bool isDecimalDigit(char c)
{
return '0' <= c && c <= '9';
}
bool IsHexDigit(char c)
bool isHexDigit(char c)
{
return IsDecimalDigit(c)
return isDecimalDigit(c)
|| ('a' <= c && c <= 'f')
|| ('A' <= c && c <= 'F');
}
bool IsLineTerminator(char c)
bool isLineTerminator(char c)
{
return c == '\n';
}
bool IsWhiteSpace(char c)
bool isWhiteSpace(char c)
{
return c == ' ' || c == '\n' || c == '\t';
}
bool IsIdentifierStart(char c)
bool isIdentifierStart(char c)
{
return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
}
bool IsIdentifierPart(char c)
bool isIdentifierPart(char c)
{
return IsIdentifierStart(c) || IsDecimalDigit(c);
return isIdentifierStart(c) || isDecimalDigit(c);
}
int HexValue(char c)
int hexValue(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
@ -104,11 +104,16 @@ int HexValue(char c)
void Scanner::reset(CharStream const& _source)
{
bool foundDocComment;
m_source = _source;
m_char = m_source.get();
skipWhitespace();
scanToken();
next();
foundDocComment = scanToken();
// special version of Scanner:next() taking the previous scanToken() result into account
m_currentToken = m_nextToken;
if (scanToken() || foundDocComment)
m_skippedComment = m_nextSkippedComment;
}
@ -117,7 +122,7 @@ bool Scanner::scanHexByte(char& o_scannedByte)
char x = 0;
for (int i = 0; i < 2; i++)
{
int d = HexValue(m_char);
int d = hexValue(m_char);
if (d < 0)
{
rollback(i);
@ -136,9 +141,10 @@ BOOST_STATIC_ASSERT(Token::NUM_TOKENS <= 0x100);
Token::Value Scanner::next()
{
m_current_token = m_next_token;
scanToken();
return m_current_token.token;
m_currentToken = m_nextToken;
if (scanToken())
m_skippedComment = m_nextSkippedComment;
return m_currentToken.token;
}
Token::Value Scanner::selectToken(char _next, Token::Value _then, Token::Value _else)
@ -153,11 +159,11 @@ Token::Value Scanner::selectToken(char _next, Token::Value _then, Token::Value _
bool Scanner::skipWhitespace()
{
int const start_position = getSourcePos();
while (IsWhiteSpace(m_char))
int const startPosition = getSourcePos();
while (isWhiteSpace(m_char))
advance();
// Return whether or not we skipped any characters.
return getSourcePos() != start_position;
return getSourcePos() != startPosition;
}
@ -167,10 +173,24 @@ Token::Value Scanner::skipSingleLineComment()
// to be part of the single-line comment; it is recognized
// separately by the lexical grammar and becomes part of the
// stream of input elements for the syntactic grammar
while (advance() && !IsLineTerminator(m_char)) { };
while (advance() && !isLineTerminator(m_char)) { };
return Token::WHITESPACE;
}
/// For the moment this function simply consumes a single line triple slash doc comment
Token::Value Scanner::scanDocumentationComment()
{
LiteralScope literal(this);
advance(); //consume the last '/'
while (!isSourcePastEndOfInput() && !isLineTerminator(m_char))
{
addCommentLiteralChar(m_char);
advance();
}
literal.complete();
return Token::COMMENT_LITERAL;
}
Token::Value Scanner::skipMultiLineComment()
{
if (asserts(m_char == '*'))
@ -194,14 +214,15 @@ Token::Value Scanner::skipMultiLineComment()
return Token::ILLEGAL;
}
void Scanner::scanToken()
bool Scanner::scanToken()
{
m_next_token.literal.clear();
bool foundDocComment = false;
m_nextToken.literal.clear();
Token::Value token;
do
{
// Remember the position of the next token
m_next_token.location.start = getSourcePos();
m_nextToken.location.start = getSourcePos();
switch (m_char)
{
case '\n': // fall-through
@ -280,7 +301,7 @@ void Scanner::scanToken()
}
else if (m_char == '=')
token = selectToken(Token::ASSIGN_SUB);
else if (m_char == '.' || IsDecimalDigit(m_char))
else if (m_char == '.' || isDecimalDigit(m_char))
token = scanNumber('-');
else
token = Token::SUB;
@ -297,7 +318,22 @@ void Scanner::scanToken()
// / // /* /=
advance();
if (m_char == '/')
token = skipSingleLineComment();
{
if (!advance()) /* double slash comment directly before EOS */
token = Token::WHITESPACE;
else if (m_char == '/')
{
Token::Value comment;
m_nextSkippedComment.location.start = getSourcePos();
comment = scanDocumentationComment();
m_nextSkippedComment.location.end = getSourcePos();
m_nextSkippedComment.token = comment;
token = Token::WHITESPACE;
foundDocComment = true;
}
else
token = skipSingleLineComment();
}
else if (m_char == '*')
token = skipMultiLineComment();
else if (m_char == '=')
@ -332,7 +368,7 @@ void Scanner::scanToken()
case '.':
// . Number
advance();
if (IsDecimalDigit(m_char))
if (isDecimalDigit(m_char))
token = scanNumber('.');
else
token = Token::PERIOD;
@ -371,9 +407,9 @@ void Scanner::scanToken()
token = selectToken(Token::BIT_NOT);
break;
default:
if (IsIdentifierStart(m_char))
if (isIdentifierStart(m_char))
token = scanIdentifierOrKeyword();
else if (IsDecimalDigit(m_char))
else if (isDecimalDigit(m_char))
token = scanNumber();
else if (skipWhitespace())
token = Token::WHITESPACE;
@ -387,8 +423,10 @@ void Scanner::scanToken()
// whitespace.
}
while (token == Token::WHITESPACE);
m_next_token.location.end = getSourcePos();
m_next_token.token = token;
m_nextToken.location.end = getSourcePos();
m_nextToken.token = token;
return foundDocComment;
}
bool Scanner::scanEscape()
@ -396,7 +434,7 @@ bool Scanner::scanEscape()
char c = m_char;
advance();
// Skip escaped newlines.
if (IsLineTerminator(c))
if (isLineTerminator(c))
return true;
switch (c)
{
@ -437,7 +475,7 @@ Token::Value Scanner::scanString()
char const quote = m_char;
advance(); // consume quote
LiteralScope literal(this);
while (m_char != quote && !isSourcePastEndOfInput() && !IsLineTerminator(m_char))
while (m_char != quote && !isSourcePastEndOfInput() && !isLineTerminator(m_char))
{
char c = m_char;
advance();
@ -449,8 +487,9 @@ Token::Value Scanner::scanString()
else
addLiteralChar(c);
}
if (m_char != quote) return Token::ILLEGAL;
literal.Complete();
if (m_char != quote)
return Token::ILLEGAL;
literal.complete();
advance(); // consume quote
return Token::STRING_LITERAL;
}
@ -458,7 +497,7 @@ Token::Value Scanner::scanString()
void Scanner::scanDecimalDigits()
{
while (IsDecimalDigit(m_char))
while (isDecimalDigit(m_char))
addLiteralCharAndAdvance();
}
@ -487,9 +526,9 @@ Token::Value Scanner::scanNumber(char _charSeen)
// hex number
kind = HEX;
addLiteralCharAndAdvance();
if (!IsHexDigit(m_char))
if (!isHexDigit(m_char))
return Token::ILLEGAL; // we must have at least one hex digit after 'x'/'X'
while (IsHexDigit(m_char))
while (isHexDigit(m_char))
addLiteralCharAndAdvance();
}
}
@ -509,12 +548,13 @@ Token::Value Scanner::scanNumber(char _charSeen)
{
if (asserts(kind != HEX)) // 'e'/'E' must be scanned as part of the hex number
BOOST_THROW_EXCEPTION(InternalCompilerError());
if (kind != DECIMAL) return Token::ILLEGAL;
if (kind != DECIMAL)
return Token::ILLEGAL;
// scan exponent
addLiteralCharAndAdvance();
if (m_char == '+' || m_char == '-')
addLiteralCharAndAdvance();
if (!IsDecimalDigit(m_char))
if (!isDecimalDigit(m_char))
return Token::ILLEGAL; // we must have at least one decimal digit after 'e'/'E'
scanDecimalDigits();
}
@ -522,9 +562,9 @@ Token::Value Scanner::scanNumber(char _charSeen)
// not be an identifier start or a decimal digit; see ECMA-262
// section 7.8.3, page 17 (note that we read only one decimal digit
// if the value is 0).
if (IsDecimalDigit(m_char) || IsIdentifierStart(m_char))
if (isDecimalDigit(m_char) || isIdentifierStart(m_char))
return Token::ILLEGAL;
literal.Complete();
literal.complete();
return Token::NUMBER;
}
@ -532,9 +572,9 @@ Token::Value Scanner::scanNumber(char _charSeen)
// ----------------------------------------------------------------------------
// Keyword Matcher
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
KEYWORD_GROUP('a') \
KEYWORD("address", Token::ADDRESS) \
KEYWORD("address", Token::ADDRESS) \
KEYWORD_GROUP('b') \
KEYWORD("break", Token::BREAK) \
KEYWORD("bool", Token::BOOL) \
@ -686,29 +726,29 @@ Token::Value Scanner::scanNumber(char _charSeen)
KEYWORD("while", Token::WHILE) \
static Token::Value KeywordOrIdentifierToken(string const& input)
static Token::Value KeywordOrIdentifierToken(string const& _input)
{
if (asserts(!input.empty()))
if (asserts(!_input.empty()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
int const kMinLength = 2;
int const kMaxLength = 10;
if (input.size() < kMinLength || input.size() > kMaxLength)
if (_input.size() < kMinLength || _input.size() > kMaxLength)
return Token::IDENTIFIER;
switch (input[0])
switch (_input[0])
{
default:
#define KEYWORD_GROUP_CASE(ch) \
break; \
case ch:
#define KEYWORD(keyword, token) \
{ \
/* 'keyword' is a char array, so sizeof(keyword) is */ \
/* strlen(keyword) plus 1 for the NUL char. */ \
int const keyword_length = sizeof(keyword) - 1; \
BOOST_STATIC_ASSERT(keyword_length >= kMinLength); \
BOOST_STATIC_ASSERT(keyword_length <= kMaxLength); \
if (input == keyword) \
return token; \
#define KEYWORD_GROUP_CASE(ch) \
break; \
case ch:
#define KEYWORD(keyword, token) \
{ \
/* 'keyword' is a char array, so sizeof(keyword) is */ \
/* strlen(keyword) plus 1 for the NUL char. */ \
int const keywordLength = sizeof(keyword) - 1; \
BOOST_STATIC_ASSERT(keywordLength >= kMinLength); \
BOOST_STATIC_ASSERT(keywordLength <= kMaxLength); \
if (_input == keyword) \
return token; \
}
KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD)
}
@ -717,15 +757,15 @@ case ch:
Token::Value Scanner::scanIdentifierOrKeyword()
{
if (asserts(IsIdentifierStart(m_char)))
if (asserts(isIdentifierStart(m_char)))
BOOST_THROW_EXCEPTION(InternalCompilerError());
LiteralScope literal(this);
addLiteralCharAndAdvance();
// Scan the rest of the identifier characters.
while (IsIdentifierPart(m_char))
while (isIdentifierPart(m_char))
addLiteralCharAndAdvance();
literal.Complete();
return KeywordOrIdentifierToken(m_next_token.literal);
literal.complete();
return KeywordOrIdentifierToken(m_nextToken.literal);
}
char CharStream::advanceAndGet()

59
libsolidity/Scanner.h

@ -96,18 +96,18 @@ private:
class Scanner
{
public:
// Scoped helper for literal recording. Automatically drops the literal
// if aborting the scanning before it's complete.
/// Scoped helper for literal recording. Automatically drops the literal
/// if aborting the scanning before it's complete.
class LiteralScope
{
public:
explicit LiteralScope(Scanner* self): scanner_(self), complete_(false) { scanner_->startNewLiteral(); }
~LiteralScope() { if (!complete_) scanner_->dropLiteral(); }
void Complete() { complete_ = true; }
explicit LiteralScope(Scanner* self): m_scanner(self), m_complete(false) { m_scanner->startNewLiteral(); }
~LiteralScope() { if (!m_complete) m_scanner->dropLiteral(); }
void complete() { m_complete = true; }
private:
Scanner* scanner_;
bool complete_;
Scanner* m_scanner;
bool m_complete;
};
Scanner() { reset(CharStream()); }
@ -116,25 +116,34 @@ public:
/// Resets the scanner as if newly constructed with _input as input.
void reset(CharStream const& _source);
/// Returns the next token and advances input.
/// Returns the next token and advances input
Token::Value next();
///@{
///@name Information about the current token
/// Returns the current token
Token::Value getCurrentToken() { return m_current_token.token; }
Location getCurrentLocation() const { return m_current_token.location; }
std::string const& getCurrentLiteral() const { return m_current_token.literal; }
Token::Value getCurrentToken()
{
return m_currentToken.token;
}
Location getCurrentLocation() const { return m_currentToken.location; }
std::string const& getCurrentLiteral() const { return m_currentToken.literal; }
///@}
///@{
///@name Information about the current comment token
Location getCurrentCommentLocation() const { return m_skippedComment.location; }
std::string const& getCurrentCommentLiteral() const { return m_skippedComment.literal; }
///@}
///@{
///@name Information about the next token
/// Returns the next token without advancing input.
Token::Value peekNextToken() const { return m_next_token.token; }
Location peekLocation() const { return m_next_token.location; }
std::string const& peekLiteral() const { return m_next_token.literal; }
Token::Value peekNextToken() const { return m_nextToken.token; }
Location peekLocation() const { return m_nextToken.location; }
std::string const& peekLiteral() const { return m_nextToken.literal; }
///@}
///@{
@ -146,7 +155,7 @@ public:
///@}
private:
// Used for the current and look-ahead token.
/// Used for the current and look-ahead token and comments
struct TokenDesc
{
Token::Value token;
@ -156,9 +165,10 @@ private:
///@{
///@name Literal buffer support
inline void startNewLiteral() { m_next_token.literal.clear(); }
inline void addLiteralChar(char c) { m_next_token.literal.push_back(c); }
inline void dropLiteral() { m_next_token.literal.clear(); }
inline void startNewLiteral() { m_nextToken.literal.clear(); }
inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); }
inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); }
inline void dropLiteral() { m_nextToken.literal.clear(); }
inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); }
///@}
@ -171,8 +181,9 @@ private:
bool scanHexByte(char& o_scannedByte);
/// Scans a single JavaScript token.
void scanToken();
/// Scans a single Solidity token. Returns true if the scanned token was
/// a skipped documentation comment. False in all other cases.
bool scanToken();
/// Skips all whitespace and @returns true if something was skipped.
bool skipWhitespace();
@ -184,6 +195,7 @@ private:
Token::Value scanIdentifierOrKeyword();
Token::Value scanString();
Token::Value scanDocumentationComment();
/// Scans an escape-sequence which is part of a string and adds the
/// decoded character to the current literal. Returns true if a pattern
@ -194,8 +206,11 @@ private:
int getSourcePos() { return m_source.getPos(); }
bool isSourcePastEndOfInput() { return m_source.isPastEndOfInput(); }
TokenDesc m_current_token; // desc for current token (as returned by Next())
TokenDesc m_next_token; // desc for next token (one token look-ahead)
TokenDesc m_skippedComment; // desc for current skipped comment
TokenDesc m_nextSkippedComment; // desc for next skiped comment
TokenDesc m_currentToken; // desc for current token (as returned by Next())
TokenDesc m_nextToken; // desc for next token (one token look-ahead)
CharStream m_source;

1
libsolidity/Token.h

@ -281,6 +281,7 @@ namespace solidity
K(FALSE_LITERAL, "false", 0) \
T(NUMBER, NULL, 0) \
T(STRING_LITERAL, NULL, 0) \
T(COMMENT_LITERAL, NULL, 0) \
\
/* Identifiers (not keywords or future reserved words). */ \
T(IDENTIFIER, NULL, 0) \

1
test/TestHelper.cpp

@ -374,6 +374,7 @@ void executeTests(const string& _name, const string& _testPathAppendix, std::fun
{
BOOST_ERROR("Failed test with Exception: " << _e.what());
}
break;
}
}

35
test/recursiveCreateFiller.json

@ -0,0 +1,35 @@
{
"recursiveCreate": {
"env": {
"previousHash": "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",
"currentNumber": "0",
"currentGasLimit": "10000000",
"currentDifficulty": "256",
"currentTimestamp": 1,
"currentCoinbase": "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"
},
"pre": {
"095e7baea6a6c7c4c2dfeb977efac326af552d87": {
"balance": "20000000",
"nonce": 0,
"code": "{(CODECOPY 0 0 32)(CREATE 0 0 32)}",
"storage": {}
},
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
"balance": "1000000000000000000",
"nonce": 0,
"code": "",
"storage": {}
}
},
"transaction": {
"nonce": "0",
"gasPrice": "1",
"gasLimit": "465224",
"to": "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"value": "100000",
"secretKey": "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"data": ""
}
}
}

22
test/solidityEndToEndTest.cpp

@ -700,6 +700,28 @@ BOOST_AUTO_TEST_CASE(structs)
BOOST_CHECK(callContractFunction(0) == bytes({0x01}));
}
BOOST_AUTO_TEST_CASE(constructor)
{
char const* sourceCode = "contract test {\n"
" mapping(uint => uint) data;\n"
" function test() {\n"
" data[7] = 8;\n"
" }\n"
" function get(uint key) returns (uint value) {\n"
" return data[key];"
" }\n"
"}\n";
compileAndRun(sourceCode);
map<u256, byte> data;
data[7] = 8;
auto get = [&](u256 const& _x) -> u256
{
return data[_x];
};
testSolidityAgainstCpp(0, get, u256(6));
testSolidityAgainstCpp(0, get, u256(7));
}
BOOST_AUTO_TEST_SUITE_END()
}

40
test/solidityScanner.cpp

@ -153,6 +153,46 @@ BOOST_AUTO_TEST_CASE(ambiguities)
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL);
}
BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin)
{
Scanner scanner(CharStream("/// Send $(value / 1000) chocolates to the user"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(documentation_comments_parsed)
{
Scanner scanner(CharStream("some other tokens /// Send $(value / 1000) chocolates to the user"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(comment_before_eos)
{
Scanner scanner(CharStream("//"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
}
BOOST_AUTO_TEST_CASE(documentation_comment_before_eos)
{
Scanner scanner(CharStream("///"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
}
BOOST_AUTO_TEST_CASE(comments_mixed_in_sequence)
{
Scanner scanner(CharStream("hello_world ///documentation comment \n"
"//simple comment \n"
"<<"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "documentation comment ");
}
BOOST_AUTO_TEST_SUITE_END()

77
test/vm.cpp

@ -300,6 +300,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin)
VM vm(fev.gas);
u256 gas;
bool vmExceptionOccured = false;
try
{
output = vm.go(fev, fev.simpleTrace()).toVector();
@ -308,7 +309,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin)
catch (VMException const& _e)
{
cnote << "VM did throw an exception: " << diagnostic_information(_e);
gas = 0;
vmExceptionOccured = true;
}
catch (Exception const& _e)
{
@ -342,48 +343,58 @@ void doVMTests(json_spirit::mValue& v, bool _fillin)
{
o["env"] = mValue(fev.exportEnv());
o["exec"] = mValue(fev.exportExec());
o["post"] = mValue(fev.exportState());
o["callcreates"] = fev.exportCallCreates();
o["out"] = "0x" + toHex(output);
fev.push(o, "gas", gas);
if (!vmExceptionOccured)
{
o["post"] = mValue(fev.exportState());
o["callcreates"] = fev.exportCallCreates();
o["out"] = "0x" + toHex(output);
fev.push(o, "gas", gas);
}
}
else
{
BOOST_REQUIRE(o.count("post") > 0);
BOOST_REQUIRE(o.count("callcreates") > 0);
BOOST_REQUIRE(o.count("out") > 0);
BOOST_REQUIRE(o.count("gas") > 0);
if (o.count("post") > 0) // No exceptions expected
{
BOOST_CHECK(!vmExceptionOccured);
dev::test::FakeExtVM test;
test.importState(o["post"].get_obj());
test.importCallCreates(o["callcreates"].get_array());
BOOST_REQUIRE(o.count("post") > 0);
BOOST_REQUIRE(o.count("callcreates") > 0);
BOOST_REQUIRE(o.count("out") > 0);
BOOST_REQUIRE(o.count("gas") > 0);
checkOutput(output, o);
dev::test::FakeExtVM test;
test.importState(o["post"].get_obj());
test.importCallCreates(o["callcreates"].get_array());
BOOST_CHECK_EQUAL(toInt(o["gas"]), gas);
checkOutput(output, o);
auto& expectedAddrs = test.addresses;
auto& resultAddrs = fev.addresses;
for (auto&& expectedPair : expectedAddrs)
{
auto& expectedAddr = expectedPair.first;
auto resultAddrIt = resultAddrs.find(expectedAddr);
if (resultAddrIt == resultAddrs.end())
BOOST_ERROR("Missing expected address " << expectedAddr);
else
{
auto& expectedState = expectedPair.second;
auto& resultState = resultAddrIt->second;
BOOST_CHECK_MESSAGE(std::get<0>(expectedState) == std::get<0>(resultState), expectedAddr << ": incorrect balance " << std::get<0>(resultState) << ", expected " << std::get<0>(expectedState));
BOOST_CHECK_MESSAGE(std::get<1>(expectedState) == std::get<1>(resultState), expectedAddr << ": incorrect txCount " << std::get<1>(resultState) << ", expected " << std::get<1>(expectedState));
BOOST_CHECK_MESSAGE(std::get<3>(expectedState) == std::get<3>(resultState), expectedAddr << ": incorrect code");
BOOST_CHECK_EQUAL(toInt(o["gas"]), gas);
checkStorage(std::get<2>(expectedState), std::get<2>(resultState), expectedAddr);
auto& expectedAddrs = test.addresses;
auto& resultAddrs = fev.addresses;
for (auto&& expectedPair : expectedAddrs)
{
auto& expectedAddr = expectedPair.first;
auto resultAddrIt = resultAddrs.find(expectedAddr);
if (resultAddrIt == resultAddrs.end())
BOOST_ERROR("Missing expected address " << expectedAddr);
else
{
auto& expectedState = expectedPair.second;
auto& resultState = resultAddrIt->second;
BOOST_CHECK_MESSAGE(std::get<0>(expectedState) == std::get<0>(resultState), expectedAddr << ": incorrect balance " << std::get<0>(resultState) << ", expected " << std::get<0>(expectedState));
BOOST_CHECK_MESSAGE(std::get<1>(expectedState) == std::get<1>(resultState), expectedAddr << ": incorrect txCount " << std::get<1>(resultState) << ", expected " << std::get<1>(expectedState));
BOOST_CHECK_MESSAGE(std::get<3>(expectedState) == std::get<3>(resultState), expectedAddr << ": incorrect code");
checkStorage(std::get<2>(expectedState), std::get<2>(resultState), expectedAddr);
}
}
}
checkAddresses<std::map<Address, std::tuple<u256, u256, std::map<u256, u256>, bytes> > >(test.addresses, fev.addresses);
BOOST_CHECK(test.callcreates == fev.callcreates);
checkAddresses<std::map<Address, std::tuple<u256, u256, std::map<u256, u256>, bytes> > >(test.addresses, fev.addresses);
BOOST_CHECK(test.callcreates == fev.callcreates);
}
else // Exception expected
BOOST_CHECK(vmExceptionOccured);
}
}
}

Loading…
Cancel
Save