Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A new attempt at location information for decoded JSON elements #662

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ if (NOT JANSSON_WITHOUT_TESTS)
test_load
test_load_callback
test_loadb
test_location
test_number
test_object
test_pack
Expand Down
39 changes: 39 additions & 0 deletions doc/apiref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,12 @@ macros can be ORed together to obtain *flags*.

.. versionadded:: 2.6

``JSON_STORE_LOCATION``
Add location information to decoded :type:`json_t` objects. See
:ref:`apiref-location-information` for details.

.. versionadded:: 2.15

Each function also takes an optional :type:`json_error_t` parameter
that is filled with error information if decoding fails. It's also
updated on success; the number of bytes of input read is written to
Expand Down Expand Up @@ -2062,3 +2068,36 @@ And usage::

json_decref(obj);
}

.. _apiref-location-information:

Location Information
====================

Jansson supports storing decoded objects' locations in input for better
reporting of semantic errors in applications. Since this comes with a certain
overhead, location information is stored only if ``JSON_STORE_LOCATION`` flag
was specified during decoding.

.. function:: int json_get_location(json_t *json, int *line, int *column, int *position, int *length);

Retrieve location of *json* writing it to memory locations pointed to by
*line*, *column*, *position* and *length* if not *NULL*. Returns 0 on success
or -1 if no location information is available for *json*.

``line``
The line number on which the object occurred.

``column``
The column on which the object occurred. Note that this is the *character
column*, not the byte column, i.e. a multibyte UTF-8 character counts as one
column.

``position``
The position in bytes from the start of the input. This is useful for
debugging Unicode encoding problems.

``length``
The length of the object in bytes. For arrays and objects, length is always
1. For all other types, the value resembles the actual length as it appears
in input. Note that for strings, this includes the quotes.
1 change: 1 addition & 0 deletions src/jansson.def
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,5 @@ EXPORTS
json_get_alloc_funcs
jansson_version_str
jansson_version_cmp
json_get_location

5 changes: 5 additions & 0 deletions src/jansson.h
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ json_t *json_deep_copy(const json_t *value) JANSSON_ATTRS((warn_unused_result));
#define JSON_DECODE_ANY 0x4
#define JSON_DECODE_INT_AS_REAL 0x8
#define JSON_ALLOW_NUL 0x10
#define JSON_STORE_LOCATION 0x20

typedef size_t (*json_load_callback_t)(void *buffer, size_t buflen, void *data);

Expand Down Expand Up @@ -412,6 +413,10 @@ void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn);
const char *jansson_version_str(void);
int jansson_version_cmp(int major, int minor, int micro);

/* location information */

int json_get_location(json_t *json, int *line, int *column, int *position, int *length);

#ifdef __cplusplus
}
#endif
Expand Down
10 changes: 10 additions & 0 deletions src/jansson_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,16 @@ typedef struct {
json_int_t value;
} json_integer_t;

typedef struct {
json_t json;
} json_simple_t;

#define json_to_object(json_) container_of(json_, json_object_t, json)
#define json_to_array(json_) container_of(json_, json_array_t, json)
#define json_to_string(json_) container_of(json_, json_string_t, json)
#define json_to_real(json_) container_of(json_, json_real_t, json)
#define json_to_integer(json_) container_of(json_, json_integer_t, json)
#define json_to_simple(json_) container_of(json_, json_simple_t, json)

/* Create a string by taking ownership of an existing buffer */
json_t *jsonp_stringn_nocheck_own(const char *value, size_t len);
Expand Down Expand Up @@ -94,6 +99,11 @@ char *jsonp_strndup(const char *str, size_t len) JANSSON_ATTRS((warn_unused_resu
int jsonp_loop_check(hashtable_t *parents, const json_t *json, char *key, size_t key_size,
size_t *key_len_out);

/* Helpers for location information */
json_t *jsonp_simple(json_t *json, size_t flags);
void jsonp_store_location(json_t *json, int line, int column,
int position, int length);

/* Windows compatibility */
#if defined(_WIN32) || defined(WIN32)
#if defined(_MSC_VER) /* MS compiller */
Expand Down
30 changes: 27 additions & 3 deletions src/load.c
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,22 @@ static void lex_close(lex_t *lex) {
strbuffer_close(&lex->saved_text);
}

static void store_location_from_lex(json_t *json, size_t flags, const lex_t *lex)
{
int tlen = lex->saved_text.length;

if (!(flags & JSON_STORE_LOCATION))
return;

if (tlen)
tlen--;

jsonp_store_location(json, lex->stream.line,
lex->stream.column - tlen,
lex->stream.position - tlen,
tlen + 1);
}

/*** parser ***/

static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error);
Expand All @@ -664,6 +680,7 @@ static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error) {
if (!object)
return NULL;

store_location_from_lex(object, flags, lex);
lex_scan(lex, error);
if (lex->token == '}')
return object;
Expand Down Expand Up @@ -741,6 +758,7 @@ static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error) {
if (!array)
return NULL;

store_location_from_lex(array, flags, lex);
lex_scan(lex, error);
if (lex->token == ']')
return array;
Expand Down Expand Up @@ -796,31 +814,37 @@ static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error) {
}

