PJ_DEF(void) pj_scan_get_until_ch( pj_scanner *scanner,
				   int until_char, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
	pj_scan_syntax_err(scanner);
	return;
    }

    while (PJ_SCAN_CHECK_EOF(s) && *s != until_char) {
	++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_scan_init( pj_scanner *scanner, char *bufstart,
			   pj_size_t buflen, unsigned options,
			   pj_syn_err_func_ptr callback )
{
    PJ_CHECK_STACK();

    scanner->begin = scanner->curptr = bufstart;
    scanner->end = bufstart + buflen;
    scanner->line = 1;
    scanner->start_line = scanner->begin;
    scanner->callback = callback;
    scanner->skip_ws = options;

    if (scanner->skip_ws)
	pj_scan_skip_whitespace(scanner);
}

Merge pull request from GHSA-7fw8-54cv-r7pm


PJ_DEF(int) pj_scan_peek_n( pj_scanner *scanner,
			     pj_size_t len, pj_str_t *out)
{
    char *endpos = scanner->curptr + len;

    if (endpos > scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }

    pj_strset(out, scanner->curptr, len);
    return *endpos;
}


PJ_DEF(void) pj_scan_get_newline( pj_scanner *scanner )
{
    if (!PJ_SCAN_IS_NEWLINE(*scanner->curptr)) {
	pj_scan_syntax_err(scanner);
	return;
    }

    if (*scanner->curptr == '\r') {
	++scanner->curptr;
    }
    if (*scanner->curptr == '\n') {
	++scanner->curptr;
    }

    ++scanner->line;
    scanner->start_line = scanner->curptr;

    /**
     * This probably is a bug, see PROTOS test #2480.
     * This would cause scanner to incorrectly eat two new lines, e.g.
     * when parsing:
     *
     *	Content-Length: 120\r\n
     *	\r\n
     *	<space><space><space>...
     *
     * When pj_scan_get_newline() is called to parse the first newline
     * in the Content-Length header, it will eat the second newline
     * too because it thinks that it's a header continuation.
     *
     * if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
     *    pj_scan_skip_whitespace(scanner);
     * }
     */
}


PJ_DEF(void) pj_cis_del_range( pj_cis_t *cis, int cstart, int cend)
{
    while (cstart != cend) {
        PJ_CIS_CLR(cis, cstart);
        cstart++;
    }
}


PJ_DEF(void) pj_cis_invert( pj_cis_t *cis )
{
    unsigned i;
    /* Can not set zero. This is the requirement of the parser. */
    for (i=1; i<256; ++i) {
	if (PJ_CIS_ISSET(cis,i))
            PJ_CIS_CLR(cis,i);
        else
            PJ_CIS_SET(cis,i);
    }
}


PJ_DEF(int) pj_scan_stricmp( pj_scanner *scanner, const char *s, int len)
{
    if (scanner->curptr + len > scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }
    return pj_ansi_strnicmp(scanner->curptr, s, len);
}


PJ_DEF(int) pj_scan_strcmp( pj_scanner *scanner, const char *s, int len)
{
    if (scanner->curptr + len > scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }
    return strncmp(scanner->curptr, s, len);
}


PJ_DEF(void) pj_scan_get_until_chr( pj_scanner *scanner,
				     const char *until_spec, pj_str_t *out)
{
    register char *s = scanner->curptr;
    pj_size_t speclen;

    if (s >= scanner->end) {
	pj_scan_syntax_err(scanner);
	return;
    }

    speclen = strlen(until_spec);
    while (PJ_SCAN_CHECK_EOF(s) && !memchr(until_spec, *s, speclen)) {
	++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_cis_add_range(pj_cis_t *cis, int cstart, int cend)
{
    /* Can not set zero. This is the requirement of the parser. */
    pj_assert(cstart > 0);

    while (cstart != cend) {
        PJ_CIS_SET(cis, cstart);
	++cstart;
    }
}


PJ_DEF(int) pj_scan_peek_until( pj_scanner *scanner,
				const pj_cis_t *spec,
				pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }

    while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match( spec, *s))
	++s;

    pj_strset3(out, scanner->curptr, s);
    return *s;
}


PJ_DEF(void) pj_scan_get( pj_scanner *scanner,
			  const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    pj_assert(pj_cis_match(spec,0)==0);

    /* EOF is detected implicitly */
    if (!pj_cis_match(spec, *s)) {
	pj_scan_syntax_err(scanner);
	return;
    }

    do {
	++s;
    } while (pj_cis_match(spec, *s));
    /* No need to check EOF here (PJ_SCAN_CHECK_EOF(s)) because
     * buffer is NULL terminated and pj_cis_match(spec,0) should be
     * false.
     */

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_cis_del_str( pj_cis_t *cis, const char *str)
{
    while (*str) {
        PJ_CIS_CLR(cis, *str);
	++str;
    }
}


static void pj_scan_syntax_err(pj_scanner *scanner)
{
    (*scanner->callback)(scanner);
}


PJ_DEF(void) pj_scan_get_until( pj_scanner *scanner,
				const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
	pj_scan_syntax_err(scanner);
	return;
    }

    while (PJ_SCAN_CHECK_EOF(s) && !pj_cis_match(spec, *s)) {
	++s;
    }

    pj_strset3(out, scanner->curptr, s);

    scanner->curptr = s;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(int) pj_scan_stricmp_alnum( pj_scanner *scanner, const char *s,
				   int len)
{
    if (scanner->curptr + len > scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }
    return strnicmp_alnum(scanner->curptr, s, len);
}


PJ_DEF(void) pj_scan_save_state( const pj_scanner *scanner,
				 pj_scan_state *state)
{
    state->curptr = scanner->curptr;
    state->line = scanner->line;
    state->start_line = scanner->start_line;
}


PJ_DEF(void) pj_scan_restore_state( pj_scanner *scanner,
				    pj_scan_state *state)
{
    scanner->curptr = state->curptr;
    scanner->line = state->line;
    scanner->start_line = state->start_line;
}


PJ_DEF(void) pj_scan_get_unescape( pj_scanner *scanner,
				   const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;
    char *dst = s;

    pj_assert(pj_cis_match(spec,0)==0);

    /* Must not match character '%' */
    pj_assert(pj_cis_match(spec,'%')==0);

    /* EOF is detected implicitly */
    if (!pj_cis_match(spec, *s) && *s != '%') {
	pj_scan_syntax_err(scanner);
	return;
    }

    out->ptr = s;
    do {
	if (*s == '%') {
	    if (s+3 <= scanner->end && pj_isxdigit(*(s+1)) &&
		pj_isxdigit(*(s+2)))
	    {
		*dst = (pj_uint8_t) ((pj_hex_digit_to_val(*(s+1)) << 4) +
				      pj_hex_digit_to_val(*(s+2)));
		++dst;
		s += 3;
	    } else {
		*dst++ = *s++;
		*dst++ = *s++;
		break;
	    }
	}

	if (pj_cis_match(spec, *s)) {
	    char *start = s;
	    do {
		++s;
	    } while (pj_cis_match(spec, *s));

	    if (dst != start) pj_memmove(dst, start, s-start);
	    dst += (s-start);
	}

    } while (*s == '%');

    scanner->curptr = s;
    out->slen = (dst - out->ptr);

    if (PJ_SCAN_IS_PROBABLY_SPACE(*s) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
}


PJ_DEF(void) pj_cis_add_str( pj_cis_t *cis, const char *str)
{
    while (*str) {
        PJ_CIS_SET(cis, *str);
	++str;
    }
}


PJ_DEF(void) pj_cis_add_alpha(pj_cis_t *cis)
{
    pj_cis_add_range( cis, 'a', 'z'+1);
    pj_cis_add_range( cis, 'A', 'Z'+1);
}


PJ_DEF(int) pj_scan_peek( pj_scanner *scanner,
			  const pj_cis_t *spec, pj_str_t *out)
{
    register char *s = scanner->curptr;

    if (s >= scanner->end) {
	pj_scan_syntax_err(scanner);
	return -1;
    }

    /* Don't need to check EOF with PJ_SCAN_CHECK_EOF(s) */
    while (pj_cis_match(spec, *s))
	++s;

    pj_strset3(out, scanner->curptr, s);
    return *s;
}


PJ_DEF(void) pj_scan_fini( pj_scanner *scanner )
{
    PJ_CHECK_STACK();
    PJ_UNUSED_ARG(scanner);
}


static void r_rebase_info_free(RRebaseInfo *info) {
	if (!info) {
		return;
	}

	if (info->ranges) {
		R_FREE (info->ranges);
		info->ranges = NULL;
	}

	R_FREE (info);
}

Fix null deref in xnu.kernelcache ##crash

* Reported by @xshad3 via huntr.dev


static RList *sections(RBinFile *bf) {
	RList *ret = NULL;
	RBinObject *obj = bf ? bf->o : NULL;

	if (!obj || !obj->bin_obj || !(ret = r_list_newf ((RListFree)free))) {
		return NULL;
	}

	RKernelCacheObj *kobj = (RKernelCacheObj*) obj->bin_obj;
	ensure_kexts_initialized (kobj, bf);

	int iter;
	RKext *kext;
	r_kext_index_foreach (kobj->kexts, iter, kext) {
		ut8 magicbytes[4];

		r_buf_read_at (kobj->cache_buf, kext->range.offset, magicbytes, 4);
		int magic = r_read_le32 (magicbytes);
		switch (magic) {
		case MH_MAGIC_64:
			sections_from_mach0 (ret, kext->mach0, bf, kext->range.offset, kext->name, kobj);
			break;
		default:
			eprintf ("Unknown sub-bin\n");
			break;
		}
	}

	sections_from_mach0 (ret, kobj->mach0, bf, 0, NULL, kobj);

	struct MACH0_(segment_command) *seg;
	int nsegs = R_MIN (kobj->mach0->nsegs, 128);
	int i;
	for (i = 0; i < nsegs; i++) {
		RBinSection *ptr;
		char segname[17];

		if (!(ptr = R_NEW0 (RBinSection))) {
			break;
		}

		seg = &kobj->mach0->segs[i];
		r_str_ncpy (segname, seg->segname, 17);
		r_str_filter (segname, -1);
		ptr->name = r_str_newf ("%d.%s", i, segname);
		ptr->size = seg->vmsize;
		ptr->vsize = seg->vmsize;
		ptr->paddr = seg->fileoff + bf->o->boffset;
		ptr->vaddr = seg->vmaddr;
		ptr->add = true;
		ptr->is_segment = true;
		if (!ptr->vaddr) {
			ptr->vaddr = ptr->paddr;
		}
		ptr->perm = prot2perm (seg->initprot);
		r_list_append (ret, ptr);
	}

	return ret;
}


static int prot2perm(int x) {
	int r = 0;
	if (x&1) r |= 4;
	if (x&2) r |= 2;
	if (x&4) r |= 1;
	return r;
}


static void r_kext_index_free(RKextIndex *index) {
	if (!index) {
		return;
	}

	int i = 0;
	RKext *kext;
	r_kext_index_foreach (index, i, kext) {
		r_kext_free (kext);
		index->entries[i] = NULL;
	}

	index->length = 0;
	R_FREE (index);
}


static ut64 baddr(RBinFile *bf) {
	if (!bf || !bf->o || !bf->o->bin_obj) {
		return 8LL;
	}

	RKernelCacheObj *obj = (RKernelCacheObj*) bf->o->bin_obj;
	return MACH0_(get_baddr)(obj->mach0);
}


static void process_kmod_init_term(RKernelCacheObj *obj, RKext *kext, RList *ret, ut64 **inits, ut64 **terms) {
	if (!*inits || !*terms) {
		struct section_t *sections = NULL;
		if (!(sections = MACH0_(get_sections) (obj->mach0))) {
			return;
		}

		int i = 0;
		for (; !sections[i].last; i++) {
			if (sections[i].size == 0) {
				continue;
			}

			ut64 start_paddr = 0;
			ut64 *target = NULL;
			int n_ptrs = 0;

			if (!*inits && strstr (sections[i].name, "__kmod_init")) {
				int n_inits = sections[i].size / 8;
				if (n_inits <= 0) {
					continue;
				}
				*inits = R_NEWS0 (ut64, n_inits + 1);
				target = *inits;
				n_ptrs = n_inits;
			}
			if (!*terms && strstr (sections[i].name, "__kmod_term")) {
				int n_terms = sections[i].size / 8;
				if (n_terms <= 0) {
					continue;
				}
				*terms = R_NEWS0 (ut64, n_terms + 1);
				target = *terms;
				n_ptrs = n_terms;
			}
			if (!target || !n_ptrs) {
				continue;
			}
			start_paddr = sections[i].offset;
			int j = 0;
			ut8 bytes[8];
			for (; j < n_ptrs; j++) {
				if (r_buf_read_at (obj->cache_buf, start_paddr + j * 8, bytes, 8) < 8) {
					break;
				}
				target[j] = K_RPTR (bytes);
			}
			target[j] = 0;
		}

		R_FREE (sections);
	}

	if (*inits) {
		create_initterm_syms (kext, ret, R_BIN_ENTRY_TYPE_INIT, *inits);
	}
	if (*terms) {
		create_initterm_syms (kext, ret, R_BIN_ENTRY_TYPE_FINI, *terms);
	}
}


static RList *entries(RBinFile *bf) {
	RList *ret;
	RBinObject *obj = bf ? bf->o : NULL;

	if (!obj || !obj->bin_obj || !(ret = r_list_newf (free))) {
		return NULL;
	}

	RKernelCacheObj *kobj = (RKernelCacheObj*) obj->bin_obj;
	ut64 entry_vaddr = kobj->mach0->entry;
	if (kobj->pa2va_exec <= entry_vaddr) {
		ut64 entry_paddr = entry_vaddr - kobj->pa2va_exec;
		RBinAddr *ba = newEntry (entry_paddr, entry_vaddr, 0);
		if (ba) {
			r_list_append (ret, ba);
		}
	}

	process_constructors (kobj, kobj->mach0, ret, 0, true, R_K_CONSTRUCTOR_TO_ENTRY, NULL);

	return ret;
}


static bool check_buffer(RBinFile *bf, RBuffer *b) {
	if (r_buf_size (b) > 4) {
		ut8 buf[4];
		r_buf_read_at (b, 0, buf, sizeof (buf));
		if (!memcmp (buf, "\xcf\xfa\xed\xfe", 4)) {
			return is_kernelcache_buffer (b);
		}
	}
	return false;
}


static RList *symbols(RBinFile *bf) {
	RList *ret = r_list_newf (free);
	if (!ret) {
		return NULL;
	}

	RKernelCacheObj *obj = (RKernelCacheObj*) bf->o->bin_obj;

	symbols_from_mach0 (ret, obj->mach0, bf, 0, 0);

	HtPP *kernel_syms_by_addr = sdb_ht_new ();
	if (!kernel_syms_by_addr) {
		r_list_free (ret);
		return NULL;
	}

	RListIter *iter;
	RBinSymbol *sym;
	ut64 enosys_addr = 0;
	r_list_foreach (ret, iter, sym) {
		r_strf_var (key, 64, "%"PFMT64x, sym->vaddr);
		sdb_ht_insert (kernel_syms_by_addr, key, sym->dname ? sym->dname : sym->name);
		if (!enosys_addr && strstr (sym->name, "enosys")) {
			enosys_addr = sym->vaddr;
		}
	}

	RList *syscalls = resolve_syscalls (obj, enosys_addr);
	if (syscalls) {
		r_list_foreach (syscalls, iter, sym) {
			r_strf_var (key, 32, "%"PFMT64x, sym->vaddr);
			sdb_ht_insert (kernel_syms_by_addr, key, sym->name);
			r_list_append (ret, sym);
		}
		syscalls->free = NULL;
		r_list_free (syscalls);
	}

	RList *subsystem = resolve_mig_subsystem (obj);
	if (subsystem) {
		r_list_foreach (subsystem, iter, sym) {
			r_strf_var (key, 64, "%"PFMT64x, sym->vaddr);
			sdb_ht_insert (kernel_syms_by_addr, key, sym->name);
			r_list_append (ret, sym);
		}
		subsystem->free = NULL;
		r_list_free (subsystem);
	}

	ensure_kexts_initialized (obj, bf);

	RKext *kext;
	int kiter;
	ut64 *inits = NULL;
	ut64 *terms = NULL;
	r_kext_index_foreach (obj->kexts, kiter, kext) {
		ut8 magicbytes[4];
		r_buf_read_at (obj->cache_buf, kext->range.offset, magicbytes, 4);
		int magic = r_read_le32 (magicbytes);
		switch (magic) {
		case MH_MAGIC_64:
			symbols_from_mach0 (ret, kext->mach0, bf, kext->range.offset, r_list_length (ret));
			symbols_from_stubs (ret, kernel_syms_by_addr, obj, bf, kext, r_list_length (ret));
			process_constructors (obj, kext->mach0, ret, kext->range.offset, false, R_K_CONSTRUCTOR_TO_SYMBOL, kext_short_name (kext));
			process_kmod_init_term (obj, kext, ret, &inits, &terms);

			break;
		default:
			eprintf ("Unknown sub-bin\n");
			break;
		}
	}

	R_FREE (inits);
	R_FREE (terms);

	sdb_ht_free (kernel_syms_by_addr);

	return ret;
}


static void r_kext_fill_text_range(RKext *kext) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (kext->mach0))) {
		return;
	}

	int i = 0;
	for (; !sections[i].last; i++) {
		if (strstr (sections[i].name, "__TEXT_EXEC.__text")) {
			kext->text_range.offset = sections[i].offset;
			kext->text_range.size = sections[i].size;
			kext->vaddr = sections[i].addr;
			break;
		}
	}

	R_FREE (sections);
}


