Baby X

M

Malcolm McLean

On Tue, 06 Aug 2013 12:38:47 -0700, Malcolm McLean wrote:


No, the mode you should use except in special circumstances is conforming
mode, with maximum warning and error reporting. That way, when you get
rid of all the warnings and errors, you have *some* vague hope the code
is actually clean and proper... and it will also, very likely, compile in
"default mode".

Code written with the requirement that one turn off or ignore warnings
and errors, except in very rare cases, is code that simply cannot be
trusted.
I've just Walled it.
Walling did show up some bugs that I'd missed. Only one which was serious,
passing a char instead of an int as a length parameter for a string. Since
the strings will almost always be under 127 chars, this tested OK, but if
the edit box goes wider it might fall over. So that was a catch.

Some of the warnings weren't bugs, but were legitimate - functions declaring
int return types and then not returning a status. That's just sloppiness,
I prototyped the function before writing it, then didn't change the type
when in fact there were no error conditions. The code's improved as a
result.

But the unsigned debacle is deadly. X doesn't just demand an unsigned depth
parameter for a window geometry, which can be safely ignored. It demands
unsigned width and height as well. But width and height are used extensively
in drawing calculations, and of course it's vital that geometry has negative
co-ordinates if it goes out of window bounds. What I did was to replace all
the get geometry calls to a wrapper.
There are also a few little niggles. For example often you want mouse
co-ordinates, but as it happens you're only using one of them. It's a lot
better to have both x and y in scope, set to mouse x and mouse y. It makes
code clearer. But that generates a warning about unused variables.

People put a trust in machines. A machine can tell you whether code is
compliant C or not. But it can't really tell you whether it is likely to
mislead a human programmer reading it. It can pick up on things which,
from experience, mislead, but it can't actually over-rule a human. But of
course the fact that we have a warning system means that we treat is as if
it can. When you start mis-using the warning system, e.g. using the werror
flag, people have to code round the warning system to express their
intention, and the code becomes convoluted, with variables introduced just
to work round warnings.
Wall isn't an unmitigated good. But it's probably psychologically necessary
if people are to use Baby X.
 
G

glen herrmannsfeldt

(snip)
I've just Walled it.
(snip)
Some of the warnings weren't bugs, but were legitimate - functions declaring
int return types and then not returning a status. That's just sloppiness,
I prototyped the function before writing it, then didn't change the type
when in fact there were no error conditions. The code's improved as a
result.

Yesterday, I recompiled some programs that I wrote in the pre-ANSI
days, with functions returning int, and no return. (I had compiled
them since ANSI, but hadn't changed it.)

Note that before ANSI there was no void, and it was usual for
functions to return int, and no return statement. I believe it
returns zero in that case.

Likely for some time after ANSI, to allow older code without excess
warnings, compilers allowed it without a warning.

-- glen
 
I

Ike Naar

I've just Walled it.
Walling did show up some bugs that I'd missed. Only one which was serious,
passing a char instead of an int as a length parameter for a string. Since
the strings will almost always be under 127 chars, this tested OK, but if
the edit box goes wider it might fall over. So that was a catch.

Some of the warnings weren't bugs, but were legitimate - functions declaring
int return types and then not returning a status. That's just sloppiness,
I prototyped the function before writing it, then didn't change the type
when in fact there were no error conditions. The code's improved as a
result.

Did you fix the one where the value of an uninitialized variable was used?
 
K

Keith Thompson

glen herrmannsfeldt said:
Note that before ANSI there was no void, and it was usual for
functions to return int, and no return statement. I believe it
returns zero in that case.

Falling off the end of a non-void function causes it to return an
undefined result to the caller. If the caller attempts to use that
value, the behavior is undefined.

For backward compatibility, this:

int func(void) {
/* do something */
}

/* ... */

func();

is well defined, since the undefined result is not used.

C99 added a special case for main() that makes reaching the closing }
equivalent to "return 0;". That doesn't affect functions other than
main().
 
M

Malcolm McLean

Did you fix the one where the value of an uninitialized variable was used?
Yes, that one was in the getsavefile function.
It was a bug, but it would have come out pretty quickly.
 
I

Ike Naar

Yes, that one was in the getsavefile function.
It was a bug, but it would have come out pretty quickly.

There's more, e.g. in BBX_FilePicker.c:

