drill_parse_G_code(gerb_file_t *fd, gerbv_image_t *image, ssize_t file_line)
{
    char op[3];
    drill_g_code_t g_code;
    gerbv_drill_stats_t *stats = image->drill_stats;

    dprintf("---> entering %s()...\n", __FUNCTION__);

    op[0] = gerb_fgetc(fd);
    op[1] = gerb_fgetc(fd);
    op[2] = '\0';

    if (op[0] == EOF
    ||  op[1] == EOF) {
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Unexpected EOF found while parsing G-code in file \"%s\""),
		fd->filename);
	return DRILL_G_UNKNOWN;
    }

    dprintf("  Compare G-code \"%s\" at line %ld\n", op, file_line);

    switch (g_code = atoi(op)) {
    case 0:
	/* atoi() return 0 in case of error, recheck string */
	if (0 != strncmp(op, "00", 2)) {
	    g_code = DRILL_G_UNKNOWN;
	    gerb_ungetc(fd);
	    gerb_ungetc(fd);
	    break;
	}
	stats->G00++;
	break;
    case 1:
	stats->G01++;
	break;
    case 2:
	stats->G02++;
	break;
    case 3:
	stats->G03++;
	break;
    case 5:
	stats->G05++;
	break;
    case 85:
	stats->G85++;
	break;
    case 90:
	stats->G90++;
	break;
    case 91:
	stats->G91++;
	break;
    case 93:
	stats->G93++;
	break;

    case DRILL_G_UNKNOWN:
    default:
	stats->G_unknown++;
	break;
    }

    dprintf("<----  ...leaving %s()\n", __FUNCTION__);

    return g_code;
} /* drill_parse_G_code() */


parse_drillfile(gerb_file_t *fd, gerbv_HID_Attribute *attr_list, int n_attr, int reload)
{
    drill_state_t *state = NULL;
    gerbv_image_t *image = NULL;
    gerbv_net_t *curr_net = NULL;
    gerbv_HID_Attribute *hid_attrs;
    int read;
    gerbv_drill_stats_t *stats;
    gchar *tmps;
    ssize_t file_line = 1;

    /*
     * many locales redefine "." as "," and so on, so sscanf and strtod
     * has problems when reading files using %f format.
     * Fixes bug #1963618 reported by Lorenzo Marcantonio.
     */
    setlocale(LC_NUMERIC, "C" );

    /* Create new image for this layer */
    dprintf("In parse_drillfile, about to create image for this layer\n");

    image = gerbv_create_image(image, "Excellon Drill File");
    if (image == NULL)
	GERB_FATAL_ERROR("malloc image failed in %s()", __FUNCTION__);

    if (reload && attr_list != NULL) {
      /* FIXME there should probably just be a function to copy an
	 attribute list including using strdup as needed */

	image->info->n_attr = n_attr;
	image->info->attr_list = gerbv_attribute_dup(attr_list, n_attr);

    } else {
	/* Copy in the default attribute list for drill files.  We make a
	 * copy here because we will allow per-layer editing of the
	 * attributes.
	 */
	image->info->n_attr = sizeof (drill_attribute_list) / sizeof (drill_attribute_list[0]);
	image->info->attr_list = gerbv_attribute_dup (drill_attribute_list, image->info->n_attr);

	/* now merge any project attributes */
	drill_attribute_merge (image->info->attr_list, image->info->n_attr,
			 attr_list, n_attr);
    }

    curr_net = image->netlist;
    curr_net->layer = image->layers;
    curr_net->state = image->states;
    image->layertype = GERBV_LAYERTYPE_DRILL;
    stats = gerbv_drill_stats_new();
    if (stats == NULL)
	GERB_FATAL_ERROR("malloc stats failed in %s()", __FUNCTION__);
    image->drill_stats = stats;

    /* Create local state variable to track photoplotter state */
    state = new_state(state);
    if (state == NULL)
	GERB_FATAL_ERROR("malloc state failed in %s()", __FUNCTION__);

    image->format = g_new0(gerbv_format_t, 1);
    if (image->format == NULL)
	GERB_FATAL_ERROR("malloc format failed in %s()", __FUNCTION__);

    image->format->omit_zeros = GERBV_OMIT_ZEROS_UNSPECIFIED;

    hid_attrs = image->info->attr_list;

    if (!hid_attrs[HA_auto].default_val.int_value) {
	state->autod = 0;
	state->number_format = FMT_USER;
	state->decimals = hid_attrs[HA_digits].default_val.int_value;

	if (GERBV_UNIT_MM == hid_attrs[HA_xy_units].default_val.int_value)
	    state->unit = GERBV_UNIT_MM;

	switch (hid_attrs[HA_suppression].default_val.int_value) {
	case SUP_LEAD:
	    image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
	    break;

	case SUP_TRAIL:
	    image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING;
	    break;

	default:
	    image->format->omit_zeros = GERBV_OMIT_ZEROS_EXPLICIT;
	    break;
	}
    }

    dprintf("%s():  Starting parsing of drill file \"%s\"\n",
		    __FUNCTION__, fd->filename);

    while ((read = gerb_fgetc(fd)) != EOF) {

	switch ((char) read) {

	case ';' :
	    /* Comment found. Eat rest of line */
	    tmps = get_line(fd);
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
		    _("Comment \"%s\" at line %ld in file \"%s\""),
		    tmps, file_line, fd->filename);
	    dprintf("    Comment with ';' \"%s\" at line %ld\n",
		    tmps, file_line);
	    g_free(tmps);
	    break;

	case 'D' :
	    gerb_ungetc (fd);
	    tmps = get_line (fd);
	    if (strcmp (tmps, "DETECT,ON") == 0 ||
		strcmp (tmps, "DETECT,OFF") == 0) {
		gchar *tmps2;
		gchar *tmps3;
		if (strcmp (tmps, "DETECT,ON") == 0)
		    tmps3 = "ON";
		else
		    tmps3 = "OFF";

		/* broken tool detect on/off.  Silently ignored. */
		if (stats->detect) {
		    tmps2 = g_strdup_printf ("%s\n%s", stats->detect, tmps3);
		    g_free (stats->detect);
		} else {
		    tmps2 = g_strdup_printf ("%s", tmps3);
		}
		stats->detect = tmps2;
	    } else {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Unrecognised string \"%s\" in header "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
	    }
	    g_free (tmps);
	    break;

	case 'F' :
	    gerb_ungetc (fd);
	    tmps = get_line (fd);

	    /* Silently ignore FMAT,2.  Not sure what others are allowed */
	    if (0 == strcmp (tmps, "FMAT,2")) {
		g_free (tmps);
		break;
	    }

	    if (0 == strcmp (tmps, "FMAT,1")) {
		gerbv_stats_printf(stats->error_list,
			GERBV_MESSAGE_ERROR, -1,
			_("File in unsupported format 1 "
			    "at line %ld in file \"%s\""),
			file_line, fd->filename);

		gerbv_destroy_image(image);
		g_free (tmps);

		return NULL;
	    }

	    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_ERROR, -1,
			    _("Unrecognised string \"%s\" in header "
				    "at line %ld in file \"%s\""),
			    tmps, file_line, fd->filename);

	    g_free (tmps);
	    break;

	case 'G': {
	    /* Most G codes aren't used, for now */
	    drill_g_code_t  g_code;

	    switch (g_code = drill_parse_G_code(fd, image, file_line)) {

	    case DRILL_G_DRILL :
		/* Drill mode */
		break;

	    case DRILL_G_SLOT : {
		/* Parse drilled slot end coords */
		gerbv_render_size_t *bbox = &curr_net->boundingBox;
		double r;

		if (EOF == (read = gerb_fgetc(fd))) {
		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_ERROR, -1,
			    _("Unexpected EOF found in file \"%s\""),
			    fd->filename);
		    break;
		}

		drill_parse_coordinate(fd, read, image, state, file_line);

		/* Modify last curr_net as drilled slot */
		curr_net->stop_x = state->curr_x;
		curr_net->stop_y = state->curr_y;

		if (state->unit == GERBV_UNIT_MM) {
		    /* Convert to inches -- internal units */
		    curr_net->stop_x /= 25.4;
		    curr_net->stop_y /= 25.4;
		}

		r = image->aperture[state->current_tool]->parameter[0]/2;

		/* Update boundingBox with drilled slot stop_x,y coords */
		bbox->left   = MIN(bbox->left,   curr_net->stop_x - r);
		bbox->right  = MAX(bbox->right,  curr_net->stop_x + r);
		bbox->bottom = MIN(bbox->bottom, curr_net->stop_y - r);
		bbox->top    = MAX(bbox->top,    curr_net->stop_y + r);

		drill_update_image_info_min_max_from_bbox(image->info, bbox);

		curr_net->aperture_state = GERBV_APERTURE_STATE_ON;

		break;
	    }

	    case DRILL_G_ABSOLUTE :
		state->coordinate_mode = DRILL_MODE_ABSOLUTE;
		break;

	    case DRILL_G_INCREMENTAL :
		state->coordinate_mode = DRILL_MODE_INCREMENTAL;
		break;

	    case DRILL_G_ZEROSET :
		if (EOF == (read = gerb_fgetc(fd))) {
		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_ERROR, -1,
			    _("Unexpected EOF found in file \"%s\""),
			    fd->filename);
		    break;
		}

		drill_parse_coordinate(fd, (char)read, image,
			state, file_line);
		state->origin_x = state->curr_x;
		state->origin_y = state->curr_y;
		break;

	    case DRILL_G_UNKNOWN:
		tmps = get_line(fd);
		gerbv_stats_printf(stats->error_list,
			GERBV_MESSAGE_ERROR, -1,
			_("Unrecognized string \"%s\" found "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
		g_free(tmps);
		break;

	    default:
		eat_line(fd);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Unsupported G%02d (%s) code "
			    "at line %ld in file \"%s\""),
			g_code, drill_g_code_name(g_code),
			file_line, fd->filename);
		break;
	    }

	    break;
	}

	case 'I':
	    gerb_ungetc(fd); /* To compare full string in function or
				report full string  */
	    if (drill_parse_header_is_inch(fd, state, image, file_line))
		break;

	    if (drill_parse_header_is_ici(fd, state, image, file_line))
		break;

	    tmps = get_line(fd);
	    gerbv_stats_printf(stats->error_list,
		    GERBV_MESSAGE_ERROR, -1,
		    _("Unrecognized string \"%s\" found "
			"at line %ld in file \"%s\""),
		    tmps, file_line, fd->filename);
	    g_free(tmps);

	    break;

	case 'M': {
	    int m_code;

	    switch (m_code = drill_parse_M_code(fd, state, image, file_line)) {
	    case DRILL_M_HEADER :
		state->curr_section = DRILL_HEADER;
		break;
	    case DRILL_M_HEADEREND :
		state->curr_section = DRILL_DATA;

		if (image->format->omit_zeros == GERBV_OMIT_ZEROS_UNSPECIFIED) {
		    /* Excellon says they default to specify leading
		       zeros, i.e. omit trailing zeros.	 The Excellon
		       files floating around that don't specify the
		       leading/trailing zeros in the header seem to
		       contradict to this though.

		       XXX We should probably ask the user. */

		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_ERROR, -1,
			    _("End of Excellon header reached "
				"but no leading/trailing zero "
				"handling specified "
				"at line %ld in file \"%s\""),
			    file_line, fd->filename);
		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_WARNING, -1,
			    _("Assuming leading zeros in file \"%s\""),
			    fd->filename);
		    image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
		}
		break;
	    case DRILL_M_METRIC :
		if (state->unit == GERBV_UNIT_UNSPECIFIED
		&&  state->curr_section != DRILL_HEADER) {
		    double size;

		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_WARNING, -1,
			    _("M71 code found but no METRIC "
				"specification in header "
				"at line %ld in file \"%s\""),
			    file_line, fd->filename);
		    gerbv_stats_printf(stats->error_list,
			    GERBV_MESSAGE_WARNING, -1,
			    _("Assuming all tool sizes are MM in file \"%s\""),
			    fd->filename);

		    stats = image->drill_stats;
		    for (int tool_num = TOOL_MIN; tool_num < TOOL_MAX;
			    tool_num++) {
			if (image->aperture && image->aperture[tool_num]) {
			    /* First update stats.   Do this before changing drill dias.
			     * Maybe also put error into stats? */
			    size = image->aperture[tool_num]->parameter[0];
			    drill_stats_modify_drill_list(stats->drill_list,
							  tool_num,
							  size,
							  "MM");
			    /* Now go back and update all tool dias, since
			     * tools are displayed in inch units
			     */
			    image->aperture[tool_num]->parameter[0] /= 25.4;
			}
		    }
		}
		if (state->autod) {
		    state->number_format = state->backup_number_format;
		    state->unit = GERBV_UNIT_MM;
		}
		break;
	    case DRILL_M_IMPERIAL :
		if (state->autod) {
		    if (state->number_format != FMT_00_0000)
			/* save metric format definition for later */
			state->backup_number_format = state->number_format;
		    state->number_format = FMT_00_0000;
		    state->decimals = 4;
		    state->unit = GERBV_UNIT_INCH;
		}

		break;
	    case DRILL_M_CANNEDTEXTX :
	    case DRILL_M_CANNEDTEXTY :
		tmps = get_line(fd);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
			_("Canned text \"%s\" "
			    "at line %ld in drill file \"%s\""),
			tmps, file_line, fd->filename);
		g_free(tmps);
		break;
	    case DRILL_M_MESSAGELONG :
	    case DRILL_M_MESSAGE :
		tmps = get_line(fd);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
			_("Message \"%s\" embedded "
			    "at line %ld in drill file \"%s\""),
			tmps, file_line, fd->filename);
		g_free(tmps);
		break;
	    case DRILL_M_PATTERNEND :
	    case DRILL_M_TOOLTIPCHECK :
		break;

	    case DRILL_M_END :
		/* M00 has optional arguments */
		eat_line(fd);

	    case DRILL_M_ENDREWIND :
		goto drill_parse_end;
		break;

	    case DRILL_M_UNKNOWN:
		gerb_ungetc(fd); /* To compare full string in function or
				    report full string  */
		if (drill_parse_header_is_metric(fd, state, image, file_line))
		    break;

		stats->M_unknown++;
		tmps = get_line(fd);
		gerbv_stats_printf(stats->error_list,
			GERBV_MESSAGE_ERROR, -1,
			_("Unrecognized string \"%s\" found "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
		g_free(tmps);

		break;

	    default:
		stats->M_unknown++;
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Unsupported M%02d (%s) code found "
			    "at line %ld in file \"%s\""),
			m_code, _(drill_m_code_name(m_code)),
			file_line, fd->filename);
		break;
	    } /* switch(m_code) */

	    break;
	} /* case 'M' */

	case 'R':
	    if (state->curr_section == DRILL_HEADER) {
		stats->unknown++;
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Not allowed 'R' code in the header "
			    "at line %ld in file \"%s\""),
			file_line, fd->filename);
	    } else {
	      double start_x, start_y;
	      double step_x, step_y;
	      int c;
	      int rcnt;
	      /*
	       * This is the "Repeat hole" command.  Format is:
	       * R##[X##][Y##]
	       * This repeats the previous hole stepping in the X and
	       * Y increments give.  Example:
	       * R04X0.1 -- repeats the drill hole 4 times, stepping
	       * 0.1" in the X direction.  Note that the X and Y step
	       * sizes default to zero if not given and that they use
	       * the same format and units as the normal X,Y
	       * coordinates.
	       */
	      stats->R++;

	      start_x = state->curr_x;
	      start_y = state->curr_y;

	      /* figure out how many repeats there are */
	      c = gerb_fgetc (fd);
	      rcnt = 0;
	      while ( '0' <= c && c <= '9') {
		rcnt = 10*rcnt + (c - '0');
		c = gerb_fgetc (fd);
	      }
	      dprintf ("working on R code (repeat) with a number of reps equal to %d\n", rcnt);

	      step_x = 0.0;
	      if (c == 'X') {
		step_x = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
		c = gerb_fgetc (fd);
	      }

	      step_y = 0.0;
	      if( c == 'Y') {
		  step_y = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	      } else {
		gerb_ungetc (fd);
	      }

	      dprintf ("Getting ready to repeat the drill %d times with delta_x = %g, delta_y = %g\n", rcnt, step_x, step_y);

	      /* spit out the drills */
	      for (c = 1 ; c <= rcnt ; c++) {
		state->curr_x = start_x + c*step_x;
		state->curr_y = start_y + c*step_y;
		dprintf ("    Repeat #%d - new location is (%g, %g)\n", c, state->curr_x, state->curr_y);
		curr_net = drill_add_drill_hole (image, state, stats, curr_net);
	      }

	    }

	case 'S':
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
		    _("Ignoring setting spindle speed "
			"at line %ld in drill file \"%s\""),
		    file_line, fd->filename);
	    eat_line(fd);
	    break;
	case 'T':
	    drill_parse_T_code(fd, state, image, file_line);
	    break;
	case 'V' :
	    gerb_ungetc (fd);
	    tmps = get_line (fd);
	    /* Silently ignore VER,1.  Not sure what others are allowed */
	    if (0 != strcmp (tmps, "VER,1")) {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
			_("Undefined string \"%s\" in header "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
	    }
	    g_free (tmps);
	    break;

	case 'X':
	case 'Y':
	    /* Hole coordinate found. Do some parsing */
	    drill_parse_coordinate(fd, read, image, state, file_line);

	    /* add the new drill hole */
	    curr_net = drill_add_drill_hole (image, state, stats, curr_net);
	    break;

	case '%':
	    state->curr_section = DRILL_DATA;
	    break;

	case '\n' :
	    file_line++;

	    /* Get <CR> char, if any, from <LF><CR> pair */
	    read = gerb_fgetc(fd);
	    if (read != '\r' && read != EOF)
		    gerb_ungetc(fd);
	    break;

	case '\r' :
	    file_line++;

	    /* Get <LF> char, if any, from <CR><LF> pair */
	    read = gerb_fgetc(fd);
	    if (read != '\n' && read != EOF)
		    gerb_ungetc(fd);
	    break;

	case ' ' :	/* White space */
	case '\t' :
	    break;

	default:
	    stats->unknown++;

	    if (DRILL_HEADER == state->curr_section) {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Undefined code '%s' (0x%x) found in header "
			    "at line %ld in file \"%s\""),
			gerbv_escape_char(read), read,
			file_line, fd->filename);
		gerb_ungetc(fd);

		/* Unrecognised crap in the header is thrown away */
		tmps = get_line(fd);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
			_("Unrecognised string \"%s\" in header "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
	  	g_free (tmps);
	    } else {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Ignoring undefined character '%s' (0x%x) "
			    "found inside data at line %ld in file \"%s\""),
			gerbv_escape_char(read), read, file_line, fd->filename);
	    }
	}
    }

    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
	    _("No EOF found in drill file \"%s\""), fd->filename);

