def check_types(ir): """Checks that expressions within the IR have the correct top-level types. check_types ensures that expressions at the top level have correct types; in particular, it ensures that array sizes are integers ("UInt[true]" is not a valid array type) and that the starts and ends of ranges are integers. Arguments: ir: an IR to type check. Returns: A (possibly empty) list of errors. """ errors = [] traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.FieldLocation], _type_check_field_location, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.ArrayType, ir_pb2.Expression], _type_check_array_size, skip_descendants_of={ir_pb2.AtomicType}, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Field], _type_check_field_existence_condition, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.RuntimeParameter], _type_check_parameter, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.AtomicType], _type_check_passed_parameters, parameters={"errors": errors}) return errors
def _find_dependencies(ir): """Constructs a dependency graph for the entire IR.""" dependencies = {} errors = [] traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Reference], _add_reference_to_dependencies, # TODO(bolms): Add handling for references inside of attributes, once # there are attributes with non-constant values. skip_descendants_of={ ir_pb2.AtomicType, ir_pb2.Attribute, ir_pb2.FieldReference }, incidental_actions={ ir_pb2.Field: _add_name_to_dependencies, ir_pb2.EnumValue: _add_name_to_dependencies, ir_pb2.RuntimeParameter: _add_name_to_dependencies, }, parameters={ "dependencies": dependencies, "errors": errors, }) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.FieldReference], _add_field_reference_to_dependencies, skip_descendants_of={ir_pb2.Attribute}, incidental_actions={ ir_pb2.Field: _add_name_to_dependencies, ir_pb2.EnumValue: _add_name_to_dependencies, ir_pb2.RuntimeParameter: _add_name_to_dependencies, }, parameters={"dependencies": dependencies}) return dependencies, errors
def test_filter_on_type_in_type(self): constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.Function, ir_pb2.Expression, ir_pb2.NumericConstant], _record_constant, parameters={"constant_list": constants}) self.assertEqual([1, 1], constants)
def test_filter_on_type(self): constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.NumericConstant], _record_constant, parameters={"constant_list": constants}) self.assertEqual( _count_entries([0, 8, 8, 8, 16, 24, 32, 16, 32, 320, 1, 1, 1, 64]), _count_entries(constants))
def test_filter_on_not_type(self): notstruct_constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.NumericConstant], _record_constant, skip_descendants_of=(ir_pb2.Structure, ), parameters={"constant_list": notstruct_constants}) self.assertEqual(_count_entries([1, 1, 1, 64]), _count_entries(notstruct_constants))
def set_write_methods(ir): """Sets the write_method member of all ir_pb2.Fields in ir. Arguments: ir: The IR to which to add write_methods. Returns: A list of errors, or an empty list. """ traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Field], _add_write_method) return []
def test_field_is_populated(self): constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.Field, ir_pb2.NumericConstant], _record_field_name_and_constant, parameters={"constant_list": constants}) self.assertEqual( _count_entries([("field1", 0), ("field1", 8), ("field2", 8), ("field2", 8), ("field2", 16), ("bar_field1", 24), ("bar_field1", 32), ("bar_field2", 16), ("bar_field2", 32), ("bar_field2", 320)]), _count_entries(constants))
def synthesize_fields(ir): """Adds synthetic fields to all structures. Adds aliases for all fields in anonymous `bits` to the enclosing structure. Arguments: ir: The IR to which to add fields. Returns: A list of errors, or an empty list. """ traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure], _add_virtuals_to_structure) return []
def test_type_definition_is_populated(self): constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.NumericConstant], _record_kind_and_constant, parameters={"constant_list": constants}) self.assertEqual( _count_entries([("structure", 0), ("structure", 8), ("structure", 8), ("structure", 8), ("structure", 16), ("structure", 24), ("structure", 32), ("structure", 16), ("structure", 32), ("structure", 320), ("enumeration", 1), ("enumeration", 1), ("enumeration", 1), ("external", 64)]), _count_entries(constants))
def test_filter_on_type_star_type(self): struct_constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.Structure, ir_pb2.NumericConstant], _record_constant, parameters={"constant_list": struct_constants}) self.assertEqual(_count_entries([0, 8, 8, 8, 16, 24, 32, 16, 32, 320]), _count_entries(struct_constants)) enum_constants = [] traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.Enum, ir_pb2.NumericConstant], _record_constant, parameters={"constant_list": enum_constants}) self.assertEqual(_count_entries([1, 1, 1]), _count_entries(enum_constants))
def _find_dependency_ordering_for_fields(ir): """Populates the fields_in_dependency_order fields throughout ir.""" dependencies = {} # TODO(bolms): This duplicates work in _find_dependencies that could be # shared. traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.FieldReference], _add_field_reference_to_dependencies, skip_descendants_of={ir_pb2.Attribute}, incidental_actions={ ir_pb2.Field: _add_name_to_dependencies, ir_pb2.EnumValue: _add_name_to_dependencies, ir_pb2.RuntimeParameter: _add_name_to_dependencies, }, parameters={"dependencies": dependencies}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Structure], _find_dependency_ordering_for_fields_in_structure, parameters={"dependencies": dependencies})
def resolve_field_references(ir): """Resolves structure member accesses ("field.subfield") in ir.""" errors = [] traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.FieldReference], _resolve_field_reference, incidental_actions={ ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition, ir_pb2.Module: _set_visible_scopes_for_module, ir_pb2.Field: lambda f: { "field": f }, ir_pb2.Attribute: _set_visible_scopes_for_attribute, }, parameters={ "errors": errors, "field": None }) return errors
def compute_constants(ir): """Computes constant values for all expressions in ir. compute_constants calculates all constant values and adds them to the type information for each expression and subexpression. Arguments: ir: an IR on which to compute constants Returns: A (possibly empty) list of errors. """ traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Expression], compute_constraints_of_expression, skip_descendants_of={ir_pb2.Expression}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.RuntimeParameter], _compute_constraints_of_parameter, skip_descendants_of={ir_pb2.Expression}) return []
def test_keyword_args_dict_in_action(self): call_counts = {"populated": 0, "not": 0} def check_field_is_populated(node, **kwargs): del node # Unused. self.assertTrue(kwargs["field"]) call_counts["populated"] += 1 def check_field_is_not_populated(node, **kwargs): del node # Unused. self.assertFalse("field" in kwargs) call_counts["not"] += 1 traverse_ir.fast_traverse_ir_top_down(_EXAMPLE_IR, [ir_pb2.Field, ir_pb2.Type], check_field_is_populated) self.assertEqual(7, call_counts["populated"]) traverse_ir.fast_traverse_ir_top_down(_EXAMPLE_IR, [ir_pb2.Enum, ir_pb2.EnumValue], check_field_is_not_populated) self.assertEqual(2, call_counts["not"])
def _verify_attributes_on_ir(ir): """Verifies attributes in a complete IR.""" errors = [] traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Structure], _verify_size_attributes_on_structure, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Enum], _verify_width_attribute_on_enum, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.External], _verify_addressable_unit_attribute_on_external, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Field], _verify_field_attributes, parameters={"errors": errors}) return errors
def desugar(ir): """Translates pure syntactic sugar to its desugared form. Replaces `$next` symbols with the start+length of the previous physical field. Adds aliases for all fields in anonymous `bits` to the enclosing structure. Arguments: ir: The IR to desugar. Returns: A list of errors, or an empty list. """ errors = [] traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure], _replace_next_keyword, parameters={"errors": errors}) if errors: return errors traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure], _add_virtuals_to_structure) return []
def test_pass_only_to_sub_nodes(self): constants = [] def pass_location_down(field): return { "location": (int(field.location.start.constant.value), int(field.location.size.constant.value)) } traverse_ir.fast_traverse_ir_top_down( _EXAMPLE_IR, [ir_pb2.NumericConstant], _record_location_parameter_and_constant, incidental_actions={ir_pb2.Field: pass_location_down}, parameters={ "constant_list": constants, "location": None }) self.assertEqual( _count_entries([((0, 8), 0), ((0, 8), 8), ((8, 16), 8), ((8, 16), 8), ((8, 16), 16), ((24, 32), 24), ((24, 32), 32), ((32, 320), 16), ((32, 320), 32), ((32, 320), 320), (None, 1), (None, 1), (None, 1), (None, 64)]), _count_entries(constants))
def _resolve_symbols_from_table(ir, table): """Resolves all references in the given IR, given the constructed table.""" errors = [] # Symbol resolution is broken into five passes. First, this code resolves any # imports, and adds import aliases to modules. traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Import], _add_import_to_scope, incidental_actions={ ir_pb2.Module: lambda m, table: { "module": table[m.source_file_name] }, }, parameters={ "errors": errors, "table": table }) if errors: return errors # Next, this resolves all absolute references (e.g., it resolves "UInt" in # "0:1 UInt field" to [prelude]::UInt). traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Reference], _resolve_reference, skip_descendants_of=(ir_pb2.FieldReference, ), incidental_actions={ ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition, ir_pb2.Module: _set_visible_scopes_for_module, ir_pb2.Field: lambda f: { "field": f }, ir_pb2.Attribute: _set_visible_scopes_for_attribute, }, parameters={ "table": table, "errors": errors, "field": None }) # Lastly, head References to fields (e.g., the `a` of `a.b.c`) are resolved. traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.FieldReference], _resolve_head_of_field_reference, incidental_actions={ ir_pb2.TypeDefinition: _set_visible_scopes_for_type_definition, ir_pb2.Module: _set_visible_scopes_for_module, ir_pb2.Field: lambda f: { "field": f }, ir_pb2.Attribute: _set_visible_scopes_for_attribute, }, parameters={ "table": table, "errors": errors, "field": None }) return errors
def annotate_types(ir): """Adds type annotations to all expressions in ir. annotate_types adds type information to all expressions (and subexpressions) in the IR. Additionally, it checks expressions for internal type consistency: it will generate an error for constructs like "1 + true", where the types of the operands are not accepted by the operator. Arguments: ir: an IR to which to add type annotations Returns: A (possibly empty) list of errors. """ errors = [] traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Expression], _type_check_expression, skip_descendants_of={ir_pb2.Expression}, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.RuntimeParameter], _annotate_parameter_type, parameters={"errors": errors}) return errors
def _add_missing_attributes_on_ir(ir): """Adds missing attributes in a complete IR.""" traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.External], _add_addressable_unit_to_external) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Enum], _add_missing_width_and_sign_attributes_on_enum) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Structure], _add_missing_size_attributes_on_structure, incidental_actions={ ir_pb2.Module: _gather_default_attributes, ir_pb2.TypeDefinition: _gather_default_attributes, ir_pb2.Field: _gather_default_attributes, }, parameters={"defaults": {}}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Field], _add_missing_byte_order_attribute_on_field, incidental_actions={ ir_pb2.Module: _gather_default_attributes, ir_pb2.TypeDefinition: _gather_default_attributes, ir_pb2.Field: _gather_default_attributes, }, parameters={"defaults": {}}) return []
def check_constraints(ir): """Checks miscellaneous validity constraints in ir. Checks that auto array sizes are only used for the outermost size of multidimensional arrays. That is, Type[3][] is OK, but Type[][3] is not. Checks that fixed-size fields are a correct size to hold statically-sized types. Checks that inner array dimensions are constant. Checks that only constant-size types are used in arrays. Arguments: ir: An ir_pb2.EmbossIr object to check. Returns: A list of ConstraintViolations, or an empty list if there are none. """ errors = [] traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure, ir_pb2.Type], _check_allowed_in_bits, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( # TODO(bolms): look for [ir_pb2.ArrayType], [ir_pb2.AtomicType], and # simplify _check_that_array_base_types_are_fixed_size. ir, [ir_pb2.ArrayType], _check_that_array_base_types_are_fixed_size, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Structure, ir_pb2.ArrayType], _check_that_array_base_types_in_structs_are_multiples_of_bytes, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.ArrayType, ir_pb2.ArrayType], _check_that_inner_array_dimensions_are_constant, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure], _check_size_of_bits, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Structure, ir_pb2.Type], _check_type_requirements_for_field, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Field], _check_field_name_for_reserved_words, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.EnumValue], _check_enum_name_for_reserved_words, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.TypeDefinition], _check_type_name_for_reserved_words, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Expression], _check_constancy_of_constant_references, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Enum], _check_that_enum_values_are_representable, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Expression], _check_bounds_on_runtime_integer_expressions, incidental_actions={ir_pb2.Attribute: lambda a: { "in_attribute": a }}, skip_descendants_of={ir_pb2.EnumValue, ir_pb2.Expression}, parameters={ "errors": errors, "in_attribute": None }) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.RuntimeParameter], _check_type_requirements_for_parameter_type, parameters={"errors": errors}) return errors
def _construct_symbol_tables(ir): """Constructs per-module symbol tables for each module in ir.""" symbol_tables = {} errors = [] traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Module], _add_module_to_scope, parameters={ "errors": errors, "scope": symbol_tables }) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.TypeDefinition], _add_type_name_to_scope, incidental_actions={ir_pb2.Module: _set_scope_for_module}, parameters={ "errors": errors, "scope": symbol_tables }) if errors: # Ideally, we would find duplicate field names elsewhere in the module, even # if there are duplicate type names, but field/enum names in the colliding # types also end up colliding, leading to spurious errors. E.g., if you # have two `struct Foo`s, then the field check will also discover a # collision for `$size_in_bytes`, since there are two `Foo.$size_in_bytes`. return symbol_tables, errors traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.EnumValue], _add_enum_value_to_scope, incidental_actions={ ir_pb2.Module: _set_scope_for_module, ir_pb2.TypeDefinition: _set_scope_for_type_definition, }, parameters={ "errors": errors, "scope": symbol_tables }) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.Field], _add_struct_field_to_scope, incidental_actions={ ir_pb2.Module: _set_scope_for_module, ir_pb2.TypeDefinition: _set_scope_for_type_definition, }, parameters={ "errors": errors, "scope": symbol_tables }) traverse_ir.fast_traverse_ir_top_down(ir, [ir_pb2.RuntimeParameter], _add_parameter_name_to_scope, incidental_actions={ ir_pb2.Module: _set_scope_for_module, ir_pb2.TypeDefinition: _set_scope_for_type_definition, }, parameters={ "errors": errors, "scope": symbol_tables }) return symbol_tables, errors
def _check_attributes_in_ir(ir): """Performs basic checks on all attributes in the given ir. This function calls _check_attributes on each attribute list in ir. Arguments: ir: An ir_pb2.EmbossIr to check. Returns: A list of lists of error.error, or an empty list if there were no errors. """ def check_module(module, errors): errors.extend(_check_attributes( module.attribute, _MODULE_ATTRIBUTES, "module '{}'".format( module.source_file_name), module.source_file_name)) def check_type_definition(type_definition, source_file_name, errors): if type_definition.HasField("structure"): if type_definition.addressable_unit == ir_pb2.TypeDefinition.BYTE: errors.extend(_check_attributes( type_definition.attribute, _STRUCT_ATTRIBUTES, "struct '{}'".format( type_definition.name.name.text), source_file_name)) elif type_definition.addressable_unit == ir_pb2.TypeDefinition.BIT: errors.extend(_check_attributes( type_definition.attribute, _BITS_ATTRIBUTES, "bits '{}'".format( type_definition.name.name.text), source_file_name)) else: assert False, "Unexpected addressable_unit '{}'".format( type_definition.addressable_unit) elif type_definition.HasField("enumeration"): errors.extend(_check_attributes( type_definition.attribute, _ENUM_ATTRIBUTES, "enum '{}'".format( type_definition.name.name.text), source_file_name)) elif type_definition.HasField("external"): errors.extend(_check_attributes( type_definition.attribute, _EXTERNAL_ATTRIBUTES, "external '{}'".format( type_definition.name.name.text), source_file_name)) def check_struct_field(field, source_file_name, errors): if ir_util.field_is_virtual(field): field_attributes = _STRUCT_VIRTUAL_FIELD_ATTRIBUTES field_adjective = "virtual " else: field_attributes = _STRUCT_PHYSICAL_FIELD_ATTRIBUTES field_adjective = "" errors.extend(_check_attributes( field.attribute, field_attributes, "{}struct field '{}'".format(field_adjective, field.name.name.text), source_file_name)) errors = [] # TODO(bolms): Add a check that only known $default'ed attributes are # used. traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Module], check_module, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.TypeDefinition], check_type_definition, parameters={"errors": errors}) traverse_ir.fast_traverse_ir_top_down( ir, [ir_pb2.Field], check_struct_field, parameters={"errors": errors}) return errors