json = jsonp_stringn_nocheck_own(value, len);
store_location_from_lex(json, flags, lex);
lex->value.string.val = NULL;
lex->value.string.len = 0;
break;
}

case TOKEN_INTEGER: {
json = json_integer(lex->value.integer);
store_location_from_lex(json, flags, lex);
break;
}

case TOKEN_REAL: {
json = json_real(lex->value.real);
store_location_from_lex(json, flags, lex);
break;
}

case TOKEN_TRUE:
json = json_true();
json = jsonp_simple(json_true(), flags);
store_location_from_lex(json, flags, lex);
break;

case TOKEN_FALSE:
json = json_false();
json = jsonp_simple(json_false(), flags);
store_location_from_lex(json, flags, lex);
break;

case TOKEN_NULL:
json = json_null();
json = jsonp_simple(json_null(), flags);
store_location_from_lex(json, flags, lex);
break;

case '{':
Expand Down
133 changes: 131 additions & 2 deletions src/value.c
Original file line number Diff line number Diff line change
Expand Up @@ -997,12 +997,42 @@ json_t *json_null(void) {
return &the_null;
}

/*** refcounted simple values ***/

json_t *jsonp_simple(json_t *json, size_t flags)
{
json_simple_t *simple;

/* pointless if not recording object location */
if (!(flags & JSON_STORE_LOCATION))
return json;

simple = jsonp_malloc(sizeof(json_simple_t));
if (!simple)
return NULL;

simple->json.type = json->type;
simple->json.refcount = 1;

return &simple->json;
}

static void json_delete_simple(json_simple_t *simple)
{
/* catch accidental calls for singletons */
if (simple && simple->json.refcount == 0)
jsonp_free(simple);
}

/*** deletion ***/

static void delete_location(json_t *json);

void json_delete(json_t *json) {
if (!json)
return;

delete_location(json);
switch (json_typeof(json)) {
case JSON_OBJECT:
json_delete_object(json_to_object(json));
Expand All @@ -1019,11 +1049,14 @@ void json_delete(json_t *json) {
case JSON_REAL:
json_delete_real(json_to_real(json));
break;
case JSON_TRUE:
case JSON_FALSE:
case JSON_NULL:
json_delete_simple(json_to_simple(json));
break;
default:
return;
}

/* json_delete is not called for true, false or null */
}

/*** equality ***/
Expand Down Expand Up @@ -1118,3 +1151,99 @@ json_t *do_deep_copy(const json_t *json, hashtable_t *parents) {
return NULL;
}
}

/*** location information ***/

typedef struct {
json_t json; /* just to integrate with hashtable */
int line;
int column;
int position;
int length;
} json_location_t;

static hashtable_t location_hash;
static int location_hash_initialized;

static void location_atexit(void)
{
if (location_hash_initialized)
hashtable_close(&location_hash);
}

void jsonp_store_location(json_t *json, int line, int column,
int position, int length)
{
json_location_t *loc = NULL;

/* not possible to store location for the singleton primitives
* as one can't distinguish them by their memory location */
if (json->refcount == (size_t)-1)
return;

if (!location_hash_initialized) {
if (!hashtable_seed) {
/* Autoseed */
json_object_seed(0);
}
if (hashtable_init(&location_hash))
return;

atexit(location_atexit);
location_hash_initialized = 1;
} else {
loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));
}
if (!loc) {
loc = jsonp_malloc(sizeof(*loc));
if (!loc)
return;

loc->json.refcount = (size_t)-1;

if (hashtable_set(&location_hash,
(void *)&json, sizeof(json), (void *)loc))
return;
}

loc->line = line;
loc->column = column;
loc->position = position;
loc->length = length;
}

int json_get_location(json_t *json, int *line, int *column,
int *position, int *length)
{
json_location_t *loc = NULL;

if (location_hash_initialized)
loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));

if (!loc)
return -1;

if (line)
*line = loc->line;
if (column)
*column = loc->column;
if (position)
*position = loc->position;
if (length)
*length = loc->length;

return 0;
}

static void delete_location(json_t *json)
{
struct json_location_t *loc;

if (!location_hash_initialized)
return;

loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));
hashtable_del(&location_hash, (void *)&json, sizeof(json));
if (loc)
jsonp_free(loc);
}
2 changes: 2 additions & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ suites/api/test_cpp
suites/api/test_dump
suites/api/test_dump_callback
suites/api/test_equal
suites/api/test_fixed_size
suites/api/test_load
suites/api/test_load_callback
suites/api/test_loadb
suites/api/test_location
suites/api/test_memory_funcs
suites/api/test_number
suites/api/test_object
Expand Down
2 changes: 2 additions & 0 deletions test/suites/api/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ check_PROGRAMS = \
test_load \
test_load_callback \
test_loadb \
test_location \
test_memory_funcs \
test_number \
test_object \
Expand All @@ -28,6 +29,7 @@ test_dump_callback_SOURCES = test_dump_callback.c util.h
test_fixed_size_SOURCES = test_fixed_size.c util.h
test_load_SOURCES = test_load.c util.h
test_loadb_SOURCES = test_loadb.c util.h
test_location_SOURCES = test_location.c util.h
test_memory_funcs_SOURCES = test_memory_funcs.c util.h
test_number_SOURCES = test_number.c util.h
test_object_SOURCES = test_object.c util.h
Expand Down