static void sm501_i2c_write(void *opaque, hwaddr addr, uint64_t value,
                            unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    SM501_DPRINTF("sm501 i2c regs : write addr=%" HWADDR_PRIx
                  " val=%" PRIx64 "\n", addr, value);

    switch (addr) {
    case SM501_I2C_BYTE_COUNT:
        s->i2c_byte_count = value & 0xf;
        break;
    case SM501_I2C_CONTROL:
        if (value & SM501_I2C_CONTROL_ENABLE) {
            if (value & SM501_I2C_CONTROL_START) {
                int res = i2c_start_transfer(s->i2c_bus,
                                             s->i2c_addr >> 1,
                                             s->i2c_addr & 1);
                s->i2c_status |= (res ? SM501_I2C_STATUS_ERROR : 0);
                if (!res) {
                    int i;
                    SM501_DPRINTF("sm501 i2c : transferring %d bytes to 0x%x\n",
                                  s->i2c_byte_count + 1, s->i2c_addr >> 1);
                    for (i = 0; i <= s->i2c_byte_count; i++) {
                        res = i2c_send_recv(s->i2c_bus, &s->i2c_data[i],
                                            !(s->i2c_addr & 1));
                        if (res) {
                            SM501_DPRINTF("sm501 i2c : transfer failed"
                                          " i=%d, res=%d\n", i, res);
                            s->i2c_status |= SM501_I2C_STATUS_ERROR;
                            return;
                        }
                    }
                    if (i) {
                        SM501_DPRINTF("sm501 i2c : transferred %d bytes\n", i);
                        s->i2c_status = SM501_I2C_STATUS_COMPLETE;
                    }
                }
            } else {
                SM501_DPRINTF("sm501 i2c : end transfer\n");
                i2c_end_transfer(s->i2c_bus);
                s->i2c_status &= ~SM501_I2C_STATUS_ERROR;
            }
        }
        break;
    case SM501_I2C_RESET:
        if ((value & SM501_I2C_RESET_ERROR) == 0) {
            s->i2c_status &= ~SM501_I2C_STATUS_ERROR;
        }
        break;
    case SM501_I2C_SLAVE_ADDRESS:
        s->i2c_addr = value & 0xff;
        break;
    case SM501_I2C_DATA ... SM501_I2C_DATA + 15:
        s->i2c_data[addr - SM501_I2C_DATA] = value & 0xff;
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "sm501 i2c : not implemented register write. "
                      "addr=0x%" HWADDR_PRIx " val=%" PRIx64 "\n", addr, value);
    }
}


static void sm501_update_display(void *opaque)
{
    SM501State *s = (SM501State *)opaque;
    DisplaySurface *surface = qemu_console_surface(s->con);
    DirtyBitmapSnapshot *snap;
    int y, c_x = 0, c_y = 0;
    int crt = (s->dc_crt_control & SM501_DC_CRT_CONTROL_SEL) ? 1 : 0;
    int width = get_width(s, crt);
    int height = get_height(s, crt);
    int src_bpp = get_bpp(s, crt);
    int dst_bpp = surface_bytes_per_pixel(surface);
    int dst_depth_index = get_depth_index(surface);
    draw_line_func *draw_line = NULL;
    draw_hwc_line_func *draw_hwc_line = NULL;
    int full_update = 0;
    int y_start = -1;
    ram_addr_t offset;
    uint32_t *palette;
    uint8_t hwc_palette[3 * 3];
    uint8_t *hwc_src = NULL;

    if (!((crt ? s->dc_crt_control : s->dc_panel_control)
          & SM501_DC_CRT_CONTROL_ENABLE)) {
        return;
    }

    palette = (uint32_t *)(crt ? &s->dc_palette[SM501_DC_CRT_PALETTE -
                                                SM501_DC_PANEL_PALETTE]
                               : &s->dc_palette[0]);

    /* choose draw_line function */
    switch (src_bpp) {
    case 1:
        draw_line = draw_line8_funcs[dst_depth_index];
        break;
    case 2:
        draw_line = draw_line16_funcs[dst_depth_index];
        break;
    case 4:
        draw_line = draw_line32_funcs[dst_depth_index];
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR, "sm501: update display"
                      "invalid control register value.\n");
        return;
    }

    /* set up to draw hardware cursor */
    if (is_hwc_enabled(s, crt)) {
        /* choose cursor draw line function */
        draw_hwc_line = draw_hwc_line_funcs[dst_depth_index];
        hwc_src = get_hwc_address(s, crt);
        c_x = get_hwc_x(s, crt);
        c_y = get_hwc_y(s, crt);
        get_hwc_palette(s, crt, hwc_palette);
    }

    /* adjust console size */
    if (s->last_width != width || s->last_height != height) {
        qemu_console_resize(s->con, width, height);
        surface = qemu_console_surface(s->con);
        s->last_width = width;
        s->last_height = height;
        full_update = 1;
    }

    /* someone else requested a full update */
    if (s->do_full_update) {
        s->do_full_update = false;
        full_update = 1;
    }

    /* draw each line according to conditions */
    offset = get_fb_addr(s, crt);
    snap = memory_region_snapshot_and_clear_dirty(&s->local_mem_region,
              offset, width * height * src_bpp, DIRTY_MEMORY_VGA);
    for (y = 0; y < height; y++, offset += width * src_bpp) {
        int update, update_hwc;

        /* check if hardware cursor is enabled and we're within its range */
        update_hwc = draw_hwc_line && c_y <= y && y < c_y + SM501_HWC_HEIGHT;
        update = full_update || update_hwc;
        /* check dirty flags for each line */
        update |= memory_region_snapshot_get_dirty(&s->local_mem_region, snap,
                                                   offset, width * src_bpp);

        /* draw line and change status */
        if (update) {
            uint8_t *d = surface_data(surface);
            d +=  y * width * dst_bpp;

            /* draw graphics layer */
            draw_line(d, s->local_mem + offset, width, palette);

            /* draw hardware cursor */
            if (update_hwc) {
                draw_hwc_line(d, hwc_src, width, hwc_palette, c_x, y - c_y);
            }

            if (y_start < 0) {
                y_start = y;
            }
        } else {
            if (y_start >= 0) {
                /* flush to display */
                dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
                y_start = -1;
            }
        }
    }
    g_free(snap);

    /* complete flush to display */
    if (y_start >= 0) {
        dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
    }
}

sm501: Replace hand written implementation with pixman where possible

Besides being faster this should also prevent malicious guests to
abuse 2D engine to overwrite data or cause a crash.

Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
Message-id: 58666389b6cae256e4e972a32c05cf8aa51bffc0.1590089984.git.balaton@eik.bme.hu
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>


static void sm501_system_config_write(void *opaque, hwaddr addr,
                                      uint64_t value, unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    SM501_DPRINTF("sm501 system config regs : write addr=%x, val=%x\n",
                  (uint32_t)addr, (uint32_t)value);

    switch (addr) {
    case SM501_SYSTEM_CONTROL:
        s->system_control &= 0x10DB0000;
        s->system_control |= value & 0xEF00B8F7;
        break;
    case SM501_MISC_CONTROL:
        s->misc_control &= 0xEF;
        s->misc_control |= value & 0xFF7FFF10;
        break;
    case SM501_GPIO31_0_CONTROL:
        s->gpio_31_0_control = value;
        break;
    case SM501_GPIO63_32_CONTROL:
        s->gpio_63_32_control = value & 0xFF80FFFF;
        break;
    case SM501_DRAM_CONTROL:
        s->local_mem_size_index = (value >> 13) & 0x7;
        /* TODO : check validity of size change */
        s->dram_control &= 0x80000000;
        s->dram_control |= value & 0x7FFFFFC3;
        break;
    case SM501_ARBTRTN_CONTROL:
        s->arbitration_control = value & 0x37777777;
        break;
    case SM501_IRQ_MASK:
        s->irq_mask = value & 0xFFDF3F5F;
        break;
    case SM501_MISC_TIMING:
        s->misc_timing = value & 0xF31F1FFF;
        break;
    case SM501_POWER_MODE_0_GATE:
    case SM501_POWER_MODE_1_GATE:
    case SM501_POWER_MODE_0_CLOCK:
    case SM501_POWER_MODE_1_CLOCK:
        /* TODO : simulate gate & clock control */
        break;
    case SM501_POWER_MODE_CONTROL:
        s->power_mode_control = value & 0x00000003;
        break;
    case SM501_ENDIAN_CONTROL:
        if (value & 0x00000001) {
            qemu_log_mask(LOG_UNIMP, "sm501: system config big endian mode not"
                          " implemented.\n");
        }
        break;

    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented system config"
                      "register write. addr=%" HWADDR_PRIx
                      ", val=%" PRIx64 "\n", addr, value);
    }
}


static inline uint32_t get_hwc_x(SM501State *state, int crt)
{
    uint32_t location = crt ? state->dc_crt_hwc_location
                            : state->dc_panel_hwc_location;
    return location & 0x000007FF;
}


static inline void get_hwc_palette(SM501State *state, int crt, uint8_t *palette)
{
    int i;
    uint32_t color_reg;
    uint16_t rgb565;

    for (i = 0; i < 3; i++) {
        if (i + 1 == 3) {
            color_reg = crt ? state->dc_crt_hwc_color_3
                            : state->dc_panel_hwc_color_3;
        } else {
            color_reg = crt ? state->dc_crt_hwc_color_1_2
                            : state->dc_panel_hwc_color_1_2;
        }

        if (i + 1 == 2) {
            rgb565 = (color_reg >> 16) & 0xFFFF;
        } else {
            rgb565 = color_reg & 0xFFFF;
        }
        palette[i * 3 + 0] = ((rgb565 >> 11) * 527 + 23) >> 6; /* r */
        palette[i * 3 + 1] = (((rgb565 >> 5) & 0x3f) * 259 + 33) >> 6; /* g */
        palette[i * 3 + 2] = ((rgb565 & 0x1f) * 527 + 23) >> 6; /* b */
    }
}


static uint32_t sm501_palette_read(void *opaque, hwaddr addr)
{
    SM501State *s = (SM501State *)opaque;
    SM501_DPRINTF("sm501 palette read addr=%x\n", (int)addr);

    /* TODO : consider BYTE/WORD access */
    /* TODO : consider endian */

    assert(range_covers_byte(0, 0x400 * 3, addr));
    return *(uint32_t *)&s->dc_palette[addr];
}


static uint64_t sm501_disp_ctrl_read(void *opaque, hwaddr addr,
                                     unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    uint32_t ret = 0;
    SM501_DPRINTF("sm501 disp ctrl regs : read addr=%x\n", (int)addr);

    switch (addr) {

    case SM501_DC_PANEL_CONTROL:
        ret = s->dc_panel_control;
        break;
    case SM501_DC_PANEL_PANNING_CONTROL:
        ret = s->dc_panel_panning_control;
        break;
    case SM501_DC_PANEL_COLOR_KEY:
        /* Not implemented yet */
        break;
    case SM501_DC_PANEL_FB_ADDR:
        ret = s->dc_panel_fb_addr;
        break;
    case SM501_DC_PANEL_FB_OFFSET:
        ret = s->dc_panel_fb_offset;
        break;
    case SM501_DC_PANEL_FB_WIDTH:
        ret = s->dc_panel_fb_width;
        break;
    case SM501_DC_PANEL_FB_HEIGHT:
        ret = s->dc_panel_fb_height;
        break;
    case SM501_DC_PANEL_TL_LOC:
        ret = s->dc_panel_tl_location;
        break;
    case SM501_DC_PANEL_BR_LOC:
        ret = s->dc_panel_br_location;
        break;

    case SM501_DC_PANEL_H_TOT:
        ret = s->dc_panel_h_total;
        break;
    case SM501_DC_PANEL_H_SYNC:
        ret = s->dc_panel_h_sync;
        break;
    case SM501_DC_PANEL_V_TOT:
        ret = s->dc_panel_v_total;
        break;
    case SM501_DC_PANEL_V_SYNC:
        ret = s->dc_panel_v_sync;
        break;

    case SM501_DC_PANEL_HWC_ADDR:
        ret = s->dc_panel_hwc_addr;
        break;
    case SM501_DC_PANEL_HWC_LOC:
        ret = s->dc_panel_hwc_location;
        break;
    case SM501_DC_PANEL_HWC_COLOR_1_2:
        ret = s->dc_panel_hwc_color_1_2;
        break;
    case SM501_DC_PANEL_HWC_COLOR_3:
        ret = s->dc_panel_hwc_color_3;
        break;

    case SM501_DC_VIDEO_CONTROL:
        ret = s->dc_video_control;
        break;

    case SM501_DC_CRT_CONTROL:
        ret = s->dc_crt_control;
        break;
    case SM501_DC_CRT_FB_ADDR:
        ret = s->dc_crt_fb_addr;
        break;
    case SM501_DC_CRT_FB_OFFSET:
        ret = s->dc_crt_fb_offset;
        break;
    case SM501_DC_CRT_H_TOT:
        ret = s->dc_crt_h_total;
        break;
    case SM501_DC_CRT_H_SYNC:
        ret = s->dc_crt_h_sync;
        break;
    case SM501_DC_CRT_V_TOT:
        ret = s->dc_crt_v_total;
        break;
    case SM501_DC_CRT_V_SYNC:
        ret = s->dc_crt_v_sync;
        break;

    case SM501_DC_CRT_HWC_ADDR:
        ret = s->dc_crt_hwc_addr;
        break;
    case SM501_DC_CRT_HWC_LOC:
        ret = s->dc_crt_hwc_location;
        break;
    case SM501_DC_CRT_HWC_COLOR_1_2:
        ret = s->dc_crt_hwc_color_1_2;
        break;
    case SM501_DC_CRT_HWC_COLOR_3:
        ret = s->dc_crt_hwc_color_3;
        break;

    case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400 * 3 - 4:
        ret = sm501_palette_read(opaque, addr - SM501_DC_PANEL_PALETTE);
        break;

    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
                      "read. addr=%" HWADDR_PRIx "\n", addr);
    }

    return ret;
}


