Type checking for Yul.

This commit is contained in:
chriseth 2020-01-16 18:56:05 +01:00
parent aa6a2b4706
commit 1f51716227
4 changed files with 101 additions and 34 deletions

View File

@ -82,7 +82,6 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
{
expectValidType(_literal.type, _literal.location);
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
typeError(
_literal.location,
@ -93,6 +92,13 @@ vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
else if (_literal.kind == LiteralKind::Boolean)
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "");
if (!m_dialect.validTypeForLiteral(_literal.kind, _literal.value, _literal.type))
typeError(
_literal.location,
"Invalid type \"" + _literal.type.str() + "\" for literal \"" + _literal.value.str() + "\"."
);
return {_literal.type};
}
@ -179,7 +185,8 @@ void AsmAnalyzer::operator()(Assignment const& _assignment)
);
for (size_t i = 0; i < numVariables; ++i)
checkAssignment(_assignment.variableNames[i]);
if (i < types.size())
checkAssignment(_assignment.variableNames[i], types[i]);
}
void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
@ -193,6 +200,9 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
yul::IdentifierContext::VariableDeclaration,
m_currentScope->insideFunction()
);
for (auto const& variable: _varDecl.variables)
expectValidType(variable.type, variable.location);
if (_varDecl.value)
{
vector<YulString> types = std::visit(*this, *_varDecl.value);
@ -204,15 +214,25 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
to_string(types.size()) +
" values."
);
for (size_t i = 0; i < _varDecl.variables.size(); ++i)
{
YulString givenType = m_dialect.defaultType;
if (i < types.size())
givenType = types[i];
TypedName const& variable = _varDecl.variables[i];
if (variable.type != givenType)
typeError(
variable.location,
"Assigning value of type \"" + givenType.str() + "\" to variable of type \"" + variable.type.str() + "."
);
}
}
for (TypedName const& variable: _varDecl.variables)
{
expectValidType(variable.type, variable.location);
m_activeVariables.insert(&std::get<Scope::Variable>(
m_currentScope->identifiers.at(variable.name))
);
}
}
void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
@ -291,6 +311,11 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
);
}
}
std::reverse(argTypes.begin(), argTypes.end());
if (parameterTypes && parameterTypes->size() == argTypes.size())
for (size_t i = 0; i < parameterTypes->size(); ++i)
expectType((*parameterTypes)[i], argTypes[i], locationOf(_funCall.arguments[i]));
if (m_success)
{
@ -306,7 +331,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
void AsmAnalyzer::operator()(If const& _if)
{
expectExpression(*_if.condition);
expectBoolExpression(*_if.condition);
(*this)(_if.body);
}
@ -315,32 +340,15 @@ void AsmAnalyzer::operator()(Switch const& _switch)
{
yulAssert(_switch.expression, "");
expectExpression(*_switch.expression);
YulString caseType;
bool mismatchingTypes = false;
for (auto const& _case: _switch.cases)
if (_case.value)
{
if (caseType.empty())
caseType = _case.value->type;
else if (caseType != _case.value->type)
{
mismatchingTypes = true;
break;
}
}
if (mismatchingTypes)
m_errorReporter.typeError(
_switch.location,
"Switch cases have non-matching types."
);
YulString valueType = expectExpression(*_switch.expression);
set<u256> cases;
for (auto const& _case: _switch.cases)
{
if (_case.value)
{
expectType(valueType, _case.value->type, _case.value->location);
// We cannot use "expectExpression" here because *_case.value is not an
// Expression and would be converted to an Expression otherwise.
(*this)(*_case.value);
@ -366,8 +374,7 @@ void AsmAnalyzer::operator()(ForLoop const& _for)
// condition, the body and the post part inside.
m_currentScope = &scope(&_for.pre);
expectExpression(*_for.condition);
expectBoolExpression(*_for.condition);
// backup outer for-loop & create new state
auto outerForLoop = m_currentForLoop;
m_currentForLoop = &_for;
@ -403,10 +410,24 @@ YulString AsmAnalyzer::expectExpression(Expression const& _expr)
return types.empty() ? m_dialect.defaultType : types.front();
}
void AsmAnalyzer::checkAssignment(Identifier const& _variable)
void AsmAnalyzer::expectBoolExpression(Expression const& _expr)
{
YulString type = expectExpression(_expr);
if (type != m_dialect.boolType)
typeError(locationOf(_expr),
"Expected a value of boolean type \"" +
m_dialect.boolType.str() +
"\" but got \"" +
type.str() +
"\""
);
}
void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueType)
{
yulAssert(!_variable.name.empty(), "");
size_t numErrorsBefore = m_errorReporter.errors().size();
YulString const* variableType = nullptr;
bool found = false;
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{
@ -418,6 +439,8 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
_variable.location,
"Variable " + _variable.name.str() + " used before it was declared."
);
else
variableType = &std::get<Scope::Variable>(*var).type;
found = true;
}
else if (m_resolver)
@ -427,6 +450,7 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
if (variableSize != size_t(-1))
{
found = true;
variableType = &m_dialect.defaultType;
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
}
}
@ -438,6 +462,17 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
if (numErrorsBefore == m_errorReporter.errors().size())
declarationError(_variable.location, "Variable not found or variable not lvalue.");
}
if (variableType && *variableType != _valueType)
typeError(_variable.location,
"Assigning a value of type \"" +
_valueType.str() +
"\" to a variable of type \"" +
variableType->str() +
"\"."
);
if (m_success)
yulAssert(variableType, "");
}
Scope& AsmAnalyzer::scope(Block const* _block)
@ -447,15 +482,28 @@ Scope& AsmAnalyzer::scope(Block const* _block)
yulAssert(scopePtr, "Scope requested but not present.");
return *scopePtr;
}
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
{
if (!_type.empty() && !m_dialect.types.count(_type))
m_errorReporter.typeError(
if (!m_dialect.types.count(_type))
typeError(
_location,
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
);
}
void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, SourceLocation const& _location)
{
if (_expectedType != _givenType)
typeError(_location,
"Expected a value of type \"" +
_expectedType.str() +
"\" but got \"" +
_givenType.str() +
"\""
);
}
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
{
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));

