From f48189951b95f752540043aa3c427f2a12f6aaaa Mon Sep 17 00:00:00 2001 From: bart1e Date: Sun, 26 Feb 2023 17:39:01 +0100 Subject: [PATCH 1/5] Detector for array length caching added --- slither/detectors/all_detectors.py | 1 + .../operations/cache_array_length.py | 215 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol | 127 +++++++++++ ...rayLength.sol.0.8.17.CacheArrayLength.json | 74 ++++++ tests/e2e/detectors/test_detectors.py | 5 + 5 files changed, 422 insertions(+) create mode 100644 slither/detectors/operations/cache_array_length.py create mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol create mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 9722b87937..6a1ec67390 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -89,3 +89,4 @@ from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex from .functions.cyclomatic_complexity import CyclomaticComplexity +from .operations.cache_array_length import CacheArrayLength diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py new file mode 100644 index 0000000000..8c1112d2d9 --- /dev/null +++ b/slither/detectors/operations/cache_array_length.py @@ -0,0 +1,215 @@ +from typing import List, Set + +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function +from slither.core.expressions import BinaryOperation, Identifier, MemberAccess, UnaryOperation +from slither.core.solidity_types import ArrayType +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import Length, Delete + + +class CacheArrayLength(AbstractDetector): + """ + Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it. + """ + + ARGUMENT = "cache-array-length" + HELP = ( + "Detects `for` loops that use `length` member of some storage array in their loop condition and don't " + "modify it. " + ) + IMPACT = DetectorClassification.OPTIMIZATION + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length" + + WIKI_TITLE = "Cache array length" + WIKI_DESCRIPTION = ( + "Detects `for` loops that use `length` member of some storage array in their loop condition " + "and don't modify it. " + ) + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract C +{ + uint[] array; + + function f() public + { + for (uint i = 0; i < array.length; i++) + { + // code that does not modify length of `array` + } + } +} +``` +Since the `for` loop in `f` doesn't modify `array.length`, it is more gas efficient to cache it in some local variable and use that variable instead, like in the following example: + +```solidity +contract C +{ + uint[] array; + + function f() public + { + uint array_length = array.length; + for (uint i = 0; i < array_length; i++) + { + // code that does not modify length of `array` + } + } +} +``` + """ + WIKI_RECOMMENDATION = ( + "Cache the lengths of storage arrays if they are used and not modified in `for` loops." + ) + + @staticmethod + def _is_identifier_member_access_comparison(exp: BinaryOperation) -> bool: + """ + Checks whether a BinaryOperation `exp` is an operation on Identifier and MemberAccess. + """ + return ( + isinstance(exp.expression_left, Identifier) + and isinstance(exp.expression_right, MemberAccess) + ) or ( + isinstance(exp.expression_left, MemberAccess) + and isinstance(exp.expression_right, Identifier) + ) + + @staticmethod + def _extract_array_from_length_member_access(exp: MemberAccess) -> [StateVariable | None]: + """ + Given a member access `exp`, it returns state array which `length` member is accessed through `exp`. + If array is not a state array or its `length` member is not referenced, it returns `None`. + """ + if exp.member_name != "length": + return None + if not isinstance(exp.expression, Identifier): + return None + if not isinstance(exp.expression.value, StateVariable): + return None + if not isinstance(exp.expression.value.type, ArrayType): + return None + return exp.expression.value + + @staticmethod + def _is_loop_referencing_array_length( + node: Node, visited: Set[Node], array: StateVariable, depth: int + ) -> True: + """ + For a given loop, checks if it references `array.length` at some point. + Will also return True if `array.length` is referenced but not changed. + This may potentially generate false negatives in the detector, but it was done this way because: + - situations when array `length` is referenced but not modified in loop are rare + - checking if `array.length` is indeed modified would require much more work + """ + visited.add(node) + if node.type == NodeType.STARTLOOP: + depth += 1 + if node.type == NodeType.ENDLOOP: + depth -= 1 + if depth == 0: + return False + + # Array length may change in the following situations: + # - when `push` is called + # - when `pop` is called + # - when `delete` is called on the entire array + if node.type == NodeType.EXPRESSION: + for op in node.irs: + if isinstance(op, Length) and op.value == array: + # op accesses array.length, not necessarily modifying it + return True + if isinstance(op, Delete): + # take into account only delete entire array, since delete array[i] doesn't change `array.length` + if ( + isinstance(op.expression, UnaryOperation) + and isinstance(op.expression.expression, Identifier) + and op.expression.expression.value == array + ): + return True + + for son in node.sons: + if son not in visited: + if CacheArrayLength._is_loop_referencing_array_length(son, visited, array, depth): + return True + return False + + @staticmethod + def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[SourceMapping]) -> None: + """ + For each loop, checks if it has a comparison with `length` array member and, if it has, checks whether that + array size could potentially change in that loop. + If it cannot, the loop condition is added to `non_optimal_array_len_usages`. + There may be some false negatives here - see docs for `_is_loop_referencing_array_length` for more information. + """ + for node in nodes: + if node.type == NodeType.STARTLOOP: + if_node = node.sons[0] + if if_node.type != NodeType.IFLOOP: + continue + if not isinstance(if_node.expression, BinaryOperation): + continue + exp: BinaryOperation = if_node.expression + if not CacheArrayLength._is_identifier_member_access_comparison(exp): + continue + array: StateVariable + if isinstance(exp.expression_right, MemberAccess): + array = CacheArrayLength._extract_array_from_length_member_access( + exp.expression_right + ) + else: # isinstance(exp.expression_left, MemberAccess) == True + array = CacheArrayLength._extract_array_from_length_member_access( + exp.expression_left + ) + if array is None: + continue + + visited: Set[Node] = set() + if not CacheArrayLength._is_loop_referencing_array_length( + if_node, visited, array, 1 + ): + non_optimal_array_len_usages.append(if_node.expression) + + @staticmethod + def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[SourceMapping]: + """ + Finds non-optimal usages of array length in loop conditions in a given function. + """ + non_optimal_array_len_usages: List[SourceMapping] = [] + CacheArrayLength._handle_loops(f.nodes, non_optimal_array_len_usages) + + return non_optimal_array_len_usages + + @staticmethod + def _get_non_optimal_array_len_usages(functions: List[Function]) -> List[SourceMapping]: + """ + Finds non-optimal usages of array length in loop conditions in given functions. + """ + non_optimal_array_len_usages: List[SourceMapping] = [] + + for f in functions: + non_optimal_array_len_usages += ( + CacheArrayLength._get_non_optimal_array_len_usages_for_function(f) + ) + + return non_optimal_array_len_usages + + def _detect(self): + results = [] + + non_optimal_array_len_usages = CacheArrayLength._get_non_optimal_array_len_usages( + self.compilation_unit.functions + ) + for usage in non_optimal_array_len_usages: + info = [ + f"Loop condition at {usage.source_mapping} should use cached array length instead of referencing " + f"`length` member of the storage array.\n " + ] + res = self.generate_result(info) + results.append(res) + return results diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol new file mode 100644 index 0000000000..79858d1825 --- /dev/null +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol @@ -0,0 +1,127 @@ +pragma solidity 0.8.17; + +contract CacheArrayLength +{ + struct S + { + uint s; + } + + S[] array; + S[] array2; + + function f() public + { + // array accessed but length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array[i] = S(0); + } + + // array.length doesn't change, but array.length not used in loop condition + for (uint i = array.length; i >= 0; i--) + { + + } + + // array.length changes in the inner loop + for (uint i = 0; i < array.length; i++) + { + for (uint j = i; j < 2 * i; j++) + array.push(S(j)); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + array.pop(); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + delete array; + } + + // array.length doesn't change despite using delete + for (uint i = 0; i < array.length; i++) // warning should appear + { + delete array[i]; + } + + // array.length changes; push used in more complex expression + for (uint i = 0; i < array.length; i++) + { + array.push() = S(i); + } + + // array.length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array2.pop(); + array2.push(); + array2.push(S(i)); + delete array2; + delete array[0]; + } + + // array.length changes; array2.length doesn't change + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + array.pop(); + } + } + } + + // array.length doesn't change; array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) + { + array2.pop(); + } + } + } + + // none of array.length and array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + + } + } + } + + S[] memory array3; + + // array3 not modified, but it's not a storage array + for (uint i = 0; i < array3.length; i++) + { + + } + } +} \ No newline at end of file diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json new file mode 100644 index 0000000000..b2050508fb --- /dev/null +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json @@ -0,0 +1,74 @@ +[ + [ + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "66f0059f2e2608b9e532ab9f6a473aa9128f1e9f02fe16fcb960b4ae4970b2cb", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "8463e2cd88985588ad2426476df033957fb4f8969117b7649f1165244b86d2e0", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "961db8345eea46c3814eb2c6f6fc6d1506a2feaa85630349266cff940f33a7ab", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "aea0a11159dec6d660f39a09aa44b56ad0effb7ced7183552b21b69f0f62e0c7", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "d296a0fd4cbe7b9a2db11b3d08b2db08a53414326a91eeda1f78853c4d4c5714", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "db2bfdc710cfa1ea5339e1f14b1a5b80735c50c37522f983a220b579df34c5d0", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "f5085e5d1ddd69d59f60443b7e89ea5f7301ba6a0a42786624b783389474aaf7", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index e6b87d530a..5a91802562 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1639,6 +1639,11 @@ def id_test(test_item: Test): "LowCyclomaticComplexity.sol", "0.8.16", ), + Test( + all_detectors.CacheArrayLength, + "CacheArrayLength.sol", + "0.8.17", + ), ] GENERIC_PATH = "/GENERIC_PATH" From 71fef2c437ca97bc7e303f6eadc615252ef78726 Mon Sep 17 00:00:00 2001 From: bart1e Date: Sun, 26 Feb 2023 18:11:34 +0100 Subject: [PATCH 2/5] 'unsupported operand type' error fix --- .../operations/cache_array_length.py | 2 +- ...rayLength.sol.0.8.17.CacheArrayLength.json | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py index 8c1112d2d9..1f8111bdb4 100644 --- a/slither/detectors/operations/cache_array_length.py +++ b/slither/detectors/operations/cache_array_length.py @@ -81,7 +81,7 @@ def _is_identifier_member_access_comparison(exp: BinaryOperation) -> bool: ) @staticmethod - def _extract_array_from_length_member_access(exp: MemberAccess) -> [StateVariable | None]: + def _extract_array_from_length_member_access(exp: MemberAccess) -> StateVariable: """ Given a member access `exp`, it returns state array which `length` member is accessed through `exp`. If array is not a state array or its `length` member is not referenced, it returns `None`. diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json index b2050508fb..3323ff4791 100644 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json @@ -2,70 +2,70 @@ [ { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "66f0059f2e2608b9e532ab9f6a473aa9128f1e9f02fe16fcb960b4ae4970b2cb", + "id": "2ff6144814e406cadadd58712f5a7a8ef6ee169da06660d590e7bee37759fc98", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "8463e2cd88985588ad2426476df033957fb4f8969117b7649f1165244b86d2e0", + "id": "48a6388cf2193fdd780ea86cb3e588dfd3276182e209f3f2807d9927a5ba25bc", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "961db8345eea46c3814eb2c6f6fc6d1506a2feaa85630349266cff940f33a7ab", + "id": "562b7ae618977ea1d0a232d8af5edd81ce404b6844e864d0c4ab162a88142c71", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "aea0a11159dec6d660f39a09aa44b56ad0effb7ced7183552b21b69f0f62e0c7", + "id": "8f1aa2da0763f65179e90a2b96d7feb68c99aab60f227a5da8dad2f12069f047", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "d296a0fd4cbe7b9a2db11b3d08b2db08a53414326a91eeda1f78853c4d4c5714", + "id": "9c988bc3f6748fadb8527c9eea53e3d06f5f4e7b2a0c7c70993c4af758bbba47", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "db2bfdc710cfa1ea5339e1f14b1a5b80735c50c37522f983a220b579df34c5d0", + "id": "a1c39b4ae47535f3354d0bf26ea67d3b524efb94a3bdb2f503bda245471ee451", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "f5085e5d1ddd69d59f60443b7e89ea5f7301ba6a0a42786624b783389474aaf7", + "id": "c57b54ebb07e5114ccb8c124fa3971b9385d5252264f2bf2acbe131b3a986aa8", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" From d1804f3d01edb309f9632ac0ba67e0ca7e8fdce3 Mon Sep 17 00:00:00 2001 From: bart1e Date: Wed, 3 May 2023 19:20:05 +0200 Subject: [PATCH 3/5] Tests updated --- ...yLength_0_8_17_CacheArrayLength_sol__0.txt | 14 ++ .../0.8.17/CacheArrayLength.sol | 127 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol-0.8.17.zip | Bin 0 -> 7589 bytes 3 files changed, 141 insertions(+) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol create mode 100644 tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip diff --git a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt new file mode 100644 index 0000000000..a0ba357409 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt @@ -0,0 +1,14 @@ +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array. + diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol new file mode 100644 index 0000000000..79858d1825 --- /dev/null +++ b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol @@ -0,0 +1,127 @@ +pragma solidity 0.8.17; + +contract CacheArrayLength +{ + struct S + { + uint s; + } + + S[] array; + S[] array2; + + function f() public + { + // array accessed but length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array[i] = S(0); + } + + // array.length doesn't change, but array.length not used in loop condition + for (uint i = array.length; i >= 0; i--) + { + + } + + // array.length changes in the inner loop + for (uint i = 0; i < array.length; i++) + { + for (uint j = i; j < 2 * i; j++) + array.push(S(j)); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + array.pop(); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + delete array; + } + + // array.length doesn't change despite using delete + for (uint i = 0; i < array.length; i++) // warning should appear + { + delete array[i]; + } + + // array.length changes; push used in more complex expression + for (uint i = 0; i < array.length; i++) + { + array.push() = S(i); + } + + // array.length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array2.pop(); + array2.push(); + array2.push(S(i)); + delete array2; + delete array[0]; + } + + // array.length changes; array2.length doesn't change + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + array.pop(); + } + } + } + + // array.length doesn't change; array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) + { + array2.pop(); + } + } + } + + // none of array.length and array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + + } + } + } + + S[] memory array3; + + // array3 not modified, but it's not a storage array + for (uint i = 0; i < array3.length; i++) + { + + } + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip new file mode 100644 index 0000000000000000000000000000000000000000..ab3d813718b45d438f91556934cc243e900beb57 GIT binary patch literal 7589 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 literal 0 HcmV?d00001 From 9cc616e4cc539a6479673d62565625d446532b0e Mon Sep 17 00:00:00 2001 From: bart1e Date: Wed, 3 May 2023 19:21:58 +0200 Subject: [PATCH 4/5] Old tests removed --- .../0.8.17/CacheArrayLength.sol | 127 ------------------ ...rayLength.sol.0.8.17.CacheArrayLength.json | 74 ---------- 2 files changed, 201 deletions(-) delete mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol delete mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol deleted file mode 100644 index 79858d1825..0000000000 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol +++ /dev/null @@ -1,127 +0,0 @@ -pragma solidity 0.8.17; - -contract CacheArrayLength -{ - struct S - { - uint s; - } - - S[] array; - S[] array2; - - function f() public - { - // array accessed but length doesn't change - for (uint i = 0; i < array.length; i++) // warning should appear - { - array[i] = S(0); - } - - // array.length doesn't change, but array.length not used in loop condition - for (uint i = array.length; i >= 0; i--) - { - - } - - // array.length changes in the inner loop - for (uint i = 0; i < array.length; i++) - { - for (uint j = i; j < 2 * i; j++) - array.push(S(j)); - } - - // array.length changes - for (uint i = 0; i < array.length; i++) - { - array.pop(); - } - - // array.length changes - for (uint i = 0; i < array.length; i++) - { - delete array; - } - - // array.length doesn't change despite using delete - for (uint i = 0; i < array.length; i++) // warning should appear - { - delete array[i]; - } - - // array.length changes; push used in more complex expression - for (uint i = 0; i < array.length; i++) - { - array.push() = S(i); - } - - // array.length doesn't change - for (uint i = 0; i < array.length; i++) // warning should appear - { - array2.pop(); - array2.push(); - array2.push(S(i)); - delete array2; - delete array[0]; - } - - // array.length changes; array2.length doesn't change - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) // warning should appear - { - array.pop(); - } - } - } - - // array.length doesn't change; array2.length changes - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) // warning should appear - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) - { - array2.pop(); - } - } - } - - // none of array.length and array2.length changes - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) // warning should appear - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) // warning should appear - { - - } - } - } - - S[] memory array3; - - // array3 not modified, but it's not a storage array - for (uint i = 0; i < array3.length; i++) - { - - } - } -} \ No newline at end of file diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json deleted file mode 100644 index 3323ff4791..0000000000 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json +++ /dev/null @@ -1,74 +0,0 @@ -[ - [ - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "2ff6144814e406cadadd58712f5a7a8ef6ee169da06660d590e7bee37759fc98", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "48a6388cf2193fdd780ea86cb3e588dfd3276182e209f3f2807d9927a5ba25bc", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "562b7ae618977ea1d0a232d8af5edd81ce404b6844e864d0c4ab162a88142c71", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "8f1aa2da0763f65179e90a2b96d7feb68c99aab60f227a5da8dad2f12069f047", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "9c988bc3f6748fadb8527c9eea53e3d06f5f4e7b2a0c7c70993c4af758bbba47", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "a1c39b4ae47535f3354d0bf26ea67d3b524efb94a3bdb2f503bda245471ee451", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "c57b54ebb07e5114ccb8c124fa3971b9385d5252264f2bf2acbe131b3a986aa8", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - } - ] -] \ No newline at end of file From 0b7257209dd09b1d877ec40870dd55eaf8ab10d1 Mon Sep 17 00:00:00 2001 From: bart1e Date: Mon, 15 May 2023 21:01:04 +0200 Subject: [PATCH 5/5] External calls handled + output printing changed --- .../operations/cache_array_length.py | 16 +++++-- ...yLength_0_8_17_CacheArrayLength_sol__0.txt | 18 ++++--- .../0.8.17/CacheArrayLength.sol | 44 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol-0.8.17.zip | Bin 7589 -> 8898 bytes 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py index 1f8111bdb4..da73d3fd5a 100644 --- a/slither/detectors/operations/cache_array_length.py +++ b/slither/detectors/operations/cache_array_length.py @@ -7,7 +7,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import Length, Delete +from slither.slithir.operations import Length, Delete, HighLevelCall class CacheArrayLength(AbstractDetector): @@ -119,8 +119,10 @@ def _is_loop_referencing_array_length( # - when `push` is called # - when `pop` is called # - when `delete` is called on the entire array + # - when external function call is made (instructions from internal function calls are already in + # `node.all_slithir_operations()`, so we don't need to handle internal calls separately) if node.type == NodeType.EXPRESSION: - for op in node.irs: + for op in node.all_slithir_operations(): if isinstance(op, Length) and op.value == array: # op accesses array.length, not necessarily modifying it return True @@ -132,6 +134,8 @@ def _is_loop_referencing_array_length( and op.expression.expression.value == array ): return True + if isinstance(op, HighLevelCall) and not op.function.view and not op.function.pure: + return True for son in node.sons: if son not in visited: @@ -173,7 +177,7 @@ def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[SourceMa if not CacheArrayLength._is_loop_referencing_array_length( if_node, visited, array, 1 ): - non_optimal_array_len_usages.append(if_node.expression) + non_optimal_array_len_usages.append(if_node) @staticmethod def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[SourceMapping]: @@ -207,8 +211,10 @@ def _detect(self): ) for usage in non_optimal_array_len_usages: info = [ - f"Loop condition at {usage.source_mapping} should use cached array length instead of referencing " - f"`length` member of the storage array.\n " + "Loop condition at ", + usage, + " should use cached array length instead of referencing `length` member " + "of the storage array.\n ", ] res = self.generate_result(info) results.append(res) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt index a0ba357409..63d4b883c3 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt @@ -1,14 +1,18 @@ -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#36) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_22 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#166) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at j_scope_11 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#108) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_6 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#79) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_21 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#160) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at k_scope_9 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#98) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at k_scope_17 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#132) should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at j_scope_15 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#125) should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at i_scope_4 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#67) should use cached array length instead of referencing `length` member of the storage array. diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol index 79858d1825..704d6bed9e 100644 --- a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol +++ b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol @@ -10,6 +10,26 @@ contract CacheArrayLength S[] array; S[] array2; + function h() external + { + + } + + function g() internal + { + this.h(); + } + + function h_view() external view + { + + } + + function g_view() internal view + { + this.h_view(); + } + function f() public { // array accessed but length doesn't change @@ -123,5 +143,29 @@ contract CacheArrayLength { } + + // array not modified, but it may potentially change in an internal function call + for (uint i = 0; i < array.length; i++) + { + g(); + } + + // array not modified, but it may potentially change in an external function call + for (uint i = 0; i < array.length; i++) + { + this.h(); + } + + // array not modified and it cannot be changed in a function call since g_view is a view function + for (uint i = 0; i < array.length; i++) // warning should appear + { + g_view(); + } + + // array not modified and it cannot be changed in a function call since h_view is a view function + for (uint i = 0; i < array.length; i++) // warning should appear + { + this.h_view(); + } } } \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip index ab3d813718b45d438f91556934cc243e900beb57..dafdfc43167f9eb622fed98bdc69e91d02f89dfa 100644 GIT binary patch delta 8644 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 delta 7346 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