예제 #1
0
def _find_object_dependency_cycles(ir):
    """Finds dependency cycles in types in the ir."""
    dependencies, find_dependency_errors = _find_dependencies(ir)
    if find_dependency_errors:
        return find_dependency_errors
    errors = []
    cycles = _find_cycles(dict(dependencies))
    for cycle in cycles:
        # TODO(bolms): This lists the entire strongly-connected component in a
        # fairly arbitrary order.  This is simple, and handles components that
        # aren't simple cycles, but may not be the most user-friendly way to
        # present this information.
        cycle_list = sorted(list(cycle))
        node_object = ir_util.find_object(cycle_list[0], ir)
        error_group = [
            error.error(cycle_list[0][0], node_object.source_location,
                        "Dependency cycle\n" + node_object.name.name.text)
        ]
        for node in cycle_list[1:]:
            node_object = ir_util.find_object(node, ir)
            error_group.append(
                error.note(node[0], node_object.source_location,
                           node_object.name.name.text))
        errors.append(error_group)
    return errors
예제 #2
0
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.")])
예제 #3
0
def _set_expression_type_from_physical_type_reference(expression,
                                                      type_reference, ir):
    """Sets the type of an expression to match a physical type."""
    field_type = ir_util.find_object(type_reference, ir)
    assert field_type, "Field type should be non-None after name resolution."
    expression.type.CopyFrom(
        unbounded_expression_type_for_physical_type(field_type))
예제 #4
0
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)
예제 #5
0
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."
예제 #6
0
def _check_that_array_base_types_are_fixed_size(type_ir, source_file_name,
                                                errors, ir):
    """Checks that the sizes of array elements are known at compile time."""
    if type_ir.base_type.HasField("array_type"):
        # An array is fixed size if its base_type is fixed size and its array
        # dimension is constant.  This function will be called again on the inner
        # array, and we do not want to cascade errors if the inner array's base_type
        # is not fixed size.  The array dimensions are separately checked by
        # _check_that_inner_array_dimensions_are_constant, which will provide an
        # appropriate error message for that case.
        return
    assert type_ir.base_type.HasField("atomic_type")
    if type_ir.base_type.HasField("size_in_bits"):
        # If the base_type has a size_in_bits, then it is fixed size.
        return
    base_type = ir_util.find_object(type_ir.base_type.atomic_type.reference,
                                    ir)
    base_type_fixed_size = ir_util.get_integer_attribute(
        base_type.attribute, attributes.FIXED_SIZE)
    if base_type_fixed_size is None:
        errors.append([
            error.error(source_file_name,
                        type_ir.base_type.atomic_type.source_location,
                        "Array elements must be fixed size.")
        ])
예제 #7
0
def _render_atomic_type_name(type_ir, ir, suffix=None):
    assert type_ir.HasField("atomic_type"), (
        "_render_atomic_type_name() requires an atomic type")
    if not suffix:
        suffix = ""
    type_definition = ir_util.find_object(type_ir.atomic_type.reference, ir)
    if type_definition.name.is_anonymous:
        return "anonymous type"
    else:
        return "type '{}{}'".format(type_definition.name.name.text, suffix)
예제 #8
0
def _find_module_dependency_cycles(ir):
    """Finds dependency cycles in modules in the ir."""
    dependencies = _find_module_import_dependencies(ir)
    cycles = _find_cycles(dict(dependencies))
    errors = []
    for cycle in cycles:
        cycle_list = sorted(list(cycle))
        module = ir_util.find_object(cycle_list[0], ir)
        error_group = [
            error.error(cycle_list[0][0], module.source_location,
                        "Import dependency cycle\n" + module.source_file_name)
        ]
        for module_name in cycle_list[1:]:
            module = ir_util.find_object(module_name, ir)
            error_group.append(
                error.note(module_name[0], module.source_location,
                           module.source_file_name))
        errors.append(error_group)
    return errors
예제 #9
0
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
예제 #10
0
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
예제 #11
0
 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)))
예제 #12
0
 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)))
