mirror of
				https://github.com/stedolan/jq.git
				synced 2024-05-11 05:55:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			359 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			359 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #include <assert.h>
 | |
| #include <errno.h>
 | |
| #include <limits.h>
 | |
| #include <string.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdint.h>
 | |
| #include <sys/stat.h>
 | |
| #include <libgen.h>
 | |
| 
 | |
| #include "jq_parser.h"
 | |
| #include "locfile.h"
 | |
| #include "jv.h"
 | |
| #include "jq.h"
 | |
| #include "parser.h"
 | |
| #include "util.h"
 | |
| #include "compile.h"
 | |
| 
 | |
| struct lib_loading_state {
 | |
|   char **names;
 | |
|   block *defs;
 | |
|   uint64_t ct;
 | |
| };
 | |
| static int load_library(jq_state *jq, jv lib_path, int is_data, int raw,
 | |
|                         const char *as, block *out_block,
 | |
|                         struct lib_loading_state *lib_state);
 | |
| 
 | |
| 
 | |
| // Given a lib_path to search first, creates a chain of search paths
 | |
| // in the following order:
 | |
| // 1. lib_path
 | |
| // 2. -L paths passed in on the command line (from jq_state*) or builtin list
 | |
| static jv build_lib_search_chain(jq_state *jq, jv search_path, jv jq_origin, jv lib_origin) {
 | |
|   assert(jv_get_kind(search_path) == JV_KIND_ARRAY);
 | |
|   jv expanded = jv_array();
 | |
|   jv expanded_elt;
 | |
|   jv_array_foreach(search_path, i, path) {
 | |
|     if (strcmp(".",jv_string_value(path)) == 0) {
 | |
|       expanded_elt = jv_copy(path);
 | |
|     } else if (jv_get_kind(lib_origin) == JV_KIND_STRING &&
 | |
|                strncmp("./", jv_string_value(path),sizeof("./")-1) == 0) {
 | |
|       expanded_elt = jv_string_fmt("%s/%s",
 | |
|                                jv_string_value(lib_origin),
 | |
|                                jv_string_value(path) + sizeof ("./") - 1);
 | |
|     } else if (strncmp("$ORIGIN/",jv_string_value(path),sizeof("$ORIGIN/")-1) == 0) {
 | |
|       expanded_elt = jv_string_fmt("%s/%s",
 | |
|                                jv_string_value(jq_origin),
 | |
|                                jv_string_value(path) + sizeof ("$ORIGIN/") - 1);
 | |
|     } else {
 | |
|       expanded_elt = expand_path(path);
 | |
|       if (!jv_is_valid(expanded_elt)) {
 | |
|         jv_free(search_path);
 | |
|         jv_free(expanded);
 | |
|         jv_free(path);
 | |
|         return expanded_elt;
 | |
|       }
 | |
|       path = jv_invalid();
 | |
|     }
 | |
|     expanded = jv_array_append(expanded, expanded_elt);
 | |
|     jv_free(path);
 | |
|   }
 | |
|   jv_free(jq_origin);
 | |
|   jv_free(lib_origin);
 | |
|   jv_free(search_path);
 | |
|   return expanded;
 | |
| }
 | |
| 
 | |
| // Doesn't actually check that name not be an absolute path, and we
 | |
| // don't have to: we always append relative paths to others (with a '/'
 | |
| // in between).
 | |
| static jv validate_relpath(jv name) {
 | |
|   const char *s = jv_string_value(name);
 | |
|   if (strchr(s, '\\')) {
 | |
|     jv res = jv_invalid_with_msg(jv_string_fmt("Modules must be named by relative paths using '/', not '\\' (%s)", s));
 | |
|     jv_free(name);
 | |
|     return res;
 | |
|   }
 | |
|   jv components = jv_string_split(jv_copy(name), jv_string("/"));
 | |
|   jv rp = jv_array_get(jv_copy(components), 0);
 | |
|   components = jv_array_slice(components, 1, jv_array_length(jv_copy(components)));
 | |
|   jv_array_foreach(components, i, x) {
 | |
|     if (!strcmp(jv_string_value(x), "..")) {
 | |
|       jv_free(x);
 | |
|       jv_free(rp);
 | |
|       jv_free(components);
 | |
|       jv res = jv_invalid_with_msg(jv_string_fmt("Relative paths to modules may not traverse to parent directories (%s)", s));
 | |
|       jv_free(name);
 | |
|       return res;
 | |
|     }
 | |
|     if (i > 0 && jv_equal(jv_copy(x), jv_array_get(jv_copy(components), i - 1))) {
 | |
|       jv_free(x);
 | |
|       jv_free(rp);
 | |
|       jv_free(components);
 | |
|       jv res = jv_invalid_with_msg(jv_string_fmt("module names must not have equal consecutive components: %s",
 | |
|                                                  jv_string_value(name)));
 | |
|       jv_free(name);
 | |
|       return res;
 | |
|     }
 | |
|     rp = jv_string_concat(rp, jv_string_concat(jv_string("/"), x));
 | |
|   }
 | |
|   jv_free(components);
 | |
|   jv_free(name);
 | |
|   return rp;
 | |
| }
 | |