static uint64_t sm501_i2c_read(void *opaque, hwaddr addr, unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    uint8_t ret = 0;

    switch (addr) {
    case SM501_I2C_BYTE_COUNT:
        ret = s->i2c_byte_count;
        break;
    case SM501_I2C_STATUS:
        ret = s->i2c_status;
        break;
    case SM501_I2C_SLAVE_ADDRESS:
        ret = s->i2c_addr;
        break;
    case SM501_I2C_DATA ... SM501_I2C_DATA + 15:
        ret = s->i2c_data[addr - SM501_I2C_DATA];
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "sm501 i2c : not implemented register read."
                      " addr=0x%" HWADDR_PRIx "\n", addr);
    }

    SM501_DPRINTF("sm501 i2c regs : read addr=%" HWADDR_PRIx " val=%x\n",
                  addr, ret);
    return ret;
}


static inline int get_width(SM501State *s, int crt)
{
    int width = crt ? s->dc_crt_h_total : s->dc_panel_h_total;
    return (width & 0x00000FFF) + 1;
}


static void sm501_sysbus_init(Object *o)
{
    SM501SysBusState *sm501 = SYSBUS_SM501(o);
    SerialMM *smm = &sm501->serial;

    sysbus_init_child_obj(o, "serial", smm, sizeof(SerialMM), TYPE_SERIAL_MM);
    qdev_set_legacy_instance_id(DEVICE(smm), SM501_UART0, 2);
    qdev_prop_set_uint8(DEVICE(smm), "regshift", 2);
    qdev_prop_set_uint8(DEVICE(smm), "endianness", DEVICE_LITTLE_ENDIAN);

    object_property_add_alias(o, "chardev",
                              OBJECT(smm), "chardev");
}


static inline void hwc_invalidate(SM501State *s, int crt)
{
    int w = get_width(s, crt);
    int h = get_height(s, crt);
    int bpp = get_bpp(s, crt);
    int start = get_hwc_y(s, crt);
    int end = MIN(h, start + SM501_HWC_HEIGHT) + 1;

    start *= w * bpp;
    end *= w * bpp;

    memory_region_set_dirty(&s->local_mem_region,
                            get_fb_addr(s, crt) + start, end - start);
}


static void sm501_palette_write(void *opaque, hwaddr addr,
                                uint32_t value)
{
    SM501State *s = (SM501State *)opaque;
    SM501_DPRINTF("sm501 palette write addr=%x, val=%x\n",
                  (int)addr, value);

    /* TODO : consider BYTE/WORD access */
    /* TODO : consider endian */

    assert(range_covers_byte(0, 0x400 * 3, addr));
    *(uint32_t *)&s->dc_palette[addr] = value;
    s->do_full_update = true;
}


static inline uint32_t get_hwc_y(SM501State *state, int crt)
{
    uint32_t location = crt ? state->dc_crt_hwc_location
                            : state->dc_panel_hwc_location;
    return (location & 0x07FF0000) >> 16;
}


static inline int get_height(SM501State *s, int crt)
{
    int height = crt ? s->dc_crt_v_total : s->dc_panel_v_total;
    return (height & 0x00000FFF) + 1;
}


static inline int get_depth_index(DisplaySurface *surface)
{
    switch (surface_bits_per_pixel(surface)) {
    default:
    case 8:
        return 0;
    case 15:
        return 1;
    case 16:
        return 2;
    case 32:
        if (is_surface_bgr(surface)) {
            return 4;
        } else {
            return 3;
        }
    }
}


static inline int is_hwc_enabled(SM501State *state, int crt)
{
    uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
    return addr & SM501_HWC_EN;
}


static inline uint8_t *get_hwc_address(SM501State *state, int crt)
{
    uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
    return state->local_mem + (addr & 0x03FFFFF0);
}


static ram_addr_t get_fb_addr(SM501State *s, int crt)
{
    return (crt ? s->dc_crt_fb_addr : s->dc_panel_fb_addr) & 0x3FFFFF0;
}


static void sm501_reset_pci(DeviceState *dev)
{
    SM501PCIState *s = PCI_SM501(dev);
    sm501_reset(&s->state);
    /* Bits 2:0 of misc_control register is 001 for PCI */
    s->state.misc_control |= 1;
}


static void sm501_2d_operation(SM501State *s)
{
    int cmd = (s->twoD_control >> 16) & 0x1F;
    int rtl = s->twoD_control & BIT(27);
    int format = (s->twoD_stretch >> 20) & 0x3;
    int rop_mode = (s->twoD_control >> 15) & 0x1; /* 1 for rop2, else rop3 */
    /* 1 if rop2 source is the pattern, otherwise the source is the bitmap */
    int rop2_source_is_pattern = (s->twoD_control >> 14) & 0x1;
    int rop = s->twoD_control & 0xFF;
    unsigned int dst_x = (s->twoD_destination >> 16) & 0x01FFF;
    unsigned int dst_y = s->twoD_destination & 0xFFFF;
    unsigned int width = (s->twoD_dimension >> 16) & 0x1FFF;
    unsigned int height = s->twoD_dimension & 0xFFFF;
    uint32_t dst_base = s->twoD_destination_base & 0x03FFFFFF;
    unsigned int dst_pitch = (s->twoD_pitch >> 16) & 0x1FFF;
    int crt = (s->dc_crt_control & SM501_DC_CRT_CONTROL_SEL) ? 1 : 0;
    int fb_len = get_width(s, crt) * get_height(s, crt) * get_bpp(s, crt);

    if ((s->twoD_stretch >> 16) & 0xF) {
        qemu_log_mask(LOG_UNIMP, "sm501: only XY addressing is supported.\n");
        return;
    }

    if (s->twoD_source_base & BIT(27) || s->twoD_destination_base & BIT(27)) {
        qemu_log_mask(LOG_UNIMP, "sm501: only local memory is supported.\n");
        return;
    }

    if (!dst_pitch) {
        qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero dest pitch.\n");
        return;
    }

    if (!width || !height) {
        qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero size 2D op.\n");
        return;
    }

    if (rtl) {
        dst_x -= width - 1;
        dst_y -= height - 1;
    }

    if (dst_base >= get_local_mem_size(s) || dst_base +
        (dst_x + width + (dst_y + height) * (dst_pitch + width)) *
        (1 << format) >= get_local_mem_size(s)) {
        qemu_log_mask(LOG_GUEST_ERROR, "sm501: 2D op dest is outside vram.\n");
        return;
    }

    switch (cmd) {
    case 0: /* BitBlt */
    {
        unsigned int src_x = (s->twoD_source >> 16) & 0x01FFF;
        unsigned int src_y = s->twoD_source & 0xFFFF;
        uint32_t src_base = s->twoD_source_base & 0x03FFFFFF;
        unsigned int src_pitch = s->twoD_pitch & 0x1FFF;

        if (!src_pitch) {
            qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero src pitch.\n");
            return;
        }

        if (rtl) {
            src_x -= width - 1;
            src_y -= height - 1;
        }

        if (src_base >= get_local_mem_size(s) || src_base +
            (src_x + width + (src_y + height) * (src_pitch + width)) *
            (1 << format) >= get_local_mem_size(s)) {
            qemu_log_mask(LOG_GUEST_ERROR,
                          "sm501: 2D op src is outside vram.\n");
            return;
        }

        if ((rop_mode && rop == 0x5) || (!rop_mode && rop == 0x55)) {
            /* Invert dest, is there a way to do this with pixman? */
            unsigned int x, y, i;
            uint8_t *d = s->local_mem + dst_base;

            for (y = 0; y < height; y++) {
                i = (dst_x + (dst_y + y) * dst_pitch) * (1 << format);
                for (x = 0; x < width; x++, i += (1 << format)) {
                    switch (format) {
                    case 0:
                        d[i] = ~d[i];
                        break;
                    case 1:
                        *(uint16_t *)&d[i] = ~*(uint16_t *)&d[i];
                        break;
                    case 2:
                        *(uint32_t *)&d[i] = ~*(uint32_t *)&d[i];
                        break;
                    }
                }
            }
        } else {
            /* Do copy src for unimplemented ops, better than unpainted area */
            if ((rop_mode && (rop != 0xc || rop2_source_is_pattern)) ||
                (!rop_mode && rop != 0xcc)) {
                qemu_log_mask(LOG_UNIMP,
                              "sm501: rop%d op %x%s not implemented\n",
                              (rop_mode ? 2 : 3), rop,
                              (rop2_source_is_pattern ?
                                  " with pattern source" : ""));
            }
            /* Check for overlaps, this could be made more exact */
            uint32_t sb, se, db, de;
            sb = src_base + src_x + src_y * (width + src_pitch);
            se = sb + width + height * (width + src_pitch);
            db = dst_base + dst_x + dst_y * (width + dst_pitch);
            de = db + width + height * (width + dst_pitch);
            if (rtl && ((db >= sb && db <= se) || (de >= sb && de <= se))) {
                /* regions may overlap: copy via temporary */
                int llb = width * (1 << format);
                int tmp_stride = DIV_ROUND_UP(llb, sizeof(uint32_t));
                uint32_t *tmp = g_malloc(tmp_stride * sizeof(uint32_t) *
                                         height);
                pixman_blt((uint32_t *)&s->local_mem[src_base], tmp,
                           src_pitch * (1 << format) / sizeof(uint32_t),
                           tmp_stride, 8 * (1 << format), 8 * (1 << format),
                           src_x, src_y, 0, 0, width, height);
                pixman_blt(tmp, (uint32_t *)&s->local_mem[dst_base],
                           tmp_stride,
                           dst_pitch * (1 << format) / sizeof(uint32_t),
                           8 * (1 << format), 8 * (1 << format),
                           0, 0, dst_x, dst_y, width, height);
                g_free(tmp);
            } else {
                pixman_blt((uint32_t *)&s->local_mem[src_base],
                           (uint32_t *)&s->local_mem[dst_base],
                           src_pitch * (1 << format) / sizeof(uint32_t),
                           dst_pitch * (1 << format) / sizeof(uint32_t),
                           8 * (1 << format), 8 * (1 << format),
                           src_x, src_y, dst_x, dst_y, width, height);
            }
        }
        break;
    }
    case 1: /* Rectangle Fill */
    {
        uint32_t color = s->twoD_foreground;

        if (format == 2) {
            color = cpu_to_le32(color);
        } else if (format == 1) {
            color = cpu_to_le16(color);
        }

        pixman_fill((uint32_t *)&s->local_mem[dst_base],
                    dst_pitch * (1 << format) / sizeof(uint32_t),
                    8 * (1 << format), dst_x, dst_y, width, height, color);
        break;
    }
    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented 2D operation: %d\n",
                      cmd);
        return;
    }

    if (dst_base >= get_fb_addr(s, crt) &&
        dst_base <= get_fb_addr(s, crt) + fb_len) {
        int dst_len = MIN(fb_len, ((dst_y + height - 1) * dst_pitch +
                          dst_x + width) * (1 << format));
        if (dst_len) {
            memory_region_set_dirty(&s->local_mem_region, dst_base, dst_len);
        }
    }
}


