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 _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 _add_size_virtuals(structure, type_definition): """Adds a $size_in_bits or $size_in_bytes virtual field to structure.""" names = { ir_pb2.TypeDefinition.BIT: "$size_in_bits", ir_pb2.TypeDefinition.BYTE: "$size_in_bytes", } size_field_name = names[type_definition.addressable_unit] size_clauses = [] for field in structure.field: # Virtual fields do not have a physical location, and thus do not contribute # to the size of the structure. if ir_util.field_is_virtual(field): continue size_clause = ir_pb2.Expression() size_clause.CopyFrom(_SIZE_CLAUSE_SKELETON) # Copy the appropriate clauses into `existence_condition ? start + size : 0` size_clause.function.args[0].CopyFrom(field.existence_condition) size_clause.function.args[1].function.args[0].CopyFrom( field.location.start) size_clause.function.args[1].function.args[1].CopyFrom( field.location.size) size_clauses.append(size_clause) size_expression = ir_pb2.Expression() size_expression.CopyFrom(_SIZE_SKELETON) size_expression.function.args.extend(size_clauses) _mark_as_synthetic(size_expression) size_field = ir_pb2.Field( read_transform=size_expression, name=ir_pb2.NameDefinition(name=ir_pb2.Word(text=size_field_name)), existence_condition=ir_pb2.Expression( boolean_constant=ir_pb2.BooleanConstant(value=True)), attribute=[_skip_text_output_attribute()]) structure.field.extend([size_field])
def _type_check_constant_reference(expression, source_file_name, ir, errors): """Annotates the type of a constant reference.""" referred_name = expression.constant_reference.canonical_name referred_object = ir_util.find_object(referred_name, ir) if isinstance(referred_object, ir_pb2.EnumValue): expression.type.enumeration.name.CopyFrom( expression.constant_reference) del expression.type.enumeration.name.canonical_name.object_path[-1] elif isinstance(referred_object, ir_pb2.Field): if not ir_util.field_is_virtual(referred_object): errors.append([ error.error( source_file_name, expression.source_location, "Static references to physical fields are not allowed."), error.note( referred_name.module_file, referred_object.source_location, "{} is a physical field.".format( referred_name.object_path[-1])), ]) return _type_check_expression(referred_object.read_transform, referred_name.module_file, ir, errors) expression.type.CopyFrom(referred_object.read_transform.type) else: assert False, "Unexpected constant reference type."
def _resolve_field_reference(field_reference, source_file_name, errors, ir): """Resolves the References inside of a FieldReference.""" if field_reference.path[-1].HasField("canonical_name"): # Already done. return previous_field = ir_util.find_object_or_none(field_reference.path[0], ir) previous_reference = field_reference.path[0] for ref in field_reference.path[1:]: while ir_util.field_is_virtual(previous_field): if (previous_field.read_transform.WhichOneof("expression") == "field_reference"): # Pass a separate error list into the recursive _resolve_field_reference # call so that only one copy of the error for a particular reference # will actually surface: in particular, the one that results from a # direct call from traverse_ir_top_down into _resolve_field_reference. new_errors = [] _resolve_field_reference( previous_field.read_transform.field_reference, previous_field.name.canonical_name.module_file, new_errors, ir) # If the recursive _resolve_field_reference was unable to resolve the # field, then bail. Otherwise we get a cascade of errors, where an # error in `x` leads to errors in anything trying to reach a member of # `x`. if not previous_field.read_transform.field_reference.path[ -1].HasField("canonical_name"): return previous_field = ir_util.find_object( previous_field.read_transform.field_reference.path[-1], ir) else: errors.append( noncomposite_subfield_error( source_file_name, previous_reference.source_location, previous_reference.source_name[0].text)) return if previous_field.type.WhichOneof("type") == "array_type": errors.append( array_subfield_error(source_file_name, previous_reference.source_location, previous_reference.source_name[0].text)) return assert previous_field.type.WhichOneof("type") == "atomic_type" member_name = ir_pb2.CanonicalName() member_name.CopyFrom( previous_field.type.atomic_type.reference.canonical_name) member_name.object_path.extend([ref.source_name[0].text]) previous_field = ir_util.find_object_or_none(member_name, ir) if previous_field is None: errors.append( missing_name_error(source_file_name, ref.source_name[0].source_location, ref.source_name[0].text)) return ref.canonical_name.CopyFrom(member_name) previous_reference = ref
def _field_needs_byte_order(field, type_definition, ir): """Returns true if the given field needs a byte_order attribute.""" if ir_util.field_is_virtual(field): # Virtual fields have no physical type, and thus do not need a byte order. return False field_type = ir_util.find_object( ir_util.get_base_type(field.type).atomic_type.reference.canonical_name, ir) assert field_type is not None assert field_type.addressable_unit != ir_pb2.TypeDefinition.NONE return field_type.addressable_unit != type_definition.addressable_unit
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))
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 _replace_next_keyword(structure, source_file_name, errors): last_physical_field_location = None new_errors = [] for field in structure.field: if ir_util.field_is_virtual(field): # TODO(bolms): It could be useful to allow `$next` in a virtual field, in # order to reuse the value (say, to allow overlapping fields in a # mostly-packed structure), but it seems better to add `$end_of(field)`, # `$offset_of(field)`, and `$size_of(field)` constructs of some sort, # instead. continue traverse_ir.fast_traverse_node_top_down( field.location.size, [ir_pb2.Expression], _check_for_bad_next_keyword_in_size, parameters={ "errors": new_errors, "source_file_name": source_file_name, }) # If `$next` is misused in a field size, it can end up causing a # `RecursionError` in fast_traverse_node_top_down. (When the `$next` node # in the next field is replaced, its replacement gets traversed, but the # replacement also contains a `$next` node, leading to infinite recursion.) # # Technically, we could scan all of the sizes instead of bailing early, but # it seems relatively unlikely that someone will have `$next` in multiple # sizes and not figure out what is going on relatively quickly. if new_errors: errors.extend(new_errors) return traverse_ir.fast_traverse_node_top_down( field.location.start, [ir_pb2.Expression], _maybe_replace_next_keyword_in_expression, parameters={ "last_location": last_physical_field_location, "errors": new_errors, "source_file_name": source_file_name, }) # The only possible error from _maybe_replace_next_keyword_in_expression is # `$next` occurring in the start expression of the first physical field, # which leads to similar recursion issue if `$next` is used in the start # expression of the next physical field. if new_errors: errors.extend(new_errors) return last_physical_field_location = field.location
def _type_check_local_reference(expression, ir, errors): """Annotates the type of a local reference.""" referrent = ir_util.find_object(expression.field_reference.path[-1], ir) assert referrent, "Local reference should be non-None after name resolution." if isinstance(referrent, ir_pb2.RuntimeParameter): parameter = referrent _set_expression_type_from_physical_type_reference( expression, parameter.physical_type_alias.atomic_type.reference, ir) return field = referrent if ir_util.field_is_virtual(field): _type_check_expression(field.read_transform, expression.field_reference.path[0], ir, errors) expression.type.CopyFrom(field.read_transform.type) return if not field.type.HasField("atomic_type"): expression.type.opaque.CopyFrom(ir_pb2.OpaqueType()) else: _set_expression_type_from_physical_type_reference( expression, field.type.atomic_type.reference, ir)
def test_field_is_not_virtual(self): self.assertFalse( ir_util.field_is_virtual( ir_pb2.Field(location=ir_pb2.FieldLocation())))
def test_field_is_virtual(self): self.assertTrue(ir_util.field_is_virtual(ir_pb2.Field()))
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)