static void pressok(void *obj)
{
/* ... */
int sel;

if(dlg->list->sel != -1)
{
sel = dlg->list->sel; /* ---> sel is initialized here */
/* ... */
}
else if(dlg->save)
{
char *fget = bbx_lineedit_gettext(dlg->savename_edt);
if(strlen(fget))
{
/* ---> sel is uninitialized at this point but used below */
makepath(dlg->fullname, dlg->directory, dlg->list->list[sel].name);
 
M

Malcolm McLean

There's more, e.g. in BBX_FilePicker.c:



static void pressok(void *obj)

{
}

That is the same bug.
When you call bbx_getsavefile it calls pressok() in response to the OK button,
then incorrectly creates the answer from the list rather than from the
edit box.
I was careless in testing that change, and it''s very clearly a bug. But
one that would have come out pretty quickly. The FilePicker dialog is
in a bit of flux, since it doesn't really look nice enough yet.
 
I

Ike Naar

That is the same bug.
When you call bbx_getsavefile it calls pressok() in response to the OK button,
then incorrectly creates the answer from the list rather than from the
edit box.
I was careless in testing that change, and it''s very clearly a bug. But
one that would have come out pretty quickly. The FilePicker dialog is
in a bit of flux, since it doesn't really look nice enough yet.

Perhaps the moral of the story is that bugs like these can be caught
early by increasing the warning level of the compiler when developing
the code.
 
J

James Kuyper

On 08/10/2013 03:11 PM, Dr Nick wrote:
....
This presumably makes this (typed into this message) abomination legal:

int func(int parm) {
if(parm > 0) {
printf("I'm positive about things\n");
return -parm;
}
printf("I'm not very positive you know\n");
}

int main(void) {
int x = 27;
...
code to get a value in q
...
if(q > 0)
x = func(y);
else
func(y);
/* x is 27 if q was negative or zero, otherwise it's -q */
....
}

I think you did an incomplete change of variable names while editing
that. Were 'q' and 'y' meant to be the same variable?
 
J

James Kuyper

On Sat, 10 Aug 2013 20:11:36 +0100, Dr Nick



No it doesn't. As Keith mentioned, the special rule is only for
main(). Falling off other functions is undefined. C89 has always
allowed you to fall off the end of main() to end the program (again,
that applies to no other function but main()), although the specific
value returned to the system is undefined. C99 strengthens that by
defining the value retuned in that case to be zero.

I think that, in Dr. Nick's example, 'q' and 'y' were meant to be the
same variable. If so, then his example does not depend upon the special
rule that applies to main(), but only upon the more general rule:

"If the } that terminates a function is reached, and the value of the
function call is used by the caller, the behavior is undefined." (6.9.1p12).

The '}' that terminates func() is reached only if y is not positive. The
value returned by func() is used only if q is positive. If I'm right
about 'q' and 'y' being intended to be the same variable, then 6.9.1p12
never applies.
 
M

Malcolm McLean

This presumably makes this (typed into this message) abomination legal:



int func(int parm) {

if(parm > 0) {

printf("I'm positive about things\n");

return -parm;

}

printf("I'm not very positive you know\n");

}



int main(void) {

int x = 27;

...

code to get a value in q

...

if(q > 0)

x = func(y);

else

func(y);

/* x is 27 if q was negative or zero, otherwise it's -q */

....

}

Yes.
In Baby X's case there's nothing like that going on. There a few functions
like bbx_radiobox_disable which takes an index of the radio button to
disable. So what's meant to happen if passed out-of-bounds? You don't
want an undefined behaviour crash, so the error is guarded. But there's
no error-handling system in Baby X. So it was going to return -1 to caller.
But I forgot to put that in, and also forgot to change the function to void.
It's kind of useless, because if anyone is careful enough to test the return, they'll be careful enough to know how many buttons they have.

But it is sloppy to declare a function as int then return nothing, and
it's a source of confusion. So it's a good catch by Wall.
 
J

James Kuyper

It was. Heaven knows how and why I did that.

That's what I think. Horrible, isn't it?

It may be horrible, but I can't imagine that it was unintentional. I see
no plausible reason for adding "and the value of the function call is
used by the caller", except for the explicit purpose of allowing code
like that. I've no idea why - the Rationale says nothing about that
decision.
 
J

James Kuyper

There are several reasons I can think of. You can't require the compiler
to issue a diagnostic for falling off the end of the function without an
explicit return, because in general, this would require solving the
halting problem. ...

We're talking about an exception to a rule that says the behavior is
undefined. No diagnostic is required if the main rule is violated, so
avoiding mandating a diagnostic can't be the reason for the exception.
 
K

Keith Thompson

Dr Nick said:
That's what I think. Horrible, isn't it?

What's horrible about it? Do you know of a language that makes it the
least bit difficult to write legal but horrible code? What change would
you make to C to fix this?

I don't particularly like the fact that a non-void function can fall off
the end without returning a value, but that rule was necessary to avoid
breaking pre-ANSI code written when void functions didn't exist.
 
K

Keith Thompson

Richard Damon said:
There are several reasons I can think of. You can't require the compiler
to issue a diagnostic for falling off the end of the function without an
explicit return, because in general, this would require solving the
halting problem. The compiler can probably find a lot of cases, and it
can be a good point to issue a warning for a possible falling off the
end without a return (but is really annoying if it complains about no
return at the end of the function which is not reachable, especially if
another compiler you use will then complain about the unreachable return).

You *could* require all possible execution paths for a non-void function
to lead to a return statement. C doesn't currently require that kind of
compile-time analysis, but it's not all that difficult to do, and I
think I've seen at least one language that requires it.

So this:

int func(void) {
if (cond) {
return 1;
}
}

would be a constraint violation, but this:

int func(void) {
if (cond) {
return 1;
}
else {
return 0;
}
}

would be valid, since the compiler can prove that the closing } is never
reached. The compiler wouldn't be required to analyze the evaluation of
"cond"; even if it's a constant expression, it would still check for
returns on all paths.

It's already entirely possible for a compiler to warn about such things.
You also have legacy code (before void) which might be declared to
return int, but actually just falls off the end. The legacy call point
won't be using the "non-existent" return value, so meets the requirement
of the clause. Without the clause, there would be a good chance of a lot
of "gratuitous" instilled into legacy programs. To my knowledge, no
machine of the period had a problem implementing this exception clause,
it became a "good" compromise.

I believe that's why the rule is there. Note that in pre-ANSI code,
functions that didn't return a meaningful value were commonly defined
with no explicit return type; the return type was "int" by default.
 
K

Keith Thompson

Richard Damon said:
The problem is, you can't, at least unless you provide a solution to the
halting problem. It is quite possible that a path that looks possible is
in fact not possible, perhaps even due to constraints that are not part
of this translation unit.

For the standard to make this a constraint violation, and thus require a
diagnostic, it would need to make some limitations to what the compiler
needs to look at to make some functions that might look like they may
fall of the end of the function, but actually can never do it, in error
and need an "unneeded" return at the end of the function.

I mentioned before that some languages require returns on all possible
execution paths. It was C# that I was thinking of. Quoting the C#
standard (ISO/IEC 23270:2006(e)):

When the return type of a method is not void, each return
statement in that method body shall specify an expression of
a type that is implicitly convertible to the return type. The
endpoint of the method body of a value-returning method shall
not be reachable. In other words, in a value-returning method,
control is not permitted to flow off the end of the method body.

Enforcing this doesn't require solving the halting problem, just tracing
all possible execution paths without making any assumptions about any
run-time conditions. It results in some false positives (cases where
the closing "}" is never actually reached, but the compiler can't prove
it given the rules laid out in the standard), but that's ok.

I don't suggest adding such a requirement to C, but it's not
particularly difficult to implement.
Since at the time of the standard was written, compilers did very little
analysis of the program, the rule would need to be very simple and
almost always require the dummy return at the end, wasting bytes of
memory (which were at time precious back then).

Yes, requiring that in 1989 or earlier might have been impractical; as I
said, it requires more program flow analysis than C compilers are
currently required to perform.
 
G

glen herrmannsfeldt

Keith Thompson said:
You *could* require all possible execution paths for a non-void function
to lead to a return statement. C doesn't currently require that kind of
compile-time analysis, but it's not all that difficult to do, and I
think I've seen at least one language that requires it.

I believe Java requires it. If not, the usual compilers do.

Even more, for non-void they require an expression on the return
and for void require no expression.
int func(void) {
if (cond) {
return 1;
}
}
would be a constraint violation, but this:

int func(void) {
if (cond) {
return 1;
}
else {
return 0;
}
}
would be valid, since the compiler can prove that the closing } is never
reached. The compiler wouldn't be required to analyze the evaluation of
"cond"; even if it's a constant expression, it would still check for
returns on all paths.

Somewhat similar to the way Java compilers check for variables that
are referenced before being given a value. If the compiler can't verify
that all paths will give a variable a value before it is used, it is
a compilation error.
It's already entirely possible for a compiler to warn about such things.

A problem Java doesn't (and didn't) have.

