Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #420 from icon-project/IS-1021-dict-db-iteration-c…
Browse files Browse the repository at this point in the history
…auses-infinite-loop

IS-1021: DictDB iteration causes infinite loop
  • Loading branch information
Chiwon Cho authored Mar 6, 2020
2 parents a09f612 + 16f850f commit 728fb20
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 0 deletions.
3 changes: 3 additions & 0 deletions iconservice/iconscore/icon_container_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ def __remove(self, key: K) -> None:
raise InvalidContainerAccessException('DictDB depth mismatch')
self._db.delete(get_encoded_key(key))

def __iter__(self):
raise InvalidContainerAccessException("Not Supported iter function on DictDB")


class ArrayDB(object):
"""
Expand Down
50 changes: 50 additions & 0 deletions tests/icon_score/test_icon_container_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,53 @@ def test_container_util(self):
with self.assertRaises(InvalidParamsException):
prefix: bytes = ContainerUtil.create_db_prefix(VarDB, 'vardb')


"""
Dict DB infinity infinite case
implement __getitem__ function means can be iter
https://www.python.org/dev/peps/pep-0234
https://docs.python.org/3/reference/datamodel.html#object.__getitem__
"""


class TestOnlyGetItemObj:
"""
Override getitem function
If you don't consider this case, this logic will always have an infinite loop.
https://stackoverflow.com/questions/926574/why-does-defining-getitem-on-a-class-make-it-iterable-in-python
"""
def __init__(self, limit):
self._limit = limit

def __getitem__(self, key):
if key > self._limit:
raise IndexError(key)


class TestIterObj(TestOnlyGetItemObj):
"""
Override the iter function.
This logic prevents the above infinite loop situation.
https://stackoverflow.com/questions/926574/why-does-defining-getitem-on-a-class-make-it-iterable-in-python
"""
def __iter__(self):
pass


class TestIsAvailableToIterator(unittest.TestCase):
def test_getitem(self):
# it is possible to use foreach by using getitem
limit = 100_000
datas = TestOnlyGetItemObj(limit)
index = 0
for index, e in enumerate(datas):
pass
self.assertEqual(index, limit)

def test_iter(self):
# prevent infinite loop by using empty __iter function
limit = 100_000
datas = TestIterObj(limit)
with self.assertRaises(TypeError) as e:
for e in datas:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": "0.0.1",
"main_file": "sample_dict_db",
"main_score": "IterableDictDB"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-

"""
Example
Problem with DictDB : cannot iterate
https://forum.icon.community/t/problem-with-dictdb-cannot-iterate/484
"""

from iconservice import *
TAG = 'IterableDictDB'


class IterableDictDB(IconScoreBase):
""" IterableDictDB SCORE Base implementation """

# ================================================
# Initialization
# ================================================
def __init__(self, db: IconScoreDatabase) -> None:
super().__init__(db)
self._dict = DictDB('DICT', db, value_type=int)

def on_install(self) -> None:
super().on_install()

def on_update(self) -> None:
super().on_update()

@external
def create_item(self, key: str, value: int) -> None:
self._dict[key] = value

@external(readonly=True)
def get_items(self) -> list:
items = []
for item in self._dict:
items.append(item)
return items
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from iconservice.base.address import GOVERNANCE_SCORE_ADDRESS
from iconservice.icon_constant import Revision
from iconservice.base.exception import InvalidContainerAccessException
from tests.integrate_test.test_integrate_base import TestIntegrateBase

if TYPE_CHECKING:
Expand Down Expand Up @@ -112,3 +113,45 @@ def test_array_db_patch(self):
)

self.assertEqual(len(response), 2)


class TestIntegrateDictDBPatch(TestIntegrateBase):
def test_dict_db_defective(self):
self.update_governance("0_0_4")

expected_status = {
"code": Revision.TWO.value,
"name": "1.1.0"
}

query_request = {
"version": self._version,
"from": self._accounts[0],
"to": GOVERNANCE_SCORE_ADDRESS,
"dataType": "call",
"data": {
"method": "getRevision",
"params": {}
}
}
response = self._query(query_request)
self.assertEqual(expected_status, response)

tx_results: List['TransactionResult'] = self.deploy_score("sample_scores",
"sample_dict_db",
self._accounts[0])
score_address: 'Address' = tx_results[0].score_address

self.score_call(self._accounts[0], score_address, "create_item", params={"key": "a", "value": hex(1)})

with self.assertRaises(InvalidContainerAccessException) as e:
self._query(
{
'to': score_address,
'dataType': 'call',
'data': {
'method': 'get_items'
}
}
)
self.assertEqual(e.exception.message, "Not Supported on DictDB")

0 comments on commit 728fb20

Please sign in to comment.