static void r_rebase_info_populate(RRebaseInfo *info, RKernelCacheObj *obj) {
	struct section_t *sections = NULL;
	int i = 0;

	if (obj->rebase_info_populated) {
		return;
	}
	obj->rebase_info_populated = true;

	for (; i < info->n_ranges; i++) {
		if (info->ranges[i].size != UT64_MAX) {
			goto cleanup;
		} else if (sections == NULL) {
			if (!(sections = MACH0_(get_sections) (obj->mach0))) {
				return;
			}
		}
		info->ranges[i].offset = r_rebase_offset_to_paddr (obj, sections, info->ranges[i].offset);
		ut64 end = iterate_rebase_list (obj->cache_buf, info->multiplier, info->ranges[i].offset, NULL, NULL);
		if (end != UT64_MAX) {
			info->ranges[i].size = end - info->ranges[i].offset + 8;
		} else {
			info->ranges[i].size = 0;
		}
	}

cleanup:
	R_FREE (sections);
}


static ut64 r_rebase_offset_to_paddr(RKernelCacheObj *obj, struct section_t *sections, ut64 offset) {
	ut64 vaddr = obj->rebase_info->kernel_base + offset;
	int i = 0;
	for (; !sections[i].last; i++) {
		if (sections[i].addr <= vaddr && vaddr < (sections[i].addr + sections[i].vsize)) {
			return sections[i].offset + (vaddr - sections[i].addr);
		}
	}
	return offset;
}


static void rebase_buffer_fixup(RKernelCacheObj *kobj, ut64 off, RIODesc *fd, ut8 *buf, int count) {
	if (kobj->rebasing_buffer) {
		return;
	}
	kobj->rebasing_buffer = true;
	struct MACH0_(obj_t) *obj = kobj->mach0;
	ut64 eob = off + count;
	size_t i = 0;
	for (; i < obj->nsegs; i++) {
		if (!obj->chained_starts[i]) {
			continue;
		}
		ut64 page_size = obj->chained_starts[i]->page_size;
		ut64 start = obj->segs[i].fileoff;
		ut64 end = start + obj->segs[i].filesize;
		if (end >= off && start <= eob) {
			ut64 page_idx = (R_MAX (start, off) - start) / page_size;
			ut64 page_end_idx = (R_MIN (eob, end) - start) / page_size;
			for (; page_idx <= page_end_idx; page_idx++) {
				if (page_idx >= obj->chained_starts[i]->page_count) {
					break;
				}
				ut16 page_start = obj->chained_starts[i]->page_start[page_idx];
				if (page_start == DYLD_CHAINED_PTR_START_NONE) {
					continue;
				}
				ut64 cursor = start + page_idx * page_size + page_start;
				while (cursor < eob && cursor < end) {
					ut8 tmp[8];
					if (r_buf_read_at (obj->b, cursor, tmp, 8) != 8) {
						break;
					}
					ut64 raw_ptr = r_read_le64 (tmp);
					ut64 ptr_value = raw_ptr;
					ut64 delta = 0;
					ut64 stride = 8;
					if (obj->chained_starts[i]->pointer_format == DYLD_CHAINED_PTR_ARM64E) {
						bool is_auth = IS_PTR_AUTH (raw_ptr);
						bool is_bind = IS_PTR_BIND (raw_ptr);
						if (is_auth && is_bind) {
							struct dyld_chained_ptr_arm64e_auth_bind *p =
									(struct dyld_chained_ptr_arm64e_auth_bind *) &raw_ptr;
							delta = p->next;
						} else if (!is_auth && is_bind) {
							struct dyld_chained_ptr_arm64e_bind *p =
									(struct dyld_chained_ptr_arm64e_bind *) &raw_ptr;
							delta = p->next;
						} else if (is_auth && !is_bind) {
							struct dyld_chained_ptr_arm64e_auth_rebase *p =
									(struct dyld_chained_ptr_arm64e_auth_rebase *) &raw_ptr;
							delta = p->next;
							ptr_value = p->target + obj->baddr;
						} else {
							struct dyld_chained_ptr_arm64e_rebase *p =
									(struct dyld_chained_ptr_arm64e_rebase *) &raw_ptr;
							delta = p->next;
							ptr_value = ((ut64)p->high8 << 56) | p->target;
							ptr_value += obj->baddr;
						}
					} else if (obj->chained_starts[i]->pointer_format == DYLD_CHAINED_PTR_64_KERNEL_CACHE ||
							obj->chained_starts[i]->pointer_format == DYLD_CHAINED_PTR_ARM64E_KERNEL) {
						bool is_auth = IS_PTR_AUTH (raw_ptr);
						stride = 4;
						if (is_auth) {
							struct dyld_chained_ptr_arm64e_cache_auth_rebase *p =
									(struct dyld_chained_ptr_arm64e_cache_auth_rebase *) &raw_ptr;
							delta = p->next;
							ptr_value = p->target + obj->baddr;
						} else {
							struct dyld_chained_ptr_arm64e_cache_rebase *p =
									(struct dyld_chained_ptr_arm64e_cache_rebase *) &raw_ptr;
							delta = p->next;
							ptr_value = ((ut64)p->high8 << 56) | p->target;
							ptr_value += obj->baddr;
						}
					} else {
						eprintf ("Unsupported pointer format: %u\n", obj->chained_starts[i]->pointer_format);
					}
					ut64 in_buf = cursor - off;
					if (cursor >= off && cursor <= eob - 8) {
						r_write_le64 (&buf[in_buf], ptr_value);
					}
					cursor += delta * stride;
					if (!delta) {
						break;
					}
				}
			}
		}
	}
	kobj->rebasing_buffer = false;
}


static RList *kexts_from_load_commands(RKernelCacheObj *obj, RBinFile *bf) {
	RList *kexts = r_list_newf ((RListFree) &r_kext_free);
	if (!kexts) {
		return NULL;
	}

	ut32 i, ncmds = r_buf_read_le32_at (obj->cache_buf, 16);
	ut64 length = r_buf_size (obj->cache_buf);

	ut32 cursor = sizeof (struct MACH0_(mach_header));
	for (i = 0; i < ncmds && cursor < length; i++) {
		ut32 cmdtype = r_buf_read_le32_at (obj->cache_buf, cursor);
		ut32 cmdsize = r_buf_read_le32_at (obj->cache_buf, cursor + 4);
		if (!cmdsize || cmdsize + cursor < cursor) {
			break;
		}
		if (cmdtype != LC_KEXT) {
			cursor += cmdsize;
			continue;
		}

		ut64 vaddr = r_buf_read_le64_at (obj->cache_buf, cursor + 8);
		ut64 paddr = r_buf_read_le64_at (obj->cache_buf, cursor + 16);
		st32 padded_name_length = (st32)cmdsize - 32;
		if (padded_name_length <= 0 || cmdsize - 32 + cursor >= length) {
			cursor += cmdsize;
			continue;
		}

		char *padded_name = calloc (1, padded_name_length);
		if (!padded_name) {
			goto beach;
		}
		if (r_buf_read_at (obj->cache_buf, cursor + 32, (ut8 *)padded_name, padded_name_length)
				!= padded_name_length) {
			free (padded_name);
			goto early;
		}

		RKext *kext = R_NEW0 (RKext);
		if (!kext) {
			free (padded_name);
			goto beach;
		}

		kext->vaddr = vaddr;
		kext->range.offset = paddr;

		kext->mach0 = create_kext_shared_mach0 (obj, kext, bf);
		if (!kext->mach0) {
			free (padded_name);
			r_kext_free (kext);
			cursor += cmdsize;
			continue;
		}

		r_kext_fill_text_range (kext);
		kext->vaddr = K_PPTR (kext->vaddr);
		kext->pa2va_exec = obj->pa2va_exec;
		kext->pa2va_data = obj->pa2va_data;
		kext->name = strdup (padded_name);
		kext->own_name = true;
		free (padded_name);
		r_list_push (kexts, kext);

		cursor += cmdsize;
	}
early:
	return kexts;
beach:
	r_list_free (kexts);
	return NULL;
}


static ut64 p_ptr(ut64 decorated_addr, RKernelCacheObj *obj) {
	RParsedPointer ptr;
	r_parse_pointer (&ptr, decorated_addr, obj);
	return ptr.address;
}


static void destroy(RBinFile *bf) {
	r_kernel_cache_free ((RKernelCacheObj*) bf->o->bin_obj);
}


static RList *resolve_mig_subsystem(RKernelCacheObj *obj) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (obj->mach0))) {
		return NULL;
	}

	HtPP *mig_hash = NULL;
	RList *subsystem = NULL;
	ut8 *data_const = NULL;
	ut64 data_const_offset = 0, data_const_size = 0, data_const_vaddr = 0;
	ut64 text_exec_offset = 0, text_exec_size = 0, text_exec_vaddr = 0;
	int incomplete = 2;
	int i = 0;
	for (; !sections[i].last && incomplete > 0; i++) {
		if (strstr (sections[i].name, "__DATA_CONST.__const")) {
			data_const_offset = sections[i].offset;
			data_const_size = sections[i].size;
			data_const_vaddr = K_PPTR (sections[i].addr);
			incomplete--;
		}
		if (strstr (sections[i].name, "__TEXT_EXEC.__text")) {
			text_exec_offset = sections[i].offset;
			text_exec_size = sections[i].size;
			text_exec_vaddr = K_PPTR (sections[i].addr);
			incomplete--;
		}
	}

	if (!data_const_offset || !data_const_size || !data_const_vaddr ||
		!text_exec_offset || !text_exec_size || !text_exec_vaddr) {
		goto beach;
	}

	data_const = malloc (data_const_size);
	if (!data_const) {
		goto beach;
	}
	if (r_buf_read_at (obj->cache_buf, data_const_offset, data_const, data_const_size) < data_const_size) {
		goto beach;
	}

	subsystem = r_list_newf (r_bin_symbol_free);
	if (!subsystem) {
		goto beach;
	}

	mig_hash = mig_hash_new ();
	if (!mig_hash) {
		goto beach;
	}

	ut8 *cursor = data_const;
	ut8 *end = data_const + data_const_size;
	while (cursor < end) {
		ut64 subs_p = K_PPTR (r_read_le64 (cursor));
		if (subs_p < text_exec_vaddr || subs_p >= text_exec_vaddr + text_exec_size) {
			cursor += 8;
			continue;
		}
		ut32 subs_min_idx = r_read_le32 (cursor + 8);
		ut32 subs_max_idx = r_read_le32 (cursor + 12);
		if (subs_min_idx >= subs_max_idx || (subs_max_idx - subs_min_idx) > K_MIG_MAX_ROUTINES) {
			cursor += 16;
			continue;
		}

		ut32 n_routines = (subs_max_idx - subs_min_idx);
		ut64 *routines = (ut64 *) calloc (n_routines, sizeof (ut64));
		if (!routines) {
			goto beach;
		}
		ut8 *array_cursor = cursor + K_MIG_SUBSYSTEM_SIZE;
		ut8 *end_array = array_cursor + n_routines * K_MIG_ROUTINE_SIZE;
		bool is_consistent = true;
		int idx = 0;
		while (array_cursor < end_array) {
			ut64 should_be_null = r_read_le64 (array_cursor);
			if (should_be_null != 0) {
				is_consistent = false;
				break;
			}

			ut64 routine_p = K_PPTR (r_read_le64 (array_cursor + 8));
			if (routine_p != 0 && (routine_p < text_exec_vaddr || routine_p >= text_exec_vaddr + text_exec_size)) {
				is_consistent = false;
				break;
			}

			routines[idx++] = routine_p;
			array_cursor += K_MIG_ROUTINE_SIZE;
		}

		if (is_consistent) {
			for (idx = 0; idx < n_routines; idx++) {
				ut64 routine_p = routines[idx];
				if (!routine_p) {
					continue;
				}

				RBinSymbol *sym = R_NEW0 (RBinSymbol);
				if (!sym) {
					R_FREE (routines);
					goto beach;
				}

				int num = idx + subs_min_idx;
				bool found = false;
				r_strf_var (key, 32, "%d", num);
				const char *name = sdb_ht_find (mig_hash, key, &found);
				if (found && name && *name) {
					sym->name = r_str_newf ("mig.%d.%s", num, name);
				} else {
					sym->name = r_str_newf ("mig.%d", num);
				}

				sym->vaddr = routine_p;
				sym->paddr = sym->vaddr - text_exec_vaddr + text_exec_offset;
				sym->size = 0;
				sym->forwarder = "NONE";
				sym->bind = "GLOBAL";
				sym->type = "OBJECT";
				r_list_append (subsystem, sym);
			}

			cursor += K_MIG_SUBSYSTEM_SIZE + n_routines * K_MIG_ROUTINE_SIZE;
		} else {
			cursor += 8;
		}

		R_FREE (routines);
	}

	sdb_ht_free (mig_hash);
	R_FREE (data_const);
	R_FREE (sections);
	return subsystem;

beach:
	if (subsystem) {
		r_list_free (subsystem);
	}
	if (mig_hash) {
		sdb_ht_free (mig_hash);
	}
	R_FREE (data_const);
	R_FREE (sections);
	return NULL;
}


