XINCGer/Unity3DTraining · error · DescriptorValidationException

FieldDescriptorProto.oneof_index is out of range for type

Error message

FieldDescriptorProto.oneof_index is out of range for type {parent.Name}

What it means

Google.Protobuf throws this DescriptorValidationException while building a FieldDescriptor when the proto's oneof_index does not point at a valid entry in the parent message's oneof declaration list. A field that declares it belongs to a oneof must reference an existing oneof_decl; an index of -1 means 'no oneof' and is handled specially. Any other out-of-range index means the file descriptor is internally inconsistent.

Solutions

  1. Regenerate the descriptor from the authoritative .proto source with protoc so oneof_index values are recomputed consistently.
  2. If building descriptors in code, add all OneofDecl entries to the message proto BEFORE appending fields whose OneofIndex refers to them.
  3. Validate each field's OneofIndex (-1 or 0..OneofDecl.Count-1) against the parent's OneofDecl list before deserializing/constructing descriptors from untrusted data.

Example fix

// before: field added before its oneof decl
msg.Field.Add(new FieldDescriptorProto { Name = "val", OneofIndex = 0 });
msg.OneofDecl.Add(new OneofDescriptorProto { Name = "choice" });
// after: declare the oneof first
msg.OneofDecl.Add(new OneofDescriptorProto { Name = "choice" });
msg.Field.Add(new FieldDescriptorProto { Name = "val", OneofIndex = 0 });
Defensive patterns

Strategy: validation

Validate before calling

bool ok = field.Proto.OneofIndex == -1
    || (field.Proto.OneofIndex >= 0 && field.Proto.OneofIndex < parent.Proto.OneofDecl.Count);
if (!ok) throw new InvalidDataException($"oneof_index {field.Proto.OneofIndex} out of range for {parent.Name}");

Try / catch

try { descriptor = FileDescriptor.BuildFromByteStrings(data); }
catch (DescriptorValidationException ex) { log.LogError(ex, "Invalid descriptor: " + ex.Message); }
// DescriptorValidationException.Message includes the offending field name.

Prevention

When it happens

Trigger: Building descriptor objects from a FileDescriptorSet/proto bytes where a field's oneof_index is negative (other than -1) or >= parent.Proto.OneofDecl.Count, e.g. a hand-edited or corrupted serialized FileDescriptorProto, or programmatically constructed protos where Oneofs were added to the parent after the fields referencing them by index.

Common situations: Manually assembling FileDescriptorProto in code and appending fields before their oneof declarations; regenerating descriptors with mismatched toolchain versions; corrupt or tampered descriptor payloads received over the network (this plugin ships in a Unity socket/networking project).

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/e38b8e8537e9b7c1. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/Reflection/FieldDescriptor.cs:87

            : base(file, file.ComputeFullName(parent, proto.Name), index)
        {
            Proto = proto;
            if (proto.Type != 0)
            {
                fieldType = GetFieldTypeFromProtoType(proto.Type);
            }

            if (FieldNumber <= 0)
            {
                throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
            }
            ContainingType = parent;
            // OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction.
            if (proto.OneofIndex != -1)
            {
                if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
                {
                    throw new DescriptorValidationException(this, "FieldDescriptorProto.oneof_index is out of range for type " + parent.Name);
                }
                ContainingOneof = parent.Oneofs[proto.OneofIndex];
            }

            file.DescriptorPool.AddSymbol(this);
            // We can't create the accessor until we've cross-linked, unfortunately, as we
            // may not know whether the type of the field is a map or not. Remember the property name
            // for later.
            // We could trust the generated code and check whether the type of the property is
            // a MapField, but that feels a tad nasty.
            this.propertyName = propertyName;
            JsonName = Proto.JsonName == "" ? JsonFormatter.ToJsonName(Proto.Name) : Proto.JsonName;
        }


        /// <summary>
        /// The brief name of the descriptor's target.
        /// </summary>

View on GitHub (pinned to 016f98412e)