|
| 1 | +/* |
| 2 | + * QuickJS 2026-06-04 oracle for parse-time module cache publication. |
| 3 | + * |
| 4 | + * This is test-only C and links only against the pinned external oracle. The |
| 5 | + * product engine remains Rust-only. |
| 6 | + */ |
| 7 | + |
| 8 | +#include "quickjs.h" |
| 9 | + |
| 10 | +#include <stdio.h> |
| 11 | +#include <string.h> |
| 12 | + |
| 13 | +typedef enum ProbeMode { |
| 14 | + PROBE_SUCCESS, |
| 15 | + PROBE_FAILURE, |
| 16 | +} ProbeMode; |
| 17 | + |
| 18 | +typedef struct ProbeState { |
| 19 | + ProbeMode mode; |
| 20 | + unsigned int check_count; |
| 21 | + unsigned int load_count; |
| 22 | + JSValue nested_module; |
| 23 | +} ProbeState; |
| 24 | + |
| 25 | +static const char *mode_name(ProbeMode mode) |
| 26 | +{ |
| 27 | + return mode == PROBE_SUCCESS ? "success" : "failure"; |
| 28 | +} |
| 29 | + |
| 30 | +static const char *boolean_name(int value) |
| 31 | +{ |
| 32 | + return value ? "true" : "false"; |
| 33 | +} |
| 34 | + |
| 35 | +static const char *promise_state_name(JSPromiseStateEnum state) |
| 36 | +{ |
| 37 | + switch (state) { |
| 38 | + case JS_PROMISE_PENDING: |
| 39 | + return "pending"; |
| 40 | + case JS_PROMISE_FULFILLED: |
| 41 | + return "fulfilled"; |
| 42 | + case JS_PROMISE_REJECTED: |
| 43 | + return "rejected"; |
| 44 | + } |
| 45 | + return "unknown"; |
| 46 | +} |
| 47 | + |
| 48 | +static int check_attributes(JSContext *ctx, void *opaque, |
| 49 | + JSValueConst attributes) |
| 50 | +{ |
| 51 | + ProbeState *state = opaque; |
| 52 | + const char *label = mode_name(state->mode); |
| 53 | + unsigned int check_index = state->check_count++; |
| 54 | + const char *source = state->mode == PROBE_SUCCESS |
| 55 | + ? "export const marker = 99;" |
| 56 | + : "export const broken = ;"; |
| 57 | + |
| 58 | + printf("%s check[%u] attrs=%s has-exception=%s\n", label, check_index, |
| 59 | + JS_IsObject(attributes) ? "object" : "other", |
| 60 | + boolean_name(JS_HasException(ctx))); |
| 61 | + if (check_index != 0) { |
| 62 | + JS_ThrowInternalError(ctx, "unexpected recursive attribute checker"); |
| 63 | + return -1; |
| 64 | + } |
| 65 | + |
| 66 | + state->nested_module = |
| 67 | + JS_Eval(ctx, source, strlen(source), "same.js", |
| 68 | + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
| 69 | + printf("%s reentry result=%s has-exception=%s\n", label, |
| 70 | + JS_IsException(state->nested_module) ? "exception" : "module", |
| 71 | + boolean_name(JS_HasException(ctx))); |
| 72 | + return JS_IsException(state->nested_module) ? -1 : 0; |
| 73 | +} |
| 74 | + |
| 75 | +static JSModuleDef *load_module(JSContext *ctx, const char *module_name, |
| 76 | + void *opaque, JSValueConst attributes) |
| 77 | +{ |
| 78 | + ProbeState *state = opaque; |
| 79 | + |
| 80 | + (void)attributes; |
| 81 | + printf("%s load[%u] name=%s\n", mode_name(state->mode), |
| 82 | + state->load_count++, module_name); |
| 83 | + JS_ThrowReferenceError(ctx, "unexpected loader call for '%s'", |
| 84 | + module_name); |
| 85 | + return NULL; |
| 86 | +} |
| 87 | + |
| 88 | +static int run_success_scenario(void) |
| 89 | +{ |
| 90 | + static const char source[] = |
| 91 | + "import { marker as cachedMarker } from './same.js' with { type: " |
| 92 | + "'probe' };" |
| 93 | + "export const marker = 41;" |
| 94 | + "globalThis.__cachePublicationResult = cachedMarker + 1;"; |
| 95 | + JSRuntime *runtime = JS_NewRuntime(); |
| 96 | + JSContext *context; |
| 97 | + ProbeState state = { |
| 98 | + .mode = PROBE_SUCCESS, |
| 99 | + .nested_module = JS_UNDEFINED, |
| 100 | + }; |
| 101 | + JSValue outer_module = JS_UNDEFINED; |
| 102 | + JSValue evaluation = JS_UNDEFINED; |
| 103 | + JSValue global = JS_UNDEFINED; |
| 104 | + JSValue result = JS_UNDEFINED; |
| 105 | + int32_t result_number = 0; |
| 106 | + int modules_are_distinct; |
| 107 | + int status = 0; |
| 108 | + |
| 109 | + if (!runtime) |
| 110 | + return 2; |
| 111 | + context = JS_NewContext(runtime); |
| 112 | + if (!context) { |
| 113 | + JS_FreeRuntime(runtime); |
| 114 | + return 2; |
| 115 | + } |
| 116 | + JS_SetModuleLoaderFunc2(runtime, NULL, load_module, check_attributes, |
| 117 | + &state); |
| 118 | + |
| 119 | + outer_module = JS_Eval( |
| 120 | + context, source, sizeof(source) - 1, "same.js", |
| 121 | + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
| 122 | + modules_are_distinct = |
| 123 | + !JS_IsException(outer_module) && |
| 124 | + !JS_IsException(state.nested_module) && |
| 125 | + JS_VALUE_GET_PTR(outer_module) != JS_VALUE_GET_PTR(state.nested_module); |
| 126 | + printf("success outer result=%s distinct=%s checks=%u loads=%u " |
| 127 | + "has-exception=%s\n", |
| 128 | + JS_IsException(outer_module) ? "exception" : "module", |
| 129 | + boolean_name(modules_are_distinct), state.check_count, |
| 130 | + state.load_count, boolean_name(JS_HasException(context))); |
| 131 | + if (JS_IsException(outer_module) || !modules_are_distinct || |
| 132 | + state.check_count != 1 || state.load_count != 0 || |
| 133 | + JS_HasException(context)) { |
| 134 | + status = 1; |
| 135 | + goto done; |
| 136 | + } |
| 137 | + |
| 138 | + evaluation = JS_EvalFunction(context, outer_module); |
| 139 | + outer_module = JS_UNDEFINED; |
| 140 | + if (JS_IsException(evaluation)) { |
| 141 | + status = 1; |
| 142 | + goto done; |
| 143 | + } |
| 144 | + global = JS_GetGlobalObject(context); |
| 145 | + result = JS_GetPropertyStr(context, global, "__cachePublicationResult"); |
| 146 | + if (JS_IsException(result) || |
| 147 | + JS_ToInt32(context, &result_number, result) < 0) { |
| 148 | + status = 1; |
| 149 | + goto done; |
| 150 | + } |
| 151 | + printf("success evaluate promise=%s result=%d has-exception=%s\n", |
| 152 | + promise_state_name(JS_PromiseState(context, evaluation)), |
| 153 | + result_number, boolean_name(JS_HasException(context))); |
| 154 | + if (JS_PromiseState(context, evaluation) != JS_PROMISE_FULFILLED || |
| 155 | + result_number != 42 || JS_HasException(context)) { |
| 156 | + status = 1; |
| 157 | + } |
| 158 | + |
| 159 | +done: |
| 160 | + JS_FreeValue(context, result); |
| 161 | + JS_FreeValue(context, global); |
| 162 | + JS_FreeValue(context, evaluation); |
| 163 | + JS_FreeValue(context, outer_module); |
| 164 | + JS_FreeValue(context, state.nested_module); |
| 165 | + JS_FreeContext(context); |
| 166 | + JS_FreeRuntime(runtime); |
| 167 | + return status; |
| 168 | +} |
| 169 | + |
| 170 | +static int run_failure_scenario(void) |
| 171 | +{ |
| 172 | + static const char source[] = |
| 173 | + "import './same.js' with { type: 'probe' };" |
| 174 | + "export const marker = 41;"; |
| 175 | + static const char retry_source[] = "export const marker = 42;"; |
| 176 | + JSRuntime *runtime = JS_NewRuntime(); |
| 177 | + JSContext *context; |
| 178 | + ProbeState state = { |
| 179 | + .mode = PROBE_FAILURE, |
| 180 | + .nested_module = JS_UNDEFINED, |
| 181 | + }; |
| 182 | + JSValue outer_module; |
| 183 | + JSValue exception; |
| 184 | + JSValue exception_name; |
| 185 | + JSValue retry_module; |
| 186 | + const char *exception_name_string; |
| 187 | + int status = 0; |
| 188 | + |
| 189 | + if (!runtime) |
| 190 | + return 2; |
| 191 | + context = JS_NewContext(runtime); |
| 192 | + if (!context) { |
| 193 | + JS_FreeRuntime(runtime); |
| 194 | + return 2; |
| 195 | + } |
| 196 | + JS_SetModuleLoaderFunc2(runtime, NULL, load_module, check_attributes, |
| 197 | + &state); |
| 198 | + |
| 199 | + outer_module = JS_Eval( |
| 200 | + context, source, sizeof(source) - 1, "same.js", |
| 201 | + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
| 202 | + printf("failure outer result=%s checks=%u loads=%u has-exception=%s\n", |
| 203 | + JS_IsException(outer_module) ? "exception" : "module", |
| 204 | + state.check_count, state.load_count, |
| 205 | + boolean_name(JS_HasException(context))); |
| 206 | + if (!JS_IsException(outer_module) || state.check_count != 1 || |
| 207 | + state.load_count != 0 || !JS_HasException(context)) { |
| 208 | + JS_FreeValue(context, outer_module); |
| 209 | + status = 1; |
| 210 | + goto done; |
| 211 | + } |
| 212 | + |
| 213 | + exception = JS_GetException(context); |
| 214 | + exception_name = JS_GetPropertyStr(context, exception, "name"); |
| 215 | + exception_name_string = JS_ToCString(context, exception_name); |
| 216 | + printf("failure exception=%s has-exception-after-get=%s\n", |
| 217 | + exception_name_string ? exception_name_string : "<non-string>", |
| 218 | + boolean_name(JS_HasException(context))); |
| 219 | + if (!exception_name_string || strcmp(exception_name_string, "SyntaxError") || |
| 220 | + JS_HasException(context)) { |
| 221 | + status = 1; |
| 222 | + } |
| 223 | + if (exception_name_string) |
| 224 | + JS_FreeCString(context, exception_name_string); |
| 225 | + JS_FreeValue(context, exception_name); |
| 226 | + JS_FreeValue(context, exception); |
| 227 | + |
| 228 | + retry_module = JS_Eval( |
| 229 | + context, retry_source, sizeof(retry_source) - 1, "same.js", |
| 230 | + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
| 231 | + printf("failure retry result=%s checks=%u loads=%u has-exception=%s\n", |
| 232 | + JS_IsException(retry_module) ? "exception" : "module", |
| 233 | + state.check_count, state.load_count, |
| 234 | + boolean_name(JS_HasException(context))); |
| 235 | + if (JS_IsException(retry_module) || state.check_count != 1 || |
| 236 | + state.load_count != 0 || JS_HasException(context)) { |
| 237 | + status = 1; |
| 238 | + } |
| 239 | + JS_FreeValue(context, retry_module); |
| 240 | + |
| 241 | +done: |
| 242 | + JS_FreeValue(context, state.nested_module); |
| 243 | + JS_FreeContext(context); |
| 244 | + JS_FreeRuntime(runtime); |
| 245 | + return status; |
| 246 | +} |
| 247 | + |
| 248 | +int main(void) |
| 249 | +{ |
| 250 | + int status = run_success_scenario(); |
| 251 | + |
| 252 | + if (status) |
| 253 | + return status; |
| 254 | + return run_failure_scenario(); |
| 255 | +} |
0 commit comments