static void sm501_sysbus_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = sm501_realize_sysbus;
    set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
    dc->desc = "SM501 Multimedia Companion";
    device_class_set_props(dc, sm501_sysbus_properties);
    dc->reset = sm501_reset_sysbus;
    dc->vmsd = &vmstate_sm501_sysbus;
}


static uint64_t sm501_system_config_read(void *opaque, hwaddr addr,
                                         unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    uint32_t ret = 0;
    SM501_DPRINTF("sm501 system config regs : read addr=%x\n", (int)addr);

    switch (addr) {
    case SM501_SYSTEM_CONTROL:
        ret = s->system_control;
        break;
    case SM501_MISC_CONTROL:
        ret = s->misc_control;
        break;
    case SM501_GPIO31_0_CONTROL:
        ret = s->gpio_31_0_control;
        break;
    case SM501_GPIO63_32_CONTROL:
        ret = s->gpio_63_32_control;
        break;
    case SM501_DEVICEID:
        ret = 0x050100A0;
        break;
    case SM501_DRAM_CONTROL:
        ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13;
        break;
    case SM501_ARBTRTN_CONTROL:
        ret = s->arbitration_control;
        break;
    case SM501_COMMAND_LIST_STATUS:
        ret = 0x00180002; /* FIFOs are empty, everything idle */
        break;
    case SM501_IRQ_MASK:
        ret = s->irq_mask;
        break;
    case SM501_MISC_TIMING:
        /* TODO : simulate gate control */
        ret = s->misc_timing;
        break;
    case SM501_CURRENT_GATE:
        /* TODO : simulate gate control */
        ret = 0x00021807;
        break;
    case SM501_CURRENT_CLOCK:
        ret = 0x2A1A0A09;
        break;
    case SM501_POWER_MODE_CONTROL:
        ret = s->power_mode_control;
        break;
    case SM501_ENDIAN_CONTROL:
        ret = 0; /* Only default little endian mode is supported */
        break;

    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented system config"
                      "register read. addr=%" HWADDR_PRIx "\n", addr);
    }

    return ret;
}


static void sm501_disp_ctrl_write(void *opaque, hwaddr addr,
                                  uint64_t value, unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    SM501_DPRINTF("sm501 disp ctrl regs : write addr=%x, val=%x\n",
                  (unsigned)addr, (unsigned)value);

    switch (addr) {
    case SM501_DC_PANEL_CONTROL:
        s->dc_panel_control = value & 0x0FFF73FF;
        break;
    case SM501_DC_PANEL_PANNING_CONTROL:
        s->dc_panel_panning_control = value & 0xFF3FFF3F;
        break;
    case SM501_DC_PANEL_COLOR_KEY:
        /* Not implemented yet */
        break;
    case SM501_DC_PANEL_FB_ADDR:
        s->dc_panel_fb_addr = value & 0x8FFFFFF0;
        if (value & 0x8000000) {
            qemu_log_mask(LOG_UNIMP, "Panel external memory not supported\n");
        }
        s->do_full_update = true;
        break;
    case SM501_DC_PANEL_FB_OFFSET:
        s->dc_panel_fb_offset = value & 0x3FF03FF0;
        break;
    case SM501_DC_PANEL_FB_WIDTH:
        s->dc_panel_fb_width = value & 0x0FFF0FFF;
        break;
    case SM501_DC_PANEL_FB_HEIGHT:
        s->dc_panel_fb_height = value & 0x0FFF0FFF;
        break;
    case SM501_DC_PANEL_TL_LOC:
        s->dc_panel_tl_location = value & 0x07FF07FF;
        break;
    case SM501_DC_PANEL_BR_LOC:
        s->dc_panel_br_location = value & 0x07FF07FF;
        break;

    case SM501_DC_PANEL_H_TOT:
        s->dc_panel_h_total = value & 0x0FFF0FFF;
        break;
    case SM501_DC_PANEL_H_SYNC:
        s->dc_panel_h_sync = value & 0x00FF0FFF;
        break;
    case SM501_DC_PANEL_V_TOT:
        s->dc_panel_v_total = value & 0x0FFF0FFF;
        break;
    case SM501_DC_PANEL_V_SYNC:
        s->dc_panel_v_sync = value & 0x003F0FFF;
        break;

    case SM501_DC_PANEL_HWC_ADDR:
        value &= 0x8FFFFFF0;
        if (value != s->dc_panel_hwc_addr) {
            hwc_invalidate(s, 0);
            s->dc_panel_hwc_addr = value;
        }
        break;
    case SM501_DC_PANEL_HWC_LOC:
        value &= 0x0FFF0FFF;
        if (value != s->dc_panel_hwc_location) {
            hwc_invalidate(s, 0);
            s->dc_panel_hwc_location = value;
        }
        break;
    case SM501_DC_PANEL_HWC_COLOR_1_2:
        s->dc_panel_hwc_color_1_2 = value;
        break;
    case SM501_DC_PANEL_HWC_COLOR_3:
        s->dc_panel_hwc_color_3 = value & 0x0000FFFF;
        break;

    case SM501_DC_VIDEO_CONTROL:
        s->dc_video_control = value & 0x00037FFF;
        break;

    case SM501_DC_CRT_CONTROL:
        s->dc_crt_control = value & 0x0003FFFF;
        break;
    case SM501_DC_CRT_FB_ADDR:
        s->dc_crt_fb_addr = value & 0x8FFFFFF0;
        if (value & 0x8000000) {
            qemu_log_mask(LOG_UNIMP, "CRT external memory not supported\n");
        }
        s->do_full_update = true;
        break;
    case SM501_DC_CRT_FB_OFFSET:
        s->dc_crt_fb_offset = value & 0x3FF03FF0;
        break;
    case SM501_DC_CRT_H_TOT:
        s->dc_crt_h_total = value & 0x0FFF0FFF;
        break;
    case SM501_DC_CRT_H_SYNC:
        s->dc_crt_h_sync = value & 0x00FF0FFF;
        break;
    case SM501_DC_CRT_V_TOT:
        s->dc_crt_v_total = value & 0x0FFF0FFF;
        break;
    case SM501_DC_CRT_V_SYNC:
        s->dc_crt_v_sync = value & 0x003F0FFF;
        break;

    case SM501_DC_CRT_HWC_ADDR:
        value &= 0x8FFFFFF0;
        if (value != s->dc_crt_hwc_addr) {
            hwc_invalidate(s, 1);
            s->dc_crt_hwc_addr = value;
        }
        break;
    case SM501_DC_CRT_HWC_LOC:
        value &= 0x0FFF0FFF;
        if (value != s->dc_crt_hwc_location) {
            hwc_invalidate(s, 1);
            s->dc_crt_hwc_location = value;
        }
        break;
    case SM501_DC_CRT_HWC_COLOR_1_2:
        s->dc_crt_hwc_color_1_2 = value;
        break;
    case SM501_DC_CRT_HWC_COLOR_3:
        s->dc_crt_hwc_color_3 = value & 0x0000FFFF;
        break;

    case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400 * 3 - 4:
        sm501_palette_write(opaque, addr - SM501_DC_PANEL_PALETTE, value);
        break;

    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
                      "write. addr=%" HWADDR_PRIx
                      ", val=%" PRIx64 "\n", addr, value);
    }
}


static void sm501_pci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);

    k->realize = sm501_realize_pci;
    k->vendor_id = PCI_VENDOR_ID_SILICON_MOTION;
    k->device_id = PCI_DEVICE_ID_SM501;
    k->class_id = PCI_CLASS_DISPLAY_OTHER;
    set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
    dc->desc = "SM501 Display Controller";
    device_class_set_props(dc, sm501_pci_properties);
    dc->reset = sm501_reset_pci;
    dc->hotpluggable = false;
    dc->vmsd = &vmstate_sm501_pci;
}


static void sm501_init(SM501State *s, DeviceState *dev,
                       uint32_t local_mem_bytes)
{
    s->local_mem_size_index = get_local_mem_size_index(local_mem_bytes);
    SM501_DPRINTF("sm501 local mem size=%x. index=%d\n", get_local_mem_size(s),
                  s->local_mem_size_index);

    /* local memory */
    memory_region_init_ram(&s->local_mem_region, OBJECT(dev), "sm501.local",
                           get_local_mem_size(s), &error_fatal);
    memory_region_set_log(&s->local_mem_region, true, DIRTY_MEMORY_VGA);
    s->local_mem = memory_region_get_ram_ptr(&s->local_mem_region);

    /* i2c */
    s->i2c_bus = i2c_init_bus(dev, "sm501.i2c");
    /* ddc */
    I2CDDCState *ddc = I2CDDC(qdev_create(BUS(s->i2c_bus), TYPE_I2CDDC));
    i2c_set_slave_address(I2C_SLAVE(ddc), 0x50);

    /* mmio */
    memory_region_init(&s->mmio_region, OBJECT(dev), "sm501.mmio", MMIO_SIZE);
    memory_region_init_io(&s->system_config_region, OBJECT(dev),
                          &sm501_system_config_ops, s,
                          "sm501-system-config", 0x6c);
    memory_region_add_subregion(&s->mmio_region, SM501_SYS_CONFIG,
                                &s->system_config_region);
    memory_region_init_io(&s->i2c_region, OBJECT(dev), &sm501_i2c_ops, s,
                          "sm501-i2c", 0x14);
    memory_region_add_subregion(&s->mmio_region, SM501_I2C, &s->i2c_region);
    memory_region_init_io(&s->disp_ctrl_region, OBJECT(dev),
                          &sm501_disp_ctrl_ops, s,
                          "sm501-disp-ctrl", 0x1000);
    memory_region_add_subregion(&s->mmio_region, SM501_DC,
                                &s->disp_ctrl_region);
    memory_region_init_io(&s->twoD_engine_region, OBJECT(dev),
                          &sm501_2d_engine_ops, s,
                          "sm501-2d-engine", 0x54);
    memory_region_add_subregion(&s->mmio_region, SM501_2D_ENGINE,
                                &s->twoD_engine_region);

    /* create qemu graphic console */
    s->con = graphic_console_init(dev, 0, &sm501_ops, s);
}


