netcdf-c/ncgen/genchar.c
Dennis Heimbigner 751300ec59 Fix more memory leaks in netcdf-c library
This is a follow up to PR https://github.com/Unidata/netcdf-c/pull/1173

Sorry that it is so big, but leak suppression can be complex.

This PR fixes all remaining memory leaks -- as determined by
-fsanitize=address, and with the exceptions noted below.

Unfortunately. there remains a significant leak that I cannot
solve. It involves vlens, and it is unclear if the leak is
occurring in the netcdf-c library or the HDF5 library.

I have added a check_PROGRAM to the ncdump directory to show the
problem.  The program is called tst_vlen_demo.c To exercise it,
build the netcdf library with -fsanitize=address enabled. Then
go into ncdump and do a "make clean check".  This should build
tst_vlen_demo without actually executing it.  Then do the
command "./tst_vlen_demo" to see the output of the memory
checker.  Note the the lost malloc is deep in the HDF5 library
(in H5Tvlen.c).

I am temporarily working around this error in the following way.
1. I modified several test scripts to not execute known vlen tests
   that fail as described above.
2. Added an environment variable called NC_VLEN_NOTEST.
   If set, then those specific tests are suppressed.

This should mean that the --disable-utilities option to
./configure should not need to be set to get a memory leak clean
build.  This should allow for detection of any new leaks.

Note: I used an environment variable rather than a ./configure
option to control the vlen tests. This is because it is
temporary (I hope) and because it is a bit tricky for shell
scripts to access ./configure options.

Finally, as before, this only been tested with netcdf-4 and hdf5 support.
2018-11-15 10:00:38 -07:00

366 lines
12 KiB
C