예제 #13
0
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 []
예제 #14
0
def _type_check_passed_parameters(atomic_type, ir, source_file_name, errors):
    """Checks the types of parameters to a parameterized physical type."""
    referenced_type = ir_util.find_object(atomic_type.reference.canonical_name,
                                          ir)
    if (len(referenced_type.runtime_parameter) != len(
            atomic_type.runtime_parameter)):
        errors.append([
            error.error(
                source_file_name, atomic_type.source_location,
                "Type {} requires {} parameter{}; {} parameter{} given.".
                format(
                    referenced_type.name.name.text,
                    len(referenced_type.runtime_parameter),
                    "" if len(referenced_type.runtime_parameter) == 1 else "s",
                    len(atomic_type.runtime_parameter),
                    "" if len(atomic_type.runtime_parameter) == 1 else "s")),
            error.note(
                atomic_type.reference.canonical_name.module_file,
                referenced_type.source_location,
                "Definition of type {}.".format(
                    referenced_type.name.name.text))
        ])
        return
    for i in range(len(referenced_type.runtime_parameter)):
        if referenced_type.runtime_parameter[i].type.WhichOneof(
                "type") not in ("integer", "boolean", "enumeration"):
            # _type_check_parameter will catch invalid parameter types at the
            # definition site; no need for another, probably-confusing error at any
            # usage sites.
            continue
        if (atomic_type.runtime_parameter[i].type.WhichOneof("type") !=
                referenced_type.runtime_parameter[i].type.WhichOneof("type")):
            errors.append([
                error.error(
                    source_file_name,
                    atomic_type.runtime_parameter[i].source_location,
                    "Parameter {} of type {} must be {}, not {}.".format(
                        i, referenced_type.name.name.text,
                        _type_name_for_error_messages(
                            referenced_type.runtime_parameter[i].type),
                        _type_name_for_error_messages(
                            atomic_type.runtime_parameter[i].type))),
                error.note(
                    atomic_type.reference.canonical_name.module_file,
                    referenced_type.runtime_parameter[i].source_location,
                    "Parameter {} of {}.".format(
                        i, referenced_type.name.name.text))
            ])
예제 #15
0
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."
예제 #16
0
def _check_allowed_in_bits(type_ir, type_definition, source_file_name, ir,
                           errors):
    if not type_ir.HasField("atomic_type"):
        return
    referenced_type_definition = ir_util.find_object(
        type_ir.atomic_type.reference, ir)
    if (type_definition.addressable_unit %
            referenced_type_definition.addressable_unit != 0):
        assert type_definition.addressable_unit == ir_pb2.TypeDefinition.BIT
        assert (referenced_type_definition.addressable_unit ==
                ir_pb2.TypeDefinition.BYTE)
        errors.append([
            error.error(
                source_file_name, type_ir.source_location,
                "Byte-oriented {} cannot be used in a bits field.".format(
                    _render_type(type_ir, ir)))
        ])
예제 #17
0
def _check_constancy_of_constant_references(expression, source_file_name,
                                            errors, ir):
    """Checks that constant_references are constant."""
    if expression.WhichOneof("expression") != "constant_reference":
        return
    # This is a bit of a hack: really, we want to know that the referred-to object
    # has no dependencies on any instance variables of its parent structure; i.e.,
    # that its value does not depend on having a view of the structure.
    if not ir_util.is_constant_type(expression.type):
        referred_name = expression.constant_reference.canonical_name
        referred_object = ir_util.find_object(referred_name, ir)
        errors.append([
            error.error(source_file_name, expression.source_location,
                        "Static references must refer to constants."),
            error.note(
                referred_name.module_file, referred_object.source_location,
                "{} is not constant.".format(referred_name.object_path[-1]))
        ])
예제 #18
0
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)
예제 #19
0
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))
예제 #20
0
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)
예제 #21
0
def _compute_constraints_of_existence_function(expression, ir):
    """Computes the constraints of a $has(field) expression."""
    field_path = expression.function.args[0].field_reference.path[-1]
    field = ir_util.find_object(field_path, ir)
    compute_constraints_of_expression(field.existence_condition, ir)
    expression.type.CopyFrom(field.existence_condition.type)