static bool load_buffer(RBinFile *bf, void **bin_obj, RBuffer *buf, ut64 loadaddr, Sdb *sdb) {
	RBuffer *fbuf = r_buf_ref (buf);
	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default) (&opts, bf);
	struct MACH0_(obj_t) *main_mach0 = MACH0_(new_buf) (fbuf, &opts);
	if (!main_mach0) {
		return false;
	}

	RRebaseInfo *rebase_info = r_rebase_info_new_from_mach0 (fbuf, main_mach0);
	RKernelCacheObj *obj = NULL;

	RPrelinkRange *prelink_range = get_prelink_info_range_from_mach0 (main_mach0);
	if (!prelink_range) {
		goto beach;
	}

	obj = R_NEW0 (RKernelCacheObj);
	if (!obj) {
		R_FREE (prelink_range);
		goto beach;
	}

	RCFValueDict *prelink_info = NULL;
	if (main_mach0->hdr.filetype != MH_FILESET && prelink_range->range.size) {
		prelink_info = r_cf_value_dict_parse (fbuf, prelink_range->range.offset,
				prelink_range->range.size, R_CF_OPTION_SKIP_NSDATA);
		if (!prelink_info) {
			R_FREE (prelink_range);
			R_FREE (obj);
			goto beach;
		}
	}

	if (!pending_bin_files) {
		pending_bin_files = r_list_new ();
		if (!pending_bin_files) {
			R_FREE (prelink_range);
			R_FREE (obj);
			R_FREE (prelink_info);
			goto beach;
		}
	}

	obj->mach0 = main_mach0;
	obj->rebase_info = rebase_info;
	obj->prelink_info = prelink_info;
	obj->cache_buf = fbuf;
	obj->pa2va_exec = prelink_range->pa2va_exec;
	obj->pa2va_data = prelink_range->pa2va_data;

	R_FREE (prelink_range);

	*bin_obj = obj;

	r_list_push (pending_bin_files, bf);

	if (rebase_info || main_mach0->chained_starts) {
		RIO *io = bf->rbin->iob.io;
		swizzle_io_read (obj, io);
	}

	return true;

beach:
	r_buf_free (fbuf);
	if (obj) {
		obj->cache_buf = NULL;
	}
	MACH0_(mach0_free) (main_mach0);
	return false;
}


static int kernelcache_io_read(RIO *io, RIODesc *fd, ut8 *buf, int count) {
	r_return_val_if_fail (io, -1);
	RCore *core = (RCore*) io->corebind.core;

	if (!fd || !core || !core->bin || !core->bin->binfiles) {
		return -1;
	}

	RKernelCacheObj *cache = NULL;
	RListIter *iter;
	RBinFile *bf;
	r_list_foreach (core->bin->binfiles, iter, bf) {
		if (bf->fd == fd->fd && bf->o && bf->o->bin_obj) {
			cache = bf->o->bin_obj;
			if (pending_bin_files) {
				RListIter *to_remove = r_list_contains (pending_bin_files, bf);
				if (to_remove) {
					r_list_delete (pending_bin_files, to_remove);
					if (r_list_empty (pending_bin_files)) {
						r_list_free (pending_bin_files);
						pending_bin_files = NULL;
					}
				}
			}
			break;
		}
	}

	if (!cache) {
		r_list_foreach (pending_bin_files, iter, bf) {
			if (bf->fd == fd->fd && bf->o) {
				cache = bf->o->bin_obj;
				break;
			}
		}
	}

	if (!cache || !cache->original_io_read || cache->rebasing_buffer) {
		if (cache) {
			if ((!cache->rebasing_buffer && fd->plugin->read == &kernelcache_io_read) ||
					(cache->rebasing_buffer && !cache->original_io_read)) {
				return -1;
			}
			if (cache->rebasing_buffer) {
				return cache->original_io_read (io, fd, buf, count);
			}
		}
		if (fd->plugin->read == kernelcache_io_read) {
			if (core->bin->verbose) {
				eprintf ("Avoid recursive reads\n");
			}
			return -1;
		}
		return fd->plugin->read (io, fd, buf, count);
	}

	if (cache->rebase_info) {
		r_rebase_info_populate (cache->rebase_info, cache);
	}

	static ut8 *internal_buffer = NULL;
	static int internal_buf_size = 0;
	if (count > internal_buf_size) {
		if (internal_buffer) {
			R_FREE (internal_buffer);
			internal_buffer = NULL;
		}
		internal_buffer = (ut8 *) malloc (count);
		internal_buf_size = count;
	}

	if (!cache->original_io_read) {
		return -1;
	}
	ut64 io_off = io->off;
	int result = cache->original_io_read (io, fd, internal_buffer, count);

	if (result == count) {
		if (cache->mach0->chained_starts) {
			rebase_buffer_fixup (cache, io_off, fd, internal_buffer, count);
		} else if (cache->rebase_info) {
			rebase_buffer (cache, io_off, fd, internal_buffer, count);
		}
		memcpy (buf, internal_buffer, result);
	}

	return result;
}


static HtPP *mig_hash_new(void) {
	HtPP *hash = sdb_ht_new ();
	if (!hash) {
		return NULL;
	}

	int i;
	for (i = 0; i < R_MIG_INDEX_LEN; i += 2) {
		const char *num = mig_index[i];
		const char *name = mig_index[i+1];
		sdb_ht_insert (hash, num, name);
	}

	return hash;
}


static RList *resolve_syscalls(RKernelCacheObj *obj, ut64 enosys_addr) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (obj->mach0))) {
		return NULL;
	}

	RList *syscalls = NULL;
	RSyscall *syscall = NULL;
	ut8 *data_const = NULL;
	ut64 data_const_offset = 0, data_const_size = 0, data_const_vaddr = 0;
	int i = 0;
	for (; !sections[i].last; i++) {
		if (strstr (sections[i].name, "__DATA_CONST.__const")) {
			data_const_offset = sections[i].offset;
			data_const_size = sections[i].size;
			data_const_vaddr = K_PPTR (sections[i].addr);
			break;
		}
	}

	if (!data_const_offset || !data_const_size || !data_const_vaddr) {
		goto beach;
	}

	data_const = malloc (data_const_size);
	if (!data_const) {
		goto beach;
	}
	if (r_buf_read_at (obj->cache_buf, data_const_offset, data_const, data_const_size) < data_const_size) {
		goto beach;
	}

	ut8 *cursor = data_const;
	ut8 *end = data_const + data_const_size;
	while (cursor < end) {
		ut64 test = r_read_le64 (cursor);
		if (test == enosys_addr) {
			break;
		}
		cursor += 8;
	}

	if (cursor >= end) {
		goto beach;
	}

	cursor -= 24;
	while (cursor >= data_const) {
		ut64 addr = r_read_le64 (cursor);
		ut64 x = r_read_le64 (cursor + 8);
		ut64 y = r_read_le64 (cursor + 16);

		if (IS_KERNEL_ADDR (addr) &&
			(x == 0 || IS_KERNEL_ADDR (x)) &&
			(y != 0 && !IS_KERNEL_ADDR (y))) {
			cursor -= 24;
			continue;
		}

		cursor += 24;
		break;
	}

	if (cursor < data_const) {
		goto beach;
	}

	syscalls = r_list_newf (r_bin_symbol_free);
	if (!syscalls) {
		goto beach;
	}

	syscall = r_syscall_new ();
	if (!syscall) {
		goto beach;
	}
	r_syscall_setup (syscall, "arm", 64, NULL, "ios");
	if (!syscall->db) {
		r_syscall_free (syscall);
		goto beach;
	}

	ut64 sysent_vaddr = cursor - data_const + data_const_vaddr;

	RBinSymbol *sym = R_NEW0 (RBinSymbol);
	if (!sym) {
		goto beach;
	}

	sym->name = r_str_newf ("sysent");
	sym->vaddr = sysent_vaddr;
	sym->paddr = cursor - data_const + data_const_offset;
	sym->size = 0;
	sym->forwarder = "NONE";
	sym->bind = "GLOBAL";
	sym->type = "OBJECT";
	r_list_append (syscalls, sym);

	i = 1;
	cursor += 24;
	int num_syscalls = sdb_count (syscall->db);
	while (cursor < end && i < num_syscalls) {
		ut64 addr = r_read_le64 (cursor);
		RSyscallItem *item = r_syscall_get (syscall, i, 0x80);
		if (item && item->name) {
			RBinSymbol *sym = R_NEW0 (RBinSymbol);
			if (!sym) {
				r_syscall_item_free (item);
				goto beach;
			}

			sym->name = r_str_newf ("syscall.%d.%s", i, item->name);
			sym->vaddr = addr;
			sym->paddr = addr;
			sym->size = 0;
			sym->forwarder = "NONE";
			sym->bind = "GLOBAL";
			sym->type = "FUNC";
			r_list_append (syscalls, sym);
		}
		r_syscall_item_free (item);
		cursor += 24;
		i++;
	}

	r_syscall_free (syscall);
	R_FREE (data_const);
	R_FREE (sections);
	return syscalls;

beach:
	r_syscall_free (syscall);
	if (syscalls) {
		r_list_free (syscalls);
	}
	R_FREE (data_const);
	R_FREE (sections);
	return NULL;
}


static void handle_data_sections(RBinSection *sect) {
	if (strstr (sect->name, "_cstring")) {
		sect->is_data = true;
	} else if (strstr (sect->name, "_os_log")) {
		sect->is_data = true;
	} else if (strstr (sect->name, "_objc_methname")) {
		sect->is_data = true;
	} else if (strstr (sect->name, "_objc_classname")) {
		sect->is_data = true;
	} else if (strstr (sect->name, "_objc_methtype")) {
		sect->is_data = true;
	}
}


static void ensure_kexts_initialized(RKernelCacheObj *obj, RBinFile *bf) {
	if (obj->kexts_initialized) {
		return;
	}
	obj->kexts_initialized = true;

	RList *kexts = NULL;

	if (obj->prelink_info) {
		kexts = filter_kexts (obj, bf);
	}

	if (kexts && !r_list_length (kexts)) {
		r_list_free (kexts);
		kexts = NULL;
	}

	if (!kexts) {
		kexts = kexts_from_load_commands (obj, bf);
	}

	if (kexts && !r_list_length (kexts)) {
		r_list_free (kexts);
		kexts = NULL;
	}

	if (!kexts) {
		kexts = carve_kexts (obj, bf);
	}

	obj->kexts = r_kext_index_new (kexts);

	if (kexts) {
		kexts->free = NULL;
		r_list_free (kexts);
	}
}


static void rebase_buffer(RKernelCacheObj *obj, ut64 off, RIODesc *fd, ut8 *buf, int count) {
	if (obj->rebasing_buffer || !buf) {
		return;
	}
	obj->rebasing_buffer = true;

	ut64 eob = off + count;
	int i = 0;
	RRebaseCtx ctx;

	ctx.off = off;
	ctx.eob = eob;
	ctx.buf = buf;
	ctx.count = count;
	ctx.obj = obj;

	for (; i < obj->rebase_info->n_ranges; i++) {
		ut64 start = obj->rebase_info->ranges[i].offset;
		ut64 end = start + obj->rebase_info->ranges[i].size;
		if (end >= off && start <= eob) {
			iterate_rebase_list (obj->cache_buf, obj->rebase_info->multiplier, start,
				(ROnRebaseFunc) on_rebase_pointer, &ctx);
		}
	}

	obj->rebasing_buffer = false;
}


static RList *filter_kexts(RKernelCacheObj *obj, RBinFile *bf) {
	RCFValueArray *kext_array = NULL;
	RListIter *iter;
	RCFKeyValue *item;
	r_list_foreach (obj->prelink_info->pairs, iter, item) {
		if (!strcmp (item->key, "_PrelinkInfoDictionary")) {
			kext_array = (RCFValueArray*) item->value;
			break;
		}
	}

	if (!kext_array) {
		return NULL;
	}

	RList *kexts = r_list_newf ((RListFree) &r_kext_free);
	if (!kexts) {
		return NULL;
	}

	bool is_sorted = true;
	RKext *prev_kext = NULL;
	RCFValueDict *kext_item;
	r_list_foreach (kext_array->values, iter, kext_item) {
		RKext *kext = R_NEW0 (RKext);
		if (!kext) {
			R_FREE (kexts);
			return NULL;
		}

		int kext_incomplete = 5;
		RListIter *internal_iter;
		r_list_foreach (kext_item->pairs, internal_iter, item) {
			if (!strcmp (item->key, "CFBundlePackageType")) {
				if (item->value->type != R_CF_STRING) {
					break;
				}
				RCFValueString *type = (RCFValueString*) item->value;
				if (strcmp (type->value, "KEXT")) {
					break;
				}
				kext_incomplete--;
			}

			if (!strcmp (item->key, "_PrelinkExecutableLoadAddr")) {
				if (item->value->type == R_CF_INTEGER) {
					kext_incomplete--;
					kext->vaddr = ((RCFValueInteger*) item->value)->value;
					kext->range.offset = kext->vaddr - obj->pa2va_exec;
				}
			}

			if (!strcmp (item->key, "_PrelinkExecutableSize")) {
				kext_incomplete--;
				if (item->value->type == R_CF_INTEGER) {
					kext->range.size = ((RCFValueInteger*) item->value)->value;
				} else {
					kext->range.size = 0;
				}
			}

			if (!strcmp (item->key, "_PrelinkKmodInfo")) {
				if (item->value->type == R_CF_INTEGER) {
					kext_incomplete--;
					kext->mod_info = ((RCFValueInteger*) item->value)->value;
					kext->mod_info -= obj->pa2va_data;
				}
			}

			if (!strcmp (item->key, "CFBundleIdentifier")) {
				if (item->value->type == R_CF_STRING) {
					kext_incomplete--;
					kext->name = ((RCFValueString*) item->value)->value;
				}
			}
		}

		if (kext_incomplete) {
			r_kext_free (kext);
			continue;
		}

		if (prev_kext && kext->vaddr < prev_kext->vaddr) {
			is_sorted = false;
		}
		prev_kext = kext;

		kext->mach0 = create_kext_mach0 (obj, kext, bf);
		if (!kext->mach0) {
			r_kext_free (kext);
			continue;
		}

		r_kext_fill_text_range (kext);

		r_list_push (kexts, kext);
	}

	if (!is_sorted) {
		eprintf ("SORTING KEXTs...\n");
		r_list_sort (kexts, kexts_sort_vaddr_func);
	}
	return kexts;
}


static void create_initterm_syms(RKext *kext, RList *ret, int type, ut64 *pointers) {
	int i = 0;
	int count = 0;
	for (; pointers[i]; i++) {
		ut64 func_vaddr = pointers[i];
		ut64 text_start = kext->vaddr;
		ut64 text_end = text_start + kext->text_range.size;

		if (text_start == text_end) {
			continue;
		}

		if (text_start > func_vaddr || func_vaddr >= text_end) {
			continue;
		}

		RBinSymbol *sym = R_NEW0 (RBinSymbol);
		if (!sym) {
			break;
		}

		sym->name = r_str_newf ("%s.%s.%d", kext_short_name (kext), (type == R_BIN_ENTRY_TYPE_INIT) ? "init" : "fini", count++);
		sym->vaddr = func_vaddr;
		sym->paddr = func_vaddr - kext->pa2va_exec;
		sym->size = 0;
		sym->forwarder = "NONE";
		sym->bind = "GLOBAL";
		sym->type = "FUNC";

		r_list_append (ret, sym);
	}
}


static void r_kernel_cache_free(RKernelCacheObj *obj) {
	if (!obj) {
		return;
	}

	if (obj->mach0) {
		MACH0_(mach0_free) (obj->mach0);
		obj->mach0 = NULL;
		obj->cache_buf = NULL;
	}

	if (obj->cache_buf) {
		r_buf_free (obj->cache_buf);
		obj->cache_buf = NULL;
	}

	if (obj->prelink_info) {
		r_cf_value_dict_free (obj->prelink_info);
		obj->prelink_info = NULL;
	}

	if (obj->kexts) {
		r_kext_index_free (obj->kexts);
		obj->kexts = NULL;
	}

	if (obj->rebase_info) {
		r_rebase_info_free (obj->rebase_info);
		obj->rebase_info = NULL;
	}

	R_FREE (obj);
}


