def test_adds_null_byte_order_attributes(self): ir = _make_ir_from_emb("struct Foo:\n" " 0 [+1] UInt bar\n" " 1 [+1] UInt baz\n" ' [byte_order: "LittleEndian"]\n' " 2 [+2] UInt:8[] baseball\n" " 4 [+2] UInt:8[] bat\n" ' [byte_order: "LittleEndian"]\n') self.assertEqual([], attribute_checker.normalize_and_verify(ir)) structure = ir.module[0].type[0].structure byte_order_attr = ir_util.get_attribute(structure.field[0].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("Null", byte_order_attr.string_constant.text) self.assertEqual(structure.field[0].source_location, byte_order_attr.source_location) byte_order_attr = ir_util.get_attribute(structure.field[1].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("LittleEndian", byte_order_attr.string_constant.text) byte_order_attr = ir_util.get_attribute(structure.field[2].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("Null", byte_order_attr.string_constant.text) self.assertEqual(structure.field[2].source_location, byte_order_attr.source_location) byte_order_attr = ir_util.get_attribute(structure.field[3].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("LittleEndian", byte_order_attr.string_constant.text)
def test_adds_fixed_size_attribute_to_anonymous_bits(self): struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' "struct Foo:\n" " 0 [+4] bits:\n" " 0 [+8] UInt field\n") self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) size_attr = ir_util.get_attribute( struct_ir.module[0].type[0].attribute, _FIXED_SIZE) self.assertEqual(32, ir_util.constant_value(size_attr.expression)) bits_size_attr = ir_util.get_attribute( struct_ir.module[0].type[0].subtype[0].attribute, _FIXED_SIZE) self.assertEqual(8, ir_util.constant_value(bits_size_attr.expression)) self.assertEqual(struct_ir.module[0].type[0].source_location, size_attr.source_location)
def test_adds_byte_order_attributes_from_default(self): ir = _make_ir_from_emb('[$default byte_order: "BigEndian"]\n' "struct Foo:\n" " 0 [+2] UInt bar\n" " 2 [+2] UInt baz\n" ' [byte_order: "LittleEndian"]\n') self.assertEqual([], attribute_checker.normalize_and_verify(ir)) byte_order_attr = ir_util.get_attribute( ir.module[0].type[0].structure.field[0].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("BigEndian", byte_order_attr.string_constant.text) byte_order_attr = ir_util.get_attribute( ir.module[0].type[0].structure.field[1].attribute, _BYTE_ORDER) self.assertTrue(byte_order_attr.HasField("string_constant")) self.assertEqual("LittleEndian", byte_order_attr.string_constant.text)
def _verify_requires_attribute_on_field(field, source_file_name, ir, errors): """Verifies that [requires] is valid on the given field.""" requires_attr = ir_util.get_attribute(field.attribute, attributes.REQUIRES) if not requires_attr: return if ir_util.field_is_virtual(field): field_expression_type = field.read_transform.type else: if not field.type.HasField("atomic_type"): errors.append([ error.error(source_file_name, requires_attr.source_location, "Attribute 'requires' is only allowed on integer, " "enumeration, or boolean fields, not arrays."), error.note(source_file_name, field.type.source_location, "Field type."), ]) return field_type = ir_util.find_object(field.type.atomic_type.reference, ir) assert field_type, "Field type should be non-None after name resolution." field_expression_type = ( type_check.unbounded_expression_type_for_physical_type(field_type)) if field_expression_type.WhichOneof("type") not in ( "integer", "enumeration", "boolean"): errors.append([error.error( source_file_name, requires_attr.source_location, "Attribute 'requires' is only allowed on integer, enumeration, or " "boolean fields.")])
def test_adds_max_bits_attribute(self): ir = _make_ir_from_emb("enum Foo:\n" " ZERO = 0\n") self.assertEqual([], attribute_checker.normalize_and_verify(ir)) enum = ir.module[0].type[0] max_bits_attr = ir_util.get_attribute(enum.attribute, _MAX_BITS) self.assertTrue(max_bits_attr.expression.HasField("constant")) self.assertEqual("64", max_bits_attr.expression.constant.value)
def test_adds_true_is_signed_attribute(self): ir = _make_ir_from_emb("enum Foo:\n" " NEGATIVE_ONE = -1\n") self.assertEqual([], attribute_checker.normalize_and_verify(ir)) enum = ir.module[0].type[0] is_signed_attr = ir_util.get_attribute(enum.attribute, _IS_SIGNED) self.assertTrue(is_signed_attr.expression.HasField("boolean_constant")) self.assertTrue(is_signed_attr.expression.boolean_constant.value)
def test_does_not_add_fixed_size_attribute_to_variable_size_struct(self): struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' "struct Foo:\n" " 0 [+4] UInt n\n" " 4 [+n] UInt:8[] payload\n") self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) self.assertIsNone( ir_util.get_attribute(struct_ir.module[0].type[0].attribute, _FIXED_SIZE))
def test_accepts_correct_size_attribute_on_struct(self): struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' "struct Foo:\n" " [fixed_size_in_bits: 64]\n" " 0 [+2] UInt field1\n" " 4 [+4] UInt field3\n") self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) size_attr = ir_util.get_attribute( struct_ir.module[0].type[0].attribute, _FIXED_SIZE) self.assertTrue(size_attr.expression) self.assertEqual(64, ir_util.constant_value(size_attr.expression))
def test_adds_fixed_size_attribute_to_struct_with_virtual_field(self): struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' "struct Foo:\n" " 0 [+2] UInt field1\n" " let field2 = field1\n" " 2 [+2] UInt field3\n") self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) size_attr = ir_util.get_attribute( struct_ir.module[0].type[0].attribute, _FIXED_SIZE) self.assertEqual(32, ir_util.constant_value(size_attr.expression)) self.assertEqual(struct_ir.module[0].type[0].source_location, size_attr.source_location)
def test_explicit_size_too_small(self): ir = _make_ir_from_emb("bits Foo:\n" " 0 [+0] UInt:0 zero_bit\n") error_field = ir.module[0].type[0].structure.field[0] uint_type = ir_util.find_object(error_field.type.atomic_type.reference, ir) uint_requirements = ir_util.get_attribute(uint_type.attribute, attributes.STATIC_REQUIREMENTS) self.assertEqual([[ error.error("m.emb", error_field.source_location, "Requirements of UInt not met."), error.note("", uint_requirements.source_location, "Requirements specified here."), ]], error.filter_errors(constraints.check_constraints(ir)))
def _add_missing_byte_order_attribute_on_field(field, type_definition, ir, defaults): """Adds missing byte_order attributes to fields that need them.""" if _field_needs_byte_order(field, type_definition, ir): byte_order_attr = ir_util.get_attribute(field.attribute, attributes.BYTE_ORDER) if byte_order_attr is None: if attributes.BYTE_ORDER in defaults: field.attribute.extend([defaults[attributes.BYTE_ORDER]]) elif _field_may_have_null_byte_order(field, type_definition, ir): field.attribute.extend( [_construct_string_attribute(attributes.BYTE_ORDER, "Null", field.source_location)])
def _verify_width_attribute_on_enum(enum, type_definition, source_file_name, errors): """Verifies the maximum_bits attribute for an enum TypeDefinition.""" max_bits_value = ir_util.get_integer_attribute(type_definition.attribute, attributes.ENUM_MAXIMUM_BITS) # The attribute should already have been defaulted, if not originally present. assert max_bits_value is not None, "maximum_bits not set" if max_bits_value > 64 or max_bits_value < 1: max_bits_attr = ir_util.get_attribute(type_definition.attribute, attributes.ENUM_MAXIMUM_BITS) errors.append([ error.error(source_file_name, max_bits_attr.source_location, "'maximum_bits' on an 'enum' must be between 1 and 64.") ])
def test_adds_fixed_size_attribute_to_struct(self): # field2 is intentionally after field3, in order to trigger certain code # paths in attribute_checker.py. struct_ir = _make_ir_from_emb('[$default byte_order: "LittleEndian"]\n' "struct Foo:\n" " 0 [+2] UInt field1\n" " 4 [+4] UInt field2\n" " 2 [+2] UInt field3\n") self.assertEqual([], attribute_checker.normalize_and_verify(struct_ir)) size_attr = ir_util.get_attribute( struct_ir.module[0].type[0].attribute, _FIXED_SIZE) self.assertEqual(64, ir_util.constant_value(size_attr.expression)) self.assertEqual(struct_ir.module[0].type[0].source_location, size_attr.source_location)
def test_explicit_size_too_big(self): ir = _make_ir_from_emb("struct Foo:\n" " 0 [+16] UInt:128 one_twenty_eight_bit\n" ' [byte_order: "LittleEndian"]\n') error_field = ir.module[0].type[0].structure.field[0] uint_type = ir_util.find_object(error_field.type.atomic_type.reference, ir) uint_requirements = ir_util.get_attribute(uint_type.attribute, attributes.STATIC_REQUIREMENTS) self.assertEqual([[ error.error("m.emb", error_field.source_location, "Requirements of UInt not met."), error.note("", uint_requirements.source_location, "Requirements specified here."), ]], error.filter_errors(constraints.check_constraints(ir)))
def _add_missing_size_attributes_on_structure(struct, type_definition): """Adds missing size attributes on a struct.""" fixed_size = _fixed_size_of_struct_or_bits(struct, type_definition.addressable_unit) if fixed_size is None: return fixed_size_attr = ir_util.get_attribute(type_definition.attribute, attributes.FIXED_SIZE) if not fixed_size_attr: # TODO(bolms): Use the offset and length of the last field as the # source_location of the fixed_size attribute? type_definition.attribute.extend([ _construct_integer_attribute(attributes.FIXED_SIZE, fixed_size, type_definition.source_location)])
def test_get_attribute(self): type_def = ir_pb2.TypeDefinition(attribute=[ ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=ir_pb2.Expression()), name=ir_pb2.Word(text="phil")), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("false")), name=ir_pb2.Word(text="bob"), is_default=True), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("true")), name=ir_pb2.Word(text="bob")), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("false")), name=ir_pb2.Word(text="bob2")), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("true")), name=ir_pb2.Word(text="bob2"), is_default=True), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("false")), name=ir_pb2.Word(text="bob3"), is_default=True), ir_pb2.Attribute(value=ir_pb2.AttributeValue( expression=_parse_expression("false")), name=ir_pb2.Word()), ]) self.assertEqual( ir_pb2.AttributeValue(expression=_parse_expression("true")), ir_util.get_attribute(type_def.attribute, "bob")) self.assertEqual( ir_pb2.AttributeValue(expression=_parse_expression("false")), ir_util.get_attribute(type_def.attribute, "bob2")) self.assertEqual(None, ir_util.get_attribute(type_def.attribute, "Bob")) self.assertEqual(None, ir_util.get_attribute(type_def.attribute, "bob3"))
def _check_physical_type_requirements(type_ir, usage_source_location, size, ir, source_file_name): """Checks that the given atomic `type_ir` is allowed to be `size` bits.""" referenced_type_definition = ir_util.find_object( type_ir.atomic_type.reference, ir) # TODO(bolms): replace this with a check against an automatically-generated # `static_requirements` attribute on enum types. (The main problem is that # the generated attribute would have no source text, so there would be a crash # when trying to display the error.) if referenced_type_definition.HasField("enumeration"): if size is None: return [[ error.error( source_file_name, type_ir.source_location, "Enumeration {} cannot be placed in a dynamically-sized " "field.".format(_render_type(type_ir, ir))) ]] elif size < 1 or size > 64: return [[ error.error( source_file_name, type_ir.source_location, "Enumeration {} cannot be {} bits; enumerations must be between " "1 and 64 bits, inclusive.".format( _render_atomic_type_name(type_ir, ir), size)) ]] if size is None: bindings = {"$is_statically_sized": False} else: bindings = {"$is_statically_sized": True, "$static_size_in_bits": size} requires_attr = ir_util.get_attribute(referenced_type_definition.attribute, attributes.STATIC_REQUIREMENTS) if requires_attr and not ir_util.constant_value(requires_attr.expression, bindings): # TODO(bolms): Figure out a better way to build this error message. # The "Requirements specified here." message should print out the actual # source text of the requires attribute, so that should help, but it's still # a bit generic and unfriendly. return [[ error.error( source_file_name, usage_source_location, "Requirements of {} not met.".format( type_ir.atomic_type.reference.canonical_name. object_path[-1])), error.note( type_ir.atomic_type.reference.canonical_name.module_file, requires_attr.source_location, "Requirements specified here.") ]] return []
def _verify_size_attributes_on_structure(struct, type_definition, source_file_name, errors): """Verifies size attributes on a struct or bits.""" fixed_size = _fixed_size_of_struct_or_bits(struct, type_definition.addressable_unit) fixed_size_attr = ir_util.get_attribute(type_definition.attribute, attributes.FIXED_SIZE) if not fixed_size_attr: return if fixed_size is None: errors.append([error.error( source_file_name, fixed_size_attr.source_location, "Struct is marked as fixed size, but contains variable-location " "fields.")]) elif ir_util.constant_value(fixed_size_attr.expression) != fixed_size: errors.append([error.error( source_file_name, fixed_size_attr.source_location, "Struct is {} bits, but is marked as {} bits.".format( fixed_size, ir_util.constant_value(fixed_size_attr.expression)))])
def _verify_byte_order_attribute_on_field(field, type_definition, source_file_name, ir, errors): """Verifies the byte_order attribute on the given field.""" byte_order_attr = ir_util.get_attribute(field.attribute, attributes.BYTE_ORDER) field_needs_byte_order = _field_needs_byte_order(field, type_definition, ir) if byte_order_attr and not field_needs_byte_order: errors.append([error.error( source_file_name, byte_order_attr.source_location, "Attribute 'byte_order' not allowed on field which is not byte order " "dependent.")]) if not byte_order_attr and field_needs_byte_order: errors.append([error.error( source_file_name, field.source_location, "Attribute 'byte_order' required on field which is byte order " "dependent.")]) if (byte_order_attr and byte_order_attr.string_constant.text == "Null" and not _field_may_have_null_byte_order(field, type_definition, ir)): errors.append([error.error( source_file_name, byte_order_attr.source_location, "Attribute 'byte_order' may only be 'Null' for one-byte fields.")])
def _add_write_method(field, ir): """Adds an appropriate write_method to field, if applicable. Currently, the "alias" write_method will be added for virtual fields of the form `let v = some_field_reference` when `some_field_reference` is a physical field or a writeable alias. The "physical" write_method will be added for physical fields. The "transform" write_method will be added when the virtual field's value is an easily-invertible function of a single writeable field. All other fields will have the "read_only" write_method; i.e., they will not be writeable. Arguments: field: an ir_pb2.Field to which to add a write_method. ir: The IR in which to look up field_references. Returns: None """ if field.HasField("write_method"): # Do not recompute anything. return if not ir_util.field_is_virtual(field): # If the field is not virtual, writes are physical. field.write_method.physical = True return # A virtual field cannot be a direct alias if it has an additional # requirement. requires_attr = ir_util.get_attribute(field.attribute, attributes.REQUIRES) if (field.read_transform.WhichOneof("expression") != "field_reference" or requires_attr is not None): inverse = _invert_expression(field.read_transform, ir) if inverse: field_reference, function_body = inverse referenced_field = ir_util.find_object( field_reference.field_reference.path[-1], ir) if not isinstance(referenced_field, ir_pb2.Field): reference_is_read_only = True else: _add_write_method(referenced_field, ir) reference_is_read_only = referenced_field.write_method.read_only if not reference_is_read_only: field.write_method.transform.destination.CopyFrom( field_reference.field_reference) field.write_method.transform.function_body.CopyFrom(function_body) else: # If the virtual field's expression is invertible, but its target field # is read-only, it is also read-only. field.write_method.read_only = True else: # If the virtual field's expression is not invertible, it is # read-only. field.write_method.read_only = True return referenced_field = ir_util.find_object( field.read_transform.field_reference.path[-1], ir) if not isinstance(referenced_field, ir_pb2.Field): # If the virtual field aliases a non-field (i.e., a parameter), it is # read-only. field.write_method.read_only = True return _add_write_method(referenced_field, ir) if referenced_field.write_method.read_only: # If the virtual field directly aliases a read-only field, it is read-only. field.write_method.read_only = True return # Otherwise, it can be written as a direct alias. field.write_method.alias.CopyFrom( field.read_transform.field_reference)
def _check_type_requirements_for_field(type_ir, type_definition, field, ir, source_file_name, errors): """Checks that the `requires` attribute of each field's type is fulfilled.""" if not type_ir.HasField("atomic_type"): return if field.type.HasField("atomic_type"): field_min_size = (int(field.location.size.type.integer.minimum_value) * type_definition.addressable_unit) field_max_size = (int(field.location.size.type.integer.maximum_value) * type_definition.addressable_unit) field_is_atomic = True else: field_is_atomic = False if type_ir.HasField("size_in_bits"): element_size = ir_util.constant_value(type_ir.size_in_bits) else: element_size = None referenced_type_definition = ir_util.find_object( type_ir.atomic_type.reference, ir) type_is_anonymous = referenced_type_definition.name.is_anonymous type_size_attr = ir_util.get_attribute( referenced_type_definition.attribute, attributes.FIXED_SIZE) if type_size_attr: type_size = ir_util.constant_value(type_size_attr.expression) else: type_size = None if (element_size is not None and type_size is not None and element_size != type_size): errors.append([ error.error( source_file_name, type_ir.size_in_bits.source_location, "Explicit size of {} bits does not match fixed size ({} bits) of " "{}.".format(element_size, type_size, _render_atomic_type_name(type_ir, ir))), error.note( type_ir.atomic_type.reference.canonical_name.module_file, type_size_attr.source_location, "Size specified here.") ]) return # If the type had no size specifier (the ':32' in 'UInt:32'), but the type is # fixed size, then continue as if the type's size were explicitly stated. if element_size is None: element_size = type_size # TODO(bolms): When the full dynamic size expression for types is generated, # add a check that dynamically-sized types can, at least potentially, fit in # their fields. if field_is_atomic and element_size is not None: # If the field has a fixed size, and the (atomic) type contained therein is # also fixed size, then the sizes should match. # # TODO(bolms): Maybe change the case where the field is bigger than # necessary into a warning? if (field_max_size == field_min_size and (element_size > field_max_size or (element_size < field_min_size and not type_is_anonymous))): errors.append([ error.error( source_file_name, type_ir.source_location, "Fixed-size {} cannot be placed in field of size {} bits; " "requires {} bits.".format(_render_type(type_ir, ir), field_max_size, element_size)) ]) return elif element_size > field_max_size: errors.append([ error.error( source_file_name, type_ir.source_location, "Field of maximum size {} bits cannot hold fixed-size {}, which " "requires {} bits.".format(field_max_size, _render_type(type_ir, ir), element_size)) ]) return # If we're here, then field/type sizes are consistent. if (element_size is None and field_is_atomic and field_min_size == field_max_size): # From here down, we just use element_size. element_size = field_min_size errors.extend( _check_physical_type_requirements(type_ir, field.source_location, element_size, ir, source_file_name))