| 
 | |
| // Assumes name has been validated
 | |
| static jv jv_basename(jv name) {
 | |
|   const char *s = jv_string_value(name);
 | |
|   const char *p = strrchr(s, '/');
 | |
|   if (!p)
 | |
|     return name;
 | |
|   jv res = jv_string_fmt("%s", p);
 | |
|   jv_free(name);
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| // Asummes validated relative path to module
 | |
| static jv find_lib(jq_state *jq, jv rel_path, jv search, const char *suffix, jv jq_origin, jv lib_origin) {
 | |
|   if (jv_get_kind(search) != JV_KIND_ARRAY)
 | |
|     return jv_invalid_with_msg(jv_string_fmt("Module search path must be an array"));
 | |
|   if (jv_get_kind(rel_path) != JV_KIND_STRING)
 | |
|     return jv_invalid_with_msg(jv_string_fmt("Module path must be a string"));
 | |
| 
 | |
|   struct stat st;
 | |
|   int ret;
 | |
| 
 | |
|   // Ideally we should cache this somewhere
 | |
|   search = build_lib_search_chain(jq, search, jq_origin, lib_origin);
 | |
| 
 | |
|   jv bname = jv_basename(jv_copy(rel_path));
 | |
| 
 | |
|   jv_array_foreach(search, i, spath) {
 | |
|     if (jv_get_kind(spath) == JV_KIND_NULL) {
 | |
|       jv_free(spath);
 | |
|       break;
 | |
|     }
 | |
|     if (jv_get_kind(spath) != JV_KIND_STRING ||
 | |
|         strcmp(jv_string_value(spath), "") == 0) {
 | |
|       jv_free(spath);
 | |
|       continue; /* XXX report non-strings in search path?? */
 | |
|     }
 | |
|     // Try ${search_dir}/${rel_path}.jq
 | |
|     jv testpath = jq_realpath(jv_string_fmt("%s/%s%s",
 | |
|                                             jv_string_value(spath),
 | |
|                                             jv_string_value(rel_path),
 | |
|                                             suffix));
 | |
|     ret = stat(jv_string_value(testpath),&st);
 | |
|     if (ret == -1 && errno == ENOENT) {
 | |
|       jv_free(testpath);
 | |
|       // Try ${search_dir}/$(dirname ${rel_path})/jq/main.jq
 | |
|       testpath = jq_realpath(jv_string_fmt("%s/%s/%s%s",
 | |
|                                            jv_string_value(spath),
 | |
|                                            jv_string_value(rel_path),
 | |
|                                            "jq/main",
 | |
|                                            suffix));
 | |
|       ret = stat(jv_string_value(testpath),&st);
 | |
|     }
 | |
|     if (ret == -1 && errno == ENOENT) {
 | |
|       jv_free(testpath);
 | |
|       // Try ${search_dir}/${rel_path}/$(basename ${rel_path}).jq
 | |
|       testpath = jq_realpath(jv_string_fmt("%s/%s/%s%s",
 | |
|                                            jv_string_value(spath),
 | |
|                                            jv_string_value(rel_path),
 | |
|                                            jv_string_value(bname),
 | |
|                                            suffix));
 | |
|       ret = stat(jv_string_value(testpath),&st);
 | |
|     }
 | |
|     if (ret == 0) {
 | |
|       jv_free(rel_path);
 | |
|       jv_free(search);
 | |
|       jv_free(bname);
 | |
|       jv_free(spath);
 | |
|       return testpath;
 | |
|     }
 | |
|     jv_free(testpath);
 | |
|     jv_free(spath);
 | |
|   }
 | |
|   jv output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", jv_string_value(rel_path)));
 | |
|   jv_free(rel_path);
 | |
|   jv_free(search);
 | |
|   jv_free(bname);
 | |
|   return output;
 | |
| }
 | |
| 
 | |
| static jv default_search(jq_state *jq, jv value) {
 | |
|   if (!jv_is_valid(value)) {
 | |
|     // dependent didn't say; prepend . to system search path listj
 | |
|     jv_free(value);
 | |
|     return jv_array_concat(JV_ARRAY(jv_string(".")), jq_get_lib_dirs(jq));
 | |
|   }
 | |
|   if (jv_get_kind(value) != JV_KIND_ARRAY)
 | |
|     return JV_ARRAY(value);
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| // XXX Split this into a util that takes a callback, and then...
 | |
| static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block *src_block, struct lib_loading_state *lib_state) {
 | |
|   jv deps = block_take_imports(src_block);
 | |
|   block bk = *src_block;
 | |
|   int nerrors = 0;
 | |
|   const char *as_str = NULL;
 | |
| 
 | |
|   jv_array_foreach(deps, i, dep) {
 | |
|     int is_data = jv_get_kind(jv_object_get(jv_copy(dep), jv_string("is_data"))) == JV_KIND_TRUE;
 | |
|     int raw = 0;
 | |
|     jv v = jv_object_get(jv_copy(dep), jv_string("raw"));
 | |
|     if (jv_get_kind(v) == JV_KIND_TRUE)
 | |
|       raw = 1;
 | |
|     jv_free(v);
 | |
|     jv relpath = validate_relpath(jv_object_get(jv_copy(dep), jv_string("relpath")));
 | |
|     jv as = jv_object_get(jv_copy(dep), jv_string("as"));
 | |
|     assert(!jv_is_valid(as) || jv_get_kind(as) == JV_KIND_STRING);
 | |
|     if (jv_get_kind(as) == JV_KIND_STRING)
 | |
|       as_str = jv_string_value(as);
 | |
|     jv search = default_search(jq, jv_object_get(dep, jv_string("search")));
 | |
|     // dep is now freed; do not reuse
 | |
| 
 | |
|     // find_lib does a lot of work that could be cached...
 | |
|     jv resolved = find_lib(jq, relpath, search, is_data ? ".json" : ".jq", jv_copy(jq_origin), jv_copy(lib_origin));
 | |
|     // XXX ...move the rest of this into a callback.
 | |
|     if (!jv_is_valid(resolved)) {
 | |
|       jv emsg = jv_invalid_get_msg(resolved);
 | |
|       jq_report_error(jq, jv_string_fmt("jq: error: %s\n",jv_string_value(emsg)));
 | |
|       jv_free(emsg);
 | |
|       jv_free(as);
 | |
|       jv_free(deps);
 | |
|       jv_free(jq_origin);
 | |
|       jv_free(lib_origin);
 | |
|       return 1;
 | |
|     }
 | |
|     uint64_t state_idx = 0;
 | |
|     for (; state_idx < lib_state->ct; ++state_idx) {
 | |
|       if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
 | |
|         break;
 | |
|     }
 | |
|     if (state_idx < lib_state->ct) { // Found
 | |
|       jv_free(resolved);
 | |
|       // Bind the library to the program
 | |
|       bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, as_str);
 | |
|     } else { // Not found.   Add it to the table before binding.
 | |
|       block dep_def_block = gen_noop();
 | |
|       nerrors += load_library(jq, resolved, is_data, raw, as_str, &dep_def_block, lib_state);
 | |
|       // resolved has been freed
 | |
|       if (nerrors == 0) {
 | |
|         // Bind the library to the program
 | |
|         bk = block_bind_library(dep_def_block, bk, OP_IS_CALL_PSEUDO, as_str);
 | |
|       }
 | |
|     }
 | |
|     jv_free(as);
 | |
|   }
 | |
|   jv_free(lib_origin);
 | |
|   jv_free(jq_origin);
 | |
|   jv_free(deps);
 | |
|   return nerrors;
 | |
| }
 | |