static RList *carve_kexts(RKernelCacheObj *obj, RBinFile *bf) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (obj->mach0))) {
		return NULL;
	}

	ut64 pa2va_exec = 0;
	ut64 pa2va_data = 0;
	ut64 kmod_start = 0, kmod_end = 0;
	ut64 kmod_info = 0, kmod_info_end = 0;
	int incomplete = 4;
	RKmodInfo *all_infos = NULL;

	int i = 0;
	for (; !sections[i].last && incomplete > 0; i++) {
		if (strstr (sections[i].name, "__TEXT_EXEC.__text")) {
			pa2va_exec = sections[i].addr - sections[i].offset;
			incomplete--;
		}
		if (strstr (sections[i].name, "__DATA.__data")) {
			pa2va_data = sections[i].addr - sections[i].offset;
			incomplete--;
		}
		if (strstr (sections[i].name, "__PRELINK_INFO.__kmod_start")) {
			kmod_start = sections[i].offset;
			kmod_end = kmod_start + sections[i].size;
			incomplete--;
		}
		if (strstr (sections[i].name, "__PRELINK_INFO.__kmod_info")) {
			kmod_info = sections[i].offset;
			kmod_info_end = kmod_info + sections[i].size;
			incomplete--;
		}
	}

	R_FREE (sections);

	if (incomplete) {
		return NULL;
	}

	RList *kexts = r_list_newf ((RListFree) &r_kext_free);
	if (!kexts) {
		return NULL;
	}

	int n_kmod_info = (kmod_info_end - kmod_info) / 8;
	if (n_kmod_info == 0) {
		goto beach;
	}

	all_infos = R_NEWS0 (RKmodInfo, n_kmod_info);
	if (!all_infos) {
		goto beach;
	}

	ut8 bytes[8];
	int j = 0;
	for (; j < n_kmod_info; j++) {
		ut64 entry_offset = j * 8 + kmod_info;

		if (r_buf_read_at (obj->cache_buf, entry_offset, bytes, 8) < 8) {
			goto beach;
		}

		ut64 kmod_info_paddr = K_RPTR (bytes) - pa2va_data;

		ut64 field_name = kmod_info_paddr + 0x10;
		ut64 field_start = kmod_info_paddr + 0xb4;

		if (r_buf_read_at (obj->cache_buf, field_start, bytes, 8) < 8) {
			goto beach;
		}

		all_infos[j].start = K_RPTR (bytes);

		if (r_buf_read_at (obj->cache_buf, field_name, (ut8 *) all_infos[j].name, 0x40) < 0x40) {
			goto beach;
		}

		all_infos[j].name[0x40] = 0;
	}

	ut64 cursor = kmod_start;
	for(; cursor < kmod_end; cursor += 8) {
		ut8 bytes[8];
		if (r_buf_read_at (obj->cache_buf, cursor, bytes, 8) < 8) {
			goto beach;
		}

		RKext *kext = R_NEW0 (RKext);
		if (!kext) {
			goto beach;
		}

		kext->vaddr = K_RPTR (bytes);
		kext->range.offset = kext->vaddr - pa2va_exec;

		kext->mach0 = create_kext_mach0 (obj, kext, bf);
		if (!kext->mach0) {
			r_kext_free (kext);
			continue;
		}

		r_kext_fill_text_range (kext);
		kext->vaddr = K_PPTR (kext->vaddr);
		kext->pa2va_exec = pa2va_exec;
		kext->pa2va_data = pa2va_data;

		ut64 text_start = kext->vaddr;
		ut64 text_end = text_start + kext->text_range.size;

		if (text_start == text_end) {
			r_kext_free (kext);
			continue;
		}

		for (j = 0; j < n_kmod_info; j++) {
			if (text_start > all_infos[j].start || all_infos[j].start >= text_end) {
				continue;
			}

			kext->name = strdup (all_infos[j].name);
			kext->own_name = true;
			break;
		}

		if (!kext->name) {
			r_kext_free (kext);
			continue;
		}

		r_list_push (kexts, kext);
	}

	R_FREE (all_infos);
	return kexts;

beach:
	r_list_free (kexts);
	R_FREE (all_infos);
	return NULL;
}


static bool r_parse_pointer(RParsedPointer *ptr, ut64 decorated_addr, RKernelCacheObj *obj) {
	/*
	 * Logic taken from:
	 * https://github.com/Synacktiv/kernelcache-laundering/blob/master/ios12_kernel_cache_helper.py
	 */

	if ((decorated_addr & 0x4000000000000000LL) == 0 && obj->rebase_info) {
		if (decorated_addr & 0x8000000000000000LL) {
			ptr->address = obj->rebase_info->kernel_base + (decorated_addr & 0xFFFFFFFFLL);
		} else {
			ptr->address = ((decorated_addr << 13) & 0xFF00000000000000LL) | (decorated_addr & 0x7ffffffffffLL);
			if (decorated_addr & 0x40000000000LL) {
				ptr->address |= 0xfffc0000000000LL;
			}
		}
	} else {
		ptr->address = decorated_addr;
	}

	return true;
}


static void sections_from_mach0(RList *ret, struct MACH0_(obj_t) *mach0, RBinFile *bf, ut64 paddr, char *prefix, RKernelCacheObj *obj) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (mach0))) {
		return;
	}
	int i;
	for (i = 0; !sections[i].last; i++) {
		RBinSection *ptr;
		if (!(ptr = R_NEW0 (RBinSection))) {
			break;
		}
		if (prefix) {
			ptr->name = r_str_newf ("%s.%s", prefix, (char*)sections[i].name);
		} else {
			ptr->name = r_str_newf ("%s", (char*)sections[i].name);
		}
		if (strstr (ptr->name, "la_symbol_ptr")) {
			int len = sections[i].size / 8;
			ptr->format = r_str_newf ("Cd %d[%d]", 8, len);
		}
		handle_data_sections (ptr);
		ptr->size = sections[i].size;
		ptr->vsize = sections[i].vsize;
		ptr->paddr = sections[i].offset + bf->o->boffset + paddr;
		ptr->vaddr = K_PPTR (sections[i].addr);
		if (!ptr->vaddr) {
			ptr->vaddr = ptr->paddr;
		}
		ptr->perm = sections[i].perm;
		if (!ptr->perm && strstr (sections[i].name, "__TEXT_EXEC.__text")) {
			ptr->perm = 1 | 4;
		}
		r_list_append (ret, ptr);
	}
	free (sections);
}


static struct MACH0_(obj_t) *create_kext_mach0(RKernelCacheObj *obj, RKext *kext, RBinFile *bf) {
	RBuffer *buf = r_buf_new_slice (obj->cache_buf, kext->range.offset, r_buf_size (obj->cache_buf) - kext->range.offset);
	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default) (&opts, bf);
	opts.verbose = true;
	opts.header_at = 0;
	struct MACH0_(obj_t) *mach0 = MACH0_(new_buf) (buf, &opts);
	r_buf_free (buf);
	return mach0;
}


static void swizzle_io_read(RKernelCacheObj *obj, RIO *io) {
	r_return_if_fail (io && io->desc && io->desc->plugin);
	RIOPlugin *plugin = io->desc->plugin;
	obj->original_io_read = plugin->read;
	plugin->read = &kernelcache_io_read;
}


static void r_kext_free(RKext *kext) {
	if (!kext) {
		return;
	}

	if (kext->mach0) {
		MACH0_(mach0_free) (kext->mach0);
		kext->mach0 = NULL;
	}

	if (kext->own_name && kext->name) {
		R_FREE (kext->name);
		kext->name = NULL;
	}

	R_FREE (kext);
}


static void process_constructors(RKernelCacheObj *obj, struct MACH0_(obj_t) *mach0, RList *ret, ut64 paddr, bool is_first, int mode, const char *prefix) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (mach0))) {
		return;
	}
	int i, type;
	for (i = 0; !sections[i].last; i++) {
		if (sections[i].size == 0) {
			continue;
		}

		if (strstr (sections[i].name, "_mod_fini_func") || strstr (sections[i].name, "_mod_term_func")) {
			type  = R_BIN_ENTRY_TYPE_FINI;
		} else if (strstr (sections[i].name, "_mod_init_func")) {
			type  = is_first ? 0 : R_BIN_ENTRY_TYPE_INIT;
			is_first = false;
		} else {
			continue;
		}

		ut8 *buf = calloc (sections[i].size, 1);
		if (!buf) {
			break;
		}
		if (r_buf_read_at (obj->cache_buf, sections[i].offset + paddr, buf, sections[i].size) < sections[i].size) {
			free (buf);
			break;
		}
		int j;
		int count = 0;
		for (j = 0; j < sections[i].size; j += 8) {
			ut64 addr64 = K_RPTR (buf + j);
			ut64 paddr64 = sections[i].offset + paddr + j;
			if (mode == R_K_CONSTRUCTOR_TO_ENTRY) {
				RBinAddr *ba = newEntry (paddr64, addr64, type);
				r_list_append (ret, ba);
			} else if (mode == R_K_CONSTRUCTOR_TO_SYMBOL) {
				RBinSymbol *sym = R_NEW0 (RBinSymbol);
				if (!sym) {
					break;
				}

				sym->name = r_str_newf ("%s.%s.%d", prefix, (type == R_BIN_ENTRY_TYPE_INIT) ? "init" : "fini", count++);
				sym->vaddr = addr64;
				sym->paddr = paddr64;
				sym->size = 0;
				sym->forwarder = "NONE";
				sym->bind = "GLOBAL";
				sym->type = "FUNC";

				r_list_append (ret, sym);
			}
		}
		free (buf);
	}
	free (sections);
}


static RPrelinkRange *get_prelink_info_range_from_mach0(struct MACH0_(obj_t) *mach0) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (mach0))) {
		return NULL;
	}

	RPrelinkRange *prelink_range = R_NEW0 (RPrelinkRange);
	if (!prelink_range) {
		R_FREE (sections);
		return NULL;
	}

	int incomplete = 3;
	int i = 0;
	for (; !sections[i].last; i++) {
		if (strstr (sections[i].name, "__PRELINK_INFO.__info")) {
			prelink_range->range.offset = sections[i].offset;
			prelink_range->range.size = sections[i].size;
			if (!--incomplete) {
				break;
			}
		}

		if (strstr (sections[i].name, "__PRELINK_TEXT.__text")) {
			prelink_range->pa2va_exec = sections[i].addr - sections[i].offset;
			if (!--incomplete) {
				break;
			}
		}

		if (strstr (sections[i].name, "__PRELINK_DATA.__data")) {
			prelink_range->pa2va_data = sections[i].addr - sections[i].offset;
			if (!--incomplete) {
				break;
			}
		}
	}

	R_FREE (sections);

	if (incomplete == 1 && !prelink_range->pa2va_data) {
		struct MACH0_(segment_command) *seg;
		int nsegs = R_MIN (mach0->nsegs, 128);
		size_t i;
		for (i = 0; i < nsegs; i++) {
			seg = &mach0->segs[i];
			if (!strcmp (seg->segname, "__DATA")) {
				prelink_range->pa2va_data = seg->vmaddr - seg->fileoff;
				incomplete--;
				break;
			}
		}
	}

	if (incomplete) {
		R_FREE (prelink_range);
	}

	return prelink_range;
}


static RBinAddr *newEntry(ut64 haddr, ut64 vaddr, int type) {
	RBinAddr *ptr = R_NEW0 (RBinAddr);
	if (!ptr) {
		return NULL;
	}
	ptr->paddr = haddr;
	ptr->vaddr = vaddr;
	ptr->hpaddr = haddr;
	ptr->bits = 64;
	ptr->type = type;
	return ptr;
}


static int kexts_sort_vaddr_func(const void *a, const void *b) {
	RKext *A = (RKext *) a;
	RKext *B = (RKext *) b;
	int vaddr_compare = A->vaddr - B->vaddr;
	if (vaddr_compare == 0) {
		return A->text_range.size - B->text_range.size;
	}
	return vaddr_compare;
}


static const char *kext_short_name(RKext *kext) {
	const char *sn = strrchr (kext->name, '.');
	return sn ? sn + 1 : kext->name;
}


static ut64 r_ptr(ut8 *buf, RKernelCacheObj *obj) {
	ut64 decorated_addr = r_read_le64 (buf);
	return K_PPTR (decorated_addr);
}


static ut64 iterate_rebase_list(RBuffer *cache_buf, ut64 multiplier, ut64 start_offset, ROnRebaseFunc func, void *user_data) {
	ut8 bytes[8];
	ut64 cursor = start_offset;

	while (true) {
		if (r_buf_read_at (cache_buf, cursor, bytes, 8) < 8) {
			return UT64_MAX;
		}

		ut64 decorated_addr = r_read_le64 (bytes);

		if (func) {
			bool carry_on = func (cursor, decorated_addr, user_data);
			if (!carry_on) {
				break;
			}
		}

		ut64 delta = ((decorated_addr >> 51) & 0x7ff) * multiplier;
		if (delta == 0) {
			break;
		}
		cursor += delta;
	}

	return cursor;
}


static struct MACH0_(obj_t) *create_kext_shared_mach0(RKernelCacheObj *obj, RKext *kext, RBinFile *bf) {
	RBuffer *buf = r_buf_ref (obj->cache_buf);
	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default) (&opts, bf);
	opts.verbose = false;
	opts.header_at = kext->range.offset;
	struct MACH0_(obj_t) *mach0 = MACH0_(new_buf) (buf, &opts);
	r_buf_free (buf);
	return mach0;
}


static RKext *r_kext_index_vget(RKextIndex *index, ut64 vaddr) {
	int imid;
	int imin = 0;
	int imax = index->length - 1;

	while (imin < imax) {
		imid = (imin + imax) / 2;
		RKext *entry = index->entries[imid];
		if ((entry->vaddr + entry->text_range.size) <= vaddr || (entry->vaddr == vaddr && entry->text_range.size == 0)) {
			imin = imid + 1;
		} else {
			imax = imid;
		}
	}

	RKext *minEntry = index->entries[imin];
	if ((imax == imin) && (minEntry->vaddr <= vaddr) && ((minEntry->vaddr + minEntry->text_range.size) > vaddr)) {
		return minEntry;
	}
	return NULL;
}


static RRebaseInfo *r_rebase_info_new_from_mach0(RBuffer *cache_buf, struct MACH0_(obj_t) *mach0) {
	RFileRange *rebase_ranges = NULL;
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (mach0))) {
		return NULL;
	}

	ut64 starts_offset = 0, starts_size = 0;

	int i = 0;
	for (; !sections[i].last; i++) {
		if (strstr (sections[i].name, "__TEXT.__thread_starts")) {
			starts_offset = sections[i].offset;
			starts_size = sections[i].size;
			break;
		}
	}

	R_FREE (sections);

	ut64 kernel_base = 0;

	struct MACH0_(segment_command) *seg;
	int nsegs = R_MIN (mach0->nsegs, 128);
	for (i = 0; i < nsegs; i++) {
		char segname[17];
		seg = &mach0->segs[i];
		r_str_ncpy (segname, seg->segname, 17);
		if (!strncmp (segname, "__TEXT", 6) && segname[6] == '\0') {
			kernel_base = seg->vmaddr;
			break;
		}
	}

	if (starts_offset == 0 || starts_size == 0 || kernel_base == 0) {
		return NULL;
	}

	int n_starts = starts_size / 4;
	if (n_starts <= 1) {
		return NULL;
	}
	rebase_ranges = R_NEWS0 (RFileRange, n_starts - 1);
	if (rebase_ranges == NULL) {
		return NULL;
	}

	ut64 multiplier = 4;
	for (i = 0; i != n_starts; i++) {
		ut8 bytes[4];
		if (r_buf_read_at (cache_buf, starts_offset + i * 4, bytes, 4) < 4) {
			goto beach;
		}

		if (i == 0) {
			multiplier += 4 * (r_read_le32 (bytes) & 1);
			continue;
		}

		rebase_ranges[i - 1].offset = r_read_le32 (bytes);
		rebase_ranges[i - 1].size = UT64_MAX;
	}

	RRebaseInfo *rebase_info = R_NEW0 (RRebaseInfo);
	if (rebase_info == NULL) {
		goto beach;
	}
	rebase_info->ranges = rebase_ranges;
	rebase_info->n_ranges = n_starts - 1;
	rebase_info->multiplier = multiplier;
	rebase_info->kernel_base = kernel_base;

	return rebase_info;

