certora-cli-beta-mirror 8.6.3__py3-none-any.whl → 8.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,13 +66,6 @@ class CompilerLang(metaclass=AbstractAndSingleton):
66
66
  """
67
67
  return func_hash
68
68
 
69
- @staticmethod
70
- def normalize_file_compiler_path_name(file_abs_path: str) -> str:
71
- """
72
- Normalizes the absolute path name [file_abs_path] of a file, given to the compiler.
73
- """
74
- return file_abs_path
75
-
76
69
  @staticmethod
77
70
  def normalize_deployed_bytecode(deployed_bytecode: str) -> str:
78
71
  """
@@ -114,7 +107,9 @@ class CompilerLang(metaclass=AbstractAndSingleton):
114
107
  config_path: Path,
115
108
  compiler_cmd: str,
116
109
  compiler_version: Optional[CompilerVersion],
117
- data: Dict[str, Any]) -> Dict[str, Any]:
110
+ data: Dict[str, Any],
111
+ asts : Dict[str, Dict[int, Any]],
112
+ ast_key: str) -> Dict[str, Any]:
118
113
  """
119
114
  Returns the data dictionary of the contract with storage layout information if needed
120
115
  """
@@ -195,5 +190,11 @@ class CompilerCollector(ABC):
195
190
  def compiler_version(self) -> CompilerVersion:
196
191
  pass
197
192
 
193
+ def normalize_file_compiler_path_name(self, file_abs_path: str) -> str:
194
+ """
195
+ Normalizes the absolute path name [file_abs_path] of a file, given to the compiler.
196
+ """
197
+ return file_abs_path
198
+
198
199
  def __str__(self) -> str:
199
200
  return f"{self.compiler_name} {self.compiler_version}"
@@ -52,12 +52,6 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
52
52
  except ValueError:
53
53
  raise Exception(f'{func_hash} is not convertible to hexadecimal')
54
54
 
55
- @staticmethod
56
- def normalize_file_compiler_path_name(file_abs_path: str) -> str:
57
- if not file_abs_path.startswith('/'):
58
- return '/' + file_abs_path
59
- return file_abs_path
60
-
61
55
  @staticmethod
62
56
  def normalize_deployed_bytecode(deployed_bytecode: str) -> str:
63
57
  assert deployed_bytecode.startswith("0x"), f'expected {deployed_bytecode} to have hexadecimal prefix'
@@ -785,10 +779,10 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
785
779
  return [t.resolve_forward_declared_types(name_resolution_dict) for t in real_types]
786
780
 
787
781
  @staticmethod
788
- def extract_ast_types_and_public_vardecls(ast_body_nodes: Dict[int, Dict[str, Any]]) -> \
782
+ def extract_ast_types_and_public_vardecls(ast_body_nodes_per_file: Dict[str, Dict[int, Dict[str, Any]]]) -> \
789
783
  Tuple[List[VyperType], Dict[str, VyperType]]:
