[net.bugs.4bsd] My list of 4.2 Curses library bug fixes

laman@ncr-sd.UUCP (Mike Laman) (10/31/85)

This file contains a concatenated list of bug fixes that I have found in
the 4.2 curses library over time.  There is no real order here.
Included in this file is a bug fix posted by Sam Kendall earlier, too.
Most of these fixes (maybe all?) have also been added by Tom Truscott
for the his window management library, ``wm''.
I have included two messages that I sent out on bug fixes.  They are at the
end.  Several other fixes are as follows:

1. Add touchwin(win); to the end of winsertln().
2. If your loop in wgetstr() has a semicolon on the end which blocks off the
   pointer incrementation as implied by the indentation style, reomve that
   semi colon:

   while(some ugly god awful expression);
	++str;			        ^
				        |
				        |
				      Opps
3. If your first loop in wdeleteln() looks like:

				:
				:
				:
	for (y = win->_cury; y < win->_maxy - 2; y++) {
				:
				:
				:

   This doesn't get the last line of the given window.  Change it to the
   following:

				:
				:
				:
	for (y = win->_cury; y < win->_maxy - 1; y++) {
				:
				:
				:

Here are the other two fixes which I have previously posted on the network.

There is a bug in wdeleteln().  This bug is in the curses library distributed
over net.sources, and in the 4.2 BSD distribution.

Even though the last line of the window gets cleared internally, its "refresh"
image may not.  The fix is simple.  Add the following two lines to the end
of the wdeleteln() routine.

	win->_firstch[win->_maxy-1] = 0;
	win->_lastch[win->_maxy-1] = win->_maxx - 1;

Now wrefresh() will look at the entire line.

I have enclosed a little program that will show you if you have the bug.  Just
compile it with you curses library (termlib too) and run it.

#include	<curses.h>

main() {
	register i;

	initscr();
	mvaddstr(0, 20, "This program will delete line #5 after");
	mvaddstr(1, 20, "writing the line number for each line.");
	for(i = 0; i < LINES; ++i)
		mvprintw(i, 0, "Line #%d", i);
	refresh();
	addstr("     You have the bug if the BOTTOM line is not COMPLETELY blank!");
	move(5, 0);
	deleteln();
	move(LINES - 3, 0);
	refresh();
	endwin();
}

And the other:

    The following bug is in the curses library distributed over net.sources,
and in the 4.2 BSD distribution (that sdcsvax received at least).

    The bug is in makech() (in refresh.c).  makech() gets called to give
output for the given LINE (hint hint).  It is interesting that this bug
managed to get out.  Here is the offending code (the simple fix follows).

			:
			:
			:
		else if (wx < lch)
			while (*nsp == *csp) {
				nsp++;
				if (!curwin)
					csp++;
				++wx;
			}
		else
			:
			:
			:

I wrote a program that added '*' to (0, 0) on stdscr then a refresh().
wx ended up with a value of over 3000!  That loop walked down the line
and the next, ... (all the way down the window!).  The following code
is the fix.  Notice that the ++wx looks just perfect.  It really makes one
think "they" thought of it, but merely forgot to add the test.

			:
			:
			:
		else if (wx < lch)
			while (*nsp == *csp && wx <= lch) {
				nsp++;
				if (!curwin)
					csp++;
				++wx;
			}
		else
			:
			:
			:

More fixes:
	Expand ttytype[] in curses.c to a larger value, add the length into
	curses.h, and change the strcpy() to strncpy() [with
	"sizeof ttytype - 1" as the third argument] in cr_tty.c.

	Fix overlay to <= on the first for-loop.  It loses the last line
	to overlay.

Problem with NONL not getting reset in nl() and nonl().  Consequently the
terminal settings are correct, but the library doesn't "know" the mode
for NONL has possibly changed.  Need CRMOD false.
Here is a little program to see if you have the problem.
#include	<curses.h>

main() {
	initscr();
	nl();
	mvaddstr(0, 0, "Beginning of line #1\nThis should be at beginning of line #2!!!");
	move(LINES-1, 0);
	refresh();
	nonl();
	endwin();
}

Fix:  Add NONL=0 to nl() and NONL = 1 to nonl().

Another bug:
	newwin() will allow creating a window of dimensions that would be off
	the screen.  This has been know to cause core dumps when refreshes
	are done.  The fix is to the following original statement in newwin()
	so an error value is returned.

		if (nl < 0 || nc < 0 )

	Change it to:

		if (nl < 0 || nc < 0 || nl + by > LINES || nc + bx > COLS)

	Since refresh() assumes the window fits on the screen, I believe curses
	does not mean to allow windows larger than the screen to be refreshed.
	Overlay and overwrite should not have any problems with windows larger
	than screen size.  Off hand I see "wrefresh(toobig)" to be the problem.
	I looked into modifying refresh() to allow refreshing wiondows that
	larger than the screen size, but this gets a bit ugly.  If you want to
	play around with windows larger than screen size, I suggest you look
	into System V.2's curses.  It has scratch pads, which are just what
	you would want.  You can write on them to your heart's content.
	And when you want, you can write part/all of it on the screen with
	"prefresh()".  Glancing into System V.2, it SEEMS it handles the
	problem of refreshing an image too big for the screen.  I haven't
	gotten to tinker with scratch pads yet.

Around the end of May, Lorien Pratt send out a plea for getting curses to use
the scrolling of the screen to scroll text and not have curses refresh the
screen.  I finally got time to dig into the problem.  This message includes
a program to show you if you have the bug, and a fix to get rid of the bug.
There is also a "fix" to a problem with the same line (oddly enough) of
code to handle an "undefined" situation a little better than the library
currently does.

The bug is caused in scroll().  When "curscr" is scrolled, a newline is
outputted, as I feel it should.  BUT the window's current cursor location
IS DECREMENTED! [Think about it a second...  Since when does scrolling the
screen up one line cause the cursor to move up with the text?!]  This was
causing an extra newline to be outputted during refresh(), since the cursor is
thought to be on line the next to the last line when it is actually on the
last line.  Refresh() calls makech() which calls domvcur() with the old line #
being LINES - 2 and new line # being LINES - 1.  So domvcur() outputs a newline
to get the cursor to where it was told.  Since the cursor is actually on the
last line of the screen, the screen gets scrolled.  scroll(curscr) already
scrolled the screen, so we now have the extra line.

/* Start of program.  Cut here------ */
#include	<curses.h>

main() {
	register int i;

	initscr();
	scrollok(stdscr, TRUE);
	scrollok(curscr, TRUE);
	noecho(); /* Since we are scrolling and I/O occurs on the last line */
	mvaddstr(0, 0, "The screen will fill with lines that specify the line number.");
	mvaddstr(1, 0, "Hit return to start scrolling your screen.");
	mvaddstr(2, 0, "If there are blank lines between the lines saying that they were");
	mvaddstr(3, 0, "positioned by scrolling, THEN YOU HAVE THE BUG!!!!");
	for(i = 4; i < LINES; ++i)
		mvprintw(i, 0, "This is the line numbered %d.", i );
	addstr(" --Hit return to continue--");
	refresh();
	getchar();
	for(i = LINES; i < LINES + 10; ++i) {
		scroll(curscr);
		scroll(stdscr);
		mvprintw(LINES - 1, 0, "Scroll positioned line %d", i);
		refresh();
	}
	endwin();
	putchar('\n');
}
/* End of program.  Cut here------ */

The fix is pretty simple.  Remove the line that decrements the current cursor
position, namely "win->_cury".  Chances are it looks like this:

	win->_cury--;

[ I think I may have sent out a suggested change to curses that would make this
  a bit more complicated.  More on that latter in the message.
]

Add an else clause to the "if (win == curscr) {" that immediately follows.
(Notice that you have to be careful so if DEBUG is defined you don't get
 syntax errors.)
Here is the original if-then-else:

	if (win == curscr) {
		putchar('\n');
		if (!NONL)
			win->_curx = 0;
# ifdef DEBUG
		fprintf(outf, "SCROLL: win == curscr\n");
		fflush(outf);
# endif
	}
# ifdef DEBUG
	else {
		fprintf(outf, "SCROLL: win [0%o] != curscr [0%o]\n",win,curscr);
		fflush(outf);
	}
# endif

And here is my change:

	if (win == curscr) {
		putchar('\n');
		if (!NONL)
			win->_curx = 0;
# ifdef DEBUG
		fprintf(outf, "SCROLL: win == curscr\n");
		fflush(outf);
# endif
	} else {
		if(win->_cury-- <= 0)
			win->_cury = 0;
# ifdef DEBUG
		fprintf(outf, "SCROLL: win [0%o] != curscr [0%o]\n",win,curscr);
		fflush(outf);
# endif
	}

Notice that instead of just decrementing "win->_cury", I reset it to zero.
I see no reason to leave the current cursor position in an undefined
and useless value.  I think this a nicer thing to do, but if programs
depend on a specific value on this, then they will probably have other
problems too.

From uucp Fri May  3 13:44 EDT 1985
>From greg Fri May  3 10:42 PDT 1985 remote from ncr-tp
Relay-Version: version B 2.10.2 9/18/84; site ncr-tp.UUCP
Posting-Version: version B 2.10.2 9/3/84; site talcott.UUCP
Path: ncr-tp!sdcc6!sdcc3!sdcsvax!dcdwest!ittvax!decvax!genrad!panda!talcott!kendall
From: kendall@talcott.UUCP (Sam Kendall)
Newsgroups: net.bugs.4bsd
Subject: Pointer bounds violations in curses
Message-ID: <426@talcott.UUCP>
Date: 2 May 85 00:54:24 GMT
Date-Received: 3 May 85 11:12:20 GMT
Distribution: net
Organization: Delft Consulting Corp., New York
Lines: 83
Status: R

Index:	usr.lib/libcurses 4.2BSD

Description:
	In "refresh.c", pointers access storage beyond the bounds of
	the array they are supposed to point into, leading to
	unpredictable behavior.  It is a coincidence that the original
	code works on a VAX, and it might not work in all cases, or on
	other machines.  The bugs lead to some unnecessary looping even
	on the VAX.  In more detail: in most of the function
	`makech', `wx <= lch' should always hold true; if it does not,
	then `nsp' and `csp' point past the end of the arrays that they
	are supposed to point into.  This happens in the two places
	that are corrected.
Repeat-By:
	These problems were detected with lint and with the Bcc Compiler,
	a C language checkout compiler.  If you do not have the Bcc
	Compiler, insert debugging statements in "refresh.c" to monitor the
	values of `wx' and `lch' at the two points where corrections are
	to be made, and note that `wx' gets much larger than `lch'.
Fix:
	There are three changes.  The first two changes prevent the
	bounds violations; the third merely corrects a lint-reported
	inconsistency.

*** /usr/src/usr.lib/libcurses/refresh.c	Thu Jun 23 12:53:54 1983
--- libcurses/refresh.c	Wed May  1 15:51:17 1985
***************
*** 143,149
  # endif	
  			ly = y;
  			lx = wx + win->_begx;
! 			while (*nsp != *csp && wx <= lch) {
  				if (ce != NULL && wx >= nlsp && *nsp == ' ') {
  					/*
  					 * check for clear to end-of-line

--- 143,149 -----
  # endif	
  			ly = y;
  			lx = wx + win->_begx;
! 			while (wx <= lch && *nsp != *csp) {
  				if (ce != NULL && wx >= nlsp && *nsp == ' ') {
  					/*
  					 * check for clear to end-of-line
***************
[ Note from Mike Laman.  This following change is already in this file ]
*** 224,230
  			lx = wx + win->_begx;
  		}
  		else if (wx < lch)
! 			while (*nsp == *csp) {
  				nsp++;
  				if (!curwin)
  					csp++;

--- 224,230 -----
  			lx = wx + win->_begx;
  		}
  		else if (wx < lch)
! 			while (wx <= lch && *nsp == *csp) {
  				nsp++;
  				if (!curwin)
  					csp++;

*** /usr/src/usr.lib/libcurses/addch.c	Thu Jun 23 12:54:48 1983
--- libcurses/addch.c	Tue Apr  9 11:22:32 1985
***************
*** 36,42
  # endif
  		if (win->_flags & _STANDOUT)
  			c |= _STANDOUT;
! 		set_ch(win, y, x, c, NULL);
  		for (wp = win->_nextp; wp != win; wp = wp->_nextp)
  			set_ch(wp, y, x, c, win);
  		win->_y[y][x++] = c;

--- 36,42 -----
  # endif
  		if (win->_flags & _STANDOUT)
  			c |= _STANDOUT;
! 		set_ch(win, y, x, c, (WINDOW *)NULL);
  		for (wp = win->_nextp; wp != win; wp = wp->_nextp)
  			set_ch(wp, y, x, c, win);
  		win->_y[y][x++] = c;

End of bug fixes...  Good luck...

		Mike Laman, NCR Rancho Bernardo
		UUCP: {ucbvax,philabs,sdcsla}!sdcsvax!ncr-sd!laman