drill_parse_end:
    dprintf ("%s():  Populating file attributes\n", __FUNCTION__);

    hid_attrs = image->info->attr_list;

    switch (state->unit) {
    case GERBV_UNIT_MM:
	hid_attrs[HA_xy_units].default_val.int_value = GERBV_UNIT_MM;
	break;

    default:
	hid_attrs[HA_xy_units].default_val.int_value = GERBV_UNIT_INCH;
	break;
    }

    switch (state->number_format) {
    case FMT_000_00:
    case FMT_0000_00:
	hid_attrs[HA_digits].default_val.int_value = 2;
	break;

    case FMT_000_000:
	hid_attrs[HA_digits].default_val.int_value = 3;
	break;

    case FMT_00_0000:
	hid_attrs[HA_digits].default_val.int_value = 4;
	break;

    case FMT_USER:
	dprintf ("%s():  Keeping user specified number of decimal places (%d)\n",
		 __FUNCTION__,
		 hid_attrs[HA_digits].default_val.int_value);
	break;

    default:
	break;
    }

    switch (image->format->omit_zeros) {
    case GERBV_OMIT_ZEROS_LEADING:
	hid_attrs[HA_suppression].default_val.int_value = SUP_LEAD;
	break;

    case GERBV_OMIT_ZEROS_TRAILING:
	hid_attrs[HA_suppression].default_val.int_value = SUP_TRAIL;
	break;

    default:
	hid_attrs[HA_suppression].default_val.int_value = SUP_NONE;
	break;
    }

    g_free(state);

    return image;
} /* parse_drillfile */

An out-of-bounds write vulnerability exists in the drill format T-code tool number functionality of Gerbv 2.7.0, dev (commit b5f1eacd), and the forked version of Gerbv (commit 71493260). A specially-crafted drill file can lead to code execution. An attacker can provide a malicious file to trigger this vulnerability.


drill_attribute_merge (gerbv_HID_Attribute *dest, int ndest, gerbv_HID_Attribute *src, int nsrc)
{
    int i, j;

    /* Here is a brain dead merge algorithm which should make anyone cringe.
     * Still, it is simple and we won't merge many attributes and not
     * many times either.
     */

    for (i = 0 ; i < nsrc ; i++) {
	/* see if our destination wants this attribute */
	j = 0;
	while (j < ndest && strcmp (src[i].name, dest[j].name) != 0)
	    j++;

	/* if we wanted it and it is the same type, copy it over */
	if (j < ndest && src[i].type == dest[j].type) {
	    dest[j].default_val = src[i].default_val;
	} else {
	    GERB_MESSAGE("Ignoring \"%s\" attribute for drill file", src[i].name);
	}
    }
}


drill_parse_header_is_inch(gerb_file_t *fd, drill_state_t *state,
			gerbv_image_t *image, ssize_t file_line)
{
    gerbv_drill_stats_t *stats = image->drill_stats;
    char c;

    dprintf("    %s(): entering\n", __FUNCTION__);

    if (DRILL_HEADER != state->curr_section)
	return 0;

    switch (file_check_str(fd, "INCH")) {
    case -1:
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Unexpected EOF found while parsing \"%s\" string "
		    "in file \"%s\""), "INCH", fd->filename);
	return 0;

    case 0:
	return 0;
    }

    /* Look for TZ/LZ */
    if (',' != gerb_fgetc(fd)) {
	/* Unget the char in case we just advanced past a new line char */
	gerb_ungetc (fd);
    } else {
	c = gerb_fgetc(fd);
	if (c != EOF && 'Z' == gerb_fgetc(fd)) {
	    switch (c) {
	    case 'L':
		if (state->autod) {
		    image->format->omit_zeros = GERBV_OMIT_ZEROS_TRAILING;
		    state->header_number_format =
			state->number_format = FMT_00_0000;
		    state->decimals = 4;
		}
		break;

	    case 'T':
		if (state->autod) {
		    image->format->omit_zeros = GERBV_OMIT_ZEROS_LEADING;
		    state->header_number_format =
			state->number_format = FMT_00_0000;
		    state->decimals = 4;
		}
		break;

	    default:
		gerbv_stats_printf(stats->error_list,
			GERBV_MESSAGE_WARNING, -1,
			_("Found junk '%s' after "
			    "INCH command "
			    "at line %ld in file \"%s\""),
			gerbv_escape_char(c),
			file_line, fd->filename);
		break;
	    }
	} else {
	    gerbv_stats_printf(stats->error_list,
		    GERBV_MESSAGE_WARNING, -1,
		    _("Found junk '%s' after INCH command "
			"at line %ld in file \"%s\""),
		    gerbv_escape_char(c),
		    file_line, fd->filename);
	}
    }

    state->unit = GERBV_UNIT_INCH;

    return 1;
} /* drill_parse_header_is_inch() */


drill_add_drill_hole (gerbv_image_t *image, drill_state_t *state,
		gerbv_drill_stats_t *stats, gerbv_net_t *curr_net)
{
    gerbv_render_size_t *bbox;
    double r;

    /* Add one to drill stats  for the current tool */
    drill_stats_increment_drill_counter(image->drill_stats->drill_list,
	    state->current_tool);

    curr_net->next = g_new0(gerbv_net_t, 1);
    if (curr_net->next == NULL)
	GERB_FATAL_ERROR("malloc curr_net->next failed in %s()",
			__FUNCTION__);

    curr_net = curr_net->next;
    curr_net->layer = image->layers;
    curr_net->state = image->states;
    curr_net->start_x = state->curr_x;
    curr_net->start_y = state->curr_y;
    /* KLUDGE. This function isn't allowed to return anything
       but inches */
    if(state->unit == GERBV_UNIT_MM) {
	curr_net->start_x /= 25.4;
	curr_net->start_y /= 25.4;
	/* KLUDGE. All images, regardless of input format,
	   are returned in INCH format */
	curr_net->state->unit = GERBV_UNIT_INCH;
    }

    curr_net->stop_x = curr_net->start_x - state->origin_x;
    curr_net->stop_y = curr_net->start_y - state->origin_y;
    curr_net->aperture = state->current_tool;
    curr_net->aperture_state = GERBV_APERTURE_STATE_FLASH;

    /* Check if aperture is set. Ignore the below instead of
       causing SEGV... */
    if(image->aperture[state->current_tool] == NULL)
	return curr_net;

    bbox = &curr_net->boundingBox;
    r = image->aperture[state->current_tool]->parameter[0] / 2;

    /* Set boundingBox */
    bbox->left   = curr_net->start_x - r;
    bbox->right  = curr_net->start_x + r;
    bbox->bottom = curr_net->start_y - r;
    bbox->top    = curr_net->start_y + r;

    drill_update_image_info_min_max_from_bbox(image->info, bbox);

    return curr_net;
}


