def test_constant_none_value_of_operator_and_with_missing_value(self): self.assertIsNone( ir_util.constant_value(_parse_expression("true && foo"), {"bar": 12})) self.assertIsNone( ir_util.constant_value(_parse_expression("foo && true"), {"bar": 12}))
def _compute_constraints_of_field_reference(expression, ir): """Computes the constraints of a reference to a structure's field.""" field_path = expression.field_reference.path[-1] field = ir_util.find_object(field_path, ir) if isinstance(field, ir_pb2.Field) and ir_util.field_is_virtual(field): # References to virtual fields should have the virtual field's constraints # copied over. compute_constraints_of_expression(field.read_transform, ir) expression.type.CopyFrom(field.read_transform.type) return # Non-virtual non-integer fields do not (yet) have constraints. if expression.type.WhichOneof("type") == "integer": # TODO(bolms): These lines will need to change when support is added for # fixed-point types. expression.type.integer.modulus = "1" expression.type.integer.modular_value = "0" type_definition = ir_util.find_parent_object(field_path, ir) if isinstance(field, ir_pb2.Field): referrent_type = field.type else: referrent_type = field.physical_type_alias if referrent_type.HasField("size_in_bits"): type_size = ir_util.constant_value(referrent_type.size_in_bits) else: field_size = ir_util.constant_value(field.location.size) if field_size is None: type_size = None else: type_size = field_size * type_definition.addressable_unit assert referrent_type.HasField("atomic_type"), field assert not referrent_type.atomic_type.reference.canonical_name.module_file _set_integer_constraints_from_physical_type(expression, referrent_type, type_size)
def test_constant_none_value_of_operator_or_with_missing_value(self): self.assertIsNone( ir_util.constant_value(_parse_expression("foo || false"), {"bar": 12})) self.assertIsNone( ir_util.constant_value(_parse_expression("false || foo"), {"bar": 12}))
def test_constant_value_of_maximum(self): self.assertEqual( 10, ir_util.constant_value(_parse_expression("$max(5, 10)"))) self.assertEqual(10, ir_util.constant_value(_parse_expression("$max(10)"))) self.assertEqual( 10, ir_util.constant_value( _parse_expression("$max(5, 9, 7, 10, 6, -100)")))
def test_constant_false_value_of_operator_and_with_missing_value(self): self.assertIs( False, ir_util.constant_value(_parse_expression("false && foo"), {"bar": 12})) self.assertIs( False, ir_util.constant_value(_parse_expression("foo && false"), {"bar": 12}))
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 _check_that_enum_values_are_representable(enum_type, source_file_name, errors): """Checks that enumeration values can fit in an int64 or uint64.""" values = [] for value in enum_type.value: values.append((ir_util.constant_value(value.value), value)) # Guess if the user intended a signed or unsigned enumeration based on how # many values would be out of range given either type. signed_out_of_range = [ v for v in values if not -2**63 <= v[0] <= 2**63 - 1 ] unsigned_out_of_range = [v for v in values if not 0 <= v[0] <= 2**64 - 1] if len(signed_out_of_range) < len(unsigned_out_of_range): out_of_range = signed_out_of_range range_name = "signed " else: out_of_range = unsigned_out_of_range range_name = "unsigned " # If all values are in range for either a signed or an unsigned enumeration, # this loop will have zero iterations. for value in out_of_range: errors.append([ error.error( source_file_name, value[1].value.source_location, "Value {} is out of range for {}enumeration.".format( value[0], range_name if -2**63 <= value[0] <= 2**64 - 1 else "")) ])
def _check_type_requirements_for_parameter_type(runtime_parameter, ir, source_file_name, errors): """Checks that the type of a parameter is valid.""" physical_type = runtime_parameter.physical_type_alias logical_type = runtime_parameter.type size = ir_util.constant_value(physical_type.size_in_bits) if logical_type.WhichOneof("type") == "integer": integer_errors = _integer_bounds_errors(logical_type.integer, "parameter", source_file_name, physical_type.source_location) if integer_errors: errors.extend(integer_errors) return errors.extend( _check_physical_type_requirements( physical_type, runtime_parameter.source_location, size, ir, source_file_name)) elif logical_type.WhichOneof("type") == "enumeration": if physical_type.HasField("size_in_bits"): # This seems a little weird: for `UInt`, `Int`, etc., the explicit size is # required, but for enums it is banned. This is because enums have a # "native" 64-bit size in expressions, so the physical size is just # ignored. errors.extend([[ error.error( source_file_name, physical_type.size_in_bits.source_location, "Parameters with enum type may not have explicit size.") ]]) else: assert False, "Non-integer/enum parameters should have been caught earlier."
def test_constant_value_of_builtin_reference(self): self.assertEqual( 12, ir_util.constant_value( ir_pb2.Expression(builtin_reference=ir_pb2.Reference( canonical_name=ir_pb2.CanonicalName( object_path=["$foo"]))), {"$foo": 12}))
def test_constant_value_of_boolean_reference(self): self.assertTrue( ir_util.constant_value( ir_pb2.Expression( constant_reference=ir_pb2.Reference(), type=ir_pb2.ExpressionType(boolean=ir_pb2.BooleanType( value=True)))))
def _check_that_array_base_types_in_structs_are_multiples_of_bytes( type_ir, type_definition, source_file_name, errors, ir): # TODO(bolms): Remove this limitation. """Checks that the sizes of array elements are multiples of 8 bits.""" if type_ir.base_type.HasField("array_type"): # Only check the innermost array for multidimensional arrays. return assert type_ir.base_type.HasField("atomic_type") if type_ir.base_type.HasField("size_in_bits"): assert ir_util.is_constant(type_ir.base_type.size_in_bits) base_type_size = ir_util.constant_value(type_ir.base_type.size_in_bits) else: fixed_size = ir_util.fixed_size_of_type_in_bits(type_ir.base_type, ir) if fixed_size is None: # Variable-sized elements are checked elsewhere. return base_type_size = fixed_size if base_type_size % type_definition.addressable_unit != 0: assert type_definition.addressable_unit == ir_pb2.TypeDefinition.BYTE errors.append([ error.error( source_file_name, type_ir.base_type.source_location, "Array elements in structs must have sizes " "which are a multiple of 8 bits.") ])
def test_constant_value_of_enum(self): self.assertEqual( 12, ir_util.constant_value( ir_pb2.Expression( constant_reference=ir_pb2.Reference(), type=ir_pb2.ExpressionType(enumeration=ir_pb2.EnumType( value="12")))))
def test_constant_value_of_integer_reference(self): self.assertEqual( 12, ir_util.constant_value( ir_pb2.Expression( constant_reference=ir_pb2.Reference(), type=ir_pb2.ExpressionType(integer=ir_pb2.IntegerType( modulus="infinity", modular_value="12")))))
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 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 _render_type(type_ir, ir): """Returns the human-readable notation of the given type.""" assert type_ir.HasField("atomic_type"), ( "TODO(bolms): Implement _render_type for array types.") if type_ir.HasField("size_in_bits"): return _render_atomic_type_name( type_ir, ir, suffix=":" + str(ir_util.constant_value(type_ir.size_in_bits))) else: return _render_atomic_type_name(type_ir, ir)
def _fixed_size_of_struct_or_bits(struct, unit_size): """Returns size of struct in bits or None, if struct is not fixed size.""" size = 0 for field in struct.field: if not field.HasField("location"): # Virtual fields do not contribute to the physical size of the struct. continue field_start = ir_util.constant_value(field.location.start) field_size = ir_util.constant_value(field.location.size) if field_start is None or field_size is None: # Technically, start + size could be constant even if start and size are # not; e.g. if start == x and size == 10 - x, but we don't handle that # here. return None # TODO(bolms): knows_own_size # TODO(bolms): compute min/max sizes for variable-sized arrays. field_end = field_start + field_size if field_end >= size: size = field_end return size * unit_size
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 _field_may_have_null_byte_order(field, type_definition, ir): """Returns true if "Null" is a valid byte order for the given field.""" # If the field is one unit in length, then byte order does not matter. if (ir_util.is_constant(field.location.size) and ir_util.constant_value(field.location.size) == 1): return True unit = type_definition.addressable_unit # Otherwise, if the field's type is either a one-unit-sized type or an array # of a one-unit-sized type, then byte order does not matter. if (ir_util.fixed_size_of_type_in_bits(ir_util.get_base_type(field.type), ir) == unit): return True # In all other cases, byte order does matter. return False
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 _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 _compute_constant_value_of_constant_reference(expression, ir): referred_object = ir_util.find_object( expression.constant_reference.canonical_name, ir) if isinstance(referred_object, ir_pb2.EnumValue): compute_constraints_of_expression(referred_object.value, ir) assert ir_util.is_constant(referred_object.value) new_value = str(ir_util.constant_value(referred_object.value)) expression.type.enumeration.value = new_value elif isinstance(referred_object, ir_pb2.Field): assert ir_util.field_is_virtual(referred_object), ( "Non-virtual non-enum-value constant reference should have been caught " "in type_check.py") compute_constraints_of_expression(referred_object.read_transform, ir) expression.type.CopyFrom(referred_object.read_transform.type) else: assert False, "Unexpected constant reference type."
def _compute_constant_value_of_comparison_operator(expression): """Computes the constant value, if any, of a comparison operator.""" args = expression.function.args if all(ir_util.is_constant(arg) for arg in args): functions = { ir_pb2.Function.EQUALITY: operator.eq, ir_pb2.Function.INEQUALITY: operator.ne, ir_pb2.Function.LESS: operator.lt, ir_pb2.Function.LESS_OR_EQUAL: operator.le, ir_pb2.Function.GREATER: operator.gt, ir_pb2.Function.GREATER_OR_EQUAL: operator.ge, ir_pb2.Function.AND: operator.and_, ir_pb2.Function.OR: operator.or_, } func = functions[expression.function.function] expression.type.boolean.value = func( *[ir_util.constant_value(arg) for arg in args])
def _add_missing_width_and_sign_attributes_on_enum(enum, type_definition): """Sets the maximum_bits and is_signed attributes for an enum, if needed.""" max_bits_attr = ir_util.get_integer_attribute(type_definition.attribute, attributes.ENUM_MAXIMUM_BITS) if max_bits_attr is None: type_definition.attribute.extend([ _construct_integer_attribute(attributes.ENUM_MAXIMUM_BITS, _DEFAULT_ENUM_MAXIMUM_BITS, type_definition.source_location)]) signed_attr = ir_util.get_boolean_attribute(type_definition.attribute, attributes.IS_SIGNED) if signed_attr is None: for value in enum.value: numeric_value = ir_util.constant_value(value.value) if numeric_value < 0: is_signed = True break else: is_signed = False type_definition.attribute.extend([ _construct_boolean_attribute(attributes.IS_SIGNED, is_signed, type_definition.source_location)])
def test_constant_value_of_subtraction(self): self.assertEqual(-2, ir_util.constant_value(_parse_expression("2-4")))
def test_constant_value_of_integer(self): self.assertEqual(6, ir_util.constant_value(_parse_expression("6")))
def test_constant_value_of_none(self): self.assertIsNone(ir_util.constant_value(ir_pb2.Expression()))
def test_constant_value_of_addition(self): self.assertEqual(6, ir_util.constant_value(_parse_expression("2+4")))
def _field_size(field, type_definition): """Calculates the size of the given field in bits, if it is constant.""" size = ir_util.constant_value(field.location.size) if size is None: return None return size * type_definition.addressable_unit
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))