mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-01-22 04:18:01 +00:00
b72bb6f1f4
It was freeing the wrong object when there was an error attaching an object to its parent! Don't crash when printing null (partially read) hash values.
586 lines
12 KiB
C
586 lines
12 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include "jsonpull.h"
|
|
|
|
#include "debug.h"
|
|
|
|
json_pull *json_begin(int (*read)(struct json_pull *), int (*peek)(struct json_pull *), void *source) {
|
|
json_pull *j = malloc(sizeof(json_pull));
|
|
|
|
j->error = NULL;
|
|
j->line = 1;
|
|
j->container = NULL;
|
|
j->root = NULL;
|
|
|
|
j->read = read;
|
|
j->peek = peek;
|
|
j->source = source;
|
|
|
|
return j;
|
|
}
|
|
|
|
static int read_file(json_pull *j) {
|
|
return fgetc(j->source);
|
|
}
|
|
|
|
static int peek_file(json_pull *j) {
|
|
int c = getc(j->source);
|
|
ungetc(c, j->source);
|
|
return c;
|
|
}
|
|
|
|
json_pull *json_begin_file(FILE *f) {
|
|
return json_begin(read_file, peek_file, f);
|
|
}
|
|
|
|
static int read_string(json_pull *j) {
|
|
char *cp = j->source;
|
|
if (*cp == '\0') {
|
|
return EOF;
|
|
}
|
|
int c = (unsigned char) *cp;
|
|
j->source = cp + 1;
|
|
return c;
|
|
}
|
|
|
|
static int peek_string(json_pull *p) {
|
|
char *cp = p->source;
|
|
if (*cp == '\0') {
|
|
return EOF;
|
|
}
|
|
return (unsigned char) *cp;
|
|
}
|
|
|
|
json_pull *json_begin_string(char *s) {
|
|
return json_begin(read_string, peek_string, s);
|
|
}
|
|
|
|
void json_end(json_pull *p) {
|
|
free(p);
|
|
}
|
|
|
|
static int read_wrap(json_pull *j) {
|
|
int c = j->read(j);
|
|
|
|
if (c == '\n') {
|
|
j->line++;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
#define SIZE_FOR(i) (((i) + 31) & ~31)
|
|
|
|
static json_object *fabricate_object(json_object *parent, json_type type) {
|
|
json_object *o = malloc(sizeof(struct json_object));
|
|
o->type = type;
|
|
o->parent = parent;
|
|
o->array = NULL;
|
|
o->keys = NULL;
|
|
o->values = NULL;
|
|
o->length = 0;
|
|
return o;
|
|
}
|
|
|
|
static json_object *add_object(json_pull *j, json_type type) {
|
|
json_object *c = j->container;
|
|
json_object *o = fabricate_object(c, type);
|
|
|
|
if (c != NULL) {
|
|
if (c->type == JSON_ARRAY) {
|
|
if (c->expect == JSON_ITEM) {
|
|
if (SIZE_FOR(c->length + 1) != SIZE_FOR(c->length)) {
|
|
c->array = realloc(c->array, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
}
|
|
|
|
c->array[c->length++] = o;
|
|
c->expect = JSON_COMMA;
|
|
} else {
|
|
j->error = "Expected a comma, not a list item";
|
|
free(o);
|
|
return NULL;
|
|
}
|
|
} else if (c->type == JSON_HASH) {
|
|
if (c->expect == JSON_VALUE) {
|
|
c->values[c->length - 1] = o;
|
|
c->expect = JSON_COMMA;
|
|
} else if (c->expect == JSON_KEY) {
|
|
if (type != JSON_STRING) {
|
|
j->error = "Hash key is not a string";
|
|
free(o);
|
|
return NULL;
|
|
}
|
|
|
|
if (SIZE_FOR(c->length + 1) != SIZE_FOR(c->length)) {
|
|
c->keys = realloc(c->keys, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
c->values = realloc(c->values, SIZE_FOR(c->length + 1) * sizeof(json_object *));
|
|
}
|
|
|
|
c->keys[c->length] = o;
|
|
c->values[c->length] = NULL;
|
|
c->length++;
|
|
c->expect = JSON_COLON;
|
|
} else {
|
|
j->error = "Expected a comma or colon";
|
|
free(o);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
j->root = o;
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
json_object *json_hash_get(json_object *o, char *s) {
|
|
if (o == NULL || o->type != JSON_HASH) {
|
|
return NULL;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < o->length; i++) {
|
|
if (o->keys[i] != NULL && o->keys[i]->type == JSON_STRING) {
|
|
if (strcmp(o->keys[i]->string, s) == 0) {
|
|
return o->values[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct string {
|
|
char *buf;
|
|
int n;
|
|
int nalloc;
|
|
};
|
|
|
|
static void string_init(struct string *s) {
|
|
s->nalloc = 500;
|
|
s->buf = malloc(s->nalloc);
|
|
s->n = 0;
|
|
s->buf[0] = '\0';
|
|
}
|
|
|
|
static void string_append(struct string *s, char c) {
|
|
if (s->n + 2 >= s->nalloc) {
|
|
s->nalloc += 500;
|
|
s->buf = realloc(s->buf, s->nalloc);
|
|
}
|
|
|
|
s->buf[s->n++] = c;
|
|
s->buf[s->n] = '\0';
|
|
}
|
|
|
|
static void string_free(struct string *s) {
|
|
free(s->buf);
|
|
}
|
|
|
|
json_object *json_read_separators(json_pull *j, json_separator_callback cb, void *state) {
|
|
int c;
|
|
again:
|
|
/////////////////////////// Whitespace
|
|
|
|
do {
|
|
c = read_wrap(j);
|
|
if (c == EOF) {
|
|
if (j->container != NULL) {
|
|
j->error = "Reached EOF without all containers being closed";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
} while (c == ' ' || c == '\t' || c == '\r' || c == '\n');
|
|
|
|
/////////////////////////// Arrays
|
|
|
|
if (c == '[') {
|
|
json_object *o = add_object(j, JSON_ARRAY);
|
|
if (o == NULL) {
|
|
return NULL;
|
|
}
|
|
j->container = o;
|
|
j->container->expect = JSON_ITEM;
|
|
|
|
if (cb != NULL) {
|
|
cb(JSON_ARRAY, j, state);
|
|
}
|
|
|
|
goto again;
|
|
} else if (c == ']') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found ] at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type != JSON_ARRAY) {
|
|
j->error = "Found ] not in an array";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
if (! (j->container->expect == JSON_ITEM && j->container->length == 0)) {
|
|
j->error = "Found ] without final element";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
json_object *ret = j->container;
|
|
j->container = ret->parent;
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////// Hashes
|
|
|
|
if (c == '{') {
|
|
json_object *o = add_object(j, JSON_HASH);
|
|
if (o == NULL) {
|
|
return NULL;
|
|
}
|
|
j->container = o;
|
|
j->container->expect = JSON_KEY;
|
|
|
|
if (cb != NULL) {
|
|
cb(JSON_HASH, j, state);
|
|
}
|
|
|
|
goto again;
|
|
} else if (c == '}') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found } at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type != JSON_HASH) {
|
|
j->error = "Found } not in a hash";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
if (! (j->container->expect == JSON_KEY && j->container->length == 0)) {
|
|
j->error = "Found } without final element";
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
json_object *ret = j->container;
|
|
j->container = ret->parent;
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////// Null
|
|
|
|
if (c == 'n') {
|
|
if (read_wrap(j) != 'u' || read_wrap(j) != 'l' || read_wrap(j) != 'l') {
|
|
j->error = "Found misspelling of null";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_NULL);
|
|
}
|
|
|
|
/////////////////////////// True
|
|
|
|
if (c == 't') {
|
|
if (read_wrap(j) != 'r' || read_wrap(j) != 'u' || read_wrap(j) != 'e') {
|
|
j->error = "Found misspelling of true";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_TRUE);
|
|
}
|
|
|
|
/////////////////////////// False
|
|
|
|
if (c == 'f') {
|
|
if (read_wrap(j) != 'a' || read_wrap(j) != 'l' || read_wrap(j) != 's' || read_wrap(j) != 'e') {
|
|
j->error = "Found misspelling of false";
|
|
return NULL;
|
|
}
|
|
|
|
return add_object(j, JSON_FALSE);
|
|
}
|
|
|
|
/////////////////////////// Comma
|
|
|
|
if (c == ',') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found comma at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COMMA) {
|
|
j->error = "Found unexpected comma";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->type == JSON_HASH) {
|
|
j->container->expect = JSON_KEY;
|
|
} else {
|
|
j->container->expect = JSON_ITEM;
|
|
}
|
|
|
|
if (cb != NULL) {
|
|
cb(JSON_COMMA, j, state);
|
|
}
|
|
|
|
goto again;
|
|
}
|
|
|
|
/////////////////////////// Colon
|
|
|
|
if (c == ':') {
|
|
if (j->container == NULL) {
|
|
j->error = "Found colon at top level";
|
|
return NULL;
|
|
}
|
|
|
|
if (j->container->expect != JSON_COLON) {
|
|
j->error = "Found unexpected colon";
|
|
return NULL;
|
|
}
|
|
|
|
j->container->expect = JSON_VALUE;
|
|
|
|
if (cb != NULL) {
|
|
cb(JSON_COLON, j, state);
|
|
}
|
|
|
|
goto again;
|
|
}
|
|
|
|
/////////////////////////// Numbers
|
|
|
|
if (c == '-' || (c >= '0' && c <= '9')) {
|
|
struct string val;
|
|
string_init(&val);
|
|
|
|
if (c == '-') {
|
|
string_append(&val, c);
|
|
c = read_wrap(j);
|
|
}
|
|
|
|
if (c == '0') {
|
|
string_append(&val, c);
|
|
} else if (c >= '1' && c <= '9') {
|
|
string_append(&val, c);
|
|
c = j->peek(j);
|
|
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, read_wrap(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
if (j->peek(j) == '.') {
|
|
string_append(&val, read_wrap(j));
|
|
|
|
c = j->peek(j);
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, read_wrap(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
c = j->peek(j);
|
|
if (c == 'e' || c == 'E') {
|
|
string_append(&val, read_wrap(j));
|
|
|
|
c = j->peek(j);
|
|
if (c == '+' || c == '-') {
|
|
string_append(&val, read_wrap(j));
|
|
}
|
|
|
|
c = j->peek(j);
|
|
if (c < '0' || c > '9') {
|
|
j->error = "Exponent without digits";
|
|
return NULL;
|
|
}
|
|
while (c >= '0' && c <= '9') {
|
|
string_append(&val, read_wrap(j));
|
|
c = j->peek(j);
|
|
}
|
|
}
|
|
|
|
json_object *n = add_object(j, JSON_NUMBER);
|
|
if (n != NULL) {
|
|
n->number = atof(val.buf);
|
|
}
|
|
string_free(&val);
|
|
return n;
|
|
}
|
|
|
|
/////////////////////////// Strings
|
|
|
|
if (c == '"') {
|
|
struct string val;
|
|
string_init(&val);
|
|
|
|
while ((c = read_wrap(j)) != EOF) {
|
|
if (c == '"') {
|
|
break;
|
|
} else if (c == '\\') {
|
|
c = read_wrap(j);
|
|
|
|
if (c == '"') {
|
|
string_append(&val, '"');
|
|
} else if (c == '\\') {
|
|
string_append(&val, '\\');
|
|
} else if (c == '/') {
|
|
string_append(&val, '/');
|
|
} else if (c == 'b') {
|
|
string_append(&val, '\b');
|
|
} else if (c == 'f') {
|
|
string_append(&val, '\f');
|
|
} else if (c == 'n') {
|
|
string_append(&val, '\n');
|
|
} else if (c == 'r') {
|
|
string_append(&val, '\r');
|
|
} else if (c == 't') {
|
|
string_append(&val, '\t');
|
|
} else if (c == 'u') {
|
|
char hex[5] = "aaaa";
|
|
int i;
|
|
for (i = 0; i < 4; i++) {
|
|
hex[i] = read_wrap(j);
|
|
}
|
|
unsigned long ch = strtoul(hex, NULL, 16);
|
|
if (ch <= 0x7F) {
|
|
string_append(&val, ch);
|
|
} else if (ch <= 0x7FF) {
|
|
string_append(&val, 0xC0 | (ch >> 6));
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
} else {
|
|
string_append(&val, 0xE0 | (ch >> 12));
|
|
string_append(&val, 0x80 | ((ch >> 6) & 0x3F));
|
|
string_append(&val, 0x80 | (ch & 0x3F));
|
|
}
|
|
} else {
|
|
j->error = "Found backslash followed by unknown character";
|
|
return NULL;
|
|
}
|
|
} else {
|
|
string_append(&val, c);
|
|
}
|
|
}
|
|
|
|
json_object *s = add_object(j, JSON_STRING);
|
|
if (s != NULL) {
|
|
s->string = val.buf;
|
|
s->length = val.n;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
j->error = "Found unexpected character";
|
|
return NULL;
|
|
}
|
|
|
|
json_object *json_read(json_pull *j) {
|
|
return json_read_separators(j, NULL, NULL);
|
|
}
|
|
|
|
json_object *json_read_tree(json_pull *p) {
|
|
json_object *j;
|
|
|
|
while ((j = json_read(p)) != NULL) {
|
|
if (j->parent == NULL) {
|
|
return j;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void json_free(json_object *o) {
|
|
int i;
|
|
|
|
if (o == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Free any data linked from here
|
|
|
|
if (o->type == JSON_ARRAY) {
|
|
json_object **a = o->array;
|
|
int n = o->length;
|
|
|
|
o->array = NULL;
|
|
o->length = 0;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
json_free(a[i]);
|
|
}
|
|
|
|
free(a);
|
|
} else if (o->type == JSON_HASH) {
|
|
json_object **k = o->keys;
|
|
json_object **v = o->values;
|
|
int n = o->length;
|
|
|
|
o->keys = NULL;
|
|
o->values = NULL;
|
|
o->length = 0;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
json_free(k[i]);
|
|
json_free(v[i]);
|
|
}
|
|
|
|
free(k);
|
|
free(v);
|
|
} else if (o->type == JSON_STRING) {
|
|
free(o->string);
|
|
}
|
|
|
|
// Expunge references to this as an array element
|
|
// or a hash key or value.
|
|
|
|
if (o->parent != NULL) {
|
|
if (o->parent->type == JSON_ARRAY) {
|
|
for (i = 0; i < o->parent->length; i++) {
|
|
if (o->parent->array[i] == o) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < o->parent->length) {
|
|
memmove(o->parent->array + i, o->parent->array + i + 1, o->parent->length - i - 1);
|
|
o->parent->length--;
|
|
}
|
|
}
|
|
|
|
if (o->parent->type == JSON_HASH) {
|
|
for (i = 0; i < o->parent->length; i++) {
|
|
if (o->parent->keys[i] == o) {
|
|
o->parent->keys[i] = fabricate_object(o->parent, JSON_NULL);
|
|
break;
|
|
}
|
|
if (o->parent->values[i] == o) {
|
|
o->parent->values[i] = fabricate_object(o->parent, JSON_NULL);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i < o->parent->length) {
|
|
if (o->parent->keys[i] != NULL && o->parent->keys[i]->type == JSON_NULL) {
|
|
if (o->parent->values[i] != NULL && o->parent->values[i]->type == JSON_NULL) {
|
|
free(o->parent->keys[i]);
|
|
free(o->parent->values[i]);
|
|
|
|
memmove(o->parent->keys + i, o->parent->keys + i + 1, o->parent->length - i - 1);
|
|
memmove(o->parent->values + i, o->parent->values + i + 1, o->parent->length - i - 1);
|
|
o->parent->length--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(o);
|
|
}
|