diff --git a/svf_runtime/src/svf_compatibility.c b/svf_runtime/src/svf_compatibility.c index 2f922b0..902fec9 100644 --- a/svf_runtime/src/svf_compatibility.c +++ b/svf_runtime/src/svf_compatibility.c @@ -3,341 +3,544 @@ #include "svf_internal.h" #endif +// Note: #unsafe-naming-semantics. +// +// We are reading potentially adversarial data, and it's important to delineate, +// which is which. "Unsafe" value in this context means that is is accessible, +// but may contain something completely unexpected or malicious. This concerns: +// +// - Value types, e.g. `unsafe_index` would mean that the index itself may be +// out of bounds. +// +// - Range types, e.g. `unsafe_xs` would mean that a contiguous memory area +// with X-s is accessible, but the X-s themselves can be anything. +// +// - Pointer types, which can be dereferenced, but the value cannot be relied upon. +// +// For all pointers, nullability is a separate concern and is handled independedntly +// of this. + +typedef struct SVFRT_IndexPair { + uint32_t index_src; + uint32_t index_dst; +} SVFRT_IndexPair; +// TODO: sizeof must be 8, and alignof must be 4. Check this at compile time. + +typedef struct SVFRT_IndexPairQueue { + SVFRT_IndexPair *pointer; + uint32_t capacity; + uint32_t occupied; +} SVFRT_IndexPairQueue; + typedef struct SVFRT_CheckContext { - // The entry structure for both schemas. We are going to check the "backward" - // (write Schema 0, then read Schema 1) compatibility. - SVF_META_Schema* s0; - SVF_META_Schema* s1; + // The root definition for both schemas. + SVF_META_Schema* unsafe_definition_src; + SVF_META_Schema* definition_dst; // Byte ranges of the two schemas. - SVFRT_Bytes r0; - SVFRT_Bytes r1; + SVFRT_Bytes unsafe_schema_src; + SVFRT_Bytes schema_dst; + + SVFRT_RangeStructDefinition unsafe_structs_src; + SVFRT_RangeStructDefinition structs_dst; + + SVFRT_RangeChoiceDefinition unsafe_choices_src; + SVFRT_RangeChoiceDefinition choices_dst; - // What do Schema 1 structs/choices match to in Schema 0? - SVFRT_RangeU32 s1_struct_matches; - SVFRT_RangeU32 s1_choice_matches; + // What do each of dst-schema structs/choices match to in src-schema? + SVFRT_RangeU32 dst_to_src_struct_matches; + SVFRT_RangeU32 dst_to_src_choice_matches; // What stride should be used when indexing a sequence of structs? With binary - // compatibility, it is not size of the Schema 1 struct, even though we are - // accessing the data through it. It is actually the size of the Schema 0 + // compatibility, it is not size of the dst-schema struct, even though we are + // accessing the data through it. It is actually the size of the src-schema // struct, since that's what the original sequence was using as the stride. - SVFRT_RangeU32 s1_struct_strides; + SVFRT_RangeU32 unsafe_struct_strides_dst; + + // Fixed-size FIFO queues in scratch memory to avoid using the real stack, and + // avoid any potential overflow problems. + SVFRT_IndexPairQueue struct_queue; + SVFRT_IndexPairQueue choice_queue; // Lowest level seen so far. Should be >= required level. SVFRT_CompatibilityLevel current_level; // Required level. We can exit early if we see that something does not match it. SVFRT_CompatibilityLevel required_level; + + uint32_t work_max; + uint32_t work_done; + + SVFRT_ErrorCode error_code; // See `SVFRT_code_compatibility__*`. } SVFRT_CheckContext; -bool SVFRT_check_struct( +void SVFRT_check_add_struct( SVFRT_CheckContext *ctx, - uint32_t s0_index, - uint32_t s1_index -); + uint32_t struct_index_src, + uint32_t struct_index_dst +) { + uint32_t match = ctx->dst_to_src_struct_matches.pointer[struct_index_dst]; + if (match == struct_index_src) { + // We previously checked how this dst-index matches this src-index, and it + // was fine. + return; + } + + if (match != (uint32_t) (-1)) { + // We previously matched this dst-index to a different src-index, so + // something is wrong. + ctx->error_code = SVFRT_code_compatibility__struct_index_mismatch; + return; + } + + ctx->dst_to_src_struct_matches.pointer[struct_index_dst] = struct_index_src; + + if (ctx->struct_queue.occupied >= ctx->struct_queue.capacity) { + ctx->error_code = SVFRT_code_compatibility_internal__queue_overflow; + return; + } + SVFRT_IndexPair *pair = ctx->struct_queue.pointer + ctx->struct_queue.occupied; + ctx->struct_queue.occupied++; -bool SVFRT_check_choice( + pair->index_src = struct_index_src; + pair->index_dst = struct_index_dst; +} + +void SVFRT_check_add_choice( SVFRT_CheckContext *ctx, - uint32_t s0_index, - uint32_t s1_index -); + uint32_t choice_index_src, + uint32_t choice_index_dst +) { + uint32_t match = ctx->dst_to_src_choice_matches.pointer[choice_index_dst]; + if (match == choice_index_src) { + // We previously checked how this dst-index matches this src-index, and it + // was fine. + return; + } -bool check_concrete_type( + if (match != (uint32_t) (-1)) { + // We previously matched this dst-index to a different src-index, so + // something is wrong. + ctx->error_code = SVFRT_code_compatibility__choice_index_mismatch; + return; + } + + ctx->dst_to_src_choice_matches.pointer[choice_index_dst] = choice_index_src; + + if (ctx->choice_queue.occupied >= ctx->choice_queue.capacity) { + ctx->error_code = SVFRT_code_compatibility_internal__queue_overflow; + return; + } + SVFRT_IndexPair *pair = ctx->choice_queue.pointer + ctx->choice_queue.occupied; + ctx->choice_queue.occupied++; + + pair->index_src = choice_index_src; + pair->index_dst = choice_index_dst; +} + +static inline +bool SVFRT_check_work(SVFRT_CheckContext *ctx, uint32_t work_count) { + ctx->work_done += work_count; + if (ctx->work_done > ctx->work_max) { + ctx->error_code = SVFRT_code_compatibility__work_max_exceeded; + return false; + } + + return true; +} + +#define SVFRT_RETURN_IF_WORK_MAX(ctx, count) if (!SVFRT_check_work((ctx), (count))) { return; } + +// TODO: report the specific types that did not match? +void SVFRT_check_concrete_type( SVFRT_CheckContext *ctx, - SVF_META_ConcreteType_enum t0, - SVF_META_ConcreteType_enum t1, - SVF_META_ConcreteType_union *u0, - SVF_META_ConcreteType_union *u1 + SVF_META_ConcreteType_enum unsafe_enum_src, + SVF_META_ConcreteType_union *unsafe_union_src, + SVF_META_ConcreteType_enum enum_dst, + SVF_META_ConcreteType_union *union_dst ) { - if (t0 == t1) { - if (t0 == SVF_META_ConcreteType_defined_struct) { - return SVFRT_check_struct(ctx, u0->defined_struct.index, u1->defined_struct.index); - } else if (t0 == SVF_META_ConcreteType_defined_choice) { - return SVFRT_check_choice(ctx, u0->defined_choice.index, u1->defined_choice.index); - } + if (unsafe_enum_src == enum_dst) { + switch (enum_dst) { + case SVF_META_ConcreteType_defined_struct: { + uint32_t unsafe_src_index = unsafe_union_src->defined_struct.index; + + // This guarantees that the index is valid, and is also not `UINT32_MAX`. + if (unsafe_src_index >= ctx->unsafe_structs_src.count) { + ctx->error_code = SVFRT_code_compatibility__invalid_struct_index; + return; + } + + SVFRT_check_add_struct( + ctx, + unsafe_src_index, + union_dst->defined_struct.index + ); + return; + } + case SVF_META_ConcreteType_defined_choice: { + uint32_t unsafe_src_index = unsafe_union_src->defined_choice.index; - // Other types are primitive. - return true; + // This guarantees that the index is valid, and is also not `UINT32_MAX`. + if (unsafe_src_index >= ctx->unsafe_choices_src.count) { + ctx->error_code = SVFRT_code_compatibility__invalid_choice_index; + return; + } + + SVFRT_check_add_choice( + ctx, + unsafe_src_index, + union_dst->defined_choice.index + ); + return; + } + // TODO: this is not exhaustive, but there's not much to gain from that? + default: { + // Other types are primitive, so enum equality is enough. + return; + } + } } if (ctx->required_level <= SVFRT_compatibility_logical) { - switch (t1) { + switch (enum_dst) { case SVF_META_ConcreteType_u16: { - return t0 == SVF_META_ConcreteType_u8; + if (unsafe_enum_src != SVF_META_ConcreteType_u8) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_u32: { - return (0 - || (t0 == SVF_META_ConcreteType_u16) - || (t0 == SVF_META_ConcreteType_u8) - ); + if ( + (unsafe_enum_src != SVF_META_ConcreteType_u16) && + (unsafe_enum_src != SVF_META_ConcreteType_u8) + ) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_u64: { - return (0 - || (t0 == SVF_META_ConcreteType_u32) - || (t0 == SVF_META_ConcreteType_u16) - || (t0 == SVF_META_ConcreteType_u8) - ); + if ( + (unsafe_enum_src != SVF_META_ConcreteType_u32) && + (unsafe_enum_src != SVF_META_ConcreteType_u16) && + (unsafe_enum_src != SVF_META_ConcreteType_u8) + ) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_i16: { - return (0 - || (t0 == SVF_META_ConcreteType_i8) - || (t0 == SVF_META_ConcreteType_u8) - ); + if ( + (unsafe_enum_src != SVF_META_ConcreteType_i8) && + (unsafe_enum_src != SVF_META_ConcreteType_u8) + ) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_i32: { - return (0 - || (t0 == SVF_META_ConcreteType_i16) - || (t0 == SVF_META_ConcreteType_i8) - || (t0 == SVF_META_ConcreteType_u16) - || (t0 == SVF_META_ConcreteType_u8) - ); + if ( + (unsafe_enum_src != SVF_META_ConcreteType_i16) && + (unsafe_enum_src != SVF_META_ConcreteType_i8) && + (unsafe_enum_src != SVF_META_ConcreteType_u16) && + (unsafe_enum_src != SVF_META_ConcreteType_u8) + ) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_i64: { - return (0 - || (t0 == SVF_META_ConcreteType_i32) - || (t0 == SVF_META_ConcreteType_i16) - || (t0 == SVF_META_ConcreteType_i8) - || (t0 == SVF_META_ConcreteType_u32) - || (t0 == SVF_META_ConcreteType_u16) - || (t0 == SVF_META_ConcreteType_u8) - ); + if ( + (unsafe_enum_src != SVF_META_ConcreteType_i32) && + (unsafe_enum_src != SVF_META_ConcreteType_i16) && + (unsafe_enum_src != SVF_META_ConcreteType_i8) && + (unsafe_enum_src != SVF_META_ConcreteType_u32) && + (unsafe_enum_src != SVF_META_ConcreteType_u16) && + (unsafe_enum_src != SVF_META_ConcreteType_u8) + ) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } case SVF_META_ConcreteType_f64: { - return t0 == SVF_META_ConcreteType_f32; + if (unsafe_enum_src != SVF_META_ConcreteType_f32) { + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + } + return; } + // TODO: this is not exhaustive, but there's not much to gain from that? default: { - return false; + ctx->error_code = SVFRT_code_compatibility__concrete_type_mismatch; + return; } } } - - return false; } -bool check_type( +void SVFRT_check_type( SVFRT_CheckContext *ctx, - SVF_META_Type_enum t0, - SVF_META_Type_enum t1, - SVF_META_Type_union *u0, - SVF_META_Type_union *u1 + SVF_META_Type_enum unsafe_enum_src, + SVF_META_Type_union *unsafe_union_src, + SVF_META_Type_enum enum_dst, + SVF_META_Type_union *union_dst ) { - if (t0 != t1) { - return false; - } - - if (t0 == SVF_META_Type_reference) { - return check_concrete_type( - ctx, - u0->reference.type_enum, - u1->reference.type_enum, - &u0->reference.type_union, - &u1->reference.type_union - ); - } - - if (t0 == SVF_META_Type_sequence) { - return check_concrete_type( - ctx, - u0->sequence.element_type_enum, - u1->sequence.element_type_enum, - &u0->sequence.element_type_union, - &u1->sequence.element_type_union - ); + if (unsafe_enum_src != enum_dst) { + ctx->error_code = SVFRT_code_compatibility__type_mismatch; + return; } - if (t0 == SVF_META_Type_concrete) { - return check_concrete_type( - ctx, - u0->concrete.type_enum, - u1->concrete.type_enum, - &u0->concrete.type_union, - &u1->concrete.type_union - ); + switch (enum_dst) { + case SVF_META_Type_reference: { + SVFRT_check_concrete_type( + ctx, + unsafe_union_src->reference.type_enum, + &unsafe_union_src->reference.type_union, + union_dst->reference.type_enum, + &union_dst->reference.type_union + ); + return; + } + case SVF_META_Type_sequence: { + SVFRT_check_concrete_type( + ctx, + unsafe_union_src->sequence.element_type_enum, + &unsafe_union_src->sequence.element_type_union, + union_dst->sequence.element_type_enum, + &union_dst->sequence.element_type_union + ); + return; + } + case SVF_META_Type_concrete: { + SVFRT_check_concrete_type( + ctx, + unsafe_union_src->concrete.type_enum, + &unsafe_union_src->concrete.type_union, + union_dst->concrete.type_enum, + &union_dst->concrete.type_union + ); + return; + } + default: { + ctx->error_code = SVFRT_code_compatibility_internal__invalid_type_enum; + return; + } } - - // Internal error. - return false; } -bool SVFRT_check_struct( +void SVFRT_check_struct( SVFRT_CheckContext *ctx, - uint32_t s0_index, - uint32_t s1_index + uint32_t struct_index_src, + uint32_t struct_index_dst ) { - uint32_t match = ctx->s1_struct_matches.pointer[s1_index]; - if (match == s0_index) { - return true; - } - - if (match != (uint32_t) (-1)) { - // Internal error. - return false; + SVF_META_StructDefinition *unsafe_definition_src = ctx->unsafe_structs_src.pointer + struct_index_src; + SVF_META_StructDefinition *definition_dst = ctx->structs_dst.pointer + struct_index_dst; + + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeFieldDefinition unsafe_fields_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->unsafe_schema_src, + unsafe_definition_src->fields, + SVF_META_FieldDefinition + ); + if (!unsafe_fields_src.pointer && unsafe_fields_src.count) { + ctx->error_code = SVFRT_code_compatibility__invalid_fields; + return; } - ctx->s1_struct_matches.pointer[s1_index] = s0_index; - SVFRT_RangeStructDefinition structs0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r0, ctx->s0->structs, SVF_META_StructDefinition); - SVFRT_RangeStructDefinition structs1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r1, ctx->s1->structs, SVF_META_StructDefinition); - if (!structs0.pointer || !structs1.pointer) { - // Internal error. - return false; + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeFieldDefinition fields_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->schema_dst, + definition_dst->fields, + SVF_META_FieldDefinition + ); + if (!fields_dst.pointer && fields_dst.count) { + ctx->error_code = SVFRT_code_compatibility_internal__invalid_fields; + return; } - SVF_META_StructDefinition *s0 = structs0.pointer + s0_index; - SVF_META_StructDefinition *s1 = structs1.pointer + s1_index; + // TODO: this will be more complicated with @optional-types. - SVFRT_RangeFieldDefinition fields0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r0, s0->fields, SVF_META_FieldDefinition); - SVFRT_RangeFieldDefinition fields1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r1, s1->fields, SVF_META_FieldDefinition); - - if (!fields0.pointer || !fields0.pointer) { - // Internal error. - return false; - } - - // Logical compatibility: all Schema 1 fields must be present in Schema 0, + // Logical compatibility: all dst-schema fields must be present in src-schema, // with the same `name_hash`. Their types must be logically compatible. - // Binary compatibility: all Schema 1 fields must be present in Schema 0, with + // Binary compatibility: all dst-schema fields must be present in src-schema, with // the same `name_hash` and `offset`. Their types must be binary compatible. - // Exact compatibility: all Schema 1 fields must be present in Schema 0, with + // Exact compatibility: all dst-schema fields must be present in src-schema, with // the same `name_hash` and `offset`. Their types must be exactly compatible. // The sizes of structs must be equal. - if (fields0.count < fields1.count) { - // Early exit, because we know that at least one field from Schema 1 is not - // present in Schema 0. - return false; + if (unsafe_fields_src.count < fields_dst.count) { + // Early exit, because we know that at least one field from dst-schema is not + // present in src-schema. + ctx->error_code = SVFRT_code_compatibility__field_is_missing; + return; } + // #compatibility-reasonable-fields. + // + // #compatibility-work [2]: this is linear in respect to + // `unsafe_fields_src.count * fields_dst.count`. + // // TODO @performance: N^2 - for (uint32_t i = 0; i < fields1.count; i++) { - SVF_META_FieldDefinition *field1 = fields1.pointer + i; + for (uint32_t i = 0; i < fields_dst.count; i++) { + SVF_META_FieldDefinition *field_dst = fields_dst.pointer + i; + + // Looping over potentially adversarial data, so limit work. + SVFRT_RETURN_IF_WORK_MAX(ctx, unsafe_fields_src.count); bool found = false; - for (uint32_t j = 0; j < fields0.count; j++) { - SVF_META_FieldDefinition *field0 = fields0.pointer + j; + for (uint32_t j = 0; j < unsafe_fields_src.count; j++) { + SVF_META_FieldDefinition *unsafe_field_src = unsafe_fields_src.pointer + j; - if (field0->name_hash == field1->name_hash) { + if (unsafe_field_src->name_hash == field_dst->name_hash) { if ( (ctx->current_level >= SVFRT_compatibility_binary) - && (field0->offset != field1->offset) + && (unsafe_field_src->offset != field_dst->offset) ) { ctx->current_level = SVFRT_compatibility_logical; if (ctx->current_level < ctx->required_level) { - return false; + ctx->error_code = SVFRT_code_compatibility__field_offset_mismatch; + return; } } - if (!check_type( - ctx, - field0->type_enum, - field1->type_enum, - &field0->type_union, - &field1->type_union - )) { - return false; + SVFRT_check_type( + ctx, + unsafe_field_src->type_enum, + &unsafe_field_src->type_union, + field_dst->type_enum, + &field_dst->type_union + ); + + if (ctx->error_code) { + return; } found = true; + // Incorrect src-schema could have multiple fields with the same + // name-hash. We will simply ignore every but the first one. + // + // TODO: should this be guarded against? Can this cause trouble? break; } } if (!found) { - return false; + ctx->error_code = SVFRT_code_compatibility__field_is_missing; + return; } } if (ctx->current_level == SVFRT_compatibility_exact) { - if (s0->size != s1->size) { + // #dst-stride-is-sane. + if (unsafe_definition_src->size != definition_dst->size) { + // Sizes do not match, downgrade to binary compatibility. ctx->current_level = SVFRT_compatibility_binary; if (ctx->current_level < ctx->required_level) { - return false; + ctx->error_code = SVFRT_code_compatibility__struct_size_mismatch; + return; } } } - ctx->s1_struct_strides.pointer[s1_index] = s0->size; + if (ctx->current_level == SVFRT_compatibility_binary) { + // #dst-stride-is-sane. + if (unsafe_definition_src->size < definition_dst->size) { + // Can't provide binary compatibility, so downgrade to logical compatibility. + ctx->current_level = SVFRT_compatibility_logical; + if (ctx->current_level < ctx->required_level) { + ctx->error_code = SVFRT_code_compatibility__struct_size_mismatch; + return; + } + } + } - return true; + ctx->unsafe_struct_strides_dst.pointer[struct_index_dst] = unsafe_definition_src->size; } -bool SVFRT_check_choice( +void SVFRT_check_choice( SVFRT_CheckContext *ctx, - uint32_t s0_index, - uint32_t s1_index + uint32_t choice_index_src, + uint32_t choice_index_dst ) { - uint32_t match = ctx->s1_choice_matches.pointer[s1_index]; - if (match == s0_index) { - return true; - } - - if (match != (uint32_t) (-1)) { - // Internal error. - return false; + SVF_META_ChoiceDefinition *unsafe_definition_src = ctx->unsafe_choices_src.pointer + choice_index_src; + SVF_META_ChoiceDefinition *definition_dst = ctx->choices_dst.pointer + choice_index_dst; + + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeOptionDefinition unsafe_options_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->unsafe_schema_src, + unsafe_definition_src->options, + SVF_META_OptionDefinition + ); + if (!unsafe_options_src.pointer && unsafe_options_src.count) { + ctx->error_code = SVFRT_code_compatibility__invalid_options; + return; } - ctx->s1_choice_matches.pointer[s1_index] = s0_index; - SVFRT_RangeChoiceDefinition choices0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r0, ctx->s0->choices, SVF_META_ChoiceDefinition); - SVFRT_RangeChoiceDefinition choices1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r1, ctx->s1->choices, SVF_META_ChoiceDefinition); - if (!choices0.pointer || !choices1.pointer) { - // Internal error. - return false; + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeOptionDefinition options_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->schema_dst, + definition_dst->options, + SVF_META_OptionDefinition + ); + if (!options_dst.pointer && options_dst.count) { + ctx->error_code = SVFRT_code_compatibility_internal__invalid_options; + return; } - SVF_META_ChoiceDefinition *c0 = choices0.pointer + s0_index; - SVF_META_ChoiceDefinition *c1 = choices1.pointer + s1_index; - - SVFRT_RangeOptionDefinition options0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r0, c0->options, SVF_META_OptionDefinition); - SVFRT_RangeOptionDefinition options1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r1, c1->options, SVF_META_OptionDefinition); - - if (!options0.pointer || !options0.pointer) { - // Internal error. - return false; - } + // TODO: this will be more complicated with @optional-types. - // Logical compatibility: all Schema 0 fields must be present in Schema 1, + // Logical compatibility: all src-schema options must be present in dst-schema, // with the same `name_hash`. Their types must be logically compatible. - // Binary compatibility: all Schema 0 options must be present in Schema 1, with + // Binary compatibility: all src-schema options must be present in dst-schema, with // the same `name_hash` and `index`. Their types must be binary compatible. - // Exact compatibility: all Schema 0 options must be present in Schema 1, with + // Exact compatibility: all src-schema options must be present in dst-schema, with // the same `name_hash` and `index`. Their types must be exactly compatible. - if (options1.count < options0.count) { - // Early exit, because we know that at least one option from Schema 0 is not - // present in Schema 1. - return false; + if (options_dst.count < unsafe_options_src.count) { + // Early exit, because we know that at least one option from src-schema is not + // present in dst-schema. + ctx->error_code = SVFRT_code_compatibility__option_is_missing; + return; } + // Unlike with structs, here we know that the total work done is bound by + // `options_dst.count`. So, we don't really need to limit work here, even if + // `unsafe_options_dst` is adversarial. + // + // #compatibility-reasonable-options. + // // TODO @performance: N^2 - for (uint32_t i = 0; i < options0.count; i++) { - SVF_META_OptionDefinition *option0 = options0.pointer + i; + for (uint32_t i = 0; i < unsafe_options_src.count; i++) { + SVF_META_OptionDefinition *unsafe_option_src = unsafe_options_src.pointer + i; bool found = false; - for (uint32_t j = 0; j < options1.count; j++) { - SVF_META_OptionDefinition *option1 = options1.pointer + j; + for (uint32_t j = 0; j < options_dst.count; j++) { + SVF_META_OptionDefinition *option_dst = options_dst.pointer + j; - if (option0->name_hash == option1->name_hash) { + if (unsafe_option_src->name_hash == option_dst->name_hash) { if ( (ctx->current_level >= SVFRT_compatibility_binary) - && (option0->index != option1->index) + && (unsafe_option_src->index != option_dst->index) ) { ctx->current_level = SVFRT_compatibility_logical; if (ctx->current_level < ctx->required_level) { - return false; + ctx->error_code = SVFRT_code_compatibility__option_tag_mismatch; + return; } } - if (!check_type( - ctx, - option0->type_enum, - option1->type_enum, - &option0->type_union, - &option1->type_union - )) { - return false; + SVFRT_check_type( + ctx, + unsafe_option_src->type_enum, + &unsafe_option_src->type_union, + option_dst->type_enum, + &option_dst->type_union + ); + + if (ctx->error_code) { + return; } found = true; @@ -346,134 +549,270 @@ bool SVFRT_check_choice( } if (!found) { - return false; + ctx->error_code = SVFRT_code_compatibility__option_is_missing; + return; } } - - return true; } void SVFRT_check_compatibility( SVFRT_CompatibilityResult *out_result, SVFRT_Bytes scratch_memory, - SVFRT_Bytes schema_write, - SVFRT_Bytes schema_read, + SVFRT_Bytes unsafe_schema_src, + SVFRT_Bytes schema_dst, uint64_t entry_name_hash, SVFRT_CompatibilityLevel required_level, - SVFRT_CompatibilityLevel sufficient_level + SVFRT_CompatibilityLevel sufficient_level, + uint32_t work_max ) { if (required_level == SVFRT_compatibility_none) { + out_result->error_code = SVFRT_code_compatibility__required_level_is_none; return; } if (sufficient_level < required_level) { + out_result->error_code = SVFRT_code_compatibility__invalid_sufficient_level; return; } - SVFRT_Bytes r0 = schema_write; - SVFRT_Bytes r1 = schema_read; - // TODO @proper-alignment. - SVF_META_Schema *s0 = (SVF_META_Schema *) (r0.pointer + r0.count - sizeof(SVF_META_Schema)); - SVF_META_Schema *s1 = (SVF_META_Schema *) (r1.pointer + r1.count - sizeof(SVF_META_Schema)); + SVF_META_Schema *unsafe_definition_src = (SVF_META_Schema *) ( + unsafe_schema_src.pointer + + unsafe_schema_src.count + - sizeof(SVF_META_Schema) + ); + SVF_META_Schema *definition_dst = (SVF_META_Schema *) ( + schema_dst.pointer + + schema_dst.count + - sizeof(SVF_META_Schema) + ); - size_t partitions[3] = { - sizeof(uint32_t) * s1->structs.count, - sizeof(uint32_t) * s1->choices.count, - sizeof(uint32_t) * s1->structs.count, + uint32_t scratch_misalignment = ((uintptr_t) scratch_memory.pointer) % sizeof(uint32_t); + uint32_t scratch_padding = scratch_misalignment ? sizeof(uint32_t) - scratch_misalignment : 0; + + // See #scratch-memory-partitions. + size_t partitions[5] = { + sizeof(uint32_t) * definition_dst->structs.count, // Strides. + sizeof(uint32_t) * definition_dst->structs.count, // Matches. + sizeof(uint32_t) * definition_dst->choices.count, // Matches. + sizeof(SVFRT_IndexPair) * definition_dst->structs.count, // Queue. + sizeof(SVFRT_IndexPair) * definition_dst->choices.count // Queue. }; - size_t total_needed = ( - partitions[0] + + size_t total_scratch_needed = ( + scratch_padding + + partitions[0] + partitions[1] + partitions[2] + + partitions[3] + + partitions[4] ); - if (scratch_memory.count < total_needed) { + if (scratch_memory.count < total_scratch_needed) { + out_result->error_code = SVFRT_code_compatibility__not_enough_scratch_memory; return; } - SVFRT_RangeU32 s1_struct_matches = { - /*.pointer =*/ (uint32_t *) scratch_memory.pointer, - /*.count =*/ s1->structs.count + uint8_t *partition_pointer = scratch_memory.pointer + scratch_padding; + SVFRT_RangeU32 unsafe_struct_strides_dst = { + /*.pointer =*/ (uint32_t *) partition_pointer, + /*.count =*/ definition_dst->structs.count }; - SVFRT_RangeU32 s1_choice_matches = { - /*.pointer =*/ (uint32_t *) (scratch_memory.pointer + partitions[0]), - /*.count =*/ s1->choices.count + + partition_pointer += partitions[0]; + SVFRT_RangeU32 dst_to_src_struct_matches = { + /*.pointer =*/ (uint32_t *) partition_pointer, + /*.count =*/ definition_dst->structs.count + }; + + partition_pointer += partitions[1]; + SVFRT_RangeU32 dst_to_src_choice_matches = { + /*.pointer =*/ (uint32_t *) partition_pointer, + /*.count =*/ definition_dst->choices.count }; - SVFRT_RangeU32 s1_struct_strides = { - /*.pointer =*/ (uint32_t *) (scratch_memory.pointer + partitions[0] + partitions[1]), - /*.count =*/ s1->structs.count + + partition_pointer += partitions[2]; + SVFRT_IndexPairQueue struct_queue_initial = { + /*.pointer =*/ (SVFRT_IndexPair *) partition_pointer, + /*.capacity =*/ definition_dst->structs.count, + /*.occupied =*/ 0 + }; + + partition_pointer += partitions[3]; + SVFRT_IndexPairQueue choice_queue_initial = { + /*.pointer =*/ (SVFRT_IndexPair *) partition_pointer, + /*.capacity =*/ definition_dst->choices.count, + /*.occupied =*/ 0 }; - for (uint32_t i = 0; i < s1->structs.count; i++) { - s1_struct_matches.pointer[i] = (uint32_t) (-1); - s1_struct_strides.pointer[i] = 0; + for (uint32_t i = 0; i < unsafe_struct_strides_dst.count; i++) { + unsafe_struct_strides_dst.pointer[i] = 0; + } + for (uint32_t i = 0; i < dst_to_src_struct_matches.count; i++) { + dst_to_src_struct_matches.pointer[i] = (uint32_t) (-1); + } + for (uint32_t i = 0; i < dst_to_src_choice_matches.count; i++) { + dst_to_src_choice_matches.pointer[i] = (uint32_t) (-1); + } + for (uint32_t i = 0; i < struct_queue_initial.capacity; i++) { + struct_queue_initial.pointer[i].index_src = (uint32_t) (-1); + struct_queue_initial.pointer[i].index_dst = (uint32_t) (-1); + } + for (uint32_t i = 0; i < choice_queue_initial.capacity; i++) { + choice_queue_initial.pointer[i].index_src = (uint32_t) (-1); + choice_queue_initial.pointer[i].index_dst = (uint32_t) (-1); + } + + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeStructDefinition unsafe_structs_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + unsafe_schema_src, + unsafe_definition_src->structs, + SVF_META_StructDefinition + ); + if (!unsafe_structs_src.pointer && unsafe_structs_src.count) { + out_result->error_code = SVFRT_code_compatibility__invalid_structs; + return; + } + + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeStructDefinition structs_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + schema_dst, + definition_dst->structs, + SVF_META_StructDefinition + ); + if (!structs_dst.pointer && structs_dst.count) { + out_result->error_code = SVFRT_code_compatibility_internal__invalid_structs; + return; + } + + // Same for choices. + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeChoiceDefinition unsafe_choices_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + unsafe_schema_src, + unsafe_definition_src->choices, + SVF_META_ChoiceDefinition + ); + if (!unsafe_choices_src.pointer && unsafe_choices_src.count) { + out_result->error_code = SVFRT_code_compatibility__invalid_choices; + return; } - for (uint32_t i = 0; i < s1->choices.count; i++) { - s1_choice_matches.pointer[i] = (uint32_t) (-1); + + // Safety: out-of-bounds access will be caught, and `.pointer` will be NULL. + SVFRT_RangeChoiceDefinition choices_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + schema_dst, + definition_dst->choices, + SVF_META_ChoiceDefinition + ); + if (!choices_dst.pointer && choices_dst.count) { + out_result->error_code = SVFRT_code_compatibility_internal__invalid_choices; + return; } SVFRT_CheckContext ctx_val = { - .s0 = s0, - .s1 = s1, - .r0 = r0, - .r1 = r1, - .s1_struct_matches = s1_struct_matches, - .s1_choice_matches = s1_choice_matches, - .s1_struct_strides = s1_struct_strides, + .unsafe_definition_src = unsafe_definition_src, + .definition_dst = definition_dst, + .unsafe_schema_src = unsafe_schema_src, + .schema_dst = schema_dst, + .unsafe_structs_src = unsafe_structs_src, + .structs_dst = structs_dst, + .unsafe_choices_src = unsafe_choices_src, + .choices_dst = choices_dst, + .dst_to_src_struct_matches = dst_to_src_struct_matches, + .dst_to_src_choice_matches = dst_to_src_choice_matches, + .unsafe_struct_strides_dst = unsafe_struct_strides_dst, + .struct_queue = struct_queue_initial, + .choice_queue = choice_queue_initial, .current_level = sufficient_level, - .required_level = required_level + .required_level = required_level, + .work_max = work_max, }; SVFRT_CheckContext *ctx = &ctx_val; - uint32_t struct_index0 = (uint32_t) (-1); - uint32_t struct_index1 = (uint32_t) (-1); - - SVFRT_RangeStructDefinition structs0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r0, ctx->s0->structs, SVF_META_StructDefinition); - SVFRT_RangeStructDefinition structs1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->r1, ctx->s1->structs, SVF_META_StructDefinition); - - if (!structs0.pointer || !structs1.pointer) { - // Internal error. + // #compatibility-work [1]: this is linear in respect to + // `unsafe_structs_src.count`. + // + // Looping over potentially adversarial data, so limit work. Do not use the + // convenience macro here, because it works with `ctx`, not `out_result`. + if (!SVFRT_check_work(ctx, unsafe_structs_src.count)) { + out_result->error_code = ctx->error_code; return; } - for (uint32_t i = 0; i < structs0.count; i++) { - if (structs0.pointer[i].name_hash == entry_name_hash) { - struct_index0 = i; + uint32_t struct_index_src = (uint32_t) (-1); + for (uint32_t i = 0; i < unsafe_structs_src.count; i++) { + if (unsafe_structs_src.pointer[i].name_hash == entry_name_hash) { + struct_index_src = i; break; } } - for (uint32_t i = 0; i < structs1.count; i++) { - if (structs1.pointer[i].name_hash == entry_name_hash) { - struct_index1 = i; + uint32_t struct_index_dst = (uint32_t) (-1); + for (uint32_t i = 0; i < structs_dst.count; i++) { + if (structs_dst.pointer[i].name_hash == entry_name_hash) { + struct_index_dst = i; + break; + } + } + + if (struct_index_src == (uint32_t) (-1) || struct_index_dst == (uint32_t) (-1)) { + out_result->error_code = SVFRT_code_compatibility__entry_name_hash_not_found; + return; + } + + SVFRT_check_add_struct(ctx, struct_index_src, struct_index_dst); + + for (uint32_t i = 0; i < ctx->struct_queue.capacity + ctx->choice_queue.capacity; i++) { + if (ctx->struct_queue.occupied > 0) { + ctx->struct_queue.occupied--; + SVFRT_IndexPair pair = ctx->struct_queue.pointer[ctx->struct_queue.occupied]; + SVFRT_check_struct(ctx, pair.index_src, pair.index_dst); + + if (ctx->error_code) { + out_result->error_code = ctx->error_code; + return; + } + } else if (ctx->choice_queue.occupied > 0) { + ctx->choice_queue.occupied--; + SVFRT_IndexPair pair = ctx->choice_queue.pointer[ctx->choice_queue.occupied]; + SVFRT_check_choice(ctx, pair.index_src, pair.index_dst); + + if (ctx->error_code) { + out_result->error_code = ctx->error_code; + return; + } + } else { break; } } - if (struct_index0 == (uint32_t) (-1) || struct_index1 == (uint32_t) (-1)) { - // Internal error. + if (ctx->struct_queue.occupied > 0 || ctx->choice_queue.occupied > 0) { + out_result->error_code = SVFRT_code_compatibility_internal__queue_unhandled; return; } - if (!SVFRT_check_struct( - ctx, - struct_index0, - struct_index1 - )) { + if (ctx->error_code) { + out_result->error_code = ctx->error_code; return; } + out_result->level = ctx->current_level; + + // We drop the "unsafe" naming here, because: + // - for exact/binary compatibility, see #dst-stride-is-sane. + // - for logical compatibility, see #logical-compatibility-stride-quirk. + out_result->quirky_struct_strides_dst = ctx->unsafe_struct_strides_dst; + + out_result->unsafe_entry_size_src = unsafe_structs_src.pointer[struct_index_src].size; + out_result->entry_struct_index_src = struct_index_src; + out_result->entry_struct_index_dst = struct_index_dst; + if (ctx->current_level == SVFRT_compatibility_logical) { + // #logical-compatibility-stride-quirk. + // // If we have logical compatibility, then we will do a conversion, so it - // does not make sense to store Schema 0 sizes here. - for (size_t i = 0; i < structs1.count; i++) { - ctx->s1_struct_strides.pointer[i] = structs1.pointer[i].size; + // does not make sense to store src-schema sizes here. So patch them up. + for (size_t i = 0; i < structs_dst.count; i++) { + // These are actually safe here, but the naming stays... + out_result->quirky_struct_strides_dst.pointer[i] = structs_dst.pointer[i].size; } } - - out_result->level = ctx->current_level; - out_result->struct_strides = ctx->s1_struct_strides; - out_result->entry_size0 = structs0.pointer[struct_index0].size; - out_result->entry_struct_index0 = struct_index0; - out_result->entry_struct_index1 = struct_index1; } diff --git a/svf_runtime/src/svf_conversion.c b/svf_runtime/src/svf_conversion.c index 7084c39..c91fc1b 100644 --- a/svf_runtime/src/svf_conversion.c +++ b/svf_runtime/src/svf_conversion.c @@ -17,43 +17,84 @@ #include "svf_internal.h" #endif -// Toggle direct unaligned access to memory when converting. This invoked -// undefined behavior, but it should be fine on x64. Disabled to be safe, but -// left as an option. +// !! // -// When @proper-alignment is done, this might be unnecessary. +// Important note: right now, all of the conversion code is written with the +// assumptions, that compatibility has been successfully verified between the +// schemas, and we can trust that the potentially unsafe src-schema is safe for +// our purposes. These assumptions should be referenced in the code wherever they +// are used. +// +// !! + +// Note on stack usage: all recursive code in this file should be tracking the +// current recursion depth, both to prevent malicious data exploiting recursion, +// and as an attempt to prevent stack overflow generally. Since C/C++ is woefully +// lacking with regards to working with OS stack limits, this is an "attempt" and +// not a real portable solution, because on some unknown platform with small stack +// size it will still be easy to crash. +// +// The real solution would only be to reimplement the recursive calls with iteration, +// which would make maintaining a custom stack necessary. This would maximize +// the user's control, but make the API more unwieldy, probably requiring the user +// to provide their own memory for the custom stack. This would negatively affect +// ease-of-use, while providing no real benefit in most cases (and huge benefit in +// a few marginal cases, of course). +// +// TODO (low priority until a real use case happens): Reimplement with custom +// stack. There probably should be a (compile-time?) switch to use one +// implementation or the other. + +// Note: after the conversion, the value tree will be suballocated parent-before-child, +// i.e. the parent's suballocation bytes come before the child suballocation bytes. +// This is contrary to how it should be, but this is never checked anywhere currently. +// +// The tree root (entry) is exempt from this, and is always placed at the end of +// the allocation, because that's how the entry is always found in the data. +// +// TODO: The suballocations should be in the right order (reversed from what is -// #define SVFRT_DIRECT_UNALIGNED_ACCESS + +// Note: #unsafe-naming-semantics. +// +// Just like with compatibility checks, we should be mindful of adversarial data. +// However, it seems like in most (or all) of the code in this file we treat +// src-schema and dst-schema roughly equally, e.g. bounds checks, index checks. +// So, it's less clear why this naming is beneficial here. +// +// TODO: what would break if we treated src- and dst-schemas equally here? +// The answer to this question would make everything more clear. typedef struct SVFRT_ConversionContext { SVFRT_ConversionInfo *info; SVFRT_Bytes data_bytes; - size_t max_recursion_depth; + uint32_t max_recursion_depth; + + SVFRT_RangeStructDefinition unsafe_structs_src; + SVFRT_RangeStructDefinition structs_dst; - SVFRT_RangeStructDefinition structs0; - SVFRT_RangeStructDefinition structs1; + SVFRT_RangeChoiceDefinition unsafe_choices_src; + SVFRT_RangeChoiceDefinition choices_dst; - SVFRT_RangeChoiceDefinition choices0; - SVFRT_RangeChoiceDefinition choices1; + uint32_t total_data_size_limit_dst; - // For Phase 1. - size_t allocation_needed; + // Both for Phase 1 and 2. Should be reset in-between. + uint32_t tally_src; + uint32_t tally_dst; - // For Phase 2. + // For Phase 2 only. SVFRT_Bytes allocation; - size_t allocated_already; - bool internal_error; - bool data_error; + SVFRT_ErrorCode error_code; } SVFRT_ConversionContext; -uint32_t SVFRT_get_type_size( - SVFRT_ConversionContext *ctx, +// Returns 0 on an error. +uint32_t SVFRT_conversion_get_type_size( SVFRT_RangeStructDefinition structs, - SVF_META_ConcreteType_enum t, - SVF_META_ConcreteType_union *u + SVF_META_ConcreteType_enum type_enum, + SVF_META_ConcreteType_union *type_union ) { - switch (t) { + switch (type_enum) { case SVF_META_ConcreteType_u8: { return 1; } @@ -85,9 +126,8 @@ uint32_t SVFRT_get_type_size( return 8; } case SVF_META_ConcreteType_defined_struct: { - uint32_t index = u->defined_struct.index; + uint32_t index = type_union->defined_struct.index; if (index >= structs.count) { - ctx->internal_error = true; return 0; } return structs.pointer[index].size; @@ -95,1124 +135,1036 @@ uint32_t SVFRT_get_type_size( case SVF_META_ConcreteType_zero_sized: case SVF_META_ConcreteType_defined_choice: default: { - ctx->internal_error = true; return 0; } } } -void SVFRT_bump_size( +static inline +uint8_t SVFRT_conversion_read_uint8_t ( SVFRT_ConversionContext *ctx, - size_t size + SVFRT_Bytes range_src, + uint32_t unsafe_offset_src ) { - // TODO @proper-alignment. - ctx->allocation_needed += size; + if (unsafe_offset_src + 1 > range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return 0; + } + // Can't be misaligned... + return *((uint8_t *) (range_src.pointer + unsafe_offset_src)); } -void SVFRT_bump_structs( +static inline +uint8_t SVFRT_conversion_read_int8_t ( SVFRT_ConversionContext *ctx, - size_t s1_index, - size_t count + SVFRT_Bytes range_src, + uint32_t unsafe_offset_src ) { - SVF_META_StructDefinition *s1 = ctx->structs1.pointer + s1_index; + if (unsafe_offset_src + 1 > range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return 0; + } + // Can't be misaligned... + return *((int8_t *) (range_src.pointer + unsafe_offset_src)); +} - if (s1_index >= ctx->structs1.count) { - ctx->internal_error = true; - return; +#define SVFRT_INSTANTIATE_READ_TYPE(type) \ + static inline \ + type SVFRT_conversion_read_##type ( \ + SVFRT_ConversionContext *ctx, \ + SVFRT_Bytes range_src, \ + uint32_t unsafe_offset_src \ + ) { \ + type value = 0; \ + if (unsafe_offset_src + sizeof(value) > range_src.count) { \ + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; \ + return value; \ + } \ + void *pointer_src = (void *) (range_src.pointer + unsafe_offset_src); \ + SVFRT_MEMCPY(&value, pointer_src, sizeof(value)); \ + return value; \ } - // TODO @proper-alignment. - ctx->allocation_needed += s1->size * count; -} +SVFRT_INSTANTIATE_READ_TYPE(uint32_t) +SVFRT_INSTANTIATE_READ_TYPE(uint16_t) +SVFRT_INSTANTIATE_READ_TYPE(int32_t) +SVFRT_INSTANTIATE_READ_TYPE(int16_t) +SVFRT_INSTANTIATE_READ_TYPE(float) + +#define SVFRT_INSTANTIATE_WRITE_TYPE(type) \ + static inline \ + void SVFRT_conversion_write_##type ( \ + SVFRT_ConversionContext *ctx, \ + SVFRT_Bytes range_dst, \ + uint32_t offset_dst, \ + type value \ + ) { \ + if (offset_dst + sizeof(value) > range_dst.count) { \ + ctx->error_code = SVFRT_code_conversion_internal__suballocation_out_of_bounds; \ + return; \ + } \ + void *pointer_dst = (void *) (range_dst.pointer + offset_dst); \ + SVFRT_MEMCPY(pointer_dst, &value, sizeof(value)); \ + } + +SVFRT_INSTANTIATE_WRITE_TYPE(uint64_t) +SVFRT_INSTANTIATE_WRITE_TYPE(uint32_t) +SVFRT_INSTANTIATE_WRITE_TYPE(uint16_t) +SVFRT_INSTANTIATE_WRITE_TYPE(int64_t) +SVFRT_INSTANTIATE_WRITE_TYPE(int32_t) +SVFRT_INSTANTIATE_WRITE_TYPE(int16_t) +SVFRT_INSTANTIATE_WRITE_TYPE(double) -void SVFRT_bump_struct_contents( +void SVFRT_conversion_copy_exact( SVFRT_ConversionContext *ctx, - size_t s0_index, - size_t s1_index, - SVFRT_Bytes input_bytes -); + SVFRT_Bytes range_src, + uint32_t unsafe_offset_src, + SVFRT_Bytes range_dst, + uint32_t offset_dst, + uint32_t size +) { + if (unsafe_offset_src + size > range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } + void *pointer_src = (void *) (range_src.pointer + unsafe_offset_src); + + if (offset_dst + size > range_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__suballocation_out_of_bounds; + return; + } + void *pointer_dst = (void *) (range_dst.pointer + offset_dst); + + SVFRT_MEMCPY(pointer_dst, pointer_src, size); +} -void SVFRT_bump_type( +void SVFRT_conversion_tally( SVFRT_ConversionContext *ctx, - SVF_META_Type_enum t0, - SVF_META_Type_union *u0, - SVFRT_Bytes range_from, - uint32_t offset_from, - SVF_META_Type_enum t1, - SVF_META_Type_union *u1 + uint32_t unsafe_size_src, + uint32_t size_dst, + uint32_t unsafe_count, + SVFRT_Bytes *phase2_out_suballocation // Should be non-NULL for Phase 2. ) { - if (t0 == SVF_META_Type_concrete) { - // Sanity check. - if (t1 != SVF_META_Type_concrete) { - ctx->internal_error = true; + // Prevent multiply-add overflow by casting operands to `uint64_t` first. It + // works, because `UINT64_MAX == UINT32_MAX * UINT32_MAX + UINT32_MAX + UINT32_MAX`. + uint64_t sum_src = (uint64_t) ctx->tally_src + (uint64_t) unsafe_size_src * (uint64_t) unsafe_count; + uint64_t sum_dst = (uint64_t) ctx->tally_dst + (uint64_t) size_dst * (uint64_t) unsafe_count; + + if (sum_src > (uint64_t) ctx->data_bytes.count) { + ctx->error_code = SVFRT_code_conversion__data_aliasing_detected; + ctx->tally_src = UINT32_MAX; + return; + } + + if (phase2_out_suballocation) { + // Phase2, we have the exact allocation count here. + // + // #phase2-reasonable-dst-sum: after this value is checked, `size_dst * unsafe_count` + // is also proven not to overflow `uint32_t`. + if (sum_dst > (uint64_t) ctx->allocation.count) { + ctx->error_code = SVFRT_code_conversion_internal__suballocation_mismatch; + ctx->tally_dst = UINT32_MAX; return; } - switch (u1->concrete.type_enum) { - case SVF_META_ConcreteType_defined_struct: { - uint32_t inner_s0_index = u0->concrete.type_union.defined_struct.index; - uint32_t inner_s1_index = u1->concrete.type_union.defined_struct.index; - - size_t inner_input_size = ctx->structs0.pointer[inner_s0_index].size; + // No risk of overflow, see #phase2-reasonable-dst-sum. + phase2_out_suballocation->count = size_dst * unsafe_count; + phase2_out_suballocation->pointer = ctx->allocation.pointer + ctx->tally_dst; + } else { + // Phase 1, we don't have the exact allocation count yet here. - SVFRT_Bytes inner_input_range = { - /*.pointer =*/ (uint8_t *) range_from.pointer + offset_from, - /*.count =*/ inner_input_size, - }; + if (sum_dst > (uint64_t) ctx->total_data_size_limit_dst) { + ctx->error_code = SVFRT_code_conversion__total_data_size_limit_exceeded; + ctx->tally_dst = UINT32_MAX; + return; + } + } - if (inner_input_range.pointer + inner_input_range.count > range_from.pointer + range_from.count) { - ctx->internal_error = true; - return; - } + // Both sums are less or equal to some `uint32_t` value, so casts are lossless. + ctx->tally_src = (uint32_t) sum_src; + ctx->tally_dst = (uint32_t) sum_dst; +} - SVFRT_bump_struct_contents( - ctx, - inner_s0_index, - inner_s1_index, - inner_input_range - ); +typedef struct SVFRT_Phase2_TraverseAnyType { + SVFRT_Bytes data_range_dst; + uint32_t data_offset_dst; +} SVFRT_Phase2_TraverseAnyType; - break; - } - case SVF_META_ConcreteType_defined_choice: { - uint32_t inner_c0_index = u0->concrete.type_union.defined_choice.index; - uint32_t inner_c1_index = u1->concrete.type_union.defined_choice.index; +// This declaration is needed because of recursive calls during traversal. +// `recursion_depth` should be incremented on each call and guards against +// runaway call stacks. +void SVFRT_conversion_traverse_any_type( + SVFRT_ConversionContext *ctx, + uint32_t recursion_depth, + SVFRT_Bytes data_range_src, + uint32_t unsafe_data_offset_src, + SVF_META_Type_enum unsafe_type_enum_src, + SVF_META_Type_union *unsafe_type_union_src, + SVF_META_Type_enum type_enum_dst, + SVF_META_Type_union *type_union_dst, + SVFRT_Phase2_TraverseAnyType *phase2 +); - SVFRT_Bytes input_tag_bytes = { - /*.pointer =*/ range_from.pointer + offset_from, - /*.count =*/ SVFRT_TAG_SIZE, - }; +typedef struct SVFRT_Phase2_TraverseStruct { + SVFRT_Bytes struct_bytes_dst; +} SVFRT_Phase2_TraverseStruct; - if (input_tag_bytes.pointer + input_tag_bytes.count > range_from.pointer + range_from.count) { - ctx->internal_error = true; - return; - } +void SVFRT_conversion_traverse_struct( + SVFRT_ConversionContext *ctx, + uint32_t recursion_depth, + uint32_t unsafe_struct_index_src, + uint32_t struct_index_dst, + SVFRT_Bytes struct_bytes_src, + SVFRT_Phase2_TraverseStruct *phase2 +) { + if (unsafe_struct_index_src >= ctx->unsafe_structs_src.count) { + ctx->error_code = SVFRT_code_conversion__bad_schema_struct_index; + return; + } - uint8_t input_tag = *input_tag_bytes.pointer; + if (struct_index_dst >= ctx->structs_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__bad_schema_struct_index; + return; + } - SVF_META_ChoiceDefinition *c0 = ctx->choices0.pointer + inner_c0_index; - SVF_META_ChoiceDefinition *c1 = ctx->choices1.pointer + inner_c1_index; + SVF_META_StructDefinition *unsafe_definition_src = ctx->unsafe_structs_src.pointer + unsafe_struct_index_src; + SVF_META_StructDefinition *definition_dst = ctx->structs_dst.pointer + struct_index_dst; - SVFRT_RangeOptionDefinition options0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r0, c0->options, SVF_META_OptionDefinition); - SVFRT_RangeOptionDefinition options1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r1, c1->options, SVF_META_OptionDefinition); + SVFRT_RangeFieldDefinition unsafe_fields_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->info->unsafe_schema_src, + unsafe_definition_src->fields, + SVF_META_FieldDefinition + ); + if (!unsafe_fields_src.pointer && unsafe_fields_src.count) { + ctx->error_code = SVFRT_code_conversion__bad_schema_field_index; + return; + } - if (!options0.pointer || !options1.pointer) { - ctx->internal_error = true; - return; - } + SVFRT_RangeFieldDefinition fields_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->info->schema_dst, + definition_dst->fields, + SVF_META_FieldDefinition + ); + if (!fields_dst.pointer && fields_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__bad_schema_field_index; + return; + } - if (input_tag >= options0.count) { - ctx->data_error = true; - return; - } + // Go over any sequences and references in the intersection between + // `unsafe_definition_src` and `definition_dst`. Sub-structs and choices may + // also contain these, so recurse over them. + // + // Assumption: we have verified compatibility, so every dst-schema field will have + // exactly one corresponding src-schema field. + // + // See #compatibility-reasonable-fields. + // + // Assumption: during the compatibility check, we have gone over the "unsafe" + // fields and did find correspondences in a reasonable time. We should have + // actually built a correspondence table then, see below... + // + // TODO @performance: N^2. This looks critical, because here, worst-case data + // _and_ worst-case schema could lead to a time-bound, yet unexpectedly slow + // conversion. A table will solve everything here. + // + for (uint32_t i = 0; i < fields_dst.count; i++) { + SVF_META_FieldDefinition *field_dst = fields_dst.pointer + i; - SVF_META_OptionDefinition *option0 = options0.pointer + input_tag; - SVF_META_OptionDefinition *option1 = NULL; + bool found = false; - for (uint32_t i = 0; i < options1.count; i++) { - SVF_META_OptionDefinition *option = options1.pointer + i; + for (uint32_t j = 0; j < unsafe_fields_src.count; j++) { + SVF_META_FieldDefinition *unsafe_field_src = unsafe_fields_src.pointer + j; - if (option->name_hash == option0->name_hash) { - option1 = option; - break; - } - } + if (unsafe_field_src->name_hash != field_dst->name_hash) { + continue; + } - if (!option1) { - ctx->internal_error = true; - return; - } + SVFRT_Phase2_TraverseAnyType phase2_inner = {0}; + if (phase2) { + phase2_inner.data_range_dst = phase2->struct_bytes_dst; + phase2_inner.data_offset_dst = field_dst->offset; + } - SVFRT_bump_type( - ctx, - option0->type_enum, - &option0->type_union, - range_from, - offset_from + SVFRT_TAG_SIZE, - option1->type_enum, - &option1->type_union - ); + SVFRT_conversion_traverse_any_type( + ctx, + recursion_depth, + struct_bytes_src, + unsafe_field_src->offset, + unsafe_field_src->type_enum, + &unsafe_field_src->type_union, + field_dst->type_enum, + &field_dst->type_union, + phase2 ? &phase2_inner : NULL + ); - break; - } - default: { - // Do nothing. + if (ctx->error_code) { + // Early exit on any error. + return; } - } - } - else if (t0 == SVF_META_Type_reference) { - // Sanity check. - if (t1 != SVF_META_Type_reference) { - ctx->internal_error = true; - return; + + found = true; + break; } - if ((uint64_t) offset_from + sizeof(SVFRT_Reference) > range_from.count) { - ctx->data_error = true; + if (!found) { + ctx->error_code = SVFRT_code_conversion_internal__schema_field_missing; return; } + } +} - SVFRT_Reference in_reference = *((SVFRT_Reference *) (range_from.pointer + offset_from)); - - switch (u1->reference.type_enum) { - case SVF_META_ConcreteType_defined_struct: { - // Sanity check. - if (u0->reference.type_enum != SVF_META_ConcreteType_defined_struct) { - ctx->internal_error = true; - return; - } - - uint32_t inner_s0_index = u0->reference.type_union.defined_struct.index; - uint32_t inner_s1_index = u1->reference.type_union.defined_struct.index; - - uint32_t inner_input_size = ctx->structs0.pointer[inner_s0_index].size; +typedef struct SVFRT_Phase2_TraverseConcreteType { + SVFRT_Bytes data_range_dst; + uint32_t data_offset_dst; +} SVFRT_Phase2_TraverseConcreteType; - void *inner_input_data = SVFRT_internal_from_reference(ctx->data_bytes, in_reference, inner_input_size); - if (!inner_input_data) { - ctx->data_error = true; - return; - } +void SVFRT_conversion_traverse_concrete_type( + SVFRT_ConversionContext *ctx, + uint32_t recursion_depth, + SVFRT_Bytes data_range_src, + uint32_t unsafe_data_offset_src, + SVF_META_ConcreteType_enum unsafe_type_enum_src, + SVF_META_ConcreteType_union *unsafe_type_union_src, + SVF_META_ConcreteType_enum type_enum_dst, + SVF_META_ConcreteType_union *type_union_dst, + SVFRT_Phase2_TraverseConcreteType *phase2 +) { + switch (type_enum_dst) { + case SVF_META_ConcreteType_defined_struct: { + // Sanity check. + if (unsafe_type_enum_src != SVF_META_ConcreteType_defined_struct) { + ctx->error_code = SVFRT_code_conversion__schema_concrete_type_enum_mismatch; + return; + } - SVFRT_Bytes inner_input_range = { - /*.pointer =*/ (uint8_t *) inner_input_data, - /*.count =*/ inner_input_size, - }; + uint32_t unsafe_struct_index_src = unsafe_type_union_src->defined_struct.index; + uint32_t struct_index_dst = type_union_dst->defined_struct.index; - SVFRT_bump_struct_contents(ctx, inner_s0_index, inner_s1_index, inner_input_range); - SVFRT_bump_structs(ctx, inner_s1_index, 1); - break; - } - case SVF_META_ConcreteType_u8: { - SVFRT_bump_size(ctx, 1); - break; - } - case SVF_META_ConcreteType_u16: { - SVFRT_bump_size(ctx, 2); - break; - } - case SVF_META_ConcreteType_u32: { - SVFRT_bump_size(ctx, 4); - break; - } - case SVF_META_ConcreteType_u64: { - SVFRT_bump_size(ctx, 8); - break; - } - case SVF_META_ConcreteType_i8: { - SVFRT_bump_size(ctx, 1); - break; - } - case SVF_META_ConcreteType_i16: { - SVFRT_bump_size(ctx, 2); - break; - } - case SVF_META_ConcreteType_i32: { - SVFRT_bump_size(ctx, 4); - break; - } - case SVF_META_ConcreteType_i64: { - SVFRT_bump_size(ctx, 8); - break; - } - case SVF_META_ConcreteType_f32: { - SVFRT_bump_size(ctx, 4); - break; - } - case SVF_META_ConcreteType_f64: { - SVFRT_bump_size(ctx, 8); - break; - } - case SVF_META_ConcreteType_defined_choice: - case SVF_META_ConcreteType_zero_sized: - default: { - ctx->internal_error = true; + if (unsafe_struct_index_src >= ctx->unsafe_structs_src.count) { + ctx->error_code = SVFRT_code_conversion__bad_schema_struct_index; return; } - } - } else if (t0 == SVF_META_Type_sequence) { - // Sanity check. - if (t1 != SVF_META_Type_sequence) { - ctx->internal_error = true; - return; - } + uint32_t unsafe_struct_size_src = ctx->unsafe_structs_src.pointer[unsafe_struct_index_src].size; - if ((uint64_t) offset_from + sizeof(SVFRT_Sequence) > range_from.count) { - ctx->data_error = true; - return; - } + // Prevent addition overflow by casting operands to `uint64_t` first. + if ((uint64_t) unsafe_data_offset_src + (uint64_t) unsafe_struct_size_src > (uint64_t) data_range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } - SVFRT_Sequence in_sequence = *((SVFRT_Sequence *) (range_from.pointer + offset_from)); + SVFRT_Bytes struct_bytes_src = { + /*.pointer =*/ (uint8_t *) data_range_src.pointer + unsafe_data_offset_src, + /*.count =*/ unsafe_struct_size_src + }; - switch (u0->sequence.element_type_enum) { - case SVF_META_ConcreteType_defined_struct: { - // Sanity check. - if (u1->sequence.element_type_enum != SVF_META_ConcreteType_defined_struct) { - ctx->internal_error = true; + SVFRT_Phase2_TraverseStruct phase2_inner = {0}; + if (phase2) { + if (struct_index_dst >= ctx->structs_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__bad_schema_struct_index; return; } + uint32_t struct_size_dst = ctx->structs_dst.pointer[struct_index_dst].size; - uint32_t inner_s0_index = u0->sequence.element_type_union.defined_struct.index; - uint32_t inner_s1_index = u1->sequence.element_type_union.defined_struct.index; - size_t inner_size = ctx->structs0.pointer[inner_s0_index].size; - - void *inner_data = SVFRT_internal_from_sequence(ctx->data_bytes, in_sequence, inner_size); - if (!inner_data) { - ctx->data_error = true; + // Prevent addition overflow by casting operands to `uint64_t` first. + if ((uint64_t) phase2->data_offset_dst + (uint64_t) struct_size_dst > (uint64_t) phase2->data_range_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__suballocation_out_of_bounds; return; } - for (uint32_t i = 0; i < in_sequence.count; i++) { - SVFRT_Bytes inner_range = { - /*.pointer =*/ ((uint8_t *) inner_data) + inner_size * i, - /*.count =*/ inner_size, - }; - - SVFRT_bump_struct_contents(ctx, inner_s0_index, inner_s1_index, inner_range); - } - SVFRT_bump_structs(ctx, inner_s1_index, in_sequence.count); + SVFRT_Bytes struct_bytes_dst = { + /*.pointer =*/ (uint8_t *) phase2->data_range_dst.pointer + phase2->data_offset_dst, + /*.count =*/ struct_size_dst + }; - break; - } - case SVF_META_ConcreteType_u8: { - SVFRT_bump_size(ctx, 1 * in_sequence.count); - } - case SVF_META_ConcreteType_u16: { - SVFRT_bump_size(ctx, 2 * in_sequence.count); - } - case SVF_META_ConcreteType_u32: { - SVFRT_bump_size(ctx, 4 * in_sequence.count); - } - case SVF_META_ConcreteType_u64: { - SVFRT_bump_size(ctx, 8 * in_sequence.count); - } - case SVF_META_ConcreteType_i8: { - SVFRT_bump_size(ctx, 1 * in_sequence.count); + phase2_inner.struct_bytes_dst = struct_bytes_dst; } - case SVF_META_ConcreteType_i16: { - SVFRT_bump_size(ctx, 2 * in_sequence.count); - } - case SVF_META_ConcreteType_i32: { - SVFRT_bump_size(ctx, 4 * in_sequence.count); - } - case SVF_META_ConcreteType_i64: { - SVFRT_bump_size(ctx, 8 * in_sequence.count); - } - case SVF_META_ConcreteType_f32: { - SVFRT_bump_size(ctx, 4 * in_sequence.count); - } - case SVF_META_ConcreteType_f64: { - SVFRT_bump_size(ctx, 8 * in_sequence.count); - } - case SVF_META_ConcreteType_defined_choice: - case SVF_META_ConcreteType_zero_sized: - default: { - ctx->internal_error = true; + + SVFRT_conversion_traverse_struct( + ctx, + recursion_depth, + unsafe_struct_index_src, + struct_index_dst, + struct_bytes_src, + phase2 ? &phase2_inner : NULL + ); + return; + } + case SVF_META_ConcreteType_defined_choice: { + // Sanity check. + if (unsafe_type_enum_src != SVF_META_ConcreteType_defined_choice) { + ctx->error_code = SVFRT_code_conversion__schema_concrete_type_enum_mismatch; return; } - } - } -} - -void SVFRT_bump_struct_contents( - SVFRT_ConversionContext *ctx, - size_t s0_index, - size_t s1_index, - SVFRT_Bytes input_bytes -) { - if (s0_index >= ctx->structs0.count) { - ctx->internal_error = true; - return; - } - if (s1_index >= ctx->structs1.count) { - ctx->internal_error = true; - return; - } + uint32_t unsafe_choice_index_src = unsafe_type_union_src->defined_choice.index; + uint32_t choice_index_dst = type_union_dst->defined_choice.index; - SVF_META_StructDefinition *s0 = ctx->structs0.pointer + s0_index; - SVF_META_StructDefinition *s1 = ctx->structs1.pointer + s1_index; + // Prevent addition overflow by casting operands to `uint64_t` first. + if ((uint64_t) unsafe_data_offset_src + (uint64_t) SVFRT_TAG_SIZE > (uint64_t) data_range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } - SVFRT_RangeFieldDefinition fields0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r0, s0->fields, SVF_META_FieldDefinition); - SVFRT_RangeFieldDefinition fields1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r1, s1->fields, SVF_META_FieldDefinition); - if (!fields0.pointer || !fields1.pointer) { - ctx->internal_error = true; - return; - } + SVFRT_Bytes choice_tag_bytes_src = { + /*.pointer =*/ data_range_src.pointer + unsafe_data_offset_src, + /*.count =*/ SVFRT_TAG_SIZE + }; - // Go over any sequences and references in the intersection between s0 and s1. - // Sub-structs and choices may also contain these, so recurse over them. + uint8_t choice_tag = *choice_tag_bytes_src.pointer; - // @TODO @performance: N^2. - for (uint32_t i = 0; i < fields0.count; i++) { - SVF_META_FieldDefinition *field0 = fields0.pointer + i; + if (unsafe_choice_index_src >= ctx->unsafe_choices_src.count) { + ctx->error_code = SVFRT_code_conversion__bad_schema_choice_index; + return; + } + SVF_META_ChoiceDefinition *unsafe_definition_src = ctx->unsafe_choices_src.pointer + unsafe_choice_index_src; - for (uint32_t j = 0; j < fields1.count; j++) { - SVF_META_FieldDefinition *field1 = fields1.pointer + j; - if (field0->name_hash != field1->name_hash) { - continue; + if (choice_index_dst >= ctx->choices_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__bad_schema_choice_index; + return; } + SVF_META_ChoiceDefinition *definition_dst = ctx->choices_dst.pointer + choice_index_dst; - SVFRT_bump_type( - ctx, - field0->type_enum, - &field0->type_union, - input_bytes, - field0->offset, - field1->type_enum, - &field1->type_union + SVFRT_RangeOptionDefinition unsafe_options_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->info->unsafe_schema_src, + unsafe_definition_src->options, + SVF_META_OptionDefinition ); - } - } -} + if (!unsafe_options_src.pointer && unsafe_options_src.count) { + ctx->error_code = SVFRT_code_conversion__bad_schema_options; + return; + } -void SVFRT_copy_struct( - SVFRT_ConversionContext *ctx, - size_t recursion_depth, - size_t s0_index, - size_t s1_index, - SVFRT_Bytes input_bytes, - SVFRT_Bytes output_bytes -); + SVFRT_RangeOptionDefinition options_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + ctx->info->schema_dst, + definition_dst->options, + SVF_META_OptionDefinition + ); + if (!options_dst.pointer && options_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__bad_schema_options; + return; + } -void SVFRT_copy_type( - SVFRT_ConversionContext *ctx, - size_t recursion_depth, - SVF_META_Type_enum t0, - SVF_META_Type_union *u0, - SVFRT_Bytes range_from, - uint32_t offset_from, - SVF_META_Type_enum t1, - SVF_META_Type_union *u1, - SVFRT_Bytes range_to, - uint32_t offset_to -); + // Assumption: we have done the compatibility check, which should have + // ensured that src-schema options are a subset of dst-schema options. + // + // See #compatibility-reasonable-options. + // + // TODO: like with struct fields, we should actually have built a + // correspondence table during the compatibiltiy check. -void SVFRT_copy_size( - SVFRT_ConversionContext *ctx, - SVFRT_Bytes range_from, - uint32_t offset_from, - SVFRT_Bytes range_to, - uint32_t offset_to, - size_t size -) { - SVFRT_Bytes input_bytes = { - /*.pointer =*/ range_from.pointer + offset_from, - /*.count =*/ size, - }; - if (input_bytes.pointer + input_bytes.count > range_from.pointer + range_from.count) { - ctx->internal_error = true; - return; - } + bool found_src = false; + bool found_dst = false; - SVFRT_Bytes output_bytes = { - /*.pointer =*/ range_to.pointer + offset_to, - /*.count =*/ size, - }; - if (output_bytes.pointer + output_bytes.count > range_to.pointer + range_to.count) { - ctx->internal_error = true; - return; - } + for (uint32_t i = 0; i < unsafe_options_src.count; i++) { + SVF_META_OptionDefinition *unsafe_option_src = unsafe_options_src.pointer + i; - SVFRT_MEMCPY(output_bytes.pointer, input_bytes.pointer, size); -} + if (unsafe_option_src->index != choice_tag) { + continue; + } -void SVFRT_copy_concrete( - SVFRT_ConversionContext *ctx, - size_t recursion_depth, - SVF_META_ConcreteType_enum t0, - SVF_META_ConcreteType_union *u0, - SVFRT_Bytes range_from, - uint32_t offset_from, - SVF_META_ConcreteType_enum t1, - SVF_META_ConcreteType_union *u1, - SVFRT_Bytes range_to, - uint32_t offset_to -) { - if (recursion_depth > ctx->max_recursion_depth) { - ctx->data_error = true; - return; - } + for (uint32_t j = 0; j < options_dst.count; j++) { + SVF_META_OptionDefinition *option_dst = options_dst.pointer + j; - if (t0 == t1) { - switch (t0) { - case SVF_META_ConcreteType_defined_struct: { - SVF_META_StructDefinition *s0 = ctx->structs0.pointer + u0->defined_struct.index; - SVF_META_StructDefinition *s1 = ctx->structs1.pointer + u1->defined_struct.index; + if (option_dst->name_hash != unsafe_option_src->name_hash) { + continue; + } - SVFRT_Bytes input_bytes = { - /*.pointer =*/ range_from.pointer + offset_from, - /*.count =*/ s0->size, - }; - if (input_bytes.pointer + input_bytes.count > range_from.pointer + range_from.count) { - ctx->internal_error = true; - return; - } + SVFRT_Phase2_TraverseAnyType phase2_inner = {0}; + if (phase2) { + // Make sure we can at least write the tag. + // + // Prevent addition overflow by casting operands to `uint64_t` first. + // + // In this case, it looks rather silly, as we are adding _one_. But + // proving an overflow can't happen is much harder that just checking. + if ((uint64_t) phase2->data_offset_dst + (uint64_t) SVFRT_TAG_SIZE > (uint64_t) phase2->data_range_dst.count) { + ctx->error_code = SVFRT_code_conversion_internal__suballocation_out_of_bounds; + return; + } + + // Write the tag. + // TODO @proper-alignment. + *(phase2->data_range_dst.pointer + phase2->data_offset_dst) = option_dst->index; + + phase2_inner.data_range_dst = phase2->data_range_dst; + + // TODO @proper-alignment. + phase2_inner.data_offset_dst = phase2->data_offset_dst + SVFRT_TAG_SIZE; + } - SVFRT_Bytes output_bytes = { - /*.pointer =*/ range_to.pointer + offset_to, - /*.count =*/ s1->size, - }; - if (output_bytes.pointer + output_bytes.count > range_to.pointer + range_to.count) { - ctx->internal_error = true; - return; + SVFRT_conversion_traverse_any_type( + ctx, + recursion_depth, + data_range_src, + unsafe_data_offset_src + SVFRT_TAG_SIZE, // TODO: @proper-alignment. + unsafe_option_src->type_enum, + &unsafe_option_src->type_union, + option_dst->type_enum, + &option_dst->type_union, + phase2 ? &phase2_inner : NULL + ); + + found_dst = true; + break; } - SVFRT_copy_struct( - ctx, - recursion_depth, - u0->defined_struct.index, - u1->defined_struct.index, - input_bytes, - output_bytes - ); + found_src = true; break; } - case SVF_META_ConcreteType_defined_choice: { - SVF_META_ChoiceDefinition *c0 = ctx->choices0.pointer + u0->defined_struct.index; - SVF_META_ChoiceDefinition *c1 = ctx->choices1.pointer + u1->defined_struct.index; - // First, remap the tag. + if (!found_src) { + ctx->error_code = SVFRT_code_conversion__bad_choice_tag; + return; + } + + if (!found_dst) { + ctx->error_code = SVFRT_code_conversion_internal__schema_option_missing; + return; + } - SVFRT_Bytes input_tag_bytes = { - /*.pointer =*/ range_from.pointer + offset_from, - /*.count =*/ SVFRT_TAG_SIZE, - }; - if (input_tag_bytes.pointer + input_tag_bytes.count > range_from.pointer + range_from.count) { - ctx->internal_error = true; + return; + } + case SVF_META_ConcreteType_zero_sized: { + // Sanity check. This case should only arise inside choices. + if (unsafe_type_enum_src != SVF_META_ConcreteType_zero_sized) { + ctx->error_code = SVFRT_code_conversion__schema_concrete_type_enum_mismatch; + } + return; + } + case SVF_META_ConcreteType_u64: { + if (!phase2) return; + uint64_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_u64: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(uint64_t) + ); return; } - - SVFRT_Bytes output_tag_bytes = { - /*.pointer =*/ range_to.pointer + offset_to, - /*.count =*/ SVFRT_TAG_SIZE, - }; - if (output_tag_bytes.pointer + output_tag_bytes.count > range_to.pointer + range_to.count) { - ctx->internal_error = true; + case SVF_META_ConcreteType_u32: { // U32 -> U64. + out_value = (uint64_t) SVFRT_conversion_read_uint32_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u16: { // U16 -> U64. + out_value = (uint64_t) SVFRT_conversion_read_uint16_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u8: { // U8 -> U64. + out_value = (uint64_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; return; } + } - uint8_t input_tag = *input_tag_bytes.pointer; - - SVFRT_RangeOptionDefinition options0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r0, c0->options, SVF_META_OptionDefinition); - SVFRT_RangeOptionDefinition options1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r1, c1->options, SVF_META_OptionDefinition); - if (!options0.pointer || !options1.pointer) { - ctx->internal_error = true; + SVFRT_conversion_write_uint64_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_u32: { + if (!phase2) return; + uint32_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_u32: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(uint32_t) + ); return; } - - SVF_META_OptionDefinition *option0 = NULL; - for (uint32_t i = 0; i < options0.count; i++) { - SVF_META_OptionDefinition *option = options0.pointer + i; - - if (option->index == input_tag) { - option0 = option; - break; - } + case SVF_META_ConcreteType_u16: { // U16 -> U32. + out_value = (uint32_t) SVFRT_conversion_read_uint16_t(ctx, data_range_src, unsafe_data_offset_src); + break; } - - if (!option0) { - ctx->internal_error = true; + case SVF_META_ConcreteType_u8: { // U8 -> U32. + out_value = (uint32_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; return; } + } - SVF_META_OptionDefinition *option1 = NULL; - for (uint32_t i = 0; i < options1.count; i++) { - SVF_META_OptionDefinition *option = options1.pointer + i; - - if (option->name_hash == option0->name_hash) { - option1 = option; - break; - } + SVFRT_conversion_write_uint32_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_u16: { + if (!phase2) return; + uint16_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_u16: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(uint16_t) + ); + return; } - - if (!option1) { - ctx->internal_error = true; + case SVF_META_ConcreteType_u8: { // U8 -> U16. + out_value = (uint16_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; return; } - - *output_tag_bytes.pointer = option1->index; - - SVFRT_copy_type( - ctx, - recursion_depth, - option0->type_enum, - &option0->type_union, - range_from, - // TODO @proper-alignment. - offset_from + SVFRT_TAG_SIZE, - option1->type_enum, - &option1->type_union, - range_to, - // TODO @proper-alignment. - offset_to + SVFRT_TAG_SIZE - ); - - break; - } - case SVF_META_ConcreteType_u8: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 1); - break; } - case SVF_META_ConcreteType_u16: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 2); - break; - } - case SVF_META_ConcreteType_u32: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 4); - break; - } - case SVF_META_ConcreteType_u64: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 8); - break; - } - case SVF_META_ConcreteType_i8: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 1); - break; - } - case SVF_META_ConcreteType_i16: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 2); - break; - } - case SVF_META_ConcreteType_i32: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 4); - break; - } - case SVF_META_ConcreteType_i64: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 8); - break; - } - case SVF_META_ConcreteType_f32: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 4); - break; - } - case SVF_META_ConcreteType_f64: { - SVFRT_copy_size(ctx, range_from, offset_from, range_to, offset_to, 8); - break; - } - case SVF_META_ConcreteType_zero_sized: { - // Not sure about this case. - ctx->internal_error = true; + + SVFRT_conversion_write_uint16_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_u8: { + if (!phase2) return; + if (unsafe_type_enum_src != SVF_META_ConcreteType_u8) { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; return; } + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(uint8_t) + ); + return; } - } else { - switch (t1) { - case SVF_META_ConcreteType_u64: { - uint64_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_u32: { - // U32 -> U64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (uint64_t) *((uint32_t *) (range_from.pointer + offset_from)); - #else - uint32_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (uint64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u16: { - // U16 -> U64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (uint64_t) *((uint16_t *) (range_from.pointer + offset_from)); - #else - uint16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (uint64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u8: { - // U8 -> U64. Can't be misaligned. - out_value = (uint64_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + case SVF_META_ConcreteType_i64: { + if (!phase2) return; + int64_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_i64: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(int64_t) + ); + return; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((uint64_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; - } - case SVF_META_ConcreteType_u32: { - uint32_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_u16: { - // U16 -> U32 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (uint32_t) *((uint16_t *) (range_from.pointer + offset_from)); - #else - uint16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (uint32_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u8: { - // U8 -> U32. Can't be misaligned. - out_value = (uint32_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + case SVF_META_ConcreteType_i32: { // I32 -> I64. + out_value = (int64_t) SVFRT_conversion_read_int32_t(ctx, data_range_src, unsafe_data_offset_src); + break; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((uint32_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; - } - case SVF_META_ConcreteType_u16: { - uint16_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_u8: { - // U8 -> U16. Can't be misaligned. - out_value = (uint16_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + case SVF_META_ConcreteType_i16: { // I16 -> I64. + out_value = (int64_t) SVFRT_conversion_read_int16_t(ctx, data_range_src, unsafe_data_offset_src); + break; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((uint16_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; - } - case SVF_META_ConcreteType_i64: { - int64_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_i32: { - // I32 -> I64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int64_t) *((int32_t *) (range_from.pointer + offset_from)); - #else - int32_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_i16: { - // I16 -> I64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int64_t) *((int16_t *) (range_from.pointer + offset_from)); - #else - int16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_i8: { - // I8 -> I64. Can't be misaligned. - out_value = (int64_t) *((int8_t *) (range_from.pointer + offset_from)); - break; - } - case SVF_META_ConcreteType_u32: { - // U32 -> I64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int64_t) *((uint32_t *) (range_from.pointer + offset_from)); - #else - uint32_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u16: { - // U16 -> I64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int64_t) *((uint16_t *) (range_from.pointer + offset_from)); - #else - uint16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int64_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u8: { - // U8 -> I64. Can't be misaligned. - out_value = (int64_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + case SVF_META_ConcreteType_i8: { // I8 -> I64. + out_value = (int64_t) SVFRT_conversion_read_int8_t(ctx, data_range_src, unsafe_data_offset_src); + break; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((int64_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; - } - case SVF_META_ConcreteType_i32: { - int32_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_i16: { - // I16 -> I32 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int32_t) *((int16_t *) (range_from.pointer + offset_from)); - #else - int16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int32_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_i8: { - // I8 -> I32. Can't be misaligned. - out_value = (int32_t) *((int8_t *) (range_from.pointer + offset_from)); - break; - } - case SVF_META_ConcreteType_u16: { - // U16 -> I32 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (int32_t) *((uint16_t *) (range_from.pointer + offset_from)); - #else - uint16_t in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (int32_t) in_value; - #endif - - break; - } - case SVF_META_ConcreteType_u8: { - // U8 -> I32. Can't be misaligned. - out_value = (int32_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + case SVF_META_ConcreteType_u32: { // U32 -> I64. + out_value = (int64_t) SVFRT_conversion_read_uint32_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u16: { // U16 -> I64. + out_value = (int64_t) SVFRT_conversion_read_uint16_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u8: { // U8 -> I64. + out_value = (int64_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; + return; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((int32_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; } - case SVF_META_ConcreteType_i16: { - int16_t out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_i8: { - // I8 -> I16. Can't be misaligned. - out_value = (int16_t) *((int8_t *) (range_from.pointer + offset_from)); - break; - } - case SVF_META_ConcreteType_u8: { - // U8 -> I16. Can't be misaligned. - out_value = (int16_t) *((uint8_t *) (range_from.pointer + offset_from)); - break; - } - default: { - ctx->internal_error = true; - return; - } + + SVFRT_conversion_write_int64_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_i32: { + if (!phase2) return; + int32_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_i32: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(int32_t) + ); + return; + } + case SVF_META_ConcreteType_i64: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(int64_t) + ); + return; } + case SVF_META_ConcreteType_i16: { // I16 -> I32. + out_value = (int32_t) SVFRT_conversion_read_int16_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_i8: { // I8 -> I32. + out_value = (int32_t) SVFRT_conversion_read_int8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u16: { // U16 -> I32. + out_value = (int32_t) SVFRT_conversion_read_uint16_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u8: { // U8 -> I32. + out_value = (int32_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; + return; + } + } - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((int16_t *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif + SVFRT_conversion_write_int32_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_i16: { + if (!phase2) return; + int16_t out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_i16: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(int16_t) + ); + return; + } + case SVF_META_ConcreteType_i8: { // I8 -> I16. + out_value = (int16_t) SVFRT_conversion_read_int8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + case SVF_META_ConcreteType_u8: { // U8 -> I16. + out_value = (int16_t) SVFRT_conversion_read_uint8_t(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; + return; + } + } - break; + SVFRT_conversion_write_int16_t(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_i8: { + if (!phase2) return; + if (unsafe_type_enum_src != SVF_META_ConcreteType_i8) { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; + return; } - case SVF_META_ConcreteType_f64: { - // Note: not sure, if lossless integer -> float conversion should be - // allowed or not. Probably not, because the semantics are very different. - double out_value = 0; - switch (t0) { - case SVF_META_ConcreteType_f32: { - // F32 -> F64 - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - out_value = (double) *((float *) (range_from.pointer + offset_from)); - #else - float in_value; - SVFRT_MEMCPY(&in_value, range_from.pointer + offset_from, sizeof(in_value)); - out_value = (double) in_value; - #endif - - break; - } - default: { - ctx->internal_error = true; - return; - } + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(int8_t) + ); + return; + } + case SVF_META_ConcreteType_f64: { + if (!phase2) return; + // Note: it is unclear, whether lossless integer -> float conversions + // should be allowed or not. Probably not, because the semantics are very + // different. + + double out_value = 0; + switch (unsafe_type_enum_src) { + case SVF_META_ConcreteType_f64: { // Exact case, copy and exit. + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(double) + ); + return; + } + case SVF_META_ConcreteType_f32: { // F32 -> F64. + out_value = (double) SVFRT_conversion_read_float(ctx, data_range_src, unsafe_data_offset_src); + break; + } + default: { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; + return; } - - #ifdef SVFRT_DIRECT_UNALIGNED_ACCESS - *((double *) (range_to.pointer + offset_to)) = out_value; - #else - SVFRT_MEMCPY(range_to.pointer + offset_to, &out_value, sizeof(out_value)); - #endif - - break; } - default: { - ctx->internal_error = true; + + SVFRT_conversion_write_double(ctx, phase2->data_range_dst, phase2->data_offset_dst, out_value); + return; + } + case SVF_META_ConcreteType_f32: { + if (!phase2) return; + if (unsafe_type_enum_src != SVF_META_ConcreteType_f32) { + ctx->error_code = SVFRT_code_conversion_internal__schema_incompatible_types; return; } + SVFRT_conversion_copy_exact( + ctx, + data_range_src, + unsafe_data_offset_src, + phase2->data_range_dst, + phase2->data_offset_dst, + sizeof(float) + ); + return; + } + default: { + ctx->error_code = SVFRT_code_conversion__bad_type; + return; } } } -void SVFRT_conversion_suballocate( - SVFRT_Bytes *out_result, +void SVFRT_conversion_traverse_any_type( SVFRT_ConversionContext *ctx, - uint32_t size + uint32_t recursion_depth, + SVFRT_Bytes data_range_src, + uint32_t unsafe_data_offset_src, + SVF_META_Type_enum unsafe_type_enum_src, + SVF_META_Type_union *unsafe_type_union_src, + SVF_META_Type_enum type_enum_dst, + SVF_META_Type_union *type_union_dst, + SVFRT_Phase2_TraverseAnyType *phase2 ) { - // TODO @proper-alignment. - - if (ctx->allocated_already + (uint64_t) size > ctx->allocation.count) { + recursion_depth += 1; + if (recursion_depth >= ctx->max_recursion_depth) { + ctx->error_code = SVFRT_code_conversion__max_recursion_depth_exceeded; return; } - out_result->pointer = ctx->allocation.pointer + ctx->allocated_already; - out_result->count = (uint64_t) size; - - ctx->allocated_already += size; -} + switch (unsafe_type_enum_src) { + case SVF_META_Type_concrete: { + // Sanity check. + if (type_enum_dst != SVF_META_Type_concrete) { + ctx->error_code = SVFRT_code_conversion__schema_type_enum_mismatch; + return; + } -void SVFRT_copy_type( - SVFRT_ConversionContext *ctx, - size_t recursion_depth, - SVF_META_Type_enum t0, - SVF_META_Type_union *u0, - SVFRT_Bytes range_from, - uint32_t offset_from, - SVF_META_Type_enum t1, - SVF_META_Type_union *u1, - SVFRT_Bytes range_to, - uint32_t offset_to -) { - if (t0 == SVF_META_Type_concrete) { - if (t1 != SVF_META_Type_concrete) { - ctx->internal_error = true; - return; - } + SVFRT_Phase2_TraverseConcreteType phase2_inner = {0}; + if (phase2) { + phase2_inner.data_range_dst = phase2->data_range_dst; + phase2_inner.data_offset_dst = phase2->data_offset_dst; + } - SVFRT_copy_concrete( - ctx, - recursion_depth, - u0->concrete.type_enum, - &u0->concrete.type_union, - range_from, - offset_from, - u1->concrete.type_enum, - &u1->concrete.type_union, - range_to, - offset_to - ); - } else if (t0 == SVF_META_Type_reference) { - if (t1 != SVF_META_Type_reference) { - ctx->internal_error = true; - return; - } + SVFRT_conversion_traverse_concrete_type( + ctx, + recursion_depth, + data_range_src, + unsafe_data_offset_src, + unsafe_type_union_src->concrete.type_enum, + &unsafe_type_union_src->concrete.type_union, + type_union_dst->concrete.type_enum, + &type_union_dst->concrete.type_union, + phase2 ? &phase2_inner : NULL + ); - if ((uint64_t) offset_from + sizeof(SVFRT_Reference) > range_from.count) { - ctx->data_error = true; return; } + case SVF_META_Type_reference: { + // Sanity check. + if (type_enum_dst != SVF_META_Type_reference) { + ctx->error_code = SVFRT_code_conversion__schema_type_enum_mismatch; + return; + } - SVFRT_Reference in_reference = *((SVFRT_Reference *) (range_from.pointer + offset_from)); - - uint32_t inner_output_size = SVFRT_get_type_size( - ctx, - ctx->structs1, - u1->reference.type_enum, - &u1->reference.type_union - ); + // Prevent addition overflow by casting operands to `uint64_t` first. + if ((uint64_t) unsafe_data_offset_src + (uint64_t) sizeof(SVFRT_Reference) > (uint64_t) data_range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } + // TODO @proper-alignment: resulting pointer might be misaligned, watch out. + SVFRT_Reference unsafe_representation_src = *((SVFRT_Reference *) (data_range_src.pointer + unsafe_data_offset_src)); - SVFRT_Bytes suballocation = {0}; - SVFRT_conversion_suballocate( - &suballocation, - ctx, - inner_output_size - ); + if (unsafe_representation_src.data_offset_complement == 0) { + // Allow invalid references, but only if the representation is zero. - if (range_to.count < offset_to + sizeof(SVFRT_Reference)) { - ctx->internal_error = true; - return; - } + // For Phase 1, nothing needs to be done here. + // For Phase 2, the dst-representation is zero already, which is fine. + return; + } - SVFRT_Reference *out_reference = (SVFRT_Reference *) (range_to.pointer + offset_to); - out_reference->data_offset_complement = ~(suballocation.pointer - ctx->allocation.pointer); - - SVFRT_copy_concrete( - ctx, - recursion_depth + 1, - u0->reference.type_enum, - &u0->reference.type_union, - ctx->data_bytes, - ~in_reference.data_offset_complement, - u1->reference.type_enum, - &u1->reference.type_union, - suballocation, - 0 - ); - } else if (t0 == SVF_META_Type_sequence) { - if (t1 != SVF_META_Type_sequence) { - ctx->internal_error = true; - return; - } + uint32_t unsafe_size_src = SVFRT_conversion_get_type_size( + ctx->unsafe_structs_src, + unsafe_type_union_src->reference.type_enum, + &unsafe_type_union_src->reference.type_union + ); + if (unsafe_size_src == 0) { + ctx->error_code = SVFRT_code_conversion__bad_type; + return; + } - if ((uint64_t) offset_from + sizeof(SVFRT_Sequence) > range_from.count) { - ctx->data_error = true; - return; - } + uint32_t size_dst = SVFRT_conversion_get_type_size( + ctx->structs_dst, + type_union_dst->reference.type_enum, + &type_union_dst->reference.type_union + ); + if (size_dst == 0) { + ctx->error_code = SVFRT_code_conversion_internal__bad_type; + return; + } - SVFRT_Sequence in_sequence = *((SVFRT_Sequence *) (range_from.pointer + offset_from)); - - uint32_t inner_input_size = SVFRT_get_type_size( - ctx, - ctx->structs0, - u0->sequence.element_type_enum, - &u0->sequence.element_type_union - ); - - uint32_t inner_output_size = SVFRT_get_type_size( - ctx, - ctx->structs1, - u1->sequence.element_type_enum, - &u1->sequence.element_type_union - ); - - SVFRT_Bytes suballocation = {0}; - SVFRT_conversion_suballocate( - &suballocation, - ctx, - // TODO @proper-alignment. - inner_output_size * in_sequence.count - ); - - if (range_to.count < offset_to + sizeof(SVFRT_Sequence)) { - ctx->internal_error = true; - return; - } + SVFRT_Phase2_TraverseConcreteType phase2_inner = {0}; + SVFRT_conversion_tally( + ctx, + unsafe_size_src, + size_dst, + 1, + phase2 ? &phase2_inner.data_range_dst : NULL + ); + if (ctx->error_code) { + return; + } - SVFRT_Sequence *out_sequence = (SVFRT_Sequence *) (range_to.pointer + offset_to); - out_sequence->data_offset_complement = ~(suballocation.pointer - ctx->allocation.pointer); - out_sequence->count = in_sequence.count; + // For Phase 2: + // `phase2_inner.data_range_dst` was filled by `SVFRT_conversion_tally`. + // `phase2_inner.data_offset_dst` is zero by default - for (uint32_t i = 0; i < in_sequence.count; i++) { - SVFRT_copy_concrete( + SVFRT_conversion_traverse_concrete_type( ctx, - recursion_depth + 1, - u0->sequence.element_type_enum, - &u0->sequence.element_type_union, + recursion_depth, ctx->data_bytes, - ~in_sequence.data_offset_complement + i * inner_input_size, - u1->sequence.element_type_enum, - &u1->sequence.element_type_union, - suballocation, - i * inner_output_size + ~unsafe_representation_src.data_offset_complement, + unsafe_type_union_src->reference.type_enum, + &unsafe_type_union_src->reference.type_union, + type_union_dst->reference.type_enum, + &type_union_dst->reference.type_union, + phase2 ? &phase2_inner : NULL ); + return; } - } else { - ctx->internal_error = true; - return; - } -} - -void SVFRT_copy_struct( - SVFRT_ConversionContext *ctx, - size_t recursion_depth, - size_t s0_index, - size_t s1_index, - SVFRT_Bytes input_bytes, - SVFRT_Bytes output_bytes -) { - if (s0_index >= ctx->structs0.count) { - ctx->internal_error = true; - return; - } - - if (s1_index >= ctx->structs1.count) { - ctx->internal_error = true; - return; - } - - SVF_META_StructDefinition *s0 = ctx->structs0.pointer + s0_index; - SVF_META_StructDefinition *s1 = ctx->structs1.pointer + s1_index; + case SVF_META_Type_sequence: { + // Sanity check. + if (type_enum_dst != SVF_META_Type_sequence) { + ctx->error_code = SVFRT_code_conversion__schema_type_enum_mismatch; + return; + } - SVFRT_RangeFieldDefinition fields0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r0, s0->fields, SVF_META_FieldDefinition); - SVFRT_RangeFieldDefinition fields1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(ctx->info->r1, s1->fields, SVF_META_FieldDefinition); - if (!fields0.pointer || !fields0.pointer) { - ctx->internal_error = true; - return; - } + // Prevent addition overflow by casting operands to `uint64_t` first. + if ((uint64_t) unsafe_data_offset_src + (uint64_t) sizeof(SVFRT_Sequence) > (uint64_t) data_range_src.count) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } + // TODO @proper-alignment: resulting pointer might be misaligned, watch out. + SVFRT_Sequence unsafe_representation_src = *((SVFRT_Sequence *) (data_range_src.pointer + unsafe_data_offset_src)); - // Go over the intersection between s0 and s1. + // Allow invalid sequences, but only if the representation is zero. + if (unsafe_representation_src.data_offset_complement == 0 && unsafe_representation_src.count == 0) { + // For Phase 1, nothing needs to be done here. + // For Phase 2, the dst-representation is zero already, which is fine. + return; + } - // TODO @performance: N^2. - for (uint32_t i = 0; i < fields0.count; i++) { - SVF_META_FieldDefinition *field0 = fields0.pointer + i; + uint32_t unsafe_size_src = SVFRT_conversion_get_type_size( + ctx->unsafe_structs_src, + unsafe_type_union_src->reference.type_enum, + &unsafe_type_union_src->reference.type_union + ); + if (unsafe_size_src == 0) { + ctx->error_code = SVFRT_code_conversion__bad_type; + return; + } - for (uint32_t j = 0; j < fields1.count; j++) { - SVF_META_FieldDefinition *field1 = fields1.pointer + j; - if (field0->name_hash != field1->name_hash) { - continue; + uint32_t size_dst = SVFRT_conversion_get_type_size( + ctx->structs_dst, + type_union_dst->reference.type_enum, + &type_union_dst->reference.type_union + ); + if (size_dst == 0) { + ctx->error_code = SVFRT_code_conversion_internal__bad_type; + return; } - SVFRT_copy_type( + SVFRT_Phase2_TraverseConcreteType phase2_inner = {0}; + SVFRT_conversion_tally( ctx, - recursion_depth, - field0->type_enum, - &field0->type_union, - input_bytes, - field0->offset, - field1->type_enum, - &field1->type_union, - output_bytes, - field1->offset + unsafe_size_src, + size_dst, + unsafe_representation_src.count, + phase2 ? &phase2_inner.data_range_dst : NULL ); + if (ctx->error_code) { + return; + } - break; + for (uint32_t i = 0; i < unsafe_representation_src.count; i++) { + // Prevent multiply-add overflow by casting operands to `uint64_t` first. It + // works, because `UINT64_MAX == UINT32_MAX * UINT32_MAX + UINT32_MAX + UINT32_MAX`. + // + // Strictly speaking, it does not seem necessary to catch this, because + // offset checks and aliasing checks will do their job anyway, but it's + // just nicer to catch obvious problems early. + uint64_t unsafe_final_offset_src = ( + (uint64_t) ~unsafe_representation_src.data_offset_complement + + (uint64_t) i * (uint64_t) unsafe_size_src + ); + if (unsafe_final_offset_src > (uint64_t) UINT32_MAX) { + ctx->error_code = SVFRT_code_conversion__data_out_of_bounds; + return; + } + + if (phase2) { + // `phase2_inner.data_range_dst` was filled by `SVFRT_conversion_tally`. + + // No overflow possible: + // + // Let `C = unsafe_representation_src.count`. We know that `i < C`. + // And we know that `size_dst * C` does not overflow, because + // `SVFRT_conversion_tally` has succeeded, see #phase2-reasonable-dst-sum. + phase2_inner.data_offset_dst = size_dst * i; + } + + SVFRT_conversion_traverse_concrete_type( + ctx, + recursion_depth, + ctx->data_bytes, + unsafe_final_offset_src, + unsafe_type_union_src->reference.type_enum, + &unsafe_type_union_src->reference.type_union, + type_union_dst->reference.type_enum, + &type_union_dst->reference.type_union, + phase2 ? &phase2_inner : NULL + ); + + if (ctx->error_code) { + // Exit early on any error. This would include any tally limit checks, + // so it's fine to loop even though the count might be malicious. + return; + } + } + return; + } + default: { + ctx->error_code = SVFRT_code_conversion__bad_schema_type_enum; } } } @@ -1220,105 +1172,178 @@ void SVFRT_copy_struct( void SVFRT_convert_message( SVFRT_ConversionResult *out_result, SVFRT_ConversionInfo *info, - SVFRT_Bytes entry_input_bytes, + SVFRT_Bytes entry_bytes_src, // TODO: this does not need to be a parameter? SVFRT_Bytes data_bytes, - size_t max_recursion_depth, + uint32_t max_recursion_depth, + uint32_t total_data_size_limit, SVFRT_AllocatorFn *allocator_fn, void *allocator_ptr ) { - SVFRT_RangeStructDefinition structs0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(info->r0, info->s0->structs, SVF_META_StructDefinition); - SVFRT_RangeStructDefinition structs1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(info->r1, info->s1->structs, SVF_META_StructDefinition); - SVFRT_RangeChoiceDefinition choices0 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(info->r0, info->s0->choices, SVF_META_ChoiceDefinition); - SVFRT_RangeChoiceDefinition choices1 = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(info->r1, info->s1->choices, SVF_META_ChoiceDefinition); - - if (0 - || !structs0.pointer - || !structs1.pointer - || !choices0.pointer - || !choices1.pointer - ) { + SVFRT_RangeStructDefinition unsafe_structs_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + info->unsafe_schema_src, + info->unsafe_definition_src->structs, + SVF_META_StructDefinition + ); + if (!unsafe_structs_src.pointer && unsafe_structs_src.count) { + out_result->error_code = SVFRT_code_conversion__bad_schema_structs; + return; + }; + + SVFRT_RangeStructDefinition structs_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + info->schema_dst, + info->definition_dst->structs, + SVF_META_StructDefinition + ); + if (!structs_dst.pointer && structs_dst.count) { + out_result->error_code = SVFRT_code_conversion_internal__bad_schema_structs; + return; + } + + SVFRT_RangeChoiceDefinition unsafe_choices_src = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + info->unsafe_schema_src, + info->unsafe_definition_src->choices, + SVF_META_ChoiceDefinition + ); + if (!unsafe_choices_src.pointer && unsafe_choices_src.count) { + out_result->error_code = SVFRT_code_conversion__bad_schema_choices; + return; + }; + + SVFRT_RangeChoiceDefinition choices_dst = SVFRT_INTERNAL_RANGE_FROM_SEQUENCE( + info->schema_dst, + info->definition_dst->choices, + SVF_META_ChoiceDefinition + ); + if (!choices_dst.pointer && choices_dst.count) { + out_result->error_code = SVFRT_code_conversion_internal__bad_schema_choices; + return; + } + + if (info->struct_index_src >= unsafe_structs_src.count) { + out_result->error_code = SVFRT_code_conversion__bad_schema_struct_index; + return; + } + uint32_t unsafe_entry_struct_size_src = unsafe_structs_src.pointer[info->struct_index_src].size; + + if (info->struct_index_dst >= structs_dst.count) { + out_result->error_code = SVFRT_code_conversion_internal__bad_schema_struct_index; return; } + uint32_t entry_struct_size_dst = structs_dst.pointer[info->struct_index_dst].size; SVFRT_ConversionContext ctx_val = {0}; - ctx_val.data_bytes = data_bytes; ctx_val.info = info; + ctx_val.data_bytes = data_bytes; ctx_val.max_recursion_depth = max_recursion_depth; - ctx_val.structs0 = structs0; - ctx_val.structs1 = structs1; - ctx_val.choices0 = choices0; - ctx_val.choices1 = choices1; + ctx_val.unsafe_structs_src = unsafe_structs_src; + ctx_val.structs_dst = structs_dst; + ctx_val.unsafe_choices_src = unsafe_choices_src; + ctx_val.choices_dst = choices_dst; + ctx_val.total_data_size_limit_dst = total_data_size_limit; SVFRT_ConversionContext *ctx = &ctx_val; // // Phase 1: calculate size needed for the allocation. // - SVFRT_bump_struct_contents( + uint32_t recursion_depth = 0; + SVFRT_conversion_traverse_struct( ctx, - info->struct_index0, - info->struct_index1, - entry_input_bytes + recursion_depth, + info->struct_index_src, + info->struct_index_dst, + entry_bytes_src, + NULL ); - SVFRT_bump_structs( + if (ctx->error_code) { + out_result->error_code = ctx->error_code; + return; + } + + SVFRT_conversion_tally( ctx, - info->struct_index1, - 1 // Single entry struct. + unsafe_entry_struct_size_src, + entry_struct_size_dst, + 1, + NULL ); - - if (ctx->internal_error || ctx->data_error) { + if (ctx->error_code) { + out_result->error_code = ctx->error_code; return; } - void *allocated_pointer = allocator_fn(allocator_ptr, ctx->allocation_needed); + void *allocated_pointer = allocator_fn(allocator_ptr, ctx->tally_dst); if (!allocated_pointer) { + out_result->error_code = SVFRT_code_conversion__allocation_failed; return; } + ctx->allocation.pointer = allocated_pointer; - ctx->allocation.count = ctx->allocation_needed; + ctx->allocation.count = ctx->tally_dst; + out_result->output_bytes = ctx->allocation; // Zero out the memory. SVFRT_MEMSET(ctx->allocation.pointer, 0, ctx->allocation.count); + // Reset tallies between the phases. + ctx->tally_src = 0; + ctx->tally_dst = 0; + recursion_depth = 0; + // // Phase 2: actually copy the data. // - SVF_META_StructDefinition *s1 = ctx->structs1.pointer + ctx->info->struct_index1; + SVF_META_StructDefinition *definition_dst = ctx->structs_dst.pointer + ctx->info->struct_index_dst; // Entry is special, as it always resides at the end of the data range. // // TODO: @proper-alignment. - SVFRT_Bytes entry_output_bytes = { - /*.pointer =*/ ctx->allocation.pointer + ctx->allocation.count - s1->size, - /*.size =*/ s1->size, + SVFRT_Bytes entry_bytes_dst = { + /*.pointer =*/ ctx->allocation.pointer + ctx->allocation.count - definition_dst->size, + /*.size =*/ definition_dst->size, }; - SVFRT_copy_struct( + SVFRT_Phase2_TraverseStruct phase2 = { + /*.struct_bytes_dst =*/ entry_bytes_dst + }; + SVFRT_conversion_traverse_struct( ctx, - 0, - info->struct_index0, - info->struct_index1, - entry_input_bytes, - entry_output_bytes + recursion_depth, + info->struct_index_src, + info->struct_index_dst, + entry_bytes_src, + &phase2 ); + if (ctx->error_code) { + out_result->error_code = ctx->error_code; + return; + } - // The above call will have "allocated" memory for everything preceding the - // entry, but also copied fields of the entry struct without marking that - // struct's memory as allocated. This is the only thing that remains. - // - // TODO: @proper-alignment. - ctx->allocated_already += s1->size; - - if (ctx->allocated_already != ctx->allocation.count) { - ctx->internal_error = true; + SVFRT_Bytes entry_bytes_dst_alternate = {0}; + SVFRT_conversion_tally( + ctx, + unsafe_entry_struct_size_src, + entry_struct_size_dst, + 1, + &entry_bytes_dst_alternate + ); + if (ctx->error_code) { + out_result->error_code = ctx->error_code; return; } - if (ctx->internal_error || ctx->data_error) { + // Sanity checks. + if ( + (entry_bytes_dst.pointer != entry_bytes_dst_alternate.pointer) || + (entry_bytes_dst.count != entry_bytes_dst_alternate.count) || + (ctx->tally_dst != ctx->allocation.count) + ) { + // Something went terribly wrong. + // TODO: should this kind of "fatal" error be separate? + out_result->error_code = SVFRT_code_conversion_internal__suballocation_mismatch; return; } out_result->success = true; - out_result->output_bytes = ctx->allocation; } diff --git a/svf_runtime/src/svf_internal.c b/svf_runtime/src/svf_internal.c index 4b07e2e..3a5e6d9 100644 --- a/svf_runtime/src/svf_internal.c +++ b/svf_runtime/src/svf_internal.c @@ -38,6 +38,7 @@ void *SVFRT_internal_from_sequence( if (end_offset > (uint64_t) bytes.count) { return NULL; } + return (void *) (bytes.pointer + data_offset); } diff --git a/svf_runtime/src/svf_internal.h b/svf_runtime/src/svf_internal.h index f600238..f38f99b 100644 --- a/svf_runtime/src/svf_internal.h +++ b/svf_runtime/src/svf_internal.h @@ -43,34 +43,38 @@ void *SVFRT_internal_from_sequence( ); #define SVFRT_INTERNAL_POINTER_FROM_REFERENCE(bytes, reference, type) ( \ - SVFRT_internal_from_reference((bytes), (reference), sizeof(type)), \ + (type *) SVFRT_internal_from_reference((bytes), (reference), sizeof(type)), \ ) +// Caveat: may return { NULL, count }. +// TODO: use macro tricks to force to return { NULL, 0 } in this case? #define SVFRT_INTERNAL_RANGE_FROM_SEQUENCE(bytes, sequence, type) { \ /*.pointer = */ (type *) SVFRT_internal_from_sequence((bytes), (sequence), sizeof(type)), \ /*.count = */ (sequence).count \ } typedef struct SVFRT_ConversionResult { - SVFRT_Bytes output_bytes; + SVFRT_Bytes output_bytes; // Note: may refer to allocated memory even on failure. bool success; + SVFRT_ErrorCode error_code; } SVFRT_ConversionResult; typedef struct SVFRT_ConversionInfo { - SVF_META_Schema *s0; - SVF_META_Schema *s1; - SVFRT_Bytes r0; - SVFRT_Bytes r1; - uint32_t struct_index0; - uint32_t struct_index1; + SVF_META_Schema *unsafe_definition_src; + SVF_META_Schema *definition_dst; + SVFRT_Bytes unsafe_schema_src; + SVFRT_Bytes schema_dst; + uint32_t struct_index_src; // Note: no `unsafe` prefix here. + uint32_t struct_index_dst; } SVFRT_ConversionInfo; void SVFRT_convert_message( SVFRT_ConversionResult *out_result, SVFRT_ConversionInfo *info, - SVFRT_Bytes entry_input_bytes, + SVFRT_Bytes entry_bytes_src, SVFRT_Bytes data_bytes, - size_t max_recursion_depth, + uint32_t max_recursion_depth, + uint32_t total_data_size_limit, SVFRT_AllocatorFn *allocator_fn, void *allocator_ptr ); diff --git a/svf_runtime/src/svf_meta.h b/svf_runtime/src/svf_meta.h index 5e0f382..921faf8 100644 --- a/svf_runtime/src/svf_meta.h +++ b/svf_runtime/src/svf_meta.h @@ -30,12 +30,13 @@ typedef struct SVFRT_Sequence { #pragma pack(push, 1) -#define SVF_META_min_read_scratch_memory_size 88 -#define SVF_META_binary_size 2125 -extern uint8_t const SVF_META_binary_array[]; +#define SVF_META_min_read_scratch_memory_size 187 +#define SVF_META_compatibility_work_base 95 +#define SVF_META_schema_binary_size 2125 +extern uint8_t const SVF_META_schema_binary_array[]; #if defined(SVF_INCLUDE_BINARY_SCHEMA) || defined(SVF_IMPLEMENTATION) -uint8_t const SVF_META_binary_array[] = { +uint8_t const SVF_META_schema_binary_array[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/svf_runtime/src/svf_meta.hpp b/svf_runtime/src/svf_meta.hpp index 2eb299f..a1dae7f 100644 --- a/svf_runtime/src/svf_meta.hpp +++ b/svf_runtime/src/svf_meta.hpp @@ -467,9 +467,10 @@ struct SchemaDescription { template struct PerType; - static constexpr U8 *schema_array = (U8 *) binary::array; - static constexpr size_t schema_size = binary::size; - static constexpr U64 min_read_scratch_memory_size = 88; + static constexpr U8 *schema_binary_array = (U8 *) binary::array; + static constexpr size_t schema_binary_size = binary::size; + static constexpr U64 min_read_scratch_memory_size = 187; + static constexpr U32 compatibility_work_base = 95; }; // C++ trickery: SchemaDescription::PerType. diff --git a/svf_runtime/src/svf_runtime.c b/svf_runtime/src/svf_runtime.c index 14ccb51..8493a70 100644 --- a/svf_runtime/src/svf_runtime.c +++ b/svf_runtime/src/svf_runtime.c @@ -1,5 +1,3 @@ -#include - #ifndef SVFRT_SINGLE_FILE #include "svf_internal.h" #include "svf_runtime.h" @@ -11,38 +9,41 @@ extern "C" { #endif static inline -size_t align_down(size_t value, size_t alignment) { +uint64_t SVFRT_align_down(uint64_t value, uint64_t alignment) { return value - value % alignment; } static inline -size_t align_up(size_t value, size_t alignment) { - // Sanity check to prevent overflow. - assert(value + alignment > value); - - return value + alignment - 1 - (value - 1) % alignment; +uint64_t SVFRT_align_up(uint64_t value, uint64_t alignment) { + return SVFRT_align_down(value - 1, alignment) + alignment; } -#define SVFRT_MAX_CONVERSION_RECURSION_DEPTH 256 // Some hopefully sane default. - +// TODO: error reporting (see all the `printf` placeholders). Probably should +// just return an enum? void SVFRT_read_message_implementation( - SVFRT_ReadMessageResult *result, + SVFRT_ReadMessageResult *out_result, SVFRT_Bytes message_bytes, uint64_t entry_name_hash, uint32_t entry_index, SVFRT_Bytes expected_schema, + uint32_t work_max, + uint32_t max_recursion_depth, + uint32_t max_output_size, SVFRT_Bytes scratch_memory, SVFRT_CompatibilityLevel required_level, SVFRT_AllocatorFn *allocator_fn, void *allocator_ptr ) { + out_result->entry = NULL; + out_result->allocation = NULL; + out_result->compatibility_level = SVFRT_compatibility_none; + if (message_bytes.count < sizeof(SVFRT_MessageHeader)) { // printf("Too small for an SVF header.\n"); return; } - // Maybe not needed for all cases? - if (((size_t) message_bytes.pointer) % SVFRT_MESSAGE_PART_ALIGNMENT != 0) { + if (((uintptr_t) message_bytes.pointer) % SVFRT_MESSAGE_PART_ALIGNMENT != 0) { // printf("Header not aligned.\n"); return; } @@ -57,7 +58,8 @@ void SVFRT_read_message_implementation( return; } - // For now, versions must match exactly. + // For now, versions must match exactly. Version 0 is for development only and + // does not come with any guarantees. if (header->version != 0) { // printf("Does not match SVF version 0.\n"); return; @@ -65,13 +67,7 @@ void SVFRT_read_message_implementation( // Make sure the declared entry is the same as we expect. if (header->entry_name_hash != entry_name_hash) { - // printf("Does not match SVF version 0.\n"); - return; - } - - // Make sure the schema is in-bounds. - if (sizeof(SVFRT_MessageHeader) + (size_t) (header->schema_length) > message_bytes.count) { - // printf("Schema is not in-bounds.\n"); + // printf("Entry name hash does not match.\n"); return; } @@ -81,28 +77,28 @@ void SVFRT_read_message_implementation( return; } - // We now have a valid schema range. - SVFRT_Bytes schema_range = { - /*.pointer =*/ message_bytes.pointer + sizeof(SVFRT_MessageHeader), - /*.count =*/ header->schema_length, - }; - - uint8_t *data_pointer = (uint8_t *) align_up( - (size_t) (schema_range.pointer + schema_range.count), + // Prevent addition overflow by casting operands to `uint64_t` first. + uint64_t schema_padded_end_offset = SVFRT_align_up( + (uint64_t) sizeof(SVFRT_MessageHeader) + (uint64_t) (header->schema_length), SVFRT_MESSAGE_PART_ALIGNMENT ); - if (data_pointer >= message_bytes.pointer + message_bytes.count) { - // printf("No data.\n"); + // Make sure the schema (and additional padding after it) is in-bounds. + // This also implies it is less than `UINT32_MAX`. + if (schema_padded_end_offset > (uint64_t) message_bytes.count) { + // printf("Schema is not in-bounds.\n"); return; } - // We now have a valid data range. + // We now have a valid schema and data ranges. The data range is implicit, + // from the padded end of the schema, to the end of the message. + SVFRT_Bytes schema_range = { + /*.pointer =*/ message_bytes.pointer + sizeof(SVFRT_MessageHeader), + /*.count =*/ header->schema_length, + }; SVFRT_Bytes data_range = { - /*.pointer =*/ data_pointer, - /*.count =*/ (size_t) ( - (message_bytes.pointer + message_bytes.count) - data_pointer - ), + /*.pointer =*/ message_bytes.pointer + schema_padded_end_offset, + /*.count =*/ message_bytes.count - schema_padded_end_offset, }; SVFRT_CompatibilityResult check_result = {0}; @@ -113,10 +109,11 @@ void SVFRT_read_message_implementation( expected_schema, entry_name_hash, required_level, - SVFRT_compatibility_exact + SVFRT_compatibility_exact, + work_max ); - if (check_result.level == SVFRT_compatibility_none) { + if (check_result.level == 0 || check_result.error_code != 0) { // printf("Compatibility error.\n"); return; } @@ -132,28 +129,34 @@ void SVFRT_read_message_implementation( SVFRT_ConversionResult conversion_result = {0}; - SVFRT_Bytes r0 = schema_range; - SVFRT_Bytes r1 = expected_schema; + SVFRT_Bytes unsafe_schema_src = schema_range; + SVFRT_Bytes schema_dst = expected_schema; // TODO @proper-alignment. - SVF_META_Schema *s0 = (SVF_META_Schema *) (r0.pointer + r0.count - sizeof(SVF_META_Schema)); - SVF_META_Schema *s1 = (SVF_META_Schema *) (r1.pointer + r1.count - sizeof(SVF_META_Schema)); + SVF_META_Schema *unsafe_definition_src = (SVF_META_Schema *) (unsafe_schema_src.pointer + unsafe_schema_src.count - sizeof(SVF_META_Schema)); + SVF_META_Schema *definition_dst = (SVF_META_Schema *) (schema_dst.pointer + schema_dst.count - sizeof(SVF_META_Schema)); SVFRT_ConversionInfo conversion_info = { - .r0 = r0, - .r1 = r1, - .s0 = s0, - .s1 = s1, - .struct_index0 = check_result.entry_struct_index0, - .struct_index1 = check_result.entry_struct_index1, + .unsafe_schema_src = unsafe_schema_src, + .schema_dst = schema_dst, + .unsafe_definition_src = unsafe_definition_src, + .definition_dst = definition_dst, + .struct_index_src = check_result.entry_struct_index_src, + .struct_index_dst = check_result.entry_struct_index_dst, }; - uint32_t entry_size = check_result.entry_size0; + uint32_t unsafe_entry_size = check_result.unsafe_entry_size_src; + + if (unsafe_entry_size > data_range.count) { + // printf("Conversion was not successful, because..."); + return; + } + // Now, `unsafe_entry_size` can be considered safe. // TODO @proper-alignment. SVFRT_Bytes entry_input_bytes = { - /*.pointer =*/ data_range.pointer + data_range.count - entry_size, - /*.count =*/ entry_size, + /*.pointer =*/ data_range.pointer + data_range.count - unsafe_entry_size, + /*.count =*/ unsafe_entry_size, }; SVFRT_convert_message( @@ -161,38 +164,46 @@ void SVFRT_read_message_implementation( &conversion_info, entry_input_bytes, data_range, - SVFRT_MAX_CONVERSION_RECURSION_DEPTH, + max_recursion_depth, + max_output_size, allocator_fn, allocator_ptr ); if (!conversion_result.success) { - // printf("Conversion was not successful."); + out_result->allocation = conversion_result.output_bytes.pointer; + // out_result->error_code = conversion_result->error_code; return; } final_data_range = conversion_result.output_bytes; } - uint32_t entry_size = check_result.struct_strides.pointer[entry_index]; - size_t entry_alignment = 1; // TODO @proper-alignment. + // For at least binary compatibility, this will be the size of the entry in + // the "from" schema. However, for logical compatibility, this will be the size + // of the entry in the "to" schema. + // + // This is non-obvious. See #logical-compatibility-stride-quirk. + uint32_t entry_size = check_result.quirky_struct_strides_dst.pointer[entry_index]; if (final_data_range.count < entry_size) { // printf("Data is too small.\n"); return; } - // TODO @proper-alignment. - result->compatibility_level = check_result.level; - if (check_result.level == SVFRT_compatibility_logical) { - result->allocation = final_data_range.pointer; - } - result->entry = (void *) align_down( - (size_t) final_data_range.pointer + final_data_range.count - entry_size, + uint32_t entry_alignment = 1; // TODO @proper-alignment. + uint32_t final_entry_offset = (uint32_t) SVFRT_align_down( + final_data_range.count - entry_size, entry_alignment ); - result->context.data_range = final_data_range; - result->context.struct_strides = check_result.struct_strides; + + out_result->entry = (void *) (final_data_range.pointer + final_entry_offset); + if (check_result.level == SVFRT_compatibility_logical) { + out_result->allocation = final_data_range.pointer; + } + out_result->compatibility_level = check_result.level; + out_result->context.data_range = final_data_range; + out_result->context.struct_strides = check_result.quirky_struct_strides_dst; } void SVFRT_write_message_implementation( @@ -215,27 +226,26 @@ void SVFRT_write_message_implementation( }; bool error = false; - uint32_t bytes_written = writer_fn(writer_ptr, header_bytes); - if (bytes_written != header_bytes.count) { + + uint32_t written_header = writer_fn(writer_ptr, header_bytes); + if (written_header != header_bytes.count) { + error = true; + } + + uint32_t written_schema = writer_fn(writer_ptr, schema_bytes); + if (written_schema != schema_bytes.count) { error = true; - } else { - uint32_t written = writer_fn(writer_ptr, schema_bytes); - if (written != schema_bytes.count) { - error = true; - } else { - bytes_written += written; - } } - uint8_t zeroes[SVFRT_MESSAGE_PART_ALIGNMENT] = {0}; - size_t misaligned = bytes_written % SVFRT_MESSAGE_PART_ALIGNMENT; - SVFRT_Bytes padding_range = { - /*.pointer =*/ (uint8_t *) zeroes, + uint8_t zeros[SVFRT_MESSAGE_PART_ALIGNMENT] = {0}; + uint32_t misaligned = written_schema % SVFRT_MESSAGE_PART_ALIGNMENT; + SVFRT_Bytes padding_bytes = { + /*.pointer =*/ (uint8_t *) zeros, /*.count =*/ SVFRT_MESSAGE_PART_ALIGNMENT - misaligned, }; if (misaligned != 0) { - uint32_t written = writer_fn(writer_ptr, padding_range); - if (written != padding_range.count) { + uint32_t written_padding = writer_fn(writer_ptr, padding_bytes); + if (written_padding != padding_bytes.count) { error = true; } } @@ -244,6 +254,7 @@ void SVFRT_write_message_implementation( result->writer_fn = writer_fn; result->data_bytes_written = 0; result->error = error; + result->finished = false; } #ifdef __cplusplus diff --git a/svf_runtime/src/svf_runtime.h b/svf_runtime/src/svf_runtime.h index 1afa6a1..9aa6571 100644 --- a/svf_runtime/src/svf_runtime.h +++ b/svf_runtime/src/svf_runtime.h @@ -29,6 +29,8 @@ extern "C" { #error "This library only supports 8-bit bytes. What on earth are you compiling for?!" #endif +// TOOD @support: check size_t and uintptr_t (>= 32 bit). + #ifndef SVF_COMMON_C_TYPES_INCLUDED #define SVF_COMMON_C_TYPES_INCLUDED #pragma pack(push, 1) @@ -55,13 +57,16 @@ typedef struct SVFRT_RangeU32 { uint32_t count; } SVFRT_RangeU32; +#pragma pack(push, 1) typedef struct SVFRT_MessageHeader { uint8_t magic[3]; uint8_t version; uint32_t schema_length; uint64_t entry_name_hash; } SVFRT_MessageHeader; +#pragma pack(pop) +// Alignment for message parts: header, schema, data. #define SVFRT_MESSAGE_PART_ALIGNMENT 8 // If tags ever become capable of being > 1 byte wide, this macro needs to be @@ -69,6 +74,16 @@ typedef struct SVFRT_MessageHeader { // needs to reference this macro. #define SVFRT_TAG_SIZE 1 +// By default, fail, if checking an untrusted schema for compatibility is more +// than `N` (this factor) times longer than the base scenario, which is to check +// the schema with itself. +// +// TODO: arbitrary, but hopefully sane choice. +#define SVFRT_DEFAULT_COMPATIBILITY_TRUST_FACTOR 8 + +#define SVFRT_DEFAULT_MAX_RECURSION_DEPTH 256 +#define SVFRT_NO_SIZE_LIMIT UINT32_MAX + // Must allocate with alignment of at least `SVFRT_MESSAGE_PART_ALIGNMENT`. typedef void *(SVFRT_AllocatorFn)(void *allocate_ptr, size_t size); @@ -88,42 +103,123 @@ typedef struct SVFRT_ReadMessageResult { void *entry; void *allocation; SVFRT_CompatibilityLevel compatibility_level; - SVFRT_ReadContext context; + SVFRT_ReadContext context; // Only valid, if `entry` is valid. } SVFRT_ReadMessageResult; +// Read the message. +// - `scratch_memory` must have a certain size dependent on the read schema. +// See `min_read_scratch_memory_size` in the header generated from the read schema. void SVFRT_read_message_implementation( SVFRT_ReadMessageResult *result, SVFRT_Bytes message_bytes, uint64_t entry_name_hash, uint32_t entry_index, SVFRT_Bytes expected_schema, + uint32_t work_max, // TODO: three `uint32_t` limits in a row, will be easy to mix them up... + uint32_t max_recursion_depth, + uint32_t max_output_size, SVFRT_Bytes scratch_memory, SVFRT_CompatibilityLevel required_level, SVFRT_AllocatorFn *allocator_fn, // Only for SVFRT_compatibility_logical. void *allocator_ptr // Only for SVFRT_compatibility_logical. ); +typedef uint32_t SVFRT_ErrorCode; +// TODO: codes are not final, tidy them up (after all or most of them are listed). + +#define SVFRT_code_compatibility__work_max_exceeded 0x00010001 +#define SVFRT_code_compatibility__required_level_is_none 0x00010002 +#define SVFRT_code_compatibility__invalid_sufficient_level 0x00010003 +#define SVFRT_code_compatibility__not_enough_scratch_memory 0x00010004 +#define SVFRT_code_compatibility__entry_name_hash_not_found 0x00010005 +#define SVFRT_code_compatibility__struct_index_mismatch 0x00010006 +#define SVFRT_code_compatibility__invalid_structs 0x00010007 +#define SVFRT_code_compatibility__invalid_choices 0x00010008 +#define SVFRT_code_compatibility__invalid_fields 0x00010009 +#define SVFRT_code_compatibility__invalid_options 0x0001000A +#define SVFRT_code_compatibility__field_is_missing 0x0001000B +#define SVFRT_code_compatibility__field_offset_mismatch 0x0001000C +#define SVFRT_code_compatibility__struct_size_mismatch 0x0001000D +#define SVFRT_code_compatibility__choice_index_mismatch 0x0001000E +#define SVFRT_code_compatibility__option_is_missing 0x0001000F +#define SVFRT_code_compatibility__option_tag_mismatch 0x00010010 +#define SVFRT_code_compatibility__type_mismatch 0x00010011 +#define SVFRT_code_compatibility__concrete_type_mismatch 0x00010012 +#define SVFRT_code_compatibility__invalid_struct_index 0x00010013 +#define SVFRT_code_compatibility__invalid_choice_index 0x00010014 + +#define SVFRT_code_compatibility_internal__invalid_type_enum 0x00020001 +#define SVFRT_code_compatibility_internal__invalid_structs 0x00020007 +#define SVFRT_code_compatibility_internal__invalid_choices 0x00020008 +#define SVFRT_code_compatibility_internal__invalid_fields 0x00020009 +#define SVFRT_code_compatibility_internal__invalid_options 0x0002000A +#define SVFRT_code_compatibility_internal__queue_overflow 0x0002000B +#define SVFRT_code_compatibility_internal__queue_unhandled 0x0002000C + +#define SVFRT_code_conversion__allocation_failed 0x00030001 +#define SVFRT_code_conversion__total_data_size_limit_exceeded 0x00030002 +#define SVFRT_code_conversion__bad_schema_structs 0x00030003 +#define SVFRT_code_conversion__bad_schema_choices 0x00030004 +#define SVFRT_code_conversion__bad_schema_fields 0x00030005 +#define SVFRT_code_conversion__bad_schema_options 0x00030006 +#define SVFRT_code_conversion__bad_schema_struct_index 0x00030007 +#define SVFRT_code_conversion__bad_schema_field_index 0x00030008 +#define SVFRT_code_conversion__bad_schema_choice_index 0x00030009 +#define SVFRT_code_conversion__bad_schema_option_index 0x0003000A +#define SVFRT_code_conversion__bad_schema_type_enum 0x0003000B +#define SVFRT_code_conversion__data_out_of_bounds 0x0003000C +#define SVFRT_code_conversion__bad_choice_tag 0x0003000D +#define SVFRT_code_conversion__schema_type_enum_mismatch 0x0003000E +#define SVFRT_code_conversion__schema_concrete_type_enum_mismatch 0x0003000F +#define SVFRT_code_conversion__max_recursion_depth_exceeded 0x00030010 +#define SVFRT_code_conversion__bad_type 0x00030011 +#define SVFRT_code_conversion__data_aliasing_detected 0x00030012 + +#define SVFRT_code_conversion_internal__suballocation_mismatch 0x00040001 +#define SVFRT_code_conversion_internal__suballocation_failed 0x00040002 +#define SVFRT_code_conversion_internal__bad_schema_structs 0x00040003 +#define SVFRT_code_conversion_internal__bad_schema_choices 0x00040004 +#define SVFRT_code_conversion_internal__bad_schema_fields 0x00040005 +#define SVFRT_code_conversion_internal__bad_schema_options 0x00040006 +#define SVFRT_code_conversion_internal__bad_schema_struct_index 0x00040007 +#define SVFRT_code_conversion_internal__bad_schema_field_index 0x00040008 +#define SVFRT_code_conversion_internal__bad_schema_choice_index 0x00040009 +#define SVFRT_code_conversion_internal__bad_schema_option_index 0x0004000A +#define SVFRT_code_conversion_internal__schema_field_missing 0x0004000B +#define SVFRT_code_conversion_internal__schema_option_missing 0x0004000C +#define SVFRT_code_conversion_internal__schema_impossible_type 0x0004000D +#define SVFRT_code_conversion_internal__schema_incompatible_types 0x0004000E +#define SVFRT_code_conversion_internal__suballocation_out_of_bounds 0x0004000F +#define SVFRT_code_conversion_internal__bad_type 0x00040010 + typedef struct SVFRT_CompatibilityResult { SVFRT_CompatibilityLevel level; - SVFRT_RangeU32 struct_strides; + SVFRT_RangeU32 quirky_struct_strides_dst; // See #logical-compatibility-stride-quirk. + SVFRT_ErrorCode error_code; // See `SVFRT_code_compatibility__*`. // Internal. - uint32_t entry_size0; - uint32_t entry_struct_index0; - uint32_t entry_struct_index1; + uint32_t unsafe_entry_size_src; + uint32_t entry_struct_index_src; + uint32_t entry_struct_index_dst; } SVFRT_CompatibilityResult; -// Check compatibility of two schemas. -// `result` must be zero-filled. -// `scratch_memory` must have a certain size dependent on the schema. +// Check compatibility of two schemas, i.e. can the data written in one schema +// ("src"), be read using another schema ("dst"). +// +// - `result` must be zero-initialized. +// - `scratch_memory` must have a certain size dependent on the read schema. +// See `min_read_scratch_memory_size` in the header generated from the read schema. +// +// TODO: consider, whether this should be internal. void SVFRT_check_compatibility( SVFRT_CompatibilityResult *out_result, SVFRT_Bytes scratch_memory, - SVFRT_Bytes schema_write, - SVFRT_Bytes schema_read, + SVFRT_Bytes unsafe_schema_src, + SVFRT_Bytes schema_dst, uint64_t entry_name_hash, SVFRT_CompatibilityLevel required_level, - SVFRT_CompatibilityLevel sufficient_level + SVFRT_CompatibilityLevel sufficient_level, + uint32_t work_max ); typedef uint32_t (SVFRT_WriterFn)(void *write_pointer, SVFRT_Bytes data); @@ -358,7 +454,7 @@ void const *SVFRT_read_sequence_element( (ctx), \ (writer_ptr), \ (writer_fn), \ - (SVFRT_Bytes) { (void *) schema_name ## _binary_array, schema_name ## _binary_size }, \ + (SVFRT_Bytes) { (void *) schema_name ## _schema_binary_array, schema_name ## _schema_binary_size }, \ entry_name ## _name_hash \ ) @@ -374,13 +470,17 @@ void const *SVFRT_read_sequence_element( #define SVFRT_WRITE_MESSAGE_END(ctx, data_ptr) \ SVFRT_write_message_end((ctx), (void *) (data_ptr), sizeof(*(data_ptr))) -#define SVFRT_READ_MESSAGE(schema_name, entry_name, out_ptr, message_bytes, scratch_memory, required_level, allocator_fn, allocator_ptr) \ +// `*out_result` must be zero-initialized. +#define SVFRT_READ_MESSAGE(schema_name, entry_name, out_result, message_bytes, scratch_memory, required_level, allocator_fn, allocator_ptr) \ SVFRT_read_message_implementation( \ - (out_ptr), \ + (out_result), \ (message_bytes), \ - entry_name ## _name_hash, \ - entry_name ## _struct_index, \ - (SVFRT_Bytes) { (void *) schema_name ## _binary_array, schema_name ## _binary_size }, \ + (entry_name ## _name_hash), \ + (entry_name ## _struct_index), \ + (SVFRT_Bytes) { (void *) schema_name ## _schema_binary_array, schema_name ## _schema_binary_size }, \ + (schema_name ## _compatibility_work_base) * SVFRT_DEFAULT_COMPATIBILITY_TRUST_FACTOR, \ + SVFRT_DEFAULT_MAX_RECURSION_DEPTH, \ + SVFRT_NO_SIZE_LIMIT, \ (scratch_memory), \ (required_level), \ (allocator_fn), \ diff --git a/svf_runtime/src/svf_runtime.hpp b/svf_runtime/src/svf_runtime.hpp index f2b44ba..8594e7d 100644 --- a/svf_runtime/src/svf_runtime.hpp +++ b/svf_runtime/src/svf_runtime.hpp @@ -118,9 +118,12 @@ ReadMessageResult read_message( SchemaDescription::template PerType::name_hash, SchemaDescription::template PerType::index, SVFRT_Bytes { - /*.pointer =*/ (U8 *) SchemaDescription::schema_array, - /*.count =*/ SchemaDescription::schema_size + /*.pointer =*/ (U8 *) SchemaDescription::schema_binary_array, + /*.count =*/ SchemaDescription::schema_binary_size }, + SchemaDescription::compatibility_work_base * SVFRT_DEFAULT_COMPATIBILITY_TRUST_FACTOR, + SVFRT_DEFAULT_MAX_RECURSION_DEPTH, + SVFRT_NO_SIZE_LIMIT, SVFRT_Bytes { /*.pointer =*/ scratch_memory.pointer, /*.count =*/ scratch_memory.count, @@ -196,7 +199,7 @@ WriteContext write_message_start( &ctx_value, writer_ptr, writer_fn, - { SchemaDescription::schema_array, SchemaDescription::schema_size }, + { SchemaDescription::schema_binary_array, SchemaDescription::schema_binary_size }, SchemaDescription::template PerType::name_hash ); return ctx_value; diff --git a/svf_tools/schema/META.txt b/svf_tools/schema/META.txt index 7b25843..2d84866 100644 --- a/svf_tools/schema/META.txt +++ b/svf_tools/schema/META.txt @@ -17,7 +17,7 @@ ChoiceDefinition: struct { OptionDefinition: struct { name_hash: U64; name: U8[]; - index: U8; + index: U8; // TODO: rename to tag. type: Type; }; diff --git a/svf_tools/src/core/common.hpp b/svf_tools/src/core/common.hpp index ca0d7c5..bbe51a9 100644 --- a/svf_tools/src/core/common.hpp +++ b/svf_tools/src/core/common.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "../core.hpp" template @@ -29,10 +30,30 @@ TypePluralityAndSize get_plurality( svf::META::Type_union *in_union ); +static inline +U32 get_compatibility_work_base(Bytes schema_bytes, svf::META::Schema *in_schema) { + // See #compatibility-work. For the "base" we will take the scenario where + // the schema is checked for compatibility with one equivalent to itself. + // + uint32_t result = in_schema->structs.count; // [1] + + auto the_structs = to_range(schema_bytes, in_schema->structs); + for (UInt i = 0; i < the_structs.count; i++) { + auto definition = the_structs.pointer + i; + result += definition->fields.count * definition->fields.count; // [2] + } + + ASSERT((uint64_t) result * SVFRT_DEFAULT_COMPATIBILITY_TRUST_FACTOR < UINT32_MAX); + + return result; +} + static inline UInt get_min_read_scratch_memory_size(svf::META::Schema *in_schema) { + // #scratch-memory-partitions. return ( - in_schema->structs.count * sizeof(U32) * 2 // index and stride - + in_schema->choices.count * sizeof(U32) // index + (sizeof(U32) - 1) // In case of misalignment. + + in_schema->structs.count * sizeof(U32) * 4 // Strides, matches, 2x queue. + + in_schema->choices.count * sizeof(U32) * 3 // Matches, 2x queue. ); } diff --git a/svf_tools/src/core/output_c.cpp b/svf_tools/src/core/output_c.cpp index ca930be..5c2804a 100644 --- a/svf_tools/src/core/output_c.cpp +++ b/svf_tools/src/core/output_c.cpp @@ -367,13 +367,19 @@ typedef struct SVFRT_Sequence { output_cstring(ctx, "#define SVF_"); output_u8_array(ctx, in_schema->name); - output_cstring(ctx, "_binary_size "); + output_cstring(ctx, "_compatibility_work_base "); + output_decimal(ctx, get_compatibility_work_base(schema_bytes ,in_schema)); + output_cstring(ctx, "\n"); + + output_cstring(ctx, "#define SVF_"); + output_u8_array(ctx, in_schema->name); + output_cstring(ctx, "_schema_binary_size "); output_decimal(ctx, schema_bytes.count); output_cstring(ctx, "\n"); output_cstring(ctx, "extern uint8_t const SVF_"); output_u8_array(ctx, in_schema->name); - output_cstring(ctx, "_binary_array[];\n"); + output_cstring(ctx, "_schema_binary_array[];\n"); output_cstring(ctx, "\n"); output_cstring(ctx, "#if defined(SVF_INCLUDE_BINARY_SCHEMA) || defined(SVF_IMPLEMENTATION)\n"); @@ -381,7 +387,7 @@ typedef struct SVFRT_Sequence { output_cstring(ctx, "uint8_t const SVF_"); output_u8_array(ctx, in_schema->name); - output_cstring(ctx, "_binary_array[] = {\n"); + output_cstring(ctx, "_schema_binary_array[] = {\n"); output_raw_bytes(ctx, schema_bytes); output_cstring(ctx, "};\n"); diff --git a/svf_tools/src/core/output_cpp.cpp b/svf_tools/src/core/output_cpp.cpp index 70a527c..eaf888b 100644 --- a/svf_tools/src/core/output_cpp.cpp +++ b/svf_tools/src/core/output_cpp.cpp @@ -424,12 +424,20 @@ Bytes as_code( template struct PerType; - static constexpr U8 *schema_array = (U8 *) binary::array; - static constexpr size_t schema_size = binary::size; - static constexpr U64 min_read_scratch_memory_size = )" - ); + static constexpr U8 *schema_binary_array = (U8 *) binary::array; + static constexpr size_t schema_binary_size = binary::size;)"); + output_cstring(ctx, "\n"); + + output_cstring(ctx, " static constexpr U64 min_read_scratch_memory_size = "); output_decimal(ctx, get_min_read_scratch_memory_size(in_schema)); - output_cstring(ctx, ";\n};\n\n"); + output_cstring(ctx, ";\n"); + + output_cstring(ctx, " static constexpr U32 compatibility_work_base = "); + output_decimal(ctx, get_compatibility_work_base(schema_bytes, in_schema)); + output_cstring(ctx, ";\n"); + + output_cstring(ctx, "};\n"); + output_cstring(ctx, "\n"); output_cstring(ctx, "// C++ trickery: SchemaDescription::PerType.\n"); diff --git a/svf_tools/src/exe/svfc.cpp b/svf_tools/src/exe/svfc.cpp index cbcfa6c..2a7fa6c 100644 --- a/svf_tools/src/exe/svfc.cpp +++ b/svf_tools/src/exe/svfc.cpp @@ -215,9 +215,9 @@ int main(int argc, char *argv[]) { // Align. { - uint8_t zeroes[SVFRT_MESSAGE_PART_ALIGNMENT] = {0}; + uint8_t zeros[SVFRT_MESSAGE_PART_ALIGNMENT] = {0}; size_t misaligned = meta::binary::size % SVFRT_MESSAGE_PART_ALIGNMENT; - auto result = fwrite(zeroes, 1, SVFRT_MESSAGE_PART_ALIGNMENT - misaligned, output_file); + auto result = fwrite(zeros, 1, SVFRT_MESSAGE_PART_ALIGNMENT - misaligned, output_file); if (result != SVFRT_MESSAGE_PART_ALIGNMENT - misaligned) { printf("Error: failed to write output.\n"); return 1; diff --git a/svf_tools/src/test/hello_write.c b/svf_tools/src/test/hello_write.c index d68fe64..3acb484 100644 --- a/svf_tools/src/test/hello_write.c +++ b/svf_tools/src/test/hello_write.c @@ -16,7 +16,7 @@ void example_write(FILE *file) { world.current_year = 2023; world.mechanics_enum = SVF_Hello_Mechanics_quantum; - char const name[] = "The Universe"; + char const name[] = "The Universe"; // TODO: null-termination is not what we want. world.name.utf8 = SVFRT_WRITE_FIXED_SIZE_ARRAY(&ctx, name); SVFRT_WRITE_MESSAGE_END(&ctx, &world); diff --git a/svf_tools/src/test/json_write.cpp b/svf_tools/src/test/json_write.cpp index 3a86d46..eccfce7 100644 --- a/svf_tools/src/test/json_write.cpp +++ b/svf_tools/src/test/json_write.cpp @@ -15,10 +15,12 @@ void example_write(FILE *file) { JSON::Field fields[2] = {}; + // TODO: null-termination is not what we want. fields[0].name = RT::write_fixed_size_string(&ctx, "hello"); fields[0].value_enum = JSON::Value_enum::number; fields[0].value_union.number = 42.0; + // TODO: null-termination is not what we want. fields[1].name = RT::write_fixed_size_string(&ctx, "world"); fields[1].value_enum = JSON::Value_enum::array; for (int i = 0; i < 42; i++) {