def search_identifier_or_scope( identifiers: IdentifierManager, accessible_scopes: List[ScopedName], name: ScopedName) -> Union[IdentifierSearchResult, 'IdentifierScope']: """ If there is an identifier with the given name, returns an IdentifierSearchResult. Otherwise, if there is a scope with that name, returns the IdentifierScope instance. If name does not refer to an identifier or a scope, raises an exception. """ try: return identifiers.search(accessible_scopes=accessible_scopes, name=name) except IdentifierError as exc: first_exception = exc try: return identifiers.search_scope(accessible_scopes=accessible_scopes, name=name) except IdentifierError: raise first_exception from None
def test_identifier_manager_aliases(): identifier_dict = { scope('a.b.c'): AliasDefinition(destination=scope('x.y')), scope('x.y'): AliasDefinition(destination=scope('x.y2')), scope('x.y2.z'): ConstDefinition(value=3), scope('x.y2.s.z'): ConstDefinition(value=4), scope('x.y2.s2'): AliasDefinition(destination=scope('x.y2.s')), scope('z0'): AliasDefinition(destination=scope('z1.z2')), scope('z1.z2'): AliasDefinition(destination=scope('z3')), scope('z3'): AliasDefinition(destination=scope('z0')), scope('to_const'): AliasDefinition(destination=scope('x.y2.z')), scope('unresolved'): AliasDefinition(destination=scope('z1.missing')), } manager = IdentifierManager.from_dict(identifier_dict) # Test manager.get(). assert manager.get(scope('a.b.c.z.w')) == IdentifierSearchResult( identifier_definition=identifier_dict[scope('x.y2.z')], canonical_name=scope('x.y2.z'), non_parsed=scope('w')) assert manager.get(scope('to_const.w')) == IdentifierSearchResult( identifier_definition=identifier_dict[scope('x.y2.z')], canonical_name=scope('x.y2.z'), non_parsed=scope('w')) with pytest.raises(IdentifierError, match='Cyclic aliasing detected: z0 -> z1.z2 -> z3 -> z0'): manager.get(scope('z0')) with pytest.raises(IdentifierError, match=(re.escape( 'Alias resolution failed: unresolved -> z1.missing. ' "Unknown identifier 'z1.missing'."))): manager.get(scope('unresolved')) # Test manager.get_scope(). assert manager.get_scope(scope('a.b')).fullname == scope('a.b') assert manager.get_scope(scope('a.b.c')).fullname == scope('x.y2') assert manager.get_scope(scope('a.b.c.s')).fullname == scope('x.y2.s') assert manager.get_scope(scope('a.b.c.s2')).fullname == scope('x.y2.s') with pytest.raises(IdentifierError, match='Cyclic aliasing detected: z0 -> z1.z2 -> z3 -> z0'): manager.get_scope(scope('z0')) with pytest.raises(IdentifierError, match=( 'Alias resolution failed: unresolved -> z1.missing. ' "Unknown identifier 'z1.missing'.")): manager.get_scope(scope('unresolved')) with pytest.raises(IdentifierError, match=( "^Identifier 'x.y2.z' is const, expected a scope.")): manager.get_scope(scope('x.y2.z')) with pytest.raises(IdentifierError, match=( 'Alias resolution failed: a.b.c.z.w -> x.y.z.w -> x.y2.z.w. ' "Identifier 'x.y2.z' is const, expected a scope.")): manager.get_scope(scope('a.b.c.z.w'))
def get_vm_consts(identifier_values, reference_manager, flow_tracking_data, memory={}): """ Creates a simple VmConsts object. """ identifiers = IdentifierManager.from_dict(identifier_values) context = VmConstsContext( identifiers=identifiers, evaluator=ExpressionEvaluator(2**64 + 13, 0, 0, memory, identifiers).eval, reference_manager=reference_manager, flow_tracking_data=flow_tracking_data, memory=memory, pc=9) return VmConsts(context=context, accessible_scopes=[ScopedName()])
def test_identifier_manager_get_by_full_name(): identifier_dict = { scope('a.b.c'): ConstDefinition(value=7), scope('x'): AliasDefinition(destination=scope('a')), } manager = IdentifierManager.from_dict(identifier_dict) assert manager.get_by_full_name(scope('a.b.c')) == identifier_dict[scope('a.b.c')] assert manager.get_by_full_name(scope('x')) == identifier_dict[scope('x')] assert manager.get_by_full_name(scope('a.b')) is None assert manager.get_by_full_name(scope('a.b.c.d')) is None assert manager.get_by_full_name(scope('x.b.c')) is None
def test_scope_order(): identifier_values = { scope('x.y'): ConstDefinition(1), scope('y'): ConstDefinition(2), } context = VmConstsContext( identifiers=IdentifierManager.from_dict(identifier_values), evaluator=dummy_evaluator, reference_manager=ReferenceManager(), flow_tracking_data=FlowTrackingDataActual(ap_tracking=RegTrackingData()), memory={}, pc=0) consts = VmConsts(context=context, accessible_scopes=[ScopedName(), scope('x')]) assert consts.y == 1 assert consts.x.y == 1
def test_unparsed(): identifier_values = { scope('x'): LabelDefinition(10), } context = VmConstsContext( identifiers=IdentifierManager.from_dict(identifier_values), evaluator=dummy_evaluator, reference_manager=ReferenceManager(), flow_tracking_data=FlowTrackingDataActual(ap_tracking=RegTrackingData()), memory={}, pc=0) consts = VmConsts(context=context, accessible_scopes=[scope('')]) with pytest.raises(IdentifierError, match="Unexpected '.' after 'x' which is label."): consts.x.z
def test_get_dunder_something(): context = VmConstsContext(identifiers=IdentifierManager(), evaluator=dummy_evaluator, reference_manager=ReferenceManager(), flow_tracking_data=FlowTrackingDataActual( ap_tracking=RegTrackingData()), memory={}, pc=0) consts = VmConsts(context=context, accessible_scopes=[scope('')]) with pytest.raises( AttributeError, match=re.escape( "'VmConsts' object has no attribute '__something'")): consts.__something
def get_struct_members( struct_name: ScopedName, identifier_manager: IdentifierManager) -> Dict[str, MemberDefinition]: """ Returns the member definitions of a struct sorted by offset. """ scope_items = identifier_manager.get_scope(struct_name).identifiers members = ( (name, indentifier_def) for (name, indentifier_def) in scope_items.items() if isinstance(indentifier_def, MemberDefinition)) return { name: indentifier_def for name, indentifier_def in sorted(members, key=lambda key_value: key_value[1].offset) }
def get_struct_definition( struct_name: ScopedName, identifier_manager: IdentifierManager) -> StructDefinition: """ Returns the struct definition of a struct given its full name (no alias resolution). """ struct_def = identifier_manager.get_by_full_name(struct_name) if struct_def is None: raise MissingIdentifierError(struct_name) if not isinstance(struct_def, StructDefinition): raise DefinitionError(f"""\ Expected '{struct_name}' to be a {StructDefinition.TYPE}. Found: '{struct_def.TYPE}'.""" ) return struct_def
def test_get_struct_members(): identifier_dict = { scope('T.b'): MemberDefinition(offset=1, cairo_type=TypeFelt()), scope('T.a'): MemberDefinition(offset=0, cairo_type=TypeFelt()), scope('T.SIZE'): ConstDefinition(value=2), scope('S.a'): MemberDefinition(offset=0, cairo_type=TypeFelt()), scope('S.c'): MemberDefinition(offset=1, cairo_type=TypeFelt()), scope('S.SIZE'): ConstDefinition(value=2), } manager = IdentifierManager.from_dict(identifier_dict) member = get_struct_members(scope('T'), manager) # Convert to a list, to check the order of the elements in the dict. assert list(member.items()) == [ ('a', MemberDefinition(offset=0, cairo_type=TypeFelt())), ('b', MemberDefinition(offset=1, cairo_type=TypeFelt())), ]
def test_type_casts(src: str, dest: str, explicit_cast: bool, unpacking_cast: bool, assign_cast: bool): identifier_manager = IdentifierManager() src_type = parse_type(src) dest_type = parse_type(dest) expr = parse_expr('[ap]') actual_results = [ check_cast(src_type=src_type, dest_type=dest_type, identifier_manager=identifier_manager, expr=expr, cast_type=cast_type) for cast_type in [CastType.EXPLICIT, CastType.UNPACKING, CastType.ASSIGN] ] expected_results = [explicit_cast, unpacking_cast, assign_cast] assert actual_results == expected_results
def get_main_functions_to_compile( identifiers: IdentifierManager, main_scope: ScopedName) -> Set[ScopedName]: """ Retrieves the root functions to compile from a main scope. The definition of which functions we need to compile is somewhat arbitrary: All functions explicitly defined, or aliased in the main scope. """ main_functions: Set[ScopedName] = set() try: scope = identifiers.get_scope(main_scope) main_functions = {main_scope + name for name in scope.subscopes} main_functions |= { identifier_definition.destination for identifier_definition in scope.identifiers.values() if isinstance(identifier_definition, AliasDefinition)} except MissingIdentifierError: return set() return main_functions
def test_missing_attributes(): identifier_values = { scope('x.y'): ConstDefinition(1), scope('z'): AliasDefinition(scope('x')), scope('x.missing'): AliasDefinition(scope('nothing')), } context = VmConstsContext( identifiers=IdentifierManager.from_dict(identifier_values), evaluator=dummy_evaluator, reference_manager=ReferenceManager(), flow_tracking_data=FlowTrackingDataActual( ap_tracking=RegTrackingData()), memory={}, pc=0) consts = VmConsts(context=context, accessible_scopes=[ScopedName()]) # Identifier not exists anywhere. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'xx'."): consts.xx # Identifier not exists in accessible scopes. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'y'."): consts.y # Recursive search. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'x.z'."): consts.x.z # Pass through alias. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'z.x'."): consts.z.x # Pass through bad alias. with pytest.raises( IdentifierError, match= "Alias resolution failed: x.missing -> nothing. Unknown identifier 'nothing'." ): consts.x.missing.y
def resolve_search_result( search_result: IdentifierSearchResult, identifiers: IdentifierManager) -> IdentifierDefinition: """ Returns a fully parsed identifier definition for the given identifier search result. If search_result contains a reference with non_parsed data, returns an instance of OffsetReferenceDefinition. """ identifier_definition = search_result.identifier_definition if isinstance(identifier_definition, ReferenceDefinition) and \ len(search_result.non_parsed) > 0: identifier_definition = OffsetReferenceDefinition( parent=identifier_definition, identifier_values=identifiers.as_dict(), member_path=search_result.non_parsed) else: search_result.assert_fully_parsed() return identifier_definition
def preprocess_codes( codes: Sequence[Tuple[str, str]], pass_manager: PassManager, main_scope: ScopedName = ScopedName() ) -> PreprocessedProgram: """ Preprocesses a list of Cairo files and returns a PreprocessedProgram instance. codes is a list of pairs (code_string, file_name). """ context = PassManagerContext( codes=list(codes), main_scope=main_scope, identifiers=IdentifierManager(), ) pass_manager.run(context) assert context.preprocessed_program is not None return context.preprocessed_program
def process_test_calldata(members: Dict[str, MemberDefinition], has_range_check_builtin=True): identifier_values: Dict[ScopedName, IdentifierDefinition] = { scope('MyStruct'): StructDefinition( full_name=scope('MyStruct'), members=members, size=0, ), } identifiers = IdentifierManager.from_dict(identifier_values) calldata_ptr = ExprIdentifier('calldata_ptr') calldata_size = ExprIdentifier('calldata_size') return process_calldata(calldata_ptr=calldata_ptr, calldata_size=calldata_size, identifiers=identifiers, struct_def=get_struct_definition( struct_name=scope('MyStruct'), identifier_manager=identifiers), has_range_check_builtin=has_range_check_builtin, location=dummy_location())
def test_type_tuples_failures(): identifier_dict = { scope('T'): StructDefinition( full_name=scope('T'), members={ 'x': MemberDefinition(offset=0, cairo_type=TypeFelt()), 'y': MemberDefinition(offset=1, cairo_type=TypeFelt()), }, size=2, ), } identifiers = IdentifierManager.from_dict(identifier_dict) verify_exception('1 + cast((1, 2), T).x', """ file:?:?: Accessing struct members for r-value structs is not supported yet. 1 + cast((1, 2), T).x ^***************^ """, identifiers=identifiers)
def test_main_scope(): identifiers = IdentifierManager.from_dict({ ScopedName.from_string('a.b'): ConstDefinition(value=1), ScopedName.from_string('x.y.z'): ConstDefinition(value=2), }) reference_manager = ReferenceManager() program = Program(prime=0, data=[], hints={}, builtins=[], main_scope=ScopedName.from_string('a'), identifiers=identifiers, reference_manager=reference_manager) # Check accessible identifiers. assert program.get_identifier('b', ConstDefinition).value == 1 # Ensure inaccessible identifiers. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'a'."): program.get_identifier('a.b', ConstDefinition) with pytest.raises(MissingIdentifierError, match="Unknown identifier 'x'."): program.get_identifier('x.y', ConstDefinition) with pytest.raises(MissingIdentifierError, match="Unknown identifier 'y'."): program.get_identifier('y', ConstDefinition) # Full name lookup. assert program.get_identifier('a.b', ConstDefinition, full_name_lookup=True).value == 1 assert program.get_identifier('x.y.z', ConstDefinition, full_name_lookup=True).value == 2
def test_identifier_manager_get(): identifier_dict = { scope('a.b.c'): ConstDefinition(value=7), } manager = IdentifierManager.from_dict(identifier_dict) for name in ['a', 'a.b']: with pytest.raises(MissingIdentifierError, match=f"Unknown identifier '{name}'."): manager.get(scope(name)) # Search 'a.b.c.*'. for suffix in ['d', 'd.e']: result = manager.get(scope('a.b.c') + scope(suffix)) assert result == IdentifierSearchResult( identifier_definition=identifier_dict[scope('a.b.c')], canonical_name=scope('a.b.c'), non_parsed=scope(suffix)) error_msg = re.escape("Unexpected '.' after 'a.b.c' which is const") with pytest.raises(IdentifierError, match=error_msg): result.assert_fully_parsed() with pytest.raises(IdentifierError, match=error_msg): result.get_canonical_name() result = manager.get(scope('a.b.c')) assert result == IdentifierSearchResult( identifier_definition=identifier_dict[scope('a.b.c')], canonical_name=scope('a.b.c'), non_parsed=ScopedName()) result.assert_fully_parsed() assert result.get_canonical_name() == scope('a.b.c') for name in ['a.d', 'a.d.e']: # The error should point to the first unknown item, rather then the entire name. with pytest.raises(MissingIdentifierError, match="Unknown identifier 'a.d'."): manager.get(scope(name))
def test_resolve_search_result(): struct_def = StructDefinition( full_name=scope('T'), members={ 'a': MemberDefinition(offset=0, cairo_type=TypeFelt()), 'b': MemberDefinition(offset=1, cairo_type=TypeFelt()), }, size=2, ) identifier_dict = { struct_def.full_name: struct_def, } identifier = IdentifierManager.from_dict(identifier_dict) with pytest.raises(IdentifierError, match="Unexpected '.' after 'T.a' which is member"): resolve_search_result(search_result=IdentifierSearchResult( identifier_definition=struct_def, canonical_name=struct_def.full_name, non_parsed=scope('a.z')), identifiers=identifier)
def test_identifier_manager_field_serialization(): @marshmallow_dataclass.dataclass class Foo: identifiers: IdentifierManager = field(metadata=dict( marshmallow_field=IdentifierManagerField())) Schema = marshmallow_dataclass.class_schema(Foo) foo = Foo(identifiers=IdentifierManager.from_dict({ scope('aa.b'): LabelDefinition(pc=1000), })) serialized = Schema().dump(foo) assert serialized == { 'identifiers': { 'aa.b': { 'pc': 1000, 'type': 'label' } } } assert Schema().load(serialized) == foo
def test_revoked_reference(): reference_manager = ReferenceManager() ref_id = reference_manager.alloc_id(reference=Reference( pc=0, value=parse_expr('[ap + 1]'), ap_tracking_data=RegTrackingData(group=0, offset=2), )) identifier_values = { scope('x'): ReferenceDefinition( full_name=scope('x'), cairo_type=TypeFelt(), references=[] ), } identifiers = IdentifierManager.from_dict(identifier_values) prime = 2**64 + 13 ap = 100 fp = 200 memory = {} flow_tracking_data = FlowTrackingDataActual( ap_tracking=RegTrackingData(group=1, offset=4), reference_ids={scope('x'): ref_id}, ) context = VmConstsContext( identifiers=identifiers, evaluator=ExpressionEvaluator(prime, ap, fp, memory, identifiers).eval, reference_manager=reference_manager, flow_tracking_data=flow_tracking_data, memory=memory, pc=0) consts = VmConsts(context=context, accessible_scopes=[ScopedName()]) with pytest.raises(FlowTrackingError, match="Reference 'x' is revoked."): consts.x with pytest.raises(FlowTrackingError, match="Reference 'x' is revoked."): consts.x = 85
def __init__(self): super().__init__() self.anon_label_gen = AnonymousLabelGenerator() self.identifiers = IdentifierManager()
def __init__(self): super().__init__() self.identifiers = IdentifierManager()
class IdentifierCollector(Visitor): """ Collects all the identifiers in a code element. Uses a partial visitor. """ # A dict from code element types to the identifier type they define. IDENTIFIER_DEFINERS = { CodeElementConst: ConstDefinition, CodeElementLabel: LabelDefinition, CodeElementReference: ReferenceDefinition, CodeElementLocalVariable: ReferenceDefinition, CodeElementTemporaryVariable: ReferenceDefinition, CodeElementReturnValueReference: ReferenceDefinition, } def __init__(self): super().__init__() self.identifiers = IdentifierManager() def add_identifier(self, name: ScopedName, identifier_definition: IdentifierDefinition, location: Optional[Location]): """ Adds an identifier with name 'name' and the given identifier definition at location 'location'. """ existing_definition = self.identifiers.get_by_full_name(name) if existing_definition is not None: if not isinstance(existing_definition, FutureIdentifierDefinition) or \ not isinstance(identifier_definition, FutureIdentifierDefinition): raise PreprocessorError(f"Redefinition of '{name}'.", location=location) if (existing_definition.identifier_type, identifier_definition.identifier_type) != ( ReferenceDefinition, ReferenceDefinition): # Redefinition is only allowed in reference rebinding. raise PreprocessorError(f"Redefinition of '{name}'.", location=location) self.identifiers.add_identifier(name, identifier_definition) def add_future_identifier(self, name: ScopedName, identifier_type: type, location: Optional[Location]): """ Adds a future identifier with name 'name' of type 'identifier_type' at location 'location'. """ self.add_identifier(name=name, identifier_definition=FutureIdentifierDefinition( identifier_type=identifier_type), location=location) def visit(self, obj): if type(obj) in self.IDENTIFIER_DEFINERS: definition_type = self.IDENTIFIER_DEFINERS[type(obj)] identifier = _get_identifier(obj) self.add_future_identifier(self.current_scope + identifier.name, definition_type, identifier.location) return super().visit(obj) def _visit_default(self, obj): assert isinstance(obj, (CodeBlock, CodeElement)), \ f'Received unexpected object of type {type(obj).__name__}.' def visit_CodeElementFunction(self, elm: CodeElementFunction): """ Registers the function's identifier, arguments and return values, and then recursively visits the code block contained in the function. """ function_scope = self.current_scope + elm.name if elm.element_type == 'struct': self.add_future_identifier(function_scope, StructDefinition, elm.identifier.location) return args_scope = function_scope + CodeElementFunction.ARGUMENT_SCOPE implicit_args_scope = function_scope + CodeElementFunction.IMPLICIT_ARGUMENT_SCOPE rets_scope = function_scope + CodeElementFunction.RETURN_SCOPE def handle_struct_def(identifier_list: Optional[IdentifierList], struct_name: ScopedName): location = elm.identifier.location if identifier_list is not None: location = identifier_list.location self.add_future_identifier(name=struct_name, identifier_type=StructDefinition, location=location) def handle_function_arguments( identifier_list: Optional[IdentifierList], struct_name: ScopedName): handle_struct_def(identifier_list=identifier_list, struct_name=struct_name) if identifier_list is None: return for arg_id in identifier_list.identifiers: if arg_id.name == N_LOCALS_CONSTANT: raise PreprocessorError( f"The name '{N_LOCALS_CONSTANT}' is reserved and cannot be used as an " 'argument name.', location=arg_id.location) # Within a function, arguments are also accessible directly. self.add_future_identifier(function_scope + arg_id.name, ReferenceDefinition, arg_id.location) handle_function_arguments(identifier_list=elm.arguments, struct_name=args_scope) handle_function_arguments(identifier_list=elm.implicit_arguments, struct_name=implicit_args_scope) handle_struct_def(identifier_list=elm.returns, struct_name=rets_scope) # Make sure there is no name collision. if elm.implicit_arguments is not None: implicit_arg_names = { arg_id.name for arg_id in elm.implicit_arguments.identifiers } arg_and_return_identifiers = list(elm.arguments.identifiers) if elm.returns is not None: arg_and_return_identifiers += elm.returns.identifiers for arg_id in arg_and_return_identifiers: if arg_id.name in implicit_arg_names: raise PreprocessorError( 'Arguments and return values cannot have the same name of an implicit ' 'argument.', location=arg_id.location) self.add_future_identifier(function_scope, LabelDefinition, elm.identifier.location) # Add SIZEOF_LOCALS for current block at identifier definition location if available. self.add_future_identifier(function_scope + N_LOCALS_CONSTANT, ConstDefinition, elm.identifier.location) super().visit_CodeElementFunction(elm) def visit_CodeElementUnpackBinding(self, elm: CodeElementUnpackBinding): """ Registers all the unpacked identifiers. """ for identifier in elm.unpacking_list.identifiers: if identifier.name == '_': continue self.add_future_identifier(self.current_scope + identifier.name, ReferenceDefinition, identifier.location) def visit_CodeElementIf(self, obj: CodeElementIf): assert obj.label_neq is not None assert obj.label_end is not None self.add_future_identifier(name=self.current_scope + obj.label_neq, identifier_type=LabelDefinition, location=obj.location) self.add_future_identifier(name=self.current_scope + obj.label_end, identifier_type=LabelDefinition, location=obj.location) self.visit(obj.main_code_block) if obj.else_code_block is not None: self.visit(obj.else_code_block) def visit_CodeBlock(self, code_block: CodeBlock): """ Collects all identifiers in a code block. """ for elm in code_block.code_elements: self.visit(elm.code_elm) def visit_CodeElementImport(self, elm: CodeElementImport): for import_item in elm.import_items: alias_dst = ScopedName.from_string( elm.path.name) + import_item.orig_identifier.name local_identifier = import_item.identifier # Ensure destination is a valid identifier. if self.identifiers.get_by_full_name(alias_dst) is None: raise PreprocessorError( f"Scope '{elm.path.name}' does not include identifier " f"'{import_item.orig_identifier.name}'.", location=import_item.orig_identifier.location) # Add alias to identifiers. self.add_identifier( name=self.current_scope + local_identifier.name, identifier_definition=AliasDefinition(destination=alias_dst), location=import_item.identifier.location) def visit_CodeElementWith(self, elm: CodeElementWith): for aliased_identifier in elm.identifiers: if aliased_identifier.local_name is not None: self.add_future_identifier( name=self.current_scope + aliased_identifier.local_name.name, identifier_type=ReferenceDefinition, location=aliased_identifier.local_name.location) self.visit(elm.code_block)
def __init__(self, identifiers: Optional[IdentifierManager] = None): super().__init__() if identifiers is None: identifiers = IdentifierManager() self.identifiers = identifiers self.identifier_locations: Dict[ScopedName, Location] = {}
def test_offset_reference_definition_typed_members(): t = TypeStruct(scope=scope('T'), is_fully_resolved=True) t_star = TypePointer(pointee=t) s_star = TypePointer( pointee=TypeStruct(scope=scope('S'), is_fully_resolved=True)) reference_manager = ReferenceManager() identifiers = IdentifierManager.from_dict({ scope('T'): StructDefinition( full_name='T', members={ 'x': MemberDefinition(offset=3, cairo_type=s_star), 'flt': MemberDefinition(offset=4, cairo_type=TypeFelt()), }, size=5, ), scope('S'): StructDefinition( full_name='S', members={ 'x': MemberDefinition(offset=10, cairo_type=t), }, size=15, ), }) main_reference = ReferenceDefinition(full_name=scope('a'), cairo_type=t_star, references=[]) references = { scope('a'): reference_manager.alloc_id( Reference( pc=0, value=mark_types_in_expr_resolved(parse_expr('cast(ap, T*)')), ap_tracking_data=RegTrackingData(group=0, offset=0), )), } flow_tracking_data = FlowTrackingDataActual( ap_tracking=RegTrackingData(group=0, offset=1), reference_ids=references, ) # Create OffsetReferenceDefinition instances for expressions of the form "a.<member_path>", # such as a.x and a.x.x, and check the result of evaluation those expressions. for member_path, expected_result in [ ('x', 'cast([ap - 1 + 3], S*)'), ('x.x', 'cast([[ap - 1 + 3] + 10], T)'), ('x.x.x', 'cast([&[[ap - 1 + 3] + 10] + 3], S*)'), ('x.x.flt', 'cast([&[[ap - 1 + 3] + 10] + 4], felt)') ]: definition = OffsetReferenceDefinition(parent=main_reference, identifiers=identifiers, member_path=scope(member_path)) definition.eval( reference_manager=reference_manager, flow_tracking_data=flow_tracking_data).format() == expected_result definition = OffsetReferenceDefinition(parent=main_reference, identifiers=identifiers, member_path=scope('x.x.flt.x')) with pytest.raises( DefinitionError, match='Member access requires a type of the form Struct*.'): definition.eval(reference_manager=reference_manager, flow_tracking_data=flow_tracking_data) definition = OffsetReferenceDefinition(parent=main_reference, identifiers=identifiers, member_path=scope('x.y')) with pytest.raises(DefinitionError, match="'y' is not a member of 'S'."): definition.eval(reference_manager=reference_manager, flow_tracking_data=flow_tracking_data)
def test_references(): reference_manager = ReferenceManager() references = { scope('x.ref'): reference_manager.get_id( Reference( pc=0, value=parse_expr('[ap + 1]'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.ref2'): reference_manager.get_id( Reference( pc=0, value=parse_expr('[ap + 1] + 0'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.ref3'): reference_manager.get_id( Reference( pc=0, value=parse_expr('ap + 1'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.typeref'): reference_manager.get_id( Reference( pc=0, value=mark_types_in_expr_resolved( parse_expr('cast(ap + 1, a*)')), ap_tracking_data=RegTrackingData(group=0, offset=3), )), scope('x.typeref2'): reference_manager.get_id( Reference( pc=0, value=mark_types_in_expr_resolved( parse_expr('cast([ap + 1], a*)')), ap_tracking_data=RegTrackingData(group=0, offset=3), )), } identifier_values = { scope('x.ref'): ReferenceDefinition(full_name=scope('x.ref'), references=[]), scope('x.ref2'): ReferenceDefinition(full_name=scope('x.ref2'), references=[]), scope('x.ref3'): ReferenceDefinition(full_name=scope('x.ref3'), references=[]), scope('x.typeref'): ReferenceDefinition(full_name=scope('x.typeref'), references=[]), scope('x.typeref2'): ReferenceDefinition(full_name=scope('x.typeref2'), references=[]), scope('a.member'): MemberDefinition(offset=10, cairo_type=TypeFelt()), scope('a.scope0.member'): MemberDefinition(offset=2, cairo_type=TypeFelt()), } prime = 2**64 + 13 ap = 100 fp = 200 memory = { (ap - 2) + 1: 1234, (ap - 1) + 1: 1000, (ap - 1) + 1 + 2: 13, (ap - 1) + 1 + 10: 17, } flow_tracking_data = FlowTrackingDataActual( ap_tracking=RegTrackingData(group=0, offset=4), reference_ids=references, ) context = VmConstsContext( identifiers=IdentifierManager.from_dict(identifier_values), evaluator=ExpressionEvaluator(prime, ap, fp, memory).eval, reference_manager=reference_manager, flow_tracking_data=flow_tracking_data, memory=memory, pc=0) consts = VmConsts(context=context, accessible_scopes=[ScopedName()]) assert consts.x.ref == memory[(ap - 2) + 1] assert consts.x.typeref.address_ == (ap - 1) + 1 assert consts.x.typeref.member == memory[(ap - 1) + 1 + 10] with pytest.raises( NotImplementedError, match="Expected a member, found 'scope0' which is 'scope'"): consts.x.typeref.scope0 # Test that VmConsts can be used to assign values to references of the form '[...]'. memory.clear() consts.x.ref = 1234 assert memory == {(ap - 2) + 1: 1234} memory.clear() consts.x.typeref.member = 1001 assert memory == {(ap - 1) + 1 + 10: 1001} memory.clear() consts.x.typeref2 = 4321 assert memory == {(ap - 1) + 1: 4321} consts.x.typeref2.member = 1 assert memory == { (ap - 1) + 1: 4321, 4321 + 10: 1, } with pytest.raises(AssertionError, match='Cannot change the value of a scope definition'): consts.x = 1000 with pytest.raises( AssertionError, match= r'x.ref2 \(= \[ap \+ 1\] \+ 0\) does not reference memory and cannot be assigned.', ): consts.x.ref2 = 1000 with pytest.raises( AssertionError, match= r'x.typeref \(= ap \+ 1\) does not reference memory and cannot be assigned.', ): consts.x.typeref = 1000
def __init__(self, identifiers: Optional[IdentifierManager] = None): super().__init__() self.identifiers = IdentifierManager( ) if identifiers is None else identifiers
def test_references(): reference_manager = ReferenceManager() references = { scope('x.ref'): reference_manager.alloc_id( Reference( pc=0, value=parse_expr('[ap + 1]'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.ref2'): reference_manager.alloc_id( Reference( pc=0, value=parse_expr('[ap + 1] + 0'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.ref3'): reference_manager.alloc_id( Reference( pc=0, value=parse_expr('ap + 1'), ap_tracking_data=RegTrackingData(group=0, offset=2), )), scope('x.typeref'): reference_manager.alloc_id( Reference( pc=0, value=mark_types_in_expr_resolved( parse_expr('cast(ap + 1, MyStruct*)')), ap_tracking_data=RegTrackingData(group=0, offset=3), )), scope('x.typeref2'): reference_manager.alloc_id( Reference( pc=0, value=mark_types_in_expr_resolved( parse_expr('cast([ap + 1], MyStruct*)')), ap_tracking_data=RegTrackingData(group=0, offset=3), )), } my_struct_star = TypePointer( pointee=TypeStruct(scope=scope('MyStruct'), is_fully_resolved=True)) identifier_values = { scope('x.ref'): ReferenceDefinition(full_name=scope('x.ref'), cairo_type=TypeFelt(), references=[]), scope('x.ref2'): ReferenceDefinition(full_name=scope('x.ref2'), cairo_type=TypeFelt(), references=[]), scope('x.ref3'): ReferenceDefinition(full_name=scope('x.ref3'), cairo_type=TypeFelt(), references=[]), scope('x.typeref'): ReferenceDefinition(full_name=scope('x.typeref'), cairo_type=my_struct_star, references=[]), scope('x.typeref2'): ReferenceDefinition(full_name=scope('x.typeref2'), cairo_type=my_struct_star, references=[]), scope('MyStruct'): StructDefinition( full_name=scope('MyStruct'), members={ 'member': MemberDefinition(offset=10, cairo_type=TypeFelt()), }, size=11, ), } prime = 2**64 + 13 ap = 100 fp = 200 memory = { (ap - 2) + 1: 1234, (ap - 1) + 1: 1000, (ap - 1) + 1 + 2: 13, (ap - 1) + 1 + 10: 17, } flow_tracking_data = FlowTrackingDataActual( ap_tracking=RegTrackingData(group=0, offset=4), reference_ids=references, ) context = VmConstsContext( identifiers=IdentifierManager.from_dict(identifier_values), evaluator=ExpressionEvaluator(prime, ap, fp, memory).eval, reference_manager=reference_manager, flow_tracking_data=flow_tracking_data, memory=memory, pc=0) consts = VmConsts(context=context, accessible_scopes=[ScopedName()]) assert consts.x.ref == memory[(ap - 2) + 1] assert consts.x.typeref.address_ == (ap - 1) + 1 assert consts.x.typeref.member == memory[(ap - 1) + 1 + 10] with pytest.raises(IdentifierError, match="'abc' is not a member of 'MyStruct'."): consts.x.typeref.abc with pytest.raises(IdentifierError, match="'SIZE' is not a member of 'MyStruct'."): consts.x.typeref.SIZE with pytest.raises( AssertionError, match='Cannot change the value of a struct definition.'): consts.MyStruct = 13 assert consts.MyStruct.member == 10 with pytest.raises(AssertionError, match='Cannot change the value of a constant.'): consts.MyStruct.member = 13 assert consts.MyStruct.SIZE == 11 with pytest.raises(AssertionError, match='Cannot change the value of a constant.'): consts.MyStruct.SIZE = 13 with pytest.raises(IdentifierError, match="'abc' is not a member of 'MyStruct'."): consts.MyStruct.abc # Test that VmConsts can be used to assign values to references of the form '[...]'. memory.clear() consts.x.ref = 1234 assert memory == {(ap - 2) + 1: 1234} memory.clear() consts.x.typeref.member = 1001 assert memory == {(ap - 1) + 1 + 10: 1001} memory.clear() consts.x.typeref2 = 4321 assert memory == {(ap - 1) + 1: 4321} consts.x.typeref2.member = 1 assert memory == { (ap - 1) + 1: 4321, 4321 + 10: 1, } with pytest.raises(AssertionError, match='Cannot change the value of a scope definition'): consts.x = 1000 with pytest.raises( AssertionError, match= r'x.ref2 \(= \[ap \+ 1\] \+ 0\) does not reference memory and cannot be assigned.', ): consts.x.ref2 = 1000 with pytest.raises( AssertionError, match= r'x.typeref \(= ap \+ 1\) does not reference memory and cannot be assigned.', ): consts.x.typeref = 1000