Previously, the "in" operator swap flag was relayed through generator
context. This broke when call-end handlers switched to
njs_generator_stack_pop(NULL), which released the context before the
swap was read in njs_generate_3addr_operation_end().
The fix derives the swap directly from the node token type
(NJS_TOKEN_IN), eliminating the context relay.
Introduce NJS_TOKEN_OPTIONAL_PRESERVE for optional-chain preserve nodes
instead of reusing OBJECT_VALUE, so OBJECT_VALUE remains strictly an
object/array literal structure token.
Route optional-preserve, object-value, and optional method-preserve
access through dedicated helper functions with narrow assertions,
removing direct u.object/left/right access from general parser and
generator paths.
Previously, grouped optional calls like (o?.m)() resolved the callee
through the optional chain but dispatched via plain FUNCTION_CALL,
losing the original receiver.
The fix stores the receiver on the call node so the upcoming
call-argument reorder can emit METHOD_FRAME with an explicit "this".
Call-expression setup and optional-chain preserve lookup are routed
through named helpers with generator-side validation.
Previously, the generator inferred reference intent from raw AST shape.
Now the parser marks the relevant property node as PROPERTY_REF before
building METHOD_CALL, assignment, or update nodes, and the generator
accepts both PROPERTY and PROPERTY_REF via
njs_generate_is_property_lvalue().
Introduce NJS_TOKEN_PROPERTY_REF as an explicit parser-side marker for
property accesses that carry reference semantics (assignment targets,
delete operands, increment/decrement, method-call receivers).
WebCrypto: validate JWK key type against algorithm in importKey().
Previously, importKey() did not verify that the JWK "kty" field
matched the requested algorithm. For example, importing a JWK with
kty "oct" (symmetric) while specifying an asymmetric algorithm like
ECDH caused a SEGV in EVP_PKEY_free() during cleanup. This happened
because the symmetric key data written into the union's "raw" member
overlapped with the "pkey" pointer, corrupting it.
The fix validates kty before calling any JWK import function:
- "RSA" is only accepted for RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5
- "EC" is only accepted for ECDSA, ECDH
- "oct" is only accepted for HMAC, AES-GCM, AES-CTR, AES-CBC
Dmitry Volyntsev [Thu, 26 Feb 2026 15:45:46 +0000 (07:45 -0800)]
Fixed string offset map corruption in scope values hash.
The issue was introduced in e7caa46d (0.9.5). When compile-time
UTF-8 string constants were copied into the values hash in
njs_scope_value_index(), the string data layout was calculated
incorrectly: the map offset did not account for the null terminator
added in e7caa46d, the "size" variable was overwritten corrupting
the subsequent memcpy, and the offset map was never initialized
to zero.
This caused SEGV/SIGBUS crashes for any multi-byte UTF-8 string
constant with more than 32 characters when accessing a character
at index >= 32 (e.g. via .replace() or bracket notation). The bug
only manifested when the string byte size was 4-byte aligned, as
otherwise alignment padding absorbed the missing byte.
The fix factors out njs_string_data_size() and njs_string_data_init()
helpers shared by njs_string_alloc() and njs_scope_value_index(),
eliminating the duplicated layout logic that caused the divergence.
Fixed logical assignment short-circuit with non-writable properties.
When the logical condition was already satisfied (e.g., falsy for &&=,
truthy for ||=, non-nullish for ??=), the short-circuit jump landed on
the property set instruction instead of past it. This caused spurious
TypeErrors for non-writable, getter-only, and non-extensible property
targets even though no assignment should occur.
This fixes logical assignment introduced in 1a64ba68.
This change fixes 9 more tests in test262.
Dmitry Volyntsev [Wed, 11 Feb 2026 15:13:06 +0000 (07:13 -0800)]
Added support for ||= and &&= logical assignment operators.
Unlike regular compound assignments (+=, -=), these operators
short-circuit: the RHS is not evaluated and no assignment occurs
if the logical condition is already satisfied.
Dmitry Volyntsev [Fri, 13 Feb 2026 05:31:23 +0000 (21:31 -0800)]
Shell: fixed interactive mode detection for piped stdin.
Previously, libedit callback API (rl_callback_read_char) was used for
interactive input processing. Unlike the blocking readline() API, the
callback interface does not work correctly when stdin is a pipe: input
characters are silently dropped and the line handler is never invoked.
Dmitry Volyntsev [Wed, 18 Feb 2026 01:56:22 +0000 (17:56 -0800)]
Replace per-VM indexed array for modules with hash-based lookup.
Previously, evaluated module values were cached in a separate
vm->modules array indexed by module->index assigned at compile time.
This required keeping the array size in sync with shared->module_items,
which was error-prone in interactive mode where new modules could be
compiled across commands.
Instead, store evaluated module values directly in the per-VM module
copy already maintained by vm->modules_hash. This unifies the import
and require() caching paths and eliminates the index-based array along
with module->index and shared->module_items fields.
This fixes `make shell_test` when configured with --debug=YES.
Dmitry Volyntsev [Wed, 25 Feb 2026 07:13:54 +0000 (23:13 -0800)]
Modules: suppressed slab log_nomem for evict shared dict zones.
When evict is enabled, memory allocation failures are expected
and handled by evicting old entries. The slab allocator's
"no memory" log messages are now suppressed for such zones.
Dmitry Volyntsev [Fri, 20 Feb 2026 23:45:25 +0000 (15:45 -0800)]
Modules: fixed expire field truncation in shared dict state files.
The njs_sprintf buffer for the expire field was sized for 10-digit
numbers, but current millisecond timestamps are 13 digits. This caused
silent truncation, making entries appear expired on a full restart.
The issue has been present since eca03622 (0.9.1), which introduced
the shared dictionary state file support.
Dmitry Volyntsev [Tue, 17 Feb 2026 00:20:20 +0000 (16:20 -0800)]
Fixed heap-buffer-overflow in atom hash caused by Symbol().
Previously, there was a key_hash collision between strings and
symbols in the atom hash table that led to use of uninitialized memory
and desynchronization of vm->atom_id_generator.
In the atom hash, string entries used djb_hash(content) as key_hash
while symbol entries used raw atom_id. Since djb_hash produces arbitrary
uint32_t values and atom_ids grow monotonically from NJS_ATOM_SIZE,
these spaces overlapped. When a symbol's atom_id matched an existing
string's djb_hash, njs_flathsh_insert() invoked the test function which
read uninitialized fhq.key fields (the function expected string data,
but the caller never initialized it for symbols). Additionally,
atom_id_generator was incremented before the insert, so on failure the
counter diverged from the actual element count, causing subsequent
njs_atom_to_value() calls to read out of bounds.
The fix is to partition the key_hash space using bit 31.
Dmitry Volyntsev [Tue, 17 Feb 2026 00:16:12 +0000 (16:16 -0800)]
Refactored atom hash into orthogonal find and add operations.
Previously, njs_atom_find_or_add() and njs_atom_find_or_add_string()
duplicated the lookup logic and coupled find with insert. Splitting
them into njs_atom_find() and njs_atom_add() separates concerns:
find does a pure lookup with raw bytes, add inserts a pre-built value.
Dmitry Volyntsev [Thu, 29 Jan 2026 02:36:39 +0000 (18:36 -0800)]
Make Error.stack faster.
Previously, error.stack reported full names for native function (for
example Array.prototype.map -> map), but it was achieved by iteration
through a global object which is slow.
The fix is to report only function name at hand, this loses a bit
of verbosity but make it ~100 times faster.
make benchmark
before
...
exception.stack: 38.421µs, 26027 times/s
exception.native.stack: 226.711µs, 4410 times/s
after
...
exception.stack: 1.239µs, 807356 times/s
exception.native.stack: 2.419µs, 413339 times/s
Zurab Kvachadze [Mon, 26 Jan 2026 14:54:51 +0000 (15:54 +0100)]
auto/cc: Use portable/POSIX 'command -v' instead of 'which'
'which' is not a portable utility as it is not specified by POSIX. Since
auto/cc is already a shell script, use the more direct and portable
'command' builtin to detect $CC.
There are two bugs linked here. The first one is a downstream report of
this issue. The second one is more general information on why 'which'
usage is an issue and should be avoided.
Added "js_load_http_native_module" and "js_load_stream_native_module"
main nginx.conf level directives. The directives load a dynamic
library. For security reason it is only allowed in the main context.
Later, JS code may import modules loaded with these directives
with standard import syntax.
example.conf:
...
js_load_http_native_module /path/to/lib.so;
js_load_http_native_module /path/to/lib2.so as lib2;
http {
js_import main.js;
...
main.js:
import * as lib from 'lib.so';
import * as lib2 from 'lib2';
...
See quickjs.h for the complete QuickJS API reference and
nginx/t/js_native_module.t for a working example.
Modules: extracted config-time merging into separate function.
This introduces ngx_js_merge_conftime_loc_conf() to handle merging
of configuration-time properties.
Normally ngx_js_merge_conf() does all the default value initialization
for child location configurations.
There is a special case for global "http" or "stream" configuration
where the parent configuration needs to be initialized (so it can be
reused by server configurations if no additional directives were
defined in them). But parent configurations are not initialized by
ngx_js_merge_conf().
Most of the ngx_js_loc_conf_t values are only used at runtime, so
only configuration-time values need to be merged in the parent.
The runtime values will be provided from the appropriate
ngx_js_loc_conf_t during request processing.
Previously, configuration-time merging was done inline. Extracting
it into a dedicated function simplifies adding new configuration-time
properties.
Dmitry Volyntsev [Sat, 10 Jan 2026 02:08:56 +0000 (18:08 -0800)]
QuickJS: fixed js_body_filter with multiple chunks.
Previously, last_key atoms was freed too early. Also, js_body_filter.t
is modified to ensure js_body_filter sees multiple data chains to catch
the issue.
Dmitry Volyntsev [Fri, 12 Dec 2025 05:57:18 +0000 (21:57 -0800)]
HTTP: fixed buffer_type inheritance in if blocks.
Previously, when js_body_filter was used inside an if block that
evaluated to true, the data parameter received Buffer type instead
of the expected String type. This happened because buffer_type field
in ngx_http_js_loc_conf_t was not properly initialized, causing the
configuration merge to fail when nginx created a new location context
for if blocks.
Previously, when upstream data was delivered from nginx cache
js_body_filter was not able to process it correctly. In particular,
it was treated as a chain of empty buffers.
The fix is to set r->filter_need_in_memory flag, which ensures
that ngx_http_core_module reads the data into memory before
js_body_filter sees it.
Dmitry Volyntsev [Fri, 24 Oct 2025 03:06:42 +0000 (20:06 -0700)]
Using printing and parsing library from QuickJS.
- Number.prototype.toString(radix)
Improved accuracy for edge cases
Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FREE | JS_DTOA_EXP_DISABLED
- Number.prototype.toFixed(frac)
Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FIXED
Removed old njs_fixed_dtoa() implementation
- Number.prototype.toPrecision(prec)
Reimplemented using njs_dtoa2() with precision format
Removed old njs_dtoa_precision() implementation
- Number.prototype.toExponential(frac)
Reimplemented using njs_dtoa2() with exponential format
Removed old njs_dtoa_exponential() implementation
- parseInt()
Simplified parsing implementation
Removed custom njs_number_radix_parse() helper
- parseFloat()
Simplified parsing implementation
Removed custom njs_number_bin_parse(), njs_number_oct_parse(),
njs_number_dec_parse() and njs_strtod.c module Better handling of large
numbers and denormal floats and invalid inputs.
Fetch: added forward proxy support with HTTPS tunneling.
Supports Basic authentication via Proxy-Authorization header.
- js_fetch_proxy - configures forward proxy URL. It takes proxy URL
as a parameter. The URL may optionally contain user and password.
Parameter value can contain variables. If value is empty,
forward proxy is disabled.
Dmitry Volyntsev [Tue, 14 Oct 2025 00:37:11 +0000 (17:37 -0700)]
QuickJS: fixed r.subrequest() to a location with JS handler.
Previously, when a subrequest location had a JS handler, an object of a
subrequest JS context was provided as an argument to a parent contexts.
This may cause all sorts of problems due to incorrect reference
counting.
After this change in bellard/quickjs@42eb2795 the bug became apparent.
The issue was introduced in commit 04f6dfb (0.9.2) by moving VM
destruction from the pool cleanup handler to the http cleanup handler.
Moving VM destruction to the http cleanup handler broke js_set variable
usage during the log phase, because these variables are called after the
VM has been destroyed.
The fix is to move VM destruction back to the pool cleanup handler, but
use a temporary pool while njs.on('exit', ...) is executing.
build/src/njs_object.dep -MT build/src/njs_object.o \ src/njs_object.c
In file included from src/njs_main.h:18, from src/njs_object.c:8: In
function ‘njs_utf8_copy’, inlined from ‘njs_object_enumerate_string’ at
src/njs_object.c:769:21: src/njs_utf8.h:115:20: error: writing 1 byte
into a region of size 0 [-Werror=stringop-overflow=] 115 |
*dst++ = c; | ~~~~~~~^~~ src/njs_object.c: In function
‘njs_object_enumerate_string’: src/njs_object.c:719:24: note: at offset
4 into destination object ‘buf’ of size 4 719 | u_char
buf[4], *c; | ^~~ In function ‘njs_utf8_copy’,
inlined from ‘njs_object_enumerate_string’ at src/njs_object.c:769:21:
src/njs_utf8.h:115:20: error: writing 1 byte into a region of size 0
[-Werror=stringop-overflow=]
GCC-15 does not know that the loop in njs_utf8_copy() is bounded
because the input is a valid UTF-8 here.