diff options
author | David Tolnay <dtolnay@gmail.com> | 2015-08-23 20:36:11 -0700 |
---|---|---|
committer | David Tolnay <dtolnay@gmail.com> | 2015-08-23 20:36:11 -0700 |
commit | 0c93eb3379241dc4775718a9d39f54a6c4de20d6 (patch) | |
tree | 67bb5510adb707d54c6f72b51b0718578a2caf5c /src/linker.c | |
parent | 891f28ef5e406a8d2156ad88d0244ab03fe490eb (diff) |
Move source files to src/
Diffstat (limited to 'src/linker.c')
-rw-r--r-- | src/linker.c | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/src/linker.c b/src/linker.c new file mode 100644 index 00000000..85378a2d --- /dev/null +++ b/src/linker.c @@ -0,0 +1,388 @@ +#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); + +static int path_is_relative(jv p) { + const char *s = jv_string_value(p); + +#ifdef WIN32 + int res = PathIsRelativeA(s); +#else + int res = *s != '/'; +#endif + jv_free(p); + return res; +} + + +// 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 err = jv_null(); + jv_array_foreach(search_path, i, path) { + if (jv_get_kind(path) != JV_KIND_STRING) { + jv_free(path); + continue; + } + path = expand_path(path); + if (!jv_is_valid(path)) { + err = path; + path = jv_null(); + continue; + } + if (strcmp(".",jv_string_value(path)) == 0) { + expanded_elt = jv_copy(path); + } 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 if (jv_get_kind(lib_origin) == JV_KIND_STRING && + path_is_relative(jv_copy(path))) { + expanded_elt = jv_string_fmt("%s/%s", + jv_string_value(lib_origin), + jv_string_value(path)); + } else { + expanded_elt = path; + 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 JV_ARRAY(expanded, err); +} + +// 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 err = jv_array_get(jv_copy(search), 1); + search = jv_array_get(search, 0); + + 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(err); + jv_free(rel_path); + jv_free(search); + jv_free(bname); + jv_free(spath); + return testpath; + } + jv_free(testpath); + jv_free(spath); + } + jv output; + if (!jv_is_valid(err)) { + err = jv_invalid_get_msg(err); + output = jv_invalid_with_msg(jv_string_fmt("module not found: %s (%s)", + jv_string_value(rel_path), + jv_string_value(err))); + } else { + output = jv_invalid_with_msg(jv_string_fmt("module not found: %s", + jv_string_value(rel_path))); + } + jv_free(err); + 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; +} |