get_line(gerb_file_t *fd)
{
	int read;
	gchar *retstring;
	gchar *tmps=g_strdup("");

	read = gerb_fgetc(fd);
	while (read != '\n' && read != '\r' && read != EOF) {
		retstring = g_strdup_printf("%s%c", tmps, read);

		/* since g_strdup_printf allocates memory, we need to free it */
		if (tmps)  {
			g_free (tmps);
			tmps = NULL;
		}
		tmps = retstring;
		read = gerb_fgetc(fd);
	}

	/* Restore new line character for processing */
	if (read != EOF)
	    gerb_ungetc(fd);

	return tmps;
} /* get_line */


file_check_str(gerb_file_t *fd, const char *str)
{
    char c;

    for (int i = 0; str[i] != '\0'; i++) {

	c = gerb_fgetc(fd);

	if (c == EOF)
	    return -1;

	if (c != str[i]) {
	    do {
		/* Restore checked string */
		gerb_ungetc(fd);
	    } while (i--);

	    return 0;
	}
    }

    return 1;
}


drill_parse_header_is_ici(gerb_file_t *fd, drill_state_t *state,
			gerbv_image_t *image, ssize_t file_line)
{
    gerbv_drill_stats_t *stats = image->drill_stats;

    switch (file_check_str(fd, "ICI,ON")) {
    case -1:
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Unexpected EOF found while parsing \"%s\" string "
		    "in file \"%s\""), "ICI,ON", fd->filename);
	return 0;

    case 1:
	state->coordinate_mode = DRILL_MODE_INCREMENTAL;
	return 1;
    }

    switch (file_check_str(fd, "ICI,OFF")) {
    case -1:
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Unexpected EOF found while parsing \"%s\" string "
		    "in file \"%s\""), "ICI,OFF", fd->filename);
	return 0;

    case 1:
	state->coordinate_mode = DRILL_MODE_ABSOLUTE;
	return 1;
    }

    return 0;
} /* drill_parse_header_is_ici() */


read_double(gerb_file_t *fd, number_fmt_t fmt, gerbv_omit_zeros_t omit_zeros, int decimals)
{
    int read;
    char temp[DRILL_READ_DOUBLE_SIZE];
    unsigned int i = 0, ndigits = 0;
    double result;
    gboolean decimal_point = FALSE;
    gboolean sign_prepend = FALSE;

    memset(temp, 0, sizeof(temp));

    read = gerb_fgetc(fd);
    while(read != EOF && i < (DRILL_READ_DOUBLE_SIZE -1) &&
	  (isdigit(read) || read == '.' || read == ',' || read == '+' || read == '-')) {
      if(read == ',' || read == '.') decimal_point = TRUE;

      /*
       * FIXME -- if we are going to do this, don't we need a
       * locale-independent strtod()?  I think pcb has one.
       */
      if(read == ',')
	    read = '.'; /* adjust for strtod() */

      if(isdigit(read)) ndigits++;

	if(read == '-' || read == '+')
	    sign_prepend = TRUE;

      temp[i++] = (char)read;
      read = gerb_fgetc(fd);
    }

    temp[i] = 0;
    gerb_ungetc(fd);

    if (decimal_point) {
	result = strtod(temp, NULL);
    } else {
	unsigned int wantdigits;
	double scale;
	char tmp2[DRILL_READ_DOUBLE_SIZE];

	memset(tmp2, 0, sizeof(tmp2));

	/* Nothing to take care for when leading zeros are
	   omitted. */
	if (omit_zeros == GERBV_OMIT_ZEROS_TRAILING) {
	    switch (fmt) {
	    case FMT_00_0000:
	      wantdigits = 2;
	      break;

	    case FMT_000_000:
	      wantdigits = 3;
	      break;

	    case FMT_0000_00:
		wantdigits = 4;
		break;

	    case FMT_000_00:
		wantdigits = 3;
		break;

	    case FMT_USER:
		wantdigits = decimals;
		break;

	    default:
	      /* cannot happen, just plugs a compiler warning */
	      fprintf(stderr, _("%s():  omit_zeros == GERBV_OMIT_ZEROS_TRAILING but fmt = %d.\n"
		      "This should never have happened\n"), __FUNCTION__, fmt);
	      return 0;
	    }

	    /* need to add an extra char for '+' or '-' */
	    if (sign_prepend)
	      wantdigits++;

	    /*
	     * we need at least wantdigits + one for the decimal place
	     * + one for the terminating null character
	     */
	    if (wantdigits > sizeof(tmp2) - 2) {
	      fprintf(stderr, _("%s():  wantdigits = %d which exceeds the maximum allowed size\n"),
		      __FUNCTION__, wantdigits);
	      return 0;
	    }

	    /*
	     * After we have read the correct number of digits
	     * preceeding the decimal point, insert a decimal point
	     * and append the rest of the digits.
	     */
	    dprintf("%s():  wantdigits = %d, strlen(\"%s\") = %ld\n",
		    __FUNCTION__, wantdigits, temp, (long) strlen(temp));
	    for (i = 0 ; i < wantdigits && i < strlen(temp) ; i++) {
	      tmp2[i] = temp[i];
	    }
	    for ( ; i < wantdigits ; i++) {
	      tmp2[i] = '0';
	    }
	    tmp2[i++] = '.';
	    for ( ; i <= strlen(temp) ; i++) {
	      tmp2[i] = temp[i-1];
	    }
	    dprintf("%s():  After dealing with trailing zero suppression, convert \"%s\"\n", __FUNCTION__, tmp2);
	    scale = 1.0;

	    for (i = 0 ; i <= strlen(tmp2) && i < sizeof (temp) ; i++) {
	      temp[i] = tmp2[i];
	    }

	} else {

	  /*
	   * figure out the scale factor when we are not suppressing
	   * trailing zeros.
	   */
	  switch (fmt) {
	  case FMT_00_0000:
	    scale = 1E-4;
	    break;

	  case FMT_000_000:
	    scale = 1E-3;
	    break;

	  case FMT_000_00:
	  case FMT_0000_00:
	    scale = 1E-2;
	    break;

	  case FMT_USER:
	    scale = pow (10.0, -1.0*decimals);
	    break;

	  default:
	    /* cannot happen, just plugs a compiler warning */
	    fprintf (stderr, _("%s(): Unhandled fmt ` %d\n"), __FUNCTION__, fmt);
	    exit (1);
	  }
	}

	result = strtod(temp, NULL) * scale;
    }

    dprintf("    %s()=%f: fmt=%d, omit_zeros=%d, decimals=%d \n",
		    __FUNCTION__, result, fmt, omit_zeros, decimals);

    return result;
} /* read_double */


drill_parse_M_code(gerb_file_t *fd, drill_state_t *state,
	gerbv_image_t *image, ssize_t file_line)
{
    gerbv_drill_stats_t *stats = image->drill_stats;
    drill_m_code_t m_code;
    char op[3];

    dprintf("---> entering %s() ...\n", __FUNCTION__);

    op[0] = gerb_fgetc(fd);
    op[1] = gerb_fgetc(fd);
    op[2] = '\0';

    if (op[0] == EOF
    ||  op[1] == EOF) {
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Unexpected EOF found while parsing M-code in file \"%s\""),
		fd->filename);

	return DRILL_M_UNKNOWN;
    }

    dprintf("  Compare M-code \"%s\" at line %ld\n", op, file_line);

    switch (m_code = atoi(op)) {
    case 0:
	/* atoi() return 0 in case of error, recheck string */
	if (0 != strncmp(op, "00", 2)) {
	    m_code = DRILL_M_UNKNOWN;
	    gerb_ungetc(fd);
	    gerb_ungetc(fd);
	    break;
	}
	stats->M00++;
	break;
    case 1:
	stats->M01++;
	break;
    case 18:
	stats->M18++;
	break;
    case 25:
	stats->M25++;
	break;
    case 30:
	stats->M30++;
	break;
    case 45:
	stats->M45++;
	break;
    case 47:
	stats->M47++;
	break;
    case 48:
	stats->M48++;
	break;
    case 71:
	stats->M71++;
	eat_line(fd);
	break;
    case 72:
	stats->M72++;
	eat_line(fd);
	break;
    case 95:
	stats->M95++;
	break;
    case 97:
	stats->M97++;
	break;
    case 98:
	stats->M98++;
	break;

    default:
    case DRILL_M_UNKNOWN:
	break;
    }

    dprintf("<----  ...leaving %s()\n", __FUNCTION__);

    return m_code;
} /* drill_parse_M_code() */


drill_parse_coordinate(gerb_file_t *fd, char firstchar,
		       gerbv_image_t *image, drill_state_t *state,
		       ssize_t file_line)
{
    int read;
    gerbv_drill_stats_t *stats = image->drill_stats;

    if(state->coordinate_mode == DRILL_MODE_ABSOLUTE) {
	if (firstchar == 'X') {
	    state->curr_x = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	    if ((read = (char)gerb_fgetc(fd)) == 'Y') {
		state->curr_y = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	    } else {
		gerb_ungetc(fd);
	    }
	} else if (firstchar == 'Y') {
	    state->curr_y = read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	}
    } else if(state->coordinate_mode == DRILL_MODE_INCREMENTAL) {
	if (firstchar == 'X') {
	    state->curr_x += read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	    if((read = (char)gerb_fgetc(fd)) == 'Y') {
		state->curr_y += read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	    } else {
		gerb_ungetc(fd);
	    }
	} else if (firstchar == 'Y') {
	    state->curr_y += read_double(fd, state->number_format, image->format->omit_zeros, state->decimals);
	}
    } else {
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Coordinate mode is not absolute and not incremental "
		    "at line %ld in file \"%s\""),
		file_line, fd->filename);
    }

} /* drill_parse_coordinate */


new_state(drill_state_t *state)
{
    state = g_new0(drill_state_t, 1);
    if (state != NULL) {
	/* Init structure */
	state->curr_section = DRILL_NONE;
	state->coordinate_mode = DRILL_MODE_ABSOLUTE;
	state->origin_x = 0.0;
	state->origin_y = 0.0;
	state->unit = GERBV_UNIT_UNSPECIFIED;
	state->backup_number_format = FMT_000_000; /* only used for METRIC */
	state->header_number_format = state->number_format = FMT_00_0000; /* i. e. INCH */
	state->autod = 1;
	state->decimals = 4;
    }

    return state;
} /* new_state */