/*********************************************************************
* Copyright 2009, UCAR/Unidata
* See netcdf/COPYRIGHT file for copying and redistribution conditions.
*********************************************************************/
#include "includes.h"
#include "odom.h"
/******************************************************/
/* Code for generating char variables etc; mostly
language independent */
/******************************************************/
/*Forward*/
static size_t gen_charconstant(NCConstant*, Bytebuffer*, int fillchar);
static int getfillchar(Datalist* fillsrc);
static void gen_leafchararray(Dimset*,int,Datalist*,Bytebuffer*, int);
static NCConstant* makeconst(int lineno, int len, char* str);
/*
Matching strings to char variables, attributes, and vlen
constants is challenging because it is desirable to mimic
the original ncgen(3). The "algorithms" used there have no
simple characterization (such as "abc" == {'a','b','c'}).
So, this rather ugly code is kept in this file
and a variety of heuristics are used to mimic ncgen3.
The core algorithm is as follows.
1. Assume we have a set of dimensions D1..Dn.
Any of the Di may be unlimited,
but it is assumed that the sizes of the Di are all known.
2. Given a sequence of string or character constants
C1..Cm, our goal is to construct a single string
whose length is the cross product of D1 thru Dn.
3. For purposes of this algorithm, character constants
are treated as strings of size 1.
4. Construct Dx = cross product of D1 thru D(n-1).
5. For each constant Ci, add fill characters, if necessary,
so that its length is a multiple of Dn.
6. Concatenate the modified C1..Cm to produce string S.
7. Add fill characters to S to make its size be a multiple of
Dn.
8. If S is longer than the Dx * Dn, then truncate
and generate a warning.
Two special cases:
1. character vlen: char(*) vlen_t.
For this case, we simply concat all the elements.
2. character attribute.
For this case, we simply concat all the elements.
*/
void
gen_chararray(Dimset* dimset, int dimindex, Datalist* data, Bytebuffer* charbuf, Datalist* fillsrc)
{
int fillchar = getfillchar(fillsrc);
int rank = rankfor(dimset);
int firstunlim = findunlimited(dimset,0);
int nunlim = countunlimited(dimset);
int nc3unlim = (nunlim <= 1 && (firstunlim == 0 || firstunlim == rank)); /* netcdf-3 case of at most 1 unlim in 0th dimension */
/* Case: netcdf3 case */
if(nc3unlim) {
gen_leafchararray(dimset,0,data,charbuf,fillchar);
return;
}
/* else generate should have done all the hard work */
gen_leafchararray(dimset,dimindex,data,charbuf,fillchar);
}
#if 0
/* Recursive helper */
static void
gen_chararrayr(Dimset* dimset, int dimindex,
Bytebuffer* databuf, Datalist* data, int fillchar,
int unitsize, int expectedsize)
{
int i;
size_t dimsize = declsizefor(dimset,dimindex);
int rank = dimset->ndims;
int firstunlim = findunlimited(dimset,0);
int lastunlimited = findlastunlimited(dimset);
int nextunlimited = findunlimited(dimset,dimindex+1);
int islastgroup = (lastunlimited == rank || dimindex >= lastunlimited || dimindex == rank-1);
Odometer* subodom = NULL;
ASSERT(rank > 0);
ASSERT((islastgroup));
/* we should be at a list of simple constants */
for(i=0;i<data->length;i++) {
NCConstant* c = datalistith(data,i);
ASSERT(!islistconst(c));
if(isstringable(c->nctype)) {
int j;
size_t constsize;
constsize = gen_charconstant(c,databuf,fillchar);
if(constsize % unitsize > 0) {
size_t padsize = unitsize - (constsize % unitsize);
for(j=0;j<padsize;j++) bbAppend(databuf,fillchar);
}
} else {
semwarn(constline(c),
"Encountered non-string and non-char constant in datalist; ignored");
}
}/* for */
/* If |databuf| > expectedsize, complain: exception is zero length */
if(bbLength(databuf) == 0 && expectedsize == 1) {
/* this is okay */
} else if(bbLength(databuf) > expectedsize) {
semwarn(data->data[0].lineno,"character data list too long; expected %d character constant, found %d: ",expectedsize,bbLength(databuf));
} else {
size_t bufsize = bbLength(databuf);
/* Pad to size dimproduct size */
if(bufsize % expectedsize > 0) {
size_t padsize = expectedsize - (bufsize % expectedsize);
for(i=0;i<padsize;i++) bbAppend(databuf,fillchar);
}
}
}
#endif
void
gen_charattr(Datalist* data, Bytebuffer* databuf)
{
gen_charseq(data,databuf);
}
void
gen_charseq(Datalist* data, Bytebuffer* databuf)
{
int i;
NCConstant* c;
ASSERT(bbLength(databuf) == 0);
for(i=0;i<data->length;i++) {
c = datalistith(data,i);
if(isstringable(c->nctype)) {
(void)gen_charconstant(c,databuf,NC_FILL_CHAR);
} else {
semerror(constline(c),
"Encountered non-string and non-char constant in datalist");
return;
}
}
}
static size_t
gen_charconstant(NCConstant* con, Bytebuffer* databuf, int fillchar)
{
/* Following cases should be consistent with isstringable */
size_t constsize = 1;
switch (con->nctype) {
case NC_CHAR:
bbAppend(databuf,con->value.charv);
break;
case NC_BYTE:
bbAppend(databuf,con->value.int8v);
break;
case NC_UBYTE:
bbAppend(databuf,con->value.uint8v);
break;
case NC_STRING:
constsize = con->value.stringv.len;
if(constsize > 0)
bbAppendn(databuf,con->value.stringv.stringv,
con->value.stringv.len);
bbNull(databuf);
break;
case NC_FILL:
bbAppend(databuf,fillchar);
break;
default:
PANIC("unexpected constant type");
}
return constsize;
}
static int
getfillchar(Datalist* fillsrc)
{
/* Determine the fill char */
int fillchar = 0;
if(fillsrc != NULL && fillsrc->length > 0) {
NCConstant* ccon = fillsrc->data[0];
if(ccon->nctype == NC_CHAR) {
fillchar = ccon->value.charv;
} else if(ccon->nctype == NC_STRING) {
if(ccon->value.stringv.len > 0) {
fillchar = ccon->value.stringv.stringv[0];
}
}
}
if(fillchar == 0) fillchar = NC_FILL_CHAR; /* default */
return fillchar;
}
/* I think there is a flaw in the ncgen manual
about handling something like this:
dimensions:
n = 8 ;
variables:
char cdata2(n) ;
data:
cdata2 = '\000','\001','\002','\177','\200','\201','\376','\377';
The rules would say that each of the 8 char constants must
be padded to length 8. I think this is only true if the dimension
is unlimited, and even then, I am not sure.
*/
static void
gen_leafchararray(Dimset* dimset, int dimindex, Datalist* data,
Bytebuffer* charbuf, int fillchar)
{
int i;
size_t expectedsize,xproduct,unitsize;
int rank = rankfor(dimset);
ASSERT(bbLength(charbuf) == 0);
ASSERT((findlastunlimited(dimset) == rank
|| findlastunlimited(dimset) == dimindex));
/*
There are a number of special cases that must be
considered, mostly driven by the need to keep consistent
with ncgen3. These cases are driven by the number of
dimensions, which dimensions are unlimited (if any), etc.
The general rule is based on the size of the last
dimension, we compute the required size (after padding)
of each string constant. Expected size is then the size
of concat of the string constants after padding.
There is another special case used for back compatibility with ncgen3.
In the datalist, all sequences of character constants (i.e. 'X')
are concatenated into a single string; the result, however is not
concatenated with any trailing or leading string (with double quotes).
*/
/* Rebuild the datalist to merge '0x' constants;
WARNING: this is tricky.
*/
{
int i,cccount = 0;
/* Do initial walk */
for(i=0;i<datalistlen(data);i++) {
NCConstant* con = datalistith(data,i);
if(consttype(con) == NC_CHAR || consttype(con) == NC_BYTE) {
cccount++;
}
}
if(cccount > 1) {
Bytebuffer* accum = bbNew();
int len = 0; /* >0 implies doing accum */
Datalist* newlist = builddatalist(datalistlen(data));
int lineno = 0;
NCConstant* con;
/* We are going to construct a single string constant for each
contiguous sequence of single char values.
Assume that the constants are all primitive types */
for(i=0;i<datalistlen(data);i++) {
con = datalistith(data,i);
if(consttype(con) == NC_CHAR || consttype(con) == NC_BYTE) {
if(len == 0) { /* Start an accumulation */
lineno = constline(con);
bbClear(accum);
}
bbAppend(accum,con->value.charv);
len++;
/* Discard this constant */
reclaimconstant(con);
} else {
if(len > 0) { /* End the accumulation */
bbNull(accum);
con = makeconst(lineno,len,bbContents(accum));
len = 0;
lineno = 0;
dlappend(newlist,con);
} else
dlappend(newlist,con);
}
}
/* deal with any unclosed strings */
if(len > 0) {
con = makeconst(lineno,len,bbContents(accum));
len = 0;
lineno = 0;
dlappend(newlist,con);
}
bbFree(accum);
/* Move the newlist sequence of constants into the old list */
efree(data->data);
data->data = newlist->data;
data->length = newlist->length;
data->alloc = newlist->alloc;
efree(newlist);
}
}
/* Compute crossproduct up to (but not including) the last dimension */
xproduct = crossproduct(dimset,dimindex,rank-1);
/* Start casing it out */
if(rank == 0) {
unitsize = 1;
expectedsize = (xproduct * unitsize);
} else if(rank == 1) {
unitsize = 1;
expectedsize = (xproduct * declsizefor(dimset,rank-1));
} else if(isunlimited(dimset,rank-1)) {/* last dimension is unlimited */
unitsize = 1;
expectedsize = (xproduct*declsizefor(dimset,rank-1));
} else { /* rank > 0 && last dim is not unlimited */
unitsize = declsizefor(dimset,rank-1);
expectedsize = (xproduct * unitsize);
}
for(i=0;i<data->length;i++) {
NCConstant* c = datalistith(data,i);
ASSERT(!islistconst(c));
if(isstringable(c->nctype)) {
int j;
size_t constsize;
constsize = gen_charconstant(c,charbuf,fillchar);
if(constsize == 0 || constsize % unitsize > 0) {
size_t padsize = unitsize - (constsize % unitsize);
for(j=0;j<padsize;j++) bbAppend(charbuf,fillchar);
}
} else {
semwarn(constline(c),"Encountered non-string and non-char constant in datalist; ignored");
}
}
/* If |databuf| > expectedsize, complain: exception is zero length */
if(bbLength(charbuf) == 0 && expectedsize == 1) {
/* this is okay */
} else if(bbLength(charbuf) > expectedsize) {
semwarn(data->data[0]->lineno,"character data list too long; expected %d character constant, found %d: ",expectedsize,bbLength(charbuf));
} else {
size_t bufsize = bbLength(charbuf);
/* Pad to size dimproduct size */
if(bufsize % expectedsize > 0) {
size_t padsize = expectedsize - (bufsize % expectedsize);
for(i=0;i<padsize;i++) bbAppend(charbuf,fillchar);
}
}
}
/* Create a new string constant */
static NCConstant*
makeconst(int lineno, int len, char* str)
{
NCConstant* con = nullconst();
con->nctype = NC_STRING;
con->lineno = lineno;
con->filled = 0;
con->value.stringv.len = len;
/* We cannot use strdup because str might have embedded nuls */
con->value.stringv.stringv = (char*)ecalloc(len+1);
memcpy((void*)con->value.stringv.stringv,(void*)str,len);
con->value.stringv.stringv[len] = '\0';
return con;
}