beach:

	R_FREE (rebase_ranges);
	return NULL;
}


static bool on_rebase_pointer(ut64 offset, ut64 decorated_addr, RRebaseCtx *ctx) {
	if (offset < ctx->off) {
		return true;
	}
	if (offset >= ctx->eob) {
		return false;
	}
	ut64 in_buf = offset - ctx->off;
	if (in_buf >= ctx->count || (in_buf + 8) > ctx->count) {
		return false;
	}

	RParsedPointer ptr;
	r_parse_pointer (&ptr, decorated_addr, ctx->obj);

	r_write_le64 (&ctx->buf[in_buf], ptr.address);

	return true;
}


static RKextIndex *r_kext_index_new(RList *kexts) {
	if (!kexts) {
		return NULL;
	}

	int length = r_list_length (kexts);
	if (!length) {
		return NULL;
	}

	RKextIndex *index = R_NEW0 (RKextIndex);
	if (!index) {
		return NULL;
	}

	index->entries = malloc (length *sizeof(RKext*));
	if (!index->entries) {
		R_FREE (index);
		return NULL;
	}

	RListIter *iter;
	RKext *kext;
	int i = 0;
	r_list_foreach (kexts, iter, kext) {
		index->entries[i++] = kext;
	}
	index->length = i;

	return index;
}


static void symbols_from_stubs(RList *ret, HtPP *kernel_syms_by_addr, RKernelCacheObj *obj, RBinFile *bf, RKext *kext, int ordinal) {
	RStubsInfo *stubs_info = get_stubs_info(kext->mach0, kext->range.offset, obj);
	if (!stubs_info) {
		return;
	}
	ut64 stubs_cursor = stubs_info->stubs.offset;
	ut64 stubs_end = stubs_cursor + stubs_info->stubs.size;

	for (; stubs_cursor < stubs_end; stubs_cursor += 12) {
		ut8 arm64_code[8];
		if (r_buf_read_at (obj->cache_buf, stubs_cursor, arm64_code, 8) < 8) {
			break;
		}

		ut64 vaddr = stubs_cursor + obj->pa2va_exec;
		ut64 addr_in_got = extract_addr_from_code (arm64_code, vaddr);

		bool found = false;
		int level = 3;

		ut64 target_addr = UT64_MAX;

		while (!found && level-- > 0) {
			ut64 offset_in_got = addr_in_got - obj->pa2va_exec;
			ut64 addr;
			if (r_buf_read_at (obj->cache_buf, offset_in_got, (ut8*) &addr, 8) < 8) {
				break;
			}

			if (level == 2) {
				target_addr = addr;
			}

			r_strf_var (key, 32, "%"PFMT64x, addr);
			const char *name = sdb_ht_find (kernel_syms_by_addr, key, &found);

			if (found) {
				RBinSymbol *sym = R_NEW0 (RBinSymbol);
				if (!sym) {
					break;
				}
				sym->name = r_str_newf ("stub.%s", name);
				sym->vaddr = vaddr;
				sym->paddr = stubs_cursor;
				sym->size = 12;
				sym->forwarder = "NONE";
				sym->bind = "LOCAL";
				sym->type = "FUNC";
				sym->ordinal = ordinal ++;
				r_list_append (ret, sym);
				break;
			}

			addr_in_got = addr;
		}

		if (found || target_addr == UT64_MAX) {
			continue;
		}

		ensure_kexts_initialized (obj, bf);
		RKext *remote_kext = r_kext_index_vget (obj->kexts, target_addr);
		if (!remote_kext) {
			continue;
		}

		RBinSymbol *remote_sym = R_NEW0 (RBinSymbol);
		if (!remote_sym) {
			break;
		}

		remote_sym->name = r_str_newf ("exp.%s.0x%"PFMT64x, kext_short_name (remote_kext), target_addr);
		remote_sym->vaddr = target_addr;
		remote_sym->paddr = target_addr - obj->pa2va_exec;
		remote_sym->size = 0;
		remote_sym->forwarder = "NONE";
		remote_sym->bind = "GLOBAL";
		remote_sym->type = "FUNC";
		remote_sym->ordinal = ordinal ++;
		r_list_append (ret, remote_sym);

		RBinSymbol *local_sym = R_NEW0 (RBinSymbol);
		if (!local_sym) {
			break;
		}

		local_sym->name = r_str_newf ("stub.%s.0x%"PFMT64x, kext_short_name (remote_kext), target_addr);
		local_sym->vaddr = vaddr;
		local_sym->paddr = stubs_cursor;
		local_sym->size = 12;
		local_sym->forwarder = "NONE";
		local_sym->bind = "GLOBAL";
		local_sym->type = "FUNC";
		local_sym->ordinal = ordinal ++;
		r_list_append (ret, local_sym);
	}

	R_FREE (stubs_info);
}


static RBinInfo *info(RBinFile *bf) {
	RBinInfo *ret = NULL;
	bool big_endian = 0;
	if (!(ret = R_NEW0 (RBinInfo))) {
		return NULL;
	}
	ret->file = strdup (bf->file);
	ret->bclass = strdup ("kernelcache");
	ret->rclass = strdup ("ios");
	ret->os = strdup ("iOS");
	ret->arch = strdup ("arm"); // XXX
	ret->machine = strdup (ret->arch);
	ret->subsystem = strdup ("xnu");
	ret->type = strdup ("kernel-cache");
	ret->bits = 64;
	ret->has_va = true;
	ret->big_endian = big_endian;
	ret->dbg_info = 0;
	return ret;
}


static ut64 extract_addr_from_code(ut8 *arm64_code, ut64 vaddr) {
	ut64 addr = vaddr & ~0xfff;

	ut64 adrp = r_read_le32 (arm64_code);
	ut64 adrp_offset = ((adrp & 0x60000000) >> 29) | ((adrp & 0xffffe0) >> 3);
	addr += adrp_offset << 12;

	ut64 ldr = r_read_le32 (arm64_code + 4);
	addr += ((ldr & 0x3ffc00) >> 10) << ((ldr & 0xc0000000) >> 30);

	return addr;
}


static RStubsInfo *get_stubs_info(struct MACH0_(obj_t) *mach0, ut64 paddr, RKernelCacheObj *obj) {
	struct section_t *sections = NULL;
	if (!(sections = MACH0_(get_sections) (mach0))) {
		return NULL;
	}

	RStubsInfo *stubs_info = R_NEW0 (RStubsInfo);
	if (!stubs_info) {
		free (sections);
		return NULL;
	}

	int incomplete = 2;
	int i = 0;
	for (; !sections[i].last; i++) {
		if (strstr (sections[i].name, "__DATA_CONST.__got")) {
			stubs_info->got.offset = sections[i].offset + paddr;
			stubs_info->got.size = sections[i].size;
			stubs_info->got_addr = K_PPTR (sections[i].addr);
			if (!--incomplete) {
				break;
			}
		}

		if (strstr (sections[i].name, "__TEXT_EXEC.__stubs")) {
			stubs_info->stubs.offset = sections[i].offset + paddr;
			stubs_info->stubs.size = sections[i].size;
			if (!--incomplete) {
				break;
			}
		}
	}

	R_FREE (sections);

	if (incomplete) {
		R_FREE (stubs_info);
	}

	return stubs_info;
}


static void symbols_from_mach0(RList *ret, struct MACH0_(obj_t) *mach0, RBinFile *bf, ut64 paddr, int ordinal) {
	const struct symbol_t *symbols = MACH0_(get_symbols) (mach0);
	if (!symbols) {
		return;
	}
	int i;
	for (i = 0; !symbols[i].last; i++) {
		if (!symbols[i].name[0] || symbols[i].addr < 100) {
			continue;
		}
		RBinSymbol *sym = R_NEW0 (RBinSymbol);
		if (!sym) {
			break;
		}
		sym->name = strdup (symbols[i].name);
		sym->vaddr = symbols[i].addr;
		if (sym->name[0] == '_') {
			char *dn = r_bin_demangle (bf, sym->name, sym->name, sym->vaddr, false);
			if (dn) {
				sym->dname = dn;
				char *p = strchr (dn, '.');
				if (p) {
					if (IS_UPPER (sym->name[0])) {
						sym->classname = strdup (sym->name);
						sym->classname[p - sym->name] = 0;
					} else if (IS_UPPER (p[1])) {
						sym->classname = strdup (p + 1);
						p = strchr (sym->classname, '.');
						if (p) {
							*p = 0;
						}
					}
				}
			}
		}
		sym->forwarder = "NONE";
		sym->bind = (symbols[i].type == R_BIN_MACH0_SYMBOL_TYPE_LOCAL)? "LOCAL": "GLOBAL";
		sym->type = "FUNC";
		sym->paddr = symbols[i].offset + bf->o->boffset + paddr;
		sym->size = symbols[i].size;
		sym->ordinal = ordinal + i;
		r_list_append (ret, sym);
	}
}


fix_indent(void)
{
    if (p_paste)
	return;
# ifdef FEAT_LISP
    if (curbuf->b_p_lisp && curbuf->b_p_ai)
	fixthisline(get_lisp_indent);
# endif
# if defined(FEAT_LISP) && defined(FEAT_CINDENT)
    else
# endif
# ifdef FEAT_CINDENT
	if (cindent_on())
	    do_c_expr_indent();
# endif
}

patch 8.2.4359: crash when repeatedly using :retab

Problem:    crash when repeatedly using :retab.
Solution:   Bail out when the line is getting too long.


ex_retab(exarg_T *eap)
{
    linenr_T	lnum;
    int		got_tab = FALSE;
    long	num_spaces = 0;
    long	num_tabs;
    long	len;
    long	col;
    long	vcol;
    long	start_col = 0;		// For start of white-space string
    long	start_vcol = 0;		// For start of white-space string
    long	old_len;
    char_u	*ptr;
    char_u	*new_line = (char_u *)1; // init to non-NULL
    int		did_undo;		// called u_save for current line
#ifdef FEAT_VARTABS
    int		*new_vts_array = NULL;
    char_u	*new_ts_str;		// string value of tab argument
#else
    int		temp;
    int		new_ts;
#endif
    int		save_list;
    linenr_T	first_line = 0;		// first changed line
    linenr_T	last_line = 0;		// last changed line

    save_list = curwin->w_p_list;
    curwin->w_p_list = 0;	    // don't want list mode here

#ifdef FEAT_VARTABS
    new_ts_str = eap->arg;
    if (tabstop_set(eap->arg, &new_vts_array) == FAIL)
	return;
    while (vim_isdigit(*(eap->arg)) || *(eap->arg) == ',')
	++(eap->arg);

    // This ensures that either new_vts_array and new_ts_str are freshly
    // allocated, or new_vts_array points to an existing array and new_ts_str
    // is null.
    if (new_vts_array == NULL)
    {
	new_vts_array = curbuf->b_p_vts_array;
	new_ts_str = NULL;
    }
    else
	new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
#else
    ptr = eap->arg;
    new_ts = getdigits(&ptr);
    if (new_ts < 0 && *eap->arg == '-')
    {
	emsg(_(e_argument_must_be_positive));
	return;
    }
    if (new_ts < 0 || new_ts > TABSTOP_MAX)
    {
	semsg(_(e_invalid_argument_str), eap->arg);
	return;
    }
    if (new_ts == 0)
	new_ts = curbuf->b_p_ts;
#endif
    for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
    {
	ptr = ml_get(lnum);
	col = 0;
	vcol = 0;
	did_undo = FALSE;
	for (;;)
	{
	    if (VIM_ISWHITE(ptr[col]))
	    {
		if (!got_tab && num_spaces == 0)
		{
		    // First consecutive white-space
		    start_vcol = vcol;
		    start_col = col;
		}
		if (ptr[col] == ' ')
		    num_spaces++;
		else
		    got_tab = TRUE;
	    }
	    else
	    {
		if (got_tab || (eap->forceit && num_spaces > 1))
		{
		    // Retabulate this string of white-space

		    // len is virtual length of white string
		    len = num_spaces = vcol - start_vcol;
		    num_tabs = 0;
		    if (!curbuf->b_p_et)
		    {
#ifdef FEAT_VARTABS
			int t, s;

			tabstop_fromto(start_vcol, vcol,
					curbuf->b_p_ts, new_vts_array, &t, &s);
			num_tabs = t;
			num_spaces = s;
#else
			temp = new_ts - (start_vcol % new_ts);
			if (num_spaces >= temp)
			{
			    num_spaces -= temp;
			    num_tabs++;
			}
			num_tabs += num_spaces / new_ts;
			num_spaces -= (num_spaces / new_ts) * new_ts;
#endif
		    }
		    if (curbuf->b_p_et || got_tab ||
					(num_spaces + num_tabs < len))
		    {
			if (did_undo == FALSE)
			{
			    did_undo = TRUE;
			    if (u_save((linenr_T)(lnum - 1),
						(linenr_T)(lnum + 1)) == FAIL)
			    {
				new_line = NULL;	// flag out-of-memory
				break;
			    }
			}

			// len is actual number of white characters used
			len = num_spaces + num_tabs;
			old_len = (long)STRLEN(ptr);
			new_line = alloc(old_len - col + start_col + len + 1);
			if (new_line == NULL)
			    break;
			if (start_col > 0)
			    mch_memmove(new_line, ptr, (size_t)start_col);
			mch_memmove(new_line + start_col + len,
				      ptr + col, (size_t)(old_len - col + 1));
			ptr = new_line + start_col;
			for (col = 0; col < len; col++)
			    ptr[col] = (col < num_tabs) ? '\t' : ' ';
			if (ml_replace(lnum, new_line, FALSE) == OK)
			    // "new_line" may have been copied
			    new_line = curbuf->b_ml.ml_line_ptr;
			if (first_line == 0)
			    first_line = lnum;
			last_line = lnum;
			ptr = new_line;
			col = start_col + len;
		    }
		}
		got_tab = FALSE;
		num_spaces = 0;
	    }
	    if (ptr[col] == NUL)
		break;
	    vcol += chartabsize(ptr + col, (colnr_T)vcol);
	    if (vcol >= MAXCOL)
	    {
		emsg(_(e_resulting_text_too_long));
		break;
	    }
	    if (has_mbyte)
		col += (*mb_ptr2len)(ptr + col);
	    else
		++col;
	}
	if (new_line == NULL)		    // out of memory
	    break;
	line_breakcheck();
    }
    if (got_int)
	emsg(_(e_interrupted));

#ifdef FEAT_VARTABS
    // If a single value was given then it can be considered equal to
    // either the value of 'tabstop' or the value of 'vartabstop'.
    if (tabstop_count(curbuf->b_p_vts_array) == 0
	&& tabstop_count(new_vts_array) == 1
	&& curbuf->b_p_ts == tabstop_first(new_vts_array))
	; // not changed
    else if (tabstop_count(curbuf->b_p_vts_array) > 0
        && tabstop_eq(curbuf->b_p_vts_array, new_vts_array))
	; // not changed
    else
	redraw_curbuf_later(NOT_VALID);
#else
    if (curbuf->b_p_ts != new_ts)
	redraw_curbuf_later(NOT_VALID);
#endif
    if (first_line != 0)
	changed_lines(first_line, 0, last_line + 1, 0L);

    curwin->w_p_list = save_list;	// restore 'list'

#ifdef FEAT_VARTABS
    if (new_ts_str != NULL)		// set the new tabstop
    {
	// If 'vartabstop' is in use or if the value given to retab has more
	// than one tabstop then update 'vartabstop'.
	int *old_vts_ary = curbuf->b_p_vts_array;

	if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1)
	{
	    set_string_option_direct((char_u *)"vts", -1, new_ts_str,
							OPT_FREE|OPT_LOCAL, 0);
	    curbuf->b_p_vts_array = new_vts_array;
	    vim_free(old_vts_ary);
	}
	else
	{
	    // 'vartabstop' wasn't in use and a single value was given to
	    // retab then update 'tabstop'.
	    curbuf->b_p_ts = tabstop_first(new_vts_array);
	    vim_free(new_vts_array);
	}
	vim_free(new_ts_str);
    }