View File

@ -96,13 +96,18 @@ private:
/// Visits the expression, expects that it evaluates to exactly one value and
/// returns the type. Reports errors on errors and returns the default type.
YulString expectExpression(Expression const& _expr);
/// Vists the expression and expects it to return a single boolean value.
/// Reports an error otherwise.
void expectBoolExpression(Expression const& _expr);
bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and can be assigned to.
void checkAssignment(Identifier const& _variable);
/// Verifies that a variable to be assigned to exists, can be assigned to
/// and has the same type as the value.
void checkAssignment(Identifier const& _variable, YulString _valueType);
Scope& scope(Block const* _block);
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);

View File

@ -19,10 +19,19 @@
*/
#include <libyul/Dialect.h>
#include <libyul/AsmData.h>
using namespace solidity::yul;
using namespace std;
bool Dialect::validTypeForLiteral(LiteralKind _kind, YulString, YulString _type) const
{
if (_kind == LiteralKind::Boolean)
return _type == boolType;
else
return true;
}
Dialect const& Dialect::yulDeprecated()
{
static unique_ptr<Dialect> dialect;

View File

@ -33,6 +33,7 @@ namespace solidity::yul
class YulString;
using Type = YulString;
enum class LiteralKind;
struct BuiltinFunction
{
@ -52,7 +53,7 @@ struct Dialect: boost::noncopyable
YulString defaultType;
/// Type used for the literals "true" and "false".
YulString boolType;
std::set<YulString> types;
std::set<YulString> types = {{}};
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
@ -61,6 +62,10 @@ struct Dialect: boost::noncopyable
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
/// Check whether the given type is legal for the given literal value.
/// Should only be called if the type exists in the dialect at all.
virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const;
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
Dialect() = default;