#include "compiler.h" #include "compiler-parser.h" #include "hash.h" #include #include #include #include #include #include namespace { const char* UCE_SETUP_SYMBOL = "__uce_set_current_request"; const char* UCE_RENDER_SYMBOL = "__uce_render"; const char* UCE_COMPONENT_SYMBOL = "__uce_component"; const char* UCE_WEBSOCKET_SYMBOL = "__uce_websocket"; const char* UCE_CLI_SYMBOL = "__uce_cli"; const char* UCE_SERVE_HTTP_SYMBOL = "__uce_serve_http"; const char* UCE_ONCE_SYMBOL = "__uce_once"; const char* UCE_INIT_SYMBOL = "__uce_init"; const u64 UCE_UNIT_ABI_VERSION = 6; struct SharedUnitFilesystemState { bool source_exists = false; bool metadata_exists = false; bool compile_output_exists = false; bool metadata_parsed = false; bool abi_compatible = false; bool input_signature_matches = false; time_t source_time = 0; time_t compiled_time = 0; time_t metadata_time = 0; time_t compile_output_time = 0; time_t setup_template_time = 0; time_t compiler_header_time = 0; time_t compiler_source_time = 0; time_t runtime_binary_time = 0; time_t compiler_abi_time = 0; time_t required_time = 0; u64 metadata_abi_version = 0; u64 runtime_abi_version = UCE_UNIT_ABI_VERSION; String metadata_content; String compile_output_content; String metadata_build_token; String metadata_input_signature; String current_input_signature; }; struct SharedUnitCompileCheck { bool source_missing = false; bool needs_compile = false; }; void compiler_unload_failed_shared_unit(SharedUnit* su); bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out = 0); bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out = 0); bool compiler_jit_compile_on_request_enabled(Request* context) { if(!context || !context->server) return(true); return(config_bool("JIT_COMPILE_ON_REQUEST", true)); } bool compiler_is_u64_string(String value) { value = trim(value); if(value == "") return(false); for(auto c : value) { if(c < '0' || c > '9') return(false); } return(true); } StringMap compiler_parse_unit_metadata(String content) { StringMap metadata; content = trim(content); if(content == "") return(metadata); auto lines = split(content, "\n"); for(auto& raw_line : lines) { auto line = trim(raw_line); if(line == "") continue; auto split_pos = line.find("="); if(split_pos == String::npos) continue; auto key = trim(line.substr(0, split_pos)); auto value = trim(line.substr(split_pos + 1)); if(key != "") metadata[key] = value; } return(metadata); } String compiler_unit_input_signature(Request* context, SharedUnit* su) { if(!context || !su || !file_exists(su->file_name)) return(""); String setup_template = context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]; return( gen_sha1(file_get_contents(su->file_name)) + ":" + gen_sha1(file_get_contents(setup_template)) + ":" + std::to_string(UCE_UNIT_ABI_VERSION) ); } String compiler_unit_build_token() { return( std::to_string(getpid()) + ":" + std::to_string((u64)(time_precise() * 1000000.0)) ); } String compiler_unit_metadata_text(Request* context, SharedUnit* su) { return( "format=uce-unit-metadata-v1\n" "unit_abi_version=" + std::to_string(UCE_UNIT_ABI_VERSION) + "\n" "input_signature=" + compiler_unit_input_signature(context, su) + "\n" "build_token=" + compiler_unit_build_token() + "\n" ); } bool compiler_wasm_unit_compile_enabled(Request* context) { if(!context || !context->server) return(false); return(config_bool("COMPILE_WASM_UNITS", false)); } String compiler_wasm_compile_script(Request* context) { String script = "scripts/compile_wasm_unit"; if(context && context->server) script = first(context->server->config["WASM_COMPILE_SCRIPT"], script); if(script != "" && script[0] != '/' && context && context->server) script = path_join(context->server->config["COMPILER_SYS_PATH"], script); return(script); } SharedUnitCompileCheck shared_unit_compile_check(const SharedUnitFilesystemState& state) { SharedUnitCompileCheck result; result.source_missing = !state.source_exists; result.needs_compile = !state.source_exists || state.compiled_time == 0 || state.compiled_time < state.required_time || !state.metadata_exists || !state.metadata_parsed || !state.abi_compatible || !state.input_signature_matches; return(result); } bool compiler_failure_retry_deferred(Request* context, SharedUnit* su, const SharedUnitFilesystemState& state) { if(!context || !su) return(false); if(!state.compile_output_exists || state.compile_output_time == 0) return(false); if(state.source_time == 0) return(false); auto required_failure_inputs_time = std::max({ state.source_time, state.setup_template_time, state.compiler_abi_time }); return(state.compile_output_time >= required_failure_inputs_time); } String compiler_failure_output_for_state(SharedUnit* su, const SharedUnitFilesystemState& state) { if(!state.compile_output_exists) return(""); auto content = trim(state.compile_output_content); if(content != "") return(content); if(su) return(trim(su->compile_error_status)); return(""); } void compiler_restore_persisted_failure(SharedUnit* su, const SharedUnitFilesystemState& state, String status = "compile_error") { if(!su) return; auto message = compiler_failure_output_for_state(su, state); if(message == "") message = "recent compilation failed"; su->compiler_messages = message; su->compile_status = status; su->compile_error_status = message; su->last_error = (state.compile_output_time != 0 ? state.compile_output_time : time()); } time_t compiler_runtime_abi_time(Request* context) { if(!context || !context->server) return(0); String root = context->server->config["COMPILER_SYS_PATH"]; return(std::max({ file_mtime(root + "/src/lib/compiler.h"), file_mtime(root + "/src/lib/compiler.cpp"), file_mtime(root + "/src/lib/compiler-parser.h"), file_mtime(root + "/src/lib/compiler-parser.cpp"), file_mtime(root + "/bin/uce_fastcgi.linux.bin") })); } String compiler_registry_file_name(Request* context) { return(context->server->config["BIN_DIRECTORY"] + "/known-uce-files.txt"); } String compiler_registry_lock_file_name(Request* context) { return(compiler_registry_file_name(context) + ".lock"); } int compiler_open_lock_file(String file_name, String purpose) { (void)purpose; auto lock_dir = dirname(file_name); if(lock_dir != "") mkdir(lock_dir); int fdlock = file_open_locked(file_name, O_RDWR | O_CREAT, LOCK_EX, 0666); if(fdlock == -1) printf("(!) Could not open lock file %s\n", file_name.c_str()); return(fdlock); } void compiler_close_lock_file(int fdlock) { file_close_locked(fdlock); } String compiler_normalize_unit_path(Request* context, String file_name) { file_name = trim(file_name); if(file_name == "") return(""); if(file_name[0] != '/') file_name = expand_path(file_name, context->server->config["COMPILER_SYS_PATH"]); return(file_name); } bool compiler_is_known_unit_file(String file_name) { return(file_name.length() >= 4 && file_name.substr(file_name.length() - 4) == ".uce"); } StringList compiler_normalize_unit_list(Request* context, StringList files) { StringList normalized; for(auto& file_name : files) { auto normalized_name = compiler_normalize_unit_path(context, trim(file_name)); if(normalized_name != "" && compiler_is_known_unit_file(normalized_name)) normalized.push_back(normalized_name); } std::sort(normalized.begin(), normalized.end()); normalized.erase(std::unique(normalized.begin(), normalized.end()), normalized.end()); return(normalized); } StringList compiler_read_known_units_unlocked(Request* context) { auto content = trim(file_get_contents(compiler_registry_file_name(context))); if(content == "") return(StringList()); return(compiler_normalize_unit_list(context, split(content, "\n"))); } void compiler_write_known_units_unlocked(Request* context, StringList files) { files = compiler_normalize_unit_list(context, files); if(files.size() == 0) { file_put_contents(compiler_registry_file_name(context), ""); return; } file_put_contents(compiler_registry_file_name(context), join(files, "\n") + "\n"); } template auto compiler_with_registry_lock(Request* context, TCallback callback) -> decltype(callback()) { auto lock_file_name = compiler_registry_lock_file_name(context); int fdlock = compiler_open_lock_file(lock_file_name, "compiler-registry"); if(fdlock == -1) throw std::runtime_error("could not open compiler registry lock: " + lock_file_name); try { auto result = callback(); compiler_close_lock_file(fdlock); return(result); } catch(...) { compiler_close_lock_file(fdlock); throw; } } bool compiler_has_known_unit_cached(Request* context, String file_name) { if(!context) return(false); return(compiler_with_registry_lock(context, [&]() { auto files = compiler_read_known_units_unlocked(context); return(std::find(files.begin(), files.end(), file_name) != files.end()); })); } SharedUnitFilesystemState inspect_shared_unit_filesystem(Request* context, SharedUnit* su) { SharedUnitFilesystemState state; state.source_exists = file_exists(su->file_name); if(state.source_exists) state.source_time = file_mtime(su->file_name); state.setup_template_time = file_mtime( context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"] ); state.compiler_header_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/src/lib/compiler.h"); state.compiler_source_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/src/lib/compiler.cpp"); state.runtime_binary_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/bin/uce_fastcgi.linux.bin"); state.compiler_abi_time = compiler_runtime_abi_time(context); state.metadata_time = file_mtime(su->meta_file_name); state.compile_output_time = file_mtime(su->compile_output_file_name); state.current_input_signature = compiler_unit_input_signature(context, su); state.metadata_exists = (state.metadata_time != 0); state.compile_output_exists = (state.compile_output_time != 0); if(state.metadata_exists) { state.metadata_content = file_get_contents(su->meta_file_name); auto metadata = compiler_parse_unit_metadata(state.metadata_content); auto abi_it = metadata.find("unit_abi_version"); if(abi_it != metadata.end() && compiler_is_u64_string(abi_it->second)) { state.metadata_abi_version = (u64)atoll(abi_it->second.c_str()); state.metadata_parsed = true; } auto input_it = metadata.find("input_signature"); if(input_it != metadata.end()) state.metadata_input_signature = input_it->second; auto build_it = metadata.find("build_token"); if(build_it != metadata.end()) state.metadata_build_token = build_it->second; } if(state.compile_output_exists) state.compile_output_content = file_get_contents(su->compile_output_file_name); state.abi_compatible = (state.metadata_parsed && state.metadata_abi_version == state.runtime_abi_version); state.input_signature_matches = ( state.metadata_parsed && state.metadata_input_signature != "" && state.current_input_signature != "" && state.metadata_input_signature == state.current_input_signature ); state.required_time = std::max({state.source_time, state.setup_template_time, state.compiler_abi_time}); state.compiled_time = file_mtime(su->so_name); return(state); } void compiler_record_observed_filesystem_state(SharedUnit* su, const SharedUnitFilesystemState& state) { if(!su) return; su->observed_compiled_time = state.compiled_time; su->observed_metadata_content = state.metadata_content; } bool shared_unit_cache_is_stale(Request* context, SharedUnit* su) { if(!su) return(true); auto state = inspect_shared_unit_filesystem(context, su); if(shared_unit_compile_check(state).needs_compile) return(true); if(state.compiled_time != su->observed_compiled_time) return(true); if(state.metadata_content != su->observed_metadata_content) return(true); return(false); } void release_shared_unit_cache_entry(Request* context, String file_name) { auto it = context->server->units.find(file_name); if(it == context->server->units.end()) return; delete it->second; context->server->units.erase(it); } SharedUnit* compiler_cached_unit(Request* context, String file_name) { auto it = context->server->units.find(file_name); if(it == context->server->units.end()) return(0); return(it->second); } bool compiler_cache_mode_matches(SharedUnit* su, bool opt_so_optional) { return(su && su->opt_so_optional == opt_so_optional); } bool compiler_cached_unit_is_reusable(Request* context, SharedUnit* su, bool opt_so_optional, bool force_recompile) { return( !force_recompile && compiler_cache_mode_matches(su, opt_so_optional) && !shared_unit_cache_is_stale(context, su) ); } SharedUnit* compiler_reusable_cached_unit(Request* context, String file_name, bool opt_so_optional, bool force_recompile) { auto su = compiler_cached_unit(context, file_name); if(compiler_cached_unit_is_reusable(context, su, opt_so_optional, force_recompile)) return(su); return(0); } void compiler_release_cached_unit_if_needed(Request* context, String file_name, bool opt_so_optional, bool force_recompile) { auto su = compiler_cached_unit(context, file_name); if(!su) return; if(force_recompile || !compiler_cache_mode_matches(su, opt_so_optional) || shared_unit_cache_is_stale(context, su)) release_shared_unit_cache_entry(context, file_name); } String compiler_current_unit_path(Request* context) { if(!context) return(""); return(first( context->resources.current_unit_file, context->params["SCRIPT_FILENAME"] )); } String compiler_resolve_unit_path(Request* context, String file_name, String current_path = "") { if(!context) return(""); file_name = trim(file_name); if(file_name == "") file_name = compiler_current_unit_path(context); if(file_name == "") return(""); if(current_path == "") { auto current_unit_file = compiler_current_unit_path(context); if(current_unit_file != "") current_path = dirname(current_unit_file); else current_path = cwd_get(); } if(file_name[0] != '/') file_name = expand_path(file_name, current_path); return(compiler_normalize_unit_path(context, file_name)); } f64 compiler_average(f64 total, u64 count) { if(count == 0) return(0); return(total / (f64)count); } void compiler_record_compile_result(SharedUnit* su, f64 duration, bool success, String status, String error_status = "") { su->compile_count += 1; su->last_compile_duration = duration; su->total_compile_duration += duration; if(su->best_compile_duration == 0 || duration < su->best_compile_duration) su->best_compile_duration = duration; if(duration > su->worst_compile_duration) su->worst_compile_duration = duration; if(success) { su->compile_success_count += 1; su->compile_status = status; su->compile_error_status = ""; } else { su->compile_failure_count += 1; su->compile_status = status; su->compile_error_status = error_status; su->last_error = time(); } } void compiler_begin_render_result(SharedUnit* su, bool count_request) { su->invoke_count += 1; if(count_request) su->request_count += 1; su->last_rendered = time(); } void compiler_record_render_result(SharedUnit* su, f64 duration, bool success, String error_status = "") { su->last_render_duration = duration; su->total_render_duration += duration; if(su->best_render_duration == 0 || duration < su->best_render_duration) su->best_render_duration = duration; if(duration > su->worst_render_duration) su->worst_render_duration = duration; if(success) { su->runtime_error_status = ""; } else { su->runtime_error_count += 1; su->runtime_error_status = error_status; su->last_error = time(); } } String compiler_error_status(SharedUnit* su) { if(!su) return(""); if(trim(su->compile_error_status) != "") return(su->compile_error_status); return(su->runtime_error_status); } String compiler_status_from_filesystem(const SharedUnitFilesystemState& state, SharedUnit* su = 0) { auto compile_check = shared_unit_compile_check(state); if(!state.source_exists) return("missing_source"); if(state.compiled_time == 0 || !state.metadata_exists) return("not_compiled"); if(compile_check.needs_compile) return("stale"); if(su && su->so_handle) return("loaded"); return("compiled"); } StringList compiler_unit_exports(SharedUnit* su) { StringList exports; if(su && su->api_declarations.size() > 0) exports = su->api_declarations; for(auto it = exports.begin(); it != exports.end();) { *it = trim(*it); if(*it == "") { it = exports.erase(it); continue; } ++it; } return(exports); } String compiler_unit_exports_text(SharedUnit* su) { if(!su || su->api_file_name == "") return(""); return(trim(file_get_contents(su->api_file_name))); } void compiler_tree_push_string(DValue& tree, String value) { DValue item; item = value; tree.push(item); } void compiler_tree_push_strings(DValue& tree, StringList values) { tree.set_array(); for(auto& value : values) compiler_tree_push_string(tree, value); } void compiler_tree_set_bool(DValue& tree, String key, bool value) { tree[key].set_bool(value); } bool compiler_is_request_entry_unit(Request* context, SharedUnit* su) { if(!context || !su) return(false); auto request_file = compiler_normalize_unit_path(context, context->params["SCRIPT_FILENAME"]); return(request_file != "" && request_file == su->file_name); } bool compiler_can_write_response(Request* context) { return( context && context->params["REQUEST_METHOD"] != "" && context->ob && context->ob_stack.size() > 0 ); } } String preprocess_shared_unit(Request* context, SharedUnit* su) { String content = file_get_contents(su->file_name); return(compiler_preprocess_source(context, su, content)); } String compiler_generated_cpp_path(Request* context, String source_file) { if(!context || !context->server || source_file == "") return(""); return(context->server->config["BIN_DIRECTORY"] + dirname(source_file) + "/" + basename(source_file) + ".cpp"); } String compiler_generated_cpp_path(SharedUnit* su) { if(!su) return(""); return(su->pre_path + "/" + su->pre_file_name); } void setup_unit_paths(Request* context, SharedUnit* su, String file_name) { su->file_name = file_name; if(su->src_path.length() > 0) // we did this already return; su->src_path = dirname(file_name); su->bin_path = context->server->config["BIN_DIRECTORY"] + su->src_path; su->pre_path = context->server->config["BIN_DIRECTORY"] + su->src_path; su->src_file_name = basename(file_name); su->bin_file_name = su->src_file_name + ".so"; su->wasm_file_name = su->src_file_name + ".wasm"; su->pre_file_name = su->src_file_name + ".cpp"; su->so_name = su->bin_path + "/" + su->bin_file_name; su->wasm_name = su->bin_path + "/" + su->wasm_file_name; su->wasm_check_file_name = su->bin_path + "/" + su->src_file_name + ".wasm-check.txt"; su->api_file_name = su->bin_path + "/" + su->src_file_name + ".exports.txt"; su->meta_file_name = su->bin_path + "/" + su->src_file_name + ".meta.txt"; su->compile_output_file_name = su->bin_path + "/" + su->src_file_name + ".compile.txt"; //su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h"; } void load_shared_unit(Request* context, SharedUnit* su) { su->on_render = 0; su->on_component = 0; su->on_websocket = 0; su->on_once = 0; su->on_init = 0; su->on_setup = 0; su->so_handle = 0; su->compiler_messages = ""; if(!file_exists(su->so_name)) { if(su->opt_so_optional) { su->compile_status = "not_compiled"; return; } auto fs_state = inspect_shared_unit_filesystem(context, su); if(compiler_failure_retry_deferred(context, su, fs_state)) { compiler_restore_persisted_failure(su, fs_state); return; } //printf("(i) unit file not found: %s\n", su->so_name.c_str()); su->compiler_messages = "unit file not found"; su->compile_status = "not_compiled"; su->compile_error_status = su->compiler_messages; su->last_error = time(); return; } su->so_handle = dlopen(su->so_name.c_str(), RTLD_NOW); if(su->so_handle) { su->last_compiled = file_mtime(su->so_name); su->last_loaded = time(); su->compile_status = "loaded"; su->compile_error_status = ""; char *error; su->on_setup = (request_handler)dlsym(su->so_handle, UCE_SETUP_SYMBOL); if ((error = dlerror()) != NULL) printf("Error - %s in %s\n", error, su->file_name.c_str()); su->on_render = (request_ref_handler)dlsym(su->so_handle, UCE_RENDER_SYMBOL); dlerror(); su->on_component = (request_ref_handler)dlsym(su->so_handle, UCE_COMPONENT_SYMBOL); dlerror(); su->on_websocket = (request_ref_handler)dlsym(su->so_handle, UCE_WEBSOCKET_SYMBOL); dlerror(); su->on_cli = (request_ref_handler)dlsym(su->so_handle, UCE_CLI_SYMBOL); dlerror(); su->on_once = (request_ref_handler)dlsym(su->so_handle, UCE_ONCE_SYMBOL); dlerror(); su->on_init = (request_ref_handler)dlsym(su->so_handle, UCE_INIT_SYMBOL); dlerror(); su->api_declarations = split(file_get_contents(su->api_file_name), "\n"); String init_error = ""; if(!compiler_run_unit_init(context, su, &init_error)) { if(init_error != "") su->compiler_messages = init_error; compiler_unload_failed_shared_unit(su); } //else // printf("(i) loaded unit %s\n", su->file_name.c_str()); } else { const char* dl_error = dlerror(); su->compiler_messages = "could not open " + su->so_name; if(dl_error && String(dl_error) != "") su->compiler_messages += ": " + String(dl_error); su->compile_status = "load_error"; su->compile_error_status = su->compiler_messages; su->last_error = time(); printf("Error loading unit %s, %s\n", su->file_name.c_str(), su->compiler_messages.c_str()); } } /*String compile_setup_file(Request* context, SharedUnit* su) { String result = String("#ifndef UCE_LIB_INCLUDED\n") + "#define UCE_LIB_INCLUDED\n" + "#include \"uce_lib.h\" \n"+ file_get_contents( context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"]) + "#endif \n"; return(result); }*/ s64 compiler_first_error_line_for_path(String messages, String path) { if(path == "") return(-1); String needle = path + ":"; auto pos = messages.find(needle); if(pos == String::npos) return(-1); pos += needle.length(); String digits; while(pos < messages.length() && isdigit((unsigned char)messages[pos])) { digits.append(1, messages[pos]); pos += 1; } if(digits == "") return(-1); return(int_val(digits)); } String compiler_source_excerpt(String file_name, s64 line_number, u64 radius = 3) { if(file_name == "" || line_number <= 0 || !file_exists(file_name)) return(""); auto lines = split(file_get_contents(file_name), "\n"); if(lines.size() == 0) return(""); s64 start = line_number - (s64)radius; if(start < 1) start = 1; s64 end = line_number + (s64)radius; if(end > (s64)lines.size()) end = lines.size(); String result; for(s64 i = start; i <= end; i++) { String marker = (i == line_number ? "> " : " "); result += marker + std::to_string((u64)i) + " | " + lines[i - 1] + "\n"; } return(result); } String compiler_format_compile_failure(SharedUnit* su, String raw_messages) { String generated_file = compiler_generated_cpp_path(su); String result = "UCE compile error\n"; result += "Source: " + su->file_name + "\n"; result += "Generated C++: " + generated_file + "\n"; result += "Compile output: " + su->compile_output_file_name + "\n"; s64 source_line = compiler_first_error_line_for_path(raw_messages, su->file_name); String excerpt = compiler_source_excerpt(su->file_name, source_line); if(excerpt != "") result += "\nSource excerpt:\n" + excerpt; else { s64 generated_line = compiler_first_error_line_for_path(raw_messages, generated_file); String generated_excerpt = compiler_source_excerpt(generated_file, generated_line); if(generated_excerpt != "") result += "\nGenerated C++ excerpt:\n" + generated_excerpt; } result += "\nCompiler output:\n" + trim(raw_messages) + "\n"; return(result); } void compile_shared_unit(Request* context, SharedUnit* su) { f64 comp_start = time_precise(); if(!file_exists(su->file_name)) { su->compiler_messages = "source file not found (" + su->file_name + ")"; file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n"); compiler_untrack_known_unit(context, su->file_name); compiler_record_compile_result(su, time_precise() - comp_start, false, "missing_source", su->compiler_messages); return; } shell_exec("mkdir -p " + shell_escape(su->pre_path)); file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su)); file_put_contents(su->api_file_name, join(su->api_declarations, "\n")); if(!su->opt_so_optional) su->compiler_messages = trim(shell_exec(shell_escape(context->server->config["COMPILE_SCRIPT"])+" "+ shell_escape(su->src_path)+" "+ shell_escape(su->bin_path)+" "+ shell_escape(su->file_name)+" "+ shell_escape(su->pre_file_name)+" "+ shell_escape(su->bin_file_name) )); if(su->compiler_messages.length() == 0 && !su->opt_so_optional && compiler_wasm_unit_compile_enabled(context)) su->compiler_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+ shell_escape(su->src_path)+" "+ shell_escape(su->bin_path)+" "+ shell_escape(su->file_name)+" "+ shell_escape(su->pre_file_name)+" "+ shell_escape(su->wasm_file_name) )); if(su->compiler_messages.length() > 0) { String raw_messages = su->compiler_messages; file_put_contents(su->compile_output_file_name, raw_messages + "\n"); compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", raw_messages); printf("%s \n", compiler_format_compile_failure(su, raw_messages).c_str()); } else { load_shared_unit(context, su); if(su->so_handle) { file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su)); file_unlink(su->compile_output_file_name); } else if(trim(su->compiler_messages) != "") { file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n"); } compiler_record_compile_result( su, time_precise() - comp_start, (su->so_handle != 0), (su->so_handle ? "loaded" : "load_error"), compiler_error_status(su) ); printf("(i) compiled unit %s in %f s\n", (su->pre_path + "/" + su->pre_file_name).c_str(), time_precise() - comp_start); } } SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name, bool opt_so_optional, bool force_recompile) { file_name = compiler_normalize_unit_path(context, file_name); auto cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile); if(cached) return(cached); compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile); SharedUnit* su = new SharedUnit(); setup_unit_paths(context, su, file_name); su->opt_so_optional = opt_so_optional; int fdlock = compiler_open_lock_file(su->so_name + ".lock", "shared-unit:" + file_name); if(fdlock == -1) { su->compiler_messages = "could not open compile lock"; su->compile_status = "lock_error"; su->compile_error_status = su->compiler_messages; su->last_error = time(); context->server->units[file_name] = su; return(su); } cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile); if(cached) { compiler_close_lock_file(fdlock); delete su; return(cached); } compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile); auto state = inspect_shared_unit_filesystem(context, su); auto compile_check = shared_unit_compile_check(state); bool retry_deferred = compiler_failure_retry_deferred(context, su, state); bool jit_enabled = compiler_jit_compile_on_request_enabled(context); bool do_recompile = force_recompile || compile_check.needs_compile; if(do_recompile) { if(!force_recompile && retry_deferred) compiler_restore_persisted_failure(su, state); else if(!force_recompile && !jit_enabled) { compiler_restore_persisted_failure(su, state, "jit_compile_disabled"); if(trim(su->compiler_messages) == "") { su->compiler_messages = "JIT compilation on request is disabled"; su->compile_error_status = su->compiler_messages; } } else compile_shared_unit(context, su); } else { load_shared_unit(context, su); if(!su->so_handle) { if(!force_recompile && retry_deferred) compiler_restore_persisted_failure(su, state); else if(!force_recompile && !jit_enabled) { compiler_restore_persisted_failure(su, state, "jit_compile_disabled"); if(trim(su->compiler_messages) == "") { su->compiler_messages = "JIT compilation on request is disabled"; su->compile_error_status = su->compiler_messages; } } else compile_shared_unit(context, su); } } auto observed_state = inspect_shared_unit_filesystem(context, su); compiler_record_observed_filesystem_state(su, observed_state); compiler_close_lock_file(fdlock); context->server->units[file_name] = su; return(su); } SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional) { return(compiler_get_shared_unit_internal(context, file_name, opt_so_optional, false)); } SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional) { context->stats.invoke_count++; file_name = compiler_resolve_unit_path(context, file_name, current_path); if(file_name == "") return(0); if(file_exists(file_name)) compiler_track_known_unit(context, file_name); else compiler_untrack_known_unit(context, file_name); //printf("(i) load '%s'\n", file_name.c_str()); //switch_to_system_alloc(); auto su = get_shared_unit(context, file_name, opt_so_optional); //switch_to_arena(context->mem); if(!su) { printf("Error loading unit %s\n", file_name.c_str()); if(compiler_can_write_response(context)) print("Error loading unit: " + file_name); return(0); } else if(su->compiler_messages.length() > 0) { String display_messages = su->compiler_messages; if(su->compile_status == "compile_error") display_messages = compiler_format_compile_failure(su, su->compiler_messages); printf("%s\n", display_messages.c_str()); if(compiler_can_write_response(context) && context->stats.invoke_count == 1) { DValue error_info; error_info["type"] = "compiler_error"; error_info["status"] = su->compile_status; error_info["source"] = su->file_name; error_info["compiler_output"] = su->compiler_messages; error_info["generated_cpp"] = compiler_generated_cpp_path(su); error_info["details"] = display_messages; if(compiler_render_error_page(context, "page_compiler_error", 500, "UCE Unit Compile Error", error_info)) return(0); context->set_status(500, "UCE Unit Compile Error"); context->header["Content-Type"] = "text/plain; charset=utf-8"; } if(compiler_can_write_response(context)) print(display_messages); return(0); } else { return(su); } } String compiler_site_directory(Request* context) { String site_directory = first( context->server->config["PRECOMPILE_FILES_IN"], context->server->config["SITE_DIRECTORY"], "site" ); return(compiler_normalize_unit_path(context, site_directory)); } StringList compiler_scan_site_units(Request* context) { StringList files; auto site_directory = compiler_site_directory(context); if(site_directory == "" || !file_exists(site_directory)) return(files); std::error_code walk_error; auto options = std::filesystem::directory_options::skip_permission_denied; for(auto it = std::filesystem::recursive_directory_iterator(site_directory, options, walk_error); it != std::filesystem::recursive_directory_iterator(); it.increment(walk_error)) { if(walk_error) { printf("(!) proactive scan warning in %s: %s\n", site_directory.c_str(), walk_error.message().c_str()); walk_error.clear(); continue; } std::error_code entry_error; if(!it->is_regular_file(entry_error)) continue; if(entry_error) continue; auto path = it->path().string(); if(path.length() >= 4 && path.substr(path.length() - 4) == ".uce") files.push_back(path); } return(compiler_normalize_unit_list(context, files)); } StringList compiler_list_known_units(Request* context) { return(compiler_with_registry_lock(context, [&]() { return(compiler_read_known_units_unlocked(context)); })); } void compiler_set_known_units(Request* context, StringList files) { files = compiler_normalize_unit_list(context, files); compiler_with_registry_lock(context, [&]() { compiler_write_known_units_unlocked(context, files); return(0); }); } void compiler_track_known_unit(Request* context, String file_name) { file_name = compiler_normalize_unit_path(context, file_name); if(file_name == "" || !compiler_is_known_unit_file(file_name)) return; compiler_with_registry_lock(context, [&]() { auto files = compiler_read_known_units_unlocked(context); if(std::find(files.begin(), files.end(), file_name) != files.end()) return(0); files.push_back(file_name); compiler_write_known_units_unlocked(context, files); return(0); }); } void compiler_untrack_known_unit(Request* context, String file_name) { file_name = compiler_normalize_unit_path(context, file_name); if(file_name == "") return; compiler_with_registry_lock(context, [&]() { auto files = compiler_read_known_units_unlocked(context); files.erase(std::remove(files.begin(), files.end(), file_name), files.end()); compiler_write_known_units_unlocked(context, files); return(0); }); } bool compiler_unit_needs_recompile(Request* context, String file_name, bool* source_missing) { file_name = compiler_normalize_unit_path(context, file_name); SharedUnit su; setup_unit_paths(context, &su, file_name); auto state = inspect_shared_unit_filesystem(context, &su); auto compile_check = shared_unit_compile_check(state); if(source_missing) *source_missing = compile_check.source_missing; if(compile_check.source_missing) return(false); if(compiler_failure_retry_deferred(context, &su, state)) return(false); return(compile_check.needs_compile); } DValue unit_info(String path) { DValue info; if(!context) return(info); String resolved_path = compiler_resolve_unit_path(context, path); if(resolved_path == "") return(info); SharedUnit* su = 0; auto it = context->server->units.find(resolved_path); if(it != context->server->units.end()) { su = it->second; } else { auto known_units = compiler_list_known_units(context); if(std::find(known_units.begin(), known_units.end(), resolved_path) == known_units.end() && !file_exists(resolved_path)) return(info); } SharedUnit temp_unit; if(!su) { su = &temp_unit; setup_unit_paths(context, su, resolved_path); } auto fs_state = inspect_shared_unit_filesystem(context, su); if(su->compile_status == "unknown" && compiler_failure_retry_deferred(context, su, fs_state)) compiler_restore_persisted_failure(su, fs_state); auto exports_text = compiler_unit_exports_text(su); auto exports = compiler_unit_exports(su); if(exports.size() == 0 && exports_text != "") exports = split(exports_text, "\n"); info["path"] = resolved_path; info["file_name"] = su->file_name; info["src_path"] = su->src_path; info["bin_path"] = su->bin_path; info["pre_path"] = su->pre_path; info["src_file_name"] = su->src_file_name; info["bin_file_name"] = su->bin_file_name; info["pre_file_name"] = su->pre_file_name; info["so_name"] = su->so_name; info["wasm_name"] = su->wasm_name; info["wasm_exists"].set_bool(file_exists(su->wasm_name)); info["api_file_name"] = su->api_file_name; info["meta_file_name"] = su->meta_file_name; info["compile_output_file_name"] = su->compile_output_file_name; info["compile_status"] = (su->compile_status != "unknown" ? su->compile_status : compiler_status_from_filesystem(fs_state, su)); info["compile_error_status"] = su->compile_error_status; info["runtime_error_status"] = su->runtime_error_status; info["error_status"] = compiler_error_status(su); info["compiler_messages"] = su->compiler_messages; info["last_compiled"] = (f64)su->last_compiled; info["last_loaded"] = (f64)su->last_loaded; info["last_rendered"] = (f64)su->last_rendered; info["last_error"] = (f64)su->last_error; info["request_count"] = (f64)su->request_count; info["invoke_count"] = (f64)su->invoke_count; info["runtime_error_count"] = (f64)su->runtime_error_count; info["compile_count"] = (f64)su->compile_count; info["compile_success_count"] = (f64)su->compile_success_count; info["compile_failure_count"] = (f64)su->compile_failure_count; info["last_compile_time"] = su->last_compile_duration; info["average_compile_time"] = compiler_average(su->total_compile_duration, su->compile_count); info["best_compile_time"] = su->best_compile_duration; info["worst_compile_time"] = su->worst_compile_duration; info["last_render_time"] = su->last_render_duration; info["average_render_time"] = compiler_average(su->total_render_duration, su->invoke_count); info["best_render_time"] = su->best_render_duration; info["worst_render_time"] = su->worst_render_duration; info["source_mtime"] = (f64)fs_state.source_time; info["compiled_mtime"] = (f64)fs_state.compiled_time; info["metadata_mtime"] = (f64)fs_state.metadata_time; info["compile_output_mtime"] = (f64)fs_state.compile_output_time; info["setup_template_mtime"] = (f64)fs_state.setup_template_time; info["required_mtime"] = (f64)fs_state.required_time; info["runtime_abi_version"] = (f64)fs_state.runtime_abi_version; info["metadata_abi_version"] = (f64)fs_state.metadata_abi_version; info["current_input_signature"] = fs_state.current_input_signature; info["metadata_input_signature"] = fs_state.metadata_input_signature; info["metadata_build_token"] = fs_state.metadata_build_token; compiler_tree_set_bool(info, "known", compiler_has_known_unit_cached(context, resolved_path)); compiler_tree_set_bool(info, "current_unit", resolved_path == compiler_current_unit_path(context)); compiler_tree_set_bool(info, "loaded", su->so_handle != 0); compiler_tree_set_bool(info, "source_exists", fs_state.source_exists); compiler_tree_set_bool(info, "compiled_exists", fs_state.compiled_time != 0); compiler_tree_set_bool(info, "metadata_exists", fs_state.metadata_exists); compiler_tree_set_bool(info, "metadata_parsed", fs_state.metadata_parsed); compiler_tree_set_bool(info, "abi_compatible", fs_state.abi_compatible); compiler_tree_set_bool(info, "input_signature_matches", fs_state.input_signature_matches); compiler_tree_set_bool(info, "stale", shared_unit_compile_check(fs_state).needs_compile && !compiler_failure_retry_deferred(context, su, fs_state)); compiler_tree_set_bool(info, "retry_deferred", compiler_failure_retry_deferred(context, su, fs_state)); compiler_tree_set_bool(info, "cache_stale", shared_unit_cache_is_stale(context, su)); compiler_tree_set_bool(info, "has_render", su->on_render != 0); compiler_tree_set_bool(info, "has_component", su->on_component != 0); compiler_tree_set_bool(info, "has_websocket", su->on_websocket != 0); compiler_tree_set_bool(info, "has_setup", su->on_setup != 0); compiler_tree_set_bool(info, "has_error", trim(compiler_error_status(su)) != ""); compiler_tree_push_strings(info["exports"], exports); info["exports_text"] = exports_text; return(info); } StringList units_list() { if(!context) return(StringList()); auto known_units = compiler_list_known_units(context); for(auto& it : context->server->units) known_units.push_back(it.first); return(compiler_normalize_unit_list(context, known_units)); } bool unit_compile(String path) { if(!context) return(false); String resolved_path = compiler_resolve_unit_path(context, path); if(resolved_path == "") return(false); compiler_track_known_unit(context, resolved_path); auto su = compiler_get_shared_unit_internal(context, resolved_path, false, true); return(su && trim(su->compiler_messages) == "" && su->so_handle != 0); } namespace { struct UnitInvocationScope { Request* context = 0; String previous_working_directory; String previous_unit_file; UnitInvocationScope(Request* context, SharedUnit* su) { this->context = context; previous_working_directory = cwd_get(); previous_unit_file = context->resources.current_unit_file; cwd_set(su->src_path); context->resources.current_unit_file = su->file_name; } ~UnitInvocationScope() { if(!context) return; context->resources.current_unit_file = previous_unit_file; cwd_set(previous_working_directory); } }; enum class UnitCallMacroKind { none, render, component, once, init, cli, serve_http }; struct UnitCallMacroTarget { UnitCallMacroKind kind = UnitCallMacroKind::none; String handler_name; }; struct RequestPropsScope { Request* context = 0; DValue previous_props; RequestPropsScope(Request* context, const DValue& props) { this->context = context; if(this->context) { previous_props = this->context->props; this->context->props = props; } } ~RequestPropsScope() { if(context) context->props = previous_props; } }; void compiler_unload_failed_shared_unit(SharedUnit* su) { if(!su) return; if(su->so_handle) dlclose(su->so_handle); su->so_handle = 0; su->api_functions.clear(); su->on_setup = 0; su->on_render = 0; su->on_component = 0; su->on_websocket = 0; su->on_cli = 0; su->on_once = 0; su->on_init = 0; } bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out) { if(!su || !su->on_init) return(true); if(!context) { if(error_out) *error_out = "internal error: INIT() requires a Request context"; return(false); } if(!su->on_setup) { if(error_out) *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; return(false); } UnitInvocationScope invoke_scope(context, su); su->on_setup(context); try { su->on_init(*context); return(true); } catch(...) { su->runtime_error_status = "uncaught exception during INIT"; su->compile_status = "load_error"; su->compile_error_status = su->runtime_error_status; su->last_error = time(); if(error_out) *error_out = su->runtime_error_status; return(false); } } bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out) { if(!su || !su->on_once) return(true); if(!context) { if(error_out) *error_out = "internal error: ONCE() requires a Request context"; return(false); } if(!su->on_setup) { if(error_out) *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; return(false); } if(context->once_units.find(su->file_name) != context->once_units.end()) return(true); context->once_units.insert(su->file_name); UnitInvocationScope invoke_scope(context, su); su->on_setup(context); try { su->on_once(*context); return(true); } catch(...) { context->once_units.erase(su->file_name); su->runtime_error_status = "uncaught exception during ONCE"; su->last_error = time(); if(error_out) *error_out = su->runtime_error_status; throw; } } String unit_call_macro_trim(String function_name) { function_name = trim(function_name); if(function_name.length() >= 2 && function_name.substr(function_name.length() - 2) == "()") function_name = trim(function_name.substr(0, function_name.length() - 2)); return(function_name); } UnitCallMacroTarget unit_call_macro_target(String function_name) { UnitCallMacroTarget target; function_name = unit_call_macro_trim(function_name); if(function_name == "RENDER") { target.kind = UnitCallMacroKind::render; return(target); } if(function_name.rfind("RENDER:", 0) == 0) { target.kind = UnitCallMacroKind::render; target.handler_name = trim(function_name.substr(7)); return(target); } if(function_name == "COMPONENT") { target.kind = UnitCallMacroKind::component; return(target); } if(function_name.rfind("COMPONENT:", 0) == 0) { target.kind = UnitCallMacroKind::component; target.handler_name = trim(function_name.substr(10)); return(target); } if(function_name == "ONCE") { target.kind = UnitCallMacroKind::once; return(target); } if(function_name == "INIT") { target.kind = UnitCallMacroKind::init; return(target); } if(function_name == "CLI") { target.kind = UnitCallMacroKind::cli; return(target); } if(function_name == "SERVE_HTTP") { target.kind = UnitCallMacroKind::serve_http; return(target); } if(function_name.rfind("SERVE_HTTP:", 0) == 0) { target.kind = UnitCallMacroKind::serve_http; target.handler_name = trim(function_name.substr(11)); return(target); } return(target); } } String component_normalize_path(String name) { name = trim(name); if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce") return(name); return(name + ".uce"); } void component_parse_target(String target, String& file_name, String& render_name) { target = trim(target); render_name = ""; auto render_split_pos = target.find(":"); if(render_split_pos != String::npos) { render_name = trim(target.substr(render_split_pos + 1)); target = trim(target.substr(0, render_split_pos)); } file_name = target; } String component_resolve_path(String name, Request* request_context = 0) { String target_name = trim(name); String file_name; String render_name; component_parse_target(target_name, file_name, render_name); if(file_name == "") { if(target_name.rfind(":", 0) == 0 && request_context && request_context->resources.current_unit_file != "") file_name = request_context->resources.current_unit_file; else return(""); } StringList candidates; auto push_candidate = [&] (String candidate) { if(candidate == "") return; candidates.push_back(candidate); }; push_candidate(file_name); push_candidate(component_normalize_path(file_name)); if(file_name.rfind("components/", 0) != 0) { push_candidate("components/" + file_name); push_candidate(component_normalize_path("components/" + file_name)); } std::map seen; for(auto& candidate : candidates) { if(seen[candidate]) continue; seen[candidate] = true; String resolved = candidate; if(resolved[0] != '/') resolved = expand_path(resolved, cwd_get()); if(file_exists(resolved)) return(resolved); } return(""); } String page_render_handler_symbol(String render_name) { render_name = trim(render_name); if(render_name == "" || render_name == "render") return(UCE_RENDER_SYMBOL); return(String(UCE_RENDER_SYMBOL) + "_" + safe_name(render_name)); } String component_handler_symbol(String render_name) { render_name = trim(render_name); if(render_name == "") return(UCE_COMPONENT_SYMBOL); return(String(UCE_COMPONENT_SYMBOL) + "_" + safe_name(render_name)); } String serve_http_handler_symbol(String handler_name) { handler_name = trim(handler_name); if(handler_name == "") return(UCE_SERVE_HTTP_SYMBOL); return(String(UCE_SERVE_HTTP_SYMBOL) + "_" + safe_name(handler_name)); } String compiler_missing_request_handler_message(UnitCallMacroKind kind, String handler_name) { handler_name = trim(handler_name); if(kind == UnitCallMacroKind::render) { if(handler_name == "" || handler_name == "render") return("no RENDER() entry point"); return("no RENDER:" + handler_name + "() entry point"); } if(kind == UnitCallMacroKind::component) { if(handler_name == "") return("no COMPONENT() entry point"); return("no COMPONENT:" + handler_name + "() entry point"); } if(kind == UnitCallMacroKind::once) return("no ONCE() entry point"); if(kind == UnitCallMacroKind::cli) return("no CLI() entry point"); if(kind == UnitCallMacroKind::serve_http) { if(handler_name == "") return("no SERVE_HTTP() entry point"); return("no SERVE_HTTP:" + handler_name + "() entry point"); } if(kind == UnitCallMacroKind::init) return("no INIT() entry point"); return("request handler not found"); } bool compiler_prepare_request_handler(Request* context, SharedUnit* su, String* error_out = 0, bool run_once = false) { if(!su->on_setup) { if(error_out) *error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name; return(false); } if(run_once && !compiler_run_unit_once_if_needed(context, su, error_out)) return(false); return(true); } void compiler_execute_request_handler( Request* context, SharedUnit* su, request_ref_handler handler, bool count_request, String runtime_error_status ) { UnitInvocationScope invoke_scope(context, su); su->on_setup(context); f64 render_start = time_precise(); compiler_begin_render_result(su, count_request); try { handler(*context); compiler_record_render_result(su, time_precise() - render_start, true); } catch(...) { compiler_record_render_result( su, time_precise() - render_start, false, runtime_error_status ); throw; } } request_ref_handler get_request_handler_symbol(SharedUnit* su, String symbol, request_ref_handler default_handler = 0) { if(default_handler && symbol == "") return(default_handler); auto it = su->api_functions.find(symbol); if(it != su->api_functions.end()) return((request_ref_handler)it->second); auto handler = (request_ref_handler)dlsym(su->so_handle, symbol.c_str()); dlerror(); su->api_functions[symbol] = (void*)handler; return(handler); } request_ref_handler get_page_render_handler(SharedUnit* su, String render_name) { String symbol = page_render_handler_symbol(render_name); if(symbol == UCE_RENDER_SYMBOL) return(su->on_render); return(get_request_handler_symbol(su, symbol)); } request_ref_handler get_component_handler(SharedUnit* su, String render_name) { String symbol = component_handler_symbol(render_name); if(symbol == UCE_COMPONENT_SYMBOL) return(su->on_component); return(get_request_handler_symbol(su, symbol)); } request_ref_handler get_serve_http_handler(SharedUnit* su, String handler_name) { return(get_request_handler_symbol(su, serve_http_handler_symbol(handler_name))); } bool compiler_invoke_loaded_request_handler( Request* context, SharedUnit* su, request_ref_handler handler, UnitCallMacroKind kind, String handler_name, bool count_request, String runtime_error_status, String* error_out = 0 ) { if(!compiler_prepare_request_handler(context, su, error_out, true)) return(false); if(!handler) { if(error_out) *error_out = compiler_missing_request_handler_message(kind, handler_name); return(false); } compiler_execute_request_handler( context, su, handler, count_request, runtime_error_status ); return(true); } bool compiler_invoke_render(Request* context, String file_name, String render_name, String* error_out = 0) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(!su) return(false); return(compiler_invoke_loaded_request_handler( context, su, get_page_render_handler(su, render_name), UnitCallMacroKind::render, render_name, compiler_is_request_entry_unit(context, su), "uncaught exception during render", error_out )); } bool compiler_invoke_component(Request* context, String file_name, String render_name, String* error_out = 0) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(!su) return(false); return(compiler_invoke_loaded_request_handler( context, su, get_component_handler(su, render_name), UnitCallMacroKind::component, render_name, false, "uncaught exception during component render", error_out )); } String compiler_error_page_unit(Request* context, String config_key) { if(!context || !context->server) return(""); String configured = trim(first( context->server->config[config_key], context->server->config[to_upper(config_key)] )); if(configured == "") return(""); // Resolve relative to where the server was started, not the volatile // per-unit working directory (which is also stale right after a fault). String resolved = compiler_resolve_unit_path(context, configured, process_start_directory()); if(resolved == "" || !file_exists(resolved)) return(""); return(resolved); } bool compiler_render_error_page(Request* context, String config_key, s32 status_code, String status_reason, DValue error_info) { if(!compiler_can_write_response(context)) return(false); if(!context->server || context->resources.error_page_active) return(false); String unit_file = compiler_error_page_unit(context, config_key); if(unit_file == "") return(false); String previous_response_code = context->response_code; s32 previous_status = context->flags.status; String previous_content_type = context->header["Content-Type"]; context->resources.error_page_active = true; context->call["error"] = error_info; context->set_status(status_code, status_reason); context->header["Content-Type"] = first(context->server->config["CONTENT_TYPE"], "text/html; charset=utf-8"); ob_start(); String error_message = ""; bool rendered = compiler_invoke_render(context, unit_file, "render", &error_message); String html = ob_get_close(); context->resources.error_page_active = false; if(!rendered) { printf("(!) configured %s page %s failed to render: %s\n", config_key.c_str(), unit_file.c_str(), trim(error_message).c_str()); context->response_code = previous_response_code; context->flags.status = previous_status; context->header["Content-Type"] = previous_content_type; context->call.remove("error"); return(false); } print(html); return(true); } bool compiler_unit_compile_pending(Request* context, String file_name) { String normalized = compiler_normalize_unit_path(context, file_name); if(normalized == "" || !file_exists(normalized)) return(false); if(compiler_reusable_cached_unit(context, normalized, false, false)) return(false); SharedUnit probe; setup_unit_paths(context, &probe, normalized); auto state = inspect_shared_unit_filesystem(context, &probe); if(!shared_unit_compile_check(state).needs_compile) return(false); // A persisted, still-current failure is a compiler error, not a build in // progress: let the load path surface it. if(compiler_failure_retry_deferred(context, &probe, state)) return(false); return(true); } void compiler_invoke(Request* context, String file_name) { printf("(i) compiler_invoke file %s\n", file_name.c_str()); // Entry-unit hook for the configured "compiling" page: when the unit needs // a (re)build that the proactive compiler can deliver, answer immediately // instead of blocking the worker on a synchronous compile. if(context && context->stats.invoke_count == 0 && !context->resources.is_cli && !context->resources.error_page_active && config_bool("PROACTIVE_COMPILE_ENABLED", true) && compiler_error_page_unit(context, "page_compiling") != "") { String resolved = compiler_resolve_unit_path(context, file_name, ""); if(resolved != "" && compiler_unit_compile_pending(context, resolved)) { compiler_track_known_unit(context, resolved); DValue error_info; error_info["type"] = "compiling"; error_info["source"] = resolved; if(compiler_render_error_page(context, "page_compiling", 503, "UCE Unit Compiling", error_info)) return; } } String error_message = ""; if(!compiler_invoke_render(context, file_name, "render", &error_message) && error_message != "") { if(context->stats.invoke_count == 1) context->header["Content-Type"] = "text/plain"; print(error_message); } } void compiler_invoke_cli(Request* context, String file_name) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(!su) return; String error_message = ""; if(!compiler_invoke_loaded_request_handler( context, su, su->on_cli, UnitCallMacroKind::cli, "", compiler_is_request_entry_unit(context, su), "uncaught exception during cli handler", &error_message )) { if(!su->on_cli) context->set_status(404, "CLI Entry Point Not Found"); else context->set_status(500, "CLI Unit Error"); if(error_message != "") print(error_message); } } void compiler_invoke_serve_http(Request* context, String file_name, String handler_name) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(!su) return; String error_message = ""; if(!compiler_invoke_loaded_request_handler( context, su, get_serve_http_handler(su, handler_name), UnitCallMacroKind::serve_http, handler_name, true, "uncaught exception during HTTP server handler", &error_message )) { context->set_status(500, "HTTP Server Handler Error"); if(error_message != "") print(error_message); } } void compiler_invoke_websocket(Request* context, String file_name) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(!su) return; if(!su->on_setup) { printf("internal error: %s() not defined in %s\n", UCE_SETUP_SYMBOL, file_name.c_str()); return; } if(!su->on_websocket) { printf("no WS() entry point in %s\n", file_name.c_str()); return; } compiler_execute_request_handler( context, su, su->on_websocket, compiler_is_request_entry_unit(context, su), "uncaught exception during websocket handler" ); } void unit_render(String file_name) { //printf("(i) unit_render(%s)\n", file_name.c_str()); compiler_invoke(context, file_name); } void unit_render(String file_name, Request& context) { compiler_invoke(&context, file_name); } String component_resolve(String name) { return(component_resolve_path(name, context)); } bool component_exists(String name) { return(component_resolve(name) != ""); } String component_error_banner(String message) { return("
" + html_escape(message) + "
"); } void component_render(String name) { DValue props; component_render(name, props, *context); } void component_render(String name, Request& context) { DValue props; component_render(name, props, context); } void component_render(String name, DValue props) { component_render(name, props, *context); } void component_render(String name, DValue props, Request& context) { String file_name; String render_name; component_parse_target(name, file_name, render_name); String resolved_name = component_resolve_path(name, &context); if(resolved_name == "") { print(component_error_banner("component not found: " + trim(name))); return; } RequestPropsScope props_scope(&context, props); String error_message = ""; if(!compiler_invoke_component(&context, resolved_name, render_name, &error_message) && error_message != "") print(component_error_banner(error_message)); } String component(String name) { DValue props; return(component(name, props, *context)); } String component(String name, Request& context) { DValue props; return(component(name, props, context)); } String component(String name, DValue props) { return(component(name, props, *context)); } String component(String name, DValue props, Request& context) { ob_start(); component_render(name, props, context); return(ob_get_close()); } SharedUnit* unit_load(String file_name) { auto su = compiler_load_shared_unit(context, file_name, "", false); if(su && su->so_handle) { return(su); } else { return(0); } } DValue* unit_call(String file_name, String function_name, DValue* call_param) { DValue* result = 0; auto su = compiler_load_shared_unit(context, file_name, "", false); if(su && su->so_handle) { if(!su->on_setup) { print("internal error: ", UCE_SETUP_SYMBOL, "() not defined in ", file_name, "\n"); } else { auto macro_target = unit_call_macro_target(function_name); if(macro_target.kind != UnitCallMacroKind::none) { RequestPropsScope props_scope(context, (call_param ? *call_param : DValue())); String error_message = ""; if(macro_target.kind == UnitCallMacroKind::render) { if(!compiler_invoke_render(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "") print("Error: unit_call() ", error_message); } else if(macro_target.kind == UnitCallMacroKind::component) { if(!compiler_invoke_component(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "") print("Error: unit_call() ", error_message); } else { request_ref_handler handler = 0; if(macro_target.kind == UnitCallMacroKind::once) handler = su->on_once; else if(macro_target.kind == UnitCallMacroKind::init) handler = su->on_init; else if(macro_target.kind == UnitCallMacroKind::cli) handler = su->on_cli; if(!handler) print("Error: unit_call() ", compiler_missing_request_handler_message(macro_target.kind, macro_target.handler_name)); else if(macro_target.kind == UnitCallMacroKind::cli) { String prepare_error = ""; if(!compiler_prepare_request_handler(context, su, &prepare_error, true)) print("Error: unit_call() ", prepare_error); else compiler_execute_request_handler(context, su, handler, false, "uncaught exception during cli handler"); } else { UnitInvocationScope invoke_scope(context, su); su->on_setup(context); handler(*context); } } } else { auto f = (dv_call_handler)dlsym(su->so_handle, function_name.c_str()); if(!f) { print("Error: unit_call() function '", function_name, "' not found"); } else { UnitInvocationScope invoke_scope(context, su); su->on_setup(context); result = f(call_param); } } } } else { print("Error: unit_call() could not load unit file '", file_name, "'"); } return(result); }