fix(data): reject partial and malformed map data
This commit is contained in:
7
dat.h
7
dat.h
@@ -87,6 +87,13 @@ struct Trie
|
|||||||
int cap;
|
int cap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TrieMiss,
|
||||||
|
TriePrefix,
|
||||||
|
TrieExact,
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct Hmap Hmap;
|
typedef struct Hmap Hmap;
|
||||||
struct Hmap
|
struct Hmap
|
||||||
{
|
{
|
||||||
|
|||||||
46
dict.c
46
dict.c
@@ -1,6 +1,19 @@
|
|||||||
|
#include <errno.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
|
static char*
|
||||||
|
readline(Biobuf *b, char *path)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
line = Brdstr(b, '\n', 1);
|
||||||
|
if(errno != 0)
|
||||||
|
die("can't read %s: %s", path, strerror(errno));
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dictlookup(Dictreq *req, Dictres *res)
|
dictlookup(Dictreq *req, Dictres *res)
|
||||||
{
|
{
|
||||||
@@ -33,7 +46,10 @@ dictlookup(Dictreq *req, Dictres *res)
|
|||||||
sp = p;
|
sp = p;
|
||||||
while(p < e && *p != ' ')
|
while(p < e && *p != ' ')
|
||||||
p++;
|
p++;
|
||||||
sinit(&tmp, sp, p - sp);
|
if(!sinit(&tmp, sp, p - sp)){
|
||||||
|
res->nkouho = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
if(req->lang == LangEMOJI || scmp(&tmp, &req->key) != 0)
|
||||||
res->kouho[res->nkouho++] = tmp;
|
res->kouho[res->nkouho++] = tmp;
|
||||||
if(p < e)
|
if(p < e)
|
||||||
@@ -63,45 +79,49 @@ dictopen(char *path)
|
|||||||
{
|
{
|
||||||
Hmap *h;
|
Hmap *h;
|
||||||
Biobuf *b;
|
Biobuf *b;
|
||||||
Str key;
|
Str key, tmp;
|
||||||
char *line, *tab, *p, *e;
|
char *line, *tab, *p, *e;
|
||||||
int len, lineno;
|
int len, lineno;
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
b = Bopen(path, OREAD);
|
||||||
if(b == nil)
|
if(b == nil)
|
||||||
die("can't open: %s", path);
|
die("can't open %s: %s", path, strerror(errno));
|
||||||
h = hmapalloc(4096);
|
h = hmapalloc(4096);
|
||||||
lineno = 0;
|
lineno = 0;
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
while((line = readline(b, path)) != nil){
|
||||||
lineno++;
|
lineno++;
|
||||||
len = strlen(line);
|
len = Blinelen(b);
|
||||||
|
if(memchr(line, '\0', len) != nil)
|
||||||
|
die("NUL in dictionary: %s:%d", path, lineno);
|
||||||
if(len > 0 && line[len-1] == '\r')
|
if(len > 0 && line[len-1] == '\r')
|
||||||
line[--len] = '\0';
|
line[--len] = '\0';
|
||||||
if(len == 0 || line[0] == ';'){
|
if(len == 0 || line[0] == ';'){
|
||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = memchr(line, '\t', len);
|
||||||
if(tab == nil || tab == line || tab >= line + len - 1 ||
|
if(tab == nil || tab == line || tab >= line + len - 1 ||
|
||||||
strchr(tab+1, '\t') != nil)
|
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
|
||||||
die("malformed dictionary: %s:%d", path, lineno);
|
die("malformed dictionary: %s:%d", path, lineno);
|
||||||
*tab = '\0';
|
*tab = '\0';
|
||||||
if(utflen(line) > Maxrunes)
|
if(!sinit(&key, line, tab-line))
|
||||||
die("dictionary key too long: %s:%d", path, lineno);
|
die("invalid or oversized dictionary key: %s:%d",
|
||||||
|
path, lineno);
|
||||||
for(p = tab+1; p < line+len; p = e+1){
|
for(p = tab+1; p < line+len; p = e+1){
|
||||||
e = memchr(p, ' ', line+len-p);
|
e = memchr(p, ' ', line+len-p);
|
||||||
if(e == nil)
|
if(e == nil)
|
||||||
e = line+len;
|
e = line+len;
|
||||||
if(utfnlen(p, e-p) > Maxrunes)
|
if(!sinit(&tmp, p, e-p))
|
||||||
die("dictionary candidate too long: %s:%d", path, lineno);
|
die("invalid or oversized dictionary candidate: %s:%d",
|
||||||
|
path, lineno);
|
||||||
if(e == line+len)
|
if(e == line+len)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sinit(&key, line, tab - line);
|
|
||||||
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
hmapset(&h, &key, tab+1, len - (tab - line) - 1);
|
||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
Bterm(b);
|
if(Bterm(b) < 0)
|
||||||
|
die("can't close %s: %s", path, strerror(errno));
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
fn.h
3
fn.h
@@ -1,6 +1,6 @@
|
|||||||
void die(char*, ...);
|
void die(char*, ...);
|
||||||
|
|
||||||
void sinit(Str*, char*, int);
|
int sinit(Str*, char*, int);
|
||||||
void sclear(Str*);
|
void sclear(Str*);
|
||||||
void sputr(Str*, Rune);
|
void sputr(Str*, Rune);
|
||||||
void spopr(Str*);
|
void spopr(Str*);
|
||||||
@@ -17,7 +17,6 @@ int mapget(Trie*, Str*, Str*);
|
|||||||
|
|
||||||
Trie* trieopen(char*);
|
Trie* trieopen(char*);
|
||||||
void trieclose(Trie*);
|
void trieclose(Trie*);
|
||||||
char* trieget(Trie*, char*, int, int*);
|
|
||||||
int trielookup(Trie*, char*, int, char**, int*);
|
int trielookup(Trie*, char*, int, char**, int*);
|
||||||
|
|
||||||
Lang* getlang(int);
|
Lang* getlang(int);
|
||||||
|
|||||||
31
hash.c
31
hash.c
@@ -1,3 +1,4 @@
|
|||||||
|
#include <limits.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
@@ -27,7 +28,9 @@ hmapalloc(int nbuckets)
|
|||||||
if(nbuckets < 1)
|
if(nbuckets < 1)
|
||||||
return nil;
|
return nil;
|
||||||
nsz = Tagsize;
|
nsz = Tagsize;
|
||||||
store = emalloc(sizeof(*h) + nbuckets * nsz);
|
if((ulong)nbuckets > (ULONG_MAX-sizeof(*h))/(ulong)nsz)
|
||||||
|
return nil;
|
||||||
|
store = emalloc(sizeof(*h) + (ulong)nbuckets * nsz);
|
||||||
h = store;
|
h = store;
|
||||||
h->nbs = nbuckets;
|
h->nbs = nbuckets;
|
||||||
h->nsz = nsz;
|
h->nsz = nsz;
|
||||||
@@ -56,14 +59,14 @@ hmapget(Hmap *h, Str *key)
|
|||||||
|
|
||||||
if(h == nil || key == nil || key->n < 0 || key->n > Maxrunes)
|
if(h == nil || key == nil || key->n < 0 || key->n > Maxrunes)
|
||||||
return nil;
|
return nil;
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
v = h->nodes + (hash(key) % h->nbs) * (ulong)h->nsz;
|
||||||
for(;;){
|
for(;;){
|
||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
if(n->filled && keycmp(n, key) == 0)
|
if(n->filled && keycmp(n, key) == 0)
|
||||||
return n;
|
return n;
|
||||||
if(n->next == 0)
|
if(n->next == 0)
|
||||||
break;
|
break;
|
||||||
v = h->nodes + n->next * h->nsz;
|
v = h->nodes + (ulong)n->next * h->nsz;
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@@ -76,7 +79,7 @@ sdup(Str *s, int *len)
|
|||||||
int n;
|
int n;
|
||||||
|
|
||||||
n = stoutf(s, buf, sizeof(buf));
|
n = stoutf(s, buf, sizeof(buf));
|
||||||
p = emalloc(n + 1);
|
p = emalloc((ulong)n + 1);
|
||||||
memmove(p, buf, n);
|
memmove(p, buf, n);
|
||||||
p[n] = '\0';
|
p[n] = '\0';
|
||||||
*len = n;
|
*len = n;
|
||||||
@@ -90,7 +93,7 @@ memdup(const char *src, int n)
|
|||||||
|
|
||||||
if(n == 0)
|
if(n == 0)
|
||||||
return nil;
|
return nil;
|
||||||
p = emalloc(n + 1);
|
p = emalloc((ulong)n + 1);
|
||||||
memmove(p, src, n);
|
memmove(p, src, n);
|
||||||
p[n] = '\0';
|
p[n] = '\0';
|
||||||
return p;
|
return p;
|
||||||
@@ -105,7 +108,7 @@ hmapfree(Hmap *h)
|
|||||||
if(h == nil)
|
if(h == nil)
|
||||||
return;
|
return;
|
||||||
for(i = 0; i < h->len; i++){
|
for(i = 0; i < h->len; i++){
|
||||||
n = (Hnode*)(h->nodes + i * h->nsz);
|
n = (Hnode*)(h->nodes + (ulong)i * h->nsz);
|
||||||
if(!n->filled)
|
if(!n->filled)
|
||||||
continue;
|
continue;
|
||||||
free(n->key);
|
free(n->key);
|
||||||
@@ -122,7 +125,7 @@ hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|||||||
uchar *v;
|
uchar *v;
|
||||||
Hmap *h;
|
Hmap *h;
|
||||||
int next;
|
int next;
|
||||||
vlong diff;
|
ulong diff;
|
||||||
|
|
||||||
if(store == nil || *store == nil || key == nil ||
|
if(store == nil || *store == nil || key == nil ||
|
||||||
key->n < 0 || key->n > Maxrunes || vlen < 0 ||
|
key->n < 0 || key->n > Maxrunes || vlen < 0 ||
|
||||||
@@ -130,7 +133,7 @@ hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|||||||
return;
|
return;
|
||||||
newval = memdup(val, vlen);
|
newval = memdup(val, vlen);
|
||||||
h = *store;
|
h = *store;
|
||||||
v = h->nodes + (hash(key) % h->nbs) * h->nsz;
|
v = h->nodes + (hash(key) % h->nbs) * (ulong)h->nsz;
|
||||||
for(;;){
|
for(;;){
|
||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
next = n->next;
|
next = n->next;
|
||||||
@@ -140,21 +143,25 @@ hmapset(Hmap **store, Str *key, const char *val, int vlen)
|
|||||||
goto replace;
|
goto replace;
|
||||||
if(next == 0)
|
if(next == 0)
|
||||||
break;
|
break;
|
||||||
v = h->nodes + next * h->nsz;
|
v = h->nodes + (ulong)next * h->nsz;
|
||||||
}
|
}
|
||||||
if(h->cap == h->len){
|
if(h->cap == h->len){
|
||||||
diff = v - h->nodes;
|
diff = v - h->nodes;
|
||||||
|
if(h->cap > INT_MAX/2 ||
|
||||||
|
(ulong)h->cap > (ULONG_MAX-sizeof(*h))/(2*(ulong)h->nsz))
|
||||||
|
die("hash table is too large");
|
||||||
h->cap *= 2;
|
h->cap *= 2;
|
||||||
*store = erealloc(*store, sizeof(*h) + h->cap * h->nsz);
|
*store = erealloc(*store,
|
||||||
|
sizeof(*h) + (ulong)h->cap * h->nsz);
|
||||||
h = *store;
|
h = *store;
|
||||||
h->nodes = (uchar*)*store + sizeof(*h);
|
h->nodes = (uchar*)*store + sizeof(*h);
|
||||||
v = h->nodes + diff;
|
v = h->nodes + diff;
|
||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
}
|
}
|
||||||
n->next = h->len;
|
n->next = h->len;
|
||||||
memset(h->nodes + h->len * h->nsz, 0, h->nsz);
|
memset(h->nodes + (ulong)h->len * h->nsz, 0, h->nsz);
|
||||||
h->len++;
|
h->len++;
|
||||||
v = h->nodes + n->next * h->nsz;
|
v = h->nodes + (ulong)n->next * h->nsz;
|
||||||
n = (Hnode*)v;
|
n = (Hnode*)v;
|
||||||
replace:
|
replace:
|
||||||
if(n->filled == 0){
|
if(n->filled == 0){
|
||||||
|
|||||||
25
str.c
25
str.c
@@ -1,24 +1,33 @@
|
|||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
void
|
int
|
||||||
sinit(Str *s, char *src, int n)
|
sinit(Str *s, char *src, int n)
|
||||||
{
|
{
|
||||||
|
Str tmp = {0};
|
||||||
|
Rune r;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
|
if(s == nil)
|
||||||
|
return 0;
|
||||||
s->n = 0;
|
s->n = 0;
|
||||||
if(n < 0 || (n > 0 && src == nil))
|
if(n < 0 || (n > 0 && src == nil))
|
||||||
return;
|
return 0;
|
||||||
while(n > 0 && s->n < Maxrunes){
|
while(n > 0){
|
||||||
|
if(tmp.n >= Maxrunes)
|
||||||
|
return 0;
|
||||||
if(!fullrune(src, n))
|
if(!fullrune(src, n))
|
||||||
break;
|
return 0;
|
||||||
len = chartorune(&s->r[s->n], src);
|
len = chartorune(&r, src);
|
||||||
if(len > n)
|
if(len > n || (r == Runeerror && len == 1) ||
|
||||||
break;
|
(r >= 0xd800 && r <= 0xdfff))
|
||||||
s->n++;
|
return 0;
|
||||||
|
tmp.r[tmp.n++] = r;
|
||||||
src += len;
|
src += len;
|
||||||
n -= len;
|
n -= len;
|
||||||
}
|
}
|
||||||
|
*s = tmp;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
28
strans.c
28
strans.c
@@ -406,17 +406,18 @@ static int
|
|||||||
maplookup(Trie *t, Str *key, Str *out)
|
maplookup(Trie *t, Str *key, Str *out)
|
||||||
{
|
{
|
||||||
char buf[Maxutf], *v;
|
char buf[Maxutf], *v;
|
||||||
int klen, vlen;
|
int klen, match, vlen;
|
||||||
|
|
||||||
if(key->n == 0)
|
if(out != nil)
|
||||||
|
sclear(out);
|
||||||
|
if(t == nil || key == nil || key->n == 0)
|
||||||
return 0;
|
return 0;
|
||||||
v = nil;
|
|
||||||
vlen = 0;
|
|
||||||
klen = stoutf(key, buf, sizeof(buf));
|
klen = stoutf(key, buf, sizeof(buf));
|
||||||
if(!trielookup(t, buf, klen, &v, &vlen))
|
match = trielookup(t, buf, klen, &v, &vlen);
|
||||||
|
if(match == TrieMiss)
|
||||||
|
return 0;
|
||||||
|
if(match == TrieExact && out != nil && !sinit(out, v, vlen))
|
||||||
return 0;
|
return 0;
|
||||||
if(out != nil && v != nil)
|
|
||||||
sinit(out, v, vlen);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,16 +1044,17 @@ int
|
|||||||
mapget(Trie *t, Str *key, Str *out)
|
mapget(Trie *t, Str *key, Str *out)
|
||||||
{
|
{
|
||||||
char buf[Maxutf], *v;
|
char buf[Maxutf], *v;
|
||||||
int klen, vlen;
|
int klen, match, vlen;
|
||||||
|
|
||||||
if(key->n == 0)
|
if(out != nil)
|
||||||
|
sclear(out);
|
||||||
|
if(key == nil || out == nil || key->n == 0)
|
||||||
return 0;
|
return 0;
|
||||||
klen = stoutf(key, buf, sizeof(buf));
|
klen = stoutf(key, buf, sizeof(buf));
|
||||||
v = trieget(t, buf, klen, &vlen);
|
match = trielookup(t, buf, klen, &v, &vlen);
|
||||||
if(v == nil)
|
if(match != TrieExact)
|
||||||
return 0;
|
return 0;
|
||||||
sinit(out, v, vlen);
|
return sinit(out, v, vlen);
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
void
|
void
|
||||||
dictionary_candidates(struct ct *t)
|
dictionary_candidates(struct ct *t)
|
||||||
{
|
{
|
||||||
|
static char malformed[] = { 'o', 'k', ' ', (char)0x80 };
|
||||||
char many[512], item[8];
|
char many[512], item[8];
|
||||||
char *p;
|
char *p;
|
||||||
Dictreq req;
|
Dictreq req;
|
||||||
@@ -53,6 +54,11 @@ dictionary_candidates(struct ct *t)
|
|||||||
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
CT_EQ_INT(t, 0, scmp(&req.pre, &res.key));
|
||||||
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
checkstr(t, "first capped candidate", "c00", &res.kouho[0]);
|
||||||
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
checkstr(t, "last capped candidate", "c31", &res.kouho[31]);
|
||||||
|
key = mkstr("malformed");
|
||||||
|
hmapset(&lang->dict, &key, malformed, sizeof malformed);
|
||||||
|
req.key = key;
|
||||||
|
dictlookup(&req, &res);
|
||||||
|
CT_EQ_INT(t, 0, res.nkouho);
|
||||||
cleanup:
|
cleanup:
|
||||||
hmapfree(lang->dict);
|
hmapfree(lang->dict);
|
||||||
lang->dict = saved;
|
lang->dict = saved;
|
||||||
|
|||||||
@@ -3,19 +3,48 @@
|
|||||||
void
|
void
|
||||||
str_init_utf8(struct ct *t)
|
str_init_utf8(struct ct *t)
|
||||||
{
|
{
|
||||||
|
static char badtail[] = { 'a', (char)0x80 };
|
||||||
|
static char incomplete[] = { (char)0xea, (char)0xb0 };
|
||||||
|
static char overlong[] = { (char)0xc0, (char)0xaf };
|
||||||
|
static char outofrange[] = {
|
||||||
|
(char)0xf4, (char)0x90, (char)0x80, (char)0x80,
|
||||||
|
};
|
||||||
|
static char surrogate[] = {
|
||||||
|
(char)0xed, (char)0xa0, (char)0x80,
|
||||||
|
};
|
||||||
|
static char unexpected[] = { (char)0x80 };
|
||||||
|
static const struct {
|
||||||
|
char *name;
|
||||||
|
char *src;
|
||||||
|
int n;
|
||||||
|
int ok;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "canonical Runeerror", "\xef\xbf\xbd", 3, 1, "\xef\xbf\xbd" },
|
||||||
|
{ "incomplete", incomplete, sizeof incomplete, 0, "" },
|
||||||
|
{ "unexpected continuation", unexpected, sizeof unexpected, 0, "" },
|
||||||
|
{ "overlong", overlong, sizeof overlong, 0, "" },
|
||||||
|
{ "surrogate", surrogate, sizeof surrogate, 0, "" },
|
||||||
|
{ "out of range", outofrange, sizeof outofrange, 0, "" },
|
||||||
|
{ "bad tail", badtail, sizeof badtail, 0, "" },
|
||||||
|
};
|
||||||
char full[Maxrunes+2];
|
char full[Maxrunes+2];
|
||||||
char incomplete[] = { (char)0xea, (char)0xb0 };
|
|
||||||
Str s;
|
Str s;
|
||||||
|
int i;
|
||||||
|
|
||||||
s = mkstr("A한😀");
|
s = mkstr("A한😀");
|
||||||
CT_EQ_INT(t, 3, s.n);
|
CT_EQ_INT(t, 3, s.n);
|
||||||
checkstr(t, "round trip", "A한😀", &s);
|
checkstr(t, "round trip", "A한😀", &s);
|
||||||
sinit(&s, incomplete, sizeof incomplete);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
CT_EQ_INT(t, 0, s.n);
|
memset(&s, 0xa5, sizeof s);
|
||||||
|
if(sinit(&s, cases[i].src, cases[i].n) != cases[i].ok)
|
||||||
|
CT_ERRORF(t, "%s: wrong validity", cases[i].name);
|
||||||
|
checkstr(t, cases[i].name, cases[i].want, &s);
|
||||||
|
}
|
||||||
memset(full, 'a', sizeof full);
|
memset(full, 'a', sizeof full);
|
||||||
full[sizeof full-1] = '\0';
|
full[sizeof full-1] = '\0';
|
||||||
sinit(&s, full, strlen(full));
|
CT_CHECK(t, !sinit(&s, full, strlen(full)));
|
||||||
CT_EQ_INT(t, Maxrunes, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -89,9 +118,9 @@ str_invalid_and_full_appends(struct ct *t)
|
|||||||
Str s;
|
Str s;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
sinit(&s, nil, 1);
|
CT_CHECK(t, !sinit(&s, nil, 1));
|
||||||
CT_EQ_INT(t, 0, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
sinit(&s, "x", -1);
|
CT_CHECK(t, !sinit(&s, "x", -1));
|
||||||
CT_EQ_INT(t, 0, s.n);
|
CT_EQ_INT(t, 0, s.n);
|
||||||
for(i = 0; i < Maxrunes; i++)
|
for(i = 0; i < Maxrunes; i++)
|
||||||
s.r[i] = 'a';
|
s.r[i] = 'a';
|
||||||
|
|||||||
@@ -3,29 +3,41 @@
|
|||||||
void
|
void
|
||||||
trie_exact_prefix_and_duplicate(struct ct *t)
|
trie_exact_prefix_and_duplicate(struct ct *t)
|
||||||
{
|
{
|
||||||
char *v, *sentinel;
|
static const struct {
|
||||||
|
char *key;
|
||||||
|
int klen;
|
||||||
|
int match;
|
||||||
|
char *want;
|
||||||
|
} cases[] = {
|
||||||
|
{ "a", 1, TrieExact, "alpha" },
|
||||||
|
{ "dupli", 5, TriePrefix, nil },
|
||||||
|
{ "duplicate", 9, TrieExact, "second" },
|
||||||
|
{ "missing", 7, TrieMiss, nil },
|
||||||
|
{ nil, 1, TrieMiss, nil },
|
||||||
|
{ "a", -1, TrieMiss, nil },
|
||||||
|
};
|
||||||
|
char *v;
|
||||||
Trie *trie;
|
Trie *trie;
|
||||||
int n;
|
int i, match, n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
trie = trieopen("data/trie.map");
|
||||||
v = trieget(trie, "a", 1, &n);
|
for(i = 0; i < nelem(cases); i++){
|
||||||
if(!CT_CHECK(t, v != nil))
|
v = "unchanged";
|
||||||
goto cleanup;
|
n = 77;
|
||||||
CT_EQ_INT(t, 5, n);
|
match = trielookup(trie, cases[i].key, cases[i].klen, &v, &n);
|
||||||
CT_EQ_MEM(t, "alpha", v, n);
|
if(match != cases[i].match){
|
||||||
sentinel = "unchanged";
|
CT_ERRORF(t, "case %d: want match %d, got %d",
|
||||||
v = sentinel;
|
i, cases[i].match, match);
|
||||||
n = 77;
|
continue;
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, &v, &n));
|
}
|
||||||
CT_EQ_PTR(t, sentinel, v);
|
if(cases[i].want == nil){
|
||||||
CT_EQ_INT(t, 77, n);
|
CT_EQ_PTR(t, nil, v);
|
||||||
v = trieget(trie, "duplicate", 9, &n);
|
CT_EQ_INT(t, 0, n);
|
||||||
if(!CT_CHECK(t, v != nil))
|
}else{
|
||||||
goto cleanup;
|
CT_EQ_INT(t, strlen(cases[i].want), n);
|
||||||
CT_EQ_INT(t, 6, n);
|
CT_EQ_MEM(t, cases[i].want, v, n);
|
||||||
CT_EQ_MEM(t, "second", v, n);
|
}
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "missing", 7, &n));
|
}
|
||||||
cleanup:
|
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,18 +49,15 @@ trie_optional_outputs_and_invalid_lengths(struct ct *t)
|
|||||||
int n;
|
int n;
|
||||||
|
|
||||||
trie = trieopen("data/trie.map");
|
trie = trieopen("data/trie.map");
|
||||||
CT_CHECK(t, trieget(trie, "a", 1, nil) != nil);
|
CT_EQ_INT(t, TrieExact, trielookup(trie, "a", 1, &v, nil));
|
||||||
v = nil;
|
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, &v, nil));
|
|
||||||
CT_CHECK(t, v != nil);
|
CT_CHECK(t, v != nil);
|
||||||
n = -1;
|
CT_EQ_INT(t, TrieExact, trielookup(trie, "a", 1, nil, &n));
|
||||||
CT_CHECK(t, trielookup(trie, "a", 1, nil, &n));
|
|
||||||
CT_EQ_INT(t, 5, n);
|
CT_EQ_INT(t, 5, n);
|
||||||
CT_CHECK(t, trielookup(trie, "dupli", 5, nil, nil));
|
CT_EQ_INT(t, TriePrefix,
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, nil, 1, &n));
|
trielookup(trie, "dupli", 5, nil, nil));
|
||||||
CT_EQ_PTR(t, nil, trieget(trie, "a", -1, &n));
|
CT_EQ_INT(t, TriePrefix, trielookup(trie, nil, 0, &v, &n));
|
||||||
CT_CHECK(t, !trielookup(trie, nil, 1, &v, &n));
|
CT_EQ_PTR(t, nil, v);
|
||||||
CT_CHECK(t, !trielookup(trie, "a", -1, &v, &n));
|
CT_EQ_INT(t, 0, n);
|
||||||
trieclose(trie);
|
trieclose(trie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
87
trie.c
87
trie.c
@@ -1,14 +1,33 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
#include "dat.h"
|
#include "dat.h"
|
||||||
#include "fn.h"
|
#include "fn.h"
|
||||||
|
|
||||||
|
static char*
|
||||||
|
readline(Biobuf *b, char *path)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
line = Brdstr(b, '\n', 1);
|
||||||
|
if(errno != 0)
|
||||||
|
die("can't read %s: %s", path, strerror(errno));
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
newnode(Trie *t)
|
newnode(Trie *t)
|
||||||
{
|
{
|
||||||
|
int cap;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if(t->n >= t->cap){
|
if(t->n >= t->cap){
|
||||||
t->cap *= 2;
|
if(t->cap > INT_MAX/2 ||
|
||||||
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
|
(ulong)t->cap > ULONG_MAX/(2*sizeof(Tnode)))
|
||||||
|
die("map is too large");
|
||||||
|
cap = t->cap * 2;
|
||||||
|
t->nodes = erealloc(t->nodes, (ulong)cap * sizeof(Tnode));
|
||||||
|
t->cap = cap;
|
||||||
}
|
}
|
||||||
i = t->n++;
|
i = t->n++;
|
||||||
memset(&t->nodes[i], 0, sizeof(Tnode));
|
memset(&t->nodes[i], 0, sizeof(Tnode));
|
||||||
@@ -54,7 +73,7 @@ insert(Trie *t, char *key, int klen, char *val, int vlen)
|
|||||||
ni = ci;
|
ni = ci;
|
||||||
}
|
}
|
||||||
free(t->nodes[ni].val);
|
free(t->nodes[ni].val);
|
||||||
t->nodes[ni].val = emalloc(vlen + 1);
|
t->nodes[ni].val = emalloc((ulong)vlen + 1);
|
||||||
memmove(t->nodes[ni].val, val, vlen);
|
memmove(t->nodes[ni].val, val, vlen);
|
||||||
t->nodes[ni].val[vlen] = '\0';
|
t->nodes[ni].val[vlen] = '\0';
|
||||||
t->nodes[ni].vlen = vlen;
|
t->nodes[ni].vlen = vlen;
|
||||||
@@ -66,41 +85,45 @@ trieopen(char *path)
|
|||||||
Trie *t;
|
Trie *t;
|
||||||
Biobuf *b;
|
Biobuf *b;
|
||||||
char *line, *tab, *key, *val;
|
char *line, *tab, *key, *val;
|
||||||
|
Str s;
|
||||||
int klen, vlen;
|
int klen, vlen;
|
||||||
|
|
||||||
b = Bopen(path, OREAD);
|
b = Bopen(path, OREAD);
|
||||||
if(b == nil)
|
if(b == nil)
|
||||||
die("can't open: %s", path);
|
die("can't open %s: %s", path, strerror(errno));
|
||||||
t = emalloc(sizeof(*t));
|
t = emalloc(sizeof(*t));
|
||||||
t->cap = 1024;
|
t->cap = 1024;
|
||||||
t->nodes = emalloc(t->cap * sizeof(Tnode));
|
t->nodes = emalloc((ulong)t->cap * sizeof(Tnode));
|
||||||
t->n = 0;
|
t->n = 0;
|
||||||
t->root = newnode(t);
|
t->root = newnode(t);
|
||||||
while((line = Brdstr(b, '\n', 1)) != nil){
|
while((line = readline(b, path)) != nil){
|
||||||
vlen = strlen(line);
|
vlen = Blinelen(b);
|
||||||
|
if(memchr(line, '\0', vlen) != nil)
|
||||||
|
die("NUL in map: %s", path);
|
||||||
if(vlen > 0 && line[vlen-1] == '\r')
|
if(vlen > 0 && line[vlen-1] == '\r')
|
||||||
line[--vlen] = '\0';
|
line[--vlen] = '\0';
|
||||||
if(line[0] == '\0' || line[0] == ';'){
|
if(line[0] == '\0' || line[0] == ';'){
|
||||||
free(line);
|
free(line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tab = strchr(line, '\t');
|
tab = memchr(line, '\t', vlen);
|
||||||
if(tab == nil || tab == line || tab[1] == '\0' ||
|
if(tab == nil || tab == line || tab == line+vlen-1 ||
|
||||||
strchr(tab+1, '\t') != nil)
|
memchr(tab+1, '\t', line+vlen-(tab+1)) != nil)
|
||||||
die("malformed map: %s", path);
|
die("malformed map: %s", path);
|
||||||
*tab = '\0';
|
*tab = '\0';
|
||||||
key = line;
|
key = line;
|
||||||
klen = tab - line;
|
klen = tab - line;
|
||||||
if(utflen(key) > Maxrunes)
|
if(!sinit(&s, key, klen))
|
||||||
die("map key too long: %s", path);
|
die("invalid or oversized map key: %s", path);
|
||||||
val = tab + 1;
|
val = tab + 1;
|
||||||
vlen = strlen(val);
|
vlen = line + vlen - val;
|
||||||
if(utflen(val) > Maxrunes)
|
if(!sinit(&s, val, vlen))
|
||||||
die("map value too long: %s", path);
|
die("invalid or oversized map value: %s", path);
|
||||||
insert(t, key, klen, val, vlen);
|
insert(t, key, klen, val, vlen);
|
||||||
free(line);
|
free(line);
|
||||||
}
|
}
|
||||||
Bterm(b);
|
if(Bterm(b) < 0)
|
||||||
|
die("can't close %s: %s", path, strerror(errno));
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,46 +140,30 @@ trieclose(Trie *t)
|
|||||||
free(t);
|
free(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
char*
|
|
||||||
trieget(Trie *t, char *key, int klen, int *vlen)
|
|
||||||
{
|
|
||||||
int ni;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
|
||||||
return nil;
|
|
||||||
ni = t->root;
|
|
||||||
for(i = 0; i < klen; i++){
|
|
||||||
ni = find(t, ni, key[i]);
|
|
||||||
if(ni < 0)
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if(t->nodes[ni].val == nil)
|
|
||||||
return nil;
|
|
||||||
if(vlen != nil)
|
|
||||||
*vlen = t->nodes[ni].vlen;
|
|
||||||
return t->nodes[ni].val;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
|
trielookup(Trie *t, char *key, int klen, char **val, int *vlen)
|
||||||
{
|
{
|
||||||
int ni;
|
int ni;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if(val != nil)
|
||||||
|
*val = nil;
|
||||||
|
if(vlen != nil)
|
||||||
|
*vlen = 0;
|
||||||
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
if(t == nil || klen < 0 || (klen > 0 && key == nil))
|
||||||
return 0;
|
return TrieMiss;
|
||||||
ni = t->root;
|
ni = t->root;
|
||||||
for(i = 0; i < klen; i++){
|
for(i = 0; i < klen; i++){
|
||||||
ni = find(t, ni, key[i]);
|
ni = find(t, ni, key[i]);
|
||||||
if(ni < 0)
|
if(ni < 0)
|
||||||
return 0;
|
return TrieMiss;
|
||||||
}
|
}
|
||||||
if(t->nodes[ni].val != nil){
|
if(t->nodes[ni].val != nil){
|
||||||
if(val != nil)
|
if(val != nil)
|
||||||
*val = t->nodes[ni].val;
|
*val = t->nodes[ni].val;
|
||||||
if(vlen != nil)
|
if(vlen != nil)
|
||||||
*vlen = t->nodes[ni].vlen;
|
*vlen = t->nodes[ni].vlen;
|
||||||
|
return TrieExact;
|
||||||
}
|
}
|
||||||
return 1;
|
return TriePrefix;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user