Merge pull request #9862 from ethereum/develop

Merge develop into breaking
This commit is contained in:
chriseth
2020-09-23 12:22:32 +02:00
committed by GitHub
126 changed files with 2962 additions and 320 deletions
@@ -51,6 +51,8 @@ bool hasEqualNameAndParameters(T const& _a, B const& _b)
bool ContractLevelChecker::check(ContractDefinition const& _contract)
{
_contract.annotation().unimplementedDeclarations = std::vector<Declaration const*>();
checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract);
m_overrideChecker.check(_contract);
@@ -210,9 +212,10 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedDeclarations` has already been
// pre-filled by `checkBaseConstructorArguments`.
//
for (auto const& proxy: proxies)
if (proxy.unimplemented())
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
_contract.annotation().unimplementedDeclarations->push_back(proxy.declaration());
if (_contract.abstract())
{
@@ -229,17 +232,17 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
if (
_contract.contractKind() == ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedDeclarations.empty()
!_contract.annotation().unimplementedDeclarations->empty()
)
{
SecondarySourceLocation ssl;
for (auto declaration: _contract.annotation().unimplementedDeclarations)
for (auto declaration: *_contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(
3656_error,
_contract.location(),
ssl,
"Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."
"Contract \"" + *_contract.annotation().canonicalName + "\" should be marked as abstract."
);
}
}
@@ -289,7 +292,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
if (!_contract.annotation().baseConstructorArguments.count(constructor))
_contract.annotation().unimplementedDeclarations.push_back(constructor);
_contract.annotation().unimplementedDeclarations->push_back(constructor);
}
void ContractLevelChecker::annotateBaseConstructorArguments(
@@ -465,7 +468,6 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition
void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
{
bigint size = 0;
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (!(variable->isConstant() || variable->immutable()))
@@ -473,7 +475,7 @@ void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
size += variable->annotation().type->storageSizeUpperBound();
if (size >= bigint(1) << 256)
{
m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage.");
m_errorReporter.typeError(7676_error, _contract.location(), "Contract requires too much storage.");
break;
}
}
+1 -1
View File
@@ -167,7 +167,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
// If this is not an ordinary assignment, we write and read at the same time.
bool write = _expression.annotation().willBeWrittenTo;
bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment;
bool read = !_expression.annotation().willBeWrittenTo || !*_expression.annotation().lValueOfOrdinaryAssignment;
if (write)
{
if (!m_currentConstructor)
+1 -1
View File
@@ -74,7 +74,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& node: _sourceUnit.nodes())
if (auto imp = dynamic_cast<ImportDirective const*>(node.get()))
{
string const& path = imp->annotation().absolutePath;
string const& path = *imp->annotation().absolutePath;
if (!_sourceUnits.count(path))
{
m_errorReporter.declarationError(
+13 -14
View File
@@ -158,17 +158,16 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
}
if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage)
{
TypePointer varType = _variable.annotation().type;
for (Type const* subtype: frontend::oversizedSubtypes(*varType))
{
string message = "Type " + subtype->toString(true) +
" covers a large part of storage and thus makes collisions likely."
" Either use mappings or dynamic arrays and allow their size to be increased only"
" in small quantities per transaction.";
m_errorReporter.warning(7325_error, _variable.typeName().location(), message);
}
}
if (auto varType = dynamic_cast<CompositeType const*>(_variable.annotation().type))
for (Type const* type: varType->fullDecomposition())
if (type->storageSizeUpperBound() >= (bigint(1) << 64))
{
string message = "Type " + type->toString(true) +
" covers a large part of storage and thus makes collisions likely."
" Either use mappings or dynamic arrays and allow their size to be increased only"
" in small quantities per transaction.";
m_errorReporter.warning(7325_error, _variable.typeName().location(), message);
}
return true;
}
@@ -186,7 +185,7 @@ bool StaticAnalyzer::visit(Return const& _return)
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
if (*_statement.expression().annotation().isPure)
m_errorReporter.warning(
6133_error,
_statement.location(),
@@ -288,7 +287,7 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
bool StaticAnalyzer::visit(BinaryOperation const& _operation)
{
if (
_operation.rightExpression().annotation().isPure &&
*_operation.rightExpression().annotation().isPure &&
(_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
)
if (auto rhs = dynamic_cast<RationalNumberType const*>(
@@ -313,7 +312,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
{
solAssert(_functionCall.arguments().size() == 3, "");
if (_functionCall.arguments()[2]->annotation().isPure)
if (*_functionCall.arguments()[2]->annotation().isPure)
if (auto lastArg = dynamic_cast<RationalNumberType const*>(
ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
))
+80 -38
View File
@@ -538,7 +538,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!_variable.value())
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
else if (!_variable.value()->annotation().isPure)
else if (!*_variable.value()->annotation().isPure)
m_errorReporter.typeError(
8349_error,
_variable.value()->location(),
@@ -1296,11 +1296,14 @@ bool TypeChecker::visit(Conditional const& _conditional)
}
}
_conditional.annotation().isConstant = false;
_conditional.annotation().type = commonType;
_conditional.annotation().isPure =
_conditional.condition().annotation().isPure &&
_conditional.trueExpression().annotation().isPure &&
_conditional.falseExpression().annotation().isPure;
*_conditional.condition().annotation().isPure &&
*_conditional.trueExpression().annotation().isPure &&
*_conditional.falseExpression().annotation().isPure;
_conditional.annotation().isLValue = false;
if (_conditional.annotation().willBeWrittenTo)
m_errorReporter.typeError(
@@ -1354,6 +1357,9 @@ bool TypeChecker::visit(Assignment const& _assignment)
);
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
_assignment.annotation().isPure = false;
_assignment.annotation().isLValue = false;
_assignment.annotation().isConstant = false;
checkExpressionAssignment(*t, _assignment.leftHandSide());
@@ -1401,6 +1407,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
bool TypeChecker::visit(TupleExpression const& _tuple)
{
_tuple.annotation().isConstant = false;
vector<ASTPointer<Expression>> const& components = _tuple.components();
TypePointers types;
@@ -1413,7 +1420,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
requireLValue(
*component,
_tuple.annotation().lValueOfOrdinaryAssignment
*_tuple.annotation().lValueOfOrdinaryAssignment
);
types.push_back(type(*component));
}
@@ -1425,6 +1432,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().type = TypeProvider::tuple(move(types));
// If some of the components are not LValues, the error is reported above.
_tuple.annotation().isLValue = true;
_tuple.annotation().isPure = false;
}
else
{
@@ -1464,7 +1472,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
}
if (!components[i]->annotation().isPure)
if (!*components[i]->annotation().isPure)
isPure = false;
}
_tuple.annotation().isPure = isPure;
@@ -1495,6 +1503,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().type = TypeProvider::tuple(move(types));
}
_tuple.annotation().isLValue = false;
}
return false;
}
@@ -1522,7 +1531,9 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
t = subExprType;
}
_operation.annotation().type = t;
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
_operation.annotation().isConstant = false;
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
_operation.annotation().isLValue = false;
return false;
}
@@ -1553,8 +1564,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
TypeProvider::boolean() :
commonType;
_operation.annotation().isPure =
_operation.leftExpression().annotation().isPure &&
_operation.rightExpression().annotation().isPure;
*_operation.leftExpression().annotation().isPure &&
*_operation.rightExpression().annotation().isPure;
_operation.annotation().isLValue = false;
_operation.annotation().isConstant = false;
if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
{
@@ -2091,18 +2104,17 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
{
bool not_all_mapped = false;
for (size_t i = 0; i < paramArgMap.size(); i++)
for (size_t i = 0; i < argumentNames.size(); i++)
{
size_t j;
for (j = 0; j < argumentNames.size(); j++)
if (parameterNames[i] == *argumentNames[j])
for (j = 0; j < parameterNames.size(); j++)
if (parameterNames[j] == *argumentNames[i])
break;
if (j < argumentNames.size())
paramArgMap[i] = arguments[j].get();
if (j < parameterNames.size())
paramArgMap[j] = arguments[i].get();
else
{
paramArgMap[i] = nullptr;
not_all_mapped = true;
m_errorReporter.typeError(
4974_error,
@@ -2175,7 +2187,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (ASTPointer<Expression const> const& argument: arguments)
{
argument->accept(*this);
if (!argument->annotation().isPure)
if (!*argument->annotation().isPure)
argumentsArePure = false;
}
@@ -2198,6 +2210,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// Determine function call kind and function type for this FunctionCall node
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
FunctionTypePointer functionType = nullptr;
funcCallAnno.isConstant = false;
bool isLValue = false;
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
switch (expressionType->category())
@@ -2209,7 +2224,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// Purity for function calls also depends upon the callee and its FunctionType
funcCallAnno.isPure =
argumentsArePure &&
_functionCall.expression().annotation().isPure &&
*_functionCall.expression().annotation().isPure &&
functionType &&
functionType->isPure();
@@ -2217,7 +2232,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
funcCallAnno.isLValue = functionType->parameterTypes().empty();
isLValue = functionType->parameterTypes().empty();
break;
@@ -2237,13 +2252,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
);
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
funcCallAnno.isPure = argumentsArePure;
}
else
{
funcCallAnno.kind = FunctionCallKind::TypeConversion;
funcCallAnno.isPure = argumentsArePure;
}
funcCallAnno.isPure = argumentsArePure;
break;
}
@@ -2256,6 +2269,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
break;
}
funcCallAnno.isLValue = isLValue;
// Determine return types
switch (*funcCallAnno.kind)
{
@@ -2326,6 +2341,9 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
_functionCallOptions.expression().accept(*this);
_functionCallOptions.annotation().isPure = false;
_functionCallOptions.annotation().isConstant = false;
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
if (!expressionFunctionType)
{
@@ -2456,6 +2474,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
TypePointer type = _newExpression.typeName().annotation().type;
solAssert(!!type, "Type name not resolved.");
_newExpression.annotation().isConstant = false;
if (auto contractName = dynamic_cast<UserDefinedTypeName const*>(&_newExpression.typeName()))
{
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
@@ -2486,6 +2506,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
}
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
_newExpression.annotation().isPure = false;
}
else if (type->category() == Type::Category::Array)
{
@@ -2542,6 +2563,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
auto& annotation = _memberAccess.annotation();
annotation.isConstant = false;
if (possibleMembers.empty())
{
if (initialMemberCount == 0 && !dynamic_cast<ArraySliceType const*>(exprType))
@@ -2674,11 +2697,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
functionType &&
functionType->kind() == FunctionType::Kind::Declaration
)
annotation.isPure = _memberAccess.expression().annotation().isPure;
annotation.isPure = *_memberAccess.expression().annotation().isPure;
}
}
else if (exprType->category() == Type::Category::Module)
annotation.isPure = _memberAccess.expression().annotation().isPure;
{
annotation.isPure = *_memberAccess.expression().annotation().isPure;
annotation.isLValue = false;
}
else
annotation.isLValue = false;
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this limited for now.
@@ -2694,11 +2722,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
)
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
annotation.isPure = parentAccess->expression().annotation().isPure;
bool isPure = *parentAccess->expression().annotation().isPure;
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
annotation.isPure = true;
isPure = true;
annotation.isPure = isPure;
}
if (auto magicType = dynamic_cast<MagicType const*>(exprType))
{
if (magicType->kind() == MagicType::Kind::ABI)
@@ -2746,16 +2777,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.isPure = true;
}
if (!annotation.isPure.set())
annotation.isPure = false;
return false;
}
bool TypeChecker::visit(IndexAccess const& _access)
{
_access.annotation().isConstant = false;
_access.baseExpression().accept(*this);
TypePointer baseType = type(_access.baseExpression());
TypePointer resultType = nullptr;
bool isLValue = false;
bool isPure = _access.baseExpression().annotation().isPure;
bool isPure = *_access.baseExpression().annotation().isPure;
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
@@ -2857,7 +2892,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
}
_access.annotation().type = resultType;
_access.annotation().isLValue = isLValue;
if (index && !index->annotation().isPure)
if (index && !*index->annotation().isPure)
isPure = false;
_access.annotation().isPure = isPure;
@@ -2866,21 +2901,22 @@ bool TypeChecker::visit(IndexAccess const& _access)
bool TypeChecker::visit(IndexRangeAccess const& _access)
{
_access.annotation().isConstant = false;
_access.baseExpression().accept(*this);
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
bool isPure = _access.baseExpression().annotation().isPure;
bool isPure = *_access.baseExpression().annotation().isPure;
if (Expression const* start = _access.startExpression())
{
expectType(*start, *TypeProvider::uint256());
if (!start->annotation().isPure)
if (!*start->annotation().isPure)
isPure = false;
}
if (Expression const* end = _access.endExpression())
{
expectType(*end, *TypeProvider::uint256());
if (!end->annotation().isPure)
if (!*end->annotation().isPure)
isPure = false;
}
@@ -3023,20 +3059,22 @@ bool TypeChecker::visit(Identifier const& _identifier)
!!annotation.referencedDeclaration,
"Referenced declaration is null after overload resolution."
);
bool isConstant = false;
annotation.isLValue = annotation.referencedDeclaration->isLValue();
annotation.type = annotation.referencedDeclaration->type();
solAssert(annotation.type, "Declaration referenced before type could be determined.");
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
annotation.isPure = isConstant = variableDeclaration->isConstant();
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
{
if (dynamic_cast<FunctionType const*>(annotation.type))
annotation.isPure = true;
}
annotation.isPure = dynamic_cast<FunctionType const*>(annotation.type);
else if (dynamic_cast<TypeType const*>(annotation.type))
annotation.isPure = true;
else if (dynamic_cast<ModuleType const*>(annotation.type))
annotation.isPure = true;
else
annotation.isPure = false;
annotation.isConstant = isConstant;
// Check for deprecated function names.
// The check is done here for the case without an actual function call.
@@ -3077,6 +3115,8 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
_expr.annotation().isPure = true;
_expr.annotation().isLValue = false;
_expr.annotation().isConstant = false;
}
void TypeChecker::endVisit(Literal const& _literal)
@@ -3132,6 +3172,8 @@ void TypeChecker::endVisit(Literal const& _literal)
m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value.");
_literal.annotation().isPure = true;
_literal.annotation().isLValue = false;
_literal.annotation().isConstant = false;
}
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
@@ -3216,11 +3258,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);
if (_expression.annotation().isLValue)
if (*_expression.annotation().isLValue)
return;
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
if (_expression.annotation().isConstant)
if (*_expression.annotation().isConstant)
return { 6520_error, "Cannot assign to a constant variable." };
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
+1 -1
View File
@@ -492,7 +492,7 @@ CallableDeclaration const* Scopable::functionOrModifierDefinition() const
string Scopable::sourceUnitName() const
{
return sourceUnit().annotation().path;
return *sourceUnit().annotation().path;
}
DeclarationAnnotation& Declaration::annotation() const
+10 -9
View File
@@ -47,6 +47,7 @@ namespace solidity::frontend
class Type;
using TypePointer = Type const*;
using namespace util;
struct ASTAnnotation
{
@@ -88,9 +89,9 @@ struct StructurallyDocumentedAnnotation
struct SourceUnitAnnotation: ASTAnnotation
{
/// The "absolute" (in the compiler sense) path of this source unit.
std::string path;
SetOnce<std::string> path;
/// The exported symbols (all global symbols).
std::map<ASTString, std::vector<Declaration const*>> exportedSymbols;
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
/// Experimental features.
std::set<ExperimentalFeature> experimentalFeatures;
};
@@ -122,7 +123,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
struct ImportAnnotation: DeclarationAnnotation
{
/// The absolute path of the source unit to import.
std::string absolutePath;
SetOnce<std::string> absolutePath;
/// The actual source unit.
SourceUnit const* sourceUnit = nullptr;
};
@@ -130,7 +131,7 @@ struct ImportAnnotation: DeclarationAnnotation
struct TypeDeclarationAnnotation: DeclarationAnnotation
{
/// The name of this type, prefixed by proper namespaces if globally accessible.
std::string canonicalName;
SetOnce<std::string> canonicalName;
};
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
@@ -149,7 +150,7 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// List of functions and modifiers without a body. Can also contain functions from base classes.
std::vector<Declaration const*> unimplementedDeclarations;
std::optional<std::vector<Declaration const*>> unimplementedDeclarations;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
@@ -243,16 +244,16 @@ struct ExpressionAnnotation: ASTAnnotation
/// Inferred type of the expression.
TypePointer type = nullptr;
/// Whether the expression is a constant variable
bool isConstant = false;
SetOnce<bool> isConstant;
/// Whether the expression is pure, i.e. compile-time constant.
bool isPure = false;
SetOnce<bool> isPure;
/// Whether it is an LValue (i.e. something that can be assigned to).
bool isLValue = false;
SetOnce<bool> isLValue;
/// Whether the expression is used in a context where the LValue is actually required.
bool willBeWrittenTo = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
SetOnce<bool> lValueOfOrdinaryAssignment;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resolution
+72 -28
View File
@@ -39,10 +39,33 @@
#include <vector>
#include <algorithm>
#include <limits>
#include <type_traits>
using namespace std;
using namespace solidity::langutil;
namespace
{
template<typename V, template<typename> typename C>
void addIfSet(std::vector<pair<string, Json::Value>>& _attributes, string const& _name, C<V> const& _value)
{
if constexpr (std::is_same_v<C<V>, solidity::util::SetOnce<V>>)
{
if (!_value.set())
return;
}
else if constexpr (std::is_same_v<C<V>, optional<V>>)
{
if (!_value.has_value())
return;
}
_attributes.emplace_back(_name, *_value);
}
}
namespace solidity::frontend
{
@@ -181,12 +204,14 @@ void ASTJsonConverter::appendExpressionAttributes(
{
std::vector<pair<string, Json::Value>> exprAttributes = {
make_pair("typeDescriptions", typePointerToJson(_annotation.type)),
make_pair("isConstant", _annotation.isConstant),
make_pair("isPure", _annotation.isPure),
make_pair("isLValue", _annotation.isLValue),
make_pair("lValueRequested", _annotation.willBeWrittenTo),
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
};
addIfSet(exprAttributes, "isLValue", _annotation.isLValue);
addIfSet(exprAttributes, "isPure", _annotation.isPure);
addIfSet(exprAttributes, "isConstant", _annotation.isConstant);
_attributes += exprAttributes;
}
@@ -214,23 +239,27 @@ Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
bool ASTJsonConverter::visit(SourceUnit const& _node)
{
Json::Value exportedSymbols = Json::objectValue;
for (auto const& sym: _node.annotation().exportedSymbols)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
make_pair("nodes", toJson(_node.nodes()))
};
if (_node.annotation().exportedSymbols.set())
{
exportedSymbols[sym.first] = Json::arrayValue;
for (Declaration const* overload: sym.second)
exportedSymbols[sym.first].append(nodeId(*overload));
}
setJsonNode(
_node,
"SourceUnit",
Json::Value exportedSymbols = Json::objectValue;
for (auto const& sym: *_node.annotation().exportedSymbols)
{
make_pair("absolutePath", _node.annotation().path),
make_pair("exportedSymbols", move(exportedSymbols)),
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
make_pair("nodes", toJson(_node.nodes()))
exportedSymbols[sym.first] = Json::arrayValue;
for (Declaration const* overload: sym.second)
exportedSymbols[sym.first].append(nodeId(*overload));
}
);
attributes.emplace_back("exportedSymbols", exportedSymbols);
};
addIfSet(attributes, "absolutePath", _node.annotation().path);
setJsonNode(_node, "SourceUnit", std::move(attributes));
return false;
}
@@ -249,10 +278,12 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("file", _node.path()),
make_pair("absolutePath", _node.annotation().absolutePath),
make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)),
make_pair("scope", idOrNull(_node.scope()))
};
addIfSet(attributes, "absolutePath", _node.annotation().absolutePath);
attributes.emplace_back("unitAlias", _node.name());
Json::Value symbolAliases(Json::arrayValue);
for (auto const& symbolAlias: _node.symbolAliases())
@@ -270,18 +301,23 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
setJsonNode(_node, "ContractDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
make_pair("nodes", toJson(_node.subNodes())),
make_pair("scope", idOrNull(_node.scope()))
});
};
if (_node.annotation().unimplementedDeclarations.has_value())
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
if (!_node.annotation().linearizedBaseContracts.empty())
attributes.emplace_back("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts));
setJsonNode(_node, "ContractDefinition", std::move(attributes));
return false;
}
@@ -305,23 +341,31 @@ bool ASTJsonConverter::visit(UsingForDirective const& _node)
bool ASTJsonConverter::visit(StructDefinition const& _node)
{
setJsonNode(_node, "StructDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("canonicalName", _node.annotation().canonicalName),
make_pair("members", toJson(_node.members())),
make_pair("scope", idOrNull(_node.scope()))
});
};
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
setJsonNode(_node, "StructDefinition", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(EnumDefinition const& _node)
{
setJsonNode(_node, "EnumDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("canonicalName", _node.annotation().canonicalName),
make_pair("members", toJson(_node.members()))
});
};
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
setJsonNode(_node, "EnumDefinition", std::move(attributes));
return false;
}
+39 -71
View File
@@ -44,6 +44,7 @@
#include <boost/range/algorithm/copy.hpp>
#include <limits>
#include <unordered_set>
#include <utility>
using namespace std;
@@ -54,57 +55,6 @@ using namespace solidity::frontend;
namespace
{
struct TypeComp
{
bool operator()(Type const* lhs, Type const* rhs) const
{
solAssert(lhs && rhs, "");
return lhs->richIdentifier() < rhs->richIdentifier();
}
};
using TypeSet = std::set<Type const*, TypeComp>;
void oversizedSubtypesInner(
Type const& _type,
bool _includeType,
set<StructDefinition const*>& _structsSeen,
TypeSet& _oversizedSubtypes
)
{
switch (_type.category())
{
case Type::Category::Array:
{
auto const& t = dynamic_cast<ArrayType const&>(_type);
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes);
break;
}
case Type::Category::Struct:
{
auto const& t = dynamic_cast<StructType const&>(_type);
if (_structsSeen.count(&t.structDefinition()))
return;
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t);
_structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr))
oversizedSubtypesInner(*m.type, false, _structsSeen, _oversizedSubtypes);
_structsSeen.erase(&t.structDefinition());
break;
}
case Type::Category::Mapping:
{
auto const* valueType = dynamic_cast<MappingType const&>(_type).valueType();
oversizedSubtypesInner(*valueType, true, _structsSeen, _oversizedSubtypes);
break;
}
default:
break;
}
}
/// Check whether (_base ** _exp) fits into 4096 bits.
bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
{
@@ -201,16 +151,6 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
}
vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type)
{
set<StructDefinition const*> structsSeen;
TypeSet oversized;
oversizedSubtypesInner(_type, true, structsSeen, oversized);
vector<frontend::Type const*> res;
copy(oversized.cbegin(), oversized.cend(), back_inserter(res));
return res;
}
void Type::clearCache() const
{
m_members.clear();
@@ -404,10 +344,16 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
return encodingType;
TypePointer baseType = encodingType;
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType))
{
baseType = arrayType->baseType();
if (dynamic_cast<StructType const*>(baseType))
if (!_encoderV2)
auto const* baseArrayType = dynamic_cast<ArrayType const*>(baseType);
if (!_encoderV2 && baseArrayType && baseArrayType->isDynamicallySized())
return nullptr;
}
if (!_encoderV2 && dynamic_cast<StructType const*>(baseType))
return nullptr;
return encodingType;
}
@@ -1613,6 +1559,21 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const
return nullptr;
}
vector<Type const*> CompositeType::fullDecomposition() const
{
vector<Type const*> res = {this};
unordered_set<string> seen = {richIdentifier()};
for (size_t k = 0; k < res.size(); ++k)
if (auto composite = dynamic_cast<CompositeType const*>(res[k]))
for (Type const* next: composite->decomposition())
if (seen.count(next->richIdentifier()) == 0)
{
seen.insert(next->richIdentifier());
res.push_back(next);
}
return res;
}
Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const
{
return TypeProvider::withLocation(this, _location, _isPointer);
@@ -2155,7 +2116,7 @@ string ContractType::toString(bool) const
string ContractType::canonicalName() const
{
return m_contract.annotation().canonicalName;
return *m_contract.annotation().canonicalName;
}
MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const
@@ -2406,7 +2367,7 @@ bool StructType::containsNestedMapping() const
string StructType::toString(bool _short) const
{
string ret = "struct " + m_struct.annotation().canonicalName;
string ret = "struct " + *m_struct.annotation().canonicalName;
if (!_short)
ret += " " + stringForReferencePart();
return ret;
@@ -2585,7 +2546,7 @@ string StructType::signatureInExternalFunction(bool _structsByName) const
string StructType::canonicalName() const
{
return m_struct.annotation().canonicalName;
return *m_struct.annotation().canonicalName;
}
FunctionTypePointer StructType::constructorType() const
@@ -2650,6 +2611,13 @@ vector<tuple<string, TypePointer>> StructType::makeStackItems() const
solAssert(false, "");
}
vector<Type const*> StructType::decomposition() const
{
vector<Type const*> res;
for (MemberList::Member const& member: members(nullptr))
res.push_back(member.type);
return res;
}
TypePointer EnumType::encodingType() const
{
@@ -2685,12 +2653,12 @@ unsigned EnumType::storageBytes() const
string EnumType::toString(bool) const
{
return string("enum ") + m_enum.annotation().canonicalName;
return string("enum ") + *m_enum.annotation().canonicalName;
}
string EnumType::canonicalName() const
{
return m_enum.annotation().canonicalName;
return *m_enum.annotation().canonicalName;
}
size_t EnumType::numberOfMembers() const
@@ -3163,7 +3131,7 @@ string FunctionType::toString(bool _short) const
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
if (auto const* contract = dynamic_cast<ContractDefinition const*>(functionDefinition->scope()))
name += contract->annotation().canonicalName + ".";
name += *contract->annotation().canonicalName + ".";
name += functionDefinition->name();
}
name += '(';
@@ -3954,7 +3922,7 @@ bool ModuleType::operator==(Type const& _other) const
MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
{
MemberList::MemberMap symbols;
for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols)
for (auto const& symbolName: *m_sourceUnit.annotation().exportedSymbols)
for (Declaration const* symbol: symbolName.second)
symbols.emplace_back(symbolName.first, symbol->type(), symbol);
return symbols;
@@ -3962,7 +3930,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
string ModuleType::toString(bool) const
{
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
return string("module \"") + *m_sourceUnit.annotation().path + string("\"");
}
string MagicType::richIdentifier() const
+48 -5
View File
@@ -60,8 +60,6 @@ using BoolResult = util::Result<bool>;
namespace solidity::frontend
{
std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type);
inline rational makeRational(bigint const& _numerator, bigint const& _denominator)
{
solAssert(_denominator != 0, "division by zero");
@@ -694,11 +692,37 @@ public:
TypeResult interfaceType(bool) const override { return this; }
};
/**
* Base class for types which can be thought of as several elements of other types put together.
* For example a struct is composed of its members, an array is composed of multiple copies of its
* base element and a mapping is composed of its value type elements (note that keys are not
* stored anywhere).
*/
class CompositeType: public Type
{
protected:
CompositeType() = default;
public:
/// @returns a list containing the type itself, elements of its decomposition,
/// elements of decomposition of these elements and so on, up to non-composite types.
/// Each type is included only once.
std::vector<Type const*> fullDecomposition() const;
protected:
/// @returns a list of types that together make up the data part of this type.
/// Contains all types that will have to be implicitly stored, whenever an object of this type is stored.
/// In particular, it returns the base type for arrays and array slices, the member types for structs,
/// the component types for tuples and the value type for mappings
/// (note that the key type of a mapping is *not* part of the list).
virtual std::vector<Type const*> decomposition() const = 0;
};
/**
* Base class used by types which are not value types and can be stored either in storage, memory
* or calldata. This is currently used by arrays and structs.
*/
class ReferenceType: public Type
class ReferenceType: public CompositeType
{
protected:
explicit ReferenceType(DataLocation _location): m_location(_location) {}
@@ -829,6 +853,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override { return {m_baseType}; }
private:
/// String is interpreted as a subtype of Bytes.
enum class ArrayKind { Ordinary, Bytes, String };
@@ -869,6 +895,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override { return {m_arrayType.baseType()}; }
private:
ArrayType const& m_arrayType;
};
@@ -994,6 +1022,8 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override;
private:
StructDefinition const& m_struct;
// Caches for interfaceType(bool)
@@ -1044,7 +1074,7 @@ private:
* Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders).
*/
class TupleType: public Type
class TupleType: public CompositeType
{
public:
explicit TupleType(std::vector<TypePointer> _types = {}): m_components(std::move(_types)) {}
@@ -1067,6 +1097,16 @@ public:
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
std::vector<Type const*> decomposition() const override
{
// Currently calling TupleType::decomposition() is not expected, because we cannot declare a variable of a tuple type.
// If that changes, before removing the solAssert, make sure the function does the right thing and is used properly.
// Note that different tuple members can have different data locations, so using decomposition() to check
// the tuple validity for a data location might require special care.
solUnimplemented("Tuple decomposition is not expected.");
return m_components;
}
private:
std::vector<TypePointer> const m_components;
};
@@ -1349,7 +1389,7 @@ private:
* The type of a mapping, there is one distinct type per key/value type pair.
* Mappings always occupy their own storage slot, but do not actually use it.
*/
class MappingType: public Type
class MappingType: public CompositeType
{
public:
MappingType(Type const* _keyType, Type const* _valueType):
@@ -1373,6 +1413,9 @@ public:
Type const* keyType() const { return m_keyType; }
Type const* valueType() const { return m_valueType; }
protected:
std::vector<Type const*> decomposition() const override { return {m_valueType}; }
private:
TypePointer m_keyType;
TypePointer m_valueType;
+2 -2
View File
@@ -779,7 +779,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(function.parameterTypes().size() == 1, "");
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.front()->annotation().isPure)
if (!*arguments.front()->annotation().isPure)
{
arguments.front()->accept(*this);
utils().popStackElement(*arguments.front()->annotation().type);
@@ -1078,7 +1078,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(function.kind() == FunctionType::Kind::Require, "");
if (m_context.revertStrings() == RevertStrings::Strip)
{
if (!arguments.at(1)->annotation().isPure)
if (!*arguments.at(1)->annotation().isPure)
{
arguments.at(1)->accept(*this);
utils().popStackElement(*arguments.at(1)->annotation().type);
@@ -142,6 +142,9 @@ public:
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
bool inlineAssemblySeen() const { return m_inlineAssemblySeen; }
void setInlineAssemblySeen() { m_inlineAssemblySeen = true; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
@@ -159,6 +162,9 @@ private:
MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0;
/// Flag indicating whether any inline assembly block was seen.
bool m_inlineAssemblySeen = false;
/// Function definitions queued for code generation. They're the Solidity functions whose calls
/// were discovered by the IR generator during AST traversal.
/// Note that the queue gets filled in a lazy way - new definitions can be added while the
+20 -7
View File
@@ -92,7 +92,7 @@ string IRGenerator::generate(
Whiskers t(R"(
object "<CreationObject>" {
code {
<memoryInit>
<memoryInitCreation>
<callValueCheck>
<?notLibrary>
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
@@ -103,7 +103,7 @@ string IRGenerator::generate(
}
object "<RuntimeObject>" {
code {
<memoryInit>
<memoryInitRuntime>
<dispatch>
<runtimeFunctions>
}
@@ -118,7 +118,6 @@ string IRGenerator::generate(
m_context.registerImmutableVariable(*var);
t("CreationObject", IRNames::creationObject(_contract));
t("memoryInit", memoryInit());
t("notLibrary", !_contract.isLibrary());
FunctionDefinition const* constructor = _contract.constructor();
@@ -144,6 +143,10 @@ string IRGenerator::generate(
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
// This has to be called only after all other code generation for the creation object is complete.
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
resetContext(_contract);
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
@@ -158,6 +161,10 @@ string IRGenerator::generate(
generateInternalDispatchFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
// This has to be called only after all other code generation for the runtime object is complete.
bool runtimeInvolvesAssembly = m_context.inlineAssemblySeen();
t("memoryInitRuntime", memoryInit(!runtimeInvolvesAssembly));
return t.render();
}
@@ -651,16 +658,22 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
return t.render();
}
string IRGenerator::memoryInit()
string IRGenerator::memoryInit(bool _useMemoryGuard)
{
// This function should be called at the beginning of the EVM call frame
// and thus can assume all memory to be zero, including the contents of
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
return
Whiskers{"mstore(<memPtr>, <freeMemoryStart>)"}
Whiskers{
_useMemoryGuard ?
"mstore(<memPtr>, memoryguard(<freeMemoryStart>))" :
"mstore(<memPtr>, <freeMemoryStart>)"
}
("memPtr", to_string(CompilerUtils::freeMemoryPointer))
("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()))
.render();
(
"freeMemoryStart",
to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())
).render();
}
void IRGenerator::resetContext(ContractDefinition const& _contract)
+3 -1
View File
@@ -100,7 +100,9 @@ private:
std::string dispatchRoutine(ContractDefinition const& _contract);
std::string memoryInit();
/// @a _useMemoryGuard If true, use a memory guard, allowing the optimiser
/// to perform memory optimizations.
std::string memoryInit(bool _useMemoryGuard);
void resetContext(ContractDefinition const& _contract);
@@ -1858,6 +1858,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
{
setLocation(_inlineAsm);
m_context.setInlineAssemblySeen();
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
yul::Statement modified = bodyCopier(_inlineAsm.operations());
+9 -2
View File
@@ -792,12 +792,19 @@ void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
auto argument = _funCall.arguments().front();
unsigned argSize = argument->annotation().type->storageBytes();
unsigned castSize = _funCall.annotation().type->storageBytes();
if (argSize == castSize)
auto const& funCallCategory = _funCall.annotation().type->category();
// Allow casting number literals to address.
// TODO: remove the isNegative() check once the type checker disallows this
if (
auto const* numberType = dynamic_cast<RationalNumberType const*>(argument->annotation().type);
numberType && !numberType->isNegative() && (funCallCategory == Type::Category::Address)
)
defineExpr(_funCall, numberType->literalValue(nullptr));
else if (argSize == castSize)
defineExpr(_funCall, expr(*argument));
else
{
m_context.setUnknownValue(*m_context.expression(_funCall));
auto const& funCallCategory = _funCall.annotation().type->category();
// TODO: truncating and bytesX needs a different approach because of right padding.
if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address)
{
+4 -4
View File
@@ -1097,7 +1097,7 @@ void CompilerStack::resolveImports()
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
string const& path = import->annotation().absolutePath;
string const& path = *import->annotation().absolutePath;
solAssert(m_sources.count(path), "");
import->annotation().sourceUnit = m_sources[path].ast.get();
toposort(&m_sources[path]);
@@ -1295,9 +1295,9 @@ string CompilerStack::createMetadata(Contract const& _contract) const
/// All the source files (including self), which should be included in the metadata.
set<string> referencedSources;
referencedSources.insert(_contract.contract->sourceUnit().annotation().path);
referencedSources.insert(*_contract.contract->sourceUnit().annotation().path);
for (auto const sourceUnit: _contract.contract->sourceUnit().referencedSourceUnits(true))
referencedSources.insert(sourceUnit->annotation().path);
referencedSources.insert(*sourceUnit->annotation().path);
meta["sources"] = Json::objectValue;
for (auto const& s: m_sources)
@@ -1363,7 +1363,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
meta["settings"]["evmVersion"] = m_evmVersion.name();
meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] =
_contract.contract->annotation().canonicalName;
*_contract.contract->annotation().canonicalName;
meta["settings"]["remappings"] = Json::arrayValue;
set<string> remappings;