Commit message: CVE-2019-19847, Stack-based buffer overflow in the spiro_to_bpath0()

Frederic Cambus (@fcambus) discovered a bug in call-test.c using:
./configure CFLAGS="-fsanitize=address"
make
./tests/call-test[14,15,16,17,18,19]
Fredrick Brennan (@ctrlcctrlv) provided bugfix. See issue #21
CVE: CVE-2019-19847
CWE: CWE-787
Vulnerability description: Libspiro through 20190731 has a stack-based buffer overflow in the spiro_to_bpath0() function in spiro.c.

Project: libspiro
File: call-test.c

int test_multi_curves(void) {
    spiro_cp **spiro = NULL;
    spiro_cp *temp;
    int *pk, **nextknot = NULL;
    int *scl = NULL;
    test_bezctx **bc;
    int i, j, k, l, ret;
    double x, y;
    char ty;

    /* our simple curve test-check breaks-down if we go more than */
    /* 10x larger curves due to rounding errors on double values, */
    /* so, we either need a more complex curve test-check at end, */
    /* or we can cleverly increase in increments of "1/S_TESTS".  */
#define S_TESTS S_TESTP*5

#ifdef HAVE_PTHREADS
    pthread_attr_t tattr;
    pthread_t curve_test[S_TESTS];
    pthread_pcurve pdata[S_TESTS];

    printf("---\nMulti-thread testing of libspiro.\n");
#else
    printf("---\nSequential tests of libspiro.\n");
#endif

    ret = -1;	/* return error if out of memory */


    /* Expect lots of results, therefore create available memory. */
    /* This way, we won't be wasting time doing malloc because we */
    /* really want to shoot a whole bunch of pthreads all at once.*/
    if ( (bc=(test_bezctx**)calloc(S_TESTS,sizeof(test_bezctx*)))==NULL )
	goto test_multi_curves_exit;
    for (i=0; i < S_TESTS; i++) {
	if ( (bc[i]=(test_bezctx*)malloc(sizeof(test_bezctx)))==NULL )
	    goto test_multi_curves_exit;
	bc[i]->base.moveto = test_s_moveto;
	bc[i]->base.lineto = test_s_lineto;
	bc[i]->base.quadto = test_s_quadto;
	bc[i]->base.curveto = test_s_curveto;
	bc[i]->base.mark_knot = test_s_mark_knot;
	if ( (bc[i]->my_curve=(my_curve_data*)calloc(S_RESULTS,sizeof(my_curve_data)))==NULL )
	    goto test_multi_curves_exit;
	bc[i]->len = 0;		/* no curve yet, len=0 */
	bc[i]->is_open = 0;
	bc[i]->c_id = i;	/* identify each curve */
    }

    if ( (scl=(int*)malloc(S_TESTS*sizeof(int)))==NULL || \
	 (spiro=(spiro_cp**)calloc(S_TESTS,sizeof(spiro_cp*)))==NULL || \
	 (nextknot=(int**)calloc(S_TESTS,sizeof(int*)))==NULL )
	goto test_multi_curves_exit;
    for (i=0; i < S_TESTS; ) {
	/* NOTE: S_TESTS has to be multiple of 5 here. */
	/* because we test with path[0/1/2/5/6]tables, */
	/* ...and path[3] is used to test NOT success, */
	/* ...and path[4] is too fast @ 4 interations, */
	/* ...and path[7/8] ah complicates node check. */
	if ( (spiro[i]=malloc(cl[0]*sizeof(spiro_cp)))==NULL || \
	      (nextknot[i]=calloc(cl[0],sizeof(int)))==NULL )
	    goto test_multi_curves_exit;
	load_test_curve(spiro[i],nextknot[i],0);
	scl[i++]=cl[0];
	if ( (spiro[i]=malloc(cl[1]*sizeof(spiro_cp)))==NULL || \
	      (nextknot[i]=calloc(cl[1],sizeof(int)))==NULL )
	    goto test_multi_curves_exit;
	load_test_curve(spiro[i],nextknot[i],1);
	scl[i++]=cl[1];
	if ( (spiro[i]=malloc(cl[2]*sizeof(spiro_cp)))==NULL || \
	      (nextknot[i]=calloc(cl[2],sizeof(int)))==NULL )
	    goto test_multi_curves_exit;
	load_test_curve(spiro[i],nextknot[i],2);
	scl[i++]=cl[2];
	if ( (spiro[i]=malloc(cl[5]*sizeof(spiro_cp)))==NULL || \
	      (nextknot[i]=calloc(cl[5],sizeof(int)))==NULL )
	    goto test_multi_curves_exit;
	load_test_curve(spiro[i],nextknot[i],5);
	scl[i++]=cl[5];
	if ( (spiro[i]=malloc(cl[6]*sizeof(spiro_cp)))==NULL || \
	      (nextknot[i]=calloc(cl[6],sizeof(int)))==NULL )
	    goto test_multi_curves_exit;
	load_test_curve(spiro[i],nextknot[i],6);
	scl[i++]=cl[6];
    }

    /* Change to different sizes to make sure no duplicates */
    /* ...to verify we do not overwrite different user data */
    /* while running multiple threads all at the same time. */
    for (i=0; i < S_TESTS; i++) {
	temp = spiro[i];
	for (j=0; j < scl[i]; j++) {
	    if ( i&1 ) {
		temp[j].x = temp[j].x * (i/S_TESTS+1) + i;
		temp[j].y = temp[j].y * (i/S_TESTS+1) + i;
	    } else {
		/* Scaling bug fixed. Scale & shift at will */
		temp[j].x *= (i+1); temp[j].x += i*1000. - 5000.;
		temp[j].y *= (i+1); temp[j].y += i*3000. - 9000.;
	    }
	}
    }

    --ret;

#ifdef HAVE_PTHREADS
    /* Data and memory prepared before Pthreads.  Ready? Set? GO! */
    /* Test all curves, all at same time, wait for all to finish. */
    /* This test could fail if we had globally set variables that */
    /* could affect other functions, eg: static n=4 was moved out */
    /* into passed variable so that one user won't affect others. */
    /* GEGL is a good example of multiple users all at same time. */
    for (i=0; i < S_TESTS; i++) {
	pdata[i].spiro = spiro[i];
	pdata[i].bc = (bezctx*)(bc[i]);
	pdata[i].ret = i;
    }

    j=0;
    for (k=0; k < S_TESTS;) {
	/* Initialize and set thread joinable attribute */
	pthread_attr_init(&tattr);
	pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_JOINABLE);

	/* Some processors can't do too many pthreads at once so then */
	/* we need to run threads in batches until completing S_TESTS */
	for (i=k; i < S_TESTS-1; i++) {
	    /* all values passed are joined at "->" (should be okay). */
	    if ( pthread_create(&curve_test[i],&tattr,test_a_curve,(void *)&pdata[i]) ) {
		if ( i-k < 20 ) {
		    printf("bad pthread_create[%d]\n",i); /* not many */
		    j=-1;
		}
		break;
	    }
	}
	pthread_attr_destroy(&tattr);	/* Free thread attribute */
	if ( j!=-1 ) {
	    /* Test another curve while waiting for threads to finish */
	    pdata[i].ret = TaggedSpiroCPsToBezier2(pdata[i].spiro,0,pdata[i].bc);
	    printf("running simultaneous threads[%d..%d]\n",k,i);
	}
	l=i;
	while (--i >= k)
	    if ( pthread_join(curve_test[i],NULL) ) {
		fprintf(stderr,"bad pthread_join[%d]\n",i);
		j=-1;
	    }
	k=++l;
	if (j) goto test_multi_curves_exit;
    }

    for (i=0; i < S_TESTS; i++)
	if ( pdata[i].ret!=1 ) {
	    ret=ret-i;
	    fprintf(stderr,"error %d with TaggedSpiroCPsToBezier2() using data=%d.\n",ret,i);
	    goto test_multi_curves_exit;
	}
    /* All threads returned okay, Now, go check all data is good. */