예제 #22
0
    def test_find_object(self):
        ir = ir_pb2.EmbossIr.from_json("""{
          "module": [
            {
              "type": [
                {
                  "structure": {
                    "field": [
                      {
                        "name": {
                          "name": { "text": "field" },
                          "canonical_name": {
                            "module_file": "test.emb",
                            "object_path": [ "Foo", "field" ]
                          }
                        }
                      }
                    ]
                  },
                  "name": {
                    "name": { "text": "Foo" },
                    "canonical_name": {
                      "module_file": "test.emb",
                      "object_path": [ "Foo" ]
                    }
                  },
                  "runtime_parameter": [
                    {
                      "name": {
                        "name": { "text": "parameter" },
                        "canonical_name": {
                          "module_file": "test.emb",
                          "object_path": [ "Foo", "parameter" ]
                        }
                      }
                    }
                  ]
                },
                {
                  "enumeration": {
                    "value": [
                      {
                        "name": {
                          "name": { "text": "QUX" },
                          "canonical_name": {
                            "module_file": "test.emb",
                            "object_path": [ "Bar", "QUX" ]
                          }
                        }
                      }
                    ]
                  },
                  "name": {
                    "name": { "text": "Bar" },
                    "canonical_name": {
                      "module_file": "test.emb",
                      "object_path": [ "Bar" ]
                    }
                  }
                }
              ],
              "source_file_name": "test.emb"
            },
            {
              "type": [
                {
                  "external": {},
                  "name": {
                    "name": { "text": "UInt" },
                    "canonical_name": {
                      "module_file": "",
                      "object_path": [ "UInt" ]
                    }
                  }
                }
              ],
              "source_file_name": ""
            }
          ]
        }""")

        # Test that find_object works with any of its four "name" types.
        canonical_name_of_foo = ir_pb2.CanonicalName(module_file="test.emb",
                                                     object_path=["Foo"])
        self.assertEqual(
            ir.module[0].type[0],
            ir_util.find_object(
                ir_pb2.Reference(canonical_name=canonical_name_of_foo), ir))
        self.assertEqual(
            ir.module[0].type[0],
            ir_util.find_object(
                ir_pb2.NameDefinition(canonical_name=canonical_name_of_foo),
                ir))
        self.assertEqual(ir.module[0].type[0],
                         ir_util.find_object(canonical_name_of_foo, ir))
        self.assertEqual(ir.module[0].type[0],
                         ir_util.find_object(("test.emb", "Foo"), ir))

        # Test that find_object works with objects other than structs.
        self.assertEqual(ir.module[0].type[1],
                         ir_util.find_object(("test.emb", "Bar"), ir))
        self.assertEqual(ir.module[1].type[0],
                         ir_util.find_object(("", "UInt"), ir))
        self.assertEqual(ir.module[0].type[0].structure.field[0],
                         ir_util.find_object(("test.emb", "Foo", "field"), ir))
        self.assertEqual(
            ir.module[0].type[0].runtime_parameter[0],
            ir_util.find_object(("test.emb", "Foo", "parameter"), ir))
        self.assertEqual(ir.module[0].type[1].enumeration.value[0],
                         ir_util.find_object(("test.emb", "Bar", "QUX"), ir))
        self.assertEqual(ir.module[0], ir_util.find_object(("test.emb", ), ir))
        self.assertEqual(ir.module[1], ir_util.find_object(("", ), ir))

        # Test searching for non-present objects.
        self.assertIsNone(ir_util.find_object_or_none(("not_test.emb", ), ir))
        self.assertIsNone(
            ir_util.find_object_or_none(("test.emb", "NotFoo"), ir))
        self.assertIsNone(
            ir_util.find_object_or_none(("test.emb", "Foo", "not_field"), ir))
        self.assertIsNone(
            ir_util.find_object_or_none(
                ("test.emb", "Foo", "field", "no_subfield"), ir))
        self.assertIsNone(
            ir_util.find_object_or_none(("test.emb", "Bar", "NOT_QUX"), ir))
        self.assertIsNone(
            ir_util.find_object_or_none(
                ("test.emb", "Bar", "QUX", "no_subenum"), ir))

        # Test that find_parent_object works with any of its four "name" types.
        self.assertEqual(
            ir.module[0],
            ir_util.find_parent_object(
                ir_pb2.Reference(canonical_name=canonical_name_of_foo), ir))
        self.assertEqual(
            ir.module[0],
            ir_util.find_parent_object(
                ir_pb2.NameDefinition(canonical_name=canonical_name_of_foo),
                ir))
        self.assertEqual(ir.module[0],
                         ir_util.find_parent_object(canonical_name_of_foo, ir))
        self.assertEqual(ir.module[0],
                         ir_util.find_parent_object(("test.emb", "Foo"), ir))

        # Test that find_parent_object works with objects other than structs.
        self.assertEqual(ir.module[0],
                         ir_util.find_parent_object(("test.emb", "Bar"), ir))
        self.assertEqual(ir.module[1],
                         ir_util.find_parent_object(("", "UInt"), ir))
        self.assertEqual(
            ir.module[0].type[0],
            ir_util.find_parent_object(("test.emb", "Foo", "field"), ir))
        self.assertEqual(
            ir.module[0].type[1],
            ir_util.find_parent_object(("test.emb", "Bar", "QUX"), ir))