static uint64_t sm501_2d_engine_read(void *opaque, hwaddr addr,
                                     unsigned size)
{
    SM501State *s = (SM501State *)opaque;
    uint32_t ret = 0;
    SM501_DPRINTF("sm501 2d engine regs : read addr=%x\n", (int)addr);

    switch (addr) {
    case SM501_2D_SOURCE:
        ret = s->twoD_source;
        break;
    case SM501_2D_DESTINATION:
        ret = s->twoD_destination;
        break;
    case SM501_2D_DIMENSION:
        ret = s->twoD_dimension;
        break;
    case SM501_2D_CONTROL:
        ret = s->twoD_control;
        break;
    case SM501_2D_PITCH:
        ret = s->twoD_pitch;
        break;
    case SM501_2D_FOREGROUND:
        ret = s->twoD_foreground;
        break;
    case SM501_2D_BACKGROUND:
        ret = s->twoD_background;
        break;
    case SM501_2D_STRETCH:
        ret = s->twoD_stretch;
        break;
    case SM501_2D_COLOR_COMPARE:
        ret = s->twoD_color_compare;
        break;
    case SM501_2D_COLOR_COMPARE_MASK:
        ret = s->twoD_color_compare_mask;
        break;
    case SM501_2D_MASK:
        ret = s->twoD_mask;
        break;
    case SM501_2D_CLIP_TL:
        ret = s->twoD_clip_tl;
        break;
    case SM501_2D_CLIP_BR:
        ret = s->twoD_clip_br;
        break;
    case SM501_2D_MONO_PATTERN_LOW:
        ret = s->twoD_mono_pattern_low;
        break;
    case SM501_2D_MONO_PATTERN_HIGH:
        ret = s->twoD_mono_pattern_high;
        break;
    case SM501_2D_WINDOW_WIDTH:
        ret = s->twoD_window_width;
        break;
    case SM501_2D_SOURCE_BASE:
        ret = s->twoD_source_base;
        break;
    case SM501_2D_DESTINATION_BASE:
        ret = s->twoD_destination_base;
        break;
    case SM501_2D_ALPHA:
        ret = s->twoD_alpha;
        break;
    case SM501_2D_WRAP:
        ret = s->twoD_wrap;
        break;
    case SM501_2D_STATUS:
        ret = 0; /* Should return interrupt status */
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
                      "read. addr=%" HWADDR_PRIx "\n", addr);
    }

    return ret;
}


    size_t RemoteIo::read(byte* buf, size_t rcount)
    {
        assert(p_->isMalloced_);
        if (p_->eof_) return 0;
        p_->totalRead_ += (uint32_t)rcount;

        size_t allow     = std::min(rcount, p_->size_ - p_->idx_);
        size_t lowBlock  =  p_->idx_         /p_->blockSize_;
        size_t highBlock = (p_->idx_ + allow)/p_->blockSize_;

        // connect to the remote machine & populate the blocks just in time.
        p_->populateBlocks(lowBlock, highBlock);
        byte* fakeData = (byte*) std::calloc(p_->blockSize_, sizeof(byte));
        if (!fakeData) {
            throw Error(kerErrorMessage, "Unable to allocate data");
        }

        size_t iBlock = lowBlock;
        size_t startPos = p_->idx_ - lowBlock*p_->blockSize_;
        size_t totalRead = 0;
        do {
            byte* data = p_->blocksMap_[iBlock++].getData();
            if (data == nullptr) data = fakeData;
            size_t blockR = std::min(allow, p_->blockSize_ - startPos);
            std::memcpy(&buf[totalRead], &data[startPos], blockR);
            totalRead += blockR;
            startPos = 0;
            allow -= blockR;
        } while(allow);

        if (fakeData) std::free(fakeData);

        p_->idx_ += (long)totalRead;
        p_->eof_ = (p_->idx_ == p_->size_);

        return totalRead;
    }

