<commit_before>// Copyright 2008-present Contributors to the OpenImageIO project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
#include "iff_pvt.h"
#include <OpenImageIO/dassert.h>

OIIO_PLUGIN_NAMESPACE_BEGIN

using namespace iff_pvt;

#define STRINGIZE2(a) #a
#define STRINGIZE(a) STRINGIZE2(a)

bool
IffFileHeader::read_header(FILE* fd, std::string& err)
{
    uint8_t type[4];
    uint32_t size;
    uint32_t chunksize;
    uint32_t tbhdsize;
    uint32_t flags;
    uint16_t bytes;
    uint16_t prnum;
    uint16_t prden;

    // read FOR4 <size> CIMG.
    err.clear();
    for (;;) {
        // get type and length
        if (!read_typesize(fd, type, size)) {
            err = "could not read type/size @ L" STRINGIZE(__LINE__);
            return false;
        }

        chunksize = align_size(size, 4);

        if (type[0] == 'F' && type[1] == 'O' && type[2] == 'R'
            && type[3] == '4') {
            // get type
            if (!fread(&type, 1, sizeof(type), fd)) {
                err = "could not read FDR4 type @ L" STRINGIZE(__LINE__);
                return false;
            }

            // check if CIMG
            if (type[0] == 'C' && type[1] == 'I' && type[2] == 'M'
                && type[3] == 'G') {
                // read TBHD.
                for (;;) {
                    if (!read_typesize(fd, type, size)) {
                        err = "could not read CIMG length @ L" STRINGIZE(
                            __LINE__);
                        return false;
                    }

                    chunksize = align_size(size, 4);

                    if (type[0] == 'T' && type[1] == 'B' && type[2] == 'H'
                        && type[3] == 'D') {
                        tbhdsize = size;

                        // test if table header size is correct
                        if (tbhdsize != 24 && tbhdsize != 32) {
                            err = "bad table header @ L" STRINGIZE(__LINE__);
                            return false;  // bad table header
                        }

                        // get width and height
                        if (!read(fd, width) || !read(fd, height)
                            || !read(fd, prnum) || !read(fd, prden)
                            || !read(fd, flags) || !read(fd, bytes)
                            || !read(fd, tiles) || !read(fd, compression)) {
                            err = "@ L" STRINGIZE(__LINE__);
                            return false;
                        }

                        // get xy
                        if (tbhdsize == 32) {
                            if (!read(fd, x) || !read(fd, y)) {
                                err = "could not get xy @ L" STRINGIZE(
                                    __LINE__);
                                return false;
                            }
                        } else {
                            x = 0;
                            y = 0;
                        }

                        // tiles
                        if (tiles == 0) {
                            err = "non-tiles not supported @ L" STRINGIZE(
                                __LINE__);
                            return false;
                        }  // non-tiles not supported

                        // 0 no compression
                        // 1 RLE compression
                        // 2 QRL (not supported)
                        // 3 QR4 (not supported)
                        if (compression > 1) {
                            err = "only RLE compression is supported @ L" STRINGIZE(
                                __LINE__);
                            return false;
                        }

                        // test format.
                        if (flags & RGBA) {
                            // test if black is set
                            OIIO_DASSERT(!(flags & BLACK));

                            // test for RGB channels.
                            if (flags & RGB)
                                pixel_channels = 3;

                            // test for alpha channel
                            if (flags & ALPHA)
                                pixel_channels++;

                            // test pixel bits
                            if (!bytes)
                                pixel_bits = bytes ? 16 : 8;
                        }

                        // Z format.
                        else if (flags & ZBUFFER) {
                            pixel_channels = 1;
                            pixel_bits     = 32;  // 32bit
                            // NOTE: Z_F32 support - not supported
                            OIIO_DASSERT(bytes == 0);
                        }

                        // read AUTH, DATE or FOR4

                        for (;;) {
                            // get type
                            if (!read_typesize(fd, type, size)) {
                                err = "could not read type/size @ L" STRINGIZE(
                                    __LINE__);
                                return false;
                            }

                            chunksize = align_size(size, 4);

                            if (type[0] == 'A' && type[1] == 'U'
                                && type[2] == 'T' && type[3] == 'H') {
                                std::vector<char> str(chunksize);
                                if (!fread(&str[0], 1, chunksize, fd)) {
                                    err = "could not read author @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                                author = std::string(&str[0], size);
                            } else if (type[0] == 'D' && type[1] == 'A'
                                       && type[2] == 'T' && type[3] == 'E') {
                                std::vector<char> str(chunksize);
                                if (!fread(&str[0], 1, chunksize, fd)) {
                                    err = "could not read date @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                                date = std::string(&str[0], size);
                            } else if (type[0] == 'F' && type[1] == 'O'
                                       && type[2] == 'R' && type[3] == '4') {
                                if (!fread(&type, 1, sizeof(type), fd)) {
                                    err = "could not read FOR4 type @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }

                                // check if CIMG
                                if (type[0] == 'T' && type[1] == 'B'
                                    && type[2] == 'M' && type[3] == 'P') {
                                    // tbmp position for later user in in
                                    // read_native_tile

                                    tbmp_start = ftell(fd);

                                    // read first RGBA block to detect tile size.

                                    for (unsigned int t = 0; t < tiles; t++) {
                                        if (!read_typesize(fd, type, size)) {
                                            err = "xxx @ L" STRINGIZE(__LINE__);
                                            return false;
                                        }
                                        chunksize = align_size(size, 4);

                                        // check if RGBA
                                        if (type[0] == 'R' && type[1] == 'G'
                                            && type[2] == 'B'
                                            && type[3] == 'A') {
                                            // get tile coordinates.
                                            uint16_t xmin, xmax, ymin, ymax;
                                            if (!read(fd, xmin)
                                                || !read(fd, ymin)
                                                || !read(fd, xmax)
                                                || !read(fd, ymax)) {
                                                err = "xxx @ L" STRINGIZE(
                                                    __LINE__);
                                                return false;
                                            }

                                            // check tile
                                            if (xmin > xmax || ymin > ymax
                                                || xmax >= width
                                                || ymax >= height) {
                                                err = "tile min/max nonsensical @ L" STRINGIZE(
                                                    __LINE__);
                                                return false;
                                            }

                                            // set tile width and height
                                            tile_width  = xmax - xmin + 1;
                                            tile_height = ymax - ymin + 1;

                                            // done, return
                                            return true;
                                        }

                                        // skip to the next block.
                                        if (fseek(fd, chunksize, SEEK_CUR)) {
                                            err = "could not fseek @ L" STRINGIZE(
                                                __LINE__);
                                            return false;
                                        }
                                    }
                                } else {
                                    // skip to the next block.
                                    if (fseek(fd, chunksize, SEEK_CUR)) {
                                        err = "could not fseek @ L" STRINGIZE(
                                            __LINE__);
                                        return false;
                                    }
                                }
                            } else {
                                // skip to the next block.
                                if (fseek(fd, chunksize, SEEK_CUR)) {
                                    err = "could not fseek @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                            }
                        }
                        // TBHD done, break
                        break;
                    }

                    // skip to the next block.
                    if (fseek(fd, chunksize, SEEK_CUR)) {
                        err = "could not fseek @ L" STRINGIZE(__LINE__);
                        return false;
                    }
                }
            }
        }
        // skip to the next block.
        if (fseek(fd, chunksize, SEEK_CUR)) {
            err = "could not fseek @ L" STRINGIZE(__LINE__);
            return false;
        }
    }
    err = "unknown error, ended early @ L" STRINGIZE(__LINE__);
    return false;
}

bool
IffOutput::write_header(IffFileHeader& header)
{
    // write 'FOR4' type, with 0 length for now (to reserve it)
    if (!(write_str("FOR4") && write_int(0)))
        return false;

    // write 'CIMG' type
    if (!write_str("CIMG"))
        return false;

    // write 'TBHD' type
    if (!write_str("TBHD"))
        return false;

    // 'TBHD' length, 32 bytes
    if (!write_int(32))
        return false;

    if (!write_int(header.width) || !write_int(header.height))
        return false;

    // write prnum and prden (pixel aspect ratio? -- FIXME)
    if (!write_short(1) || !write_short(1))
        return false;

    // write flags and channels
    if (!write_int(header.pixel_channels == 3 ? RGB : RGBA)
        || !write_short(header.pixel_bits == 8 ? 0 : 1)
        || !write_short(header.tiles))
        return false;

    // write compression
    // 0 no compression
    // 1 RLE compression
    // 2 QRL (not supported)
    // 3 QR4 (not supported)
    if (!write_int(header.compression))
        return false;

    // write x and y
    if (!write_int(header.x) || !write_int(header.y))
        return false;

    // Write metadata
    write_meta_string("AUTH", header.author);
    write_meta_string("DATE", header.date);

    // for4 position for later user in close
    header.for4_start = ftell(m_fd);

    // write 'FOR4' type, with 0 length to reserve it for now
    if (!write_str("FOR4") || !write_int(0))
        return false;

    // write 'TBMP' type
    if (!write_str("TBMP"))
        return false;

    return true;
}

OIIO_PLUGIN_NAMESPACE_END
<commit_msg>Fix broken read of 16 bit iff files (#2736)<commit_after>// Copyright 2008-present Contributors to the OpenImageIO project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
#include "iff_pvt.h"
#include <OpenImageIO/dassert.h>

OIIO_PLUGIN_NAMESPACE_BEGIN

using namespace iff_pvt;

#define STRINGIZE2(a) #a
#define STRINGIZE(a) STRINGIZE2(a)

bool
IffFileHeader::read_header(FILE* fd, std::string& err)
{
    uint8_t type[4];
    uint32_t size;
    uint32_t chunksize;
    uint32_t tbhdsize;
    uint32_t flags;
    uint16_t bytes;
    uint16_t prnum;
    uint16_t prden;

    // read FOR4 <size> CIMG.
    err.clear();
    for (;;) {
        // get type and length
        if (!read_typesize(fd, type, size)) {
            err = "could not read type/size @ L" STRINGIZE(__LINE__);
            return false;
        }

        chunksize = align_size(size, 4);

        if (type[0] == 'F' && type[1] == 'O' && type[2] == 'R'
            && type[3] == '4') {
            // get type
            if (!fread(&type, 1, sizeof(type), fd)) {
                err = "could not read FDR4 type @ L" STRINGIZE(__LINE__);
                return false;
            }

            // check if CIMG
            if (type[0] == 'C' && type[1] == 'I' && type[2] == 'M'
                && type[3] == 'G') {
                // read TBHD.
                for (;;) {
                    if (!read_typesize(fd, type, size)) {
                        err = "could not read CIMG length @ L" STRINGIZE(
                            __LINE__);
                        return false;
                    }

                    chunksize = align_size(size, 4);

                    if (type[0] == 'T' && type[1] == 'B' && type[2] == 'H'
                        && type[3] == 'D') {
                        tbhdsize = size;

                        // test if table header size is correct
                        if (tbhdsize != 24 && tbhdsize != 32) {
                            err = "bad table header @ L" STRINGIZE(__LINE__);
                            return false;  // bad table header
                        }

                        // get width and height
                        if (!read(fd, width) || !read(fd, height)
                            || !read(fd, prnum) || !read(fd, prden)
                            || !read(fd, flags) || !read(fd, bytes)
                            || !read(fd, tiles) || !read(fd, compression)) {
                            err = "@ L" STRINGIZE(__LINE__);
                            return false;
                        }

                        // get xy
                        if (tbhdsize == 32) {
                            if (!read(fd, x) || !read(fd, y)) {
                                err = "could not get xy @ L" STRINGIZE(
                                    __LINE__);
                                return false;
                            }
                        } else {
                            x = 0;
                            y = 0;
                        }

                        // tiles
                        if (tiles == 0) {
                            err = "non-tiles not supported @ L" STRINGIZE(
                                __LINE__);
                            return false;
                        }  // non-tiles not supported

                        // 0 no compression
                        // 1 RLE compression
                        // 2 QRL (not supported)
                        // 3 QR4 (not supported)
                        if (compression > 1) {
                            err = "only RLE compression is supported @ L" STRINGIZE(
                                __LINE__);
                            return false;
                        }

                        // test format.
                        if (flags & RGBA) {
                            // test if black is set
                            OIIO_DASSERT(!(flags & BLACK));

                            // test for RGB channels.
                            if (flags & RGB)
                                pixel_channels = 3;

                            // test for alpha channel
                            if (flags & ALPHA)
                                pixel_channels++;

                            // test pixel bits
                            pixel_bits = bytes ? 16 : 8;
                        }

                        // Z format.
                        else if (flags & ZBUFFER) {
                            pixel_channels = 1;
                            pixel_bits     = 32;  // 32bit
                            // NOTE: Z_F32 support - not supported
                            OIIO_DASSERT(bytes == 0);
                        }

                        // read AUTH, DATE or FOR4

                        for (;;) {
                            // get type
                            if (!read_typesize(fd, type, size)) {
                                err = "could not read type/size @ L" STRINGIZE(
                                    __LINE__);
                                return false;
                            }

                            chunksize = align_size(size, 4);

                            if (type[0] == 'A' && type[1] == 'U'
                                && type[2] == 'T' && type[3] == 'H') {
                                std::vector<char> str(chunksize);
                                if (!fread(&str[0], 1, chunksize, fd)) {
                                    err = "could not read author @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                                author = std::string(&str[0], size);
                            } else if (type[0] == 'D' && type[1] == 'A'
                                       && type[2] == 'T' && type[3] == 'E') {
                                std::vector<char> str(chunksize);
                                if (!fread(&str[0], 1, chunksize, fd)) {
                                    err = "could not read date @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                                date = std::string(&str[0], size);
                            } else if (type[0] == 'F' && type[1] == 'O'
                                       && type[2] == 'R' && type[3] == '4') {
                                if (!fread(&type, 1, sizeof(type), fd)) {
                                    err = "could not read FOR4 type @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }

                                // check if CIMG
                                if (type[0] == 'T' && type[1] == 'B'
                                    && type[2] == 'M' && type[3] == 'P') {
                                    // tbmp position for later user in in
                                    // read_native_tile

                                    tbmp_start = ftell(fd);

                                    // read first RGBA block to detect tile size.

                                    for (unsigned int t = 0; t < tiles; t++) {
                                        if (!read_typesize(fd, type, size)) {
                                            err = "xxx @ L" STRINGIZE(__LINE__);
                                            return false;
                                        }
                                        chunksize = align_size(size, 4);

                                        // check if RGBA
                                        if (type[0] == 'R' && type[1] == 'G'
                                            && type[2] == 'B'
                                            && type[3] == 'A') {
                                            // get tile coordinates.
                                            uint16_t xmin, xmax, ymin, ymax;
                                            if (!read(fd, xmin)
                                                || !read(fd, ymin)
                                                || !read(fd, xmax)
                                                || !read(fd, ymax)) {
                                                err = "xxx @ L" STRINGIZE(
                                                    __LINE__);
                                                return false;
                                            }

                                            // check tile
                                            if (xmin > xmax || ymin > ymax
                                                || xmax >= width
                                                || ymax >= height) {
                                                err = "tile min/max nonsensical @ L" STRINGIZE(
                                                    __LINE__);
                                                return false;
                                            }

                                            // set tile width and height
                                            tile_width  = xmax - xmin + 1;
                                            tile_height = ymax - ymin + 1;

                                            // done, return
                                            return true;
                                        }

                                        // skip to the next block.
                                        if (fseek(fd, chunksize, SEEK_CUR)) {
                                            err = "could not fseek @ L" STRINGIZE(
                                                __LINE__);
                                            return false;
                                        }
                                    }
                                } else {
                                    // skip to the next block.
                                    if (fseek(fd, chunksize, SEEK_CUR)) {
                                        err = "could not fseek @ L" STRINGIZE(
                                            __LINE__);
                                        return false;
                                    }
                                }
                            } else {
                                // skip to the next block.
                                if (fseek(fd, chunksize, SEEK_CUR)) {
                                    err = "could not fseek @ L" STRINGIZE(
                                        __LINE__);
                                    return false;
                                }
                            }
                        }
                        // TBHD done, break
                        break;
                    }

                    // skip to the next block.
                    if (fseek(fd, chunksize, SEEK_CUR)) {
                        err = "could not fseek @ L" STRINGIZE(__LINE__);
                        return false;
                    }
                }
            }
        }
        // skip to the next block.
        if (fseek(fd, chunksize, SEEK_CUR)) {
            err = "could not fseek @ L" STRINGIZE(__LINE__);
            return false;
        }
    }
    err = "unknown error, ended early @ L" STRINGIZE(__LINE__);
    return false;
}

bool
IffOutput::write_header(IffFileHeader& header)
{
    // write 'FOR4' type, with 0 length for now (to reserve it)
    if (!(write_str("FOR4") && write_int(0)))
        return false;

    // write 'CIMG' type
    if (!write_str("CIMG"))
        return false;

    // write 'TBHD' type
    if (!write_str("TBHD"))
        return false;

    // 'TBHD' length, 32 bytes
    if (!write_int(32))
        return false;

    if (!write_int(header.width) || !write_int(header.height))
        return false;

    // write prnum and prden (pixel aspect ratio? -- FIXME)
    if (!write_short(1) || !write_short(1))
        return false;

    // write flags and channels
    if (!write_int(header.pixel_channels == 3 ? RGB : RGBA)
        || !write_short(header.pixel_bits == 8 ? 0 : 1)
        || !write_short(header.tiles))
        return false;

    // write compression
    // 0 no compression
    // 1 RLE compression
    // 2 QRL (not supported)
    // 3 QR4 (not supported)
    if (!write_int(header.compression))
        return false;

    // write x and y
    if (!write_int(header.x) || !write_int(header.y))
        return false;

    // Write metadata
    write_meta_string("AUTH", header.author);
    write_meta_string("DATE", header.date);

    // for4 position for later user in close
    header.for4_start = ftell(m_fd);

    // write 'FOR4' type, with 0 length to reserve it for now
    if (!write_str("FOR4") || !write_int(0))
        return false;

    // write 'TBMP' type
    if (!write_str("TBMP"))
        return false;

    return true;
}

OIIO_PLUGIN_NAMESPACE_END
<|endoftext|>


<commit_before>/*
 * Copyright 2007-2020 CM4all GmbH
 * All rights reserved.
 *
 * author: Max Kellermann <mk@cm4all.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "FileIstream.hxx"
#include "istream.hxx"
#include "New.hxx"
#include "Result.hxx"
#include "io/Buffered.hxx"
#include "io/Open.hxx"
#include "io/UniqueFileDescriptor.hxx"
#include "pool/pool.hxx"
#include "fb_pool.hxx"
#include "SliceFifoBuffer.hxx"
#include "system/Error.hxx"
#include "event/TimerEvent.hxx"
#include "util/RuntimeError.hxx"

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>

/**
 * If EAGAIN occurs (on NFS), we try again after 100ms.  We can't
 * check SocketEvent::READ, because the kernel always indicates VFS files as
 * "readable without blocking".
 */
static constexpr Event::Duration file_retry_timeout = std::chrono::milliseconds(100);

class FileIstream final : public Istream {
	FileDescriptor fd;

	FdType fd_type;

	/**
	 * A timer to retry reading after EAGAIN.
	 */
	TimerEvent retry_event;

	off_t rest;
	SliceFifoBuffer buffer;
	const char *path;

public:
	FileIstream(struct pool &p, EventLoop &event_loop,
		    UniqueFileDescriptor &&_fd, FdType _fd_type, off_t _length,
		    const char *_path) noexcept
		:Istream(p),
		 fd(_fd.Steal()), fd_type(_fd_type),
		 retry_event(event_loop, BIND_THIS_METHOD(EventCallback)),
		 rest(_length),
		 path(_path) {}

	~FileIstream() noexcept {
		retry_event.Cancel();

		if (fd.IsDefined())
			fd.Close();

		buffer.FreeIfDefined();
	}

private:
	/**
	 * @return the number of bytes still in the buffer
	 */
	size_t SubmitBuffer() noexcept {
		return ConsumeFromBuffer(buffer);
	}

	void EofDetected() noexcept {
		assert(fd.IsDefined());

		DestroyEof();
	}

	gcc_pure
	size_t GetMaxRead() const noexcept {
		if (rest != (off_t)-1 && rest < (off_t)INT_MAX)
			return (size_t)rest;
		else
			return INT_MAX;
	}

	void TryData() noexcept;
	void TryDirect() noexcept;

	void TryRead() noexcept {
		if (CheckDirect(fd_type))
			TryDirect();
		else
			TryData();
	}

	void EventCallback() noexcept {
		TryRead();
	}

	/* virtual methods from class Istream */

	off_t _GetAvailable(bool partial) noexcept override;
	off_t _Skip(gcc_unused off_t length) noexcept override;

	void _Read() noexcept override {
		retry_event.Cancel();
		TryRead();
	}

	int _AsFd() noexcept override;
	void _Close() noexcept override {
		Destroy();
	}
};

inline void
FileIstream::TryData() noexcept
{
	size_t buffer_rest = 0;

	if (buffer.IsNull()) {
		if (rest != 0)
			buffer.Allocate(fb_pool_get());
	} else {
		const size_t available = buffer.GetAvailable();
		if (available > 0) {
			buffer_rest = SubmitBuffer();
			if (buffer_rest == available)
				/* not a single byte was consumed: we may have been
				   closed, and we must bail out now */
				return;
		}
	}

	if (rest == 0) {
		if (buffer_rest == 0)
			EofDetected();
		return;
	}

	ssize_t nbytes = read_to_buffer(fd.Get(), buffer, GetMaxRead());
	if (nbytes == 0) {
		if (rest == (off_t)-1) {
			rest = 0;
			if (buffer_rest == 0)
				EofDetected();
		} else {
			DestroyError(std::make_exception_ptr(FormatRuntimeError("premature end of file in '%s'",
										path)));
		}
		return;
	} else if (nbytes == -1) {
		DestroyError(std::make_exception_ptr(FormatErrno("Failed to read from '%s'",
								 path)));
		return;
	} else if (nbytes > 0 && rest != (off_t)-1) {
		rest -= (off_t)nbytes;
		assert(rest >= 0);
	}

	assert(!buffer.empty());

	buffer_rest = SubmitBuffer();
	if (buffer_rest == 0 && rest == 0)
		EofDetected();
}

inline void
FileIstream::TryDirect() noexcept
{
	/* first consume the rest of the buffer */
	if (SubmitBuffer() > 0)
		return;

	if (rest == 0) {
		EofDetected();
		return;
	}

	ssize_t nbytes = InvokeDirect(fd_type, fd.Get(), GetMaxRead());
	if (nbytes == ISTREAM_RESULT_CLOSED)
		/* this stream was closed during the direct() callback */
		return;

	if (nbytes > 0 || nbytes == ISTREAM_RESULT_BLOCKING) {
		/* -2 means the callback wasn't able to consume any data right
		   now */
		if (nbytes > 0 && rest != (off_t)-1) {
			rest -= (off_t)nbytes;
			assert(rest >= 0);
			if (rest == 0)
				EofDetected();
		}
	} else if (nbytes == ISTREAM_RESULT_EOF) {
		if (rest == (off_t)-1) {
			EofDetected();
		} else {
			DestroyError(std::make_exception_ptr(FormatRuntimeError("premature end of file in '%s'",
										path)));
		}
	} else if (errno == EAGAIN) {
		/* this should only happen for splice(SPLICE_F_NONBLOCK) from
		   NFS files - unfortunately we cannot use SocketEvent::READ
		   here, so we just install a timer which retries after
		   100ms */

		retry_event.Schedule(file_retry_timeout);
	} else {
		/* XXX */
		DestroyError(std::make_exception_ptr(FormatErrno("Failed to read from '%s'",
								 path)));
	}
}

/*
 * istream implementation
 *
 */

off_t
FileIstream::_GetAvailable(bool partial) noexcept
{
	off_t available;
	if (rest != (off_t)-1)
		available = rest;
	else if (!partial)
		return (off_t)-1;
	else
		available = 0;

	available += buffer.GetAvailable();
	return available;
}

off_t
FileIstream::_Skip(off_t length) noexcept
{
	retry_event.Cancel();

	if (rest == (off_t)-1)
		return (off_t)-1;

	if (length == 0)
		return 0;

	const size_t buffer_available = buffer.GetAvailable();
	if (length < off_t(buffer_available)) {
		buffer.Consume(length);
		Consumed(length);
		return length;
	}

	length -= buffer_available;
	buffer.Clear();

	if (length >= rest) {
		/* skip beyond EOF */

		length = rest;
		rest = 0;
	} else {
		/* seek the file descriptor */

		if (fd.Skip(length) < 0)
			return -1;
		rest -= length;
	}

	off_t result = buffer_available + length;
	Consumed(result);
	return result;
}

int
FileIstream::_AsFd() noexcept
{
	int result_fd = fd.Steal();

	Destroy();

	return result_fd;
}

/*
 * constructor and public methods
 *
 */

UnusedIstreamPtr
istream_file_fd_new(EventLoop &event_loop, struct pool &pool,
		    const char *path,
		    UniqueFileDescriptor fd, FdType fd_type, off_t length) noexcept
{
	assert(fd.IsDefined());
	assert(length >= -1);

	return NewIstreamPtr<FileIstream>(pool, event_loop,
					  std::move(fd), fd_type,
					  length, path);
}

UnusedIstreamPtr
istream_file_new(EventLoop &event_loop, struct pool &pool,
		 const char *path)
{
	auto fd = OpenReadOnly(path);

	struct stat st;
	if (fstat(fd.Get(), &st) < 0)
		throw FormatErrno("Failed to stat %s", path);

	off_t size = S_ISREG(st.st_mode) ? st.st_size : -1;

	FdType fd_type = FdType::FD_FILE;
	if (S_ISCHR(st.st_mode))
		fd_type = FdType::FD_CHARDEV;
	else if (S_ISFIFO(st.st_mode))
		fd_type = FdType::FD_PIPE;
	else if (S_ISSOCK(st.st_mode))
		fd_type = FdType::FD_SOCKET;

	return istream_file_fd_new(event_loop, pool, path,
				   std::move(fd), fd_type, size);
}
<commit_msg>istream/file: use class UniqueFileDescriptor<commit_after>/*
 * Copyright 2007-2020 CM4all GmbH
 * All rights reserved.
 *
 * author: Max Kellermann <mk@cm4all.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "FileIstream.hxx"
#include "istream.hxx"
#include "New.hxx"
#include "Result.hxx"
#include "io/Buffered.hxx"
#include "io/Open.hxx"
#include "io/UniqueFileDescriptor.hxx"
#include "pool/pool.hxx"
#include "fb_pool.hxx"
#include "SliceFifoBuffer.hxx"
#include "system/Error.hxx"
#include "event/TimerEvent.hxx"
#include "util/RuntimeError.hxx"

#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>

/**
 * If EAGAIN occurs (on NFS), we try again after 100ms.  We can't
 * check SocketEvent::READ, because the kernel always indicates VFS files as
 * "readable without blocking".
 */
static constexpr Event::Duration file_retry_timeout = std::chrono::milliseconds(100);

class FileIstream final : public Istream {
	UniqueFileDescriptor fd;

	FdType fd_type;

	/**
	 * A timer to retry reading after EAGAIN.
	 */
	TimerEvent retry_event;

	off_t rest;
	SliceFifoBuffer buffer;
	const char *path;

public:
	FileIstream(struct pool &p, EventLoop &event_loop,
		    UniqueFileDescriptor &&_fd, FdType _fd_type, off_t _length,
		    const char *_path) noexcept
		:Istream(p),
		 fd(std::move(_fd)), fd_type(_fd_type),
		 retry_event(event_loop, BIND_THIS_METHOD(EventCallback)),
		 rest(_length),
		 path(_path) {}

	~FileIstream() noexcept {
		retry_event.Cancel();
		buffer.FreeIfDefined();
	}

private:
	/**
	 * @return the number of bytes still in the buffer
	 */
	size_t SubmitBuffer() noexcept {
		return ConsumeFromBuffer(buffer);
	}

	void EofDetected() noexcept {
		assert(fd.IsDefined());

		DestroyEof();
	}

	gcc_pure
	size_t GetMaxRead() const noexcept {
		if (rest != (off_t)-1 && rest < (off_t)INT_MAX)
			return (size_t)rest;
		else
			return INT_MAX;
	}

	void TryData() noexcept;
	void TryDirect() noexcept;

	void TryRead() noexcept {
		if (CheckDirect(fd_type))
			TryDirect();
		else
			TryData();
	}

	void EventCallback() noexcept {
		TryRead();
	}

	/* virtual methods from class Istream */

	off_t _GetAvailable(bool partial) noexcept override;
	off_t _Skip(gcc_unused off_t length) noexcept override;

	void _Read() noexcept override {
		retry_event.Cancel();
		TryRead();
	}

	int _AsFd() noexcept override;
	void _Close() noexcept override {
		Destroy();
	}
};

inline void
FileIstream::TryData() noexcept
{
	size_t buffer_rest = 0;

	if (buffer.IsNull()) {
		if (rest != 0)
			buffer.Allocate(fb_pool_get());
	} else {
		const size_t available = buffer.GetAvailable();
		if (available > 0) {
			buffer_rest = SubmitBuffer();
			if (buffer_rest == available)
				/* not a single byte was consumed: we may have been
				   closed, and we must bail out now */
				return;
		}
	}

	if (rest == 0) {
		if (buffer_rest == 0)
			EofDetected();
		return;
	}

	ssize_t nbytes = read_to_buffer(fd.Get(), buffer, GetMaxRead());
	if (nbytes == 0) {
		if (rest == (off_t)-1) {
			rest = 0;
			if (buffer_rest == 0)
				EofDetected();
		} else {
			DestroyError(std::make_exception_ptr(FormatRuntimeError("premature end of file in '%s'",
										path)));
		}
		return;
	} else if (nbytes == -1) {
		DestroyError(std::make_exception_ptr(FormatErrno("Failed to read from '%s'",
								 path)));
		return;
	} else if (nbytes > 0 && rest != (off_t)-1) {
		rest -= (off_t)nbytes;
		assert(rest >= 0);
	}

	assert(!buffer.empty());

	buffer_rest = SubmitBuffer();
	if (buffer_rest == 0 && rest == 0)
		EofDetected();
}

inline void
FileIstream::TryDirect() noexcept
{
	/* first consume the rest of the buffer */
	if (SubmitBuffer() > 0)
		return;

	if (rest == 0) {
		EofDetected();
		return;
	}

	ssize_t nbytes = InvokeDirect(fd_type, fd.Get(), GetMaxRead());
	if (nbytes == ISTREAM_RESULT_CLOSED)
		/* this stream was closed during the direct() callback */
		return;

	if (nbytes > 0 || nbytes == ISTREAM_RESULT_BLOCKING) {
		/* -2 means the callback wasn't able to consume any data right
		   now */
		if (nbytes > 0 && rest != (off_t)-1) {
			rest -= (off_t)nbytes;
			assert(rest >= 0);
			if (rest == 0)
				EofDetected();
		}
	} else if (nbytes == ISTREAM_RESULT_EOF) {
		if (rest == (off_t)-1) {
			EofDetected();
		} else {
			DestroyError(std::make_exception_ptr(FormatRuntimeError("premature end of file in '%s'",
										path)));
		}
	} else if (errno == EAGAIN) {
		/* this should only happen for splice(SPLICE_F_NONBLOCK) from
		   NFS files - unfortunately we cannot use SocketEvent::READ
		   here, so we just install a timer which retries after
		   100ms */

		retry_event.Schedule(file_retry_timeout);
	} else {
		/* XXX */
		DestroyError(std::make_exception_ptr(FormatErrno("Failed to read from '%s'",
								 path)));
	}
}

/*
 * istream implementation
 *
 */

off_t
FileIstream::_GetAvailable(bool partial) noexcept
{
	off_t available;
	if (rest != (off_t)-1)
		available = rest;
	else if (!partial)
		return (off_t)-1;
	else
		available = 0;

	available += buffer.GetAvailable();
	return available;
}

off_t
FileIstream::_Skip(off_t length) noexcept
{
	retry_event.Cancel();

	if (rest == (off_t)-1)
		return (off_t)-1;

	if (length == 0)
		return 0;

	const size_t buffer_available = buffer.GetAvailable();
	if (length < off_t(buffer_available)) {
		buffer.Consume(length);
		Consumed(length);
		return length;
	}

	length -= buffer_available;
	buffer.Clear();

	if (length >= rest) {
		/* skip beyond EOF */

		length = rest;
		rest = 0;
	} else {
		/* seek the file descriptor */

		if (fd.Skip(length) < 0)
			return -1;
		rest -= length;
	}

	off_t result = buffer_available + length;
	Consumed(result);
	return result;
}

int
FileIstream::_AsFd() noexcept
{
	int result_fd = fd.Steal();

	Destroy();

	return result_fd;
}

/*
 * constructor and public methods
 *
 */

UnusedIstreamPtr
istream_file_fd_new(EventLoop &event_loop, struct pool &pool,
		    const char *path,
		    UniqueFileDescriptor fd, FdType fd_type, off_t length) noexcept
{
	assert(fd.IsDefined());
	assert(length >= -1);

	return NewIstreamPtr<FileIstream>(pool, event_loop,
					  std::move(fd), fd_type,
					  length, path);
}

UnusedIstreamPtr
istream_file_new(EventLoop &event_loop, struct pool &pool,
		 const char *path)
{
	auto fd = OpenReadOnly(path);

	struct stat st;
	if (fstat(fd.Get(), &st) < 0)
		throw FormatErrno("Failed to stat %s", path);

	off_t size = S_ISREG(st.st_mode) ? st.st_size : -1;

	FdType fd_type = FdType::FD_FILE;
	if (S_ISCHR(st.st_mode))
		fd_type = FdType::FD_CHARDEV;
	else if (S_ISFIFO(st.st_mode))
		fd_type = FdType::FD_PIPE;
	else if (S_ISSOCK(st.st_mode))
		fd_type = FdType::FD_SOCKET;

	return istream_file_fd_new(event_loop, pool, path,
				   std::move(fd), fd_type, size);
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2011 by Tommi Meakitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "httpclientimpl.h"
#include "cxxtools/remoteprocedure.h"
#include "cxxtools/textstream.h"
#include "cxxtools/jsonformatter.h"
#include "cxxtools/http/replyheader.h"
#include "cxxtools/selectable.h"
#include "cxxtools/utf8codec.h"
#include "cxxtools/ioerror.h"
#include "cxxtools/clock.h"
#include "cxxtools/log.h"

#include <stdexcept>
#include <strings.h>

log_define("cxxtools.json.client.impl")

namespace cxxtools
{

namespace json
{

HttpClientImpl::HttpClientImpl()
: _timeout(Selectable::WaitInfinite),
  _connectTimeoutSet(false),
  _connectTimeout(Selectable::WaitInfinite),
  _proc(0),
  _exceptionPending(false),
  _count(0)
{
    _request.method("POST");
    cxxtools::connect(_client.headerReceived, *this, &HttpClientImpl::onReplyHeader);
    cxxtools::connect(_client.bodyAvailable, *this, &HttpClientImpl::onReplyBody);
    cxxtools::connect(_client.replyFinished, *this, &HttpClientImpl::onReplyFinished);
}

void HttpClientImpl::beginCall(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
{
    if (_client.selector() == 0)
        throw std::logic_error("cannot run async rpc request without a selector");

    if (_proc)
        throw std::logic_error("asyncronous request already running");

    _proc = &method;

    prepareRequest(method.name(), argv, argc);

    _client.beginExecute(_request);

    _scanner.begin(_deserializer, r);
}

void HttpClientImpl::endCall()
{
    log_debug("end call; errorPending=" << _exceptionPending);
    if (_exceptionPending)
    {
        _exceptionPending = false;
        throw;
    }

    _client.endExecute();
}

void HttpClientImpl::call(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
{
    _proc = &method;

    prepareRequest(method.name(), argv, argc);

    _client.execute(_request, timeout(), connectTimeout());

    _scanner.begin(_deserializer, r);

    char ch;
    std::istream& is = _client.in();
    while (is.get(ch))
    {
        if (_deserializer.advance(ch) != 0)
        {
            log_debug("scanner finished");
            _proc = 0;
            _scanner.finalizeReply();
            return;
        }
    }

    throw std::runtime_error("unexpected end of data");
}

const IRemoteProcedure* HttpClientImpl::activeProcedure() const
{
    return _proc;
}

void HttpClientImpl::cancel()
{
    _client.cancel();
    _proc = 0;
}

// private members

void HttpClientImpl::prepareRequest(const String& name, IDecomposer** argv, unsigned argc)
{
    _request.clear();
    _request.setHeader("Content-Type", "application/json");
    _request.method("POST");

    TextOStream ts(_request.body(), new Utf8Codec());
    JsonFormatter formatter;

    formatter.begin(ts);

    formatter.beginObject(std::string(), std::string());

    formatter.addValueStdString("jsonrpc", std::string(), "2.0");
    formatter.addValueString("method", std::string(), name);
    formatter.addValueInt("id", "int", ++_count);

    formatter.beginArray("params", std::string());

    for(unsigned n = 0; n < argc; ++n)
    {
        argv[n]->format(formatter);
    }

    formatter.finishArray();

    formatter.finishObject();

    formatter.finish();

    ts.flush();
}

void HttpClientImpl::onReplyHeader(http::Client& client)
{
    verifyHeader(client.header());
}

std::size_t HttpClientImpl::onReplyBody(http::Client& client)
{
    std::size_t count = 0;
    char ch;
    std::istream& is = client.in();
    while (is.rdbuf()->in_avail() && is.get(ch))
    {
        ++count;
        if (_deserializer.advance(ch))
        {
            log_debug("scanner finished");
            try
            {
                _scanner.finalizeReply();
            }
            catch (const RemoteException& e)
            {
                _proc->setFault(e.rc(), e.text());
            }
            catch (const std::exception&)
            {
                log_warn("exception occured in finalizeReply");
                _exceptionPending = true;
                _proc->onFinished();
            }

            break;
        }
    }

    log_debug("no more data - " << count << " bytes consumed");

    return count;
}

void HttpClientImpl::onReplyFinished(http::Client& client)
{
    log_debug("onReplyFinished; method=" << static_cast<void*>(_proc));

    try
    {
        _exceptionPending = false;
        endCall();
    }
    catch (const std::exception& e)
    {
        if (!_proc)
            throw;

        _exceptionPending = true;

        IRemoteProcedure* proc = _proc;
        _proc = 0;
        proc->onFinished();
        if (_exceptionPending)
        {
            _exceptionPending = false;
            throw;
        }
        return;
    }

    IRemoteProcedure* proc = _proc;
    _proc = 0;
    proc->onFinished();
}

void HttpClientImpl::wait(Timespan timeout)
{
    if (!_client.selector())
        throw std::logic_error("cannot run async rpc request without a selector");

    Clock clock;
    if (timeout >= Timespan(0))
        clock.start();

    Timespan remaining = timeout;

    while (activeProcedure() != 0)
    {
        if (_client.selector()->wait(remaining) == false)
            throw IOTimeout();

        if (timeout >= Timespan(0))
        {
            remaining = timeout - clock.stop();
            if (remaining < Timespan(0))
                remaining = Timespan(0);
        }
    }
}

void HttpClientImpl::verifyHeader(const http::ReplyHeader& header)
{
    if (header.httpReturnCode() != 200)
    {
        std::ostringstream msg;
        msg << "invalid http return code "
            << header.httpReturnCode()
            << ": "
            << header.httpReturnText();
        throw std::runtime_error(msg.str());
    }

    const char* contentType = header.getHeader("Content-Type");
    if (contentType == 0)
        throw std::runtime_error("missing content type header");

    if (::strncasecmp(contentType, "application/json", 16) != 0)
    {
        std::ostringstream msg;
        msg << "invalid content type " << contentType;
        throw std::runtime_error(msg.str());
    }

}

}

}
<commit_msg>fix logging category in json http client<commit_after>/*
 * Copyright (C) 2011 by Tommi Meakitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "httpclientimpl.h"
#include "cxxtools/remoteprocedure.h"
#include "cxxtools/textstream.h"
#include "cxxtools/jsonformatter.h"
#include "cxxtools/http/replyheader.h"
#include "cxxtools/selectable.h"
#include "cxxtools/utf8codec.h"
#include "cxxtools/ioerror.h"
#include "cxxtools/clock.h"
#include "cxxtools/log.h"

#include <stdexcept>
#include <strings.h>

log_define("cxxtools.json.httpclient.impl")

namespace cxxtools
{

namespace json
{

HttpClientImpl::HttpClientImpl()
: _timeout(Selectable::WaitInfinite),
  _connectTimeoutSet(false),
  _connectTimeout(Selectable::WaitInfinite),
  _proc(0),
  _exceptionPending(false),
  _count(0)
{
    _request.method("POST");
    cxxtools::connect(_client.headerReceived, *this, &HttpClientImpl::onReplyHeader);
    cxxtools::connect(_client.bodyAvailable, *this, &HttpClientImpl::onReplyBody);
    cxxtools::connect(_client.replyFinished, *this, &HttpClientImpl::onReplyFinished);
}

void HttpClientImpl::beginCall(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
{
    if (_client.selector() == 0)
        throw std::logic_error("cannot run async rpc request without a selector");

    if (_proc)
        throw std::logic_error("asyncronous request already running");

    _proc = &method;

    prepareRequest(method.name(), argv, argc);

    _client.beginExecute(_request);

    _scanner.begin(_deserializer, r);
}

void HttpClientImpl::endCall()
{
    log_debug("end call; errorPending=" << _exceptionPending);
    if (_exceptionPending)
    {
        _exceptionPending = false;
        throw;
    }

    _client.endExecute();
}

void HttpClientImpl::call(IComposer& r, IRemoteProcedure& method, IDecomposer** argv, unsigned argc)
{
    _proc = &method;

    prepareRequest(method.name(), argv, argc);

    _client.execute(_request, timeout(), connectTimeout());

    _scanner.begin(_deserializer, r);

    char ch;
    std::istream& is = _client.in();
    while (is.get(ch))
    {
        if (_deserializer.advance(ch) != 0)
        {
            log_debug("scanner finished");
            _proc = 0;
            _scanner.finalizeReply();
            return;
        }
    }

    throw std::runtime_error("unexpected end of data");
}

const IRemoteProcedure* HttpClientImpl::activeProcedure() const
{
    return _proc;
}

void HttpClientImpl::cancel()
{
    _client.cancel();
    _proc = 0;
}

// private members

void HttpClientImpl::prepareRequest(const String& name, IDecomposer** argv, unsigned argc)
{
    _request.clear();
    _request.setHeader("Content-Type", "application/json");
    _request.method("POST");

    TextOStream ts(_request.body(), new Utf8Codec());
    JsonFormatter formatter;

    formatter.begin(ts);

    formatter.beginObject(std::string(), std::string());

    formatter.addValueStdString("jsonrpc", std::string(), "2.0");
    formatter.addValueString("method", std::string(), name);
    formatter.addValueInt("id", "int", ++_count);

    formatter.beginArray("params", std::string());

    for(unsigned n = 0; n < argc; ++n)
    {
        argv[n]->format(formatter);
    }

    formatter.finishArray();

    formatter.finishObject();

    formatter.finish();

    ts.flush();
}

void HttpClientImpl::onReplyHeader(http::Client& client)
{
    verifyHeader(client.header());
}

std::size_t HttpClientImpl::onReplyBody(http::Client& client)
{
    std::size_t count = 0;
    char ch;
    std::istream& is = client.in();
    while (is.rdbuf()->in_avail() && is.get(ch))
    {
        ++count;
        if (_deserializer.advance(ch))
        {
            log_debug("scanner finished");
            try
            {
                _scanner.finalizeReply();
            }
            catch (const RemoteException& e)
            {
                _proc->setFault(e.rc(), e.text());
            }
            catch (const std::exception&)
            {
                log_warn("exception occured in finalizeReply");
                _exceptionPending = true;
                _proc->onFinished();
            }

            break;
        }
    }

    log_debug("no more data - " << count << " bytes consumed");

    return count;
}

void HttpClientImpl::onReplyFinished(http::Client& client)
{
    log_debug("onReplyFinished; method=" << static_cast<void*>(_proc));

    try
    {
        _exceptionPending = false;
        endCall();
    }
    catch (const std::exception& e)
    {
        if (!_proc)
            throw;

        _exceptionPending = true;

        IRemoteProcedure* proc = _proc;
        _proc = 0;
        proc->onFinished();
        if (_exceptionPending)
        {
            _exceptionPending = false;
            throw;
        }
        return;
    }

    IRemoteProcedure* proc = _proc;
    _proc = 0;
    proc->onFinished();
}

void HttpClientImpl::wait(Timespan timeout)
{
    if (!_client.selector())
        throw std::logic_error("cannot run async rpc request without a selector");

    Clock clock;
    if (timeout >= Timespan(0))
        clock.start();

    Timespan remaining = timeout;

    while (activeProcedure() != 0)
    {
        if (_client.selector()->wait(remaining) == false)
            throw IOTimeout();

        if (timeout >= Timespan(0))
        {
            remaining = timeout - clock.stop();
            if (remaining < Timespan(0))
                remaining = Timespan(0);
        }
    }
}

void HttpClientImpl::verifyHeader(const http::ReplyHeader& header)
{
    if (header.httpReturnCode() != 200)
    {
        std::ostringstream msg;
        msg << "invalid http return code "
            << header.httpReturnCode()
            << ": "
            << header.httpReturnText();
        throw std::runtime_error(msg.str());
    }

    const char* contentType = header.getHeader("Content-Type");
    if (contentType == 0)
        throw std::runtime_error("missing content type header");

    if (::strncasecmp(contentType, "application/json", 16) != 0)
    {
        std::ostringstream msg;
        msg << "invalid content type " << contentType;
        throw std::runtime_error(msg.str());
    }

}

}

}
<|endoftext|>


<commit_before>/*
Copyright (c) 2013 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.

Author: Leonardo de Moura
*/
#include "util/freset.h"
#include "util/flet.h"
#include "util/interrupt.h"
#include "kernel/type_checker.h"
#include "kernel/expr_maps.h"
#include "kernel/environment.h"
#include "kernel/kernel_exception.h"
#include "kernel/normalizer.h"
#include "kernel/instantiate.h"
#include "kernel/builtin.h"
#include "kernel/free_vars.h"
#include "kernel/type_checker_justification.h"

namespace lean {
static name g_x_name("x");
/** \brief Auxiliary functional object used to implement infer_type. */
class type_checker::imp {
    typedef expr_map<expr> cache;
    typedef buffer<unification_constraint> unification_constraints;

    ro_environment::weak_ref  m_env;
    cache                     m_cache;
    normalizer                m_normalizer;
    context                   m_ctx;
    cached_metavar_env        m_menv;
    unification_constraints * m_uc;

    ro_environment env() const {
        return ro_environment(m_env);
    }

    expr lift_free_vars(expr const & e, unsigned s, unsigned d) {
        return ::lean::lift_free_vars(e, s, d, m_menv.to_some_menv());
    }

    expr lift_free_vars(expr const & e, unsigned d) {
        return ::lean::lift_free_vars(e, d, m_menv.to_some_menv());
    }

    expr instantiate_with_closed(expr const & e, expr const & v) {
        return ::lean::instantiate_with_closed(e, v, m_menv.to_some_menv());
    }

    expr instantiate(expr const & e, expr const & v) {
        return ::lean::instantiate(e, v, m_menv.to_some_menv());
    }

    expr normalize(expr const & e, context const & ctx) {
        return m_normalizer(e, ctx, m_menv.to_some_menv());
    }

    expr check_pi(expr const & e, expr const & s, context const & ctx) {
        if (is_pi(e))
            return e;
        expr r = normalize(e, ctx);
        if (is_pi(r))
            return r;
        if (has_metavar(r) && m_menv && m_uc) {
            // Create two fresh variables A and B,
            // and assign r == (Pi(x : A), B)
            expr A   = m_menv->mk_metavar(ctx);
            expr B   = m_menv->mk_metavar(extend(ctx, g_x_name, A));
            expr p   = mk_pi(g_x_name, A, B);
            justification jst = mk_function_expected_justification(ctx, s);
            m_uc->push_back(mk_eq_constraint(ctx, r, p, jst));
            return p;
        }
        throw function_expected_exception(env(), ctx, s);
    }

    expr check_type(expr const & e, expr const & s, context const & ctx) {
        if (is_type(e))
            return e;
        if (is_bool(e))
            return Type();
        expr u = normalize(e, ctx);
        if (is_type(u))
            return u;
        if (is_bool(u))
            return Type();
        if (has_metavar(u) && m_menv && m_uc) {
            justification jst = mk_type_expected_justification(ctx, s);
            m_uc->push_back(mk_convertible_constraint(ctx, u, TypeU, jst));
            return u;
        }
        throw type_expected_exception(env(), ctx, s);
    }

    expr save_result(expr const & e, expr const & r, bool shared) {
        if (shared)
            m_cache[e] = r;
        return r;
    }

    expr infer_type_core(expr const & e, context const & ctx) {
        check_system("type checker");
        bool shared = false;
        if (is_shared(e)) {
            shared = true;
            auto it = m_cache.find(e);
            if (it != m_cache.end())
                return it->second;
        }

        switch (e.kind()) {
        case expr_kind::MetaVar:
            if (m_menv) {
                if (m_menv->is_assigned(e)) {
                    optional<expr> s = m_menv->get_subst(e);
                    lean_assert(s);
                    return infer_type_core(*s, ctx);
                } else {
                    return m_menv->get_type(e);
                }
            } else {
                throw unexpected_metavar_occurrence(env(), e);
            }
        case expr_kind::Constant: {
            if (const_type(e)) {
                return save_result(e, *const_type(e), shared);
            } else {
                object const & obj = env()->get_object(const_name(e));
                if (obj.has_type())
                    return save_result(e, obj.get_type(), shared);
                else
                    throw has_no_type_exception(env(), e);
            }
        }
        case expr_kind::Var: {
            unsigned i = var_idx(e);
            auto p = lookup_ext(ctx, i);
            context_entry const & def = p.first;
            context const & def_ctx   = p.second;
            lean_assert(ctx.size() > def_ctx.size());
            if (optional<expr> const & d = def.get_domain()) {
                return save_result(e, lift_free_vars(*d, ctx.size() - def_ctx.size()), shared);
            } else {
                optional<expr> const & b = def.get_body();
                lean_assert(b);
                expr t = infer_type_core(*b, def_ctx);
                return save_result(e, lift_free_vars(t, ctx.size() - def_ctx.size()), shared);
            }
        }
        case expr_kind::Type:
            return save_result(e, mk_type(ty_level(e) + 1), shared);
        case expr_kind::App: {
            unsigned num = num_args(e);
            lean_assert(num >= 2);
            buffer<expr> arg_types;
            for (unsigned i = 0; i < num; i++) {
                arg_types.push_back(infer_type_core(arg(e, i), ctx));
            }
            expr f_t     = check_pi(arg_types[0], e, ctx);
            unsigned i   = 1;
            while (true) {
                expr const & c   = arg(e, i);
                expr const & c_t = arg_types[i];
                auto mk_justification = [&](){ return mk_app_type_match_justification(ctx, e, i); }; // thunk for creating justification object if needed
                if (!is_convertible(c_t, abst_domain(f_t), ctx, mk_justification))
                    throw app_type_mismatch_exception(env(), ctx, e, arg_types.size(), arg_types.data());
                if (closed(abst_body(f_t)))
                    f_t = abst_body(f_t);
                else if (closed(c))
                    f_t = instantiate_with_closed(abst_body(f_t), c);
                else
                    f_t = instantiate(abst_body(f_t), c);
                i++;
                if (i == num)
                    return save_result(e, f_t, shared);
                f_t = check_pi(f_t, e, ctx);
            }
        }
        case expr_kind::Eq:
            infer_type_core(eq_lhs(e), ctx);
            infer_type_core(eq_rhs(e), ctx);
            return save_result(e, mk_bool_type(), shared);
        case expr_kind::Lambda: {
            expr d = infer_type_core(abst_domain(e), ctx);
            check_type(d, abst_domain(e), ctx);
            {
                freset<cache> reset(m_cache);
                return save_result(e,
                                   mk_pi(abst_name(e), abst_domain(e), infer_type_core(abst_body(e), extend(ctx, abst_name(e), abst_domain(e)))),
                                   shared);
            }
        }
        case expr_kind::Pi: {
            expr t1  = check_type(infer_type_core(abst_domain(e), ctx), abst_domain(e), ctx);
            optional<expr> t2;
            context new_ctx = extend(ctx, abst_name(e), abst_domain(e));
            {
                freset<cache> reset(m_cache);
                t2 = check_type(infer_type_core(abst_body(e), new_ctx), abst_body(e), new_ctx);
            }
            if (is_type(t1) && is_type(*t2)) {
                return save_result(e, mk_type(max(ty_level(t1), ty_level(*t2))), shared);
            } else {
                lean_assert(m_uc);
                justification jst = mk_max_type_justification(ctx, e);
                expr r = m_menv->mk_metavar(ctx);
                m_uc->push_back(mk_max_constraint(new_ctx, lift_free_vars(t1, 0, 1), *t2, r, jst));
                return save_result(e, r, shared);
            }
        }
        case expr_kind::Let: {
            expr lt = infer_type_core(let_value(e), ctx);
            if (let_type(e)) {
                expr ty = infer_type_core(*let_type(e), ctx);
                check_type(ty, *let_type(e), ctx); // check if it is really a type
                // thunk for creating justification object if needed
                auto mk_justification = [&](){ return mk_def_type_match_justification(ctx, let_name(e), let_value(e)); };
                if (!is_convertible(lt, *let_type(e), ctx, mk_justification))
                    throw def_type_mismatch_exception(env(), ctx, let_name(e), *let_type(e), let_value(e), lt);
            }
            {
                freset<cache> reset(m_cache);
                expr t = infer_type_core(let_body(e), extend(ctx, let_name(e), lt, let_value(e)));
                return save_result(e, instantiate(t, let_value(e)), shared);
            }
        }
        case expr_kind::Value: {
            // Check if the builtin value (or its set) is declared in the environment.
            name const & n = to_value(e).get_name();
            object obj = env()->get_object(n);
            if ((obj.is_builtin() && obj.get_value() == e) || (obj.is_builtin_set() && obj.in_builtin_set(e))) {
                return save_result(e, to_value(e).get_type(), shared);
            } else {
                throw invalid_builtin_value_reference(env(), e);
            }
        }
        }
        lean_unreachable(); // LCOV_EXCL_LINE
    }

    bool is_convertible_core(expr const & given, expr const & expected) {
        if (given == expected) {
            return true;
        } else {
            expr const * g = &given;
            expr const * e = &expected;
            while (true) {
                if (is_type(*e) && is_type(*g)) {
                    if (env()->is_ge(ty_level(*e), ty_level(*g)))
                        return true;
                }

                if (is_type(*e) && *g == mk_bool_type())
                    return true;

                if (is_pi(*e) && is_pi(*g) && abst_domain(*e) == abst_domain(*g)) {
                    g = &abst_body(*g);
                    e = &abst_body(*e);
                } else {
                    return false;
                }
            }
        }
    }

    template<typename MkJustification>
    bool is_convertible(expr const & given, expr const & expected, context const & ctx, MkJustification const & mk_justification) {
        if (is_convertible_core(given, expected))
            return true;
        expr new_given    = normalize(given, ctx);
        expr new_expected = normalize(expected, ctx);
        if (is_convertible_core(new_given, new_expected))
            return true;
        if (m_menv && m_uc && (has_metavar(new_given) || has_metavar(new_expected))) {
            m_uc->push_back(mk_convertible_constraint(ctx, given, expected, mk_justification()));
            return true;
        }
        return false;
    }

    void set_ctx(context const & ctx) {
        if (!is_eqp(m_ctx, ctx)) {
            clear();
            m_ctx = ctx;
        }
    }

    void update_menv(optional<metavar_env> const & menv) {
        if (m_menv.update(menv))
            clear_cache();
    }

public:
    imp(ro_environment const & env):
        m_env(env),
        m_normalizer(env) {
        m_uc              = nullptr;
    }

    expr infer_type(expr const & e, context const & ctx, optional<metavar_env> const & menv, buffer<unification_constraint> * uc) {
        set_ctx(ctx);
        update_menv(menv);
        flet<unification_constraints*> set_uc(m_uc, uc);
        return infer_type_core(e, ctx);
    }

    bool is_convertible(expr const & t1, expr const & t2, context const & ctx) {
        set_ctx(ctx);
        update_menv(none_menv());
        auto mk_justification = [](){
            lean_unreachable(); return justification(); // LCOV_EXCL_LINE
        };
        return is_convertible(t1, t2, ctx, mk_justification);
    }

    void check_type(expr const & e, context const & ctx) {
        set_ctx(ctx);
        update_menv(none_menv());
        expr t = infer_type_core(e, ctx);
        check_type(t, e, ctx);
    }

    void clear_cache() {
        m_cache.clear();
        m_normalizer.clear();
    }

    void clear() {
        clear_cache();
        m_menv.clear();
        m_ctx = context();
    }

    normalizer & get_normalizer() {
        return m_normalizer;
    }
};

type_checker::type_checker(ro_environment const & env):m_ptr(new imp(env)) {}
type_checker::~type_checker() {}
expr type_checker::infer_type(expr const & e, context const & ctx, optional<metavar_env> const & menv, buffer<unification_constraint> * uc) {
    return m_ptr->infer_type(e, ctx, menv, uc);
}
expr type_checker::infer_type(expr const & e, context const & ctx, metavar_env const & menv, buffer<unification_constraint> & uc) {
    return m_ptr->infer_type(e, ctx, some_menv(menv), &uc);
}
expr type_checker::infer_type(expr const & e, context const & ctx, metavar_env const & menv) {
    return m_ptr->infer_type(e, ctx, some_menv(menv), nullptr);
}
expr type_checker::infer_type(expr const & e, context const & ctx) {
    return infer_type(e, ctx, none_menv(), nullptr);
}
bool type_checker::is_convertible(expr const & t1, expr const & t2, context const & ctx) {
    return m_ptr->is_convertible(t1, t2, ctx);
}
void type_checker::check_type(expr const & e, context const & ctx) {
    m_ptr->check_type(e, ctx);
}
void type_checker::clear() { m_ptr->clear(); }
normalizer & type_checker::get_normalizer() { return m_ptr->get_normalizer(); }

expr  infer_type(expr const & e, ro_environment const & env, context const & ctx) {
    return type_checker(env).infer_type(e, ctx);
}
bool is_convertible(expr const & given, expr const & expected, ro_environment const & env, context const & ctx) {
    return type_checker(env).is_convertible(given, expected, ctx);
}
}
<commit_msg>feat(kernel/type_checker): use expression before normalization in the unification constraints generated by the typechecker<commit_after>/*
Copyright (c) 2013 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.

Author: Leonardo de Moura
*/
#include "util/freset.h"
#include "util/flet.h"
#include "util/interrupt.h"
#include "kernel/type_checker.h"
#include "kernel/expr_maps.h"
#include "kernel/environment.h"
#include "kernel/kernel_exception.h"
#include "kernel/normalizer.h"
#include "kernel/instantiate.h"
#include "kernel/builtin.h"
#include "kernel/free_vars.h"
#include "kernel/type_checker_justification.h"

namespace lean {
static name g_x_name("x");
/** \brief Auxiliary functional object used to implement infer_type. */
class type_checker::imp {
    typedef expr_map<expr> cache;
    typedef buffer<unification_constraint> unification_constraints;

    ro_environment::weak_ref  m_env;
    cache                     m_cache;
    normalizer                m_normalizer;
    context                   m_ctx;
    cached_metavar_env        m_menv;
    unification_constraints * m_uc;

    ro_environment env() const {
        return ro_environment(m_env);
    }

    expr lift_free_vars(expr const & e, unsigned s, unsigned d) {
        return ::lean::lift_free_vars(e, s, d, m_menv.to_some_menv());
    }

    expr lift_free_vars(expr const & e, unsigned d) {
        return ::lean::lift_free_vars(e, d, m_menv.to_some_menv());
    }

    expr instantiate_with_closed(expr const & e, expr const & v) {
        return ::lean::instantiate_with_closed(e, v, m_menv.to_some_menv());
    }

    expr instantiate(expr const & e, expr const & v) {
        return ::lean::instantiate(e, v, m_menv.to_some_menv());
    }

    expr normalize(expr const & e, context const & ctx) {
        return m_normalizer(e, ctx, m_menv.to_some_menv());
    }

    expr check_pi(expr const & e, expr const & s, context const & ctx) {
        if (is_pi(e))
            return e;
        expr r = normalize(e, ctx);
        if (is_pi(r))
            return r;
        if (has_metavar(r) && m_menv && m_uc) {
            // Create two fresh variables A and B,
            // and assign r == (Pi(x : A), B)
            expr A   = m_menv->mk_metavar(ctx);
            expr B   = m_menv->mk_metavar(extend(ctx, g_x_name, A));
            expr p   = mk_pi(g_x_name, A, B);
            justification jst = mk_function_expected_justification(ctx, s);
            m_uc->push_back(mk_eq_constraint(ctx, e, p, jst));
            return p;
        }
        throw function_expected_exception(env(), ctx, s);
    }

    expr check_type(expr const & e, expr const & s, context const & ctx) {
        if (is_type(e))
            return e;
        if (is_bool(e))
            return Type();
        expr u = normalize(e, ctx);
        if (is_type(u))
            return u;
        if (is_bool(u))
            return Type();
        if (has_metavar(u) && m_menv && m_uc) {
            justification jst = mk_type_expected_justification(ctx, s);
            m_uc->push_back(mk_convertible_constraint(ctx, e, TypeU, jst));
            return u;
        }
        throw type_expected_exception(env(), ctx, s);
    }

    expr save_result(expr const & e, expr const & r, bool shared) {
        if (shared)
            m_cache[e] = r;
        return r;
    }

    expr infer_type_core(expr const & e, context const & ctx) {
        check_system("type checker");
        bool shared = false;
        if (is_shared(e)) {
            shared = true;
            auto it = m_cache.find(e);
            if (it != m_cache.end())
                return it->second;
        }

        switch (e.kind()) {
        case expr_kind::MetaVar:
            if (m_menv) {
                if (m_menv->is_assigned(e)) {
                    optional<expr> s = m_menv->get_subst(e);
                    lean_assert(s);
                    return infer_type_core(*s, ctx);
                } else {
                    return m_menv->get_type(e);
                }
            } else {
                throw unexpected_metavar_occurrence(env(), e);
            }
        case expr_kind::Constant: {
            if (const_type(e)) {
                return save_result(e, *const_type(e), shared);
            } else {
                object const & obj = env()->get_object(const_name(e));
                if (obj.has_type())
                    return save_result(e, obj.get_type(), shared);
                else
                    throw has_no_type_exception(env(), e);
            }
        }
        case expr_kind::Var: {
            unsigned i = var_idx(e);
            auto p = lookup_ext(ctx, i);
            context_entry const & def = p.first;
            context const & def_ctx   = p.second;
            lean_assert(ctx.size() > def_ctx.size());
            if (optional<expr> const & d = def.get_domain()) {
                return save_result(e, lift_free_vars(*d, ctx.size() - def_ctx.size()), shared);
            } else {
                optional<expr> const & b = def.get_body();
                lean_assert(b);
                expr t = infer_type_core(*b, def_ctx);
                return save_result(e, lift_free_vars(t, ctx.size() - def_ctx.size()), shared);
            }
        }
        case expr_kind::Type:
            return save_result(e, mk_type(ty_level(e) + 1), shared);
        case expr_kind::App: {
            unsigned num = num_args(e);
            lean_assert(num >= 2);
            buffer<expr> arg_types;
            for (unsigned i = 0; i < num; i++) {
                arg_types.push_back(infer_type_core(arg(e, i), ctx));
            }
            expr f_t     = check_pi(arg_types[0], e, ctx);
            unsigned i   = 1;
            while (true) {
                expr const & c   = arg(e, i);
                expr const & c_t = arg_types[i];
                auto mk_justification = [&](){ return mk_app_type_match_justification(ctx, e, i); }; // thunk for creating justification object if needed
                if (!is_convertible(c_t, abst_domain(f_t), ctx, mk_justification))
                    throw app_type_mismatch_exception(env(), ctx, e, arg_types.size(), arg_types.data());
                if (closed(abst_body(f_t)))
                    f_t = abst_body(f_t);
                else if (closed(c))
                    f_t = instantiate_with_closed(abst_body(f_t), c);
                else
                    f_t = instantiate(abst_body(f_t), c);
                i++;
                if (i == num)
                    return save_result(e, f_t, shared);
                f_t = check_pi(f_t, e, ctx);
            }
        }
        case expr_kind::Eq:
            infer_type_core(eq_lhs(e), ctx);
            infer_type_core(eq_rhs(e), ctx);
            return save_result(e, mk_bool_type(), shared);
        case expr_kind::Lambda: {
            expr d = infer_type_core(abst_domain(e), ctx);
            check_type(d, abst_domain(e), ctx);
            {
                freset<cache> reset(m_cache);
                return save_result(e,
                                   mk_pi(abst_name(e), abst_domain(e), infer_type_core(abst_body(e), extend(ctx, abst_name(e), abst_domain(e)))),
                                   shared);
            }
        }
        case expr_kind::Pi: {
            expr t1  = check_type(infer_type_core(abst_domain(e), ctx), abst_domain(e), ctx);
            optional<expr> t2;
            context new_ctx = extend(ctx, abst_name(e), abst_domain(e));
            {
                freset<cache> reset(m_cache);
                t2 = check_type(infer_type_core(abst_body(e), new_ctx), abst_body(e), new_ctx);
            }
            if (is_type(t1) && is_type(*t2)) {
                return save_result(e, mk_type(max(ty_level(t1), ty_level(*t2))), shared);
            } else {
                lean_assert(m_uc);
                justification jst = mk_max_type_justification(ctx, e);
                expr r = m_menv->mk_metavar(ctx);
                m_uc->push_back(mk_max_constraint(new_ctx, lift_free_vars(t1, 0, 1), *t2, r, jst));
                return save_result(e, r, shared);
            }
        }
        case expr_kind::Let: {
            expr lt = infer_type_core(let_value(e), ctx);
            if (let_type(e)) {
                expr ty = infer_type_core(*let_type(e), ctx);
                check_type(ty, *let_type(e), ctx); // check if it is really a type
                // thunk for creating justification object if needed
                auto mk_justification = [&](){ return mk_def_type_match_justification(ctx, let_name(e), let_value(e)); };
                if (!is_convertible(lt, *let_type(e), ctx, mk_justification))
                    throw def_type_mismatch_exception(env(), ctx, let_name(e), *let_type(e), let_value(e), lt);
            }
            {
                freset<cache> reset(m_cache);
                expr t = infer_type_core(let_body(e), extend(ctx, let_name(e), lt, let_value(e)));
                return save_result(e, instantiate(t, let_value(e)), shared);
            }
        }
        case expr_kind::Value: {
            // Check if the builtin value (or its set) is declared in the environment.
            name const & n = to_value(e).get_name();
            object obj = env()->get_object(n);
            if ((obj.is_builtin() && obj.get_value() == e) || (obj.is_builtin_set() && obj.in_builtin_set(e))) {
                return save_result(e, to_value(e).get_type(), shared);
            } else {
                throw invalid_builtin_value_reference(env(), e);
            }
        }
        }
        lean_unreachable(); // LCOV_EXCL_LINE
    }

    bool is_convertible_core(expr const & given, expr const & expected) {
        if (given == expected) {
            return true;
        } else {
            expr const * g = &given;
            expr const * e = &expected;
            while (true) {
                if (is_type(*e) && is_type(*g)) {
                    if (env()->is_ge(ty_level(*e), ty_level(*g)))
                        return true;
                }

                if (is_type(*e) && *g == mk_bool_type())
                    return true;

                if (is_pi(*e) && is_pi(*g) && abst_domain(*e) == abst_domain(*g)) {
                    g = &abst_body(*g);
                    e = &abst_body(*e);
                } else {
                    return false;
                }
            }
        }
    }

    template<typename MkJustification>
    bool is_convertible(expr const & given, expr const & expected, context const & ctx, MkJustification const & mk_justification) {
        if (is_convertible_core(given, expected))
            return true;
        expr new_given    = normalize(given, ctx);
        expr new_expected = normalize(expected, ctx);
        if (is_convertible_core(new_given, new_expected))
            return true;
        if (m_menv && m_uc && (has_metavar(new_given) || has_metavar(new_expected))) {
            m_uc->push_back(mk_convertible_constraint(ctx, given, expected, mk_justification()));
            return true;
        }
        return false;
    }

    void set_ctx(context const & ctx) {
        if (!is_eqp(m_ctx, ctx)) {
            clear();
            m_ctx = ctx;
        }
    }

    void update_menv(optional<metavar_env> const & menv) {
        if (m_menv.update(menv))
            clear_cache();
    }

public:
    imp(ro_environment const & env):
        m_env(env),
        m_normalizer(env) {
        m_uc              = nullptr;
    }

    expr infer_type(expr const & e, context const & ctx, optional<metavar_env> const & menv, buffer<unification_constraint> * uc) {
        set_ctx(ctx);
        update_menv(menv);
        flet<unification_constraints*> set_uc(m_uc, uc);
        return infer_type_core(e, ctx);
    }

    bool is_convertible(expr const & t1, expr const & t2, context const & ctx) {
        set_ctx(ctx);
        update_menv(none_menv());
        auto mk_justification = [](){
            lean_unreachable(); return justification(); // LCOV_EXCL_LINE
        };
        return is_convertible(t1, t2, ctx, mk_justification);
    }

    void check_type(expr const & e, context const & ctx) {
        set_ctx(ctx);
        update_menv(none_menv());
        expr t = infer_type_core(e, ctx);
        check_type(t, e, ctx);
    }

    void clear_cache() {
        m_cache.clear();
        m_normalizer.clear();
    }

    void clear() {
        clear_cache();
        m_menv.clear();
        m_ctx = context();
    }

    normalizer & get_normalizer() {
        return m_normalizer;
    }
};

type_checker::type_checker(ro_environment const & env):m_ptr(new imp(env)) {}
type_checker::~type_checker() {}
expr type_checker::infer_type(expr const & e, context const & ctx, optional<metavar_env> const & menv, buffer<unification_constraint> * uc) {
    return m_ptr->infer_type(e, ctx, menv, uc);
}
expr type_checker::infer_type(expr const & e, context const & ctx, metavar_env const & menv, buffer<unification_constraint> & uc) {
    return m_ptr->infer_type(e, ctx, some_menv(menv), &uc);
}
expr type_checker::infer_type(expr const & e, context const & ctx, metavar_env const & menv) {
    return m_ptr->infer_type(e, ctx, some_menv(menv), nullptr);
}
expr type_checker::infer_type(expr const & e, context const & ctx) {
    return infer_type(e, ctx, none_menv(), nullptr);
}
bool type_checker::is_convertible(expr const & t1, expr const & t2, context const & ctx) {
    return m_ptr->is_convertible(t1, t2, ctx);
}
void type_checker::check_type(expr const & e, context const & ctx) {
    m_ptr->check_type(e, ctx);
}
void type_checker::clear() { m_ptr->clear(); }
normalizer & type_checker::get_normalizer() { return m_ptr->get_normalizer(); }

expr  infer_type(expr const & e, ro_environment const & env, context const & ctx) {
    return type_checker(env).infer_type(e, ctx);
}
bool is_convertible(expr const & given, expr const & expected, ro_environment const & env, context const & ctx) {
    return type_checker(env).is_convertible(given, expected, ctx);
}
}
<|endoftext|>