drill_parse_T_code(gerb_file_t *fd, drill_state_t *state,
			gerbv_image_t *image, ssize_t file_line)
{
    int tool_num;
    gboolean done = FALSE;
    int temp;
    double size;
    gerbv_drill_stats_t *stats = image->drill_stats;
    gerbv_aperture_t *apert;
    gchar *tmps;
    gchar *string;

    dprintf("---> entering %s()...\n", __FUNCTION__);

    /* Sneak a peek at what's hiding after the 'T'. Ugly fix for
       broken headers from Orcad, which is crap */
    temp = gerb_fgetc(fd);
    dprintf("  Found a char '%s' (0x%02x) after the T\n",
	    gerbv_escape_char(temp), temp);

    /* might be a tool tool change stop switch on/off*/
    if((temp == 'C') && ((fd->ptr + 2) < fd->datalen)){
    	if(gerb_fgetc(fd) == 'S'){
    	    if (gerb_fgetc(fd) == 'T' ){
    	  	fd->ptr -= 4;
    	  	tmps = get_line(fd++);
    	  	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_NOTE, -1,
			_("Tool change stop switch found \"%s\" "
			    "at line %ld in file \"%s\""),
			tmps, file_line, fd->filename);
	  	g_free (tmps);

	  	return -1;
	    }
	    gerb_ungetc(fd);
	}
	gerb_ungetc(fd);
    }

    if( !(isdigit(temp) != 0 || temp == '+' || temp =='-') ) {
	if(temp != EOF) {
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		   _("OrCAD bug: Junk text found in place of tool definition"));
	    tmps = get_line(fd);
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
		    _("Junk text \"%s\" "
			"at line %ld in file \"%s\""),
		    tmps, file_line, fd->filename);
	    g_free (tmps);
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
				  _("Ignoring junk text"));
	}
	return -1;
    }
    gerb_ungetc(fd);

    tool_num = (int) gerb_fgetint(fd, NULL);
    dprintf ("  Handling tool T%d at line %ld\n", tool_num, file_line);

    if (tool_num == 0)
	return tool_num; /* T00 is a command to unload the drill */

    if (tool_num < TOOL_MIN || tool_num >= TOOL_MAX) {
	gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		_("Out of bounds drill number %d "
		    "at line %ld in file \"%s\""),
		tool_num, file_line, fd->filename);
	return -1;
    }

    /* Set the current tool to the correct one */
    state->current_tool = tool_num;
    apert = image->aperture[tool_num];

    /* Check for a size definition */
    temp = gerb_fgetc(fd);

    /* This bit of code looks for a tool definition by scanning for strings
     * of form TxxC, TxxF, TxxS.  */
    while (!done) {
	switch((char)temp) {
	case 'C':
	    size = read_double(fd, state->header_number_format, GERBV_OMIT_ZEROS_TRAILING, state->decimals);
	    dprintf ("  Read a size of %g\n", size);

	    if (state->unit == GERBV_UNIT_MM) {
		size /= 25.4;
	    } else if(size >= 4.0) {
		/* If the drill size is >= 4 inches, assume that this
		   must be wrong and that the units are mils.
		   The limit being 4 inches is because the smallest drill
		   I've ever seen used is 0,3mm(about 12mil). Half of that
		   seemed a bit too small a margin, so a third it is */

		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Read a drill of diameter %g inches "
			    "at line %ld in file \"%s\""),
			    size, file_line, fd->filename);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
			_("Assuming units are mils"));
		size /= 1000.0;
	    }

	    if (size <= 0. || size >= 10000.) {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Unreasonable drill size %g found for drill %d "
			    "at line %ld in file \"%s\""),
			    size, tool_num, file_line, fd->filename);
	    } else {
		if (apert != NULL) {
		    /* allow a redefine of a tool only if the new definition is exactly the same.
		     * This avoid lots of spurious complaints with the output of some cad
		     * tools while keeping complaints if there is a true problem
		     */
		    if (apert->parameter[0] != size
		    ||  apert->type != GERBV_APTYPE_CIRCLE
		    ||  apert->nuf_parameters != 1
		    ||  apert->unit != GERBV_UNIT_INCH) {

			gerbv_stats_printf(stats->error_list,
				GERBV_MESSAGE_ERROR, -1,
				_("Found redefinition of drill %d "
				"at line %ld in file \"%s\""),
				tool_num, file_line, fd->filename);
		    }
		} else {
		    apert = image->aperture[tool_num] =
						g_new0(gerbv_aperture_t, 1);
		    if (apert == NULL)
			GERB_FATAL_ERROR("malloc tool failed in %s()",
					__FUNCTION__);

		    /* There's really no way of knowing what unit the tools
		       are defined in without sneaking a peek in the rest of
		       the file first. That's done in drill_guess_format() */
		    apert->parameter[0] = size;
		    apert->type = GERBV_APTYPE_CIRCLE;
		    apert->nuf_parameters = 1;
		    apert->unit = GERBV_UNIT_INCH;
		}
	    }

	    /* Add the tool whose definition we just found into the list
	     * of tools for this layer used to generate statistics. */
	    stats = image->drill_stats;
	    string = g_strdup_printf("%s", (state->unit == GERBV_UNIT_MM ? _("mm") : _("inch")));
	    drill_stats_add_to_drill_list(stats->drill_list,
					  tool_num,
					  state->unit == GERBV_UNIT_MM ? size*25.4 : size,
					  string);
	    g_free(string);
	    break;

	case 'F':
	case 'S' :
	    /* Silently ignored. They're not important. */
	    gerb_fgetint(fd, NULL);
	    break;

	default:
	    /* Stop when finding anything but what's expected
	       (and put it back) */
	    gerb_ungetc(fd);
	    done = TRUE;
	    break;
	}  /* switch((char)temp) */

	temp = gerb_fgetc(fd);
	if (EOF == temp) {
	    gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
		    _("Unexpected EOF encountered in header of "
			"drill file \"%s\""), fd->filename);

	/* Restore new line character for processing */
	if ('\n' == temp || '\r' == temp)
	    gerb_ungetc(fd);
	}
    }   /* while(!done) */  /* Done looking at tool definitions */

    /* Catch the tools that aren't defined.
       This isn't strictly a good thing, but at least something is shown */
    if (apert == NULL) {
        double dia;

	apert = image->aperture[tool_num] = g_new0(gerbv_aperture_t, 1);
	if (apert == NULL)
	    GERB_FATAL_ERROR("malloc tool failed in %s()", __FUNCTION__);

        /* See if we have the tool table */
        dia = gerbv_get_tool_diameter(tool_num);
        if (dia <= 0) {
            /*
             * There is no tool. So go out and make some.
             * This size calculation is, of course, totally bogus.
             */
            dia = (double)(16 + 8 * tool_num) / 1000;
            /*
             * Oooh, this is sooo ugly. But some CAD systems seem to always
             * use T00 at the end of the file while others that don't have
             * tool definitions inside the file never seem to use T00 at all.
             */
            if (tool_num != 0) {
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_ERROR, -1,
			_("Tool %02d used without being defined "
			    "at line %ld in file \"%s\""),
			tool_num, file_line, fd->filename);
		gerbv_stats_printf(stats->error_list, GERBV_MESSAGE_WARNING, -1,
			_("Setting a default size of %g\""), dia);
            }
	}

	apert->type = GERBV_APTYPE_CIRCLE;
	apert->nuf_parameters = 1;
	apert->parameter[0] = dia;

	/* Add the tool whose definition we just found into the list
	 * of tools for this layer used to generate statistics. */
	if (tool_num != 0) {  /* Only add non-zero tool nums.
			       * Zero = unload command. */
	    stats = image->drill_stats;
	    string = g_strdup_printf("%s",
				     (state->unit == GERBV_UNIT_MM ? _("mm") : _("inch")));
	    drill_stats_add_to_drill_list(stats->drill_list,
					  tool_num,
					  state->unit == GERBV_UNIT_MM ? dia*25.4 : dia,
					  string);
	    g_free(string);
	}
    } /* if(image->aperture[tool_num] == NULL) */

    dprintf("<----  ...leaving %s()\n", __FUNCTION__);

    return tool_num;
} /* drill_parse_T_code() */


patch 8.2.4397: crash when using many composing characters in error message

Problem:    Crash when using many composing characters in error message.
Solution:   Use mb_cptr2char_adv() instead of mb_ptr2char_adv().

assert_match_common(typval_T *argvars, assert_type_T atype)
{
    garray_T	ga;
    char_u	buf1[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		called_emsg_before = called_emsg;
    char_u	*pat;
    char_u	*text;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return 1;

    pat = tv_get_string_buf_chk(&argvars[0], buf1);
    text = tv_get_string_buf_chk(&argvars[1], buf2);
    if (called_emsg == called_emsg_before
		 && pattern_match(pat, text, FALSE) != (atype == ASSERT_MATCH))
    {
	prepare_assert_error(&ga);
	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
									atype);
	assert_error(&ga);
	ga_clear(&ga);
	return 1;
    }
    return 0;
}

Stack-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.


f_assert_nobeep(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = assert_beeps(argvars, TRUE);
}


f_assert_fails(typval_T *argvars, typval_T *rettv)
{
    char_u	*cmd;
    garray_T	ga;
    int		save_trylevel = trylevel;
    int		called_emsg_before = called_emsg;
    char	*wrong_arg_msg = NULL;

    if (check_for_string_or_number_arg(argvars, 0) == FAIL
	    || check_for_opt_string_or_list_arg(argvars, 1) == FAIL
	    || (argvars[1].v_type != VAR_UNKNOWN
		&& (argvars[2].v_type != VAR_UNKNOWN
		    && (check_for_opt_number_arg(argvars, 3) == FAIL
			|| (argvars[3].v_type != VAR_UNKNOWN
			    && check_for_opt_string_arg(argvars, 4) == FAIL)))))
	return;

    cmd = tv_get_string_chk(&argvars[0]);

    // trylevel must be zero for a ":throw" command to be considered failed
    trylevel = 0;
    suppress_errthrow = TRUE;
    in_assert_fails = TRUE;

    do_cmdline_cmd(cmd);
    if (called_emsg == called_emsg_before)
    {
	prepare_assert_error(&ga);
	ga_concat(&ga, (char_u *)"command did not fail: ");
	assert_append_cmd_or_arg(&ga, argvars, cmd);
	assert_error(&ga);
	ga_clear(&ga);
	rettv->vval.v_number = 1;
    }
    else if (argvars[1].v_type != VAR_UNKNOWN)
    {
	char_u	buf[NUMBUFLEN];
	char_u	*expected;
	char_u	*expected_str = NULL;
	int	error_found = FALSE;
	int	error_found_index = 1;
	char_u	*actual = emsg_assert_fails_msg == NULL ? (char_u *)"[unknown]"
						       : emsg_assert_fails_msg;

	if (argvars[1].v_type == VAR_STRING)
	{
	    expected = tv_get_string_buf_chk(&argvars[1], buf);
	    error_found = expected == NULL
			   || strstr((char *)actual, (char *)expected) == NULL;
	}
	else if (argvars[1].v_type == VAR_LIST)
	{
	    list_T	*list = argvars[1].vval.v_list;
	    typval_T	*tv;

	    if (list == NULL || list->lv_len < 1 || list->lv_len > 2)
	    {
		wrong_arg_msg = e_assert_fails_second_arg;
		goto theend;
	    }
	    CHECK_LIST_MATERIALIZE(list);
	    tv = &list->lv_first->li_tv;
	    expected = tv_get_string_buf_chk(tv, buf);
	    if (!pattern_match(expected, actual, FALSE))
	    {
		error_found = TRUE;
		expected_str = expected;
	    }
	    else if (list->lv_len == 2)
	    {
		tv = &list->lv_u.mat.lv_last->li_tv;
		actual = get_vim_var_str(VV_ERRMSG);
		expected = tv_get_string_buf_chk(tv, buf);
		if (!pattern_match(expected, actual, FALSE))
		{
		    error_found = TRUE;
		    expected_str = expected;
		}
	    }
	}
	else
	{
	    wrong_arg_msg = e_assert_fails_second_arg;
	    goto theend;
	}

	if (!error_found && argvars[2].v_type != VAR_UNKNOWN
		&& argvars[3].v_type != VAR_UNKNOWN)
	{
	    if (argvars[3].v_type != VAR_NUMBER)
	    {
		wrong_arg_msg = e_assert_fails_fourth_argument;
		goto theend;
	    }
	    else if (argvars[3].vval.v_number >= 0
			 && argvars[3].vval.v_number != emsg_assert_fails_lnum)
	    {
		error_found = TRUE;
		error_found_index = 3;
	    }
	    if (!error_found && argvars[4].v_type != VAR_UNKNOWN)
	    {
		if (argvars[4].v_type != VAR_STRING)
		{
		    wrong_arg_msg = e_assert_fails_fifth_argument;
		    goto theend;
		}
		else if (argvars[4].vval.v_string != NULL
		    && !pattern_match(argvars[4].vval.v_string,
					     emsg_assert_fails_context, FALSE))
		{
		    error_found = TRUE;
		    error_found_index = 4;
		}
	    }
	}

	if (error_found)
	{
	    typval_T actual_tv;

	    prepare_assert_error(&ga);
	    if (error_found_index == 3)
	    {
		actual_tv.v_type = VAR_NUMBER;
		actual_tv.vval.v_number = emsg_assert_fails_lnum;
	    }
	    else if (error_found_index == 4)
	    {
		actual_tv.v_type = VAR_STRING;
		actual_tv.vval.v_string = emsg_assert_fails_context;
	    }
	    else
	    {
		actual_tv.v_type = VAR_STRING;
		actual_tv.vval.v_string = actual;
	    }
	    fill_assert_error(&ga, &argvars[2], expected_str,
			&argvars[error_found_index], &actual_tv, ASSERT_OTHER);
	    ga_concat(&ga, (char_u *)": ");
	    assert_append_cmd_or_arg(&ga, argvars, cmd);
	    assert_error(&ga);
	    ga_clear(&ga);
	    rettv->vval.v_number = 1;
	}
    }

theend:
    trylevel = save_trylevel;
    suppress_errthrow = FALSE;
    in_assert_fails = FALSE;
    did_emsg = FALSE;
    msg_col = 0;
    need_wait_return = FALSE;
    emsg_on_display = FALSE;
    msg_scrolled = 0;
    lines_left = Rows;
    VIM_CLEAR(emsg_assert_fails_msg);
    set_vim_var_string(VV_ERRMSG, NULL, 0);
    if (wrong_arg_msg != NULL)
	emsg(_(wrong_arg_msg));
}