Add bounds check to MemIo::seek(). (#944)

- Regression test for missing bounds check in MemIo::seek()
- Add bounds check to MemIo::seek(), this fixes CVE-2019-13504


    size_t RemoteIo::Impl::populateBlocks(size_t lowBlock, size_t highBlock)
    {
        assert(isMalloced_);

        // optimize: ignore all true blocks on left & right sides.
        while(!blocksMap_[lowBlock].isNone()  && lowBlock  < highBlock) lowBlock++;
        while(!blocksMap_[highBlock].isNone() && highBlock > lowBlock)  highBlock--;

        size_t rcount = 0;
        if (blocksMap_[highBlock].isNone())
        {
            std::string data;
            getDataByRange( (long) lowBlock, (long) highBlock, data);
            rcount = data.length();
            if (rcount == 0) {
                throw Error(kerErrorMessage, "Data By Range is empty. Please check the permission.");
            }
            byte* source = (byte*)data.c_str();
            size_t remain = rcount, totalRead = 0;
            size_t iBlock = (rcount == size_) ? 0 : lowBlock;

            while (remain) {
                size_t allow = std::min(remain, blockSize_);
                blocksMap_[iBlock].populate(&source[totalRead], allow);
                remain -= allow;
                totalRead += allow;
                iBlock++;
            }
        }

        return rcount;
    }


    size_t RemoteIo::write(BasicIo& src)
    {
        assert(p_->isMalloced_);
        if (!src.isopen()) return 0;

        /*
         * The idea is to compare the file content, find the different bytes and submit them to the remote machine.
         * To simplify it, it:
         *      + goes from the left, find the first different position -> $left
         *      + goes from the right, find the first different position -> $right
         * The different bytes are [$left-$right] part.
         */
        size_t left       = 0;
        size_t right      = 0;
        size_t blockIndex = 0;
        size_t i          = 0;
        size_t readCount  = 0;
        size_t blockSize  = 0;
        byte*  buf        = (byte*) std::malloc(p_->blockSize_);
        size_t nBlocks    = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;

        // find $left
        src.seek(0, BasicIo::beg);
        bool findDiff = false;
        while (blockIndex < nBlocks && !src.eof() && !findDiff) {
            blockSize = p_->blocksMap_[blockIndex].getSize();
            bool isFakeData = p_->blocksMap_[blockIndex].isKnown(); // fake data
            readCount = src.read(buf, blockSize);
            byte* blockData = p_->blocksMap_[blockIndex].getData();
            for (i = 0; (i < readCount) && (i < blockSize) && !findDiff; i++) {
                if ((!isFakeData && buf[i] != blockData[i]) || (isFakeData && buf[i] != 0)) {
                    findDiff = true;
                } else {
                    left++;
                }
            }
            blockIndex++;
        }

        // find $right
        findDiff    = false;
        blockIndex  = nBlocks - 1;
        blockSize   = p_->blocksMap_[blockIndex].getSize();
        while ((blockIndex + 1 > 0) && right < src.size() && !findDiff) {
            if(src.seek(-1 * (blockSize + right), BasicIo::end)) {
                findDiff = true;
            } else {
                bool isFakeData = p_->blocksMap_[blockIndex].isKnown(); // fake data
                readCount = src.read(buf, (long) blockSize);
                byte* blockData = p_->blocksMap_[blockIndex].getData();
                for (i = 0; (i < readCount) && (i < blockSize) && !findDiff; i++) {
                    if ((!isFakeData && buf[readCount - i - 1] != blockData[blockSize - i - 1]) || (isFakeData && buf[readCount - i - 1] != 0)) {
                        findDiff = true;
                    } else {
                        right++;
                    }
                }
            }
            blockIndex--;
            blockSize = (long)p_->blocksMap_[blockIndex].getSize();
        }

        // free buf
        if (buf) std::free(buf);

        // submit to the remote machine.
        long dataSize = (long) (src.size() - left - right);
        if (dataSize > 0) {
            byte* data = (byte*) std::malloc(dataSize);
            src.seek(left, BasicIo::beg);
            src.read(data, dataSize);
            p_->writeRemote(data, (size_t)dataSize, (long)left, (long) (p_->size_ - right));
            if (data) std::free(data);
        }
        return (long) src.size();
    }


    int RemoteIo::getb()
    {
        assert(p_->isMalloced_);
        if (p_->idx_ == p_->size_) {
            p_->eof_ = true;
            return EOF;
        }

        size_t expectedBlock = (p_->idx_ + 1)/p_->blockSize_;
        // connect to the remote machine & populate the blocks just in time.
        p_->populateBlocks(expectedBlock, expectedBlock);

        byte* data = p_->blocksMap_[expectedBlock].getData();
        return data[p_->idx_++ - expectedBlock*p_->blockSize_];
    }


    int FileIo::seek(int64 offset, Position pos )
    {
        assert(p_->fp_ != 0);

        int fileSeek = 0;
        switch (pos) {
        case BasicIo::cur: fileSeek = SEEK_CUR; break;
        case BasicIo::beg: fileSeek = SEEK_SET; break;
        case BasicIo::end: fileSeek = SEEK_END; break;
        }

        if (p_->switchMode(Impl::opSeek) != 0) return 1;
#ifdef _WIN64
        return _fseeki64(p_->fp_, offset, fileSeek);
#else
        return std::fseek(p_->fp_,static_cast<long>(offset), fileSeek);
#endif
    }


    int64 RemoteIo::tell() const
    {
        return static_cast<int64>(p_->idx_);
    }


    size_t FileIo::write(BasicIo& src)
    {
        assert(p_->fp_ != 0);
        if (static_cast<BasicIo*>(this) == &src) return 0;
        if (!src.isopen()) return 0;
        if (p_->switchMode(Impl::opWrite) != 0) return 0;

        byte buf[4096];
        size_t readCount = 0;
        size_t writeTotal = 0;
        while ((readCount = src.read(buf, sizeof(buf)))) {
            const size_t writeCount = std::fwrite(buf, 1, readCount, p_->fp_);
            writeTotal += writeCount;
            if (writeCount != readCount) {
                // try to reset back to where write stopped
                src.seek(writeCount-readCount, BasicIo::cur);
                break;
            }
        }

        return writeTotal;
    }


    int RemoteIo::close()
    {
        if (p_->isMalloced_) {
            p_->eof_ = false;
            p_->idx_ = 0;
        }
#ifdef EXIV2_DEBUG_MESSAGES
        std::cerr << "RemoteIo::close totalRead_ = " << p_->totalRead_ << std::endl;
#endif
        if ( bigBlock_ ) {
            delete [] bigBlock_;
            bigBlock_=nullptr;
        }
        return 0;
    }


    int RemoteIo::seek(int64 offset, Position pos)
    {
        assert(p_->isMalloced_);
        int64 newIdx = 0;

        switch (pos) {
            case BasicIo::cur:
                newIdx = static_cast<int64>(p_->idx_) + offset;
                break;
            case BasicIo::beg:
                newIdx = offset;
                break;
            case BasicIo::end:
                newIdx = static_cast<int64>(p_->size_) + offset;
                break;
        }

        // #1198.  Don't return 1 when asked to seek past EOF.  Stay calm and set eof_
        // if (newIdx < 0 || newIdx > (long) p_->size_) return 1;
        p_->idx_ = static_cast<size_t>(newIdx);
        p_->eof_ = newIdx > static_cast<int64>(p_->size_);
        if (p_->idx_ > p_->size_)
          p_->idx_ = p_->size_;
        return 0;
    }


    int MemIo::seek(int64 offset, Position pos )
    {
        int64 newIdx = 0;

        switch (pos) {
            case BasicIo::cur:
                newIdx = p_->idx_ + offset;
                break;
            case BasicIo::beg:
                newIdx = offset;
                break;
            case BasicIo::end:
                newIdx = p_->size_ + offset;
                break;
        }

        if (newIdx < 0)
            return 1;

        if (static_cast<size_t>(newIdx) > p_->size_) {
            p_->eof_ = true;
            return 1;
        }

        p_->idx_ = static_cast<size_t>(newIdx);
        p_->eof_ = false;
        return 0;
    }


    int FileIo::Impl::stat(StructStat& buf) const
    {
        int ret = 0;
#ifdef EXV_UNICODE_PATH
#ifdef _WIN64
            struct _stat64 st;
            ret = ::_wstati64(wpath_.c_str(), &st);

            if (0 == ret) {
                buf.st_size = static_cast<off_t>(st.st_size);
                buf.st_mode = st.st_mode;
                buf.st_nlink = st.st_nlink;
            }
#else
            struct _stat st;
            ret = ::_wstat(wpath_.c_str(), &st);

            if (0 == ret) {
                buf.st_size = st.st_size;
                buf.st_mode = st.st_mode;
                buf.st_nlink = st.st_nlink;
            }
#endif
        else
#endif
        {
            struct stat st;
            ret = ::stat(path_.c_str(), &st);
            if (0 == ret) {
                buf.st_size = st.st_size;
                buf.st_nlink = st.st_nlink;
                buf.st_mode = st.st_mode;
            }
        }
        return ret;
    } // FileIo::Impl::stat


    size_t writeFile(const DataBuf& buf, const std::string& path)
    {
        FileIo file(path);
        if (file.open("wb") != 0) {
            throw Error(kerFileOpenFailed, path, "wb", strError());
        }
        return file.write(buf.pData_, buf.size_);
    }


    DataBuf readFile(const std::wstring& wpath)
    {
        FileIo file(wpath);
        if (file.open("rb") != 0) {
            throw WError(kerFileOpenFailed, wpath, "rb", strError().c_str());
        }
        struct _stat st;
        if (0 != ::_wstat(wpath.c_str(), &st)) {
            throw WError(kerCallFailed, wpath, strError().c_str(), "::_wstat");
        }
        DataBuf buf(st.st_size);
        size_t len = file.read(buf.pData_, buf.size_);
        if (len != buf.size_) {
            throw WError(kerCallFailed, wpath, strError().c_str(), "FileIo::read");
        }
        return buf;
    }


    int RemoteIo::open()
    {
        close(); // reset the IO position
        bigBlock_ = nullptr;
        if (p_->isMalloced_ == false) {
            long length = p_->getFileLength();
            if (length < 0) { // unable to get the length of remote file, get the whole file content.
                std::string data;
                p_->getDataByRange(-1, -1, data);
                p_->size_ = data.length();
                size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
                p_->blocksMap_  = new BlockMap[nBlocks];
                p_->isMalloced_ = true;
                byte* source = (byte*)data.c_str();
                size_t remain = p_->size_, iBlock = 0, totalRead = 0;
                while (remain) {
                    size_t allow = std::min(remain, p_->blockSize_);
                    p_->blocksMap_[iBlock].populate(&source[totalRead], allow);
                    remain -= allow;
                    totalRead += allow;
                    iBlock++;
                }
            } else if (length == 0) { // file is empty
                throw Error(kerErrorMessage, "the file length is 0");
            } else {
                p_->size_ = (size_t) length;
                size_t nBlocks = (p_->size_ + p_->blockSize_ - 1) / p_->blockSize_;
                p_->blocksMap_  = new BlockMap[nBlocks];
                p_->isMalloced_ = true;
            }
        }
        return 0; // means OK
    }


    void MemIo::Impl::reserve(size_t wcount)
    {
        const size_t need = wcount + idx_;
        size_t blockSize =     32*1024;   // 32768           `
        const size_t maxBlockSize = 4*1024*1024;

        if (!isMalloced_) {
            // Minimum size for 1st block
            size_t size  = std::max(blockSize * (1 + need / blockSize), size_);
            byte* data = (byte*)std::malloc(size);
            if (  data == nullptr ) {
                throw Error(kerMallocFailed);
            }
            if (data_ != nullptr) {
                std::memcpy(data, data_, size_);
            }
            data_ = data;
            sizeAlloced_ = size;
            isMalloced_ = true;
        }

        if (need > size_) {
            if (need > sizeAlloced_) {
                blockSize = 2*sizeAlloced_ ;
                if ( blockSize > maxBlockSize ) blockSize = maxBlockSize ;
                // Allocate in blocks
                size_t want      = blockSize * (1 + need / blockSize );
                data_ = (byte*)std::realloc(data_, want);
                if ( data_ == nullptr ) {
                    throw Error(kerMallocFailed);
                }
                sizeAlloced_ = want;
                isMalloced_ = true;
            }
            size_ = need;
        }
    }


    int64 MemIo::tell() const
    {
        return (long)p_->idx_;
    }


    std::string XPathIo::writeDataToFile(const std::string& orgPath) {
        Protocol prot = fileProtocol(orgPath);

        // generating the name for temp file.
        std::time_t timestamp = std::time(nullptr);
        std::stringstream ss;
        ss << timestamp << XPathIo::TEMP_FILE_EXT;
        std::string path = ss.str();

        if (prot == pStdin) {
            if (isatty(fileno(stdin)))
                throw Error(kerInputDataReadFailed);
#if defined(_MSC_VER) || defined(__MINGW__)
            // convert stdin to binary
            if (_setmode(_fileno(stdin), _O_BINARY) == -1)
                throw Error(kerInputDataReadFailed);
#endif
            std::ofstream fs(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
            // read stdin and write to the temp file.
            char readBuf[100*1024];
            std::streamsize readBufSize = 0;
            do {
                std::cin.read(readBuf, sizeof(readBuf));
                readBufSize = std::cin.gcount();
                if (readBufSize > 0) {
                    fs.write (readBuf, readBufSize);
                }
            } while(readBufSize);
            fs.close();
        } else if (prot == pDataUri) {
            std::ofstream fs(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc);
            // read data uri and write to the temp file.
            size_t base64Pos = orgPath.find("base64,");
            if (base64Pos == std::string::npos) {
                fs.close();
                throw Error(kerErrorMessage, "No base64 data");
            }

            std::string data = orgPath.substr(base64Pos+7);
            char* decodeData = new char[data.length()];
            long size = base64decode(data.c_str(), decodeData, data.length());
            if (size > 0) {
                fs.write(decodeData, size);
                fs.close();
            } else {
                fs.close();
                throw Error(kerErrorMessage, "Unable to decode base 64.");
            }
            delete[] decodeData;
        }

        return path;
    }


    size_t RemoteIo::size() const
    {
        return p_->size_;
    }


    size_t MemIo::read(byte* buf, size_t rcount)
    {
        size_t avail = std::max(p_->size_ - p_->idx_, 0_z);
        size_t allow = std::min(rcount, avail);
        if (p_->data_ == nullptr) {
            throw Error(kerCallFailed, "std::memcpy with src == nullptr");
        }
        std::memcpy(buf, &p_->data_[p_->idx_], allow);
        p_->idx_ += allow;
        if (rcount > avail)
            p_->eof_ = true;
        return allow;
    }


    int64 FileIo::tell() const
    {
        assert(p_->fp_ != 0);
        return std::ftell(p_->fp_);
    }


    size_t writeFile(const DataBuf& buf, const std::wstring& wpath)
    {
        FileIo file(wpath);
        if (file.open("wb") != 0) {
            throw WError(kerFileOpenFailed, wpath, "wb", strError().c_str());
        }
        return file.write(buf.pData_, buf.size_);
    }


    byte* RemoteIo::mmap(bool /*isWriteable*/)
    {
        if ( !bigBlock_ ) {
            size_t nRealData = 0 ;
            size_t blockSize = p_->blockSize_;
            size_t blocks = (p_->size_ + blockSize -1)/blockSize ;
            bigBlock_   = new byte[blocks*blockSize] ;
            for ( size_t block = 0 ; block < blocks ; block ++ ) {
                void* p = p_->blocksMap_[block].getData();
                if  ( p ) {
                    nRealData += blockSize ;
                    memcpy(bigBlock_+(block*blockSize),p,blockSize);
                }
            }
#ifdef EXIV2_DEBUG_MESSAGES
            std::cerr << "RemoteIo::mmap nRealData = " << nRealData << std::endl;
#endif
        }

        return bigBlock_;
    }


BPMDetect::~BPMDetect()
{
    delete[] xcorr;
    delete[] beatcorr_ringbuff;
    delete[] hamw;
    delete[] hamw2;
    delete buffer;
}

Fix issue CVE-2018-17096: Replace assert with runtime exception


void BPMDetect::removeBias()
{
    int i;

    // Remove linear bias: calculate linear regression coefficient
    // 1. calc mean of 'xcorr' and 'i'
    double mean_i = 0;
    double mean_x = 0;
    for (i = windowStart; i < windowLen; i++)
    {
        mean_x += xcorr[i];
    }
    mean_x /= (windowLen - windowStart);
    mean_i = 0.5 * (windowLen - 1 + windowStart);

    // 2. calculate linear regression coefficient
    double b = 0;
    double div = 0;
    for (i = windowStart; i < windowLen; i++)
    {
        double xt = xcorr[i] - mean_x;
        double xi = i - mean_i;
        b += xt * xi;
        div += xi * xi;
    }
    b /= div;

    // subtract linear regression and resolve min. value bias
    float minval = FLT_MAX;   // arbitrary large number
    for (i = windowStart; i < windowLen; i ++)
    {
        xcorr[i] -= (float)(b * i);
        if (xcorr[i] < minval)
        {
            minval = xcorr[i];
        }
    }

    // subtract min.value
    for (i = windowStart; i < windowLen; i ++)
    {
        xcorr[i] -= minval;
    }
}


int BPMDetect::getBeats(float *pos, float *values, int max_num)
{
    int num = beats.size();
    if ((!pos) || (!values)) return num;    // pos or values NULL, return just size

    for (int i = 0; (i < num) && (i < max_num); i++)
    {
        pos[i] = beats[i].pos;
        values[i] = beats[i].strength;
    }
    return num;
}


float IIR2_filter::update(float x)
{
    prev[0] = x;
    double y = x * coeffs[0];

    for (int i = 4; i >= 1; i--)
    {
        y += coeffs[i] * prev[i];
        prev[i] = prev[i - 1];
    }

    prev[3] = y;
    return (float)y;
}


IIR2_filter::IIR2_filter(const double *lpf_coeffs)
{
    memcpy(coeffs, lpf_coeffs, 5 * sizeof(double));
    memset(prev, 0, sizeof(prev));
}


void BPMDetect::updateXCorr(int process_samples)
{
    int offs;
    SAMPLETYPE *pBuffer;

    assert(buffer->numSamples() >= (uint)(process_samples + windowLen));
    assert(process_samples == XCORR_UPDATE_SEQUENCE);

    pBuffer = buffer->ptrBegin();

    // calculate decay factor for xcorr filtering
    float xcorr_decay = (float)pow(0.5, 1.0 / (XCORR_DECAY_TIME_CONSTANT * TARGET_SRATE / process_samples));

    // prescale pbuffer
    float tmp[XCORR_UPDATE_SEQUENCE];
    for (int i = 0; i < process_samples; i++)
    {
        tmp[i] = hamw[i] * hamw[i] * pBuffer[i];
    }

    #pragma omp parallel for
    for (offs = windowStart; offs < windowLen; offs ++)
    {
        double sum;
        int i;

        sum = 0;
        for (i = 0; i < process_samples; i ++)
        {
            sum += tmp[i] * pBuffer[i + offs];  // scaling the sub-result shouldn't be necessary
        }
        xcorr[offs] *= xcorr_decay;   // decay 'xcorr' here with suitable time constant.

        xcorr[offs] += (float)fabs(sum);
    }
}


void BPMDetect::inputSamples(const SAMPLETYPE *samples, int numSamples)
{
    SAMPLETYPE decimated[DECIMATED_BLOCK_SIZE];

    // iterate so that max INPUT_BLOCK_SAMPLES processed per iteration
    while (numSamples > 0)
    {
        int block;
        int decSamples;

        block = (numSamples > INPUT_BLOCK_SIZE) ? INPUT_BLOCK_SIZE : numSamples;

        // decimate. note that converts to mono at the same time
        decSamples = decimate(decimated, samples, block);
        samples += block * channels;
        numSamples -= block;

        buffer->putSamples(decimated, decSamples);
    }

    // when the buffer has enough samples for processing...
    int req = max(windowLen + XCORR_UPDATE_SEQUENCE, 2 * XCORR_UPDATE_SEQUENCE);
    while ((int)buffer->numSamples() >= req)
    {
        // ... update autocorrelations...
        updateXCorr(XCORR_UPDATE_SEQUENCE);
        // ...update beat position calculation...
        updateBeatPos(XCORR_UPDATE_SEQUENCE / 2);
        // ... and remove proceessed samples from the buffer
        int n = XCORR_UPDATE_SEQUENCE / OVERLAP_FACTOR;
        buffer->receiveSamples(n);
    }
}


void MAFilter(float *dest, const float *source, int start, int end, int N)
{
    for (int i = start; i < end; i++)
    {
        int i1 = i - N / 2;
        int i2 = i + N / 2 + 1;
        if (i1 < start) i1 = start;
        if (i2 > end)   i2 = end;

        double sum = 0;
        for (int j = i1; j < i2; j ++)
        {
            sum += source[j];
        }
        dest[i] = (float)(sum / (i2 - i1));
    }
}


void BPMDetect::updateBeatPos(int process_samples)
{
    SAMPLETYPE *pBuffer;

    assert(buffer->numSamples() >= (uint)(process_samples + windowLen));

    pBuffer = buffer->ptrBegin();
    assert(process_samples == XCORR_UPDATE_SEQUENCE / 2);

    //    static double thr = 0.0003;
    double posScale = (double)this->decimateBy / (double)this->sampleRate;
    int resetDur = (int)(0.12 / posScale + 0.5);
    double corrScale = 1.0 / (double)(windowLen - windowStart);

    // prescale pbuffer
    float tmp[XCORR_UPDATE_SEQUENCE / 2];
    for (int i = 0; i < process_samples; i++)
    {
        tmp[i] = hamw2[i] * hamw2[i] * pBuffer[i];
    }

    #pragma omp parallel for
    for (int offs = windowStart; offs < windowLen; offs++)
    {
        double sum = 0;
        for (int i = 0; i < process_samples; i++)
        {
            sum += tmp[i] * pBuffer[offs + i];
        }
        beatcorr_ringbuff[(beatcorr_ringbuffpos + offs) % windowLen] += (float)((sum > 0) ? sum : 0); // accumulate only positive correlations
    }

    int skipstep = XCORR_UPDATE_SEQUENCE / OVERLAP_FACTOR;

    // compensate empty buffer at beginning by scaling coefficient
    float scale = (float)windowLen / (float)(skipstep * init_scaler);
    if (scale > 1.0f)
    {
        init_scaler++;
    }
    else
    {
        scale = 1.0f;
    }

    // detect beats
    for (int i = 0; i < skipstep; i++)
    {
        LONG_SAMPLETYPE max = 0;

        float sum = beatcorr_ringbuff[beatcorr_ringbuffpos];
        sum -= beat_lpf.update(sum);

        if (sum > peakVal)
        {
            // found new local largest value
            peakVal = sum;
            peakPos = pos;
        }
        if (pos > peakPos + resetDur)
        {
            // largest value not updated for 200msec => accept as beat
            peakPos += skipstep;
            if (peakVal > 0)
            {
                // add detected beat to end of "beats" vector
                BEAT temp = { (float)(peakPos * posScale), (float)(peakVal * scale) };
                beats.push_back(temp);
            }

            peakVal = 0;
            peakPos = pos;
        }

        beatcorr_ringbuff[beatcorr_ringbuffpos] = 0;
        pos++;
        beatcorr_ringbuffpos = (beatcorr_ringbuffpos + 1) % windowLen;
    }
}


    void _SaveDebugBeatPos(const char *name, const std::vector<BEAT> &beats)
    {
        printf("\nWriting beat detections data into file %s\n", name);

        FILE *fptr = fopen(name, "wt");
        if (fptr)
        {
            for (uint i = 0; i < beats.size(); i++)
            {
                BEAT b = beats[i];
                fprintf(fptr, "%lf\t%lf\n", b.pos, b.strength);
            }
            fclose(fptr);
        }
    }


float BPMDetect::getBpm()
{
    double peakPos;
    double coeff;
    PeakFinder peakFinder;

    // remove bias from xcorr data
    removeBias();

    coeff = 60.0 * ((double)sampleRate / (double)decimateBy);

    // save bpm debug data if debug data writing enabled
    _SaveDebugData("soundtouch-bpm-xcorr.txt", xcorr, windowStart, windowLen, coeff);

    // Smoothen by N-point moving-average
    float *data = new float[windowLen];
    memset(data, 0, sizeof(float) * windowLen);
    MAFilter(data, xcorr, windowStart, windowLen, MOVING_AVERAGE_N);

    // find peak position
    peakPos = peakFinder.detectPeak(data, windowStart, windowLen);

    // save bpm debug data if debug data writing enabled
    _SaveDebugData("soundtouch-bpm-smoothed.txt", data, windowStart, windowLen, coeff);

    delete[] data;

    assert(decimateBy != 0);
    if (peakPos < 1e-9) return 0.0; // detection failed.

    _SaveDebugBeatPos("soundtouch-detected-beats.txt", beats);

    // calculate BPM
    float bpm = (float)(coeff / peakPos);
    return (bpm >= MIN_BPM && bpm <= MAX_BPM_VALID) ? bpm : 0;
}


int BPMDetect::decimate(SAMPLETYPE *dest, const SAMPLETYPE *src, int numsamples)
{
    int count, outcount;
    LONG_SAMPLETYPE out;

    assert(channels > 0);
    assert(decimateBy > 0);
    outcount = 0;
    for (count = 0; count < numsamples; count ++)
    {
        int j;

        // convert to mono and accumulate
        for (j = 0; j < channels; j ++)
        {
            decimateSum += src[j];
        }
        src += j;

        decimateCount ++;
        if (decimateCount >= decimateBy)
        {
            // Store every Nth sample only
            out = (LONG_SAMPLETYPE)(decimateSum / (decimateBy * channels));
            decimateSum = 0;
            decimateCount = 0;
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
            // check ranges for sure (shouldn't actually be necessary)
            if (out > 32767)
            {
                out = 32767;
            }
            else if (out < -32768)
            {
                out = -32768;
            }
#endif // SOUNDTOUCH_INTEGER_SAMPLES
            dest[outcount] = (SAMPLETYPE)out;
            outcount ++;
        }
    }
    return outcount;
}


void hamming(float *w, int N)
{
    for (int i = 0; i < N; i++)
    {
        w[i] = (float)(0.54 - 0.46 * cos(TWOPI * i / (N - 1)));
    }

}


BPMDetect::BPMDetect(int numChannels, int aSampleRate) :
    beat_lpf(_LPF_coeffs)
{
    beats.reserve(250); // initial reservation to prevent frequent reallocation

    this->sampleRate = aSampleRate;
    this->channels = numChannels;

    decimateSum = 0;
    decimateCount = 0;

    // choose decimation factor so that result is approx. 1000 Hz
    decimateBy = sampleRate / TARGET_SRATE;
    if ((decimateBy <= 0) || (decimateBy * DECIMATED_BLOCK_SIZE < INPUT_BLOCK_SIZE))
    {
        ST_THROW_RT_ERROR("Too small samplerate");
    }

    // Calculate window length & starting item according to desired min & max bpms
    windowLen = (60 * sampleRate) / (decimateBy * MIN_BPM);
    windowStart = (60 * sampleRate) / (decimateBy * MAX_BPM_RANGE);

    assert(windowLen > windowStart);

    // allocate new working objects
    xcorr = new float[windowLen];
    memset(xcorr, 0, windowLen * sizeof(float));

    pos = 0;
    peakPos = 0;
    peakVal = 0;
    init_scaler = 1;
    beatcorr_ringbuffpos = 0;
    beatcorr_ringbuff = new float[windowLen];
    memset(beatcorr_ringbuff, 0, windowLen * sizeof(float));

    // allocate processing buffer
    buffer = new FIFOSampleBuffer();
    // we do processing in mono mode
    buffer->setChannels(1);
    buffer->clear();

    // calculate hamming windows
    hamw = new float[XCORR_UPDATE_SEQUENCE];
    hamming(hamw, XCORR_UPDATE_SEQUENCE);
    hamw2 = new float[XCORR_UPDATE_SEQUENCE / 2];
    hamming(hamw2, XCORR_UPDATE_SEQUENCE / 2);
}


    static void _SaveDebugData(const char *name, const float *data, int minpos, int maxpos, double coeff)
    {
        FILE *fptr = fopen(name, "wt");
        int i;

        if (fptr)
        {
            printf("\nWriting BPM debug data into file %s\n", name);
            for (i = minpos; i < maxpos; i ++)
            {
                fprintf(fptr, "%d\t%.1lf\t%f\n", i, coeff / (double)i, data[i]);
            }
            fclose(fptr);
        }
    }


_hivex_get_iconv (hive_h *h, recode_type t)
{
  gl_lock_lock (h->iconv_cache[t].mutex);
  if (h->iconv_cache[t].handle == NULL) {
    if (t == utf8_to_latin1)
      h->iconv_cache[t].handle = iconv_open ("LATIN1", "UTF-8");
    else if (t == latin1_to_utf8)
      h->iconv_cache[t].handle = iconv_open ("UTF-8", "LATIN1");
    else if (t == utf8_to_utf16le)
      h->iconv_cache[t].handle = iconv_open ("UTF-16LE", "UTF-8");
    else if (t == utf16le_to_utf8)
      h->iconv_cache[t].handle = iconv_open ("UTF-8", "UTF-16LE");
  } else {
    /* reinitialize iconv context */
    iconv (h->iconv_cache[t].handle, NULL, 0, NULL, 0);
  }
  return h->iconv_cache[t].handle;
}

lib/handle.c: Bounds check for block exceeding page length (CVE-2021-3504)

Hives are encoded as fixed-sized pages containing smaller variable-
length blocks:

  +-------------------+-------------------+-------------------+--
  | header            |[ blk ][blk][ blk ]|[blk][blk][blk]    |
  +-------------------+-------------------+-------------------+--

Blocks should not straddle a page boundary.  However because blocks
contain a 32 bit length field it is possible to construct an invalid
hive where the last block in a page overlaps either the next page or
the end of the file:

  +-------------------+-------------------+
  | header            |[ blk ][blk][ blk ..... ]
  +-------------------+-------------------+

Hivex lacked a bounds check and would process the registry.  Because
the rest of the code assumes this situation can never happen it was
possible to have a block containing some field (eg. a registry key
name) which would extend beyond the end of the file.  Hivex mmaps or
mallocs the file, causing hivex to read memory beyond the end of the
mapped region, resulting in reading other memory structures or a
crash.  (Writing beyond the end of the mapped region seems to be
impossible because we always allocate a new page before writing.)

This commit adds a check which rejects the malformed registry on
hivex_open.

Credit: Jeremy Galindo, Sr Security Engineer, Datto.com
Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
Fixes: CVE-2021-3504
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1949687


hivex_close (hive_h *h)
{
  int r;

  DEBUG (1, "hivex_close");

  free (h->bitmap);
  if (!h->writable)
    munmap (h->addr, h->size);
  else
    free (h->addr);
  if (h->fd >= 0)
    r = close (h->fd);
  else
    r = 0;
  free (h->filename);
  for (int t=0; t<nr_recode_types; t++) {
    if (h->iconv_cache[t].handle != NULL) {
      iconv_close (h->iconv_cache[t].handle);
      h->iconv_cache[t].handle = NULL;
    }
  }
  free (h);

  return r;
}


_hivex_release_iconv (hive_h *h, recode_type t)
{
  gl_lock_unlock (h->iconv_cache[t].mutex);
}


hivex_open (const char *filename, int flags)
{
  hive_h *h = NULL;

  assert (sizeof (struct ntreg_header) == 0x1000);
  assert (offsetof (struct ntreg_header, csum) == 0x1fc);

  h = calloc (1, sizeof *h);
  if (h == NULL)
    goto error;

  h->msglvl = flags & HIVEX_OPEN_MSGLVL_MASK;

  const char *debug = getenv ("HIVEX_DEBUG");
  if (debug && STREQ (debug, "1"))
    h->msglvl = 2;

  DEBUG (2, "created handle %p", h);

  h->writable = !!(flags & HIVEX_OPEN_WRITE);
  h->unsafe = !!(flags & HIVEX_OPEN_UNSAFE);
  h->filename = strdup (filename);
  if (h->filename == NULL)
    goto error;

#ifdef O_CLOEXEC
  h->fd = open (filename, O_RDONLY | O_CLOEXEC | O_BINARY);
#else
  h->fd = open (filename, O_RDONLY | O_BINARY);
#endif
  if (h->fd == -1)
    goto error;
#ifndef O_CLOEXEC
  fcntl (h->fd, F_SETFD, FD_CLOEXEC);
#endif

  struct stat statbuf;
  if (fstat (h->fd, &statbuf) == -1)
    goto error;

  h->size = statbuf.st_size;

  if (h->size < 0x2000) {
    SET_ERRNO (EINVAL,
               "%s: file is too small to be a Windows NT Registry hive file",
               filename);
    goto error;
  }

  if (!h->writable) {
    h->addr = mmap (NULL, h->size, PROT_READ, MAP_SHARED, h->fd, 0);
    if (h->addr == MAP_FAILED)
      goto error;

    DEBUG (2, "mapped file at %p", h->addr);
  } else {
    h->addr = malloc (h->size);
    if (h->addr == NULL)
      goto error;

    if (full_read (h->fd, h->addr, h->size) < h->size)
      goto error;

    /* We don't need the file descriptor along this path, since we
     * have read all the data.
     */
    if (close (h->fd) == -1)
      goto error;
    h->fd = -1;
  }

  /* Check header. */
  if (h->hdr->magic[0] != 'r' ||
      h->hdr->magic[1] != 'e' ||
      h->hdr->magic[2] != 'g' ||
      h->hdr->magic[3] != 'f') {
    SET_ERRNO (ENOTSUP,
               "%s: not a Windows NT Registry hive file", filename);
    goto error;
  }

  /* Check major version. */
  uint32_t major_ver = le32toh (h->hdr->major_ver);
  if (major_ver != 1) {
    SET_ERRNO (ENOTSUP,
               "%s: hive file major version %" PRIu32 " (expected 1)",
               filename, major_ver);
    goto error;
  }

  h->bitmap = calloc (1 + h->size / 32, 1);
  if (h->bitmap == NULL)
    goto error;

  /* Header checksum. */
  uint32_t sum = header_checksum (h);
  if (sum != le32toh (h->hdr->csum)) {
    SET_ERRNO (EINVAL, "%s: bad checksum in hive header", filename);
    goto error;
  }

  for (int t=0; t<nr_recode_types; t++) {
    gl_lock_init (h->iconv_cache[t].mutex);
    h->iconv_cache[t].handle = NULL;
  }

  /* Last modified time. */
  h->last_modified = le64toh ((int64_t) h->hdr->last_modified);

  if (h->msglvl >= 2) {
    char *name = _hivex_recode (h, utf16le_to_utf8,
                                h->hdr->name, 64, NULL);

    fprintf (stderr,
             "hivex_open: header fields:\n"
             "  file version             %" PRIu32 ".%" PRIu32 "\n"
             "  sequence nos             %" PRIu32 " %" PRIu32 "\n"
             "    (sequences nos should match if hive was synched at shutdown)\n"
             "  last modified            %" PRIi64 "\n"
             "    (Windows filetime, x 100 ns since 1601-01-01)\n"
             "  original file name       %s\n"
             "    (only 32 chars are stored, name is probably truncated)\n"
             "  root offset              0x%x + 0x1000\n"
             "  end of last page         0x%x + 0x1000 (total file size 0x%zx)\n"
             "  checksum                 0x%x (calculated 0x%x)\n",
             major_ver, le32toh (h->hdr->minor_ver),
             le32toh (h->hdr->sequence1), le32toh (h->hdr->sequence2),
             h->last_modified,
             name ? name : "(conversion failed)",
             le32toh (h->hdr->offset),
             le32toh (h->hdr->blocks), h->size,
             le32toh (h->hdr->csum), sum);
    free (name);
  }

  h->rootoffs = le32toh (h->hdr->offset) + 0x1000;
  h->endpages = le32toh (h->hdr->blocks) + 0x1000;

  DEBUG (2, "root offset = 0x%zx", h->rootoffs);

  /* We'll set this flag when we see a block with the root offset (ie.
   * the root block).
   */
  int seen_root_block = 0, bad_root_block = 0;

  /* Collect some stats. */
  size_t pages = 0;           /* Number of hbin pages read. */
  size_t smallest_page = SIZE_MAX, largest_page = 0;
  size_t blocks = 0;          /* Total number of blocks found. */
  size_t smallest_block = SIZE_MAX, largest_block = 0, blocks_bytes = 0;
  size_t used_blocks = 0;     /* Total number of used blocks found. */
  size_t used_size = 0;       /* Total size (bytes) of used blocks. */

  /* Read the pages and blocks.  The aim here is to be robust against
   * corrupt or malicious registries.  So we make sure the loops
   * always make forward progress.  We add the address of each block
   * we read to a hash table so pointers will only reference the start
   * of valid blocks.
   */
  size_t off;
  struct ntreg_hbin_page *page;
  for (off = 0x1000; off < h->size; off += le32toh (page->page_size)) {
    if (off >= h->endpages)
      break;

    page = (struct ntreg_hbin_page *) ((char *) h->addr + off);
    if (page->magic[0] != 'h' ||
        page->magic[1] != 'b' ||
        page->magic[2] != 'i' ||
        page->magic[3] != 'n') {

      if (!h->unsafe) {
        SET_ERRNO (ENOTSUP,
                   "%s: trailing garbage at end of file "
                   "(at 0x%zx, after %zu pages)",
                   filename, off, pages);
        goto error;
      }

      DEBUG (2,
             "page not found at expected offset 0x%zx, "
             "seeking until one is found or EOF is reached",
             off);

      int found = 0;
      while (off < h->size) {
        off += 0x1000;

        if (off >= h->endpages)
          break;

        page = (struct ntreg_hbin_page *) ((char *) h->addr + off);
        if (page->magic[0] == 'h' &&
            page->magic[1] == 'b' &&
            page->magic[2] == 'i' &&
            page->magic[3] == 'n') {
          DEBUG (2, "found next page by seeking at 0x%zx", off);
          found = 1;
          break;
        }
      }

      if (!found) {
        DEBUG (2, "page not found and end of pages section reached");
        break;
      }
    }

    size_t page_size = le32toh (page->page_size);
    DEBUG (2, "page at 0x%zx, size %zu", off, page_size);
    pages++;
    if (page_size < smallest_page) smallest_page = page_size;
    if (page_size > largest_page) largest_page = page_size;

    if (page_size <= sizeof (struct ntreg_hbin_page) ||
        (page_size & 0x0fff) != 0) {
      SET_ERRNO (ENOTSUP,
                 "%s: page size %zu at 0x%zx, bad registry",
                 filename, page_size, off);
      goto error;
    }

    if (off + page_size > h->size) {
      SET_ERRNO (ENOTSUP,
                 "%s: page size %zu at 0x%zx extends beyond end of file, bad registry",
                 filename, page_size, off);
      goto error;
    }

    size_t page_offset = le32toh(page->offset_first) + 0x1000;

    if (page_offset != off) {
      SET_ERRNO (ENOTSUP,
                 "%s: declared page offset (0x%zx) does not match computed "
                 "offset (0x%zx), bad registry",
                 filename, page_offset, off);
      goto error;
    }

    /* Read the blocks in this page. */
    size_t blkoff;
    struct ntreg_hbin_block *block;
    size_t seg_len;
    for (blkoff = off + 0x20;
         blkoff < off + page_size;
         blkoff += seg_len) {
      blocks++;

      int is_root = blkoff == h->rootoffs;
      if (is_root)
        seen_root_block = 1;

      block = (struct ntreg_hbin_block *) ((char *) h->addr + blkoff);
      int used;
      seg_len = block_len (h, blkoff, &used);
/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78665 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-overflow"
      if (seg_len <= 4 || (seg_len & 3) != 0) {
#pragma GCC diagnostic pop
        if (is_root || !h->unsafe) {
          SET_ERRNO (ENOTSUP,
                     "%s, the block at 0x%zx size %" PRIu32
                     " <= 4 or not a multiple of 4, bad registry",
                     filename, blkoff, le32toh (block->seg_len));
          goto error;
        } else {
          DEBUG (2,
                 "%s: block at 0x%zx has invalid size %" PRIu32 ", skipping",
                 filename, blkoff, le32toh (block->seg_len));
          break;
        }
      }

      if (blkoff + seg_len > off + page_size) {
        SET_ERRNO (ENOTSUP,
                   "%s, the block at 0x%zx size %" PRIu32
                   " extends beyond the current page, bad registry",
                   filename, blkoff, le32toh (block->seg_len));
        goto error;
      }

      if (h->msglvl >= 2) {
        unsigned char *id = (unsigned char *) block->id;
        int id0 = id[0], id1 = id[1];

        fprintf (stderr, "%s: %s: "
                 "%s block id %d,%d (%c%c) at 0x%zx size %zu%s\n",
                 "hivex", __func__,
                 used ? "used" : "free",
                 id0, id1,
                 c_isprint (id0) ? id0 : '.',
                 c_isprint (id1) ? id1 : '.',
                 blkoff,
                 seg_len, is_root ? " (root)" : "");
      }

      blocks_bytes += seg_len;
      if (seg_len < smallest_block) smallest_block = seg_len;
      if (seg_len > largest_block) largest_block = seg_len;

      if (is_root && !used)
        bad_root_block = 1;

      if (used) {
        used_blocks++;
        used_size += seg_len;

        /* Root block must be an nk-block. */
        if (is_root && (block->id[0] != 'n' || block->id[1] != 'k'))
          bad_root_block = 1;

        /* Note this blkoff is a valid address. */
        BITMAP_SET (h->bitmap, blkoff);
      }
    }
  }

  if (!seen_root_block) {
    SET_ERRNO (ENOTSUP, "%s: no root block found", filename);
    goto error;
  }

  if (bad_root_block) {
    SET_ERRNO (ENOTSUP, "%s: bad root block (free or not nk)", filename);
    goto error;
  }

  DEBUG (1, "successfully read Windows Registry hive file:\n"
         "  pages:          %zu [sml: %zu, lge: %zu]\n"
         "  blocks:         %zu [sml: %zu, avg: %zu, lge: %zu]\n"
         "  blocks used:    %zu\n"
         "  bytes used:     %zu",
         pages, smallest_page, largest_page,
         blocks, smallest_block, blocks_bytes / blocks, largest_block,
         used_blocks, used_size);

  return h;

 error:;
  int err = errno;
  if (h) {
    free (h->bitmap);
    if (h->addr && h->size && h->addr != MAP_FAILED) {
      if (!h->writable)
        munmap (h->addr, h->size);
      else
        free (h->addr);
    }
    if (h->fd >= 0)
      close (h->fd);
    free (h->filename);
    free (h);
  }
  errno = err;
  return NULL;
}


static inline void ConvertDisplayP3ToXYZ(const double red,const double green,
  const double blue,double *X,double *Y,double *Z)
{
  double
    b,
    g,
    r;

  /*
    Convert Display P3 to XYZ colorspace.
  */
  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  r=QuantumScale*DecodePixelGamma(red);
  g=QuantumScale*DecodePixelGamma(green);
  b=QuantumScale*DecodePixelGamma(blue);
  *X=0.4865709486482162*r+0.26566769316909306*g+0.1982172852343625*b;
  *Y=0.2289745640697488*r+0.69173852183650640*g+0.0792869140937450*b;
  *Z=0.0000000000000000*r+0.04511338185890264*g+1.0439443689009760*b;
}


static inline void ConvertXYZToRGB(const double X,const double Y,const double Z,
  double *red,double *green,double *blue)
{
  double
    b,
    g,
    r;

  assert(red != (double *) NULL);
  assert(green != (double *) NULL);
  assert(blue != (double *) NULL);
  r=3.2404542*X-1.5371385*Y-0.4985314*Z;
  g=(-0.9692660)*X+1.8760108*Y+0.0415560*Z;
  b=0.0556434*X-0.2040259*Y+1.0572252*Z;
  *red=EncodePixelGamma(QuantumRange*r);
  *green=EncodePixelGamma(QuantumRange*g);
  *blue=EncodePixelGamma(QuantumRange*b);
}


static inline void ConvertXYZToAdobe98(const double X,const double Y,
  const double Z,double *red,double *green,double *blue)
{
  double
    b,
    g,
    r;

  assert(red != (double *) NULL);
  assert(green != (double *) NULL);
  assert(blue != (double *) NULL);
  r=2.041587903810746500*X-0.56500697427885960*Y-0.34473135077832956*Z;
  g=(-0.969243636280879500)*X+1.87596750150772020*Y+0.04155505740717557*Z;
  b=0.013444280632031142*X-0.11836239223101838*Y+1.01517499439120540*Z;
  *red=EncodePixelGamma(QuantumRange*r);
  *green=EncodePixelGamma(QuantumRange*g);
  *blue=EncodePixelGamma(QuantumRange*b);
}


static inline void ConvertLuvToXYZ(const double L,const double u,const double v,
  double *X,double *Y,double *Z)
{
  double
    gamma;

  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  if (L > (CIEK*CIEEpsilon))
    *Y=(double) pow((L+16.0)/116.0,3.0);
  else
    *Y=L/CIEK;
  gamma=PerceptibleReciprocal((((52.0*L*PerceptibleReciprocal(u+13.0*L*
    (4.0*D65X/(D65X+15.0*D65Y+3.0*D65Z))))-1.0)/3.0)-(-1.0/3.0));
  *X=gamma*((*Y*((39.0*L*PerceptibleReciprocal(v+13.0*L*(9.0*D65Y/
    (D65X+15.0*D65Y+3.0*D65Z))))-5.0))+5.0*(*Y));
  *Z=(*X*(((52.0*L*PerceptibleReciprocal(u+13.0*L*(4.0*D65X/
    (D65X+15.0*D65Y+3.0*D65Z))))-1.0)/3.0))-5.0*(*Y);
}


static inline void ConvertAdobe98ToXYZ(const double red,const double green,
  const double blue,double *X,double *Y,double *Z)
{
  double
    b,
    g,
    r;

  /*
    Convert Adobe '98 to XYZ colorspace.
  */
  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  r=QuantumScale*DecodePixelGamma(red);
  g=QuantumScale*DecodePixelGamma(green);
  b=QuantumScale*DecodePixelGamma(blue);
  *X=0.57666904291013050*r+0.18555823790654630*g+0.18822864623499470*b;
  *Y=0.29734497525053605*r+0.62736356625546610*g+0.07529145849399788*b;
  *Z=0.02703136138641234*r+0.07068885253582723*g+0.99133753683763880*b;
}


static inline void ConvertXYZToDisplayP3(const double X,const double Y,
  const double Z,double *red,double *green,double *blue)
{
  double
    b,
    g,
    r;

  assert(red != (double *) NULL);
  assert(green != (double *) NULL);
  assert(blue != (double *) NULL);
  r=2.49349691194142500*X-0.93138361791912390*Y-0.402710784450716840*Z;
  g=(-0.82948896956157470)*X+1.76266406031834630*Y+0.023624685841943577*Z;
  b=0.03584583024378447*X-0.07617238926804182*Y+0.956884524007687200*Z;
  *red=EncodePixelGamma(QuantumRange*r);
  *green=EncodePixelGamma(QuantumRange*g);
  *blue=EncodePixelGamma(QuantumRange*b);
}


static inline void ConvertProPhotoToXYZ(const double red,const double green,
  const double blue,double *X,double *Y,double *Z)
{
  double
    b,
    g,
    r;

  /*
    Convert ProPhoto to XYZ colorspace.
  */
  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  r=QuantumScale*DecodePixelGamma(red);
  g=QuantumScale*DecodePixelGamma(green);
  b=QuantumScale*DecodePixelGamma(blue);
  *X=0.7977604896723027*r+0.13518583717574031*g+0.03134934958152480000*b;
  *Y=0.2880711282292934*r+0.71184321781010140*g+0.00008565396060525902*b;
  *Z=0.0000000000000000*r+0.00000000000000000*g+0.82510460251046010000*b;
}


static inline void ConvertXYZToLab(const double X,const double Y,const double Z,
  double *L,double *a,double *b)
{
  double
    x,
    y,
    z;

  assert(L != (double *) NULL);
  assert(a != (double *) NULL);
  assert(b != (double *) NULL);
  if ((X/D65X) > CIEEpsilon)
    x=pow(X/D65X,1.0/3.0);
  else
    x=(CIEK*X/D65X+16.0)/116.0;
  if ((Y/D65Y) > CIEEpsilon)
    y=pow(Y/D65Y,1.0/3.0);
  else
    y=(CIEK*Y/D65Y+16.0)/116.0;
  if ((Z/D65Z) > CIEEpsilon)
    z=pow(Z/D65Z,1.0/3.0);
  else
    z=(CIEK*Z/D65Z+16.0)/116.0;
  *L=((116.0*y)-16.0)/100.0;
  *a=(500.0*(x-y))/255.0+0.5;
  *b=(200.0*(y-z))/255.0+0.5;
}


static inline void ConvertXYZToLuv(const double X,const double Y,const double Z,
  double *L,double *u,double *v)
{
  double
    alpha;

  assert(L != (double *) NULL);
  assert(u != (double *) NULL);
  assert(v != (double *) NULL);
  if ((Y/D65Y) > CIEEpsilon)
    *L=(double) (116.0*pow(Y/D65Y,1.0/3.0)-16.0);
  else
    *L=CIEK*(Y/D65Y);
  alpha=PerceptibleReciprocal(X+15.0*Y+3.0*Z);
  *u=13.0*(*L)*((4.0*alpha*X)-(4.0*D65X/(D65X+15.0*D65Y+3.0*D65Z)));
  *v=13.0*(*L)*((9.0*alpha*Y)-(9.0*D65Y/(D65X+15.0*D65Y+3.0*D65Z)));
  *L/=100.0;
  *u=(*u+134.0)/354.0;
  *v=(*v+140.0)/262.0;
}


static inline void ConvertRGBToXYZ(const double red,const double green,
  const double blue,double *X,double *Y,double *Z)
{
  double
    b,
    g,
    r;

  /*
    Convert RGB to XYZ colorspace.
  */
  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  r=QuantumScale*DecodePixelGamma(red);
  g=QuantumScale*DecodePixelGamma(green);
  b=QuantumScale*DecodePixelGamma(blue);
  *X=0.4124564*r+0.3575761*g+0.1804375*b;
  *Y=0.2126729*r+0.7151522*g+0.0721750*b;
  *Z=0.0193339*r+0.1191920*g+0.9503041*b;
}


static inline void ConvertXYZToProPhoto(const double X,const double Y,
  const double Z,double *red,double *green,double *blue)
{
  double
    b,
    g,
    r;

  assert(red != (double *) NULL);
  assert(green != (double *) NULL);
  assert(blue != (double *) NULL);
  r=1.3457989731028281*X-0.25558010007997534*Y-0.05110628506753401*Z;
  g=(-0.5446224939028347)*X+1.50823274131327810*Y+0.02053603239147973*Z;
  b=0.0000000000000000*X+0.0000000000000000*Y+1.21196754563894540*Z;
  *red=EncodePixelGamma(QuantumRange*r);
  *green=EncodePixelGamma(QuantumRange*g);
  *blue=EncodePixelGamma(QuantumRange*b);
}


static inline void ConvertLabToXYZ(const double L,const double a,const double b,
  double *X,double *Y,double *Z)
{
  double
    x,
    y,
    z;

  assert(X != (double *) NULL);
  assert(Y != (double *) NULL);
  assert(Z != (double *) NULL);
  y=(L+16.0)/116.0;
  x=y+a/500.0;
  z=y-b/200.0;
  if ((x*x*x) > CIEEpsilon)
    x=(x*x*x);
  else
    x=(116.0*x-16.0)/CIEK;
  if (L > (CIEK*CIEEpsilon))
    y=(y*y*y);
  else
    y=L/CIEK;
  if ((z*z*z) > CIEEpsilon)
    z=(z*z*z);
  else
    z=(116.0*z-16.0)/CIEK;
  *X=D65X*x;
  *Y=D65Y*y;
  *Z=D65Z*z;
}


ExpressionDateToParts::ExpressionDateToParts(ExpressionContext* const expCtx,
                                             intrusive_ptr<Expression> date,
                                             intrusive_ptr<Expression> timeZone,
                                             intrusive_ptr<Expression> iso8601)
    : Expression(expCtx, {std::move(date), std::move(timeZone), std::move(iso8601)}),
      _date(_children[0]),
      _timeZone(_children[1]),
      _iso8601(_children[2]) {}

SERVER-49404 Enforce additional checks in $arrayToObject


auto CommonRegexParse(ExpressionContext* const expCtx,
                      BSONElement expr,
                      const VariablesParseState& vpsIn,
                      StringData opName) {
    uassert(51103,
            str::stream() << opName
                          << " expects an object of named arguments but found: " << expr.type(),
            expr.type() == BSONType::Object);

    struct {
        boost::intrusive_ptr<Expression> input;
        boost::intrusive_ptr<Expression> regex;
        boost::intrusive_ptr<Expression> options;
    } parsed;
    for (auto&& elem : expr.embeddedObject()) {
        const auto field = elem.fieldNameStringData();
        if (field == "input"_sd) {
            parsed.input = Expression::parseOperand(expCtx, elem, vpsIn);
        } else if (field == "regex"_sd) {
            parsed.regex = Expression::parseOperand(expCtx, elem, vpsIn);
        } else if (field == "options"_sd) {
            parsed.options = Expression::parseOperand(expCtx, elem, vpsIn);
        } else {
            uasserted(31024,
                      str::stream() << opName << " found an unknown argument: "
                                    << elem.fieldNameStringData());
        }
    }
    uassert(31022, str::stream() << opName << " requires 'input' parameter", parsed.input);
    uassert(31023, str::stream() << opName << " requires 'regex' parameter", parsed.regex);

    return parsed;
}


boost::intrusive_ptr<Expression> ExpressionRegexFindAll::parse(ExpressionContext* const expCtx,
                                                               BSONElement expr,
                                                               const VariablesParseState& vpsIn) {
    auto opName = "$regexFindAll"_sd;
    auto [input, regex, options] = CommonRegexParse(expCtx, expr, vpsIn, opName);
    return new ExpressionRegexFindAll(
        expCtx, std::move(input), std::move(regex), std::move(options), opName);
}


intrusive_ptr<Expression> ExpressionDateToString::parse(ExpressionContext* const expCtx,
                                                        BSONElement expr,
                                                        const VariablesParseState& vps) {
    verify(expr.fieldNameStringData() == "$dateToString");

    uassert(18629,
            "$dateToString only supports an object as its argument",
            expr.type() == BSONType::Object);

    BSONElement formatElem, dateElem, timeZoneElem, onNullElem;
    for (auto&& arg : expr.embeddedObject()) {
        auto field = arg.fieldNameStringData();

        if (field == "format"_sd) {
            formatElem = arg;
        } else if (field == "date"_sd) {
            dateElem = arg;
        } else if (field == "timezone"_sd) {
            timeZoneElem = arg;
        } else if (field == "onNull"_sd) {
            onNullElem = arg;
        } else {
            uasserted(18534,
                      str::stream()
                          << "Unrecognized argument to $dateToString: " << arg.fieldName());
        }
    }

    uassert(18628, "Missing 'date' parameter to $dateToString", !dateElem.eoo());

    return new ExpressionDateToString(expCtx,
                                      parseOperand(expCtx, dateElem, vps),
                                      formatElem ? parseOperand(expCtx, formatElem, vps) : nullptr,
                                      timeZoneElem ? parseOperand(expCtx, timeZoneElem, vps)
                                                   : nullptr,
                                      onNullElem ? parseOperand(expCtx, onNullElem, vps) : nullptr);
}


intrusive_ptr<Expression> ExpressionLet::parse(ExpressionContext* const expCtx,
                                               BSONElement expr,
                                               const VariablesParseState& vpsIn) {
    verify(expr.fieldNameStringData() == "$let");

    uassert(16874, "$let only supports an object as its argument", expr.type() == Object);
    const BSONObj args = expr.embeddedObject();

    // varsElem must be parsed before inElem regardless of BSON order.
    BSONElement varsElem;
    BSONElement inElem;
    for (auto&& arg : args) {
        if (arg.fieldNameStringData() == "vars") {
            varsElem = arg;
        } else if (arg.fieldNameStringData() == "in") {
            inElem = arg;
        } else {
            uasserted(16875,
                      str::stream() << "Unrecognized parameter to $let: " << arg.fieldName());
        }
    }

    uassert(16876, "Missing 'vars' parameter to $let", !varsElem.eoo());
    uassert(16877, "Missing 'in' parameter to $let", !inElem.eoo());

    // parse "vars"
    VariablesParseState vpsSub(vpsIn);  // vpsSub gets our vars, vpsIn doesn't.
    VariableMap vars;
    std::vector<boost::intrusive_ptr<Expression>> children;
    auto&& varsObj = varsElem.embeddedObjectUserCheck();
    for (auto&& varElem : varsObj)
        children.push_back(parseOperand(expCtx, varElem, vpsIn));

    // Make a place in the vector for "in".
    auto& inPtr = children.emplace_back(nullptr);

    std::vector<boost::intrusive_ptr<Expression>>::size_type index = 0;
    std::vector<Variables::Id> orderedVariableIds;
    for (auto&& varElem : varsObj) {
        const string varName = varElem.fieldName();
        Variables::validateNameForUserWrite(varName);
        Variables::Id id = vpsSub.defineVariable(varName);

        orderedVariableIds.push_back(id);

        vars.emplace(id, NameAndExpression{varName, children[index]});  // only has outer vars
        ++index;
    }

    // parse "in"
    inPtr = parseOperand(expCtx, inElem, vpsSub);  // has our vars

    return new ExpressionLet(
        expCtx, std::move(vars), std::move(children), std::move(orderedVariableIds));
}


    static Value performFormatDouble(ExpressionContext* const expCtx, Value inputValue) {
        double doubleValue = inputValue.getDouble();

        if (std::isinf(doubleValue)) {
            return Value(std::signbit(doubleValue) ? "-Infinity"_sd : "Infinity"_sd);
        } else if (std::isnan(doubleValue)) {
            return Value("NaN"_sd);
        } else if (doubleValue == 0.0 && std::signbit(doubleValue)) {
            return Value("-0"_sd);
        } else {
            return Value(static_cast<std::string>(str::stream() << doubleValue));
        }
    }