def verify_exception(expr_str: str, error: str): """ Verifies that calling simplify_type_system() on the code results in the given error. """ with pytest.raises(CairoTypeError) as e: simplify_type_system(parse_expr(expr_str)) # Remove line and column information from the error using a regular expression. assert re.sub(':[0-9]+:[0-9]+: ', 'file:?:?: ', str(e.value)) == error.strip()
def test_type_visitor_pointer_arithmetic(): t = TypeStruct(scope=scope('T'), is_fully_resolved=False) t_star = TypePointer(pointee=t) assert simplify_type_system(parse_expr('cast(fp, T*) + 3')) == ( parse_expr('fp + 3'), t_star) assert simplify_type_system(parse_expr('cast(fp, T*) - 3')) == ( parse_expr('fp - 3'), t_star) assert simplify_type_system(parse_expr('cast(fp, T*) - cast(3, T*)')) == ( parse_expr('fp - 3'), TypeFelt())
def handle_ReferenceDefinition(self, name: str, identifier: ReferenceDefinition, scope: ScopedName, set_value: Optional[MaybeRelocatable]): # In set mode, take the address of the given reference instead. reference = self._context.flow_tracking_data.resolve_reference( reference_manager=self._context.reference_manager, name=identifier.full_name) if set_value is None: expr = reference.eval(self._context.flow_tracking_data.ap_tracking) expr, expr_type = simplify_type_system(expr) if isinstance(expr_type, TypeStruct): # If the reference is of type T, take its address and treat it as T*. assert isinstance(expr, ExprDeref), \ f"Expected expression of type '{expr_type.format()}' to have an address." expr = expr.addr expr_type = TypePointer(pointee=expr_type) val = self._context.evaluator(expr) # Check if the type is felt* or any_type**. is_pointer_to_felt_or_pointer = ( isinstance(expr_type, TypePointer) and isinstance(expr_type.pointee, (TypePointer, TypeFelt))) if isinstance(expr_type, TypeFelt) or is_pointer_to_felt_or_pointer: return val else: # Typed reference, return VmConstsReference which allows accessing members. assert isinstance(expr_type, TypePointer) and \ isinstance(expr_type.pointee, TypeStruct), \ 'Type must be of the form T*.' return VmConstsReference( context=self._context, accessible_scopes=[expr_type.pointee.scope], reference_value=val, add_addr_var=True) else: assert str(scope[-1:]) == name, 'Expecting scope to end with name.' value, value_type = simplify_type_system(reference.value) assert isinstance(value, ExprDeref), f"""\ {scope} (= {value.format()}) does not reference memory and cannot be assigned.""" value_ref = Reference( pc=reference.pc, value=ExprCast(expr=value.addr, dest_type=value_type), ap_tracking_data=reference.ap_tracking_data, ) addr = self._context.evaluator( value_ref.eval(self._context.flow_tracking_data.ap_tracking)) self._context.memory[addr] = set_value
def test_type_visitor(): t = TypeStruct(scope=scope('T'), is_fully_resolved=False) t_star = TypePointer(pointee=t) t_star2 = TypePointer(pointee=t_star) assert simplify_type_system( parse_expr('fp + 3 + [ap]')) == (parse_expr('fp + 3 + [ap]'), TypeFelt()) assert simplify_type_system( parse_expr('cast(fp + 3 + [ap], T*)')) == (parse_expr('fp + 3 + [ap]'), t_star) # Two casts. assert simplify_type_system( parse_expr('cast(cast(fp, T*), felt)')) == (parse_expr('fp'), TypeFelt()) # Cast from T to T. assert simplify_type_system( parse_expr('cast([cast(fp, T*)], T)')) == (parse_expr('[fp]'), t) # Dereference. assert simplify_type_system( parse_expr('[cast(fp, T**)]')) == (parse_expr('[fp]'), t_star) assert simplify_type_system( parse_expr('[[cast(fp, T**)]]')) == (parse_expr('[[fp]]'), t) # Address of. assert simplify_type_system( parse_expr('&([[cast(fp, T**)]])')) == (parse_expr('[fp]'), t_star) assert simplify_type_system( parse_expr('&&[[cast(fp, T**)]]')) == (parse_expr('fp'), t_star2)
def verify_exception(expr_str: str, error: str, identifiers: Optional[IdentifierManager] = None, resolve_types=True): """ Verifies that calling simplify_type_system() on the code results in the given error. """ with pytest.raises(CairoTypeError) as e: parsed_expr = parse_expr(expr_str) if resolve_types: parsed_expr = mark_types_in_expr_resolved(parsed_expr) simplify_type_system(parsed_expr, identifiers) # Remove line and column information from the error using a regular expression. assert re.sub(':[0-9]+:[0-9]+: ', 'file:?:?: ', str(e.value)) == error.strip()
def get_reference_type(name): identifier_definition = program.identifiers.get_by_full_name( ScopedName.from_string(name)) assert isinstance(identifier_definition, ReferenceDefinition) assert len(identifier_definition.references) == 1 _, expr_type = simplify_type_system( identifier_definition.references[0].value) return expr_type
def simplify_type_system_test(orig_expr: str, simplified_expr: str, simplified_type: CairoType, identifiers: Optional[IdentifierManager] = None): parsed_expr = mark_types_in_expr_resolved(parse_expr(orig_expr)) assert simplify_type_system( parsed_expr, identifiers=identifiers) == (parse_expr(simplified_expr), simplified_type)
def test_typed_references(): scope = TEST_SCOPE program = preprocess_str(code=""" func main(): struct T: member pad0 : felt member pad1 : felt member pad2 : felt member b : T* end struct Struct: member pad0 : felt member pad1 : felt member a : T* end let x : Struct* = cast(ap + 10, Struct*) let y : Struct = [x] [fp] = x.a assert [fp] = cast(x.a.b, felt) assert [fp] = cast(x.a.b.b, felt) [fp] = y.a + 1 ret end """, prime=PRIME, main_scope=scope) def get_reference(name): scoped_name = scope + name assert isinstance(program.identifiers.get_by_full_name(scoped_name), ReferenceDefinition) return program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scoped_name) expected_type_x = mark_type_resolved(parse_type(f'{scope}.main.Struct*')) assert simplify_type_system(get_reference('main.x').value)[1] == expected_type_x expected_type_y = mark_type_resolved(parse_type(f'{scope}.main.Struct')) reference = get_reference('main.y') assert simplify_type_system(reference.value)[1] == expected_type_y assert reference.value.format() == f'cast([ap + 10], {scope}.main.Struct)' assert program.format() == """\
def test_type_tuples(): t = TypeStruct(scope=scope('T'), is_fully_resolved=False) t_star = TypePointer(pointee=t) # Simple tuple. assert simplify_type_system(parse_expr('(fp, [cast(fp, T*)], cast(fp,T*))')) == ( parse_expr('(fp, [fp], fp)'), TypeTuple(members=[TypeFelt(), t, t_star],) ) # Nested. assert simplify_type_system(parse_expr('(fp, (), ([cast(fp, T*)],))')) == ( parse_expr('(fp, (), ([fp],))'), TypeTuple( members=[ TypeFelt(), TypeTuple(members=[]), TypeTuple(members=[t])], ) )
def test_return_value_reference(): program = preprocess_str(code=""" func foo() -> (val, x, y): ret end func main(): let x = call foo [ap] = 0; ap++ x.val = 9 let y : main.Return = call foo let z = call abs 0 ret end """, prime=PRIME) scope = ScopedName.from_string assert isinstance(program.identifiers.get_by_full_name(scope('main.x')), ReferenceDefinition) expected_type = mark_type_resolved( parse_type(f'foo.{CodeElementFunction.RETURN_SCOPE}')) reference = program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scope('main.x')) assert simplify_type_system(reference.value)[1] == expected_type assert isinstance(program.identifiers.get_by_full_name(scope('main.y')), ReferenceDefinition) expected_type = mark_type_resolved( parse_type(f'main.{CodeElementFunction.RETURN_SCOPE}')) reference = program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scope('main.y')) assert simplify_type_system(reference.value)[1] == expected_type assert isinstance(program.identifiers.get_by_full_name(scope('main.z')), ReferenceDefinition) expected_type = parse_type('felt') reference = program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scope('main.z')) assert simplify_type_system(reference.value)[1] == expected_type assert program.format() == """\
def test_typed_references(): program = preprocess_str(code=""" func main(): struct T: member b : T* = 3 end struct Struct: member a : T* = 2 end let x : Struct* = cast(ap + 10, Struct*) let y : Struct = [x] [fp] = x.a assert [fp] = x.a.b assert [fp] = x.a.b.b [fp] = y.a + 1 ret end """, prime=PRIME) scope = ScopedName.from_string assert isinstance(program.identifiers.get_by_full_name(scope('main.x')), ReferenceDefinition) expected_type_x = mark_type_resolved(parse_type('main.Struct*')) reference = program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scope('main.x')) assert simplify_type_system(reference.value)[1] == \ expected_type_x assert isinstance(program.identifiers.get_by_full_name(scope('main.y')), ReferenceDefinition) expected_type_y = mark_type_resolved(parse_type('main.Struct')) reference = program.instructions[-1].flow_tracking_data.resolve_reference( reference_manager=program.reference_manager, name=scope('main.y')) assert simplify_type_system(reference.value)[1] == \ expected_type_y assert reference.value.format() == \ 'cast([ap + 10], main.Struct)' assert program.format() == """\
def eval(self, expr: Expression) -> int: expr, expr_type = simplify_type_system(expr) assert isinstance(expr_type, (TypeFelt, TypePointer)), \ f"Unable to evaluate expression of type '{expr_type.format()}'." res = self.visit(expr) assert isinstance( res, ExprConst), f"Unable to evaluate expression '{expr.format()}'." assert self.prime is not None return res.val % self.prime
def eval(self, expr): if expr == 'null': return '' expr, expr_type = simplify_type_system( substitute_identifiers(parse_expr(expr), self.get_variable)) if isinstance(expr_type, TypeStruct): raise NotImplementedError('Structs are not supported.') res = self.visit(expr) if isinstance(res, ExprConst): return field_element_repr(res.val, self.tracer_data.program.prime) return res.format()
def eval( self, reference_manager: ReferenceManager, flow_tracking_data: FlowTrackingData) -> \ Expression: reference = flow_tracking_data.resolve_reference( reference_manager=reference_manager, name=self.parent.full_name) assert isinstance(flow_tracking_data, FlowTrackingDataActual), \ 'Resolved references can only come from FlowTrackingDataActual.' expr, expr_type = simplify_type_system(reference.eval(flow_tracking_data.ap_tracking)) for member_name in self.member_path.path: if isinstance(expr_type, TypeStruct): expr_type = expr_type.get_pointer_type() # In this case, take the address of the reference value. to_addr = lambda expr: ExprAddressOf(expr=expr) else: to_addr = lambda expr: expr if not isinstance(expr_type, TypePointer) or \ not isinstance(expr_type.pointee, TypeStruct): raise DefinitionError('Member access requires a type of the form Struct*.') qualified_member = expr_type.pointee.resolved_scope + member_name if qualified_member not in self.identifier_values: raise DefinitionError(f"Member '{qualified_member}' was not found.") member_definition = self.identifier_values[qualified_member] if not isinstance(member_definition, MemberDefinition): raise DefinitionError( f"Expected reference offset '{qualified_member}' to be a member, " f'found {member_definition.TYPE}.') offset_value = member_definition.offset expr_type = member_definition.cairo_type expr = ExprDeref(addr=ExprOperator(a=to_addr(expr), op='+', b=ExprConst(offset_value))) return ExprCast( expr=expr, dest_type=expr_type, )
def test_type_subscript_op(): felt_star_star = TypePointer(pointee=TypePointer(pointee=TypeFelt())) t = TypeStruct(scope=scope('T'), is_fully_resolved=True) t_star = TypePointer(pointee=t) identifier_dict = { scope('T'): StructDefinition(full_name=scope('T'), members={}, size=7) } identifiers = IdentifierManager.from_dict(identifier_dict) simplify_type_system_test('cast(fp, felt*)[3]', '[fp + 3 * 1]', TypeFelt()) simplify_type_system_test('cast(fp, felt***)[0]', '[fp + 0 * 1]', felt_star_star) simplify_type_system_test('[cast(fp, T****)][ap][ap]', '[[[fp] + ap * 1] + ap * 1]', t_star) simplify_type_system_test('cast(fp, T**)[1][2]', '[[fp + 1 * 1] + 2 * 7]', t, identifiers=identifiers) # Test that 'cast(fp, T*)[2 * ap + 3]' simplifies into '[fp + (2 * ap + 3) * 7]', but without # the parentheses. assert simplify_type_system( mark_types_in_expr_resolved(parse_expr('cast(fp, T*)[2 * ap + 3]')), identifiers=identifiers) == (remove_parentheses( parse_expr('[fp + (2 * ap + 3) * 7]')), t) # Test subscript operator for tuples. simplify_type_system_test('(cast(fp, felt**), fp, cast(fp, T*))[2]', 'fp', t_star) simplify_type_system_test('(cast(fp, felt**), fp, cast(fp, T*))[0]', 'fp', felt_star_star) simplify_type_system_test('(cast(fp, felt**), ap, cast(fp, T*))[3*4 - 11]', 'ap', TypeFelt()) simplify_type_system_test('[cast(ap, (felt, felt)*)][0]', '[ap + 0]', TypeFelt()) simplify_type_system_test('[cast(ap, (T*, T, felt, T*, felt*)*)][3]', '[ap + 9]', t_star, identifiers=identifiers) # Test failures. verify_exception( '(fp, fp, fp)[cast(ap, felt*)]', """ file:?:?: Cannot apply subscript-operator with offset of non-felt type 'felt*'. (fp, fp, fp)[cast(ap, felt*)] ^*************^ """) verify_exception( '(fp, fp, fp)[[ap]]', """ file:?:?: Subscript-operator for tuples supports only constant offsets, found 'ExprDeref'. (fp, fp, fp)[[ap]] ^**^ """) # The simplifier in TypeSystemVisitor cannot access PRIME, so PyConsts are unsimplified. verify_exception( '(fp, fp, fp)[%[1%]]', """ file:?:?: Subscript-operator for tuples supports only constant offsets, found 'ExprPyConst'. (fp, fp, fp)[%[1%]] ^***^ """) verify_exception( '(fp, fp, fp)[3]', """ file:?:?: Tuple index 3 is out of range [0, 3). (fp, fp, fp)[3] ^*************^ """) verify_exception( '[cast(fp, (T*, T, felt)*)][-1]', """ file:?:?: Tuple index -1 is out of range [0, 3). [cast(fp, (T*, T, felt)*)][-1] ^****************************^ """) verify_exception( 'cast(fp, felt)[0]', """ file:?:?: Cannot apply subscript-operator to non-pointer, non-tuple type 'felt'. cast(fp, felt)[0] ^***************^ """) verify_exception( '[cast(fp, T*)][0]', """ file:?:?: Cannot apply subscript-operator to non-pointer, non-tuple type 'T'. [cast(fp, T*)][0] ^***************^ """) verify_exception( 'cast(fp, felt*)[[cast(ap, T*)]]', """ file:?:?: Cannot apply subscript-operator with offset of non-felt type 'T'. cast(fp, felt*)[[cast(ap, T*)]] ^************^ """) verify_exception('cast(fp, Z*)[0]', """ file:?:?: Unknown identifier 'Z'. cast(fp, Z*)[0] ^*************^ """, identifiers=identifiers) verify_exception('cast(fp, T*)[0]', """ file:?:?: Unknown identifier 'T'. cast(fp, T*)[0] ^*************^ """, identifiers=None)