f_test_setmouse(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type) != VAR_NUMBER)
    {
	emsg(_(e_invalid_argument));
	return;
    }

    mouse_row = (time_t)tv_get_number(&argvars[0]) - 1;
    mouse_col = (time_t)tv_get_number(&argvars[1]) - 1;
}


f_test_null_job(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_JOB;
    rettv->vval.v_job = NULL;
}


f_test_alloc_fail(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script()
	    && (check_for_number_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL
		|| check_for_number_arg(argvars, 2) == FAIL))
	return;

    if (argvars[0].v_type != VAR_NUMBER
	    || argvars[0].vval.v_number <= 0
	    || argvars[1].v_type != VAR_NUMBER
	    || argvars[1].vval.v_number < 0
	    || argvars[2].v_type != VAR_NUMBER)
	emsg(_(e_invalid_argument));
    else
    {
	alloc_fail_id = argvars[0].vval.v_number;
	if (alloc_fail_id >= aid_last)
	    emsg(_(e_invalid_argument));
	alloc_fail_countdown = argvars[1].vval.v_number;
	alloc_fail_repeat = argvars[2].vval.v_number;
	did_outofmem_msg = FALSE;
    }
}


f_assert_notequal(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
}


f_assert_true(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_bool(argvars, TRUE);
}


f_test_null_string(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_STRING;
    rettv->vval.v_string = NULL;
}


f_test_settime(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
	return;

    time_for_testing = (time_t)tv_get_number(&argvars[0]);
}


f_assert_equalfile(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_string_arg(argvars, 1) == FAIL
		|| check_for_opt_string_arg(argvars, 2) == FAIL))
	return;

    rettv->vval.v_number = assert_equalfile(argvars);
}


f_assert_false(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_bool(argvars, FALSE);
}


f_test_getvalue(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_STRING)
	emsg(_(e_invalid_argument));
    else
    {
	char_u *name = tv_get_string(&argvars[0]);

	if (STRCMP(name, (char_u *)"need_fileinfo") == 0)
	    rettv->vval.v_number = need_fileinfo;
	else
	    semsg(_(e_invalid_argument_str), name);
    }
}


f_assert_report(typval_T *argvars, typval_T *rettv)
{
    garray_T	ga;

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

    prepare_assert_error(&ga);
    ga_concat(&ga, tv_get_string(&argvars[0]));
    assert_error(&ga);
    ga_clear(&ga);
    rettv->vval.v_number = 1;
}


f_assert_inrange(typval_T *argvars, typval_T *rettv)
{
    if (check_for_float_or_nr_arg(argvars, 0) == FAIL
	    || check_for_float_or_nr_arg(argvars, 1) == FAIL
	    || check_for_float_or_nr_arg(argvars, 2) == FAIL
	    || check_for_opt_string_arg(argvars, 3) == FAIL)
	return;

    rettv->vval.v_number = assert_inrange(argvars);
}


f_assert_notmatch(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
}


f_test_null_channel(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_CHANNEL;
    rettv->vval.v_channel = NULL;
}


f_test_null_function(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_FUNC;
    rettv->vval.v_string = NULL;
}


f_test_feedinput(typval_T *argvars, typval_T *rettv UNUSED)
{
#ifdef USE_INPUT_BUF
    char_u	*val;

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

    val = tv_get_string_chk(&argvars[0]);
# ifdef VIMDLL
    // this doesn't work in the console
    if (!gui.in_use)
	return;
# endif

    if (val != NULL)
    {
	trash_input_buf();
	add_to_input_buf_csi(val, (int)STRLEN(val));
    }
#endif
}


f_test_garbagecollect_now(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    // This is dangerous, any Lists and Dicts used internally may be freed
    // while still in use.
    if (!get_vim_var_nr(VV_TESTING))
	emsg(_(e_calling_test_garbagecollect_now_while_v_testing_is_not_set));
    else
	garbage_collect(TRUE);
}


test_gui_tabline_event(dict_T *args UNUSED)
{
#  ifdef FEAT_GUI_TABLINE
    int		tabnr;

    if (dict_find(args, (char_u *)"tabnr", -1) == NULL)
	return FALSE;

    tabnr = (int)dict_get_number(args, (char_u *)"tabnr");

    return send_tabline_event(tabnr);
#  else
    return FALSE;
#  endif
}


assert_bool(typval_T *argvars, int isTrue)
{
    int		error = FALSE;
    garray_T	ga;

    if (argvars[0].v_type == VAR_BOOL
	    && argvars[0].vval.v_number == (isTrue ? VVAL_TRUE : VVAL_FALSE))
	return 0;
    if (argvars[0].v_type != VAR_NUMBER
	    || (tv_get_number_chk(&argvars[0], &error) == 0) == isTrue
	    || error)
    {
	prepare_assert_error(&ga);
	fill_assert_error(&ga, &argvars[1],
		(char_u *)(isTrue ? "True" : "False"),
		NULL, &argvars[0], ASSERT_OTHER);
	assert_error(&ga);
	ga_clear(&ga);
	return 1;
    }
    return 0;
}


f_test_null_blob(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_BLOB;
    rettv->vval.v_blob = NULL;
}


f_test_gui_event(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
# ifdef FEAT_GUI
    char_u	*event;

    rettv->v_type = VAR_BOOL;
    rettv->vval.v_number = FALSE;

    if (check_for_string_arg(argvars, 0) == FAIL
	    || check_for_dict_arg(argvars, 1) == FAIL
	    || argvars[1].vval.v_dict == NULL)
	return;

    event = tv_get_string(&argvars[0]);
    if (STRCMP(event, "dropfiles") == 0)
	rettv->vval.v_number = test_gui_drop_files(argvars[1].vval.v_dict);
#  if defined(FIND_REPLACE_DIALOG)
    else if (STRCMP(event, "findrepl") == 0)
	rettv->vval.v_number = test_gui_find_repl(argvars[1].vval.v_dict);
#  endif
    else if (STRCMP(event, "mouse") == 0)
	rettv->vval.v_number = test_gui_mouse_event(argvars[1].vval.v_dict);
    else if (STRCMP(event, "scrollbar") == 0)
	rettv->vval.v_number = test_gui_scrollbar(argvars[1].vval.v_dict);
    else if (STRCMP(event, "tabline") == 0)
	rettv->vval.v_number = test_gui_tabline_event(argvars[1].vval.v_dict);
    else if (STRCMP(event, "tabmenu") == 0)
	rettv->vval.v_number = test_gui_tabmenu_event(argvars[1].vval.v_dict);
    else
    {
	semsg(_(e_invalid_argument_str), event);
	return;
    }
# endif
}


f_test_null_dict(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv_dict_set(rettv, NULL);
}


assert_equalfile(typval_T *argvars)
{
    char_u	buf1[NUMBUFLEN];
    char_u	buf2[NUMBUFLEN];
    int		called_emsg_before = called_emsg;
    char_u	*fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
    char_u	*fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
    garray_T	ga;
    FILE	*fd1;
    FILE	*fd2;
    char	line1[200];
    char	line2[200];
    int		lineidx = 0;

    if (called_emsg > called_emsg_before)
	return 0;

    IObuff[0] = NUL;
    fd1 = mch_fopen((char *)fname1, READBIN);
    if (fd1 == NULL)
    {
	vim_snprintf((char *)IObuff, IOSIZE, (char *)e_cant_read_file_str, fname1);
    }
    else
    {
	fd2 = mch_fopen((char *)fname2, READBIN);
	if (fd2 == NULL)
	{
	    fclose(fd1);
	    vim_snprintf((char *)IObuff, IOSIZE, (char *)e_cant_read_file_str, fname2);
	}
	else
	{
	    int	    c1, c2;
	    long    count = 0;
	    long    linecount = 1;

	    for (;;)
	    {
		c1 = fgetc(fd1);
		c2 = fgetc(fd2);
		if (c1 == EOF)
		{
		    if (c2 != EOF)
			STRCPY(IObuff, "first file is shorter");
		    break;
		}
		else if (c2 == EOF)
		{
		    STRCPY(IObuff, "second file is shorter");
		    break;
		}
		else
		{
		    line1[lineidx] = c1;
		    line2[lineidx] = c2;
		    ++lineidx;
		    if (c1 != c2)
		    {
			vim_snprintf((char *)IObuff, IOSIZE,
					    "difference at byte %ld, line %ld",
							     count, linecount);
			break;
		    }
		}
		++count;
		if (c1 == NL)
		{
		    ++linecount;
		    lineidx = 0;
		}
		else if (lineidx + 2 == (int)sizeof(line1))
		{
		    mch_memmove(line1, line1 + 100, lineidx - 100);
		    mch_memmove(line2, line2 + 100, lineidx - 100);
		    lineidx -= 100;
		}
	    }
	    fclose(fd1);
	    fclose(fd2);
	}
    }
    if (IObuff[0] != NUL)
    {
	prepare_assert_error(&ga);
	if (argvars[2].v_type != VAR_UNKNOWN)
	{
	    char_u	numbuf[NUMBUFLEN];
	    char_u	*tofree;

	    ga_concat(&ga, echo_string(&argvars[2], &tofree, numbuf, 0));
	    vim_free(tofree);
	    ga_concat(&ga, (char_u *)": ");
	}
	ga_concat(&ga, IObuff);
	if (lineidx > 0)
	{
	    line1[lineidx] = NUL;
	    line2[lineidx] = NUL;
	    ga_concat(&ga, (char_u *)" after \"");
	    ga_concat(&ga, (char_u *)line1);
	    if (STRCMP(line1, line2) != 0)
	    {
		ga_concat(&ga, (char_u *)"\" vs \"");
		ga_concat(&ga, (char_u *)line2);
	    }
	    ga_concat(&ga, (char_u *)"\"");
	}
	assert_error(&ga);
	ga_clear(&ga);
	return 1;
    }
    return 0;
}


test_gui_drop_files(dict_T *args UNUSED)
{
#  if defined(HAVE_DROP_FILE)
    int		row;
    int		col;
    int_u	mods;
    char_u	**fnames;
    int		count = 0;
    typval_T	t;
    list_T	*l;
    listitem_T	*li;

    if (dict_find(args, (char_u *)"files", -1) == NULL
	    || dict_find(args, (char_u *)"row", -1) == NULL
	    || dict_find(args, (char_u *)"col", -1) == NULL
	    || dict_find(args, (char_u *)"modifiers", -1) == NULL)
	return FALSE;

    (void)dict_get_tv(args, (char_u *)"files", &t);
    row = (int)dict_get_number(args, (char_u *)"row");
    col = (int)dict_get_number(args, (char_u *)"col");
    mods = (int)dict_get_number(args, (char_u *)"modifiers");

    if (t.v_type != VAR_LIST || list_len(t.vval.v_list) == 0)
	return FALSE;

    l = t.vval.v_list;
    fnames = ALLOC_MULT(char_u *, list_len(l));
    if (fnames == NULL)
	return FALSE;

    FOR_ALL_LIST_ITEMS(l, li)
    {
	// ignore non-string items
	if (li->li_tv.v_type != VAR_STRING
		|| li->li_tv.vval.v_string == NULL)
	    continue;

	fnames[count] = vim_strsave(li->li_tv.vval.v_string);
	if (fnames[count] == NULL)
	{
	    while (--count >= 0)
		vim_free(fnames[count]);
	    vim_free(fnames);
	    return FALSE;
	}
	count++;
    }

    if (count > 0)
	gui_handle_drop(TEXT_X(col - 1), TEXT_Y(row - 1), mods, fnames, count);
    else
	vim_free(fnames);
#  endif

    return TRUE;
}