#else
    /* No pthreads.h, test all curves sequentially, one at a time */
    /* Just do a math check and leave the pthread check for other */
    /* operating systems to verify libspiro has no static values. */
    for (i=0; i < S_TESTS; i++) {
	if ( TaggedSpiroCPsToBezier0(spiro[i],(bezctx*)(bc[i]))!=1 ) {
	    ret=ret-i;
	    fprintf(stderr,"error %d with TaggedSpiroCPsToBezier0() using data=%d.\n",ret,i);
	    goto test_multi_curves_exit;
	}
    }
#endif
    ret=ret-S_TESTS;

    /* Check ending x,y points versus input spiro knot locations. */
    for (i=0; i < S_TESTS; i++) {
	temp = spiro[i];
	pk = nextknot[i];
	k=0;
#ifdef VERBOSE
	printf("test[%d], input spiro[0..%d], output bc->my_curve[0..%d]\n", \
		   i, scl[i]-1, bc[i]->len-1);
#endif
	for (j=l=0; j < scl[i] && temp[j].ty!='z'; j++,l++) {
	    if (temp[j].ty=='h') {
#ifdef VERBOSE
		printf("  s[%d][ty=%c x=%g y=%g] is handle 'h' to anchor 'a'.\n", \
			l-1,temp[j].ty,temp[j].x,temp[j].y);
#endif
		--l;
	    } else {
		ty = bc[i]->my_curve[k].ty;
		x  = bc[i]->my_curve[k].x1;
		y  = bc[i]->my_curve[k].y1;
		if ( ty=='q' ) {
		    x = bc[i]->my_curve[k].x2;
		    y = bc[i]->my_curve[k].y2;
		}
		if ( ty=='c' ) {
		    x = bc[i]->my_curve[k].x3;
		    y = bc[i]->my_curve[k].y3;
		}
#ifdef VERBOSE
		printf("  s[%d][ty=%c x=%g y=%g], pk=%d mc[%d][x=%g y=%g]\n", \
			l,temp[j].ty,temp[j].x,temp[j].y,pk[j],k,x,y);
#endif
		if ( (fabs(temp[j].x - x) > 1e-5) || (fabs(temp[j].y - y) > 1e-5) ) {
		    /* close-enough for testing 1..10000 value ranges */
		    if ( j == scl[i]-2 && temp[j+1].ty=='z' )
			; /* Exception: skip test of this code point. */
			/* x and/or y are not valid for 'this' check, */
			/* If ending in 'z', then prior code point is */
			/* changed to a curve end point, which is '}' */
		    else {
			ret=ret-i;
			fprintf(stderr,"error %d with test_multi_curves() using data %d\n",ret,i);
			goto test_multi_curves_exit;
		    }
		}
		k += pk[l]+1;
	    }
	}
    }

#ifdef HAVE_PTHREADS
    printf("Multi-thread testing of libspiro passed.\n");
#else
    printf("Sequential tests of libspiro passed.\n");
