Browse Source

Merge pull request #4420 from laytan/cbor-better-handling-of-mismatch-in-struct-and-binary-fields

encoding/cbor: handle binary having more fields than the struct by discarding
Laytan 9 months ago
parent
commit
1e3a924e11
2 changed files with 64 additions and 1 deletions
  1. 13 1
      core/encoding/cbor/unmarshal.odin
  2. 51 0
      tests/core/encoding/cbor/test_core_cbor.odin

+ 13 - 1
core/encoding/cbor/unmarshal.odin

@@ -628,7 +628,8 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 		unknown := length == -1
 		unknown := length == -1
 		fields := reflect.struct_fields_zipped(ti.id)
 		fields := reflect.struct_fields_zipped(ti.id)
 
 
-		for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
+		idx := 0
+		for ; idx < len(fields) && (unknown || idx < length); idx += 1 {
 			// Decode key, keys can only be strings.
 			// Decode key, keys can only be strings.
 			key: string
 			key: string
 			if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
 			if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
@@ -673,6 +674,17 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 			fany  := any{ptr, field.type.id}
 			fany  := any{ptr, field.type.id}
 			_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
 			_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
 		}
 		}
+
+		// If there are fields left in the map that did not get decoded into the struct, decode and discard them.
+		if !unknown {
+			for _ in idx..<length {
+				key := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+				destroy(key, context.temp_allocator)
+				val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+				destroy(val, context.temp_allocator)
+			}
+		}
+
 		return
 		return
 
 
 	case reflect.Type_Info_Map:
 	case reflect.Type_Info_Map:

+ 51 - 0
tests/core/encoding/cbor/test_core_cbor.odin

@@ -382,6 +382,57 @@ test_lying_length_array :: proc(t: ^testing.T) {
 	testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad.
 	testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad.
 }
 }
 
 
+@(test)
+test_unmarshal_map_into_struct_partially :: proc(t: ^testing.T) {
+
+	// Tests that when unmarshalling into a struct that has less fields than the binary map has fields,
+	// the additional fields in the binary are skipped and the rest of the binary is correctly decoded.
+
+	Foo :: struct {
+		bar: struct {
+			hello: string,
+			world: string,
+		},
+		baz: int,
+	}
+
+	Foo_More :: struct {
+		bar: struct {
+			hello:   string,
+			world:   string,
+			hellope: string,
+		},
+		baz: int,
+	}
+	more := Foo_More{
+		bar = {
+			hello   = "hello",
+			world   = "world",
+			hellope = "hellope",
+		},
+		baz = 4,
+	}
+
+	more_bin, err := cbor.marshal(more)
+	testing.expect_value(t, err, nil)
+
+	less := Foo{
+		bar = {
+			hello = "hello",
+			world = "world",
+		},
+		baz = 4,
+	}
+	less_out: Foo
+	uerr := cbor.unmarshal(string(more_bin), &less_out)
+	testing.expect_value(t, uerr, nil)
+	testing.expect_value(t, less, less_out)
+
+	delete(more_bin)
+	delete(less_out.bar.hello)
+	delete(less_out.bar.world)
+}
+
 @(test)
 @(test)
 test_decode_unsigned :: proc(t: ^testing.T) {
 test_decode_unsigned :: proc(t: ^testing.T) {
 	expect_decoding(t, "\x00", "0", u8)
 	expect_decoding(t, "\x00", "0", u8)