Sujet : Re: Fixing a sample from K&R book using cake static analyser
De : 643-408-1753 (at) *nospam* kylheku.com (Kaz Kylheku)
Groupes : comp.lang.cDate : 23. Jun 2024, 23:36:50
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <20240623151344.758@kylheku.com>
References : 1 2 3 4
User-Agent : slrn/pre1.0.4-9 (Linux)
On 2024-06-23, Ben Bacarisse <
ben@bsb.me.uk> wrote:
Kaz Kylheku <643-408-1753@kylheku.com> writes:
>
On Fri, 21 Jun 2024 09:45:21 -0300, Thiago Adams wrote:
>
Page 145, The C programming Language 2 Edition
/* install: put (name, defn) in hashtab */
struct nlist *install(char *name, char *defn)
{
struct nlist *np;
unsigned hashval;
if ((np = lookup(name)) == NULL) { /* not found */
np = (struct nlist *) malloc(sizeof(*np));
if (np == NULL || (np->name = strdup(name)) == NULL)
return NULL;
hashval = hash(name);
np->next = hashtab[hashval];
hashtab[hashval] = np;
} else /* already there */
free((void *) np->defn); /* free previous defn */
if ((np->defn = strdup(defn)) == NULL)
return NULL;
return np;
}
>
[snip attempts at tidying up...]
Watch and learn:
>
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
>
if (existing) {
return existing;
} else {
struct nlist *np = calloc(1, sizeof (struct nlist));
char *dupname = strdup(name);
char *dupdefn = strdup(defn);
unsigned hashval = hash(name);
>
if (np && dupname && dupdefn) {
np->name = dupname;
np->defn = dupdefn;
np->next = hashtab[hashval];
hashtab[hashval] = np;
return np;
}
>
free(dupdefn);
free(dupname);
free(np);
>
return NULL;
}
}
>
You've over-simplified. The function needs to replace the definition
with a strdup'd string (introduction another way to fail) when the name
is found by lookup.
I couldn't see that requirement at a glance from the way the other
code is written, but in my code I made it very clear what requirement
is being followed. All we need is to add the replacement logic
to the "existing" branch. Also, I regret not using sizeof *np:
struct nlist *install(char *name, char *defn)
{
struct nlist *existing = lookup(name);
if (existing) {
char *dupdefn = strdup(defn);
if (dupdefn) {
free(existing->defn);
existing->defn = dupdefn;
return existing;
}
free(dupdefn);
} else {
struct nlist *np = calloc(1, sizeof (struct nlist));
char *dupname = strdup(name);
char *dupdefn = strdup(defn);
unsigned hashval = hash(name);
if (np && dupname && dupdefn) {
np->name = dupname;
np->defn = dupdefn;
np->next = hashtab[hashval];
hashtab[hashval] = np;
return np;
}
free(dupdefn);
free(dupname);
free(np);
}
return NULL;
}
The free(dupdefn) in the first case is defensive. We know that
since there is only one resource being allocated, that's the one
that is null, so this need not be called. But if the code expands
to multiple resources, it could help remind the maintainer that
there is a cleanup block that needs to be touched.
Freeing the old definition before allocating the new one is
a mistake. Though we return null to the caller to indicate that
the ooperation failed, in doing so, we have damaged the existing
data store. There is now an entry with a null pointer definition,
that the program has to defend against.
Functions like this should try not to mutate anything existing if they
are not able to allocate the resources they need in order to succeed.
-- TXR Programming Language: http://nongnu.org/txrCygnal: Cygwin Native Application Library: http://kylheku.com/cygnalMastodon: @Kazinator@mstdn.ca