prepare_assert_error(garray_T *gap)
{
    char    buf[NUMBUFLEN];
    char_u  *sname = estack_sfile(ESTACK_NONE);

    ga_init2(gap, 1, 100);
    if (sname != NULL)
    {
	ga_concat(gap, sname);
	if (SOURCING_LNUM > 0)
	    ga_concat(gap, (char_u *)" ");
    }
    if (SOURCING_LNUM > 0)
    {
	sprintf(buf, "line %ld", (long)SOURCING_LNUM);
	ga_concat(gap, (char_u *)buf);
    }
    if (sname != NULL || SOURCING_LNUM > 0)
	ga_concat(gap, (char_u *)": ");
    vim_free(sname);
}


test_gui_mouse_event(dict_T *args)
{
    int		button;
    int		row;
    int		col;
    int		repeated_click;
    int_u	mods;

    if (dict_find(args, (char_u *)"button", -1) == NULL
	    || dict_find(args, (char_u *)"row", -1) == NULL
	    || dict_find(args, (char_u *)"col", -1) == NULL
	    || dict_find(args, (char_u *)"multiclick", -1) == NULL
	    || dict_find(args, (char_u *)"modifiers", -1) == NULL)
	return FALSE;

    button = (int)dict_get_number(args, (char_u *)"button");
    row = (int)dict_get_number(args, (char_u *)"row");
    col = (int)dict_get_number(args, (char_u *)"col");
    repeated_click = (int)dict_get_number(args, (char_u *)"multiclick");
    mods = (int)dict_get_number(args, (char_u *)"modifiers");

    gui_send_mouse_event(button, TEXT_X(col - 1), TEXT_Y(row - 1),
							repeated_click, mods);
    return TRUE;
}


test_gui_scrollbar(dict_T *args)
{
    char_u	*which;
    long	value;
    int		dragging;
    scrollbar_T *sb = NULL;

    if (dict_find(args, (char_u *)"which", -1) == NULL
	    || dict_find(args, (char_u *)"value", -1) == NULL
	    || dict_find(args, (char_u *)"dragging", -1) == NULL)
	return FALSE;

    which = dict_get_string(args, (char_u *)"which", FALSE);
    value = (long)dict_get_number(args, (char_u *)"value");
    dragging = (int)dict_get_number(args, (char_u *)"dragging");

    if (STRCMP(which, "left") == 0)
	sb = &curwin->w_scrollbars[SBAR_LEFT];
    else if (STRCMP(which, "right") == 0)
	sb = &curwin->w_scrollbars[SBAR_RIGHT];
    else if (STRCMP(which, "hor") == 0)
	sb = &gui.bottom_sbar;
    if (sb == NULL)
    {
	semsg(_(e_invalid_argument_str), which);
	return FALSE;
    }
    gui_drag_scrollbar(sb, value, dragging);
#  ifndef USE_ON_FLY_SCROLL
    // need to loop through normal_cmd() to handle the scroll events
    exec_normal(FALSE, TRUE, FALSE);
#  endif

    return TRUE;
}


ga_concat_shorten_esc(garray_T *gap, char_u *str)
{
    char_u  *p;
    char_u  *s;
    int	    c;
    int	    clen;
    char_u  buf[NUMBUFLEN];
    int	    same_len;

    if (str == NULL)
    {
	ga_concat(gap, (char_u *)"NULL");
	return;
    }

    for (p = str; *p != NUL; ++p)
    {
	same_len = 1;
	s = p;
	c = mb_cptr2char_adv(&s);
	clen = s - p;
	while (*s != NUL && c == mb_ptr2char(s))
	{
	    ++same_len;
	    s += clen;
	}
	if (same_len > 20)
	{
	    ga_concat(gap, (char_u *)"\\[");
	    ga_concat_esc(gap, p, clen);
	    ga_concat(gap, (char_u *)" occurs ");
	    vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len);
	    ga_concat(gap, buf);
	    ga_concat(gap, (char_u *)" times]");
	    p = s - 1;
	}
	else
	    ga_concat_esc(gap, p, clen);
    }
}


f_test_garbagecollect_soon(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
    may_garbage_collect = TRUE;
}


f_assert_equal(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
}


assert_equal_common(typval_T *argvars, assert_type_T atype)
{
    garray_T	ga;

    if (tv_equal(&argvars[0], &argvars[1], FALSE, FALSE)
						   != (atype == ASSERT_EQUAL))
    {
	prepare_assert_error(&ga);
	fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1],
								       atype);
	assert_error(&ga);
	ga_clear(&ga);
	return 1;
    }
    return 0;
}


f_assert_match(typval_T *argvars, typval_T *rettv)
{
    rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
}


f_test_option_not_set(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";

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

    if (argvars[0].v_type != VAR_STRING)
	emsg(_(e_invalid_argument));
    else
    {
	name = tv_get_string(&argvars[0]);
	if (reset_option_was_set(name) == FAIL)
	    semsg(_(e_invalid_argument_str), name);
    }
}


f_test_ignore_error(typval_T *argvars, typval_T *rettv UNUSED)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    if (argvars[0].v_type != VAR_STRING)
	emsg(_(e_invalid_argument));
    else
	ignore_error_for_testing(tv_get_string(&argvars[0]));
}


f_test_null_partial(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_PARTIAL;
    rettv->vval.v_partial = NULL;
}


ga_concat_esc(garray_T *gap, char_u *p, int clen)
{
    char_u  buf[NUMBUFLEN];

    if (clen > 1)
    {
	mch_memmove(buf, p, clen);
	buf[clen] = NUL;
	ga_concat(gap, buf);
    }
    else switch (*p)
    {
	case BS: ga_concat(gap, (char_u *)"\\b"); break;
	case ESC: ga_concat(gap, (char_u *)"\\e"); break;
	case FF: ga_concat(gap, (char_u *)"\\f"); break;
	case NL: ga_concat(gap, (char_u *)"\\n"); break;
	case TAB: ga_concat(gap, (char_u *)"\\t"); break;
	case CAR: ga_concat(gap, (char_u *)"\\r"); break;
	case '\\': ga_concat(gap, (char_u *)"\\\\"); break;
	default:
		   if (*p < ' ' || *p == 0x7f)
		   {
		       vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p);
		       ga_concat(gap, buf);
		   }
		   else
		       ga_append(gap, *p);
		   break;
    }
}


fill_assert_error(
    garray_T	*gap,
    typval_T	*opt_msg_tv,
    char_u      *exp_str,
    typval_T	*exp_tv_arg,
    typval_T	*got_tv_arg,
    assert_type_T atype)
{
    char_u	numbuf[NUMBUFLEN];
    char_u	*tofree;
    typval_T	*exp_tv = exp_tv_arg;
    typval_T	*got_tv = got_tv_arg;
    int		did_copy = FALSE;
    int		omitted = 0;

    if (opt_msg_tv->v_type != VAR_UNKNOWN
	    && !(opt_msg_tv->v_type == VAR_STRING
		&& (opt_msg_tv->vval.v_string == NULL
		    || *opt_msg_tv->vval.v_string == NUL)))
    {
	ga_concat(gap, echo_string(opt_msg_tv, &tofree, numbuf, 0));
	vim_free(tofree);
	ga_concat(gap, (char_u *)": ");
    }

    if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH)
	ga_concat(gap, (char_u *)"Pattern ");
    else if (atype == ASSERT_NOTEQUAL)
	ga_concat(gap, (char_u *)"Expected not equal to ");
    else
	ga_concat(gap, (char_u *)"Expected ");
    if (exp_str == NULL)
    {
	// When comparing dictionaries, drop the items that are equal, so that
	// it's a lot easier to see what differs.
	if (atype != ASSERT_NOTEQUAL
		&& exp_tv->v_type == VAR_DICT && got_tv->v_type == VAR_DICT
		&& exp_tv->vval.v_dict != NULL && got_tv->vval.v_dict != NULL)
	{
	    dict_T	*exp_d = exp_tv->vval.v_dict;
	    dict_T	*got_d = got_tv->vval.v_dict;
	    hashitem_T	*hi;
	    dictitem_T	*item2;
	    int		todo;

	    did_copy = TRUE;
	    exp_tv->vval.v_dict = dict_alloc();
	    got_tv->vval.v_dict = dict_alloc();
	    if (exp_tv->vval.v_dict == NULL || got_tv->vval.v_dict == NULL)
		return;

	    todo = (int)exp_d->dv_hashtab.ht_used;
	    for (hi = exp_d->dv_hashtab.ht_array; todo > 0; ++hi)
	    {
		if (!HASHITEM_EMPTY(hi))
		{
		    item2 = dict_find(got_d, hi->hi_key, -1);
		    if (item2 == NULL || !tv_equal(&HI2DI(hi)->di_tv,
						  &item2->di_tv, FALSE, FALSE))
		    {
			// item of exp_d not present in got_d or values differ.
			dict_add_tv(exp_tv->vval.v_dict,
					(char *)hi->hi_key, &HI2DI(hi)->di_tv);
			if (item2 != NULL)
			    dict_add_tv(got_tv->vval.v_dict,
					    (char *)hi->hi_key, &item2->di_tv);
		    }
		    else
			++omitted;
		    --todo;
		}
	    }

	    // Add items only present in got_d.
	    todo = (int)got_d->dv_hashtab.ht_used;
	    for (hi = got_d->dv_hashtab.ht_array; todo > 0; ++hi)
	    {
		if (!HASHITEM_EMPTY(hi))
		{
		    item2 = dict_find(exp_d, hi->hi_key, -1);
		    if (item2 == NULL)
			// item of got_d not present in exp_d
			dict_add_tv(got_tv->vval.v_dict,
					(char *)hi->hi_key, &HI2DI(hi)->di_tv);
		    --todo;
		}
	    }
	}

	ga_concat_shorten_esc(gap, tv2string(exp_tv, &tofree, numbuf, 0));
	vim_free(tofree);
    }
    else
    {
	ga_concat(gap, (char_u *)"'");
	ga_concat_shorten_esc(gap, exp_str);
	ga_concat(gap, (char_u *)"'");
    }
    if (atype != ASSERT_NOTEQUAL)
    {
	if (atype == ASSERT_MATCH)
	    ga_concat(gap, (char_u *)" does not match ");
	else if (atype == ASSERT_NOTMATCH)
	    ga_concat(gap, (char_u *)" does match ");
	else
	    ga_concat(gap, (char_u *)" but got ");
	ga_concat_shorten_esc(gap, tv2string(got_tv, &tofree, numbuf, 0));
	vim_free(tofree);

	if (omitted != 0)
	{
	    char buf[100];

	    vim_snprintf(buf, 100, " - %d equal item%s omitted",
					     omitted, omitted == 1 ? "" : "s");
	    ga_concat(gap, (char_u *)buf);
	}
    }

    if (did_copy)
    {
	clear_tv(exp_tv);
	clear_tv(got_tv);
    }
}


f_test_void(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_VOID;
}


test_gui_tabmenu_event(dict_T *args UNUSED)
{
#  ifdef FEAT_GUI_TABLINE
    int	tabnr;
    int	item;

    if (dict_find(args, (char_u *)"tabnr", -1) == NULL
	    || dict_find(args, (char_u *)"item", -1) == NULL)
	return FALSE;

    tabnr = (int)dict_get_number(args, (char_u *)"tabnr");
    item = (int)dict_get_number(args, (char_u *)"item");

    send_tabline_menu_event(tabnr, item);
#  endif
    return TRUE;
}


f_assert_beeps(typval_T *argvars, typval_T *rettv)
{
    if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
	return;

    rettv->vval.v_number = assert_beeps(argvars, FALSE);
}


f_test_unknown(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv->v_type = VAR_UNKNOWN;
}


assert_beeps(typval_T *argvars, int no_beep)
{
    char_u	*cmd;
    garray_T	ga;
    int		ret = 0;

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

    cmd = tv_get_string_chk(&argvars[0]);
    called_vim_beep = FALSE;
    suppress_errthrow = TRUE;
    emsg_silent = FALSE;
    do_cmdline_cmd(cmd);
    if (no_beep ? called_vim_beep : !called_vim_beep)
    {
	prepare_assert_error(&ga);
	if (no_beep)
	    ga_concat(&ga, (char_u *)"command did beep: ");
	else
	    ga_concat(&ga, (char_u *)"command did not beep: ");
	ga_concat(&ga, cmd);
	assert_error(&ga);
	ga_clear(&ga);
	ret = 1;
    }

    suppress_errthrow = FALSE;
    emsg_on_display = FALSE;
    return ret;
}