#endif
    ret = 0;

test_multi_curves_exit:
    if ( nextknot!=NULL ) for (i=0; i < S_TESTS; i++) free(nextknot[i]);
    if ( spiro!=NULL ) for (i=0; i < S_TESTS; i++) free(spiro[i]);
    free(nextknot); free(spiro); free(scl);
    if ( bc!=NULL ) for (i=0; i < S_TESTS; i++) {
	free(bc[i]->my_curve); free(bc[i]);
    }
    free(bc);
    return ret;
}

Project: libspiro
File: call-test.c

static double get_time (void) {
#ifdef DO_TIME_DAY
    struct timeval tv;

    gettimeofday(&tv, NULL);

    return tv.tv_sec + 1e-6 * tv.tv_usec;
#else
    struct timeb tb;

    ftime(&tb);

    return tb.time + 1e-3 * tb.millitm;
#endif
}

Project: libspiro
File: call-test.c

void test_mark_knot(bezctx *bc, int knot_idx) {
    printf("test_mark_knot()_%d\n",knot_idx);
}

Project: libspiro
File: call-test.c

void *test_a_curve(void *pdata) {
    pthread_pcurve *data = (pthread_pcurve*)pdata;
    /*printf("start pthread %d\n",data->ret);*/
    data->ret = TaggedSpiroCPsToBezier2(data->spiro,0,data->bc);
    /*printf("done\n");*/
    return 0;
}

Project: libspiro
File: call-test.c

int test_curve(int c) {
    spiro_cp spiro[16];
    int nextknot[17];
    double d[6];
    spiro_seg *segs = NULL;
    bezctx *bc;
    rs_check_vals *rsp;
    int i,j,done;

    /* Load sample data so that we can see if library is callable */
    load_test_curve(spiro,nextknot,c);

    d[0] = 1.; d[1] = d[2] = 0.;
#if defined(DO_CALL_TEST20)
    /* check if spiro values are reversed correctly on input path */
    printf("---\ntesting spiroreverse() using data=path%d[].\n",c);
    if ( (spiroreverse(spiro,cl[c])) ) {
	fprintf(stderr,"error with spiroreverse() using data=path%d[].\n",c);
	return -1;
    }
    /* just do a visual check to verify types and points look ok. */
    /* NOTE: spiro[] is NOT replaced if unable to reverse values. */
    for (i=0; i < cl[c]; i++) {
	printf("  reversed %d: ty=%c, x=%g, y=%g\n", i,spiro[i].ty,spiro[i].x,spiro[i].y);
    }
#else
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST15) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
#if defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
    /* run_spiro0 with various paths to test a simple arc output. */
    d[0] = -1.;
    printf("---\ntesting run_spiro0(q) using data=path%d[].\n",c);
    if ( (segs=run_spiro0(spiro,d,SPIRO_ARC_MAYBE,cl[c]))==0 ) {
	fprintf(stderr,"error with run_spiro0(q) using data=path%d[].\n",c);
	return -1;
    }
#else
    /* Do run_spiro0 instead (these tests are far from -0.5..+0.5 */
    d[0] = -1.;
    printf("---\ntesting run_spiro0() using data=path%d[].\n",c);
    if ( (segs=run_spiro0(spiro,d,0,cl[c]))==0 ) {
	fprintf(stderr,"error with run_spiro0() using data=path%d[].\n",c);
	return -1;
    }
#endif
#else
    /* Check if run_spiro works okay (try backwards compatiblity) */
    printf("---\ntesting run_spiro() using data=path%d[].\n",c);
    if ( (segs=run_spiro(spiro,cl[c]))==0 ) {
	fprintf(stderr,"error with run_spiro() using data=path%d[].\n",c);
	return -1;
    }