(snip)
I believe that's why the rule is there. Note that in pre-ANSI code,
functions that didn't return a meaningful value were commonly defined
with no explicit return type; the return type was "int" by default.

Wouldn't have been so hard to just return 0 if it got to the end.

It is more interesting for PL/I with the ENTRY statement.
PL/I will convert the return type used in a RETURN statement to the
type required by the appropriate ENTRY. Compilers I know don't do the
flow control, but provide for all possible conversions.

-- glen
 
K

Keith Thompson

Dr Nick said:
Although it can't quite be done perfectly. It's possible to imagine a
controlled loop with a conditional return in it where the conditions are
such that the return will always execute before the loop terminates (and
so the loop is effectively an endless loop although not coded as such).
In that case there doesn't need to be a return at the end of the
function but we can make the two clauses of ever increasing complexity
until the compiler has to give up.

It doesn't have to be done perfectly. It's entirely possible to define
a fairly straightforward set of rules, checkable by a compiler, so that
any function that satisfies the rules is guaranteed to return a result.
The rules don't have to rely on data flow analysis.

It's still possible for a function that always returns a result to
violate those rules. Such a function can be "fixed" by adding an
otherwise unnecessary return statement.

This is a solved problem; C# and Java already have such rules in place.

(And again, it would be impractical to add such rules to C, but C
compilers have always been permitted to warn about cases where the
compiler can't *prove* that a function returns properly.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
474,076
Messages
2,570,565
Members
47,200
Latest member
Vanessa98N

Latest Threads

Top