#else
    curbuf->b_p_ts = new_ts;
#endif
    coladvance(curwin->w_curswant);

    u_clearline();
}


briopt_check(win_T *wp)
{
    char_u	*p;
    int		bri_shift = 0;
    long	bri_min = 20;
    int		bri_sbr = FALSE;
    int		bri_list = 0;

    p = wp->w_p_briopt;
    while (*p != NUL)
    {
	if (STRNCMP(p, "shift:", 6) == 0
		 && ((p[6] == '-' && VIM_ISDIGIT(p[7])) || VIM_ISDIGIT(p[6])))
	{
	    p += 6;
	    bri_shift = getdigits(&p);
	}
	else if (STRNCMP(p, "min:", 4) == 0 && VIM_ISDIGIT(p[4]))
	{
	    p += 4;
	    bri_min = getdigits(&p);
	}
	else if (STRNCMP(p, "sbr", 3) == 0)
	{
	    p += 3;
	    bri_sbr = TRUE;
	}
	else if (STRNCMP(p, "list:", 5) == 0)
	{
	    p += 5;
	    bri_list = getdigits(&p);
	}
	if (*p != ',' && *p != NUL)
	    return FAIL;
	if (*p == ',')
	    ++p;
    }

    wp->w_briopt_shift = bri_shift;
    wp->w_briopt_min   = bri_min;
    wp->w_briopt_sbr   = bri_sbr;
    wp->w_briopt_list  = bri_list;

    return OK;
}


get_number_indent(linenr_T lnum)
{
    colnr_T	col;
    pos_T	pos;

    regmatch_T	regmatch;
    int		lead_len = 0;	// length of comment leader

    if (lnum > curbuf->b_ml.ml_line_count)
	return -1;
    pos.lnum = 0;

    // In format_lines() (i.e. not insert mode), fo+=q is needed too...
    if ((State & INSERT) || has_format_option(FO_Q_COMS))
	lead_len = get_leader_len(ml_get(lnum), NULL, FALSE, TRUE);

    regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC);
    if (regmatch.regprog != NULL)
    {
	regmatch.rm_ic = FALSE;

	// vim_regexec() expects a pointer to a line.  This lets us
	// start matching for the flp beyond any comment leader...
	if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, (colnr_T)0))
	{
	    pos.lnum = lnum;
	    pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum));
	    pos.coladd = 0;
	}
	vim_regfree(regmatch.regprog);
    }

    if (pos.lnum == 0 || *ml_get_pos(&pos) == NUL)
	return -1;
    getvcol(curwin, &pos, &col, NULL, NULL);
    return (int)col;
}


tabstop_start(colnr_T col, int ts, int *vts)
{
    int		tabcount;
    colnr_T	tabcol = 0;
    int		t;
    int         excess;

    if (vts == NULL || vts[0] == 0)
	return (col / ts) * ts;

    tabcount = vts[0];
    for (t = 1; t <= tabcount; ++t)
    {
	tabcol += vts[t];
	if (tabcol > col)
	    return tabcol - vts[t];
    }

    excess = tabcol % vts[tabcount];
    return excess + ((col - excess) / vts[tabcount]) * vts[tabcount];
}


get_breakindent_win(
    win_T	*wp,
    char_u	*line) // start of the line
{
    static int	    prev_indent = 0;	// cached indent value
    static long	    prev_ts     = 0L;	// cached tabstop value
    static char_u   *prev_line = NULL;	// cached pointer to line
    static varnumber_T prev_tick = 0;   // changedtick of cached value
# ifdef FEAT_VARTABS
    static int      *prev_vts = NULL;   // cached vartabs values
# endif
    static int      prev_list = 0;	// cached list value
    static int      prev_listopt = 0;	// cached w_p_briopt_list value
    // cached formatlistpat value
    static char_u   *prev_flp = NULL;
    int		    bri = 0;
    // window width minus window margin space, i.e. what rests for text
    const int	    eff_wwidth = wp->w_width
			    - ((wp->w_p_nu || wp->w_p_rnu)
				&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
						? number_width(wp) + 1 : 0);

    // used cached indent, unless
    // - line pointer changed
    // - 'tabstop' changed
    // - 'briopt_list changed' changed or
    // - 'formatlistpattern' changed
    if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
	    || prev_tick != CHANGEDTICK(wp->w_buffer)
	    || prev_listopt != wp->w_briopt_list
	    || (prev_flp == NULL
		|| (STRCMP(prev_flp, get_flp_value(wp->w_buffer)) != 0))
# ifdef FEAT_VARTABS
	    || prev_vts != wp->w_buffer->b_p_vts_array
# endif
	)
    {
	prev_line = line;
	prev_ts = wp->w_buffer->b_p_ts;
	prev_tick = CHANGEDTICK(wp->w_buffer);
# ifdef FEAT_VARTABS
	prev_vts = wp->w_buffer->b_p_vts_array;
	prev_indent = get_indent_str_vtab(line,
				     (int)wp->w_buffer->b_p_ts,
				    wp->w_buffer->b_p_vts_array, wp->w_p_list);
# else
	prev_indent = get_indent_str(line,
				     (int)wp->w_buffer->b_p_ts, wp->w_p_list);
# endif
	prev_listopt = wp->w_briopt_list;
	prev_list = 0;
	vim_free(prev_flp);
	prev_flp = vim_strsave(get_flp_value(wp->w_buffer));
	// add additional indent for numbered lists
	if (wp->w_briopt_list != 0)
	{
	    regmatch_T	    regmatch;

	    regmatch.regprog = vim_regcomp(prev_flp,
				   RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT);

	    if (regmatch.regprog != NULL)
	    {
		regmatch.rm_ic = FALSE;
		if (vim_regexec(&regmatch, line, 0))
		{
		    if (wp->w_briopt_list > 0)
			prev_list = wp->w_briopt_list;
		    else
			prev_list = (*regmatch.endp - *regmatch.startp);
		}
		vim_regfree(regmatch.regprog);
	    }
	}
    }
    bri = prev_indent + wp->w_briopt_shift;

    // Add offset for number column, if 'n' is in 'cpoptions'
    bri += win_col_off2(wp);

    // add additional indent for numbered lists
    if (wp->w_briopt_list != 0)
    {
	if (wp->w_briopt_list > 0)
	    bri += prev_list;
	else
	    bri = prev_list;
    }

    // indent minus the length of the showbreak string
    if (wp->w_briopt_sbr)
	bri -= vim_strsize(get_showbreak_value(wp));

    // never indent past left window margin
    if (bri < 0)
	bri = 0;

    // always leave at least bri_min characters on the left,
    // if text width is sufficient
    else if (bri > eff_wwidth - wp->w_briopt_min)
	bri = (eff_wwidth - wp->w_briopt_min < 0)
					   ? 0 : eff_wwidth - wp->w_briopt_min;

    return bri;
}


get_expr_indent(void)
{
    int		indent = -1;
    char_u	*inde_copy;
    pos_T	save_pos;
    colnr_T	save_curswant;
    int		save_set_curswant;
    int		save_State;
    int		use_sandbox = was_set_insecurely((char_u *)"indentexpr",
								   OPT_LOCAL);
    sctx_T	save_sctx = current_sctx;

    // Save and restore cursor position and curswant, in case it was changed
    // via :normal commands
    save_pos = curwin->w_cursor;
    save_curswant = curwin->w_curswant;
    save_set_curswant = curwin->w_set_curswant;
    set_vim_var_nr(VV_LNUM, curwin->w_cursor.lnum);
    if (use_sandbox)
	++sandbox;
    ++textwinlock;
    current_sctx = curbuf->b_p_script_ctx[BV_INDE];

    // Need to make a copy, the 'indentexpr' option could be changed while
    // evaluating it.
    inde_copy = vim_strsave(curbuf->b_p_inde);
    if (inde_copy != NULL)
    {
	indent = (int)eval_to_number(inde_copy);
	vim_free(inde_copy);
    }

    if (use_sandbox)
	--sandbox;
    --textwinlock;
    current_sctx = save_sctx;

    // Restore the cursor position so that 'indentexpr' doesn't need to.
    // Pretend to be in Insert mode, allow cursor past end of line for "o"
    // command.
    save_State = State;
    State = INSERT;
    curwin->w_cursor = save_pos;
    curwin->w_curswant = save_curswant;
    curwin->w_set_curswant = save_set_curswant;
    check_cursor();
    State = save_State;

    // Reset did_throw, unless 'debug' has "throw" and inside a try/catch.
    if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0))
    {
	handle_did_throw();
	did_throw = FALSE;
    }

    // If there is an error, just keep the current indent.
    if (indent < 0)
	indent = get_indent();

    return indent;
}


ins_try_si(int c)
{
    pos_T	*pos, old_pos;
    char_u	*ptr;
    int		i;
    int		temp;

    // do some very smart indenting when entering '{' or '}'
    if (((did_si || can_si_back) && c == '{') || (can_si && c == '}'))
    {
	// for '}' set indent equal to indent of line containing matching '{'
	if (c == '}' && (pos = findmatch(NULL, '{')) != NULL)
	{
	    old_pos = curwin->w_cursor;
	    // If the matching '{' has a ')' immediately before it (ignoring
	    // white-space), then line up with the start of the line
	    // containing the matching '(' if there is one.  This handles the
	    // case where an "if (..\n..) {" statement continues over multiple
	    // lines -- webb
	    ptr = ml_get(pos->lnum);
	    i = pos->col;
	    if (i > 0)		// skip blanks before '{'
		while (--i > 0 && VIM_ISWHITE(ptr[i]))
		    ;
	    curwin->w_cursor.lnum = pos->lnum;
	    curwin->w_cursor.col = i;
	    if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL)
		curwin->w_cursor = *pos;
	    i = get_indent();
	    curwin->w_cursor = old_pos;
	    if (State & VREPLACE_FLAG)
		change_indent(INDENT_SET, i, FALSE, NUL, TRUE);
	    else
		(void)set_indent(i, SIN_CHANGED);
	}
	else if (curwin->w_cursor.col > 0)
	{
	    // when inserting '{' after "O" reduce indent, but not
	    // more than indent of previous line
	    temp = TRUE;
	    if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1)
	    {
		old_pos = curwin->w_cursor;
		i = get_indent();
		while (curwin->w_cursor.lnum > 1)
		{
		    ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum)));

		    // ignore empty lines and lines starting with '#'.
		    if (*ptr != '#' && *ptr != NUL)
			break;
		}
		if (get_indent() >= i)
		    temp = FALSE;
		curwin->w_cursor = old_pos;
	    }
	    if (temp)
		shift_line(TRUE, FALSE, 1, TRUE);
	}
    }

    // set indent of '#' always to 0
    if (curwin->w_cursor.col > 0 && can_si && c == '#')
    {
	// remember current indent for next line
	old_indent = get_indent();
	(void)set_indent(0, SIN_CHANGED);
    }

    // Adjust ai_col, the char at this position can be deleted.
    if (ai_col > curwin->w_cursor.col)
	ai_col = curwin->w_cursor.col;
}


op_reindent(oparg_T *oap, int (*how)(void))
{
    long	i;
    char_u	*l;
    int		amount;
    linenr_T	first_changed = 0;
    linenr_T	last_changed = 0;
    linenr_T	start_lnum = curwin->w_cursor.lnum;

    // Don't even try when 'modifiable' is off.
    if (!curbuf->b_p_ma)
    {
	emsg(_(e_cannot_make_changes_modifiable_is_off));
	return;
    }

    for (i = oap->line_count; --i >= 0 && !got_int; )
    {
	// it's a slow thing to do, so give feedback so there's no worry that
	// the computer's just hung.

	if (i > 1
		&& (i % 50 == 0 || i == oap->line_count - 1)
		&& oap->line_count > p_report)
	    smsg(_("%ld lines to indent... "), i);

	// Be vi-compatible: For lisp indenting the first line is not
	// indented, unless there is only one line.
# ifdef FEAT_LISP
	if (i != oap->line_count - 1 || oap->line_count == 1
						    || how != get_lisp_indent)
# endif
	{
	    l = skipwhite(ml_get_curline());
	    if (*l == NUL)		    // empty or blank line
		amount = 0;
	    else
		amount = how();		    // get the indent for this line

	    if (amount >= 0 && set_indent(amount, SIN_UNDO))
	    {
		// did change the indent, call changed_lines() later
		if (first_changed == 0)
		    first_changed = curwin->w_cursor.lnum;
		last_changed = curwin->w_cursor.lnum;
	    }
	}
	++curwin->w_cursor.lnum;
	curwin->w_cursor.col = 0;  // make sure it's valid
    }

    // put cursor on first non-blank of indented line
    curwin->w_cursor.lnum = start_lnum;
    beginline(BL_SOL | BL_FIX);

    // Mark changed lines so that they will be redrawn.  When Visual
    // highlighting was present, need to continue until the last line.  When
    // there is no change still need to remove the Visual highlighting.
    if (last_changed != 0)
	changed_lines(first_changed, 0,
		oap->is_VIsual ? start_lnum + oap->line_count :
		last_changed + 1, 0L);
    else if (oap->is_VIsual)
	redraw_curbuf_later(INVERTED);

    if (oap->line_count > p_report)
    {
	i = oap->line_count - (i + 1);
	smsg(NGETTEXT("%ld line indented ",
						 "%ld lines indented ", i), i);
    }
    if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
    {
	// set '[ and '] marks
	curbuf->b_op_start = oap->start;
	curbuf->b_op_end = oap->end;
    }
}


f_lispindent(typval_T *argvars UNUSED, typval_T *rettv)
{
# ifdef FEAT_LISP
    pos_T	pos;
    linenr_T	lnum;

    if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
	return;

    pos = curwin->w_cursor;
    lnum = tv_get_lnum(argvars);
    if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count)
    {
	curwin->w_cursor.lnum = lnum;
	rettv->vval.v_number = get_lisp_indent();
	curwin->w_cursor = pos;
    }
    else if (in_vim9script())
	semsg(_(e_invalid_line_number_nr), lnum);
    else
# endif
	rettv->vval.v_number = -1;
}


preprocs_left(void)
{
    return
# ifdef FEAT_SMARTINDENT
#  ifdef FEAT_CINDENT
	(curbuf->b_p_si && !curbuf->b_p_cin) ||
#  else
	curbuf->b_p_si
#  endif
# endif
# ifdef FEAT_CINDENT
	(curbuf->b_p_cin && in_cinkeys('#', ' ', TRUE)
					   && curbuf->b_ind_hash_comment == 0)
# endif
	;
}