#endif

    /* Load pointer to verification data to ensure it works right */
    if ( c==0 )	     rsp = verify_rs0;
    else if ( c==1 ) rsp = verify_rs1;
    else if ( c==2 ) rsp = verify_rs2;
    /* else if ( c==3 ) rsp = NULL; expecting failure to converge */
    else if ( c==4 ) rsp = verify_rs4;
    else if ( c==5 ) rsp = verify_rs5;
    else if ( c==6 ) rsp = verify_rs5; /* same angles used for #6 */
    else if ( c==7 ) rsp = verify_rs7; /* stop curve with ah code */
    else if ( c==8 ) rsp = verify_rs8;
    else if ( c==9 ) rsp = verify_rs9;
    else if ( c==10 ) rsp = verify_rs10; /* start curve using ah. */
    else if ( c==11 ) rsp = verify_rs11;
    else if ( c==12 ) rsp = verify_rs7; /* test #12 uses path7[]. */
    else if ( c==13 ) rsp = verify_rs13; /* almost same as path11 */
    else if ( c==14 ) rsp = verify_rs14; /* very large path9 copy */
    else if ( c==15 ) rsp = verify_rs15; /* sub-atomic path9 copy */
    else if ( c==16 ) rsp = verify_rs4; /* path4 arc curve output */
    else if ( c==17 ) rsp = verify_rs4; /* path4 arc curve closed */
    else if ( c==18 ) rsp = verify_rs2; /* trying many iterations */
    else	      rsp = verify_rs0; /* long list, arc output. */

    /* Quick visual check shows X,Y knots match with each pathN[] */
    for (i=j=0; i < cl[c]-1; i++,j++) {
	if ( spiro[i].ty == 'h' ) {
	    --j;
	    printf("curve %d ctrl %d t=%c x=%f y=%f (handle)\n", \
	      c,j,segs[i].ty,spiro[i].x,spiro[i].y);
	}
	printf("curve %d line %d t=%c x=%f y=%f bend=%f ch=%f th=%f ",c,j, \
	  segs[i].ty,segs[i].x,segs[i].y,segs[i].bend_th,segs[i].seg_ch,segs[i].seg_th);
	/* Let computer verify that run_spiro() data is PASS/FAIL */
	/* Tests including ah data more complicated to verify xy, */
	/* therefore, skip testing xy for call_tests shown below. */
	if ( (segs[i].ty != spiro[i].ty) ||
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST15) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
	     (fabs((segs[i].x * d[0] + d[1]) - spiro[i].x) > 1e5) ||
	     (fabs((segs[i].y * d[0] + d[2]) - spiro[i].y) > 1e5) ||
	     (fabs(segs[i].seg_ch * d[0] - rsp[i].ch) > 1e5) ||
#else
	     (fabs((segs[i].x * d[0] + d[1]) - spiro[i].x) > 1e-47) ||
	     (fabs((segs[i].y * d[0] + d[2]) - spiro[i].y) > 1e-47) ||
	     (fabs(segs[i].seg_ch * d[0] - rsp[i].ch) > 1e-47) ||
#endif
#else
#if !defined(DO_CALL_TEST6) && !defined(DO_CALL_TEST7) && !defined(DO_CALL_TEST8) && !defined(DO_CALL_TEST10) && !defined(DO_CALL_TEST11) && !defined(DO_CALL_TEST12) && !defined(DO_CALL_TEST13)
	     (fabs(segs[i].x - spiro[i].x) > 1e-5) ||
	     (fabs(segs[i].y - spiro[i].y) > 1e-5) ||
#endif
	     (fabs(segs[i].seg_ch - rsp[i].ch) > 1e-5) ||
#endif
	     (fabs(segs[i].bend_th - rsp[i].b) > 1e-5) ||
	     (fabs(segs[i].seg_th - rsp[i].th) > 1e-5) ) {
	  printf("FAIL\n");
	  fprintf(stderr,"Error found with run_spiro() data. Results are not the same.\n");
	    fprintf(stderr,"expected line %d t=%c x=%f y=%f bend=%f ch=%f th=%f\n", j, \
	      spiro[i].ty,spiro[i].x,spiro[i].y,rsp[i].b,rsp[i].ch,rsp[i].th);
	    free(segs);
	    return -2;
	} else
	    printf("PASS\n");
    }
    printf("curve %d ",c);
    if ( spiro[i].ty == '}' || spiro[i].ty == 'z' )
	printf("stop %d t=%c x=%f y=%f\n",j,segs[i].ty,segs[i].x,segs[i].y);
    else
	printf("line %d t=%c x=%f y=%f bend=%f ch=%f th=%f\n", j,segs[i].ty, \
	  segs[i].x,segs[i].y,segs[i].bend_th,segs[i].seg_ch,segs[i].seg_th);

    /* Quick visual check shows X,Y knots match with each pathN[] */
    printf("---\ntesting spiro_to_bpath() using data from run_spiro(data=path%d[],len=%d).\n",c,cl[c]);
    bc = new_bezctx_test();
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST15) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
#if defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
    spiro_to_bpath0(spiro,segs,d,SPIRO_ARC_MAYBE,cl[c],bc);
#else
    spiro_to_bpath0(spiro,segs,d,0,cl[c],bc);
#endif
#else
    spiro_to_bpath(segs,cl[c],bc);
#endif
    free(segs);
#endif

#if !defined(DO_CALL_TEST20)
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST15) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17) || defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
#if defined(DO_CALL_TEST14) || defined(DO_CALL_TEST15) || defined(DO_CALL_TEST16) || defined(DO_CALL_TEST17)
    /* Now verify we also have simple arc output too */
    printf("---\ntesting SpiroCPsToBezier2() using data=path%d[].\n",c);
    if ( SpiroCPsToBezier2(spiro,cl[c],SPIRO_ARC_MAYBE,co[c],bc)!=1 ) {
	fprintf(stderr,"error with SpiroCPsToBezier2() using data=path%d[].\n",c);
	return -7;
    }
#endif
#if defined(DO_CALL_TEST18) || defined(DO_CALL_TEST19)
    printf("---\ntesting TaggedSpiroCPsToBezier2() using data=path%d[].\n",c);
    if ( TaggedSpiroCPsToBezier2(spiro,SPIRO_ARC_MAYBE,bc)!=1 ) {
	fprintf(stderr,"error with TaggedSpiroCPsToBezier2() using data=path%d[].\n",c);
	return -8;
    }
#endif
#else
#if !defined(DO_CALL_TEST4) && !defined(DO_CALL_TEST6) && !defined(DO_CALL_TEST7) && !defined(DO_CALL_TEST8) && !defined(DO_CALL_TEST9) && !defined(DO_CALL_TEST10) && !defined(DO_CALL_TEST11)
    /* Check if TaggedSpiroCPsToBezier0() works okay */
    printf("---\ntesting TaggedSpiroCPsToBezier0() using data=path%d[].\n",c);
    if ( TaggedSpiroCPsToBezier0(spiro,bc)!=1 ) {
	fprintf(stderr,"error with TaggedSpiroCPsToBezier0() using data=path%d[].\n",c);
	return -3;
    }
#endif