790
784
  """
791
- :param ast_body_nodes:
785
+ :param ast_body_nodes_per_file:
792
786
  :return: (types, vars) where `types` is a list of all user-defined types, and `vars` maps public variables to
793
787
  their output types. Note that `types` has been fully resolved - all `VyperTypeNameReference` nodes have been
794
788
  dereferenced
@@ -805,38 +799,50 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
805
799
 
806
800
  # Process named constants ahead of time, as their use site in the source may precede
807
801
  # their definition site, e.g.
808
- for ast_node in ast_body_nodes.values():
809
- if ast_node['ast_type'] != 'VariableDecl':
810
- continue
811
- if ast_node['is_constant'] and ast_node['value'] is not None and \
812
- (ast_node['value']['ast_type'] == 'Int'):
813
- named_constants.update({ast_node['target']['id']: int(ast_node['value']['value'])})
814
-
815
- for ast_node in ast_body_nodes.values():
816
- if ast_node['ast_type'] == 'VariableDecl':
817
- decltype = CompilerLangVy.extract_type_from_variable_decl(ast_node, named_constants)
818
- result_types.append(decltype)
819
- if ast_node['is_public']:
820
- public_vardecls[ast_node['target']['id']] = decltype
821
- elif ast_node['ast_type'] == 'StructDef':
822
- result_types.append(CompilerLangVy.extract_type_from_struct_def(ast_node, named_constants))
823
- # Not sure if `Import` is an actual ast type. It was already there, so I am not removing it.
824
- # I only fixed the implementation of this case to what I think it should be.
825
- elif ast_node['ast_type'] == 'Import':
826
- result_types.append(CompilerLangVy.VyperTypeContract(ast_node['name']))
827
- elif ast_node['ast_type'] == 'ImportFrom':
828
- result_types.append(CompilerLangVy.VyperTypeContract(ast_node['name']))
829
- elif ast_node['ast_type'] == 'InterfaceDef':
830
- result_types.append(CompilerLangVy.VyperTypeContract(ast_node['name']))
831
- resolved_result_types = CompilerLangVy.resolve_extracted_types(result_types)
832
- return resolved_result_types, resolve_vardecl_types(public_vardecls, resolved_result_types)
802
+ for _, ast_body_nodes in ast_body_nodes_per_file.items():
803
+ for ast_node in ast_body_nodes.values():
804
+ if ast_node['ast_type'] != 'VariableDecl':
805
+ continue
806
+ if ast_node['is_constant'] and ast_node['value'] is not None and \
807
+ (ast_node['value']['ast_type'] == 'Int'):
808
+ named_constants.update({ast_node['target']['id']: int(ast_node['value']['value'])})
809
+
810
+ resolved_result_types = []
811
+ for _, ast_body_nodes in ast_body_nodes_per_file.items():
812
+ for ast_node in ast_body_nodes.values():
813
+ if ast_node['ast_type'] == 'VariableDecl':
814
+ decltype = CompilerLangVy.extract_type_from_variable_decl(ast_node, named_constants)
815
+ result_types.append(decltype)
816
+ if ast_node['is_public']:
817
+ public_vardecls[ast_node['target']['id']] = decltype
818
+ elif ast_node['ast_type'] == 'StructDef':
819
+ result_types.append(CompilerLangVy.extract_type_from_struct_def(ast_node, named_constants))
820
+ # Not sure if `Import` is an actual ast type. It was already there, so I am not removing it.
821
+ # I only fixed the implementation of this case to what I think it should be.
822
+ elif ast_node['ast_type'] in ['Import', 'ImportFrom']:
823
+ if "name" in ast_node:
824
+ result_types.append(CompilerLangVy.VyperTypeContract(ast_node['name']))
825
+ elif "names" in ast_node:
826
+ n_list = ast_node["names"]
827
+ for t in n_list:
828
+ result_types.append(CompilerLangVy.VyperTypeContract(t["name"]))
829
+ else:
830
+ raise Exception("Unrecognized import node")
831
+ elif ast_node['ast_type'] == 'InterfaceDef':
832
+ result_types.append(CompilerLangVy.VyperTypeContract(ast_node['name']))
833
+ resolved_result_types.extend(CompilerLangVy.resolve_extracted_types(result_types))
834
+
835
+ # SG: I'm not sure why we didn't set it as a set to begin with. Punting for now
836
+ return list(set(resolved_result_types)), resolve_vardecl_types(public_vardecls, resolved_result_types)
833
837
 
834
838
  @staticmethod
835
839
  def collect_storage_layout_info(file_abs_path: str,
836
840
  config_path: Path,
837
841
  compiler_cmd: str,
838
842
  compiler_version: Optional[CompilerVersion],
839
- data: Dict[str, Any]) -> Dict[str, Any]:
843
+ data: Dict[str, Any],
844
+ asts : Dict[str, Dict[int, Any]],
845
+ ast_key: str) -> Dict[str, Any]:
840
846
  # only Vyper versions 0.2.16 and up have the storage layout
841
847
  if compiler_version is None or not CompilerCollectorVy.supports_storage_layout(compiler_version):
842
848
  return data
@@ -872,20 +878,6 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
872
878
  print(f'Error: {e}')
873
879
  print_failed_to_run(compiler_cmd)
874
880
  raise
875
- ast_output_file_name = f'{get_certora_config_dir()}.ast'
876
- ast_stdout_name = storage_layout_output_file_name + '.stdout'
877
- ast_stderr_name = storage_layout_output_file_name + '.stderr'
878
- args = [compiler_cmd, '-f', 'ast', '-o', ast_output_file_name, file_abs_path]
879
- with Path(ast_stdout_name).open('w+') as stdout:
880
- with Path(ast_stderr_name).open('w+') as stderr:
881
- try:
882
- subprocess.run(args, stdout=stdout, stderr=stderr)
883
- with Path(ast_output_file_name).open('r') as output_file:
884
- ast_dict = json.load(output_file)
885
- except Exception as e:
886
- print(f'Error: {e}')
887
- print_failed_to_run(compiler_cmd)
888
- raise
889
881
 
890
882
  # Depressing how many bugs old Vyper had. Example:
891
883
  # vyper 0.3.7: "userBalances": {"type": "HashMap[address, uint256]", "slot": 1}
@@ -893,10 +885,7 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
893
885
  # "location": "storage", "slot": 2}
894
886
  # so we'll just gracefully exit
895
887
  try:
896
-
897
- extracted_types, _ = CompilerLangVy.extract_ast_types_and_public_vardecls(
898
- {x['node_id']: x for x in ast_dict['ast']['body']}
899
- )
888
+ extracted_types, _ = CompilerLangVy.extract_ast_types_and_public_vardecls(asts)
900
889
  all_used_types = list(itertools.chain.from_iterable([e.get_used_types() for e in extracted_types])) + \
901
890
  list(CompilerLangVy.primitive_types.values())
902
891
  type_descriptors_by_name = {i.get_canonical_vyper_name(): i.get_storage_type_descriptor()
@@ -931,22 +920,63 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
931
920
 
932
921
  return desc
933
922
 
934
- storage_field = [{
935
- 'label': v,
936
- 'slot': str(storage_layout_dict[v]['slot']),
937
- 'offset': 0,
938
- 'type': storage_layout_dict[v]['type'],
939
- 'descriptor': annotate_desc(type_descriptors_by_name[storage_layout_dict[v]['type']],
940
- storage_layout_dict[v]['type'], types_field)
941
- } for v in storage_layout_dict.keys()]
942
-
943
- contract_name = list(data['contracts'][file_abs_path].keys())[0]
944
- data['contracts'][file_abs_path][contract_name]['storageLayout'] = {
923
+ def extract_storage_fields(storage_layout_dict: Dict[str, Any],
924
+ type_descriptors_by_name: Dict[str, Dict[str, Any]],
925
+ types_field: Dict[str, Dict[str, Any]],
926
+ parent_path: str = "") -> List[Dict[str, Any]]:
927
+ """
928
+ Recursively traverse storage layout dictionary and extract all fields with 'slot' keys.
929
+
930
+ Args:
931
+ storage_layout_dict: The storage layout dictionary to traverse
932
+ type_descriptors_by_name: Type descriptors mapping
933
+ types_field: Types field for annotation
934
+ parent_path: Current path in the hierarchy (for building field labels)
935
+
936
+ Returns:
937
+ List of storage field dictionaries
938
+ """
939
+ storage_fields = []
940
+
941
+ for key, value in storage_layout_dict.items():
942
+ current_path = f"{parent_path}.{key}" if parent_path else key
943
+
944
+ if isinstance(value, dict):
945
+ # Check if this dict contains a 'slot' key (leaf node)
946
+ if 'slot' in value:
947
+ # This is a storage variable - process it
948
+ storage_fields.append({
949
+ 'label': current_path,
950
+ 'slot': str(value['slot']),
951
+ 'offset': 0,
952
+ 'type': value['type'],
953
+ 'descriptor': annotate_desc(
954
+ type_descriptors_by_name[value['type']],
955
+ value['type'],
956
+ types_field
957
+ )
958
+ })
959
+ else:
960
+ # This is a nested structure - recurse into it
961
+ storage_fields.extend(
962
+ extract_storage_fields(value, type_descriptors_by_name, types_field, current_path)
963
+ )
964
+
965
+ return storage_fields
966
+
967
+ storage_field = extract_storage_fields(storage_layout_dict, type_descriptors_by_name, types_field)
968
+
969
+ data_key = file_abs_path if file_abs_path in data["contracts"] else ast_key
970
+ if data_key not in data["contracts"]:
971
+ raise Exception(f"Expected to have the right key into the json out for updating the storage layout, "
972
+ f"tried {file_abs_path} and {ast_key} but keys are {data['contracts'].keys()}")
973
+ contract_name = list(data['contracts'][data_key].keys())[0]
974
+ data['contracts'][data_key][contract_name]['storageLayout'] = {
945
975
  'storage': storage_field,
946
976
  'types': types_field,
947
977
  'storageHashArgsReversed': True
948
978
  }
949
- data['contracts'][file_abs_path][contract_name]['storageHashArgsReversed'] = True
979
+ data['contracts'][data_key][contract_name]['storageHashArgsReversed'] = True
950
980
  return data
951
981
  except Exception as e:
952
982
  ast_logger.warning(f'Failed to get storage layout, continuing: {e}')
@@ -1175,7 +1205,7 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
1175
1205
  return funcs
1176
1206
 
1177
1207
  vyper_types, public_vardecls = \
1178
- CompilerLangVy.extract_ast_types_and_public_vardecls(asts[build_arg_contract_file][contract_file])
1208
+ CompilerLangVy.extract_ast_types_and_public_vardecls(asts[build_arg_contract_file])
1179
1209
  ct_types = [x.get_certora_type(contract_name, 0) for x in vyper_types]
1180
1210
  getter_vars_list = [(v, public_vardecls[v].get_certora_type(contract_name, 0))
1181
1211
  for v in public_vardecls if isinstance(public_vardecls[v], CompilerLangVy.VyperTypeHashMap)]
@@ -1208,7 +1238,7 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
1208
1238
 
1209
1239
  try:
1210
1240
  # TODO: verify that the collected functions matches the information in data['abi']
1211
- collector = Collector(contract_name, asts[build_arg_contract_file][contract_file])
1241
+ collector = Collector(contract_name, asts[build_arg_contract_file])
1212
1242
  type_descriptions_and_funcs = [t.get_certora_type(contract_name, 0) for t in
1213
1243
  collector.types.values()], collector.funcs
1214
1244
 
@@ -1249,17 +1279,24 @@ class Collector:
1249
1279
 
1250
1280
  _contract_name : str
1251
1281
 
1252
- def __init__(self, contract_name : str, asts: Dict[int, Dict[str, Any]]):
1282
+ def __init__(self, contract_name : str, asts_per_file: Dict[str, Dict[int, Dict[str, Any]]]):
1253
1283
  """Collect the types and functions from the top-level 'AST' node in [ast]."""
1254
1284
  self.types = {}
1255
1285
  self.funcs = []
1256
1286
  self.consts = {}
1257
1287
  self._contract_name = contract_name
1258
- for node in asts.values():
1259
- if node['ast_type'] == 'Module':
1260
- self._collect_module(node)
1261
-
1262
- def _collect_module(self, module_node: Dict[str, Any]) -> None:
1288
+ # first pass - get all constants
1289
+ for _, asts in asts_per_file.items():
1290
+ for node in asts.values():
1291
+ if node['ast_type'] == 'Module':
1292
+ self._collect_module_consts(node)
1293
+
1294
+ for _, asts in asts_per_file.items():
1295
+ for node in asts.values():
1296
+ if node['ast_type'] == 'Module':
1297
+ self._collect_module(node)
1298
+
1299
+ def _collect_module_consts(self, module_node: Dict[str, Any]) -> None:
1263
1300
  """Populate [self.types] and [self.funcs] base on 'Module' AST node in [node]."""
1264
1301
  assert module_node['ast_type'] == "Module"
1265
1302
 
@@ -1272,6 +1309,7 @@ class Collector:
1272
1309
  for v in var_decls:
1273
1310
  self._collect_const(v)
1274
1311
 
1312
+ def _collect_module(self, module_node: Dict[str, Any]) -> None:
1275
1313
  # Extract and resolve types
1276
1314
  type_asts = {'EnumDef', 'StructDef', 'InterfaceDef', 'Import', 'Import', 'ImportFrom', 'FlagDef'}
1277
1315
  types = [e for e in module_node['body'] if e['ast_type'] in type_asts]
@@ -1286,6 +1324,7 @@ class Collector:
1286
1324
  for f in funs:
1287
1325
  self._collect_func(f)
1288
1326
 
1327
+ var_decls = [e for e in module_node['body'] if e['ast_type'] == 'VariableDecl']
1289
1328
  # Add getters for public variables (also needs to happen after type resolution)
1290
1329
  for v in var_decls:
1291
1330
  self._collect_getter(v)
@@ -1308,7 +1347,16 @@ class Collector:
1308
1347
  # TODO: this is probably wrong, since you can probably import constants and things too...
1309
1348
  # although in practice it appears that people only import constants
1310
1349
  elif type_decl_node['ast_type'] in ('InterfaceDef', 'Import', 'ImportFrom'):
1311
- vy_type = CompilerLangVy.VyperTypeContract(type_decl_node['name'])
1350
+ if "names" in type_decl_node:
1351
+ n_list = type_decl_node["names"]
1352
+ for t in n_list:
1353
+ ty = CompilerLangVy.VyperTypeContract(t["name"])
1354
+ self.types[t["name"]] = ty
1355
+ return
1356
+ elif "name" in type_decl_node:
1357
+ vy_type = CompilerLangVy.VyperTypeContract(type_decl_node['name'])
1358
+ else:
1359
+ raise AssertionError("Unexpected type definition")
1312
1360
  else:
1313
1361
  raise AssertionError("Unexpected type definition")
1314
1362
  self.types[type_decl_node['name']] = vy_type
@@ -1386,6 +1434,11 @@ class CompilerCollectorVy(CompilerCollector):
1386
1434
  def compiler_version(self) -> CompilerVersion:
1387
1435
  return self.__compiler_version
1388
1436
 
1437
+ def normalize_file_compiler_path_name(self, file_abs_path: str) -> str:
1438
+ if self.compiler_version[1] < 4 and not file_abs_path.startswith('/'):
1439
+ return '/' + file_abs_path
1440
+ return file_abs_path
1441
+
1389
1442
  @staticmethod
1390
1443
  def supports_storage_layout(version: CompilerVersion) -> bool:
1391
1444
  return (version[1] > 2 or (
@@ -15,10 +15,11 @@
15
15
 
16
16
 
17
17
  from dataclasses import dataclass
18
- from typing import Dict, Any, Optional, Generator
18
+ from typing import Dict, Any, Optional, Callable, Generator
19
19
 
20
20
  from CertoraProver.Compiler.CompilerCollectorSol import CompilerCollectorSol
21
21
  from CertoraProver.certoraBuildDataClasses import SDC, Instrumentation, Replace
22
+ from CertoraProver.certoraOffsetConverter import OffsetConverter
22
23
  from Shared import certoraUtils as Util
23
24
 
24
25
 
@@ -92,7 +93,7 @@ def find_casts(ast: Dict[int, Any]) -> list[CastInfo]:
92
93
  function_nodes = [node for node in ast.values() if node.get('nodeType') == 'FunctionDefinition']
93
94
 
94
95
  for func in function_nodes:
95
- for node in iter_all_nodes(func):
96
+ for node in iter_all_nodes_under(func):
96
97
  if isinstance(node, dict) and node.get("kind") == "typeConversion":
97
98
  arguments = node.get("arguments", [])
98
99
  if len(arguments) == 1 and isinstance(arguments[0], dict):
@@ -119,19 +120,21 @@ def casting_func_name(counter: int) -> str:
119
120
  return f"cast_{counter}"
120
121
 
121
122
 
122
- def generate_casting_function(assembly_prefix: str, cast_info: CastInfo, counter: int) -> str:
123
+ def generate_casting_function(assembly_prefix: str, cast_info: CastInfo, counter: int, line: int, column: int) -> str:
123
124
  """