| 
 | |
| // Loads the library at lib_path into lib_state, putting the library's defs
 | |
| // into *out_block
 | |
| static int load_library(jq_state *jq, jv lib_path, int is_data, int raw, const char *as, block *out_block, struct lib_loading_state *lib_state) {
 | |
|   int nerrors = 0;
 | |
|   struct locfile* src = NULL;
 | |
|   block program;
 | |
|   jv data;
 | |
|   if (is_data && !raw)
 | |
|     data = jv_load_file(jv_string_value(lib_path), 0);
 | |
|   else
 | |
|     data = jv_load_file(jv_string_value(lib_path), 1);
 | |
|   int state_idx;
 | |
|   if (!jv_is_valid(data)) {
 | |
|     if (jv_invalid_has_msg(jv_copy(data)))
 | |
|       data = jv_invalid_get_msg(data);
 | |
|     else
 | |
|       data = jv_string("unknown error");
 | |
|     jq_report_error(jq, jv_string_fmt("jq: error loading data file %s: %s\n", jv_string_value(lib_path), jv_string_value(data)));
 | |
|     nerrors++;
 | |
|     goto out;
 | |
|   } else if (is_data) {
 | |
|     // import "foo" as $bar;
 | |
|     program = gen_const_global(jv_copy(data), as);
 | |
|   } else {
 | |
|     // import "foo" as bar;
 | |
|     src = locfile_init(jq, jv_string_value(lib_path), jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
 | |
|     nerrors += jq_parse_library(src, &program);
 | |
|     if (nerrors == 0) {
 | |
|       char *lib_origin = strdup(jv_string_value(lib_path));
 | |
|       nerrors += process_dependencies(jq, jq_get_jq_origin(jq),
 | |
|                                       jv_string(dirname(lib_origin)),
 | |
|                                       &program, lib_state);
 | |
|       free(lib_origin);
 | |
|     }
 | |
|   }
 | |
|   state_idx = lib_state->ct++;
 | |
|   lib_state->names = realloc(lib_state->names, lib_state->ct * sizeof(const char *));
 | |
|   lib_state->defs = realloc(lib_state->defs, lib_state->ct * sizeof(block));
 | |
|   lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
 | |
|   lib_state->defs[state_idx] = program;
 | |
|   *out_block = program;
 | |
|   if (src)
 | |
|     locfile_free(src);
 | |
| out:
 | |
|   jv_free(lib_path);
 | |
|   jv_free(data);
 | |
|   return nerrors;
 | |
| }
 | |
| 
 | |
| // FIXME It'd be nice to have an option to search the same search path
 | |
| // as we do in process_dependencies.
 | |
| jv load_module_meta(jq_state *jq, jv mod_relpath) {
 | |
|   // We can't know the caller's origin; we could though, if it was passed in
 | |
|   jv lib_path = find_lib(jq, validate_relpath(mod_relpath), jq_get_lib_dirs(jq), ".jq", jq_get_jq_origin(jq), jv_null());
 | |
|   if (!jv_is_valid(lib_path))
 | |
|     return lib_path;
 | |
|   jv meta = jv_null();
 | |
|   jv data = jv_load_file(jv_string_value(lib_path), 1);
 | |
|   if (jv_is_valid(data)) {
 | |
|     block program;
 | |
|     struct locfile* src = locfile_init(jq, jv_string_value(lib_path), jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
 | |
|     int nerrors = jq_parse_library(src, &program);
 | |
|     if (nerrors == 0) {
 | |
|       meta = block_module_meta(program);
 | |
|       if (jv_get_kind(meta) == JV_KIND_NULL)
 | |
|         meta = jv_object();
 | |
|       meta = jv_object_set(meta, jv_string("deps"), block_take_imports(&program));
 | |
|     }
 | |
|     locfile_free(src);
 | |
|     block_free(program);
 | |
|   }
 | |
|   jv_free(lib_path);
 | |
|   jv_free(data);
 | |
|   return meta;
 | |
| }
 | |
| 
 | |
| int load_program(jq_state *jq, struct locfile* src, block *out_block) {
 | |
|   int nerrors = 0;
 | |
|   block program;
 | |
|   struct lib_loading_state lib_state = {0,0,0};
 | |
|   nerrors = jq_parse(src, &program);
 | |
|   if (nerrors)
 | |
|     return nerrors;
 | |
| 
 | |
|   nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);
 | |
|   block libs = gen_noop();
 | |
|   for (uint64_t i = 0; i < lib_state.ct; ++i) {
 | |
|     free(lib_state.names[i]);
 | |
|     if (nerrors == 0 && !block_is_const(lib_state.defs[i]))
 | |
|       libs = block_join(libs, lib_state.defs[i]);
 | |
|     else
 | |
|       block_free(lib_state.defs[i]);
 | |
|   }
 | |
|   free(lib_state.names);
 | |
|   free(lib_state.defs);
 | |
|   if (nerrors)
 | |
|     block_free(program);
 | |
|   else
 | |
|     *out_block = block_drop_unreferenced(block_join(libs, program));
 | |
| 
 | |
|   return nerrors;
 | |
| }
 |