#if !defined(DO_CALL_TEST12)
    /* Check if SpiroCPsToBezier0() works okay */
    printf("---\ntesting SpiroCPsToBezier0() using data=path%d[].\n",c);
    if ( SpiroCPsToBezier0(spiro,cl[c],co[c],bc)!=1 ) {
	fprintf(stderr,"error with SpiroCPsToBezier0() using data=path%d[].\n",c);
	return -4;
    }
#endif

#if !defined(DO_CALL_TEST4) && !defined(DO_CALL_TEST6) && !defined(DO_CALL_TEST7) && !defined(DO_CALL_TEST8) && !defined(DO_CALL_TEST9) && !defined(DO_CALL_TEST10) && !defined(DO_CALL_TEST11)
    /* Check if TaggedSpiroCPsToBezier1() works okay */
    printf("---\ntesting TaggedSpiroCPsToBezier1() using data=path%d[].\n",c);
    TaggedSpiroCPsToBezier1(spiro,bc,&done);
    if ( done!=1 ) {
	fprintf(stderr,"error with TaggedSpiroCPsToBezier1() using data=path%d[].\n",c);
	return -5;
    }
#endif

#if !defined(DO_CALL_TEST12)
    /* Check if SpiroCPsToBezier1() works okay */
    printf("---\ntesting SpiroCPsToBezier1() using data=path%d[].\n",c);
    SpiroCPsToBezier1(spiro,cl[c],co[c],bc,&done);
    if ( done!=1 ) {
	fprintf(stderr,"error with SpiroCPsToBezier1() using data=path%d[].\n",c);
	return -6;
    }
#endif
#endif

#else
    /* We already visually checked output for spiroreverse above. */
    /* Some reversed paths (above) will fail (like path20), so we */
    /* reverse the reversed spiro path so we can use current test */
    /* functions & values (so that this actually tests something) */
    if (c == 20 || c == 21) {
	/* Check if SpiroCPsToBezier2() works okay */
	printf("---\ntesting SpiroCPsToBezier2(reverse) using data=path%d[].\n",c);
	if ( SpiroCPsToBezier2(spiro,cl[c],SPIRO_REVERSE_SRC,co[c],bc)!=1 ) {
	    fprintf(stderr,"error with SpiroCPsToBezier2(reverse) using data=path%d[].\n",c);
	    return -9;
	}
    } else { /* c==22 || c==23 || c==24 */
	/* Check if TaggedSpiroCPsToBezier2() works okay */
	printf("---\ntesting TaggedSpiroCPsToBezier2(reverse) using data=path%d[].\n",c);
	if ( TaggedSpiroCPsToBezier2(spiro,SPIRO_REVERSE_SRC,bc)!=1 ) {
	    fprintf(stderr,"error with TaggedSpiroCPsToBezier2(reverse) using data=path%d[].\n",c);
	    return -10;
	}
    }
#endif

    free(bc);
    return 0;
}

Project: libspiro
File: call-test.c

void test_moveto(bezctx *bc, double x, double y, int is_open) {
    printf("test_moveto(%g,%g)_%d\n",x,y,is_open);
}

Project: libspiro
File: call-test.c

void test_quadto(bezctx *bc, double x1, double y1, double x2, double y2) {
    printf("test_quadto(%g,%g, %g,%g)\n",x1,y1,x2,y2);
}

Project: libspiro
File: call-test.c

void test_lineto(bezctx *bc, double x, double y) {
    printf("test_lineto(%g,%g)\n",x,y);
}

Project: libspiro
File: call-test.c

void test_s_moveto(bezctx *z, double x, double y, int is_open) {
    test_bezctx *p = (test_bezctx*)z;
    int i;

    if ( (i=p->len) < S_RESULTS ) {
#ifdef VERBOSE
	printf("test_s_moveto(%g,%g)_%d [%d]%d\n",x,y,is_open,p->c_id,i);
#endif
	p->is_open = is_open;
	p->my_curve[i].x1 = x;
	p->my_curve[i].y1 = y;
	p->my_curve[p->len++].ty = 'm';
    }
}

Project: libspiro
File: call-test.c

