SRL_STATIC_INLINE void srl_merge_binary_utf8(pTHX_ srl_merger_t *mrg, ptable_entry_ptr ptable_entry) { int ok; UV length, total_length; strtable_entry_ptr strtable_entry; srl_reader_char_ptr tag_ptr = mrg->ibuf.pos; DEBUG_ASSERT_RDR_SANE(mrg->pibuf); DEBUG_ASSERT_BUF_SANE(&mrg->obuf); mrg->ibuf.pos++; // skip tag in input buffer length = srl_read_varint_uv_length(aTHX_ mrg->pibuf, " while reading BINARY or STR_UTF8"); assert((mrg->ibuf.pos - tag_ptr) > 0); assert((mrg->ibuf.pos - tag_ptr) <= SRL_MAX_VARINT_LENGTH); total_length = length + (mrg->ibuf.pos - tag_ptr); strtable_entry = srl_lookup_string(aTHX_ mrg, tag_ptr, total_length, &ok); if (ok) { // issue COPY tag srl_buf_cat_varint(aTHX_ &mrg->obuf, SRL_HDR_COPY, strtable_entry->offset); mrg->ibuf.pos += length; if (expect_false(ptable_entry)) { // update value in ptable entry // This is needed because if any of following tags will reffer to // this one as COPY we need to point them to original string. // By Sereal spec a COPY tag cannot reffer to another COPY tag. ptable_entry->value = INT2PTR(void *, strtable_entry->offset); } } else if (strtable_entry) {
SRL_STATIC_INLINE void srl_set_input_buffer(pTHX_ srl_merger_t *mrg, SV *src) { STRLEN len; UV header_len; U8 encoding_flags; U8 protocol_version; srl_buffer_char *tmp; IV proto_version_and_encoding_flags_int; SRL_RDR_CLEAR(&mrg->ibuf); tmp = (srl_buffer_char*) SvPV(src, len); mrg->ibuf.start = mrg->ibuf.pos = tmp; mrg->ibuf.end = mrg->ibuf.start + len; proto_version_and_encoding_flags_int = srl_validate_header_version(aTHX_ (srl_reader_char_ptr) mrg->ibuf.start, len); if (proto_version_and_encoding_flags_int < 1) { if (proto_version_and_encoding_flags_int == 0) SRL_RDR_ERROR(mrg->pibuf, "Bad Sereal header: It seems your document was accidentally UTF-8 encoded"); else SRL_RDR_ERROR(mrg->pibuf, "Bad Sereal header: Not a valid Sereal document."); } mrg->ibuf.pos += 5; encoding_flags = (U8) (proto_version_and_encoding_flags_int & SRL_PROTOCOL_ENCODING_MASK); protocol_version = (U8) (proto_version_and_encoding_flags_int & SRL_PROTOCOL_VERSION_MASK); if (expect_false(protocol_version > 3 || protocol_version < 1)) { SRL_RDR_ERRORf1(mrg->pibuf, "Unsupported Sereal protocol version %u", (unsigned int) protocol_version); } // skip header in any case header_len = srl_read_varint_uv_length(aTHX_ mrg->pibuf, " while reading header"); mrg->ibuf.pos += header_len; if (encoding_flags == SRL_PROTOCOL_ENCODING_RAW) { /* no op */ } else if ( encoding_flags == SRL_PROTOCOL_ENCODING_SNAPPY || encoding_flags == SRL_PROTOCOL_ENCODING_SNAPPY_INCREMENTAL) { srl_decompress_body_snappy(aTHX_ mrg->pibuf, encoding_flags, NULL); } else if (encoding_flags == SRL_PROTOCOL_ENCODING_ZLIB) { srl_decompress_body_zlib(aTHX_ mrg->pibuf, NULL); } else { SRL_RDR_ERROR(mrg->pibuf, "Sereal document encoded in an unknown format"); } /* this functions *MUST* be called after srl_decompress_body* */ SRL_RDR_UPDATE_BODY_POS(mrg->pibuf, protocol_version); DEBUG_ASSERT_RDR_SANE(mrg->pibuf); }
SRL_STATIC_INLINE void srl_read_header(pTHX_ srl_decoder_t *dec) { UV header_len; /* 4 byte magic string + version/flags + hdr len at least */ ASSERT_BUF_SPACE(dec, 4 + 1 + 1," while reading header"); if (strnEQ((char*)dec->pos, SRL_MAGIC_STRING, 4)) { dec->pos += 4; dec->proto_version_and_flags = *dec->pos++; if (expect_false( (dec->proto_version_and_flags & SRL_PROTOCOL_VERSION_MASK) != 1 )) SRL_ERRORf1("Unsupported Sereal protocol version %u", dec->proto_version_and_flags & SRL_PROTOCOL_VERSION_MASK); if ((dec->proto_version_and_flags & SRL_PROTOCOL_ENCODING_MASK) == SRL_PROTOCOL_ENCODING_RAW) { /* no op */ } else if ( ( dec->proto_version_and_flags & SRL_PROTOCOL_ENCODING_MASK ) == SRL_PROTOCOL_ENCODING_SNAPPY || ( dec->proto_version_and_flags & SRL_PROTOCOL_ENCODING_MASK ) == SRL_PROTOCOL_ENCODING_SNAPPY_INCREMENTAL ) { if (expect_false( SRL_DEC_HAVE_OPTION(dec, SRL_F_DECODER_REFUSE_SNAPPY) )) { SRL_ERROR("Sereal document is compressed with Snappy, " "but this decoder is configured to refuse Snappy-compressed input."); } dec->flags |= SRL_F_DECODER_DECOMPRESS_SNAPPY; } else { SRL_ERRORf1( "Sereal document encoded in an unknown format '%d'", (dec->proto_version_and_flags & SRL_PROTOCOL_ENCODING_MASK) >> 4 ); } /* Must do this via a temporary as it modifes dec->pos itself */ header_len= srl_read_varint_uv_length(aTHX_ dec, " while reading header"); /* Skip header since we don't have any defined header-content in this * protocol version. */ dec->pos += header_len; } else {
IV srl_iterator_hash_exists(pTHX_ srl_iterator_t *iter, const char *name, STRLEN name_len) { U8 tag; UV length, offset; const char *key_ptr; IV stack_depth = iter->stack.depth; srl_iterator_stack_ptr stack_ptr = iter->stack.ptr; DEBUG_ASSERT_RDR_SANE(iter->pbuf); SRL_ITER_ASSERT_EOF(iter, "stringish"); SRL_ITER_ASSERT_STACK(iter); SRL_ITER_ASSERT_HASH_ON_STACK(iter); SRL_ITER_TRACE("name=%.*s", (int) name_len, name); SRL_ITER_REPORT_STACK_STATE(iter); while (stack_ptr->idx) { stack_ptr->idx--; // do not make it be part of while clause SRL_ITER_ASSERT_STACK(iter); assert(stack_ptr->idx % 2 == 1); assert(iter->stack.depth == stack_depth); DEBUG_ASSERT_RDR_SANE(iter->pbuf); tag = *iter->buf.pos & ~SRL_HDR_TRACK_FLAG; SRL_ITER_REPORT_TAG(iter, tag); iter->buf.pos++; switch (tag) { CASE_SRL_HDR_SHORT_BINARY: length = SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); key_ptr = (const char *) iter->buf.pos; iter->buf.pos += length; break; case SRL_HDR_BINARY: length = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading BINARY"); key_ptr = (const char *) iter->buf.pos; iter->buf.pos += length; break; case SRL_HDR_STR_UTF8: // TODO deal with UTF8 length = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading STR_UTF8"); key_ptr = (const char *) iter->buf.pos; iter->buf.pos += length; break; case SRL_HDR_COPY: offset = srl_read_varint_uv_offset(aTHX_ iter->pbuf, " while reading COPY tag"); key_ptr = (const char *) iter->buf.body_pos + offset; tag = *key_ptr & ~SRL_HDR_TRACK_FLAG; key_ptr++; /* Note we do NOT validate these items, as we have already read them * and if they were a problem we would not be here to process them! */ switch (tag) { CASE_SRL_HDR_SHORT_BINARY: length = SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); break; case SRL_HDR_BINARY: SET_UV_FROM_VARINT(iter->pbuf, length, key_ptr); break; case SRL_HDR_STR_UTF8: // TODO deal with UTF8 SET_UV_FROM_VARINT(iter->pbuf, length, key_ptr); break; default: SRL_RDR_ERROR_BAD_COPY(iter->pbuf, SRL_HDR_HASH); } break; default: SRL_RDR_ERROR_UNEXPECTED(iter->pbuf, tag, "stringish"); } if (expect_false((srl_reader_char_ptr) key_ptr >= iter->buf.end)) { SRL_RDR_ERROR_EOF(iter->pbuf, "string content"); } if ( length == name_len && memcmp(name, key_ptr, name_len) == 0) { SRL_ITER_TRACE("found key '%.*s' at offset %"UVuf, (int) name_len, name, SRL_RDR_BODY_POS_OFS(iter->pbuf)); return SRL_RDR_BODY_POS_OFS(iter->pbuf); } // srl_iterator_next garantee that we remans on current stack srl_iterator_next(aTHX_ iter, 1); stack_ptr = iter->stack.ptr; } SRL_ITER_TRACE("didn't found key '%.*s'", (int) name_len, name); return SRL_ITER_NOT_FOUND; }
const char * srl_iterator_hash_key(pTHX_ srl_iterator_t *iter, STRLEN *len_out) { U8 tag; UV length, offset; const char *result = NULL; srl_reader_char_ptr orig_pos = iter->buf.pos; *len_out = 0; DEBUG_ASSERT_RDR_SANE(iter->pbuf); SRL_ITER_ASSERT_EOF(iter, "stringish"); SRL_ITER_ASSERT_STACK(iter); SRL_ITER_ASSERT_HASH_ON_STACK(iter); tag = *iter->buf.pos & ~SRL_HDR_TRACK_FLAG; SRL_ITER_REPORT_TAG(iter, tag); iter->buf.pos++; switch (tag) { CASE_SRL_HDR_SHORT_BINARY: length = SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); break; case SRL_HDR_BINARY: length = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading BINARY"); break; case SRL_HDR_STR_UTF8: // TODO deal with UTF8 length = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading STR_UTF8"); break; case SRL_HDR_COPY: offset = srl_read_varint_uv_offset(aTHX_ iter->pbuf, " while reading COPY tag"); iter->buf.pos = iter->buf.body_pos + offset; /* Note we do NOT validate these items, as we have already read them * and if they were a problem we would not be here to process them! */ tag = *iter->buf.pos & ~SRL_HDR_TRACK_FLAG; SRL_ITER_REPORT_TAG(iter, tag); iter->buf.pos++; switch (tag) { CASE_SRL_HDR_SHORT_BINARY: length = SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); break; case SRL_HDR_BINARY: SET_UV_FROM_VARINT(iter->pbuf, length, iter->buf.pos); break; case SRL_HDR_STR_UTF8: // TODO deal with UTF8 SET_UV_FROM_VARINT(iter->pbuf, length, iter->buf.pos); break; default: SRL_RDR_ERROR_BAD_COPY(iter->pbuf, SRL_HDR_HASH); } break; default: SRL_RDR_ERROR_UNEXPECTED(iter->pbuf, tag, "stringish"); } if (expect_false(iter->buf.pos + length >= iter->buf.end)) { SRL_RDR_ERROR_EOF(iter->pbuf, "string content"); } *len_out = length; result = (const char *) iter->buf.pos; iter->buf.pos = orig_pos; // restore original position DEBUG_ASSERT_RDR_SANE(iter->pbuf); return result; }
void srl_iterator_set(pTHX_ srl_iterator_t *iter, SV *src) { SV *sv; STRLEN len; UV header_len; U8 encoding_flags; U8 protocol_version; srl_reader_char_ptr tmp; IV proto_version_and_encoding_flags_int; srl_iterator_stack_ptr stack_ptr = NULL; if (iter->document) { SvREFCNT_dec(iter->document); iter->document = NULL; } iter->document = src; SvREFCNT_inc(iter->document); tmp = (srl_reader_char_ptr) SvPV(src, len); iter->buf.start = iter->buf.pos = tmp; iter->buf.end = iter->buf.start + len; proto_version_and_encoding_flags_int = srl_validate_header_version(aTHX_ iter->buf.start, len); if (proto_version_and_encoding_flags_int < 1) { if (proto_version_and_encoding_flags_int == 0) SRL_RDR_ERROR(iter->pbuf, "Bad Sereal header: It seems your document was accidentally UTF-8 encoded"); else SRL_RDR_ERROR(iter->pbuf, "Bad Sereal header: Not a valid Sereal document."); } iter->buf.pos += 5; encoding_flags = (U8) (proto_version_and_encoding_flags_int & SRL_PROTOCOL_ENCODING_MASK); protocol_version = (U8) (proto_version_and_encoding_flags_int & SRL_PROTOCOL_VERSION_MASK); if (expect_false(protocol_version > 3 || protocol_version < 1)) { SRL_RDR_ERRORf1(iter->pbuf, "Unsupported Sereal protocol version %u", (unsigned int) protocol_version); } // skip header in any case header_len = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading header"); iter->buf.pos += header_len; if (encoding_flags == SRL_PROTOCOL_ENCODING_RAW) { /* no op */ } else if ( encoding_flags == SRL_PROTOCOL_ENCODING_SNAPPY || encoding_flags == SRL_PROTOCOL_ENCODING_SNAPPY_INCREMENTAL) { srl_decompress_body_snappy(aTHX_ iter->pbuf, encoding_flags, &sv); SvREFCNT_dec(iter->document); SvREFCNT_inc(sv); iter->document = sv; } else if (encoding_flags == SRL_PROTOCOL_ENCODING_ZLIB) { srl_decompress_body_zlib(aTHX_ iter->pbuf, &sv); SvREFCNT_dec(iter->document); SvREFCNT_inc(sv); iter->document = sv; } else { SRL_RDR_ERROR(iter->pbuf, "Sereal document encoded in an unknown format"); } /* this function *MUST* be called after calling srl_decompress_body* */ SRL_RDR_UPDATE_BODY_POS(iter->pbuf, protocol_version); DEBUG_ASSERT_RDR_SANE(iter->pbuf); srl_stack_push_and_set(iter, SRL_ITER_STACK_ROOT_TAG, 1, stack_ptr); srl_iterator_reset(aTHX_ iter); }
/* Main routine. Caller must ensure that EOF is NOT reached */ SRL_STATIC_INLINE void srl_iterator_step_internal(pTHX_ srl_iterator_t *iter) { U8 tag; UV length; srl_stack_t *stack = iter->stack; DEBUG_ASSERT_RDR_SANE(iter->pbuf); srl_iterator_wrap_stack(aTHX_ iter, -1); if (srl_stack_empty(stack)) return; SRL_ITER_ASSERT_STACK(iter); stack->ptr->idx--; SRL_ITER_TRACE("stack->ptr: idx=%d depth=%d", stack->ptr->idx, (int) SRL_STACK_DEPTH(stack)); SRL_ITER_ASSERT_STACK(iter); read_again: tag = *iter->buf.pos & ~SRL_HDR_TRACK_FLAG; SRL_ITER_REPORT_TAG(iter, tag); iter->buf.pos++; /* No code which decrease step, next or stack's counters should be added here. * Otherwise the counters will be decreased twicer for tags like REFN, ALIAS, etc. */ switch (tag) { CASE_SRL_HDR_SHORT_BINARY: iter->buf.pos += SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); break; case SRL_HDR_HASH: length = srl_read_varint_uv_count(aTHX_ iter->pbuf, " while reading HASH"); if (length > 0) srl_stack_push_and_set(iter, tag, length * 2); break; case SRL_HDR_ARRAY: length = srl_read_varint_uv_count(aTHX_ iter->pbuf, " while reading ARRAY"); if (length > 0) srl_stack_push_and_set(iter, tag, length); break; CASE_SRL_HDR_HASHREF: length = SRL_HDR_HASHREF_LEN_FROM_TAG(tag); if (length > 0) srl_stack_push_and_set(iter, tag, length * 2); break; CASE_SRL_HDR_ARRAYREF: length = SRL_HDR_ARRAYREF_LEN_FROM_TAG(tag); if (length > 0) srl_stack_push_and_set(iter, tag, length); break; CASE_SRL_HDR_POS: CASE_SRL_HDR_NEG: break; case SRL_HDR_VARINT: case SRL_HDR_ZIGZAG: srl_skip_varint(aTHX_ iter->pbuf); break; case SRL_HDR_FLOAT: iter->buf.pos += 4; break; case SRL_HDR_DOUBLE: iter->buf.pos += 8; break; case SRL_HDR_LONG_DOUBLE: iter->buf.pos += 16; break; case SRL_HDR_TRUE: case SRL_HDR_FALSE: case SRL_HDR_UNDEF: case SRL_HDR_CANONICAL_UNDEF: break; case SRL_HDR_REFN: case SRL_HDR_ALIAS: case SRL_HDR_WEAKEN: goto read_again; case SRL_HDR_PAD: while (SRL_RDR_NOT_DONE(iter->pbuf) && *iter->buf.pos++ == SRL_HDR_PAD) {}; goto read_again; case SRL_HDR_BINARY: case SRL_HDR_STR_UTF8: length = srl_read_varint_uv_length(aTHX_ iter->pbuf, " while reading BINARY or STR_UTF8"); iter->buf.pos += length; break; case SRL_HDR_COPY: case SRL_HDR_REFP: srl_skip_varint(aTHX_ iter->pbuf); break; /* case SRL_HDR_OBJECTV: */ /* case SRL_HDR_OBJECTV_FREEZE: */ /* case SRL_HDR_REGEXP: */ /* case SRL_HDR_OBJECT: */ /* case SRL_HDR_OBJECT_FREEZE: */ default: SRL_RDR_ERROR_UNIMPLEMENTED(iter->pbuf, tag, ""); break; } DEBUG_ASSERT_RDR_SANE(iter->pbuf); }
/* This is the main routine to deserialize a structure. * It rolls up all the other "top level" routines into one */ SV * srl_decode_into(pTHX_ srl_decoder_t *dec, SV *src, SV* into, UV start_offset) { assert(dec != NULL); if (SvUTF8(src)) sv_utf8_downgrade(src, 0); srl_begin_decoding(aTHX_ dec, src, start_offset); srl_read_header(aTHX_ dec); if (SRL_DEC_HAVE_OPTION(dec, SRL_F_DECODER_DECOMPRESS_SNAPPY)) { /* uncompress */ uint32_t dest_len; SV *buf_sv; unsigned char *buf; unsigned char *old_pos; const ptrdiff_t sereal_header_len = dec->pos - dec->buf_start; const STRLEN compressed_packet_len = ( dec->proto_version_and_flags & SRL_PROTOCOL_ENCODING_MASK ) == SRL_PROTOCOL_ENCODING_SNAPPY_INCREMENTAL ? (STRLEN)srl_read_varint_uv_length(aTHX_ dec, " while reading compressed packet size") : (STRLEN)(dec->buf_end - dec->pos); int decompress_ok; int header_len; /* all decl's above here, or we break C89 compilers */ dec->bytes_consumed= compressed_packet_len + (dec->pos - dec->buf_start); header_len = csnappy_get_uncompressed_length( (char *)dec->pos, compressed_packet_len, &dest_len ); if (header_len == CSNAPPY_E_HEADER_BAD) SRL_ERROR("Invalid Snappy header in Snappy-compressed Sereal packet"); /* Let perl clean this up. Yes, it's not the most efficient thing * ever, but it's just one mortal per full decompression, so not * a bottle-neck. */ buf_sv = sv_2mortal( newSV(sereal_header_len + dest_len + 1 )); buf = (unsigned char *)SvPVX(buf_sv); /* FIXME probably unnecessary to copy the Sereal header! */ Copy(dec->buf_start, buf, sereal_header_len, unsigned char); old_pos = dec->pos; dec->buf_start = buf; dec->pos = buf + sereal_header_len; dec->buf_end = dec->pos + dest_len; dec->buf_len = dest_len + sereal_header_len; decompress_ok = csnappy_decompress_noheader((char *)(old_pos + header_len), compressed_packet_len - header_len, (char *)dec->pos, &dest_len); if (expect_false( decompress_ok != 0 )) { SRL_ERRORf1("Snappy decompression of Sereal packet payload failed with error %i!", decompress_ok); } } if (expect_true(!into)) { into= sv_2mortal(newSV_type(SVt_NULL)); } srl_read_single_value(aTHX_ dec, into); /* assert(dec->pos == dec->buf_end); For now we disable this */ if (expect_false(SRL_DEC_HAVE_OPTION(dec, SRL_F_DECODER_NEEDS_FINALIZE))) { srl_finalize_structure(aTHX_ dec); } /* If we aren't reading from a decompressed buffer we have to remember the number * of bytes used for the user to query. */ if (dec->bytes_consumed == 0) dec->bytes_consumed = dec->pos - dec->buf_start; if (SRL_DEC_HAVE_OPTION(dec, SRL_F_DECODER_DESTRUCTIVE_INCREMENTAL)) { STRLEN len; char *pv= SvPV(src,len); /* check the length here? do something different if the string is now exhausted? */ sv_chop(src, pv + dec->bytes_consumed); } srl_clear_decoder(aTHX_ dec); return into; }
SRL_STATIC_INLINE void srl_build_track_table(pTHX_ srl_merger_t *mrg) { U8 tag; UV offset, length; DEBUG_ASSERT_RDR_SANE(mrg->pibuf); if (mrg->tracked_offsets) srl_stack_clear(mrg->tracked_offsets); while (expect_true(BUF_NOT_DONE(mrg->pibuf))) { /* since we're doing full pass, it's not necessary to * add items into tracked_offsets here. They will be added * by corresponding REFP/ALIAS/COPY and other tags */ tag = *mrg->ibuf.pos & ~SRL_HDR_TRACK_FLAG; SRL_REPORT_CURRENT_TAG(mrg, tag); mrg->ibuf.pos++; if (tag >= SRL_HDR_SHORT_BINARY_LOW) { mrg->ibuf.pos += SRL_HDR_SHORT_BINARY_LEN_FROM_TAG(tag); } else if (tag > SRL_HDR_NEG_HIGH && tag < SRL_HDR_ARRAYREF_LOW) { switch (tag) { case SRL_HDR_VARINT: case SRL_HDR_ZIGZAG: srl_read_varint_uv(aTHX_ mrg->pibuf); // TODO test/implement srl_skip_varint() break; case SRL_HDR_FLOAT: mrg->ibuf.pos += 4; break; case SRL_HDR_DOUBLE: mrg->ibuf.pos += 8; break; case SRL_HDR_LONG_DOUBLE: mrg->ibuf.pos += 16; break; case SRL_HDR_BINARY: case SRL_HDR_STR_UTF8: length = srl_read_varint_uv_length(aTHX_ mrg->pibuf, " while reading BINARY or STR_UTF8"); mrg->ibuf.pos += length; break; case SRL_HDR_HASH: case SRL_HDR_ARRAY: srl_read_varint_uv_count(aTHX_ mrg->pibuf, " while reading ARRAY or HASH"); break; case SRL_HDR_TRUE: case SRL_HDR_FALSE: case SRL_HDR_UNDEF: case SRL_HDR_CANONICAL_UNDEF: // noop break; default: switch (tag) { case SRL_HDR_COPY: case SRL_HDR_REFP: case SRL_HDR_ALIAS: case SRL_HDR_OBJECTV: case SRL_HDR_OBJECTV_FREEZE: offset = srl_read_varint_uv_offset(aTHX_ mrg->pibuf, " while reading COPY, OBJECTV or OBJECTV_FREEZE"); srl_stack_push_val(SRL_GET_TRACKED_OFFSETS(mrg), offset); break; case SRL_HDR_PAD: case SRL_HDR_REFN: case SRL_HDR_WEAKEN: case SRL_HDR_EXTEND: case SRL_HDR_REGEXP: case SRL_HDR_OBJECT: case SRL_HDR_OBJECT_FREEZE: // noop break; default: SRL_RDR_ERROR_UNIMPLEMENTED(mrg->pibuf, tag, ""); break; } } } } if (mrg->tracked_offsets && !srl_stack_empty(mrg->tracked_offsets)) { srl_stack_rsort(aTHX_ mrg->tracked_offsets); srl_stack_dedupe(aTHX_ mrg->tracked_offsets); //int i = 0; //SRL_STACK_TYPE *ptr = mrg->tracked_offsets->begin; //while (ptr <= mrg->tracked_offsets->ptr) { // warn("tracked_offsets: offset dedups idx %d offset %d\n", i, (int) *ptr); // i++; ptr++; //} } DEBUG_ASSERT_RDR_SANE(mrg->pibuf); }