/* build.c -- The main build function Copyright © 2021-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "internal.h" #include #include #define INTERR_BUILD(errnum) MAKE_INTERR(errnum, INTERRBASE_BUILD) #ifndef PARSEBUFFER #define PARSEBUFFER 4096 #endif enum ParseLoopMode { LOOP_HEADER, LOOP_TO_EOF }; static void parse_loop(struct CSlul *ctx, CSlulFile file, char *buffer, enum ParseLoopMode); const char *make_path_n(struct CSlul *ctx, const char *directory, const char *file, size_t filelen) { size_t dirlen; char *path; dirlen = strlen(directory); while (dirlen && directory[dirlen-1] == ctx->cfg->params.dirsep) dirlen--; path = aalloc(ctx, dirlen+1+filelen+1, 1); if (!path) return NULL; memcpy(path, directory, dirlen); path[dirlen] = ctx->cfg->params.dirsep; memcpy(path+dirlen+1, file, filelen); path[dirlen+1+filelen] = '\0'; return path; } /** Merges a filename with an optional directory. Both must be null-terminated */ const char *make_path(struct CSlul *ctx, const char *directory, const char *file) { if (!directory || !*directory) return file; return make_path_n(ctx, directory, file, strlen(file)); } /** Merges a filename with an optional directory. Only the directory needs to be null-terminated. */ const char *make_nzpath(struct CSlul *ctx, const char *directory, const char *file, size_t filelen) { if (!directory || !*directory) return aalloc_memzdup(ctx, file, filelen); return make_path_n(ctx, directory, file, filelen); } /** * Builds an interface path: "/" ".slul\0" */ static char *build_iface_path(struct CSlul *ctx, const struct InterfaceDir *ifacedir, const struct CSlulDepIter *depiter) { size_t ifplen = ifacedir->pathlen; char *dp; char *buffer = aalloc(ctx, ifplen+1+depiter->modnamelen+5+1, 1); if (!buffer) return NULL; dp = buffer; memcpy(dp, ifacedir->path, ifplen); dp += ifplen; *(dp++) = ctx->cfg->params.dirsep; memcpy(dp, depiter->module_name, depiter->modnamelen); dp += depiter->modnamelen; memcpy(dp, ".slul", 5+1); return buffer; } static void parse_file(struct CSlul *ctx, const char *filename, char *buffer) { CSlulFile f; assert(filename != NULL); f = ctx_fopen(ctx, filename, "rb"); if (!f) return; cslul_ll_set_current_filename(ctx, filename); parse_loop(ctx, f, buffer, LOOP_TO_EOF); cslul_ll_set_current_filename(ctx, NULL); ctx_fclose(ctx, f); } static void delete_output_files(struct CSlul *ctx) { struct Output *output = ctx->outputs; for (; output; output = output->next) { assert(output->filename); ctx_remove(ctx, output->filename); } } /** * Checks that a dependency: * - Has a matching name. * - Is a library. */ static int sanity_check_dep(struct CSlul *ctx, const struct CSlulDepIter *depiter, const char *dep_filename) { const struct Module *mod = ctx->parsed_module; int ok = 1; if (!depiter->module_name || !mod->name) goto assert_error; if (UNLIKELY(mod->namelen != depiter->modnamelen || memcmp(mod->name, depiter->module_name, mod->namelen))) { message_set_filemsg(ctx, 0, CSLUL_LT_MAIN, dep_filename, mod->name, mod->namelen); message_final(ctx, CSLUL_E_MODNAMEMISMATCH); ok = 0; } if (UNLIKELY(mod->type != CSLUL_MT_INTERNAL && mod->type != CSLUL_MT_LIBRARY && mod->type != CSLUL_MT_LIBRARYSPEC)) { if (!mod->type || mod->type == CSLUL_MT_INVALID) goto assert_error; message_set_filemsg(ctx, 0, CSLUL_LT_MAIN, dep_filename, NULL, 0); message_final(ctx, CSLUL_E_BADLIBRARYTYPE); ok = 0; } return ok; assert_error: assert(ctx->has_errors); return 0; } struct InsufficientVersion { const char *filepath; const char *version; size_t versionlen; }; static int check_dep_version(struct CSlul *ctx, const struct CSlulDepIter *depiter, const char *dep_filename, struct InsufficientVersion *insuff) { const struct Module *mod = ctx->parsed_module; struct CSlulDependency *dep = depiter->internal; const char *minver = dep->min_version; const unsigned minverlen = dep->minverlen; if (versioncmp(mod->version, mod->versionlen, minver, minverlen) < 0) { /* Version is too old */ if (!insuff->version || versioncmp(mod->version, mod->versionlen, insuff->version, insuff->versionlen) > 0) { /* Higher version requirement */ insuff->filepath = dep_filename; insuff->version = mod->version; insuff->versionlen = mod->versionlen; } return 0; } /* Select the highest version which satisfies: apidef->version <= dep->min_version (It can be < because the module might depend on a patch version, with no API changes since the preceeding \api_def) */ if (mod->num_apidefs) { const struct ApiDef *best = NULL; const struct ApiDef *api = mod->first_apidef; assert(api); for (; api; api = api->next) { if (node_vercmp(&api->verstr, minver, minverlen) <= 0 && (!best || nodes_vercmp(&api->verstr, &best->verstr) > 0)) { best = api; } } if (UNLIKELY(!best)) { if (!insuff->version) { insuff->filepath = dep_filename; insuff->version = NULL; } return 0; } dep->effective_apiver = best; } return 1; } /** * Returns the "active" version of \interface_depends for a nested * dependency of a dependency. * * \param depiter The dependency (iterator type). * \param depdep The nested dependency. */ static const struct CSlulInterfaceDep *matching_ifacedep( const struct CSlulDepIter *depiter, const struct CSlulDependency *depdep) { const struct CSlulInterfaceDep *matching = NULL, *ifd; const char *active_version = depiter->min_version; size_t active_verlen = depiter->internal->minverlen; for (ifd = depdep->first_ifacever; ifd; ifd = ifd->sincever_next) { /* Match only if since-version <= requested version (note that \interface_depends must come in order) */ if (node_vercmp(&ifd->node, active_version, active_verlen) > 0) break; matching = ifd; } return matching; } /** * Checks that the module being compiled (main) depends on a specific * interface and version (ifacedep) or any later version. * * When require_ifacedep==0, it accepts \depends and \interface_depends. * When require_ifacedep==1, it accepts \interface_depends only, with * an equal or lower since-version. */ static int require_explicit_dep(struct CSlul *ctx, const struct Module *main, const struct CSlulDepIter *depiter, const char *dep_filename, const struct CSlulDependency *required, const struct CSlulInterfaceDep *required_iface, const char *sinceversion, int require_ifacedep) { const char *required_modname; const struct CSlulDependency *maindep; enum CSlulErrorCode errcode; assert(required_iface != NULL); /* Check if it is the current module */ required_modname = node_nameptr(&required->node); if (required->node.hashcode == main->namehash && required->node.length == main->namelen && !memcmp(required_modname, main->name, main->namelen)) { if (UNLIKELY(versioncmp(required_iface->min_version, required_iface->minverlen, main->version, main->versionlen) > 0)) { message_set_textlen(ctx, 3, CSLUL_LT_DEPENDENCY, main->version, main->versionlen); errcode = CSLUL_E_REQUIREDDEPVERSELF; goto dep_error; } return 1; } /* Check that there is any dependency at all */ maindep = (const struct CSlulDependency *) tree_search(ctx, main->deps_root, required->node.hashcode, required->node.length, required_modname); if (UNLIKELY(!maindep)) { errcode = CSLUL_E_REQUIREDDEP; goto dep_error; } if ((maindep->flags & CSLUL_DF_IFACEONLY) == 0) { /* Check \depends version */ if (versioncmp(required_iface->min_version, required_iface->minverlen, maindep->min_version, maindep->minverlen) > 0) { message_set_textlen(ctx, 3, CSLUL_LT_DEPENDENCY, maindep->min_version, maindep->minverlen); errcode = CSLUL_E_REQUIREDDEPVER; goto dep_error; } } if (require_ifacedep) { /* Check that there's a correct \interface_depends */ /* TODO */ (void)sinceversion; } return 1; dep_error: message_set_filemsg(ctx, 0, CSLUL_LT_MAIN, main->filename, required_modname, required->node.length); message_set_textlen(ctx, 1, CSLUL_LT_MINIMUM, required_iface->min_version, required_iface->minverlen); message_set_filemsg(ctx, 2, CSLUL_LT_DEPENDENCY, dep_filename, depiter->module_name, depiter->modnamelen); message_final(ctx, errcode); return 0; } /** * Checks that all \interface_depends in dependencies are explicitely listed * in the module being compiled, either as \depends (for applications/libs) * as \interface_depends (for libraries). */ static int check_iface_deps(struct CSlul *ctx, const struct CSlulDepIter *depiter, const char *dep_filename) { const struct Module *dep = ctx->parsed_module; const struct Module *mainmod = &ctx->module; const struct CSlulDependency *depdep; int ok = 1; /* For each nested dependency */ for (depdep = dep->first_dep; depdep; depdep = depdep->next) { /* TODO struct CSlulInterfaceDep *ifacedep_for_iface;*/ /* Check that any nested \interface_depends are also explicitly listed as \interface_depends or \depends in the module being compiled, so the implementation can access all necessary types. */ const struct CSlulInterfaceDep *ifacedep_for_impl = matching_ifacedep(depiter, depdep); if (ifacedep_for_impl) { ok |= require_explicit_dep(ctx, mainmod, depiter, dep_filename, depdep, ifacedep_for_impl, NULL, 0); } /* If the module being compiled as \interface_depends on the dependency, then any nested \interface_depends must be explicitly listed as \interface_depends (with the correct version) in the module being compiled.*/ /* TODO for (ifacedep_for_iface = ... */ /* XXX might need to check this for EACH version OF -->depiter<-- - first_ifacever has interface_depends - is this needed only for CSLUL_DF_IFACEONLY or always? */ } return ok; } int cslul_build(struct CSlul *ctx, const char *directory) { CSlulFile mainf = NULL; char *buffer = NULL; const char *mainfname = make_path(ctx, directory, "main.slul"); const char *outdir; char *dep_path; struct CSlulDepIter depiter; struct CSlulSrcIter srciter; int save_has_errors, has_dep_errors; if (!mainfname) goto err; ctx->module.filename = mainfname; /* 1. Open main file and parse module header */ if (!cslul_ll_start_phase(ctx, CSLUL_P_MODULEHEADER)) goto err; mainf = ctx_fopen(ctx, mainfname, "rb"); if (!mainf) goto err; cslul_ll_set_current_filename(ctx, mainfname); buffer = lowlvl_alloc(ctx->cfg, PARSEBUFFER); if (!buffer) goto outofmem; parse_loop(ctx, mainf, buffer, LOOP_HEADER); if (ctx->has_errors) goto err; outdir = ctx->cfg->outdir ? ctx->cfg->outdir : directory ? directory : ""; if (!init_outputs(ctx, outdir)) goto err; /* 2. Parse module (and internal?) interface */ if (!cslul_ll_start_phase(ctx, CSLUL_P_IFACE)) goto err; switch (ctx->module.type) { case CSLUL_MT_INTERNAL: case CSLUL_MT_LIBRARY: /* TODO allow specifying an external interface specification in main.slul */ /* Fall through */ case CSLUL_MT_LIBRARYSPEC: case CSLUL_MT_PLUGINSPEC: /* The main file contains the interface definitions also */ cslul_ll_parse(ctx); /* remainder of buffer */ if (ctx->has_fatal_errors) goto err; parse_loop(ctx, mainf, buffer, LOOP_TO_EOF); if (ctx->has_fatal_errors) goto err; break; case CSLUL_MT_PLUGIN: /* TODO load specification file */ break; case CSLUL_MT_APP: /* Add default intefaces */ /* TODO */ break; case CSLUL_MT_INVALID: case CSLUL_MT_UNSET: ; /* should not happen */ } /* 3. Parse impl source in main file */ if (!cslul_ll_start_phase(ctx, CSLUL_P_IMPL)) goto err; switch (ctx->module.type) { case CSLUL_MT_PLUGIN: case CSLUL_MT_APP: /* Continue parsing main.slul as an implementation file */ cslul_ll_parse(ctx); /* remainder of buffer */ if (ctx->has_fatal_errors) goto err; if (!ctx_ferror(ctx, mainf) && !ctx->last_buffer) { parse_loop(ctx, mainf, buffer, LOOP_TO_EOF); if (ctx->has_fatal_errors) goto err; } break; /* These have interfaces */ case CSLUL_MT_INTERNAL: case CSLUL_MT_LIBRARY: case CSLUL_MT_LIBRARYSPEC: case CSLUL_MT_PLUGINSPEC: case CSLUL_MT_INVALID: case CSLUL_MT_UNSET: ; /* should not happen */ } /* 4. Close main.slul file */ ctx_fclose(ctx, mainf); mainf = (CSlulFile)NULL; cslul_ll_set_current_filename(ctx, NULL); /* 5. Parse source files */ srciter.filename = NULL; while (cslul_ll_implsource_iter(ctx, &srciter)) { /* FIXME local variables? */ const char *path = make_nzpath(ctx, directory, srciter.filename, srciter.namelen); if (!path) goto err; parse_file(ctx, path, buffer); if (ctx->has_fatal_errors) goto err; } /* 6. Parse interfaces of dependencies */ if (!cslul_ll_start_phase(ctx, CSLUL_P_DEPS)) goto err; depiter.size = sizeof(depiter); depiter.module_name = NULL; save_has_errors = ctx->has_errors; ctx->has_errors = 0; has_dep_errors = 0; while (cslul_ll_dependency_iter(ctx, &depiter)) { const struct InterfaceDir *ifacedir = ctx->cfg->iface_dirs; struct InsufficientVersion insuff = { NULL, NULL, 0 }; assert(depiter.module_name != NULL); for (; ifacedir; ifacedir = ifacedir->next) { CSlulFile depf = NULL; dep_path = build_iface_path(ctx, ifacedir, &depiter); if (!dep_path) goto dep_end; depf = silent_open(ctx, dep_path, "rb"); if (!depf) goto next_iface_file; if (!cslul_ll_start_parsing_dep(ctx, &depiter, dep_path)) goto dep_end; cslul_ll_set_current_filename(ctx, dep_path); parse_loop(ctx, depf, buffer, LOOP_HEADER); if (ctx->has_errors) goto dep_end; /* Check module metadata */ if (!sanity_check_dep(ctx, &depiter, dep_path)) goto dep_end; if (!check_dep_version(ctx, &depiter, dep_path, &insuff)) { goto next_iface_file; } if (!check_iface_deps(ctx, &depiter, dep_path)) goto dep_end; /* Parse interface */ cslul_ll_leave_dep_moduleheader(ctx); if (ctx->has_fatal_errors) goto err; cslul_ll_parse(ctx); /* remaining data */ if (ctx->has_fatal_errors) goto err; parse_loop(ctx, depf, buffer, LOOP_TO_EOF); cslul_ll_done_parsing_dep(ctx); dep_end: cslul_ll_set_current_filename(ctx, NULL); ctx_fclose(ctx, depf); if (ctx->has_fatal_errors) goto err; has_dep_errors |= ctx->has_errors; ctx->has_errors = 0; goto dep_found; next_iface_file: if (ctx->has_fatal_errors) goto err; has_dep_errors |= ctx->has_errors; ctx->has_errors = 0; } /* Missing dependency */ message_set_filemsg(ctx, 0, CSLUL_LT_MAIN, mainfname, depiter.module_name, depiter.modnamelen); if (insuff.version) { message_set_text(ctx, 1, CSLUL_LT_MINIMUM, depiter.min_version); message_set_filemsg(ctx, 2, CSLUL_LT_INSTALLED, insuff.filepath, insuff.version, insuff.versionlen); message_final(ctx, CSLUL_E_OLDDEP); } else if (insuff.filepath) { message_set_text(ctx, 1, CSLUL_LT_DEPENDENCY, depiter.min_version); message_final(ctx, CSLUL_E_VERNOTSUPPORTED); } else { message_final(ctx, CSLUL_E_MISSINGDEP); } has_dep_errors = 1; ctx->has_errors = 0; dep_found: ; } /* Errors in dependencies would likely cause "domino" errors in the module being compiled, so don't continue on error. */ if (has_dep_errors) { ctx->has_errors = 1; goto err; } ctx->has_errors = save_has_errors; ctx_dropprivs(ctx); /* FIXME open all files just after the project file has been parsed. but on the other hand, that would use up a lot of file descriptors! could be a problem with large modules with many source files */ /* FIXME the file cannot be deleted on failure if privileges have been dropped. an option would be to create the output file later. */ lowlvl_free(ctx->cfg, buffer); buffer = NULL; /* 7. Final correctness verification */ if (!cslul_ll_start_phase(ctx, CSLUL_P_VERIFY)) goto err; if (ctx->has_errors) goto err; assert(!ctx->must_have_errors); /* 8. Output */ if (!cslul_ll_start_phase(ctx, CSLUL_P_CODEGEN)) goto err; return 1; outofmem: ctx_outofmem(ctx); err: if (buffer) lowlvl_free(ctx->cfg, buffer); if (mainf) ctx_fclose(ctx, mainf); delete_output_files(ctx); cslul_ll_set_current_filename(ctx, NULL); return 0; } /** * \param file File to read from * \param buffer Fixed-size pre-allocated buffer of PARSEBUFFER bytes */ static void parse_loop(struct CSlul *ctx, CSlulFile file, char *buffer, enum ParseLoopMode mode) { int last = 0; /* FIXME Clean up parse loop. Use separate parse functions, and clean up the state transition from header to source */ do { size_t size = ctx_fread(ctx, buffer, 1, PARSEBUFFER, file); if (size != PARSEBUFFER) { if (ctx_ferror(ctx, file)) goto error; last = 1; } cslul_ll_set_input_buffer(ctx, buffer, size, last); cslul_ll_parse(ctx); if (mode == LOOP_HEADER && !ctx->in_moduleheader) break; } while (!last && !cslul_ll_has_fatal_errors(ctx)); return; error: error_linecol(ctx, CSLUL_E_READFILE, ctx->line, ctx->startcolumn); } static void set_mh_sourceline(struct CSlul *ctx, struct TreeNode *node) { node->line = ctx->parser.mh.attr_line; node->column = ctx->parser.mh.attr_col; node->filename = ctx->current_filename; } /** * Adds the filename of a SLUL source file. Returns 0 on success. * If the returned value is neither -1 or CSLUL_E_OUTOFMEMORY, * then it is an error code that should be reported to the user. */ enum CSlulErrorCode build_add_source(struct CSlul *ctx, HashCode h, uint32 len, const char *filename) { struct TreeNode *res; struct CSlulSourceFile *sf; sf = aallocp(ctx, sizeof(*sf)); if (!sf) return CSLUL_E_OUTOFMEMORY; PROTECT_STRUCT(*sf); if (!ctx->first_srcfile) ctx->first_srcfile = sf; if (ctx->last_srcfile) ctx->last_srcfile->next = sf; ctx->last_srcfile = sf; sf->next = NULL; /* A tree is used to detect duplicates */ /* TODO consider removing support for UpperCase in filenames: - it increases code complexity - it requires the ugly ctx->case_insens global - it is better to enforce one style for all projects TODO also, ensure that there are filename length limits: - a filepath limit (e.g. 100) - maybe also a filepath-component limit (e.g. 50) */ ctx->case_insens = 1; res = tree_insert(ctx, &ctx->sources_root, h, len, filename, &sf->node, 0); ctx->case_insens = 0; if (res == NULL) return (enum CSlulErrorCode)-1; if (res->is_new) { set_mh_sourceline(ctx, &sf->node); return CSLUL_E_NOERROR; } /* Duplicate filename */ assert(res->length == len); if (memcmp(node_nameptr(res), filename, len)) { return CSLUL_E_SOURCECASEDUP; } else { return CSLUL_E_DUPLICATEFILE; } } static int init_dep(struct CSlul *ctx, struct Module *mod, struct CSlulDependency *dep, HashCode h, uint32 len, const char *modname) { if (!mod->first_dep) mod->first_dep = dep; if (mod->last_dep) mod->last_dep->next = dep; dep->next = NULL; dep->min_version = NULL; dep->minverlen = 0; dep->iface_flags = 0; dep->ifacever_root = NULL; dep->first_ifacever = NULL; dep->last_ifacever = NULL; dep->importlib = NULL; if (ctx->phase != CSLUL_P_DEPS) { /* Dependency from main module */ struct Module *depmod = aallocp(ctx, sizeof(struct Module)); if (UNLIKELY(!depmod)) return 0; PROTECT_STRUCT(*depmod); module_init(depmod); dep->module = depmod; } else { /* Dependency from a nested dependency. Just reference to the dep in the main module (it will always be there if it is needed) */ const struct CSlulDependency *main_dep = (const struct CSlulDependency *)tree_search( ctx, ctx->module.deps_root, h, len, modname); if (main_dep) { /* Dependency exists in main module, too. (This should always be the case for \interface_depends, except for circular references back to the main module itself). */ dep->module = main_dep->module; } else if (/*h == ctx->module.namehash &&*/ len == ctx->module.namelen && !memcmp(modname, ctx->module.name, len)) { /* Circular reference to the main module */ dep->module = &ctx->module; } else { /* Either a \depends in a nested dependency, or an error */ dep->module = NULL; } } mod->last_dep = dep; return 1; } /** * Adds a module dependency. Returns 0 on success. * If the returned value is neither -1 or CSLUL_E_OUTOFMEMORY, * then it is an error code that should be reported to the user. * * Used for \depends dependencies. */ enum CSlulErrorCode build_add_dep(struct CSlul *ctx, HashCode h, uint32 len, const char *modname) { struct TreeNode *res; struct CSlulDependency *dep; struct Module *mod; assert(!ctx->case_insens); dep = aallocp(ctx, sizeof(*dep)); if (!dep) return CSLUL_E_OUTOFMEMORY; PROTECT_STRUCT(*dep); mod = ctx->parsed_module; if (UNLIKELY(!init_dep(ctx, mod, dep, h, len, modname))) { return CSLUL_E_OUTOFMEMORY; } dep->flags = 0; /* A tree is used to detect duplicates */ res = tree_insert(ctx, &mod->deps_root, h, len, modname, &dep->node, 0); if (res == NULL) return (enum CSlulErrorCode)-1; else if (UNLIKELY(!res->is_new)) return CSLUL_E_DUPLICATEDEP; set_mh_sourceline(ctx, &dep->node); return CSLUL_E_NOERROR; } /** * Finds or adds a dependency. Only returns NULL on out-of-memory. * * Used for \interface_depends dependencies. */ struct CSlulDependency *findadd_dep(struct CSlul *ctx, HashCode h, uint32 len, const char *modname) { struct CSlulDependency *dep; struct Module *mod; int saved_line = ctx->tokline; assert(!ctx->case_insens); mod = ctx->parsed_module; dep = (struct CSlulDependency*)tree_insert(ctx, &mod->deps_root, h, len, modname, NULL, sizeof(*dep)); ctx->tokline = saved_line; if (!dep) return NULL; if (dep->node.is_new) { if (UNLIKELY(!init_dep(ctx, mod, dep, h, len, modname))) { return NULL; } dep->flags = CSLUL_DF_IFACEONLY; set_mh_sourceline(ctx, &dep->node); } return dep; } /** * Adds a module's interface dependency. Returns 0 on success. * If the returned value is neither -1 or CSLUL_E_OUTOFMEMORY, * then it is an error code that should be reported to the user. */ enum CSlulErrorCode build_add_ifacedep(struct CSlul *ctx, struct CSlulDependency *dep, uint32 minverlen, const char *minver, uint32 sinceverhash, uint32 sinceverlen, const char *sincever) { struct CSlulInterfaceDep *idep; assert(!ctx->case_insens); idep = aallocp(ctx, sizeof(*idep)); if (!idep) return CSLUL_E_OUTOFMEMORY; PROTECT_STRUCT(*idep); idep->min_version = minver; idep->minverlen = minverlen; idep->dep = dep; idep->sincever_next = NULL; if (!dep->first_ifacever) dep->first_ifacever = idep; if (dep->last_ifacever) dep->last_ifacever->sincever_next = idep; dep->last_ifacever = idep; if (sincever) { struct TreeNode *res; /* Use a tree to detect duplicate "since versions" */ res = tree_insert(ctx, &dep->ifacever_root, sinceverhash, sinceverlen, sincever, &idep->node, 0); if (res == NULL) return (enum CSlulErrorCode)-1; else if (UNLIKELY(!res->is_new)) return CSLUL_E_DUPLICATEIFACEDEP; } else { idep->node.length = 0; /* Avoid false positives from Valgrind: */ idep->node.rankdelta = 0; idep->node.state = 0; } set_mh_sourceline(ctx, &idep->node); if (UNLIKELY(!sincever && dep->first_ifacever != idep)) { return CSLUL_E_DUPLICATEIFACEDEP; } return CSLUL_E_NOERROR; }