void load_test_curve(spiro_cp *spiro, int *nextknot, int c) {
/* load a test curve (and nextknot locations) into memory */
    spiro_cp path0[] = { /* ...came with unit-test */
	{334, 117, 'v'},
	{305, 176, 'v'},
	{212, 142, 'c'},
	{159, 171, 'c'},
	{224, 237, 'c'},
	{347, 335, 'c'},
	{202, 467, 'c'},
	{ 81, 429, 'v'},
	{114, 368, 'v'},
	{201, 402, 'c'},
	{276, 369, 'c'},
	{218, 308, 'c'},
	{ 91, 211, 'c'},
	{124, 111, 'c'},
	{229,  82, 'c'},
	{0, 0, 'z'}
    };
    int knot0[] = {
	1, 1, 3, 3, 2, 3, 1, 1, 1, 3, 3, 2, 2, 2, 1, 1
    };
    spiro_cp path1[] = { /* do a test using "{"..."}" */
	{ 80, 738, '{'},
	{749, 540, 'o'},
	{671, 309, 'o'},
	{521, 396, 'o'},
	{377, 333, 'o'},
	{467, 231, '}'}
    };
    int knot1[] = {
	3, 4, 2, 3, 2, 0
    };
    spiro_cp path2[] = { /* this does many iterations */
	{233, 143, '{'},
	{341, 138, 'o'},
	{386,  72, 'o'},
	{444, 141, '}'}
    };
    int knot2[] = {
	2, 1, 9, 0
    };
    spiro_cp path3[] = { /* this will fail to converge */
	{233, 144, '{'}, /* will not pass (on purpose) */
	{341, 138, 'o'},
	{386,  72, 'o'},
	{443, 141, 'o'},
	{467, 231, 'o'},
	{377, 333, '}'}
    };
    int knot3[] = {
	2, 1, 9, 1, 1, 0
    };
    spiro_cp path4[] = { /* test a cyclic calculation. */
	{-100,    0, SPIRO_G4},
	{   0,  100, SPIRO_G4},
	{ 100,    0, SPIRO_G4},
	{   0, -100, SPIRO_G4}
    };
    int knot4[] = {
	2, 2, 2, 0
    };
    spiro_cp path5[] = { /* verify curve data with []. */
	{  0,   0, '{'},
	{100, 100, 'c'},
	{200, 200, '['},
	{300, 200, ']'},
	{400, 150, 'c'},
	{300, 100, '['},
	{200, 100, ']'},
	{150,  50, 'c'},
	{100,   0, '['},
	{  0,-100, ']'},
	{-50,-200, 'c'},
	{-80,-250, '}'} /* test 23 reverses this list. */
    };
    int knot5[] = {
	1, 4, 1, 3, 3, 1, 4, 2, 1, 2, 1, 0
    };
    spiro_cp path6[] = { /* verify curve data with ah. */
	{  0,   0, '{'},
	{100, 100, 'c'},
	{200, 200, 'a'},
	{300, 200, 'h'},
	{300, 150, 'c'},
	{200, 100, 'a'},
	{100, 100, 'h'},
	{150,  50, 'c'},
	{100,   0, 'a'},
	{  0,-100, 'h'},
	{ 50,-100, 'c'},
	{ 20,-150, '}'}
    };
    int knot6[] = {
	1, 4, 3, 3, 4, 2, 2, 1, 0, 0, 0, 0
    };
    spiro_cp path7[] = { /* loop stops with ah curves. */
	{  0, 200, 'o'},
	{ 50, 210, 'o'},
	{100,   0, 'a'},
	{100, -50, 'h'},
	{  0,-190, 'a'},
	{-50,-195, 'h'},
	{-90,   0, 'a'},
	{-95,  50, 'h'},/* tests 7, 8, 20, 21 end here */
	{  0,   0, 'z'} /* call_test 12 ends with ah z */
    };
    int knot7[] = {
	1, 1, 5, 5, 6, 3, 0, 0, 0
    };
    spiro_cp path10[] = { /* start loop with ah curves */
#ifndef TEST_10_11
	{100,   0, 'a'},
	{100, -50, 'h'},
	{  0,-190, 'a'},
	{-50,-195, 'h'},
	{-90,   0, 'a'},
	{-95,  50, 'h'},
	{  0, 200, 'o'},
	{ 50, 210, 'o'}
#else
	{100,   0, '['},
	{100, -50, ']'},
	{  0,-240, 'a'},
	{-50,-245, 'h'},
	{-90, -50, '['},
	{-95,   0, ']'},
	{ -5, 200, 'o'},
	{ 45, 210, 'o'}
#endif
    };
    int knot10[] = {
	5, 6, 3, 1, 1, 5, 0, 0
    };
    spiro_cp path13[] = { /* start open curve using {h */
	{100,   0, '{'},
	{100, -50, 'h'},
	{  0,-190, 'a'},
	{-50,-195, 'h'},
	{-90,   0, 'a'},
	{-95,  50, 'h'},
	{  0, 200, 'o'},
	{ 50, 210, '}'}
    };
    int knot13[] = {
	5, 6, 3, 1, 1, 5, 0, 0
    };
    spiro_cp path14[] = { /* very big path[4] version. */
	{-100e10,    0, SPIRO_G4},
	{   0,  100e10, SPIRO_G4},
	{ 100e10,    0, SPIRO_G4},
	{   0, -100e10, SPIRO_G4}
    };
    spiro_cp path15[] = { /* very tiny path[4] version */
	{-100e-42,    0, SPIRO_G4},
	{   0,  100e-42, SPIRO_G4},
	{ 100e-42,    0, SPIRO_G4},
	{   0, -100e-42, SPIRO_G4}
    };
    int i;

    /* Load static variable tables into memory because */
    /* SpiroCPsToBezier0() modifies start & end values */
    /* Later call-tests will also modify these so that */
    /* we can verify multi-threads aren't overwriting. */
    if ( c==0 ) for (i = 0; i < 16; i++) {
	spiro[i].x = path0[i].x;
	spiro[i].y = path0[i].y;
	spiro[i].ty = path0[i].ty;
	nextknot[i] = knot0[i];
    } else if ( c==1 ) for (i = 0; i < 6; i++) {
	spiro[i].x = path1[i].x;
	spiro[i].y = path1[i].y;
	spiro[i].ty = path1[i].ty;
	nextknot[i] = knot1[i];
    } else if ( c==2 ) for (i = 0; i < 4; i++) {
	spiro[i].x = path2[i].x;
	spiro[i].y = path2[i].y;
	spiro[i].ty = path2[i].ty;
	nextknot[i] = knot2[i];
    } else if ( c==3 ) for (i = 0; i < 6; i++) {
	spiro[i].x = path3[i].x;
	spiro[i].y = path3[i].y;
	spiro[i].ty = path3[i].ty;
	nextknot[i] = knot3[i];
    } else if ( c==4 ) for (i = 0; i < 4; i++) {
	spiro[i].x = path4[i].x;
	spiro[i].y = path4[i].y;
	spiro[i].ty = path4[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==5 ) for (i = 0; i < 12; i++) {
	spiro[i].x = path5[i].x;
	spiro[i].y = path5[i].y;
	spiro[i].ty = path5[i].ty;
	nextknot[i] = knot5[i];
    } else if ( c==6 ) for (i = 0; i < 12; i++) {
	spiro[i].x = path6[i].x;
	spiro[i].y = path6[i].y;
	spiro[i].ty = path6[i].ty;
	nextknot[i] = knot6[i];
    } else if ( c==7 || c==8 ) for (i = 0; i < 8; i++) {
	/* path7[]_co[7]==closed_curve, path8[]_co[8]==open_curve */
	spiro[i].x = path7[i].x;
	spiro[i].y = path7[i].y;
	spiro[i].ty = path7[i].ty;
	nextknot[i] = knot7[i];
    } else if ( c==9 ) for (i = 0; i < 4; i++) {
	/* path9[] is closed curve version of path4[] open curve. */
	spiro[i].x = path4[i].x;
	spiro[i].y = path4[i].y;
	spiro[i].ty = path4[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==10 || c==11 ) for (i = 0; i < 8; i++) {
	/* path10[]_co[10]=closedcurve, path11[]_co[10]=opencurve */
	spiro[i].x = path10[i].x;
	spiro[i].y = path10[i].y;
	spiro[i].ty = path10[i].ty;
	nextknot[i] = knot10[i];
    } else  if ( c==12 ) for (i = 0; i < 9; i++) {
	/* call_test12 checks curve ending in ah with following z */
	/* and declare cl[12] len=8 so run_spiro() can work okay. */
	spiro[i].x = path7[i].x;
	spiro[i].y = path7[i].y;
	spiro[i].ty = path7[i].ty;
	nextknot[i] = knot7[i];
    } else if ( c==13 ) for (i = 0; i < 8; i++) {
	/* path13[] is open curve based on path11[] using '{','}' */
	spiro[i].x = path13[i].x;
	spiro[i].y = path13[i].y;
	spiro[i].ty = path13[i].ty;
	nextknot[i] = knot13[i];
    } else if ( c==14 ) for (i = 0; i < 4; i++) {
	/* path14[] is a very big version of closed curve path9[] */
	spiro[i].x = path14[i].x;
	spiro[i].y = path14[i].y;
	spiro[i].ty = path14[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==15 ) for (i = 0; i < 4; i++) {
	/* path15[] is a very small copy of path9[] closed curve. */
	spiro[i].x = path15[i].x;
	spiro[i].y = path15[i].y;
	spiro[i].ty = path15[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==16 ) for (i = 0; i < 4; i++) {
	/* path16[] uses path4[] as first try at quadratic output */
	spiro[i].x = path4[i].x;
	spiro[i].y = path4[i].y;
	spiro[i].ty = path4[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==17 ) for (i = 0; i < 4; i++) {
	/* path17[] is closed curve version of path4[] open curve */
	spiro[i].x = path4[i].x;
	spiro[i].y = path4[i].y;
	spiro[i].ty = path4[i].ty;
	nextknot[i] = knot4[i];
    } else if ( c==18 ) for (i = 0; i < 4; i++) {
	spiro[i].x = path2[i].x;
	spiro[i].y = path2[i].y;
	spiro[i].ty = path2[i].ty;
	nextknot[i] = knot2[i];
    } else if ( c==19 ) for (i = 0; i < 16; i++) {
	spiro[i].x = path0[i].x;
	spiro[i].y = path0[i].y;
	spiro[i].ty = path0[i].ty;
	nextknot[i] = knot0[i];
    } else if ( c==20 || c==21 || c==22) for (i = 0; i < 9; i++) {
	/* path20[]=closed, path21[]=open, path22[]=closed_with_z */
	spiro[i].x = path7[i].x;
	spiro[i].y = path7[i].y;
	spiro[i].ty = path7[i].ty;
    } else if ( c==23 ) for (i = 0; i < 12; i++) {
	spiro[i].x = path5[i].x;
	spiro[i].y = path5[i].y;
	spiro[i].ty = path5[i].ty;
    } else if ( c==24 ) for (i = 0; i < 16; i++) {
	spiro[i].x = path0[i].x;
	spiro[i].y = path0[i].y;
	spiro[i].ty = path0[i].ty;
	nextknot[i] = knot0[i];
    }
}

Project: libspiro
File: call-test.c

void test_s_curveto(bezctx *z, double x1, double y1, double x2, double y2,
		  double x3, double y3) {
    test_bezctx *p = (test_bezctx*)z;
    int i;

    if ( (i=p->len) < S_RESULTS ) {
#ifdef VERBOSE
	printf("test_s_curveto(%g,%g, %g,%g, %g,%g) [%d]%d\n",x1,y1,x2,y2,x3,y3,p->c_id,i);
#endif
	p->my_curve[i].x1 = x1;
	p->my_curve[i].y1 = y1;
	p->my_curve[i].x2 = x2;
	p->my_curve[i].y2 = y2;
	p->my_curve[i].x3 = x3;
	p->my_curve[i].y3 = y3;
	p->my_curve[p->len++].ty = 'c';
    }
}

Project: libspiro
File: call-test.c

void test_s_lineto(bezctx *z, double x, double y) {
    test_bezctx *p = (test_bezctx*)z;
    int i;

    if ( (i=p->len) < S_RESULTS ) {
#ifdef VERBOSE
	printf("test_s_lineto(%g,%g) [%d]%d\n",x,y,p->c_id,i);
#endif
	p->my_curve[i].x1 = x;
	p->my_curve[i].y1 = y;
	p->my_curve[p->len++].ty = 'l';
    }
}

Project: libspiro
File: call-test.c

bezctx *new_bezctx_test(void) {
    bezctx *result = malloc(1*sizeof(bezctx));

    result->moveto = test_moveto;
    result->lineto = test_lineto;
    result->quadto = test_quadto;
    result->curveto = test_curveto;
    result->mark_knot = test_mark_knot;
    return result;
}

Project: libspiro
File: call-test.c

void test_curveto(bezctx *bc, double x1, double y1, double x2, double y2,
		  double x3, double y3) {
    printf("test_curveto(%g,%g, %g,%g, %g,%g)\n",x1,y1,x2,y2,x3,y3);
}

Project: libspiro
File: call-test.c

void test_s_quadto(bezctx *z, double x1, double y1, double x2, double y2) {
    test_bezctx *p = (test_bezctx*)z;
    int i;

    if ( (i=p->len) < S_RESULTS ) {
#ifdef VERBOSE
	printf("test_s_quadto(%g,%g, %g,%g) [%d]%d\n",x1,y1,x2,y2,p->c_id,i);
#endif
	p->my_curve[i].x1 = x1;
	p->my_curve[i].y1 = y1;
	p->my_curve[i].x2 = x2;
	p->my_curve[i].y2 = y2;
	p->my_curve[p->len++].ty = 'q';
    }
}

Project: libspiro
File: call-test.c

int main(int argc, char **argv) {
    double st, en;
    int ret;
    st = get_time();

#ifdef DO_CALL_TEST0
    ret=test_curve(0);	/* this comes with unit-test. */
#endif
#ifdef DO_CALL_TEST1
    ret=test_curve(1);	/* do a test using "{"..."}". */
#endif
#ifdef DO_CALL_TEST2
    ret=test_curve(2);	/* this does many iterations. */
#endif
#ifdef DO_CALL_TEST3
    if ( (ret=test_curve(3)==0) ) /* This curve will not converge */
	ret = -1 /* error found! ret=error value */;
    else
	ret = 0; /* expected failure to converge */
#endif
#ifdef DO_CALL_TEST4
    ret=test_curve(4);	/* test a cyclic calculation. */
#endif
#ifdef DO_CALL_TEST5
    ret=test_curve(5);	/* verify curve data with []. */
#endif
#ifdef DO_CALL_TEST6
    ret=test_curve(6);	/* verify curve data with ah. */
#endif
#ifdef DO_CALL_TEST7
    ret=test_curve(7);	/* loop stops with ah curves. */
#endif
#ifdef DO_CALL_TEST8
    ret=test_curve(8);	/* this open curve ends in ah */
#endif
#ifdef DO_CALL_TEST9
    ret=test_curve(9);	/* path4[] as a closed curve. */
#endif
#ifdef DO_CALL_TEST10
    /* TODO: see why can start using c, o, but not [. */
    /* TODO: see why can start using c, o, but not a. */
    ret=test_curve(10);	/* start loop with ah curves. */
    ret = 0; /* ignore result for now until improved. */
#endif
#ifdef DO_CALL_TEST11
    /* TODO: see why can start using c, o, but not [. */
    /* TODO: see why can start using c, o, but not a. */
    ret=test_curve(11);	/* start open curve using ah. */
    ret = 0; /* ignore result for now until improved. */
#endif
#ifdef DO_CALL_TEST12
    /* TODO: knot counts not matched for taggedspiro, */
    /* therefore use !defined(DO_CALL_TEST12) for now */
    ret=test_curve(12);	/* do path7[] with a z ending */
#endif
#ifdef DO_CALL_TEST13
    ret=test_curve(13);	/* start open curve using {h. */
#endif
#ifdef DO_CALL_TEST14
    ret=test_curve(14);	/* go very big! go very tiny! */
#endif
#ifdef DO_CALL_TEST15
    ret=test_curve(15);	/* go very big! go very tiny! */
#endif
#ifdef DO_CALL_TEST16
    ret=test_curve(16);	/* testing arc output path4[] */
#endif
#ifdef DO_CALL_TEST17
    ret=test_curve(17);	/* do arc closed curve outut. */
#endif
#ifdef DO_CALL_TEST18
    ret=test_curve(18);	/* do iterative as arc output */
#endif
#ifdef DO_CALL_TEST19
    ret=test_curve(19);	/* do lengthy output with arc */
#endif
#ifdef DO_CALL_TEST20
    /* test spiroreverse and verify path[] directions */
    if ( (ret=test_curve(20))==0 )
	if ( (ret=test_curve(21))==0 )
	    if ( (ret=test_curve(22))==0 )
		if ( (ret=test_curve(23))==0 )
		    ret=test_curve(24);
#endif
#ifdef DO_CALL_TESTM
    ret=test_multi_curves();
#endif

    en = get_time();
    printf("time %g\n", (en - st));
    return ret;
}

Project: libspiro
File: call-test.c

void test_s_mark_knot(bezctx *z, int knot_idx) {
    test_bezctx *p = (test_bezctx*)z;

    if ( p->len < S_RESULTS )
#ifdef VERBOSE
	printf("test_s_mark_knot()_%d [%d]%d\n",knot_idx,p->c_id,p->len);
#endif
	p->my_curve[p->len++].ty = 'k';
}