f_test_null_list(typval_T *argvars UNUSED, typval_T *rettv)
{
    rettv_list_set(rettv, NULL);
}


f_test_override(typval_T *argvars, typval_T *rettv UNUSED)
{
    char_u *name = (char_u *)"";
    int     val;
    static int save_starting = -1;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_number_arg(argvars, 1) == FAIL))
	return;

    if (argvars[0].v_type != VAR_STRING
	    || (argvars[1].v_type) != VAR_NUMBER)
	emsg(_(e_invalid_argument));
    else
    {
	name = tv_get_string(&argvars[0]);
	val = (int)tv_get_number(&argvars[1]);

	if (STRCMP(name, (char_u *)"redraw") == 0)
	    disable_redraw_for_testing = val;
	else if (STRCMP(name, (char_u *)"redraw_flag") == 0)
	    ignore_redraw_flag_for_testing = val;
	else if (STRCMP(name, (char_u *)"char_avail") == 0)
	    disable_char_avail_for_testing = val;
	else if (STRCMP(name, (char_u *)"starting") == 0)
	{
	    if (val)
	    {
		if (save_starting < 0)
		    save_starting = starting;
		starting = 0;
	    }
	    else
	    {
		starting = save_starting;
		save_starting = -1;
	    }
	}
	else if (STRCMP(name, (char_u *)"nfa_fail") == 0)
	    nfa_fail_for_testing = val;
	else if (STRCMP(name, (char_u *)"no_query_mouse") == 0)
	    no_query_mouse_for_testing = val;
	else if (STRCMP(name, (char_u *)"no_wait_return") == 0)
	    no_wait_return = val;
	else if (STRCMP(name, (char_u *)"ui_delay") == 0)
	    ui_delay_for_testing = val;
	else if (STRCMP(name, (char_u *)"term_props") == 0)
	    reset_term_props_on_termresponse = val;
	else if (STRCMP(name, (char_u *)"vterm_title") == 0)
	    disable_vterm_title_for_testing = val;
	else if (STRCMP(name, (char_u *)"uptime") == 0)
	    override_sysinfo_uptime = val;
	else if (STRCMP(name, (char_u *)"autoload") == 0)
	    override_autoload = val;
	else if (STRCMP(name, (char_u *)"ALL") == 0)
	{
	    disable_char_avail_for_testing = FALSE;
	    disable_redraw_for_testing = FALSE;
	    ignore_redraw_flag_for_testing = FALSE;
	    nfa_fail_for_testing = FALSE;
	    no_query_mouse_for_testing = FALSE;
	    ui_delay_for_testing = 0;
	    reset_term_props_on_termresponse = FALSE;
	    override_sysinfo_uptime = -1;
	    if (save_starting >= 0)
	    {
		starting = save_starting;
		save_starting = -1;
	    }
	}
	else
	    semsg(_(e_invalid_argument_str), name);
    }
}


f_assert_exception(typval_T *argvars, typval_T *rettv)
{
    garray_T	ga;
    char_u	*error;

    if (in_vim9script()
	    && (check_for_string_arg(argvars, 0) == FAIL
		|| check_for_opt_string_arg(argvars, 1) == FAIL))
	return;

    error = tv_get_string_chk(&argvars[0]);
    if (*get_vim_var_str(VV_EXCEPTION) == NUL)
    {
	prepare_assert_error(&ga);
	ga_concat(&ga, (char_u *)"v:exception is not set");
	assert_error(&ga);
	ga_clear(&ga);
	rettv->vval.v_number = 1;
    }
    else if (error != NULL
	&& strstr((char *)get_vim_var_str(VV_EXCEPTION), (char *)error) == NULL)
    {
	prepare_assert_error(&ga);
	fill_assert_error(&ga, &argvars[1], NULL, &argvars[0],
				  get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER);
	assert_error(&ga);
	ga_clear(&ga);
	rettv->vval.v_number = 1;
    }
}


f_test_autochdir(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
{
#if defined(FEAT_AUTOCHDIR)
    test_autochdir = TRUE;
#endif
}


f_test_refcount(typval_T *argvars, typval_T *rettv)
{
    int retval = -1;

    switch (argvars[0].v_type)
    {
	case VAR_UNKNOWN:
	case VAR_ANY:
	case VAR_VOID:
	case VAR_NUMBER:
	case VAR_BOOL:
	case VAR_FLOAT:
	case VAR_SPECIAL:
	case VAR_STRING:
	case VAR_INSTR:
	    break;
	case VAR_JOB:
#ifdef FEAT_JOB_CHANNEL
	    if (argvars[0].vval.v_job != NULL)
		retval = argvars[0].vval.v_job->jv_refcount - 1;
#endif
	    break;
	case VAR_CHANNEL:
#ifdef FEAT_JOB_CHANNEL
	    if (argvars[0].vval.v_channel != NULL)
		retval = argvars[0].vval.v_channel->ch_refcount - 1;
#endif
	    break;
	case VAR_FUNC:
	    if (argvars[0].vval.v_string != NULL)
	    {
		ufunc_T *fp;

		fp = find_func(argvars[0].vval.v_string, FALSE);
		if (fp != NULL)
		    retval = fp->uf_refcount;
	    }
	    break;
	case VAR_PARTIAL:
	    if (argvars[0].vval.v_partial != NULL)
		retval = argvars[0].vval.v_partial->pt_refcount - 1;
	    break;
	case VAR_BLOB:
	    if (argvars[0].vval.v_blob != NULL)
		retval = argvars[0].vval.v_blob->bv_refcount - 1;
	    break;
	case VAR_LIST:
	    if (argvars[0].vval.v_list != NULL)
		retval = argvars[0].vval.v_list->lv_refcount - 1;
	    break;
	case VAR_DICT:
	    if (argvars[0].vval.v_dict != NULL)
		retval = argvars[0].vval.v_dict->dv_refcount - 1;
	    break;
    }

    rettv->v_type = VAR_NUMBER;
    rettv->vval.v_number = retval;

}


test_gui_find_repl(dict_T *args)
{
    int		flags;
    char_u	*find_text;
    char_u	*repl_text;
    int		forward;
    int		retval;

    if (dict_find(args, (char_u *)"find_text", -1) == NULL
	    || dict_find(args, (char_u *)"repl_text", -1) == NULL
	    || dict_find(args, (char_u *)"flags", -1) == NULL
	    || dict_find(args, (char_u *)"forward", -1) == NULL)
	return FALSE;

    find_text = dict_get_string(args, (char_u *)"find_text", TRUE);
    repl_text = dict_get_string(args, (char_u *)"repl_text", TRUE);
    flags = (int)dict_get_number(args, (char_u *)"flags");
    forward = (int)dict_get_number(args, (char_u *)"forward");

    retval = gui_do_findrepl(flags, find_text, repl_text, forward);
    vim_free(find_text);
    vim_free(repl_text);

    return retval;
}


assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, char_u *cmd)
{
    char_u	*tofree;
    char_u	numbuf[NUMBUFLEN];

    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN)
    {
	ga_concat(gap, echo_string(&argvars[2], &tofree, numbuf, 0));
	vim_free(tofree);
    }
    else
	ga_concat(gap, cmd);
}


assert_inrange(typval_T *argvars)
{
    garray_T	ga;
    int		error = FALSE;
    char_u	*tofree;
    char	msg[200];
    char_u	numbuf[NUMBUFLEN];

#ifdef FEAT_FLOAT
    if (argvars[0].v_type == VAR_FLOAT
	    || argvars[1].v_type == VAR_FLOAT
	    || argvars[2].v_type == VAR_FLOAT)
    {
	float_T flower = tv_get_float(&argvars[0]);
	float_T fupper = tv_get_float(&argvars[1]);
	float_T factual = tv_get_float(&argvars[2]);

	if (factual < flower || factual > fupper)
	{
	    prepare_assert_error(&ga);
	    if (argvars[3].v_type != VAR_UNKNOWN)
	    {
		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
		vim_free(tofree);
	    }
	    else
	    {
		vim_snprintf(msg, 200, "Expected range %g - %g, but got %g",
						      flower, fupper, factual);
		ga_concat(&ga, (char_u *)msg);
	    }
	    assert_error(&ga);
	    ga_clear(&ga);
	    return 1;
	}
    }
    else
#endif
    {
	varnumber_T	lower = tv_get_number_chk(&argvars[0], &error);
	varnumber_T	upper = tv_get_number_chk(&argvars[1], &error);
	varnumber_T	actual = tv_get_number_chk(&argvars[2], &error);

	if (error)
	    return 0;
	if (actual < lower || actual > upper)
	{
	    prepare_assert_error(&ga);
	    if (argvars[3].v_type != VAR_UNKNOWN)
	    {
		ga_concat(&ga, tv2string(&argvars[3], &tofree, numbuf, 0));
		vim_free(tofree);
	    }
	    else
	    {
		vim_snprintf(msg, 200, "Expected range %ld - %ld, but got %ld",
				       (long)lower, (long)upper, (long)actual);
		ga_concat(&ga, (char_u *)msg);
	    }
	    assert_error(&ga);
	    ga_clear(&ga);
	    return 1;
	}
    }
    return 0;
}


ModuleExport size_t RegisterCINImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("CIN");
  entry->decoder=(DecodeImageHandler *) ReadCINImage;
  entry->encoder=(EncodeImageHandler *) WriteCINImage;
  entry->magick=(IsImageFormatHandler *) IsCIN;
  entry->adjoin=MagickFalse;
  entry->seekable_stream=MagickTrue;
  entry->description=ConstantString("Cineon Image File");
  entry->magick_module=ConstantString("CIN");
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

ImageMagick 7.1.0-27 is vulnerable to Buffer Overflow.