124
125
  returns the text of a solidity function that does casting according to CastInfo. It also has an encoded mload
125
126
  call, to be decoded later on the kotlin side if we run the `safeCasting` builtin rule.
126
127
  """
127
- conversion_string = assembly_prefix + \
128
- "{ mstore(0xffffff6e4604afefe123321beef1b03fffffffffffffffffffff" + \
129
- f'{"%0.4x" % counter}{"%0.4x" % encode_type(cast_info.arg_type_str)}{"%0.4x" % encode_type(cast_info.res_type_str)}, x)' + "}"
128
+ conversion_string = (assembly_prefix +
129
+ "{ mstore(0xffffff6e4604afefe123321beef1b03fffffffffffffff" +
130
+ f'{"%0.5x" % line}{"%0.5x" % column}{"%0.4x" % encode_type(cast_info.arg_type_str)}{"%0.4x" % encode_type(cast_info.res_type_str)}, x)'
131
+ "}")
130
132
  function_head = f"function {casting_func_name(counter)}({cast_info.arg_type_str} x) internal pure returns ({cast_info.res_type_str})"
131
133
  return function_head + "{\n" + conversion_string + f"return {cast_info.res_type_str}(x);\n" "}\n"
132
134
 
133
135
 
134
- def generate_casting_instrumentation(asts: Dict[str, Dict[str, Dict[int, Any]]], contract_file: str, sdc: SDC) \
136
+ def generate_casting_instrumentation(asts: Dict[str, Dict[str, Dict[int, Any]]], contract_file: str, sdc: SDC,
137
+ offset_converters: dict[str, OffsetConverter]) \
135
138
  -> tuple[Dict[str, Dict[int, Instrumentation]], Dict[str, tuple[str, list[str]]]]:
136
139
  """
