Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth
2020-11-17 18:51:57 +01:00
141 changed files with 2354 additions and 1203 deletions
+41 -10
View File
@@ -36,21 +36,52 @@ using namespace solidity::frontend;
namespace
{
void copyMissingTags(StructurallyDocumentedAnnotation& _target, set<CallableDeclaration const*> const& _baseFunctions)
void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, StructurallyDocumentedAnnotation& _target, CallableDeclaration const* _declaration = nullptr)
{
// Only copy if there is exactly one direct base function.
if (_baseFunctions.size() != 1)
return;
auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>((*_baseFunctions.begin())->annotation());
set<string> existingTags;
for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();)
{
string const& tag = it->first;
// Don't copy tag "inheritdoc" or already existing tags
if (tag == "inheritdoc" || _target.docTags.count(tag))
{
it++;
continue;
}
for (auto const& iterator: _target.docTags)
existingTags.insert(iterator.first);
size_t n = 0;
// Iterate over all values of the current tag (it's a multimap)
for (auto next = sourceDoc.docTags.upper_bound(tag); it != next; it++, n++)
{
DocTag content = it->second;
// Update the parameter name for @return tags
if (_declaration && tag == "return")
{
size_t docParaNameEndPos = content.content.find_first_of(" \t");
string const docParameterName = content.content.substr(0, docParaNameEndPos);
if (docParameterName != _declaration->returnParameters().at(n)->name())
{
bool baseHasNoName = (*_baseFunctions.begin())->returnParameters().at(n)->name().empty();
string paramName = _declaration->returnParameters().at(n)->name();
content.content =
(paramName.empty() ? "" : std::move(paramName) + " ") + (
string::npos == docParaNameEndPos || baseHasNoName ?
content.content :
content.content.substr(docParaNameEndPos + 1)
);
}
}
for (auto const& [tag, content]: sourceDoc.docTags)
if (tag != "inheritdoc" && !existingTags.count(tag))
_target.docTags.emplace(tag, content);
}
}
}
CallableDeclaration const* findBaseCallable(set<CallableDeclaration const*> const& _baseFunctions, int64_t _contractId)
@@ -91,9 +122,9 @@ bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
return false;
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
copyMissingTags(_variable.annotation(), {baseFunction});
copyMissingTags({baseFunction}, _variable.annotation());
else if (_variable.annotation().docTags.empty())
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation());
return false;
}
@@ -119,13 +150,13 @@ void DocStringAnalyser::handleCallable(
)
{
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
copyMissingTags(_annotation, {baseFunction});
copyMissingTags({baseFunction}, _annotation, &_callable);
else if (
_annotation.docTags.empty() &&
_callable.annotation().baseFunctions.size() == 1 &&
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
)
copyMissingTags(_annotation, _callable.annotation().baseFunctions);
copyMissingTags(_callable.annotation().baseFunctions, _annotation, &_callable);
}
CallableDeclaration const* DocStringAnalyser::resolveInheritDoc(
+249 -131
View File
@@ -449,6 +449,36 @@ string YulUtilFunctions::maskBytesFunctionDynamic()
});
}
string YulUtilFunctions::maskLowerOrderBytesFunction(size_t _bytes)
{
string functionName = "mask_lower_order_bytes_" + to_string(_bytes);
solAssert(_bytes <= 32, "");
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data) -> result {
result := and(data, <mask>)
})")
("functionName", functionName)
("mask", formatNumber((~u256(0)) >> (256 - 8 * _bytes)))
.render();
});
}
string YulUtilFunctions::maskLowerOrderBytesFunctionDynamic()
{
string functionName = "mask_lower_order_bytes_dynamic";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(data, bytes) -> result {
let mask := not(<shl>(mul(8, bytes), not(0)))
result := and(data, mask)
})")
("functionName", functionName)
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::roundUpFunction()
{
string functionName = "round_up_to_mul_of_32";
@@ -1342,7 +1372,6 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
@@ -1568,23 +1597,20 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
);
if (_fromType.isByteArray())
return copyByteArrayToStorageFunction(_fromType, _toType);
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
if (_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType())
return copyValueArrayStorageToStorageFunction(_fromType, _toType);
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>) {
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
let length := <arrayLength>(value<?isFromDynamicCalldata>, len</isFromDynamicCalldata>)
<?isToDynamic>
<resizeArray>(slot, length)
</isToDynamic>
let srcPtr :=
<?isFromMemoryDynamic>
add(value, 0x20)
<!isFromMemoryDynamic>
value
</isFromMemoryDynamic>
let srcPtr := <srcDataLocation>(value)
let elementSlot := <dstDataLocation>(slot)
let elementOffset := 0
@@ -1607,9 +1633,13 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
let <elementValues> := <readFromCalldataOrMemory>(srcPtr)
</fromMemory>
<updateStorageValue>(elementSlot<?isValueType>, elementOffset</isValueType>, <elementValues>)
<?fromStorage>
let <elementValues> := srcPtr
</fromStorage>
srcPtr := add(srcPtr, <stride>)
<updateStorageValue>(elementSlot, elementOffset, <elementValues>)
srcPtr := add(srcPtr, <srcStride>)
<?multipleItemsPerSlot>
elementOffset := add(elementOffset, <storageStride>)
@@ -1619,18 +1649,21 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
}
<!multipleItemsPerSlot>
elementSlot := add(elementSlot, <storageSize>)
elementOffset := 0
</multipleItemsPerSlot>
}
}
)");
if (_fromType.dataStoredIn(DataLocation::Storage))
solAssert(!_fromType.isValueType(), "");
templ("functionName", functionName);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("isFromDynamicCalldata", _fromType.isDynamicallySized() && fromCalldata);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromStorage", _fromType.dataStoredIn(DataLocation::Storage));
bool fromMemory = _fromType.dataStoredIn(DataLocation::Memory);
templ("fromMemory", fromMemory);
templ("fromCalldata", fromCalldata);
templ("isToDynamic", _toType.isDynamicallySized());
templ("isFromMemoryDynamic", _fromType.isDynamicallySized() && _fromType.dataStoredIn(DataLocation::Memory));
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
if (fromCalldata)
{
templ("dynamicallySizedBase", _fromType.baseType()->isDynamicallySized());
@@ -1643,7 +1676,7 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
templ("arrayLength",arrayLengthFunction(_fromType));
templ("isValueType", _fromType.baseType()->isValueType());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
if (!fromCalldata || _fromType.baseType()->isValueType())
if (fromMemory || (fromCalldata && _fromType.baseType()->isValueType()))
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*_fromType.baseType(), fromCalldata));
templ("elementValues", suffixedVariableNameList(
"elementValue_",
@@ -1651,7 +1684,13 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
_fromType.baseType()->stackItems().size()
));
templ("updateStorageValue", updateStorageValueFunction(*_fromType.baseType(), *_toType.baseType()));
templ("stride", to_string(fromCalldata ? _fromType.calldataStride() : _fromType.memoryStride()));
templ("srcStride",
fromCalldata ?
to_string(_fromType.calldataStride()) :
fromMemory ?
to_string(_fromType.memoryStride()) :
formatNumber(_fromType.baseType()->storageSize())
);
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
templ("storageStride", to_string(_toType.storageStride()));
templ("storageSize", _toType.baseType()->storageSize().str());
@@ -1669,12 +1708,13 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
);
solAssert(_fromType.isByteArray(), "");
solAssert(_toType.isByteArray(), "");
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) {
<?fromStorage> if eq(slot, src) { leave } </fromStorage>
let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>)
// Make sure array length is sane
if gt(newLen, 0xffffffffffffffff) { <panic>() }
@@ -1703,11 +1743,11 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
let dstPtr := dstDataArea
let i := 0
for { } lt(i, loopEnd) { i := add(i, 32) } {
sstore(dstPtr, <readFromCalldataOrMemory>(add(src, i)))
sstore(dstPtr, <read>(add(src, i)))
dstPtr := add(dstPtr, 1)
}
if lt(loopEnd, newLen) {
let lastValue := <readFromCalldataOrMemory>(add(src, i))
let lastValue := <read>(add(src, i))
sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f)))
}
sstore(slot, add(mul(newLen, 2), 1))
@@ -1715,13 +1755,15 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
default {
let value := 0
if newLen {
value := <readFromCalldataOrMemory>(src)
value := <read>(src)
}
sstore(slot, <byteArrayCombineShort>(value, newLen))
}
}
)");
templ("functionName", functionName);
bool fromStorage = _fromType.dataStoredIn(DataLocation::Storage);
templ("fromStorage", fromStorage);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata);
@@ -1730,7 +1772,7 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
templ("byteArrayLength", extractByteArrayLengthFunction());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType()));
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*TypeProvider::uint256(), fromCalldata));
templ("read", fromStorage ? "sload" : fromCalldata ? "calldataload" : "mload");
templ("maskBytes", maskBytesFunctionDynamic());
templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction());
@@ -1738,6 +1780,65 @@ string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromTy
});
}
string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solAssert(!_fromType.isByteArray(), "");
solAssert(_fromType.dataStoredIn(DataLocation::Storage) && _toType.baseType()->isValueType(), "");
solAssert(_toType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(dst, src) {
if eq(dst, src) { leave }
let length := <arrayLength>(src)
// Make sure array length is sane
if gt(length, 0xffffffffffffffff) { <panic>() }
<?isToDynamic>
<resizeArray>(dst, length)
</isToDynamic>
let srcPtr := <srcDataLocation>(src)
let dstPtr := <dstDataLocation>(dst)
let fullSlots := div(length, <itemsPerSlot>)
let i := 0
for { } lt(i, fullSlots) { i := add(i, 1) } {
sstore(add(dstPtr, i), <maskFull>(sload(add(srcPtr, i))))
}
let spill := sub(length, mul(i, <itemsPerSlot>))
if gt(spill, 0) {
sstore(add(dstPtr, i), <maskBytes>(sload(add(srcPtr, i)), mul(spill, <bytesPerItem>)))
}
}
)");
if (_fromType.dataStoredIn(DataLocation::Storage))
solAssert(!_fromType.isValueType(), "");
templ("functionName", functionName);
templ("isToDynamic", _toType.isDynamicallySized());
if (_toType.isDynamicallySized())
templ("resizeArray", resizeDynamicArrayFunction(_toType));
templ("arrayLength",arrayLengthFunction(_fromType));
templ("panic", panicFunction());
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
templ("dstDataLocation", arrayDataAreaFunction(_toType));
unsigned itemsPerSlot = 32 / _toType.storageStride();
templ("itemsPerSlot", to_string(itemsPerSlot));
templ("bytesPerItem", to_string(_toType.storageStride()));
templ("maskFull", maskLowerOrderBytesFunction(itemsPerSlot * _toType.storageStride()));
templ("maskBytes", maskLowerOrderBytesFunctionDynamic());
return templ.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
@@ -2298,132 +2399,149 @@ string YulUtilFunctions::updateStorageValueFunction(
("prepare", prepareStoreFunction(_toType))
.render();
}
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
if (_toType.category() == Type::Category::Array)
{
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<copyArrayToStorage>(slot, <value>)
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction());
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
return templ.render();
}
else
{
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
solAssert(fromReferenceType && toReferenceType, "");
solAssert(*toReferenceType->copyForLocation(
fromReferenceType->location(),
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
solUnimplementedAssert(
fromReferenceType->location() != DataLocation::Storage,
"Copying from storage to storage is not yet implemented."
);
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
solAssert(_toType.category() == Type::Category::Struct, "");
if (_toType.category() == Type::Category::Array)
{
solAssert(_offset.value_or(0) == 0, "");
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(_offset.value_or(0) == 0, "");
Whiskers templ(R"(
function <functionName>(slot, <value>) {
<copyArrayToStorage>(slot, <value>)
Whiskers templ(R"(
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset>value) {
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
<?fromStorage> if eq(slot, value) { leave } </fromStorage>
<#member>
{
<updateMemberCall>
}
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
</member>
}
)");
templ("functionName", functionName);
templ("dynamicOffset", !_offset.has_value());
templ("panic", panicFunction());
templ("fromStorage", fromStructType.dataStoredIn(DataLocation::Storage));
return templ.render();
}
else if (_toType.category() == Type::Category::Struct)
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solAssert(_offset.value_or(0) == 0, "");
Type const& memberType = *structMembers[i].type;
solAssert(memberType.memoryHeadSize() == 32, "");
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
Whiskers templ(R"(
function <functionName>(slot, value) {
<#member>
{
<updateMemberCall>
}
</member>
}
)");
templ("functionName", functionName);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
let memberSrcPtr := add(value, <memberOffset>)
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
MemberList::MemberMap toStructMembers = toStructType.nativeMembers(nullptr);
vector<map<string, string>> memberParams(structMembers.size());
for (size_t i = 0; i < structMembers.size(); ++i)
{
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
Whiskers t(R"(
let memberSlot := add(slot, <memberStorageSlotDiff>)
<?fromCalldata>
<?fromCalldata>
let <memberValues> :=
<?dynamicallyEncodedMember>
let <memberCalldataOffset> := <accessCalldataTail>(value, add(value, <memberOffset>))
<accessCalldataTail>(value, memberSrcPtr)
<!dynamicallyEncodedMember>
let <memberCalldataOffset> := add(value, <memberOffset>)
memberSrcPtr
</dynamicallyEncodedMember>
<?isValueType>
let <memberValues> := <loadFromMemoryOrCalldata>(<memberCalldataOffset>)
<updateMember>(memberSlot, <memberStorageOffset>, <memberValues>)
<!isValueType>
<updateMember>(memberSlot, <memberCalldataOffset>)
</isValueType>
<!fromCalldata>
let memberMemoryOffset := add(value, <memberOffset>)
let <memberValues> := <loadFromMemoryOrCalldata>(memberMemoryOffset)
<updateMember>(memberSlot, <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
</fromCalldata>
)");
t("fromCalldata", fromCalldata);
if (fromCalldata)
{
t("memberCalldataOffset", suffixedVariableNameList(
"memberCalldataOffset_",
0,
structMembers[i].type->stackItems().size()
));
t("dynamicallyEncodedMember", structMembers[i].type->isDynamicallyEncoded());
if (structMembers[i].type->isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(*structMembers[i].type));
}
t("isValueType", structMembers[i].type->isValueType());
t("memberValues", suffixedVariableNameList(
"memberValue_",
0,
structMembers[i].type->stackItems().size()
));
t("hasOffset", structMembers[i].type->isValueType());
t(
"updateMember",
structMembers[i].type->isValueType() ?
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type) :
updateStorageValueFunction(*structMembers[i].type, *toStructMembers[i].type, offset)
);
t("memberStorageSlotDiff", slotDiff.str());
t("memberStorageOffset", to_string(offset));
t(
"memberOffset",
fromCalldata ?
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
);
if (!fromCalldata || structMembers[i].type->isValueType())
t("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata));
memberParams[i]["updateMemberCall"] = t.render();
}
templ("member", memberParams);
<?isValueType>
<memberValues> := <read>(<memberValues>)
</isValueType>
</fromCalldata>
return templ.render();
<?fromMemory>
let <memberValues> := <read>(memberSrcPtr)
</fromMemory>
<?fromStorage>
let <memberValues> :=
<?isValueType>
<read>(memberSrcPtr)
<!isValueType>
memberSrcPtr
</isValueType>
</fromStorage>
<updateStorageValue>(memberSlot, <memberValues>)
)");
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
t("fromCalldata", fromCalldata);
bool fromMemory = fromStructType.location() == DataLocation::Memory;
t("fromMemory", fromMemory);
bool fromStorage = fromStructType.location() == DataLocation::Storage;
t("fromStorage", fromStorage);
t("isValueType", memberType.isValueType());
t("memberValues", suffixedVariableNameList("memberValue_", 0, memberType.stackItems().size()));
t("memberStorageSlotDiff", slotDiff.str());
if (fromCalldata)
{
t("memberOffset", to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)));
t("dynamicallyEncodedMember", memberType.isDynamicallyEncoded());
if (memberType.isDynamicallyEncoded())
t("accessCalldataTail", accessCalldataTailFunction(memberType));
if (memberType.isValueType())
t("read", readFromCalldata(memberType));
}
else if (fromMemory)
{
t("memberOffset", fromStructType.memoryOffsetOfMember(structMembers[i].name).str());
t("read", readFromMemory(memberType));
}
else if (fromStorage)
{
auto [srcSlotOffset, srcOffset] = fromStructType.storageOffsetsOfMember(structMembers[i].name);
t("memberOffset", formatNumber(srcSlotOffset));
if (memberType.isValueType())
t("read", readFromStorageValueType(memberType, srcOffset, false));
else
solAssert(srcOffset == 0, "");
}
t("memberStorageSlotOffset", to_string(offset));
t("updateStorageValue", updateStorageValueFunction(
memberType,
*toStructMembers[i].type,
optional<unsigned>{offset}
));
memberParams[i]["updateMemberCall"] = t.render();
}
else
solAssert(false, "Invalid non-value type for assignment.");
templ("member", memberParams);
return templ.render();
}
});
}
+16 -2
View File
@@ -106,6 +106,15 @@ public:
/// signature: (value, bytes) -> result
std::string maskBytesFunctionDynamic();
/// Zeroes out all bytes above the first ``_bytes`` lower order bytes.
/// signature: (value) -> result
std::string maskLowerOrderBytesFunction(size_t _bytes);
/// Zeroes out all bytes above the first ``bytes`` lower order bytes.
/// @note ``bytes`` has to be small enough not to overflow ``8 * bytes``.
/// signature: (value, bytes) -> result
std::string maskLowerOrderBytesFunctionDynamic();
/// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32.
/// signature: (value) -> result
@@ -209,14 +218,18 @@ public:
/// signature: (slot) ->
std::string clearStorageArrayFunction(ArrayType const& _type);
/// @returns the name of a function that will copy array from calldata or memory to storage
/// @returns the name of a function that will copy an array to storage
/// signature (to_slot, from_ptr) ->
std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
/// @returns the name of a function that will copy a byte array from calldata or memory to storage
/// @returns the name of a function that will copy a byte array to storage
/// signature (to_slot, from_ptr) ->
std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
/// @returns the name of a function that will copy an array of value types from storage to storage.
/// signature (to_slot, from_slot) ->
std::string copyValueArrayStorageToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
/// Returns the name of a function that will convert a given length to the
/// size in memory (number of storage slots or calldata/memory bytes) it
/// will require.
@@ -297,6 +310,7 @@ public:
/// Returns the name of a function will write the given value to
/// the specified slot and offset. If offset is not given, it is expected as
/// runtime parameter.
/// For reference types, offset is checked to be zero at runtime.
/// signature: (slot, [offset,] value)
std::string updateStorageValueFunction(
Type const& _fromType,
@@ -2658,22 +2658,22 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
std::visit(
util::GenericVisitor{
[&](IRLValue::Storage const& _storage) {
std::optional<unsigned> offset;
string offsetArgument;
optional<unsigned> offsetStatic;
if (std::holds_alternative<unsigned>(_storage.offset))
offset = std::get<unsigned>(_storage.offset);
std::visit(GenericVisitor{
[&](unsigned _offset) { offsetStatic = _offset; },
[&](string const& _offset) { offsetArgument = ", " + _offset; }
}, _storage.offset);
m_code <<
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offset) <<
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offsetStatic) <<
"(" <<
_storage.slot <<
(
std::holds_alternative<string>(_storage.offset) ?
(", " + std::get<string>(_storage.offset)) :
""
) <<
offsetArgument <<
_value.commaSeparatedListPrefixed() <<
")\n";
},
[&](IRLValue::Memory const& _memory) {
if (_lvalue.type.isValueType())
+22 -36
View File
@@ -96,20 +96,7 @@ bool BMC::shouldInlineFunctionCall(FunctionCall const& _funCall)
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
if (funType.kind() == FunctionType::Kind::External)
{
auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
if (!memberAccess)
return false;
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
if (!(
identifier &&
identifier->name() == "this" &&
identifier->annotation().referencedDeclaration &&
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
))
return false;
}
return isTrustedExternalCall(&_funCall.expression());
else if (funType.kind() != FunctionType::Kind::Internal)
return false;
@@ -133,7 +120,10 @@ void BMC::endVisit(ContractDefinition const& _contract)
constructor->accept(*this);
else
{
/// Visiting implicit constructor - we need a dummy callstack frame
pushCallStack({nullptr, nullptr});
inlineConstructorHierarchy(_contract);
popCallStack();
/// Check targets created by state variable initialization.
smtutil::Expression constraints = m_context.assertions();
checkVerificationTargets(constraints);
@@ -844,32 +834,28 @@ void BMC::checkCondition(
{
case smtutil::CheckResult::SATISFIABLE:
{
solAssert(!_callStack.empty(), "");
std::ostringstream message;
message << "BMC: " << _description << " happens here.";
if (_callStack.size())
{
std::ostringstream modelMessage;
modelMessage << "Counterexample:\n";
solAssert(values.size() == expressionNames.size(), "");
map<string, string> sortedModel;
for (size_t i = 0; i < values.size(); ++i)
if (expressionsToEvaluate.at(i).name != values.at(i))
sortedModel[expressionNames.at(i)] = values.at(i);
std::ostringstream modelMessage;
modelMessage << "Counterexample:\n";
solAssert(values.size() == expressionNames.size(), "");
map<string, string> sortedModel;
for (size_t i = 0; i < values.size(); ++i)
if (expressionsToEvaluate.at(i).name != values.at(i))
sortedModel[expressionNames.at(i)] = values.at(i);
for (auto const& eval: sortedModel)
modelMessage << " " << eval.first << " = " << eval.second << "\n";
for (auto const& eval: sortedModel)
modelMessage << " " << eval.first << " = " << eval.second << "\n";
m_errorReporter.warning(
_errorHappens,
_location,
message.str(),
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
.append(SMTEncoder::callStackMessage(_callStack))
.append(move(secondaryLocation))
);
}
else
m_errorReporter.warning(6084_error, _location, message.str(), secondaryLocation);
m_errorReporter.warning(
_errorHappens,
_location,
message.str(),
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
.append(SMTEncoder::callStackMessage(_callStack))
.append(move(secondaryLocation))
);
break;
}
case smtutil::CheckResult::UNSATISFIABLE:
+58 -14
View File
@@ -562,8 +562,6 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
m_context.addAssertion(interface(*contract));
}
auto previousError = errorFlag().currentValue();
m_context.addAssertion(predicate(_funCall));
connectBlocks(
@@ -572,8 +570,6 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
(errorFlag().currentValue() > 0)
);
m_context.addAssertion(errorFlag().currentValue() == 0);
errorFlag().increaseIndex();
m_context.addAssertion(errorFlag().currentValue() == previousError);
}
void CHC::externalFunctionCall(FunctionCall const& _funCall)
@@ -583,6 +579,11 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
/// so we just add the nondet_interface predicate.
solAssert(m_currentContract, "");
if (isTrustedExternalCall(&_funCall.expression()))
{
externalFunctionCallToTrustedCode(_funCall);
return;
}
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
@@ -615,6 +616,42 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
m_context.addAssertion(errorFlag().currentValue() == 0);
}
void CHC::externalFunctionCallToTrustedCode(FunctionCall const& _funCall)
{
solAssert(m_currentContract, "");
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
auto const* function = functionCallToDefinition(_funCall);
if (!function)
return;
// External call creates a new transaction.
auto originalTx = state().tx();
auto txOrigin = state().txMember("tx.origin");
state().newTx();
// set the transaction sender as this contract
m_context.addAssertion(state().txMember("msg.sender") == state().thisAddress());
// set the origin to be the current transaction origin
m_context.addAssertion(state().txMember("tx.origin") == txOrigin);
smtutil::Expression pred = predicate(_funCall);
auto txConstraints = m_context.state().txConstraints(*function);
m_context.addAssertion(pred && txConstraints);
// restore the original transaction data
state().newTx();
m_context.addAssertion(originalTx == state().tx());
connectBlocks(
m_currentBlock,
(m_currentFunction && !m_currentFunction->isConstructor()) ? summary(*m_currentFunction) : summary(*m_currentContract),
(errorFlag().currentValue() > 0)
);
m_context.addAssertion(errorFlag().currentValue() == 0);
}
void CHC::unknownFunctionCall(FunctionCall const&)
{
/// Function calls are not handled at the moment,
@@ -1011,27 +1048,34 @@ smtutil::Expression CHC::predicate(Predicate const& _block)
smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
{
/// Used only for internal calls.
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
auto kind = funType.kind();
solAssert(kind == FunctionType::Kind::Internal || kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
auto const* function = functionCallToDefinition(_funCall);
if (!function)
return smtutil::Expression(true);
auto contractAddressValue = [this](FunctionCall const& _f) {
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_f.expression().annotation().type);
if (funType.kind() == FunctionType::Kind::Internal)
return state().thisAddress();
if (MemberAccess const* callBase = dynamic_cast<MemberAccess const*>(&_f.expression()))
return expr(callBase->expression());
solAssert(false, "Unreachable!");
};
errorFlag().increaseIndex();
vector<smtutil::Expression> args{errorFlag().currentValue(), state().thisAddress(), state().crypto(), state().tx(), state().state()};
vector<smtutil::Expression> args{errorFlag().currentValue(), contractAddressValue(_funCall), state().crypto(), state().tx(), state().state()};
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
solAssert(funType.kind() == FunctionType::Kind::Internal, "");
/// Internal calls can be made to the contract itself or a library.
auto const* contract = function->annotation().contract;
auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
solAssert(contract->isLibrary() || find(hierarchy.begin(), hierarchy.end(), contract) != hierarchy.end(), "");
solAssert(kind != FunctionType::Kind::Internal || contract->isLibrary() || contains(hierarchy, contract), "");
/// If the call is to a library, we use that library as the called contract.
/// If it is not, we use the current contract even if it is a call to a contract
/// up in the inheritance hierarchy, since the interfaces/predicates are different.
auto const* calledContract = contract->isLibrary() ? contract : m_currentContract;
/// If the call is to a contract not in the inheritance hierarchy, we also use that as the called contract.
/// Otherwise, the call is to some contract in the inheritance hierarchy of the current contract.
/// In this case we use current contract as the called one since the interfaces/predicates are different.
auto const* calledContract = contains(hierarchy, contract) ? m_currentContract : contract;
solAssert(calledContract, "");
bool usesStaticCall = function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View;
+1
View File
@@ -88,6 +88,7 @@ private:
void visitAddMulMod(FunctionCall const& _funCall) override;
void internalFunctionCall(FunctionCall const& _funCall);
void externalFunctionCall(FunctionCall const& _funCall);
void externalFunctionCallToTrustedCode(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall);
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
/// Creates underflow/overflow verification targets.
+13
View File
@@ -2386,6 +2386,19 @@ MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
return nullptr;
}
bool SMTEncoder::isTrustedExternalCall(Expression const* _expr) {
auto memberAccess = dynamic_cast<MemberAccess const*>(_expr);
if (!memberAccess)
return false;
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
return identifier &&
identifier->name() == "this" &&
identifier->annotation().referencedDeclaration &&
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
;
}
string SMTEncoder::extraComment()
{
string extra;
+4
View File
@@ -295,6 +295,10 @@ protected:
/// otherwise nullptr.
MemberAccess const* isEmptyPush(Expression const& _expr) const;
/// @returns true if the given identifier is a contract which is known and trusted.
/// This means we don't have to abstract away effects of external function calls to this contract.
static bool isTrustedExternalCall(Expression const* _expr);
/// Creates symbolic expressions for the returned values
/// and set them as the components of the symbolic tuple.
void createReturnedExpressions(FunctionCall const& _funCall);
+52 -4
View File
@@ -131,6 +131,13 @@ void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
m_remappings = _remappings;
}
void CompilerStack::setViaIR(bool _viaIR)
{
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set viaIR before parsing."));
m_viaIR = _viaIR;
}
void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
{
if (m_stackState >= ParsedAndImported)
@@ -213,6 +220,7 @@ void CompilerStack::reset(bool _keepSettings)
{
m_remappings.clear();
m_libraries.clear();
m_viaIR = false;
m_evmVersion = langutil::EVMVersion();
m_modelCheckerSettings = ModelCheckerSettings{};
m_enabledSMTSolvers = smtutil::SMTSolverChoice::All();
@@ -532,10 +540,15 @@ bool CompilerStack::compile(State _stopAfter)
{
try
{
if (m_generateEvmBytecode)
compileContract(*contract, otherCompilers);
if (m_generateIR || m_generateEwasm)
if (m_viaIR || m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEvmBytecode)
{
if (m_viaIR)
generateEVMFromIR(*contract);
else
compileContract(*contract, otherCompilers);
}
if (m_generateEwasm)
generateEwasm(*contract);
}
@@ -1250,12 +1263,45 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
}
void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
{
solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEVMFromIR with errors."));
if (!_contract.canBeDeployed())
return;
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
solAssert(!compiledContract.yulIROptimized.empty(), "");
if (!compiledContract.object.bytecode.empty())
return;
// Re-parse the Yul IR in EVM dialect
yul::AssemblyStack stack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
stack.parseAndAnalyze("", compiledContract.yulIROptimized);
stack.optimize();
//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
// TODO: support passing metadata
auto result = stack.assemble(yul::AssemblyStack::Machine::EVM);
compiledContract.object = std::move(*result.bytecode);
// TODO: support runtimeObject
// TODO: add EIP-170 size check for runtimeObject
// TODO: refactor assemblyItems, runtimeAssemblyItems, generatedSources,
// assemblyString, assemblyJSON, and functionEntryPoints to work with this code path
}
void CompilerStack::generateEwasm(ContractDefinition const& _contract)
{
solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEwasm with errors."));
if (!_contract.canBeDeployed())
return;
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
solAssert(!compiledContract.yulIROptimized.empty(), "");
if (!compiledContract.ewasm.empty())
@@ -1392,6 +1438,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const
static vector<string> hashes{"ipfs", "bzzr1", "none"};
meta["settings"]["metadata"]["bytecodeHash"] = hashes.at(unsigned(m_metadataHash));
if (m_viaIR)
meta["settings"]["viaIR"] = m_viaIR;
meta["settings"]["evmVersion"] = m_evmVersion.name();
meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] =
*_contract.contract->annotation().canonicalName;
@@ -1514,7 +1562,7 @@ bytes CompilerStack::createCBORMetadata(Contract const& _contract) const
else
solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash");
if (experimentalMode)
if (experimentalMode || m_viaIR)
encoder.pushBool("experimental", true);
if (m_release)
encoder.pushBytes("solc", VersionCompactBytes);
+10
View File
@@ -162,6 +162,10 @@ public:
m_parserErrorRecovery = _wantErrorRecovery;
}
/// Sets the pipeline to go through the Yul IR or not.
/// Must be set before parsing.
void setViaIR(bool _viaIR);
/// Set the EVM version used before running compile.
/// When called without an argument it will revert to the default version.
/// Must be set before parsing.
@@ -399,7 +403,12 @@ private:
/// The IR is stored but otherwise unused.
void generateIR(ContractDefinition const& _contract);
/// Generate EVM representation for a single contract.
/// Depends on output generated by generateIR.
void generateEVMFromIR(ContractDefinition const& _contract);
/// Generate Ewasm representation for a single contract.
/// Depends on output generated by generateIR.
void generateEwasm(ContractDefinition const& _contract);
/// Links all the known library addresses in the available objects. Any unknown
@@ -455,6 +464,7 @@ private:
OptimiserSettings m_optimiserSettings;
RevertStrings m_revertStrings = RevertStrings::Default;
State m_stopAfter = State::CompilationSuccessful;
bool m_viaIR = false;
langutil::EVMVersion m_evmVersion;
ModelCheckerSettings m_modelCheckerSettings;
smtutil::SMTSolverChoice m_enabledSMTSolvers;
+15 -6
View File
@@ -323,10 +323,12 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
for (auto const& ref: linkReferences)
{
string const& fullname = ref.second;
// If the link reference does not contain a colon, assume that the file name is missing and
// the whole string represents the library name.
size_t colon = fullname.rfind(':');
solAssert(colon != string::npos, "");
string file = fullname.substr(0, colon);
string name = fullname.substr(colon + 1);
string file = (colon != string::npos ? fullname.substr(0, colon) : "");
string name = (colon != string::npos ? fullname.substr(colon + 1) : fullname);
Json::Value fileObject = ret.get(file, Json::objectValue);
Json::Value libraryArray = fileObject.get(name, Json::arrayValue);
@@ -414,7 +416,7 @@ std::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input)
std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
{
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings", "stopAfter"};
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"};
return checkKeys(_input, keys, "settings");
}
@@ -749,6 +751,13 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool();
}
if (settings.isMember("viaIR"))
{
if (!settings["viaIR"].isBool())
return formatFatalError("JSONError", "\"settings.viaIR\" must be a Boolean.");
ret.viaIR = settings["viaIR"].asBool();
}
if (settings.isMember("evmVersion"))
{
if (!settings["evmVersion"].isString())
@@ -830,8 +839,7 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
try
{
// @TODO use libraries only for the given source
ret.libraries[library] = util::h160(address);
ret.libraries[sourceName + ":" + library] = util::h160(address);
}
catch (util::BadHexCharacter const&)
{
@@ -906,6 +914,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
compilerStack.setSources(sourceList);
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
compilerStack.setViaIR(_inputsAndSettings.viaIR);
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery);
compilerStack.setRemappings(_inputsAndSettings.remappings);
+1
View File
@@ -72,6 +72,7 @@ private:
CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS;
Json::Value outputSelection;
ModelCheckerSettings modelCheckerSettings = ModelCheckerSettings{};
bool viaIR = false;
};
/// Parses the input json (and potentially invokes the read callback) and either returns