d_lite_step(int argc, VALUE *argv, VALUE self)
{
    VALUE limit, step, date;
    int c;

    rb_scan_args(argc, argv, "11", &limit, &step);

    if (argc < 2)
	step = INT2FIX(1);

#if 0
    if (f_zero_p(step))
	rb_raise(rb_eArgError, "step can't be 0");
#endif

    RETURN_ENUMERATOR(self, argc, argv);

    date = self;
    c = f_cmp(step, INT2FIX(0));
    if (c < 0) {
	while (FIX2INT(d_lite_cmp(date, limit)) >= 0) {
	    rb_yield(date);
	    date = d_lite_plus(date, step);
	}
    }
    else if (c == 0) {
	while (1)
	    rb_yield(date);
    }
    else /* if (c > 0) */ {
	while (FIX2INT(d_lite_cmp(date, limit)) <= 0) {
	    rb_yield(date);
	    date = d_lite_plus(date, step);
	}
    }
    return self;
}

`Date._<format>(nil)` should return an empty Hash

Fix: https://github.com/ruby/date/issues/39

This is how versions previous to 3.2.1 behaved and Active Support
currently rely on this behavior.

https://github.com/rails/rails/blob/90357af08048ef5076730505f6e7b14a81f33d0c/activesupport/lib/active_support/values/time_zone.rb#L383-L384

Any Rails application upgrading to date `3.2.1` might run into unexpected errors.


date_s__strptime(int argc, VALUE *argv, VALUE klass)
{
    return date_s__strptime_internal(argc, argv, klass, "%F");
}


c_valid_civil_p(int y, int m, int d, double sg,
		int *rm, int *rd, int *rjd, int *ns)
{
    int ry;

    if (m < 0)
	m += 13;
    if (d < 0) {
	if (!c_find_ldom(y, m, sg, rjd, ns))
	    return 0;
	c_jd_to_civil(*rjd + d + 1, sg, &ry, rm, rd);
	if (ry != y || *rm != m)
	    return 0;
	d = *rd;
    }
    c_civil_to_jd(y, m, d, sg, rjd, ns);
    c_jd_to_civil(*rjd, sg, &ry, rm, rd);
    if (ry != y || *rm != m || *rd != d)
	return 0;
    return 1;
}


f_gt_p(VALUE x, VALUE y)
{
    if (FIXNUM_P(x) && FIXNUM_P(y))
	return f_boolcast(FIX2LONG(x) > FIX2LONG(y));
    return rb_funcall(x, '>', 1, y);
}


m_julian_p(union DateData *x)
{
    int jd;
    double sg;

    if (simple_dat_p(x)) {
	get_s_jd(x);
	jd = x->s.jd;
	sg = s_virtual_sg(x);
    }
    else {
	get_c_jd(x);
	jd = x->c.jd;
	sg = c_virtual_sg(x);
    }
    if (isinf(sg))
	return sg == positive_inf;
    return jd < sg;
}


date_s_julian_leap_p(VALUE klass, VALUE y)
{
    VALUE nth;
    int ry;

    check_numeric(y, "year");
    decode_year(y, +1, &nth, &ry);
    return f_boolcast(c_julian_leap_p(ry));
}


tmx_m_secs(union DateData *x)
{
    VALUE s;
    int df;

    s = day_to_sec(f_sub(m_real_jd(x),
			 UNIX_EPOCH_IN_CJD));
    if (simple_dat_p(x))
	return s;
    df = m_df(x);
    if (df)
	s = f_add(s, INT2FIX(df));
    return s;
}


get_c_time(union DateData *x)
{
    assert(complex_dat_p(x));
    if (!have_time_p(x)) {
#ifndef USE_PACK
	int r;
	assert(have_df_p(x));
	r = df_utc_to_local(x->c.df, x->c.of);
	df_to_time(r, &x->c.hour, &x->c.min, &x->c.sec);
	x->c.flags |= HAVE_TIME;
#else
	int r, m, d, h, min, s;

	assert(have_df_p(x));
	m = EX_MON(x->c.pc);
	d = EX_MDAY(x->c.pc);
	r = df_utc_to_local(x->c.df, x->c.of);
	df_to_time(r, &h, &min, &s);
	x->c.pc = PACK5(m, d, h, min, s);
	x->c.flags |= HAVE_TIME;
#endif
    }
}


m_proleptic_julian_p(union DateData *x)
{
    double sg;

    sg = m_sg(x);
    if (isinf(sg) && sg > 0)
	return 1;
    return 0;
}


d_lite_zero(VALUE x)
{
    return INT2FIX(0);
}


k_numeric_p(VALUE x)
{
    return f_kind_of_p(x, rb_cNumeric);
}


valid_weeknum_p(VALUE y, int w, int d, int f, double sg,
		VALUE *nth, int *ry,
		int *rw, int *rd, int *rjd,
		int *ns)
{
    double style = guess_style(y, sg);
    int r;

    if (style == 0) {
	int jd;

	r = c_valid_weeknum_p(FIX2INT(y), w, d, f, sg, rw, rd, &jd, ns);
	if (!r)
	    return 0;
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	r = c_valid_weeknum_p(*ry, w, d, f, style, rw, rd, rjd, ns);
    }
    return r;
}


date_s__load(VALUE klass, VALUE s)
{
    VALUE a, obj;

    a = rb_marshal_load(s);
    obj = d_lite_s_alloc(klass);
    return d_lite_marshal_load(obj, a);
}


sec_to_ms(VALUE s)
{
    if (safe_mul_p(s, SECOND_IN_MILLISECONDS))
	return LONG2FIX(FIX2LONG(s) * SECOND_IN_MILLISECONDS);
    return f_mul(s, INT2FIX(SECOND_IN_MILLISECONDS));
}


decode_day(VALUE d, VALUE *jd, VALUE *df, VALUE *sf)
{
    VALUE f;

    *jd = div_day(d, &f);
    *df = div_df(f, &f);
    *sf = sec_to_ns(f);
}


day_to_ns(VALUE d)
{
    return f_mul(d, day_in_nanoseconds);
}


m_proleptic_gregorian_p(union DateData *x)
{
    double sg;

    sg = m_sg(x);
    if (isinf(sg) && sg < 0)
	return 1;
    return 0;
}


dup_obj_with_new_offset(VALUE obj, int of)
{
    volatile VALUE dup = dup_obj_as_complex(obj);
    {
	get_d1(dup);
	set_of(dat, of);
    }
    return dup;
}


d_lite_memsize(const void *ptr)
{
    const union DateData *dat = ptr;
    return complex_dat_p(dat) ? sizeof(struct ComplexDateData) : sizeof(struct SimpleDateData);
}


d_lite_gregorian_p(VALUE self)
{
    get_d1(self);
    return f_boolcast(m_gregorian_p(dat));
}


iso8601_timediv(VALUE self, long n)
{
    static const char timefmt[] = "T%H:%M:%S";
    static const char zone[] = "%:z";
    char fmt[sizeof(timefmt) + sizeof(zone) + rb_strlen_lit(".%N") +
	     DECIMAL_SIZE_OF_LONG];
    char *p = fmt;

    memcpy(p, timefmt, sizeof(timefmt)-1);
    p += sizeof(timefmt)-1;
    if (n > 0) p += snprintf(p, fmt+sizeof(fmt)-p, ".%%%ldN", n);
    memcpy(p, zone, sizeof(zone));
    return strftimev(fmt, self, set_tmx);
}


commercial_to_jd(VALUE y, int w, int d, double sg,
		 VALUE *nth, int *ry,
		 int *rjd,
		 int *ns)
{
    double style = guess_style(y, sg);

    if (style == 0) {
	int jd;

	c_commercial_to_jd(FIX2INT(y), w, d, sg, &jd, ns);
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	c_commercial_to_jd(*ry, w, d, style, rjd, ns);
    }
}


d_lite_sec(VALUE self)
{
    get_d1(self);
    return INT2FIX(m_sec(dat));
}


date_strftime_internal(int argc, VALUE *argv, VALUE self,
		       const char *default_fmt,
		       void (*func)(VALUE, struct tmx *))
{
    VALUE vfmt;
    const char *fmt;
    long len;
    char buffer[SMALLBUF], *buf = buffer;
    struct tmx tmx;
    VALUE str;

    rb_scan_args(argc, argv, "01", &vfmt);

    if (argc < 1)
	vfmt = rb_usascii_str_new2(default_fmt);
    else {
	StringValue(vfmt);
	if (!rb_enc_str_asciicompat_p(vfmt)) {
	    rb_raise(rb_eArgError,
		     "format should have ASCII compatible encoding");
	}
    }
    fmt = RSTRING_PTR(vfmt);
    len = RSTRING_LEN(vfmt);
    (*func)(self, &tmx);
    if (memchr(fmt, '\0', len)) {
	/* Ruby string may contain \0's. */
	const char *p = fmt, *pe = fmt + len;

	str = rb_str_new(0, 0);
	while (p < pe) {
	    len = date_strftime_alloc(&buf, p, &tmx);
	    rb_str_cat(str, buf, len);
	    p += strlen(p);
	    if (buf != buffer) {
		xfree(buf);
		buf = buffer;
	    }
	    for (fmt = p; p < pe && !*p; ++p);
	    if (p > fmt) rb_str_cat(str, fmt, p - fmt);
	}
	rb_enc_copy(str, vfmt);
	return str;
    }
    else
	len = date_strftime_alloc(&buf, fmt, &tmx);

    str = rb_str_new(buf, len);
    if (buf != buffer) xfree(buf);
    rb_enc_copy(str, vfmt);
    return str;
}


test_commercial(int from, int to, double sg)
{
    int j;

    fprintf(stderr, "test_commercial: %d...%d (%d) - %.0f\n",
	    from, to, to - from, sg);
    for (j = from; j <= to; j++) {
	int y, w, d, rj, ns;

	c_jd_to_commercial(j, sg, &y, &w, &d);
	c_commercial_to_jd(y, w, d, sg, &rj, &ns);
	if (j != rj) {
	    fprintf(stderr, "%d != %d\n", j, rj);
	    return 0;
	}
    }
    return 1;
}


time_to_df(int h, int min, int s)
{
    return h * HOUR_IN_SECONDS + min * MINUTE_IN_SECONDS + s;
}


d_lite_wnum1(VALUE self)
{
    get_d1(self);
    return INT2FIX(m_wnum1(dat));
}


datetime_s_commercial(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vw, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret;
    int w, d, h, min, s, rof;
    double sg;

    rb_scan_args(argc, argv, "08", &vy, &vw, &vd, &vh, &vmin, &vs, &vof, &vsg);

    y = INT2FIX(-4712);
    w = 1;
    d = 1;

    h = min = s = 0;
    fr2 = INT2FIX(0);
    rof = 0;
    sg = DEFAULT_SG;

    switch (argc) {
      case 8:
	val2sg(vsg, sg);
      case 7:
	val2off(vof, rof);
      case 6:
        check_numeric(vs, "second");
	num2int_with_frac(s, positive_inf);
      case 5:
        check_numeric(vmin, "minute");
	num2int_with_frac(min, 5);
      case 4:
        check_numeric(vh, "hour");
	num2int_with_frac(h, 4);
      case 3:
        check_numeric(vd, "cwday");
	num2int_with_frac(d, 3);
      case 2:
        check_numeric(vw, "cweek");
	w = NUM2INT(vw);
      case 1:
        check_numeric(vy, "year");
	y = vy;
    }

    {
	VALUE nth;
	int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns;

	if (!valid_commercial_p(y, w, d, sg,
				&nth, &ry,
				&rw, &rd, &rjd,
				&ns))
	    rb_raise(eDateError, "invalid date");
	if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs))
	    rb_raise(eDateError, "invalid date");
	canon24oc();

	rjd2 = jd_local_to_utc(rjd,
			       time_to_df(rh, rmin, rs),
			       rof);

	ret = d_complex_new_internal(klass,
				     nth, rjd2,
				     0, INT2FIX(0),
				     rof, sg,
				     0, 0, 0,
				     rh, rmin, rs,
				     HAVE_JD | HAVE_TIME);
    }
    add_frac();
    return ret;
}


canon(VALUE x)
{
    if (RB_TYPE_P(x, T_RATIONAL)) {
	VALUE den = rb_rational_den(x);
	if (FIXNUM_P(den) && FIX2LONG(den) == 1)
	    return rb_rational_num(x);
    }
    return x;
}


valid_ordinal_p(VALUE y, int d, double sg,
		VALUE *nth, int *ry,
		int *rd, int *rjd,
		int *ns)
{
    double style = guess_style(y, sg);
    int r;

    if (style == 0) {
	int jd;

	r = c_valid_ordinal_p(FIX2INT(y), d, sg, rd, &jd, ns);
	if (!r)
	    return 0;
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	r = c_valid_ordinal_p(*ry, d, style, rd, rjd, ns);
    }
    return r;
}


old_to_new(VALUE ajd, VALUE of, VALUE sg,
	   VALUE *rnth, int *rjd, int *rdf, VALUE *rsf,
	   int *rof, double *rsg)
{
    VALUE jd, df, sf, of2, t;

    decode_day(f_add(ajd, half_days_in_day),
	       &jd, &df, &sf);
    t = day_to_sec(of);
    of2 = f_round(t);

    if (!f_eqeq_p(of2, t))
	rb_warning("fraction of offset is ignored");

    decode_jd(jd, rnth, rjd);

    *rdf = NUM2INT(df);
    *rsf = sf;
    *rof = NUM2INT(of2);
    *rsg = NUM2DBL(sg);

    if (*rdf < 0 || *rdf >= DAY_IN_SECONDS)
	rb_raise(eDateError, "invalid day fraction");

    if (f_lt_p(*rsf, INT2FIX(0)) ||
	f_ge_p(*rsf, INT2FIX(SECOND_IN_NANOSECONDS)))

    if (*rof < -DAY_IN_SECONDS || *rof > DAY_IN_SECONDS) {
	*rof = 0;
	rb_warning("invalid offset is ignored");
    }

    if (!c_valid_start_p(*rsg)) {
	*rsg = DEFAULT_SG;
	rb_warning("invalid start is ignored");
    }
}


nth_kday_to_jd(VALUE y, int m, int n, int k, double sg,
	       VALUE *nth, int *ry,
	       int *rjd,
	       int *ns)
{
    double style = guess_style(y, sg);

    if (style == 0) {
	int jd;

	c_nth_kday_to_jd(FIX2INT(y), m, n, k, sg, &jd, ns);
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	c_nth_kday_to_jd(*ry, m, n, k, style, rjd, ns);
    }
}


datetime_s__strptime(int argc, VALUE *argv, VALUE klass)
{
    return date_s__strptime_internal(argc, argv, klass, "%FT%T%z");
}


cmp_gen(VALUE self, VALUE other)
{
    get_d1(self);

    if (k_numeric_p(other))
	return INT2FIX(f_cmp(m_ajd(dat), other));
    else if (k_date_p(other))
	return INT2FIX(f_cmp(m_ajd(dat), f_ajd(other)));
    return rb_num_coerce_cmp(self, other, id_cmp);
}


get_limit(VALUE opt)
{
    if (!NIL_P(opt)) {
        VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit")));
        if (NIL_P(limit)) return SIZE_MAX;
        return NUM2SIZET(limit);
    }
    return 128;
}


localtime_r(const time_t *t, struct tm *tm)
{
    auto struct tm *tmp = localtime(t);
    if (tmp)
	*tm = *tmp;
    return tmp;
}


m_sf_in_day(union DateData *x)
{
    return ns_to_day(m_sf(x));
}


date_s__jisx0301(int argc, VALUE *argv, VALUE klass)
{
    VALUE str, opt;

    rb_scan_args(argc, argv, "1:", &str, &opt);
    check_limit(str, opt);

    return date__jisx0301(str);
}


d_lite_marshal_dump_old(VALUE self)
{
    VALUE a;

    get_d1(self);

    a = rb_ary_new3(3,
		    m_ajd(dat),
		    m_of_in_day(dat),
		    DBL2NUM(m_sg(dat)));

    if (FL_TEST(self, FL_EXIVAR)) {
	rb_copy_generic_ivar(a, self);
	FL_SET(a, FL_EXIVAR);
    }

    return a;
}