137
140
  Generate instrumentation for integer type casts in Solidity code.
@@ -152,7 +155,7 @@ def generate_casting_instrumentation(asts: Dict[str, Dict[str, Dict[int, Any]]],
152
155
  " when trying to add casting instrumentation")
153
156
  assembly_prefix = sdc.compiler_collector.gen_memory_safe_assembly_prefix()
154
157
 
155
- casting_funcs : Dict[str, tuple[str, list[str]]] = dict()
158
+ casting_funcs: dict[str, tuple[str, list[str]]] = dict()
156
159
  counter = 0
157
160
  original_files = sorted({Util.convert_path_for_solc_import(c.original_file) for c in sdc.contracts})
158
161
  for file_count, solfile in enumerate(original_files, start=1):
@@ -165,28 +168,32 @@ def generate_casting_instrumentation(asts: Dict[str, Dict[str, Dict[int, Any]]],
165
168
  casts = find_casts(curr_file_ast)
166
169
  for cast_info in casts:
167
170
  start_offset, src_len, file = curr_file_ast[cast_info.expr_id]["src"].split(":")
171
+ line, column = offset_converters[solfile].offset_to_line_column(int(start_offset))
168
172
  counter += 1
169
173
  per_file_inst[int(start_offset)] = Instrumentation(expected=bytes(cast_info.res_type_str[0], 'utf-8'),
170
174
  to_ins=f"{libname}.{casting_func_name(counter)}",
171
175
  mut=Replace(len(cast_info.res_type_str)))
172
- new_func = generate_casting_function(assembly_prefix, cast_info, counter)
176
+ new_func = generate_casting_function(assembly_prefix, cast_info, counter, line, column)
173
177
  per_file_casts.append(new_func)
174
178
 
175
179
  return casting_instrumentation, casting_funcs
176
180
 
177
181
 
178
- def iter_all_nodes(node: Any) -> Generator[Any, Optional[Any], None]:
182
+ def iter_all_nodes_under(node: Any, f: Callable[[Any], bool] = lambda node: True, is_inside: bool = False) \
183
+ -> Generator[Any, Optional[Any], None]:
179
184
  """
180
- Yield a node and all its subnodes in depth-first order.
185
+ Yield a node and all its subnodes in depth-first order, but only recursively under nodes where f returns True.
181
186
  Works with dict nodes that may contain nested dicts and lists.
182
187
  """
183
- yield node
188
+ inside = is_inside
189
+ if f(node):
190
+ inside = True
191
+ if inside:
192
+ yield node
184
193
 
185
194
  if isinstance(node, dict):
186
195
  for value in node.values():
187
- if isinstance(value, (dict, list)):
188
- yield from iter_all_nodes(value)
196
+ yield from iter_all_nodes_under(value, f, inside)
189
197
  elif isinstance(node, list):
190
198
  for item in node:
191
- if isinstance(item, (dict, list)):
192
- yield from iter_all_nodes(item)
199
+ yield from iter_all_nodes_under(item, f, inside)