fix: codegen O(n²) HTML memory leak + elb stderr surface + runtime dir path
This commit is contained in:
@@ -1887,6 +1887,34 @@ el_val_t fs_write_bytes(el_val_t pathv, el_val_t bytesv, el_val_t lengthv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// stdout_to_file / stdout_restore — redirect process stdout to a file and
|
||||
// restore it. Used by the compiler's JS post-processing pipeline to capture
|
||||
// codegen output before piping through terser / obfuscator.
|
||||
#include <fcntl.h>
|
||||
static int _el_saved_stdout_fd = -1;
|
||||
|
||||
el_val_t stdout_to_file(el_val_t pathv) {
|
||||
const char* path = EL_CSTR(pathv);
|
||||
if (!path) return (el_val_t)(int64_t)-1;
|
||||
fflush(stdout);
|
||||
_el_saved_stdout_fd = dup(STDOUT_FILENO);
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
if (fd < 0) return (el_val_t)(int64_t)-1;
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
close(fd);
|
||||
return (el_val_t)(int64_t)0;
|
||||
}
|
||||
|
||||
el_val_t stdout_restore(void) {
|
||||
if (_el_saved_stdout_fd >= 0) {
|
||||
fflush(stdout);
|
||||
dup2(_el_saved_stdout_fd, STDOUT_FILENO);
|
||||
close(_el_saved_stdout_fd);
|
||||
_el_saved_stdout_fd = -1;
|
||||
}
|
||||
return (el_val_t)(int64_t)0;
|
||||
}
|
||||
|
||||
// exec_command — run a shell command, return exit code (0 = success).
|
||||
// Used by elb and other El tooling to invoke subprocesses.
|
||||
el_val_t exec_command(el_val_t cmdv) {
|
||||
|
||||
@@ -750,6 +750,10 @@ el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
||||
|
||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
|
||||
@@ -205,40 +205,42 @@ fn next_html_id() -> String {
|
||||
// We build them all into parts, then the caller wraps with concat chain.
|
||||
|
||||
fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
|
||||
// Accumulate fragments into a list to avoid O(n²) string growth.
|
||||
// Each append is O(1); the single str_join at the end is O(total_size).
|
||||
let n: Int = native_list_len(children)
|
||||
let i = 0
|
||||
let out = ""
|
||||
let parts: [String] = native_list_empty()
|
||||
while i < n {
|
||||
let child: Map<String, Any> = native_list_get(children, i)
|
||||
let html_kind: String = child["html"]
|
||||
if str_eq(html_kind, "Text") {
|
||||
let text: String = child["text"]
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(text) + ")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(text) + ")); ")
|
||||
}
|
||||
if str_eq(html_kind, "Doctype") {
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<!doctype html>\")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<!doctype html>\")); ")
|
||||
}
|
||||
if str_eq(html_kind, "Interp") {
|
||||
let val_node = child["value"]
|
||||
let val_c: String = cg_expr(val_node)
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); ")
|
||||
}
|
||||
if str_eq(html_kind, "Raw") {
|
||||
let val_node = child["value"]
|
||||
let val_c: String = cg_expr(val_node)
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", html_raw(" + val_c + ")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_raw(" + val_c + ")); ")
|
||||
}
|
||||
if str_eq(html_kind, "Element") {
|
||||
let elem_c: String = cg_html_element_str(child, acc_var)
|
||||
let out = out + elem_c
|
||||
let parts = native_list_append(parts, elem_c)
|
||||
}
|
||||
if str_eq(html_kind, "Each") {
|
||||
let each_c: String = cg_html_each(child, acc_var)
|
||||
let out = out + each_c
|
||||
let parts = native_list_append(parts, each_c)
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
str_join(parts, "")
|
||||
}
|
||||
|
||||
// Generate open-tag attribute fragments inline.
|
||||
@@ -247,9 +249,10 @@ fn cg_html_parts(children: [Map<String, Any>], acc_var: String) -> String {
|
||||
// Dynamic: "value" is an expr node.
|
||||
// Bool: no "value" field.
|
||||
fn cg_html_attrs_str(attrs: [Map<String, Any>], acc_var: String) -> String {
|
||||
// Accumulate fragments into a list to avoid O(n²) string growth.
|
||||
let n: Int = native_list_len(attrs)
|
||||
let i = 0
|
||||
let out = ""
|
||||
let parts: [String] = native_list_empty()
|
||||
// Closing-quote snippet: EL_STR("\"") in C text.
|
||||
let close_q: String = "EL_STR(" + c_str_lit("\"") + ")"
|
||||
while i < n {
|
||||
@@ -262,26 +265,26 @@ fn cg_html_attrs_str(attrs: [Map<String, Any>], acc_var: String) -> String {
|
||||
if str_eq(kind, "static") {
|
||||
// Static attribute: value is a raw string.
|
||||
let sv: String = attr["value"]
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); "
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(sv) + ")); "
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); ")
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(" + c_str_lit(sv) + ")); ")
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); ")
|
||||
} else {
|
||||
if str_eq(kind, "dynamic") {
|
||||
// Dynamic attribute: value is an expr node — html_escape it.
|
||||
let val_node = attr["value"]
|
||||
let val_c: String = cg_expr(val_node)
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); "
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); "
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + open_attr + "); ")
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", html_escape(" + val_c + ")); ")
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + close_q + "); ")
|
||||
} else {
|
||||
// Boolean attribute (no value): emit " name"
|
||||
let bool_attr: String = "EL_STR(" + c_str_lit(" " + attr_name) + ")"
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", " + bool_attr + "); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", " + bool_attr + "); ")
|
||||
}
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
out
|
||||
str_join(parts, "")
|
||||
}
|
||||
|
||||
// Generate code for a single element, appending into acc_var.
|
||||
@@ -290,19 +293,21 @@ fn cg_html_element_str(elem: Map<String, Any>, acc_var: String) -> String {
|
||||
let attrs: [Map<String, Any>] = elem["attrs"]
|
||||
let children: [Map<String, Any>] = elem["children"]
|
||||
let self_closing: Bool = elem["self_closing"]
|
||||
// Accumulate into a list to avoid O(n²) string growth for deeply nested trees.
|
||||
let parts: [String] = native_list_empty()
|
||||
// Open tag: <tagname
|
||||
let out = acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<" + tag + "\")); "
|
||||
let out = out + cg_html_attrs_str(attrs, acc_var)
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"<" + tag + "\")); ")
|
||||
let parts = native_list_append(parts, cg_html_attrs_str(attrs, acc_var))
|
||||
if self_closing {
|
||||
// Self-closing void element: />
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"/>\")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"/>\")); ")
|
||||
} else {
|
||||
// Close open tag: >
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\">\")); "
|
||||
let out = out + cg_html_parts(children, acc_var)
|
||||
let out = out + acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"</" + tag + ">\")); "
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\">\")); ")
|
||||
let parts = native_list_append(parts, cg_html_parts(children, acc_var))
|
||||
let parts = native_list_append(parts, acc_var + " = el_str_concat(" + acc_var + ", EL_STR(\"</" + tag + ">\")); ")
|
||||
}
|
||||
out
|
||||
str_join(parts, "")
|
||||
}
|
||||
|
||||
// Generate code for {#each list as item} ... {/each}.
|
||||
|
||||
Reference in New Issue
Block a user