ms_to_sec(VALUE m)
{
    if (FIXNUM_P(m))
	return rb_rational_new2(m, INT2FIX(SECOND_IN_MILLISECONDS));
    return f_quo(m, INT2FIX(SECOND_IN_MILLISECONDS));
}


datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vm, vn, vk, vh, vmin, vs, vof, vsg, y, fr, fr2, ret;
    int m, n, k, h, min, s, rof;
    double sg;

    rb_scan_args(argc, argv, "09", &vy, &vm, &vn, &vk,
		 &vh, &vmin, &vs, &vof, &vsg);

    y = INT2FIX(-4712);
    m = 1;
    n = 1;
    k = 1;

    h = min = s = 0;
    fr2 = INT2FIX(0);
    rof = 0;
    sg = DEFAULT_SG;

    switch (argc) {
      case 9:
	val2sg(vsg, sg);
      case 8:
	val2off(vof, rof);
      case 7:
	num2int_with_frac(s, positive_inf);
      case 6:
	num2int_with_frac(min, 6);
      case 5:
	num2int_with_frac(h, 5);
      case 4:
	num2int_with_frac(k, 4);
      case 3:
	n = NUM2INT(vn);
      case 2:
	m = NUM2INT(vm);
      case 1:
	y = vy;
    }

    {
	VALUE nth;
	int ry, rm, rn, rk, rh, rmin, rs, rjd, rjd2, ns;

	if (!valid_nth_kday_p(y, m, n, k, sg,
			      &nth, &ry,
			      &rm, &rn, &rk, &rjd,
			      &ns))
	    rb_raise(eDateError, "invalid date");
	if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs))
	    rb_raise(eDateError, "invalid date");
	canon24oc();

	rjd2 = jd_local_to_utc(rjd,
			       time_to_df(rh, rmin, rs),
			       rof);
	ret = d_complex_new_internal(klass,
				     nth, rjd2,
				     0, INT2FIX(0),
				     rof, sg,
				     0, 0, 0,
				     rh, rmin, rs,
				     HAVE_JD | HAVE_TIME);
    }
    add_frac();
    return ret;
}


date_s_nth_kday(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vm, vn, vk, vsg, y, fr, fr2, ret;
    int m, n, k;
    double sg;

    rb_scan_args(argc, argv, "05", &vy, &vm, &vn, &vk, &vsg);

    y = INT2FIX(-4712);
    m = 1;
    n = 1;
    k = 1;
    fr2 = INT2FIX(0);
    sg = DEFAULT_SG;

    switch (argc) {
      case 5:
	val2sg(vsg, sg);
      case 4:
	num2int_with_frac(k, positive_inf);
      case 3:
	n = NUM2INT(vn);
      case 2:
	m = NUM2INT(vm);
      case 1:
	y = vy;
    }

    {
	VALUE nth;
	int ry, rm, rn, rk, rjd, ns;

	if (!valid_nth_kday_p(y, m, n, k, sg,
			      &nth, &ry,
			      &rm, &rn, &rk, &rjd,
			      &ns))
	    rb_raise(eDateError, "invalid date");

	ret = d_simple_new_internal(klass,
				    nth, rjd,
				    sg,
				    0, 0, 0,
				    HAVE_JD);
    }
    add_frac();
    return ret;
}


c_jd_to_weeknum(int jd, int f, double sg, int *ry, int *rw, int *rd)
{
    int rm, rd2, rjd, ns, j;

    c_jd_to_civil(jd, sg, ry, &rm, &rd2);
    c_find_fdoy(*ry, sg, &rjd, &ns);
    rjd += 6;
    j = jd - (rjd - MOD((rjd - f) + 1, 7)) + 7;
    *rw = (int)DIV(j, 7);
    *rd = (int)MOD(j, 7);
}


d_lite_marshal_dump(VALUE self)
{
    VALUE a;

    get_d1(self);

    a = rb_ary_new3(6,
		    m_nth(dat),
		    INT2FIX(m_jd(dat)),
		    INT2FIX(m_df(dat)),
		    m_sf(dat),
		    INT2FIX(m_of(dat)),
		    DBL2NUM(m_sg(dat)));

    if (FL_TEST(self, FL_EXIVAR)) {
	rb_copy_generic_ivar(a, self);
	FL_SET(a, FL_EXIVAR);
    }

    return a;
}


m_sg(union DateData *x)
{
    if (simple_dat_p(x))
	return x->s.sg;
    else {
	get_c_jd(x);
	return x->c.sg;
    }
}


d_lite_mday(VALUE self)
{
    get_d1(self);
    return INT2FIX(m_mday(dat));
}


k_date_p(VALUE x)
{
    return f_kind_of_p(x, cDate);
}


date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vd, vsg;
    VALUE argv2[3];

    rb_scan_args(argc, argv, "21", &vy, &vd, &vsg);

    argv2[0] = vy;
    argv2[1] = vd;
    if (argc < 3)
	argv2[2] = DBL2NUM(GREGORIAN);
    else
	argv2[2] = vsg;

    return valid_ordinal_sub(3, argv2, klass, 1);
}


m_virtual_sg(union DateData *x)
{
    if (simple_dat_p(x))
	return s_virtual_sg(x);
    else
	return c_virtual_sg(x);
}


m_wnum0(union DateData *x)
{
    return m_wnumx(x, 0);
}


dup_obj_with_new_start(VALUE obj, double sg)
{
    volatile VALUE dup = dup_obj(obj);
    {
	get_d1(dup);
	set_sg(dat, sg);
    }
    return dup;
}


d_lite_marshal_load(VALUE self, VALUE a)
{
    VALUE nth, sf;
    int jd, df, of;
    double sg;

    get_d1(self);

    rb_check_frozen(self);

    if (!RB_TYPE_P(a, T_ARRAY))
	rb_raise(rb_eTypeError, "expected an array");

    switch (RARRAY_LEN(a)) {
      case 2: /* 1.6.x */
      case 3: /* 1.8.x, 1.9.2 */
	{
	    VALUE ajd, vof, vsg;

	    if  (RARRAY_LEN(a) == 2) {
		ajd = f_sub(RARRAY_AREF(a, 0), half_days_in_day);
		vof = INT2FIX(0);
		vsg = RARRAY_AREF(a, 1);
		if (!k_numeric_p(vsg))
		    vsg = DBL2NUM(RTEST(vsg) ? GREGORIAN : JULIAN);
	    }
	    else {
		ajd = RARRAY_AREF(a, 0);
		vof = RARRAY_AREF(a, 1);
		vsg = RARRAY_AREF(a, 2);
	    }

	    old_to_new(ajd, vof, vsg,
		       &nth, &jd, &df, &sf, &of, &sg);
	}
	break;
      case 6:
	{
	    nth = RARRAY_AREF(a, 0);
	    jd = NUM2INT(RARRAY_AREF(a, 1));
	    df = NUM2INT(RARRAY_AREF(a, 2));
	    sf = RARRAY_AREF(a, 3);
	    of = NUM2INT(RARRAY_AREF(a, 4));
	    sg = NUM2DBL(RARRAY_AREF(a, 5));
	}
	break;
      default:
	rb_raise(rb_eTypeError, "invalid size");
	break;
    }

    if (simple_dat_p(dat)) {
	if (df || !f_zero_p(sf) || of) {
	    /* loading a fractional date; promote to complex */
	    dat = ruby_xrealloc(dat, sizeof(struct ComplexDateData));
	    RTYPEDDATA(self)->data = dat;
	    goto complex_data;
	}
	set_to_simple(self, &dat->s, nth, jd, sg, 0, 0, 0, HAVE_JD);
    } else {
      complex_data:
	set_to_complex(self, &dat->c, nth, jd, df, sf, of, sg,
		       0, 0, 0, 0, 0, 0,
		       HAVE_JD | HAVE_DF);
    }

    if (FL_TEST(a, FL_EXIVAR)) {
	rb_copy_generic_ivar(self, a);
	FL_SET(self, FL_EXIVAR);
    }

    return self;
}


date_s_xmlschema(int argc, VALUE *argv, VALUE klass)
{
    VALUE str, sg, opt;

    rb_scan_args(argc, argv, "02:", &str, &sg, &opt);
    if (!NIL_P(opt)) argc--;

    switch (argc) {
      case 0:
	str = rb_str_new2("-4712-01-01");
      case 1:
	sg = INT2FIX(DEFAULT_SG);
    }

    {
        int argc2 = 1;
        VALUE argv2[2];
        argv2[0] = str;
        if (!NIL_P(opt)) argv2[argc2++] = opt;
	VALUE hash = date_s__xmlschema(argc2, argv2, klass);
	return d_new_by_frags(klass, hash, sg);
    }
}


date_s_test_commercial(VALUE klass)
{
    if (!test_commercial(MIN_JD, MIN_JD + 366, GREGORIAN))
	return Qfalse;
    if (!test_commercial(2305814, 2598007, GREGORIAN))
	return Qfalse;
    if (!test_commercial(MAX_JD - 366, MAX_JD, GREGORIAN))
	return Qfalse;

    if (!test_commercial(MIN_JD, MIN_JD + 366, ITALY))
	return Qfalse;
    if (!test_commercial(2305814, 2598007, ITALY))
	return Qfalse;
    if (!test_commercial(MAX_JD - 366, MAX_JD, ITALY))
	return Qfalse;

    return Qtrue;
}


expect_numeric(VALUE x)
{
    if (!k_numeric_p(x))
	rb_raise(rb_eTypeError, "expected numeric");
}


local_jd(union DateData *x)
{
    assert(complex_dat_p(x));
    assert(have_jd_p(x));
    assert(have_df_p(x));
    return jd_utc_to_local(x->c.jd, x->c.df, x->c.of);
}


check_numeric(VALUE obj, const char* field) {
    if(!RTEST(rb_obj_is_kind_of(obj, rb_cNumeric))) {
        rb_raise(rb_eTypeError, "invalid %s (not numeric)", field);
    }
}


guess_style(VALUE y, double sg) /* -/+oo or zero */
{
    double style = 0;

    if (isinf(sg))
	style = sg;
    else if (!FIXNUM_P(y))
	style = f_positive_p(y) ? negative_inf : positive_inf;
    else {
	long iy = FIX2LONG(y);

	assert(FIXNUM_P(y));
	if (iy < REFORM_BEGIN_YEAR)
	    style = positive_inf;
	else if (iy > REFORM_END_YEAR)
	    style = negative_inf;
    }
    return style;
}


cmp_dd(VALUE self, VALUE other)
{
    get_d2(self, other);

    {
	VALUE a_nth, b_nth,
	    a_sf, b_sf;
	int a_jd, b_jd,
	    a_df, b_df;

	m_canonicalize_jd(self, adat);
	m_canonicalize_jd(other, bdat);
	a_nth = m_nth(adat);
	b_nth = m_nth(bdat);
	if (f_eqeq_p(a_nth, b_nth)) {
	    a_jd = m_jd(adat);
	    b_jd = m_jd(bdat);
	    if (a_jd == b_jd) {
		a_df = m_df(adat);
		b_df = m_df(bdat);
		if (a_df == b_df) {
		    a_sf = m_sf(adat);
		    b_sf = m_sf(bdat);
		    if (f_eqeq_p(a_sf, b_sf)) {
			return INT2FIX(0);
		    }
		    else if (f_lt_p(a_sf, b_sf)) {
			return INT2FIX(-1);
		    }
		    else {
			return INT2FIX(1);
		    }
		}
		else if (a_df < b_df) {
		    return INT2FIX(-1);
		}
		else {
		    return INT2FIX(1);
		}
	    }
	    else if (a_jd < b_jd) {
		return INT2FIX(-1);
	    }
	    else {
		return INT2FIX(1);
	    }
	}
	else if (f_lt_p(a_nth, b_nth)) {
	    return INT2FIX(-1);
	}
	else {
	    return INT2FIX(1);
	}
    }
}


c_find_fdom(int y, int m, double sg, int *rjd, int *ns)
{
    int d, rm, rd;

    for (d = 1; d < 31; d++)
	if (c_valid_civil_p(y, m, d, sg, &rm, &rd, rjd, ns))
	    return 1;
    return 0;
}


d_lite_lshift(VALUE self, VALUE other)
{
    expect_numeric(other);
    return d_lite_rshift(self, f_negate(other));
}


date_s__parse_internal(int argc, VALUE *argv, VALUE klass)
{
    VALUE vstr, vcomp, hash, opt;

    rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt);
    if (!NIL_P(opt)) argc--;
    check_limit(vstr, opt);
    StringValue(vstr);
    if (!rb_enc_str_asciicompat_p(vstr))
	rb_raise(rb_eArgError,
		 "string should have ASCII compatible encoding");
    if (argc < 2)
	vcomp = Qtrue;

    hash = date__parse(vstr, vcomp);

    return hash;
}


rt_rewrite_frags(VALUE hash)
{
    VALUE seconds;

    seconds = del_hash("seconds");
    if (!NIL_P(seconds)) {
	VALUE offset, d, h, min, s, fr;

	offset = ref_hash("offset");
	if (!NIL_P(offset))
	    seconds = f_add(seconds, offset);

	d = f_idiv(seconds, INT2FIX(DAY_IN_SECONDS));
	fr = f_mod(seconds, INT2FIX(DAY_IN_SECONDS));

	h = f_idiv(fr, INT2FIX(HOUR_IN_SECONDS));
	fr = f_mod(fr, INT2FIX(HOUR_IN_SECONDS));

	min = f_idiv(fr, INT2FIX(MINUTE_IN_SECONDS));
	fr = f_mod(fr, INT2FIX(MINUTE_IN_SECONDS));

	s = f_idiv(fr, INT2FIX(1));
	fr = f_mod(fr, INT2FIX(1));

	set_hash("jd", f_add(UNIX_EPOCH_IN_CJD, d));
	set_hash("hour", h);
	set_hash("min", min);
	set_hash("sec", s);
	set_hash("sec_fraction", fr);
    }
    return hash;
}


d_lite_wday(VALUE self)
{
    get_d1(self);
    return INT2FIX(m_wday(dat));
}


m_cwday(union DateData *x)
{
    int w;

    w = m_wday(x);
    if (w == 0)
	w = 7;
    return w;
}


d_lite_asctime(VALUE self)
{
    return strftimev("%a %b %e %H:%M:%S %Y", self, set_tmx);
}


d_lite_inspect(VALUE self)
{
    get_d1(self);
    return mk_inspect(dat, rb_obj_class(self), self);
}


date_s_commercial(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vw, vd, vsg, y, fr, fr2, ret;
    int w, d;
    double sg;

    rb_scan_args(argc, argv, "04", &vy, &vw, &vd, &vsg);

    y = INT2FIX(-4712);
    w = 1;
    d = 1;
    fr2 = INT2FIX(0);
    sg = DEFAULT_SG;

    switch (argc) {
      case 4:
	val2sg(vsg, sg);
      case 3:
        check_numeric(vd, "cwday");
	num2int_with_frac(d, positive_inf);
      case 2:
        check_numeric(vw, "cweek");
	w = NUM2INT(vw);
      case 1:
        check_numeric(vy, "year");
	y = vy;
    }

    {
	VALUE nth;
	int ry, rw, rd, rjd, ns;

	if (!valid_commercial_p(y, w, d, sg,
				&nth, &ry,
				&rw, &rd, &rjd,
				&ns))
	    rb_raise(eDateError, "invalid date");

	ret = d_simple_new_internal(klass,
				    nth, rjd,
				    sg,
				    0, 0, 0,
				    HAVE_JD);
    }
    add_frac();
    return ret;
}