Data Encoding for Exceptions

10 min read

10 min read

10 min read

10 min read

10 min read

10 min read

10 min read

10 min read

10 min read

An exception is marshaled as shown below:

The encoding 1.0 exceptions layout starts with a uses-classes flag, followed by a type ID and a slice of fields for each inheritance level. Optional class instances follow the exception slices.

Marshaling format for exceptions.

Every exception instance is preceded by a single byte that indicates whether the exception uses class fields: the byte value is 1 if any of the exception fields are classes (or if any of the exception fields, recursively, contain class fields) and 0, otherwise.

Following the header byte, the exception is marshaled as a sequence of pairs: the first field of each pair is the type ID for an exception slice, and the second field of the pair is a slice containing the marshaled fields of that slice. The sequence of pairs is marshaled in derived-to-base order, with the most-derived slice first, and ending with the least-derived slice. Within each slice, fields are marshaled as for structures: in the order in which they are defined in the Slice definition.

Following the sequence of pairs, any class instances that are used by the fields of the exception are marshaled. This final part is optional: it is present only if the header byte is 1.

To illustrate the marshaling, consider the following exception hierarchy:

Slice
exception Base
{
int baseInt;
string baseString;
}
exception Derived extends Base
{
bool derivedBool;
string derivedString;
double derivedDouble;
}

Assume that the exception fields are initialized to the values shown below:

FieldTypeValueMarshaled size (in bytes)
baseInt
int
99
4
baseString
string
"Hello"
6
derivedBool
bool
true
1
derivedString
string
"World!"
7
derivedDouble
double
3.14
8

Field values of an exception of typeDerived.

From the above table, we can see that the total size of the fields of Base is 10 bytes, and the total size of the fields of Derived is 16 bytes. None of the exception fields are classes. An instance of this exception has the on-the-wire representation shown in the next table. (The size, type, and byte offset of the marshaled representation is indicated for each component.)

Marshaled valueSize in bytesTypeByte offset
0 (no class fields)
1
bool
0
"::Derived" (type ID)
10
string
1
20 (byte count for slice)
4
int
11
1 (derivedBool)
1
bool
15
"World!" (derivedString)
7
string
16
3.14 (derivedDouble)
8
double
23
"::Base" (type ID)
7
string
31
14 (byte count for slice)
4
int
38
99 (baseInt)
4
int
42
"Hello" (baseString)
6
string
46

Marshaled representation of the exception.

Note that the size of each string is one larger than the actual string length. This is because each string is preceded by a count of its number of bytes, as directed by the encoding for strings.

If an exception contains class fields, these fields are marshaled following the exception slices as described in the class encoding.

An exception is marshaled as a collection of slices whose order matches the inheritance hierarchy, with the most-derived type appearing first. The selected encoding format affects the content of each slice. The final slice, representing the least-derived type, has its last slice bit set to true.

An exception in the compact format is marshaled as follows:

In the compact exceptions format, each slice, from the most-derived to the least-derived, contains slice flags, a type ID, required fields, and optional fields when needed.

Compact format for exceptions.

The leading byte of each slice is a set of bit flags that specifies the features of the slice. Each slice includes its type ID, encoded as a string.

The sliced format adds a slice size to every slice, along with an optional indirection table:

The sliced exceptions format repeats slice flags, a type ID, slice size, required fields, optional fields when needed, and an indirection table when needed for each inheritance level.

Sliced format for exceptions.

To illustrate the marshaling, consider the following exception hierarchy:

Slice
exception Base
{
int baseInt;
string baseString;
}
exception Derived extends Base
{
bool derivedBool;
string derivedString;
double derivedDouble;
}

Assume that the exception fields are initialized to the values shown below:

FieldTypeValueMarshaled size (in bytes)
baseInt
int
99
4
baseString
string
"Hello"
6
derivedBool
bool
true
1
derivedString
string
"World!"
7
derivedDouble
double
3.14
8

Field values of an exception of typeDerived.

From the above table, we can see that the total size of the fields of Base is 10 bytes, and the total size of the fields of Derived is 16 bytes. None of the exception fields are classes. An instance of this exception using the sliced format has the on-the-wire representation shown in the next table. (The size, type, and byte offset of the marshaled representation is indicated for each component.)

Marshaled valueSize in bytesTypeByte offset
16 (flags: slice size is present)
1
byte
0
"::Derived" (type ID)
10
string
1
20 (byte count for slice)
4
int
11
1 (derivedBool)
1
bool
15
"World!" (derivedString)
7
string
16
3.14 (derivedDouble)
8
double
23
48 (flags: slice size is present, last slice)
1
byte
31
"::Base" (type ID)
7
string
32
14 (byte count for slice)
4
int
39
99 (baseInt)
4
int
43
"Hello" (baseString)
6
string
47

Marshaled representation of the exception using the sliced format.

Note that the size of each string is one larger than the actual string length. This is because each string is preceded by a count of its number of bytes, as directed by the encoding for strings.

A receiver that does not know the type ID of the first slice uses the slice size to skip to the next slice, and repeats until it reaches a type ID its Slice loader knows: it then unmarshals the exception as this type. For example, a receiver that knows Base but not Derived reads the slice size 20, skips 20-4 bytes to the flags of the ::Base slice at byte offset 31, and unmarshals the exception as Base. When it reaches the last slice without finding a type ID it knows, it throws MarshalException.

Repeating this exercise using the compact format produces the following encoding:

Marshaled valueSize in bytesTypeByte offset
0 (flags: none)
1
byte
0
"::Derived" (type ID)
10
string
1
1 (derivedBool)
1
bool
11
"World!" (derivedString)
7
string
12
3.14 (derivedDouble)
8
double
19
32 (flags: last slice)
1
byte
27
"::Base" (type ID)
7
string
28
99 (baseInt)
4
int
35
"Hello" (baseString)
6
string
39

Marshaled representation of the exception using the compact format.

With the compact format, a receiver decodes the exception only if it knows the most-derived type: without slice sizes, it cannot skip a slice it does not know how to decode, so it throws MarshalException.