static MagickBooleanType WriteCINImage(const ImageInfo *image_info,Image *image)
{
  char
    timestamp[MaxTextExtent];

  const char
    *value;

  CINInfo
    cin;

  const StringInfo
    *profile;

  MagickBooleanType
    status;

  MagickOffsetType
    offset;

  QuantumInfo
    *quantum_info;

  QuantumType
    quantum_type;

  const PixelPacket
    *p;

  ssize_t
    i;

  size_t
    length;

  ssize_t
    count,
    y;

  struct tm
    utc_time;

  time_t
    seconds;

  unsigned char
    *pixels;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == MagickFalse)
    return(status);
  if (image->colorspace != LogColorspace)
    (void) TransformImageColorspace(image,LogColorspace);
  /*
    Write image information.
  */
  (void) memset(&cin,0,sizeof(cin));
  offset=0;
  cin.file.magic=0x802A5FD7UL;
  offset+=WriteBlobLong(image,(unsigned int) cin.file.magic);
  cin.file.image_offset=0x800;
  offset+=WriteBlobLong(image,(unsigned int) cin.file.image_offset);
  cin.file.generic_length=0x400;
  offset+=WriteBlobLong(image,(unsigned int) cin.file.generic_length);
  cin.file.industry_length=0x400;
  offset+=WriteBlobLong(image,(unsigned int) cin.file.industry_length);
  cin.file.user_length=0x00;
  profile=GetImageProfile(image,"dpx:user.data");
  if (profile != (StringInfo *) NULL)
    {
      cin.file.user_length+=(size_t) GetStringInfoLength(profile);
      cin.file.user_length=(((cin.file.user_length+0x2000-1)/0x2000)*0x2000);
    }
  offset+=WriteBlobLong(image,(unsigned int) cin.file.user_length);
  cin.file.file_size=4*image->columns*image->rows+0x2000;
  offset+=WriteBlobLong(image,(unsigned int) cin.file.file_size);
  (void) CopyMagickString(cin.file.version,"V4.5",sizeof(cin.file.version));
  offset+=WriteBlob(image,sizeof(cin.file.version),(unsigned char *)
    cin.file.version);
  value=GetCINProperty(image_info,image,"dpx:file.filename");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.file.filename,value,sizeof(cin.file.filename));
  else
    (void) CopyMagickString(cin.file.filename,image->filename,
      sizeof(cin.file.filename));
  offset+=WriteBlob(image,sizeof(cin.file.filename),(unsigned char *)
    cin.file.filename);
  seconds=GetMagickTime();
  GetMagickUTCtime(&seconds,&utc_time);
  (void) memset(timestamp,0,sizeof(timestamp));
  (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%SUTC",&utc_time);
  (void) memset(cin.file.create_date,0,sizeof(cin.file.create_date));
  (void) CopyMagickString(cin.file.create_date,timestamp,11);
  offset+=WriteBlob(image,sizeof(cin.file.create_date),(unsigned char *)
    cin.file.create_date);
  (void) memset(cin.file.create_time,0,sizeof(cin.file.create_time));
  (void) CopyMagickString(cin.file.create_time,timestamp+11,11);
  offset+=WriteBlob(image,sizeof(cin.file.create_time),(unsigned char *)
    cin.file.create_time);
  offset+=WriteBlob(image,sizeof(cin.file.reserve),(unsigned char *)
    cin.file.reserve);
  cin.image.orientation=0x00;
  offset+=WriteBlobByte(image,cin.image.orientation);
  cin.image.number_channels=3;
  offset+=WriteBlobByte(image,cin.image.number_channels);
  offset+=WriteBlob(image,sizeof(cin.image.reserve1),(unsigned char *)
    cin.image.reserve1);
  for (i=0; i < 8; i++)
  {
    cin.image.channel[i].designator[0]=0; /* universal metric */
    offset+=WriteBlobByte(image,cin.image.channel[0].designator[0]);
    cin.image.channel[i].designator[1]=(unsigned char) (i > 3 ? 0 : i+1); /* channel color */;
    offset+=WriteBlobByte(image,cin.image.channel[1].designator[0]);
    cin.image.channel[i].bits_per_pixel=(unsigned char) image->depth;
    offset+=WriteBlobByte(image,cin.image.channel[0].bits_per_pixel);
    offset+=WriteBlobByte(image,cin.image.channel[0].reserve);
    cin.image.channel[i].pixels_per_line=image->columns;
    offset+=WriteBlobLong(image,(unsigned int)
      cin.image.channel[0].pixels_per_line);
    cin.image.channel[i].lines_per_image=image->rows;
    offset+=WriteBlobLong(image,(unsigned int)
      cin.image.channel[0].lines_per_image);
    cin.image.channel[i].min_data=0;
    offset+=WriteBlobFloat(image,cin.image.channel[0].min_data);
    cin.image.channel[i].min_quantity=0.0;
    offset+=WriteBlobFloat(image,cin.image.channel[0].min_quantity);
    cin.image.channel[i].max_data=(float) ((MagickOffsetType)
      GetQuantumRange(image->depth));
    offset+=WriteBlobFloat(image,cin.image.channel[0].max_data);
    cin.image.channel[i].max_quantity=2.048f;
    offset+=WriteBlobFloat(image,cin.image.channel[0].max_quantity);
  }
  offset+=WriteBlobFloat(image,image->chromaticity.white_point.x);
  offset+=WriteBlobFloat(image,image->chromaticity.white_point.y);
  offset+=WriteBlobFloat(image,image->chromaticity.red_primary.x);
  offset+=WriteBlobFloat(image,image->chromaticity.red_primary.y);
  offset+=WriteBlobFloat(image,image->chromaticity.green_primary.x);
  offset+=WriteBlobFloat(image,image->chromaticity.green_primary.y);
  offset+=WriteBlobFloat(image,image->chromaticity.blue_primary.x);
  offset+=WriteBlobFloat(image,image->chromaticity.blue_primary.y);
  value=GetCINProperty(image_info,image,"dpx:image.label");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.image.label,value,sizeof(cin.image.label));
  offset+=WriteBlob(image,sizeof(cin.image.label),(unsigned char *)
    cin.image.label);
  offset+=WriteBlob(image,sizeof(cin.image.reserve),(unsigned char *)
    cin.image.reserve);
  /*
    Write data format information.
  */
  cin.data_format.interleave=0; /* pixel interleave (rgbrgbr...) */
  offset+=WriteBlobByte(image,cin.data_format.interleave);
  cin.data_format.packing=5; /* packing ssize_tword (32bit) boundaries */
  offset+=WriteBlobByte(image,cin.data_format.packing);
  cin.data_format.sign=0; /* unsigned data */
  offset+=WriteBlobByte(image,cin.data_format.sign);
  cin.data_format.sense=0; /* image sense: positive image */
  offset+=WriteBlobByte(image,cin.data_format.sense);
  cin.data_format.line_pad=0;
  offset+=WriteBlobLong(image,(unsigned int) cin.data_format.line_pad);
  cin.data_format.channel_pad=0;
  offset+=WriteBlobLong(image,(unsigned int) cin.data_format.channel_pad);
  offset+=WriteBlob(image,sizeof(cin.data_format.reserve),(unsigned char *)
    cin.data_format.reserve);
  /*
    Write origination information.
  */
  cin.origination.x_offset=0UL;
  value=GetCINProperty(image_info,image,"dpx:origination.x_offset");
  if (value != (const char *) NULL)
    cin.origination.x_offset=(ssize_t) StringToLong(value);
  offset+=WriteBlobLong(image,(unsigned int) cin.origination.x_offset);
  cin.origination.y_offset=0UL;
  value=GetCINProperty(image_info,image,"dpx:origination.y_offset");
  if (value != (const char *) NULL)
    cin.origination.y_offset=(ssize_t) StringToLong(value);
  offset+=WriteBlobLong(image,(unsigned int) cin.origination.y_offset);
  value=GetCINProperty(image_info,image,"dpx:origination.filename");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.origination.filename,value,
      sizeof(cin.origination.filename));
  else
    (void) CopyMagickString(cin.origination.filename,image->filename,
      sizeof(cin.origination.filename));
  offset+=WriteBlob(image,sizeof(cin.origination.filename),(unsigned char *)
    cin.origination.filename);
  (void) memset(timestamp,0,sizeof(timestamp));
  (void) strftime(timestamp,MaxTextExtent,"%Y:%m:%d:%H:%M:%SUTC",&utc_time);
  (void) memset(cin.origination.create_date,0,
    sizeof(cin.origination.create_date));
  (void) CopyMagickString(cin.origination.create_date,timestamp,11);
  offset+=WriteBlob(image,sizeof(cin.origination.create_date),(unsigned char *)
    cin.origination.create_date);
  (void) memset(cin.origination.create_time,0,
    sizeof(cin.origination.create_time));
  (void) CopyMagickString(cin.origination.create_time,timestamp+11,15);
  offset+=WriteBlob(image,sizeof(cin.origination.create_time),(unsigned char *)
    cin.origination.create_time);
  value=GetCINProperty(image_info,image,"dpx:origination.device");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.origination.device,value,
      sizeof(cin.origination.device));
  offset+=WriteBlob(image,sizeof(cin.origination.device),(unsigned char *)
    cin.origination.device);
  value=GetCINProperty(image_info,image,"dpx:origination.model");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.origination.model,value,
      sizeof(cin.origination.model));
  offset+=WriteBlob(image,sizeof(cin.origination.model),(unsigned char *)
    cin.origination.model);
  value=GetCINProperty(image_info,image,"dpx:origination.serial");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.origination.serial,value,
      sizeof(cin.origination.serial));
  offset+=WriteBlob(image,sizeof(cin.origination.serial),(unsigned char *)
    cin.origination.serial);
  cin.origination.x_pitch=0.0f;
  value=GetCINProperty(image_info,image,"dpx:origination.x_pitch");
  if (value != (const char *) NULL)
    cin.origination.x_pitch=StringToDouble(value,(char **) NULL);
  offset+=WriteBlobFloat(image,cin.origination.x_pitch);
  cin.origination.y_pitch=0.0f;
  value=GetCINProperty(image_info,image,"dpx:origination.y_pitch");
  if (value != (const char *) NULL)
    cin.origination.y_pitch=StringToDouble(value,(char **) NULL);
  offset+=WriteBlobFloat(image,cin.origination.y_pitch);
  cin.origination.gamma=image->gamma;
  offset+=WriteBlobFloat(image,cin.origination.gamma);
  offset+=WriteBlob(image,sizeof(cin.origination.reserve),(unsigned char *)
    cin.origination.reserve);
  /*
    Image film information.
  */
  cin.film.id=0;
  value=GetCINProperty(image_info,image,"dpx:film.id");
  if (value != (const char *) NULL)
    cin.film.id=(char) StringToLong(value);
  offset+=WriteBlobByte(image,(unsigned char) cin.film.id);
  cin.film.type=0;
  value=GetCINProperty(image_info,image,"dpx:film.type");
  if (value != (const char *) NULL)
    cin.film.type=(char) StringToLong(value);
  offset+=WriteBlobByte(image,(unsigned char) cin.film.type);
  cin.film.offset=0;
  value=GetCINProperty(image_info,image,"dpx:film.offset");
  if (value != (const char *) NULL)
    cin.film.offset=(char) StringToLong(value);
  offset+=WriteBlobByte(image,(unsigned char) cin.film.offset);
  offset+=WriteBlobByte(image,(unsigned char) cin.film.reserve1);
  cin.film.prefix=0UL;
  value=GetCINProperty(image_info,image,"dpx:film.prefix");
  if (value != (const char *) NULL)
    cin.film.prefix=StringToUnsignedLong(value);
  offset+=WriteBlobLong(image,(unsigned int) cin.film.prefix);
  cin.film.count=0UL;
  value=GetCINProperty(image_info,image,"dpx:film.count");
  if (value != (const char *) NULL)
    cin.film.count=StringToUnsignedLong(value);
  offset+=WriteBlobLong(image,(unsigned int) cin.film.count);
  value=GetCINProperty(image_info,image,"dpx:film.format");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.film.format,value,sizeof(cin.film.format));
  offset+=WriteBlob(image,sizeof(cin.film.format),(unsigned char *)
    cin.film.format);
  cin.film.frame_position=0UL;
  value=GetCINProperty(image_info,image,"dpx:film.frame_position");
  if (value != (const char *) NULL)
    cin.film.frame_position=StringToUnsignedLong(value);
  offset+=WriteBlobLong(image,(unsigned int) cin.film.frame_position);
  cin.film.frame_rate=0.0f;
  value=GetCINProperty(image_info,image,"dpx:film.frame_rate");
  if (value != (const char *) NULL)
    cin.film.frame_rate=StringToDouble(value,(char **) NULL);
  offset+=WriteBlobFloat(image,cin.film.frame_rate);
  value=GetCINProperty(image_info,image,"dpx:film.frame_id");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.film.frame_id,value,sizeof(cin.film.frame_id));
  offset+=WriteBlob(image,sizeof(cin.film.frame_id),(unsigned char *)
    cin.film.frame_id);
  value=GetCINProperty(image_info,image,"dpx:film.slate_info");
  if (value != (const char *) NULL)
    (void) CopyMagickString(cin.film.slate_info,value,
      sizeof(cin.film.slate_info));
  offset+=WriteBlob(image,sizeof(cin.film.slate_info),(unsigned char *)
    cin.film.slate_info);
  offset+=WriteBlob(image,sizeof(cin.film.reserve),(unsigned char *)
    cin.film.reserve);
  if (profile != (StringInfo *) NULL)
    offset+=WriteBlob(image,GetStringInfoLength(profile),
      GetStringInfoDatum(profile));
  while (offset < (MagickOffsetType) cin.file.image_offset)
    offset+=WriteBlobByte(image,0x00);
  /*
    Convert pixel packets to CIN raster image.
  */
  quantum_info=AcquireQuantumInfo(image_info,image);
  if (quantum_info == (QuantumInfo *) NULL)
    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
  quantum_info->quantum=32;
  quantum_info->pack=MagickFalse;
  quantum_type=RGBQuantum;
  pixels=GetQuantumPixels(quantum_info);
  length=GetBytesPerRow(image->columns,3,image->depth,MagickTrue);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
    if (p == (const PixelPacket *) NULL)
      break;
    (void) ExportQuantumPixels(image,(const CacheView *) NULL,quantum_info,
      quantum_type,pixels,&image->exception);
    count=WriteBlob(image,length,pixels);
    if (count != (ssize_t) length)
      break;
    status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
      image->rows);
    if (status == MagickFalse)
      break;
  }
  quantum_info=DestroyQuantumInfo(quantum_info);
  (void) CloseBlob(image);
  return(status);
}


static inline MagickBooleanType IsFloatDefined(const float value)
{
  union
  {
    unsigned int
      unsigned_value;

    double
      float_value;
  } quantum;

  quantum.unsigned_value=0U;
  quantum.float_value=value;
  if (quantum.unsigned_value == 0U)
    return(MagickFalse);
  return(MagickTrue);
}