**Bug report: Inconsistent cell class naming and view file resolution**

### Observed behavior

1. **Unpredictable suffix handling in `make:cell`**
   Running `php spark make:cell RecentCell` creates the class `RecentCell` and a view file `recent_cell.php`.
   Running `php spark make:cell Another` creates the class `Another` (without the “Cell” suffix) and a view file `another.php`.
   The `--suffix` flag is advertised but its effect is confusing: class names sometimes end with “Cell” and sometimes not, depending on whether the name already contains the word “cell” anywhere. This makes it hard to know what file names will be generated.

2. **Redundant view file naming**
   When a class already ends in “Cell” (like `RecentCell`), the generated view file is `recent_cell.php`. The `_cell` part in the view file name is redundant – the class name already declares it’s a cell. This adds noise and makes it harder to distinguish the actual view name.

3. **Missing view file leads to silent or confusing failures**
   If a cell class cannot find its corresponding view file (for example, after renaming or moving the file, or when the naming convention is unclear), the error message is either missing or not helpful, making debugging difficult.

### Expected behavior

- **Consistent class naming**
  All cell classes generated by `make:cell` should always end with the suffix `Cell`. If a user provides a name that already ends with `Cell` (e.g., `RecentCell`), the command should not add another suffix; if not (e.g., `Another`), it should append `Cell` automatically (`AnotherCell`). The `--suffix` option should be removed or always implied, removing the ambiguity.

- **Clean view file naming**
  Generated view files should be named after the cell’s logical name **without** the `_cell` suffix. For `RecentCell` the view should be `recent.php`, for `AnotherCell` it should be `another.php`. This makes view file names predictable and avoids redundancy.

- **Robust view file resolution**
  When a cell is rendered without an explicitly set view, the base `Cell` class should search for the view file using the clean naming convention first (e.g., `recent.php`), but should also fall back to the old `_cell`-suffixed version (`recent_cell.php`) for backward compatibility. If no view file is found, a clear `RuntimeException` (or similar) must be thrown with a descriptive message, so developers can immediately identify the missing file.

### Impact

These issues make cell development error-prone, especially when working with multiple cells or when upgrading an existing project. Developers waste time guessing file names and tracking down silent failures. Standardizing the naming and improving error reporting will make View Cells more reliable and intuitive to use.


We need the `Request` class to correctly determine the client IP when behind a trusted reverse proxy. Add a `$proxyIPs` configuration option in `AppConfig` (accepts comma-separated string or array of IPs/subnets). Modify `Request::ipAddress()` to use this config, and only trust `HTTP_X_FORWARDED_FOR`/`HTTP_CLIENT_IP` headers when the request comes from a whitelisted proxy. Ensure the `validIP()` method accepts `null` for its `$which` parameter to avoid strict-type false negatives. The `IncomingRequest` constructor must pass its `AppConfig` instance to the parent `Request` constructor. Include early unit tests for `isAJAX()` and `validIP()` covering edge-cases like empty strings, zero, invalid large IPs, valid IPv4/IPv6.


We need a way to reliably retrieve and override the “current request path” (relative to the script name) throughout the framework and for testing. The existing path detection suffers from a few specific bugs and limitations that make it brittle in various server configurations and impossible to mock cleanly.

**Problems to solve:**

1. **Sub-folder installation path stripping** – When `SCRIPT_NAME` contains more than one segment (e.g., `/sub/folder/index.php`), the original substring logic does not correctly remove the script name from the URI, causing path detection to produce incorrect results.

2. **Scheme forcing inconsistency** – The `forceGlobalSecureRequests` config option is only applied when the base URL’s host is detected, allowing the original scheme to persist in some situations. The scheme coercion should happen any time the URI is built from the base URL.

3. **Stale superglobals after path parsing** – When path detection modifies `$_SERVER['QUERY_STRING']` (e.g., to correct Nginx’s behavior), the request’s internal copies of `$_SERVER` and `$_GET` are not refreshed. Subsequent calls that rely on those data retrievals can see outdated values.

4. **No stable accessor or setter for the path** – The final relative path is not stored explicitly on the request. Each call to `detectPath()` re-runs detection, and there is no way to programmatically set what the rest of the application considers the “current URL.” This makes testing of URL-dependent features (like menus that use `current_url()`) overly reliant on manipulating server globals.

5. **`determinePath()` in CodeIgniter uses an environment/type check** – The method contains a brittle condition (`is_cli() && ! testing`) to decide whether to call `getPath()` or `uri->getPath()`. A cleaner approach would use the actual API available on the request object.

**Desired behavior:**

- Introduce a public `getPath()` method on `IncomingRequest` that returns the relative path (relative to `SCRIPT_NAME`). The first call triggers detection once; subsequent calls return the stored path without re-detection. Calling `detectPath()` explicitly should still re-detect and update the stored value.

- Provide a `setPath(string $path)` method that allows setting the relative path directly. This must also update the `URI` object’s path so that `uri->getPath()` reflects the same value. Setting the path should apply the base URL’s scheme and host (using the current config) and also apply the `forceGlobalSecureRequests` coercion.

- When the path is set or detected, update the request’s internal globals for `$_SERVER` and `$_GET` so that any changes to `QUERY_STRING` are reflected consistently.

- Fix the `SCRIPT_NAME` stripping logic to handle arbitrary nesting by comparing path segments rather than substring offsets.

- In `CodeIgniter::determinePath()`, replace the environment-based condition with a call that works uniformly: if the request has a `getPath()` method (IncomingRequest), use it; otherwise fall back to `getUri()->getPath()`.

- Ensure that `current_url()` and other helpers that rely on the shared request instance see the path set via `setPath()`, making it trivial to “spoof” an incoming request for tests (e.g., `service('request')->setPath('users/list')`).


The memory leak checker (`CheckMemoryLeak`) currently produces false positives and misleading error messages:

1. **False leaks for non‑allocated pointers**
   When a pointer variable is declared inside a function scope but never assigned dynamically allocated memory in that scope, the tool still reports a memory leak. For example:
   ```c
   void f() {
       char *p;
       p = get_constant_string();
   }
   ```
   `p` never receives heap memory, yet a “Memory leak: p” warning is issued. The checker should only flag a leak when an actual allocation (`malloc`, `strdup`, `kmalloc`, `new`, etc.) occurs within the same scope.

2. **Leak message points to the allocation, not the declaration**
   When a real leak exists, the error line refers to the line where the allocation function is called (e.g., the `malloc` line) rather than the variable’s declaration. This makes it hard to identify the leaking variable in large functions. The message should indicate the line where the pointer variable is declared, because that is the source of the leak.

3. **Allocations hidden behind casts are missed**
   Allocations like `p = (char *)malloc(10);` are not detected because the current logic only sees the cast parentheses and fails to reach the allocator function name. Such patterns must be recognized.

4. **C++ `new` and `new[]` are not handled**
   `new type` and `new type[expr]` should be treated as allocations and flag a leak if not paired with a corresponding `delete`/`delete[]` before the pointer goes out of scope.

### Expected behavior
- The scope checker (`CheckMemoryLeak_CheckScope`) should independently verify whether an allocation (including through casts) has been performed inside the scope and only then report a leak at scope exit.
- Leak messages should consistently reference the line of the pointer’s declaration.
- Both C‑style allocation functions and C++ `new`/`new[]` expressions must be handled.

### Impact
- Developers receive false positive warnings for perfectly safe code, eroding trust in the tool.
- Real leaks are reported with line numbers that send the developer to the wrong place, slowing down debugging.
- Codebases using C++ or type‑casted allocations generate incomplete or missing leak reports.


The uninitialized variable checker ignores `switch` statements entirely, leading to false negatives when a variable is used uninitialized only inside a `case`. Extend the execution-path analysis to handle `switch`/`case`:

- Process each `case` (and `default`) as a separate branch.
- `break` terminates the execution path for a case (and for nested flow analysis).
- The analysis should combine per-case results so that a variable initialised in all cases is treated as initialised after the `switch`, but an uninitialised use inside a single case is reported.
- Do not regress the existing `if`/`while` execution-path handling.

Fixed #1721 (False negative: uninitialized variable in switch block)


## Template simplification incorrectly treats static member calls as member definitions

When `simplifyTemplates` processes code that contains a static member function call of a template class (e.g., `A<T>::h()`) inside another template class’s member function body, it erroneously matches the pattern `%typename% :: %var% (` as an out‑of‑class member function definition. This leads to mangled output or a spurious `### Error: Invalid number of character {` diagnostic.

The matching logic for member functions implemented outside the class definition should only trigger at namespace/global scope, not when the tokenizer is inside any `{ … }` block of a class or function body. Additionally, the brace‑counting within the template‑copy loop must be performed *before* checking the pattern so that nested scopes are correctly tracked and closing braces are not mishandled.

### Desired behavior

- An expression like `A<T>::h()` appearing in a member function body (indentation > 0) must **not** be treated as a template instantiation anchor.
- The tokenizer must emit clean, balanced token lists for the test case:
  ```cpp
  template <typename T> class A {};
  void f() {
      A<int> a;
  }
  template <typename T>
  class B {
      void g() {
          A<T> b = A<T>::h();
      }
  };
  ```
  The simplified token stream should contain the explicit instantiation `A<int>` only for `f()`’s usage, without errors or mismatched braces.

### Acceptance criteria

- `simplifyTemplates` produces no brace‑mismatch errors for the given example.
- The out‑of‑class definition match is guarded by an indentation‑level check (scope depth 0).
- Brace counting (`indentlevel`) is updated before the token copy logic, and the closing brace of a copied instantiation is inserted only when the count reaches a safe threshold (1, not 0).

templates: fixed problem when for example calling static member function in a template class (#293)


We need to extend the Clang AST dump importer to handle `RecordDecl` and `FieldDecl` nodes, so that C/C++ struct definitions and their members are converted into cppcheck tokens and stored in the symbol database.

**Expected behavior:**
- When the parser encounters a line starting with `RecordDecl`:
  - Create tokens: `struct` → name (from the node’s spelling) → `{` (scope set to a newly created `eStruct` scope) → members → `}` (linked back).
  - Insert a `Type` into `SymbolDatabase::typeList` for the struct and associate it with the scope.
- When a `FieldDecl` line is processed (as a child of `RecordDecl` or anywhere):
  - Generate tokens for the declared type and name; add a `Variable` to the current scope’s `varlist` with appropriate access control (`Global` if the scope is `eGlobal`, otherwise `Local`).
  - If the field has an initializer (indicated by `cinit` in the dump), produce an assignment token chain: a second reference token for the field name, an `=` token with `astOperand1` set to the reference and `astOperand2` set to the token for the initializer expression, and a trailing `;`.
- The `getSpelling()` helper should correctly return the name token for all `Decl` kinds, not only for `ParmVarDecl`.
- Scope nesting in the token list must be correct; use a shared method (`getNestedInScope`) that already exists for this purpose.

**Acceptance criteria:**
- Parsing a Clang AST dump containing a simple `RecordDecl` with `FieldDecl` children produces a well-formed token stream:
  ```
  struct S {
  int x@1 ;
  int y@2 ;
  }
  ```
- The corresponding `Scope` (type `eStruct`), `Type`, and `Variable` entries appear in the symbol database.
- Member variables with initializers (e.g., `int x = 5;`) are correctly tokenized as assignment expressions.


## Bug Report: Stack overflow in `isVarUsedInTree()` / `hasOperand()` on code with huge initializer lists

**Problem**
Cppcheck crashes immediately with a stack overflow (segmentation fault) when processing source files that contain very large braced initializer lists. The crash occurs inside `CheckLeakAutoVar::isVarUsedInTree()` and `FwdAnalysis::hasOperand()`. Both functions recursively walk abstract syntax trees (ASTs) in a way that exhausts the available call‑stack when the tree is extremely wide or deep – for example, a comma‑separated list with thousands of elements.

A minimal reproduction looks like:

```cpp
#define ROW 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
#define ROW8 ROW ROW ROW ROW ROW ROW ROW ROW
// … repeated nesting until many thousand numeric elements appear
void f(std::vector<int>& v) {
    v = {
        ROW… 0   // thousands of comma‑separated numbers
    };
}
```

**Expected behavior**
Cppcheck should handle such automatically‑generated or heavily macro‑expanded code without crashing. The functions `isVarUsedInTree()` and `hasOperand()` should be rewritten to use an iterative (queue/stack‑based) traversal that avoids unbounded recursion depth, while preserving the exact same analysis results. If any other functions share the same deep‑recursion pattern they should be fixed together to prevent similar crashes.

**Impact**
This bug makes Cppcheck unusable for real‑world C++ projects that contain large aggregate initializers or deeply nested expression trees, which are common in template libraries and generated code.

**Related issue**
#14435

Fix #14435 Stack overflow in isVarUsedInTree() (#8157)


Improve the internal token representation used by `CheckMemoryLeak` to reduce false positives and better handle real C control flow.

- **If conditions:** map variable-tested patterns to distinct tokens:
  - `if (var)` → `if(var)`
  - `if (!var)`, `if (var == NULL)`, `if (NULL == var)`, `if (var == 0)`, and `if (unlikely(!var))` etc. → `if(!var)`
  - Unmatched `if`/`else`/`switch` still emit their keyword tokens.
- **Loops:** unify `for`, `while`, `do` as `loop`. Add `continue` and `break` tokens.
- **Return:** always emit `return`; additionally emit `use` when the returned expression includes the variable under analysis.
- **Token stream cleanup (`CheckMemoryLeak_CheckScope`):** delete empty statements and empty blocks that follow `else` or a loop header:
  - `else ;`, `else { }`, `else if ;`, `else if { }`
  - `loop ;`, `loop { }`
- Replace single-token deletion (`eraseNext`) with a helper that removes a range of tokens.

These changes make the leak checker’s simplified control-flow model more accurate, preventing false leak reports on commonly used patterns.


We need to extend ExprEngine to consistently produce symbolic buffer representations for all container types, not just `std::string` in assignment statements. Currently the engine creates `ArrayValue` entries for `std::string` only during assignment from another `std::string`, and the logic for translating a `ValueType` into an abstract value is scattered and duplicates container handling. This leads to missing buffer state for containers like `std::vector<int>`, and inconsistent behavior between variable definitions, assignments, and return values.

**Desired behavior**

- For any variable or expression whose `ValueType` indicates a container (i.e. `valueType->container` is set), the abstract state should include an `ArrayValue` with:
  - A symbolic buffer size range from `0` to `UINT_MAX` (or `~0U`).
  - An element value range derived from the container’s inner value type.
- For `std::string`-like containers the element range should be `-128` to `127` (as currently handled).
- For other containers (e.g. `std::vector<int>`) the element range should be the range appropriate for the contained type, obtained by parsing the container’s `containerTypeToken`.
- This logic should be applied uniformly whenever a `ValueType` is converted into a fresh abstract value, including:
  - Local variable initialization (`createVariableValue`).
  - Assignment (`executeAssign`).
  - Function return values (`executeFunctionCall`, `executeReturn`).
  - Casts (`executeCast`).
  - Member accesses and dereferences when a full value is synthesized (`executeDot`, `executeDeref`).
  - Any other place where `getValueRangeFromValueType(data.getNewSymbolName(), vt, *data.settings)` is called today.

**Implementation hints**

- Add a new overload of `getValueRangeFromValueType` that accepts a `const ValueType*` and a `Data&` reference, and internally handles the container case before falling back to the existing integral/floating-point path.
- Remove the duplicate container-handling block from `createVariableValue` and the special-case `std::string` assignment logic from `executeAssign`; both should rely on the new unified function.
- Ensure that `std::string` assignments from a function call (e.g. `std::string s = getName(1);`) still produce the correct buffer dump as in the existing tests.

**Context**

- The `Data::memory` dump for a `std::string` should look like: `{ s=($4,[$3],[:]=$2)}` with appropriate element/size ranges.
- The `ValueType` struct provides `container`, `stdStringLike`, and `containerTypeToken` to query the container properties.
- The `ArrayValue` class already supports an optional element value and a size expression.

No changes are needed to the Z3 interface or the symbolic constraints – this is purely about generating the correct abstract state in the forward analysis.

ExprEngine; better handling of containers


Bug: When a user forces a configuration via `-D` (e.g., `-DX`), cppcheck’s configuration enumeration still generates configurations that are incompatible with that forced definition. For example, `#ifndef X` still produces a configuration, and `#else`/`#elif` branches continue to emit configurations even when the preceding `#if`/`#elif` is unconditionally true due to the forced define.

Desired behavior:

- The set of configurations returned by `Preprocessor::getConfigs` must respect the forced defines from `settings.userDefines`.
- If a macro is defined via `-D`, any configuration that requires that macro to be **undefined** must not be generated (e.g., branches guarded by `#ifndef`, `#if !defined(...)`, tests of the form `MACRO==0` when the forced value is non-zero, etc.).
- When an `#if` (or `#elif`) condition is satisfied solely because of a forced define, all subsequent `#elif`/`#else` branches in the same chain become irrelevant and must not contribute any configuration. The entire remainder of the chain up to the matching `#endif` must be skipped (including nested conditional blocks).
- The forced macro itself should be omitted from the configuration string since its value is already fixed externally (i.e., it should not appear as a variable in the configuration). That is, `cfg` output should not include entries that are already covered by a `-D` definition.

The `-U` undefine handling should continue to work as before; only `-D` needs to be additionally considered.

Safety/Correctness constraints:
- The skipping must correctly handle arbitrary nesting of `#if`/`#endif` inside the ignored blocks.
- The check must work for both `-DMACRO` and `-DMACRO=VALUE` forms.
- The logic must not affect cases where no `-D` is given — existing behavior must be preserved.

Fixed #7700 (Using -D to force a configuration still checks the configuration as well as configurations that are not relevant)


**Title:** Improve array index out‑of‑bounds detection for arithmetical loop‑counter subscripts

**Background**
In `CheckBufferOverrun::checkScope` the for‑loop handling currently tries to detect out‑of‑bounds array access using a few simple patterns (`%varid% [ %var% + %num% ]` and `%varid% [ %num% + %var% ]`) that look only at the literal offset. This logic is fundamentally wrong: it ignores the loop counter’s actual value range and therefore produces both false negatives and misleading results when the counter variable appears in an arithmetic expression.

**Desired behaviour**
When a for‑loop iterates over a known counter variable, and the loop body contains an array access where the index is computed from that counter (e.g., `a[i+1]`, `a[i*2]`, `a[1 + i]`), cppcheck should:

1. Determine the minimum and maximum possible values of the loop counter at the point of the access, using:
   - The initialiser (`int i = …`, `type var = …`, etc.) to obtain the start value.
   - The loop condition (`<` or `<=`) and the bound literal to compute the final value.

2. For any subscript of the form `<counter> <op> <literal>` or `<literal> <op> <counter>` (where `<op>` is `+`, `-`, `*`, `/`), compute the resulting index for both the minimum and maximum counter values using `MathLib::calculate`.

3. Report an out‑of‑bounds error if **either** the computed minimum index **or** the computed maximum index is ≥ the array’s size.

4. Remove the two old `Token::Match` patterns that only tested the literal offset; they are no longer needed and would conflict with the new, correct analysis.

The detection must correctly handle all variations of the loop initialiser (single variable, typed variable, type‑type variable) and both strict `<` and `<=` comparisons in the condition.

This will fix false negatives for idioms like `for (int i = 0; i < N; i++) a[i+1] = …` where `N` equals the array size, and will make the check robust for multiplicative and offset expressions that can exceed bounds even when the counter itself stays inside the range.


Fix constant propagation corrupting struct member accesses: when a const variable's name matches a struct field, replacing it with the literal value turns `obj.a` into `obj.45`. Do not perform the replacement if the token is immediately preceded by a `.` (the tokenizer already converts `->` to `.` before this pass, so checking `.` alone suffices).

Constant variable converting converted struct members (foo.a => foo.45) also, fixed that.


Cppcheck currently analyzes variable usage (`CheckOther::functionVariableUsage`) with a flat per-function map keyed only by variable name. As a result, when the same name is redeclared in nested scopes (e.g., inside `if` or `else` blocks), the tool conflates all occurrences into a single state. This produces false positives such as “Unused variable”, “Variable is not assigned”, or “Variable is assigned a value that is never used” for variables that are shadowed, and also hides real issues when an inner variable is really unused while the outer one is used.

**Example**

```cpp
void foo(int x) {
    int i;            // outer 'i'
    if (x) {
        int i;        // inner 'i' – currently treated as the same as outer 'i'
    } else {
        int i = 0;    // another inner 'i'
    }
}
```

Currently cppcheck may report misleading diagnostics because it cannot distinguish the three `i` variables. Correct behavior would treat each `i` in its own scope independently, tracking reads, writes, and modifications per scope and properly handling name shadowing.

**Expected behavior**

- Every variable declaration opens a new variable entry for its block (including single-statement blocks and compound statements).
- Read, write, use, and modified operations are attributed only to the variable visible in the current scope according to C/C++ scoping rules.
- Unused/unassigned/unread warnings are reported for the correct scope and with the correct line.
- Nested scopes (e.g., `if`, `else`, `for`, `while`, bare brace blocks) are supported. The top-level function scope and any inner scopes are checked independently.
- ASM blocks or other constructs that clear variable usage still act only on the current scope.

The handling should also cover pointer/reference declarations and array declarations with shadowing, mirroring what is already done for the flat map but respecting block nesting.

**Impact**

- Eliminates false warnings in code that uses shadowing legitimately.
- Prevents missed warnings where an inner variable hides an outer one and is truly unused/unassigned.
- Makes the checker’s output reliable for C++ code that often has inner redeclarations in conditionals, loops, and lambda captures (future-proofing).

variable usage: refactoring the checking


When Cppcheck is invoked with both `--project` (a GUI project file) and one or more `--addon` options, the addons given on the command line are silently discarded because `importCppcheckGuiProject()` overwrites the entire `settings.addons` set with the project’s addons. Instead, CLI-specified addons should be merged with those from the project so that both sets are active.

fixed #13941 - do not overwrite addons from CLI in GUI project import (#7603)


The simplification pass does not evaluate constant boolean comparisons like `(true) == true` or remove redundant parentheses around boolean/numeric literals such as `(true)` and `(2)`. Consequently, expressions like `if ((true) == true) {}` are not simplified to an empty block, even though they are trivially constant.

Extend `simplifyConditions` to fold comparisons where both operands are boolean literals (`true`/`false`), mirroring the existing handling of numeric constants. Extend `simplifyRedundantParanthesis` to strip a single parenthesis pair around a boolean or numeric literal (e.g., `(true)` → `true`, `(2)` → `2`), as well as doubled parentheses `((...))` when the inner pair encloses exactly one token. This must work together so that an input like `if ((true) == true) {}` first loses its parentheses, then the comparison reduces to `true`, and the whole `if` body is eliminated.

Ensure no tokens are leaked, and the token links (parenthesis matching) remain consistent.

Fix ticket #342 (Simplify "if( (true) == true )")


Add support for `switch` statements in the expression engine.
When executing a `switch`:

* Evaluate the condition.
* For each `case` label with an integer constant, fork a branch where the condition value is constrained to be equal to that constant.
* If a `default` label is present, fork a branch constrained by the negation of all case constants (condition not equal to any handled case value). If there is no `default`, continue execution after the switch body under the same negated constraint.
* Correctly handle `break` inside a switch body: `break` must transfer control to the end of the enclosing switch/loop scope, not return from the entire function.

Also add a bailout for `try` blocks (currently unhandled).


When generating a plain-text message snippet, if no unquoted content is found (i.e., every line starts with `>`), fall back to the quoted content as the snippet body. Prefix the fallback snippet with `>` to indicate it is quoted, and strip leading whitespace from the fallback text. If **any** unquoted text is present, the snippet must consist solely of unquoted text—never quoted. The snippet length limit applies to the fallback quoted text as well. HTML parts (which do not use `>` quoting) are unaffected: an HTML-only message without meaningful text still produces an empty snippet.

lib-mail: Use quoted text as snippet if there is nothing else


<internal>We need a repository-specific implementation request written as if by a maintainer, describing the problem with `istream-decompress` hanging in asynchronous contexts, without referencing the patch. It should mention the observable behavior, the relevant APIs (`compression_detect_handler`, `is_compressed_*`), and the required changes to return codes and data access patterns. Keep it concise, avoid implementation details beyond what's necessary.</internal>

**Title:** istream-decompress hangs when stream is used in asynchronous context

**Background**
The `compression_detect_handler()` function and the per-format `is_compressed_*()` callbacks currently use `i_stream_read_bytes()` to peek at the start of the input stream. That works in synchronous loops, but in an asynchronous I/O context (e.g. an IMAP connection with `COMPRESS` enabled, or any istream used inside an ioloop), the internal read may be called without properly returning a “need-more-data” signal. The caller in `i_stream_decompress_detect_more()` also calls `i_stream_read()` and then `compression_detect_handler()` without correctly evaluating whether data is already stalled. This can cause a hang when no more input events arrive, because the stream appears to have no data but never indicates that it needs more.

**Desired behavior / invariants**

1. `compression_detect_handler()` must conform to non-blocking stream semantics. Its signature should change from returning a handler pointer directly to:
   ```
   int compression_detect_handler(struct istream *input,
                                  const struct compression_handler **handler_r);
   ```
   Return values:
   - `1` – a handler was identified, `*handler_r` is set.
   - `0` – end of file reached without detecting a handler.
   - `-2` – not enough data available yet (the stream would block). The caller should wait for more input and retry.
   - `-1` – stream error (check `input->stream_errno`).

2. The individual `is_compressed_*` functions must not call `i_stream_read_bytes()`. Instead they should use `i_stream_get_data()` to inspect already-buffered bytes, check if the required number of bytes is available, and return `FALSE` if insufficient. That way they never trigger a hidden read that could block or consume data without the caller’s awareness.

3. `i_stream_decompress_detect_more()` must use the new return codes from `compression_detect_handler()` to properly propagate `0` / `-2` back to the istream machinery so the I/O loop can wait for data instead of spinning or hanging.

4. All existing callers (e.g. `mail-compress` plugin, test helpers) must be updated to the new interface but should still behave correctly: they must not inadvertently ignore the need-more-data condition. For synchronous callers that assume data is already present, a failure to detect a handler because of insufficient data should be treated as “not compressed” only after the stream indicates EOF or error.

**Scope**
- `src/lib-compression/compression.c` and `compression.h`
- `src/lib-compression/istream-decompress.c` (the `i_stream_decompress_detect_more` function)
- Any in-tree callers: `src/lib-compression/test-compression.c`, `src/plugins/mail-compress/mail-compress-plugin.c`

The fix should not change the decompression format detection logic; it only prevents blocking reads and ensures the asynchronous I/O contract is upheld.

lib-compression: istream-decompress - Fix hangs when stream is used in asynchronous context


**Title:** `http-client` crashes with assertion failure when a new connection fails while peer already has active connections

**Observed Behavior**
When a peer (IP address) already has one or more active HTTP connections and a new connection attempt to that same peer fails (e.g., due to a TCP connect timeout or refusal), the library aborts with:

```
Panic: file http-client-queue.c: line 481 (http_client_queue_connection_failure):
assertion failed: (queue->cur_peer == NULL)
```

**Expected Behavior**
The library should not crash. A single failed connection attempt while other connections are alive is a normal, recoverable situation. The existing connections should continue to serve queued requests, and the failure should be logged (or otherwise handled) without triggering an assertion.

**Impact**
Any process that uses `lib-http` for outbound HTTP requests and encounters a transient connection failure to a multi-connection peer will crash, leading to service outages. This is especially problematic in high-throughput applications where connection reuse and parallel connections are common.

**Possible Cause**
The affected function `http_client_queue_connection_failure` assumes that when it is called, `queue->cur_peer` is always `NULL`. However, when a new connection attempt fails for a peer that already has live connections, that peer remains the current peer (`cur_peer != NULL`). The assertion is therefore too strict for this legitimate case.

**Requested Fix**
When `cur_peer` is not `NULL`, the function should verify that the list of pending peers (`pending_peers`) is empty (no other peer is waiting to be activated) and then return early, because the remaining active connections are still able to handle requests. Only when all connections to a peer have failed and `cur_peer` becomes `NULL` should the normal failure-handling path (activating the next peer, if any) be taken.

lib-http: client: Fixed assert failure occurring when a new connection fails for a peer that has active connections.


The dsync utility currently fails to resolve UID conflicts when synchronizing mailboxes between servers if the underlying mail storage does not implement the special “change UID” operation (`mail_update_uid`). Many backends (including those commonly used in production) lack this operation, so any sync that encounters a UID mismatch—for instance, when the same message exists under different UID numbers on each side, or when a message must be given a new UID to avoid a conflict with a concurrently assigned UID—cannot complete. The result is a permanent failure that prevents the two stores from converging.

The requested fix is to replace the “change UID” approach with one that uses only standard, universally supported mailbox operations: **copy the message to the target UID, then expunge the old UID**. The entire UID conflict resolution path should be merged into the existing new-message saving/copying phase so that it can benefit from the same retry logic that handles races where a source message was expunged before it could be copied.

In more detail, the following behavior is expected:

1. **UID conflicts must be resolvable without a dedicated “change UID” feature.**
   - The local worker (or any backend adapter) must implement `msg_update_uid(old_uid, new_uid)` by allocating a save context for the new UID, copying the existing message’s flags and data, and then expunging the old message. If the copy fails (e.g., because the old message was already expunged by some other process), the operation must be reported as “failed” so that the brain can retry or skip it.
   - The remote worker protocol must continue to work: the same copy+expunge sequence should be triggered for remote mailboxes, without requiring a separate RPC for changing UIDs.

2. **The new-message saving/copying and UID conflict resolution must share a common retry loop.**
   - After all new messages have been saved/copied in a mailbox, any copy operations that failed (because the source message disappeared) must be identified and retried. The retry must continue until no more copies fail, and then proceed to the next mailbox.
   - The brain must track which messages are “saved” or “copied” versus those that failed, and feed the failed ones back into the save/copy logic without needing a separate collection of retry indexes.

3. **Comments must clearly document the synchronization phases and retry semantics.**
   - The steps in the new-message copy/save and conflict resolution phase (step 6 in the overall message sync process) should be described at the top of the relevant file.
   - The mailbox iteration and retry-on-failure logic should be explained so that future maintainers can understand the intended behavior and the handling of edge cases (e.g., messages expunged during the copy attempt, the need to wait for remote workers’ replies before declaring a retry round finished).

The lack of this functionality makes dsync unusable for replication between servers that do not implement the `mail_update_uid` operation, which is a critical requirement for many deployments. Implementing the copy+expunge approach, together with proper retry handling and documentation, will make the conflict resolution robust and backend-agnostic.

dsync: Rewrote copying and uid conflict resolution code. Added tons of comments.


When a per‑request attempt timeout is configured with `http_client_request_set_attempt_timeout_msecs()`, that timeout should govern every attempt of the request, including the time spent waiting for a connection to become available and the time until the first response byte. Currently, if the request is not yet actively receiving a response payload, the connection falls back to the client’s global `request_timeout_msecs` setting, effectively ignoring the per‑request attempt timeout for queued or waiting requests.

**Expected behavior**
- A request submitted with a specific `attempt_timeout_msecs` of *N* must time out after *N* milliseconds of inactivity on that attempt, regardless of the global `request_timeout_msecs` value.
- This must hold true both when the request is waiting for a connection slot (queued) and when it has been assigned to a connection but is not yet receiving a response payload.
- When multiple requests with different attempt timeouts are waiting on the same connection, the connection must apply the timeout of the next request that will be sent.

**Constraints / edge cases**
- The per‑request attempt timeout applies per attempt, so if the request is retried (e.g., `max_attempts > 1`), each attempt should respect the same per‑request timeout.
- The per‑request timeout must not interfere with the request’s absolute timeout if one is set; the absolute timeout is a separate constraint.
- If no per‑request attempt timeout is set, the global `request_timeout_msecs` must remain the fallback, preserving the existing default behavior.
- The fix must handle both serial (max parallel connections = 1) and parallel connection configurations correctly.

lib-http: client: Fixed request-specific attempt timeout.


The binary config file format (as described in `src/config/config-dump-full.c`) does not record the number of filter entries in a settings block.  The reader (`settings_block_read` in `src/lib-settings/settings.c`) must therefore grow the filter array dynamically as it parses the block, and it has no way to detect if the block ends prematurely while filter data is still expected.  This also prevents pre‑allocating the filter array based on the true count.

We need to add a filter count field to the binary format, placed immediately before the sequence of filter entries.  The required changes are:

* **Writer:** In `config_dump_full_sections()`, after writing the base settings key‑value pairs, write a single 32‑bit big‑endian integer representing the number of filter entries (including any delayed filter entry).  After writing all filters, if the stream is seekable, go back and rewrite the count with the final value.  If the stream is not seekable (e.g. stdout), the count must be written correctly in a single pass (this may require buffering or a different approach, but the existing code distinguishes `CONFIG_DUMP_FULL_DEST_STDOUT`, so that can be used to decide).

* **Reader:** In `settings_block_read()`, parse the 32‑bit big‑endian filter count before the loop that reads individual filters.  Use that count to allocate the filter array to the exact size (`i_array_init(&block->filters, filter_count)`).  Track the number of filters actually read, and after the loop assert or error if the parsed count does not match the expected count.  If the block ends before the expected number of filters are read, report an error.

* **File‑format documentation:** Update the comment at the top of `src/config/config-dump-full.c` to reflect the new layout, e.g.:
  ```
  <32bit big-endian: filter count>
  Repeat for "filter count":
      ...
  ```

The key non‑obvious constraint: the writer must produce a valid count even when the output is written sequentially without seeking (stdout).  The current patch uses a seek‑and‑update approach for file output and probably avoids stdout entirely by not writing the count there (or writes a dummy zero and relies on the reader ignoring it), but a consistent format is desirable.  The final implementation should ensure that the count is always present and correct across all dump destinations.

Impact: with the count available, readers can pre‑allocate memory accurately and detect truncated or malformed blocks, improving robustness and performance when loading binary configs.
</instructions>

config, lib-settings: Add filter count to binary config file


The IMAP URL struct `struct imap_url` currently stores host information using two separate fields: `const char *host_name` and `struct ip_addr host_ip`. This representation is inconsistent with the common `struct uri_host` defined in `src/lib/uri-util.h`, which encapsulates both the host name and the IP address in a single structure and provides utility functions like `uri_host_copy` and `uri_append_host`.

This inconsistency leads to manual, fragile handling of host data throughout the IMAP URL code:
- When copying host data from a parsed authority (`imap_url_parse_iserver`), both fields must be assigned individually.
- When resolving a relative URL against a base (`imap_url_do_parse`), the host name and IP must be duplicated separately using `p_strdup_empty` and a raw struct copy, rather than a single, well-tested copy helper.
- When serializing a URL (`imap_url_create`), the code must switch between `uri_append_host_name` and `uri_append_host_ip` with special-case logic for IPv6 literals, duplicating logic that `uri_append_host` already provides.

Over time this fragmentation increases the risk of bugs—for example, forgetting to copy the IP when duplicating a URL, or mishandling IPv6 brackets in serialization—and makes the code harder to maintain compared to other URI consumers in the project that already rely on `struct uri_host`.

**Expected behavior:**

`struct imap_url` should replace `host_name` and `host_ip` with a single `struct uri_host host` field. All direct accesses to the old fields should be updated accordingly:

- `url->host_name` → `url->host.name`
- `url->host_ip` → `url->host.ip`

In the implementation, existing `uri-util` helpers must be used to keep host manipulation safe and consistent:

- In `imap_url_parse_iserver`, assign `url->host = auth.host` instead of piecemeal assignments.
- When inheriting host data from a base URL, use `uri_host_copy(parser->pool, &url->host, &base->host)` rather than manually duplicating `host_name` and copying the IP struct.
- In `imap_url_create`, replace the conditional `uri_append_host_name` / `uri_append_host_ip` logic with a single call to `uri_append_host(urlstr, &url->host)`, which already correctly handles both names and IP literals (including `[::1]` style addresses).
- In `imap_urlauth_check_hostport`, change the host comparison to check `url->host.name`.
- In `src/imap-login/client-authenticate.c`, change the referral host assignment to use `url.host.name = reply->host`.
- In the test file `test-imap-url.c`, update the test structure initializers to use the nested `.host = { .name = ... }` syntax and adjust assertions to reference `url->host.name` and `url->host.ip.family`.

**Impact:**

Without this change, the IMAP URL code remains an outlier that requires manual, ad‑hoc host handling, making it more error-prone and harder to evolve. Using the unified `uri_host` struct eliminates duplication, reduces the chance of subtle mismatches, and aligns `imap-url` with the standard URI conventions already established in the codebase.

lib-imap: Started using struct uri_host in struct imap_url.


The address tokenizer (`fts-tokenizer-address`) can consume excessive memory when the input contains an extremely long run of characters that look like an email address (e.g., a local-part or domain with no separator). In such cases the tokenizer should not keep appending to an internal buffer; it should instead skip the entire overly-long candidate and hand the raw bytes directly to the parent tokenizer so they are broken into manageable pieces. This must work both during indexing and during query-time parsing (the `search` flag path).

Correspondingly, an overly long email-address string presented as a search query should be fed to the parent tokenizer rather than emitted as a truncated address token.

After skipping, the address tokenizer must ensure any buffered parent data is flushed, so the parent tokenizer’s tokens appear even when the address tokenizer itself produces no further tokens.

A side effect is that the output order can change: a well-formed address token may now be emitted before the domain/sub‑tokens that the parent tokenizer produces for that same address. This reordering is acceptable.

lib-fts: Fix address tokenizer to handle large input properly


**Feature Request: Convert HTML blockquotes to plain-text quote format**

When converting HTML email content to plain text with `mail_html2text`, `<blockquote>` elements are currently either fully removed (if `MAIL_HTML2TEXT_FLAG_SKIP_QUOTED` is set) or their content appears as normal inline text without any quotation marker (if the flag is not set). Context from replies and forwarded messages is thus lost.

**Desired behavior**

1. **Without `MAIL_HTML2TEXT_FLAG_SKIP_QUOTED`** (default):
   - The content of `<blockquote>` elements must be preserved and formatted as plain-text quotations.
   - Each blockquote line must be prefixed with `> ` (or `> > ` for nested blockquotes, etc.).
   - The transition into a blockquote must insert a newline and the `> ` prefix; the transition out must insert a newline without a trailing `>` on the subsequent non-quoted line.
   - HTML entities inside quoted content must be decoded (e.g., `&` → `&`).
   - Example:
     ```
     Input:  a<blockquote>b</blockquote>c
     Output: a
             > b
             c
     ```

2. **With `MAIL_HTML2TEXT_FLAG_SKIP_QUOTED` set**:
   - All blockquote content must remain completely omitted, as before.

3. **Edge cases**:
   - Nested `<blockquote>` levels must stack `>` prefixes correctly.
   - Adjacent blockquote sections must be treated independently; no merging of quote prefixes.
   - Malformed or self-closing tags must not cause crashes or infinite loops.

No change to the existing function signatures or flag semantics is required—only the output when the skip flag is absent (or zero) should change from the old plain-inline behavior to the quoting behavior described above.

lib-mail: Include blockquotes as quotes in plain text


## Request: Prevent premature YES→TEMP cache decision downgrades on repeated purges

### Background
Dovecot's mail cache optimizes field storage using three per-field decisions:
`NO` (not cached), `TEMP` (temporarily cached), and `YES` (permanently cached).
A field is upgraded from `TEMP` to `YES` when the client access pattern suggests the field is still needed long‑term.
A `FORCED` flag can override automatic decisions (e.g., via `mail_always_cache_fields`).

Currently, **every cache purge unconditionally changes all `YES` decisions back to `TEMP`**.
This interacts badly when a mailbox is purged multiple times within a short interval, because clients may not have had time to re‑visit the mailbox and re‑confirm the `YES` decision. Purges that happen too close together can then drop old cached data (`TEMP` → `NO`) even though clients still need it.
The only workaround is to list all useful fields in `mail_always_cache_fields`, which essentially forces them forever.

### Required behavioural change

1. **Purge must not unconditionally downgrade `YES` to `TEMP`.**
   A field with decision `YES` (not forced) must remain `YES` after a purge unless its `last_used` timestamp is older than `day_stamp - mail_cache_unaccessed_field_drop` seconds (the “unaccessed field drop” threshold).
   If older, it may be downgraded to `TEMP`; otherwise it stays `YES`.

2. **Double the window for dropping a `TEMP` field to `NO`.**
   A field with decision `TEMP` (not forced) must only be dropped to `NO` when `last_used < day_stamp - 2 * mail_cache_unaccessed_field_drop`.
   This gives clients twice the configured idle time after a `YES → TEMP` downgrade to re‑confirm the decision before the cached data is actually removed.

3. **`last_used` must only be updated for `YES` decisions when the client confirms the decision is still correct.**
   When a `YES`‑decision field is accessed, the `last_used` timestamp must advance **only** if the access pattern re‑confirms that permanent caching is still justified (e.g., the client fetches an older message and the field is still used in a way that indicates permanent interest).
   If the `YES` decision has not been confirmed (for example after a purge), repeated accesses must not refresh `last_used` and artificially extend the lifetime beyond the configured inactivity period.

4. **Forced decisions remain untouched.**
   `FORCED` decisions are never downgraded or dropped, regardless of `last_used` or inactivity.

### External configuration reference
- `mail_cache_unaccessed_field_drop` seconds (cache optimisation setting `unaccessed_field_drop_secs`) defines the base inactivity threshold for the `YES → TEMP` transition.

### Why this matters
- Prevents data loss of useful cached fields when multiple purges occur in quick succession (e.g., after migrations, restarts, or aggressive purging schedules).
- Eliminates the need for the `mail_always_cache_fields` workaround for fields that genuinely become permanent but were being reset by every purge.
- Makes the lifecycle of cached fields predictable based on actual usage, not on purge frequency.

lib-index: Cache purging shouldn't always change YES decisions to TEMP


**Title:** lib-smtp: `smtp-command-parser` fails for lines longer than the stream buffer

**Severity:** medium
**Component:** `lib-smtp` / `smtp-command-parser`
**Found in:** `src/lib-smtp/smtp-command-parser.c`

### Context
The SMTP command parser (`smtp_command_parser`) consumes commands and AUTH continuation responses from an `istream`.  It enforces per-line size limits via `struct smtp_command_limits` (`max_parameters_size`, `max_auth_size`).  When the underlying `istream` is created with a buffer smaller than one of these limits, the parser cannot read a complete line in one go and currently errors out with `SMTP_COMMAND_PARSE_ERROR_LINE_TOO_LONG`, even though the line is well within the allowed maximum.

In practice this happens when an application provides a custom input stream with a small I/O buffer (e.g., 1 KiB) via `smtp_server_connection_create_from_streams()`, but legitimate AUTH responses or commands can be up to 8 KiB.

### Expected behaviour
The parser should accumulate data across multiple `i_stream_read()` calls when the underlying buffer is full but the line is not yet complete.  As long as the total accumulated length stays within the configured limit for the current context (command parameters or AUTH response), parsing must succeed.  If the line genuinely exceeds the limit, the parser should still raise `LINE_TOO_LONG`.

### Constraints / edge cases
- Must work for both regular command parameters and AUTH response lines (selected via `auth_response` flag).
- The existing limits (`max_parameters_size`, `max_auth_size`) must remain the only ones; there is no need for a separate buffer-size limit.
- Must not introduce unbounded memory usage: the total size of buffered partial data must be bounded by the per-line limit.
- The parser should continue to return 0 (incomplete) when the end of the current stream buffer is reached, then resume correctly on the next call with fresh data.
- Existing unit tests (`test-smtp-server-errors`) must keep passing; the fix should be transparent for the case where the stream buffer is large enough.

lib-smtp: smtp-command-parser - Make parser suitable for input stream with small buffer.


lib-sql: driver-sqlite – Missing statement API support

**Background**
The shared lib-sql layer provides a structured query interface via `sql_statement_*` and `sql_prepared_statement_*` functions.  The sqlite driver currently only implements the legacy raw-query vfuncs (`query_s`, `exec`, `update`).  Code paths that rely on the statement API fail or are unavailable when using SQLite.

**Request**
Add the missing statement-API implementation to the SQLite driver.  The following capabilities must work:

- **Prepared statements**
  `sql_prepared_statement_init()` / `_unref()` should create and cache prepared statement handles.
  Prepared statements must survive a disconnect and automatic reconnect: when the database connection is re-established, previously cached prepared statements are transparently re-prepared on next use.

- **Statement objects**
  `sql_statement_init()` (non-prepared) and `sql_statement_init_prepared()` (from a prepared statement) must return usable statement objects.
  A single prepared statement’s native `sqlite3_stmt` handle cannot be used concurrently by multiple statement objects: the driver must serialize access and reject simultaneous use.

- **Bind methods**
  `sql_statement_bind_str()`, `_binary()`, `_int64()`, `_double()`, `_uuid()` must map to the appropriate `sqlite3_bind_*` calls.
  Out-of-memory errors during binding must be treated as fatal; other errors must be recorded on the statement and cause subsequent operations to fail gracefully.

- **Query execution**
  `sql_statement_query_s()` must execute the bound statement and return a result set.
  `sql_update_stmt()` / `sql_update_stmt_get_rows()` (transaction-aware) must execute the statement and report the number of affected rows.

- **Lifecycle & cleanup**
  Releasing a statement object must reset the prepared statement’s bindings and unlock it.
  Finalizing a prepared statement must finalize the native `sqlite3_stmt` handle.
  Disconnecting the database (or closing the driver) must finalize all cached prepared statements.  Reconnecting must re-prepare them.

- **Driver metadata**
  The driver’s flags should advertise `SQL_DB_FLAG_PREP_STATEMENTS` so generic code can detect that the statement API is supported.

**Out of scope**
The async (`statement_query`) callback-based variant is not required at this point; only synchronous execution is needed.

lib-sql: driver-sqlite - Implement statement API


Ensure that runtime exceptions thrown from within user-provided `NetSocket` handler callbacks are reported to the owning context's exception handler, matching the existing behavior of other Vert.x components (e.g., HTTP, EventBus).

Currently, exceptions thrown by a `NetSocket` data handler (`handler`), end handler (`endHandler`), or close handler (`closeHandler`) are not delivered to the `Context.exceptionHandler()`. They appear to be silently lost. The fix should guarantee that any uncaught exception from these handlers is forwarded to `context.reportException(t)`.

The fix must:
- Not break existing functionality.
- Apply to the data handler, end handler, and close handler callbacks (and any other internally equivalent handler paths that propagate user code on the socket’s pipeline).
- Use the context that owns the `NetSocket` (the context current when the handlers were registered).

NetSocket data handler should report runtime failures to context exception handler - see #3272


We need the pause/resume behavior on Vert.x TCP/HTTP connections to be strict. Currently, when a connection is paused it can still accumulate incoming messages without bound, and the underlying channel's auto-read isn't managed correctly. This leads to poor backpressure and memory usage in HTTP servers, and flushing behaviour during resume is inefficient.

Acceptance criteria:

1. While a connection is paused, any incoming messages must be buffered locally and never delivered to the message handler.
2. Once a small number of messages (e.g., 8) have been buffered, the underlying channel's auto-read must be turned off to signal backpressure and stop further reads from the socket.
3. When the connection is resumed, all buffered messages must be delivered to the handler in sequence, and the resulting flushes must be consolidated into a single batch (no individual flushes per message).
4. If the handler invokes pause again **during** the resume processing of buffered messages, delivery must stop immediately after the current message. Only messages up to that point are delivered, and the rest must remain pending.
5. After resume processing finishes and the pending buffer is empty (and the connection is not paused), auto-read must be re-enabled. If the buffer was not fully drained (because of a mid-processing pause), auto-read must stay disabled.
6. Multiple pause/resume calls must be idempotent—a double-resume must not accidentally deliver messages that are still pending or re-enable auto-read prematurely.
7. The HTTP 1.x server connection currently has its own local pause flag that overrides the base mechanism and breaks the strict contract; that duplication must be removed so the HTTP connection uses the common strict pause/resume semantics.

Modify pause/resume of connection base to be strict


```
Title: Deployment failure does not clean up child verticles

When a verticle's `start()` method deploys other verticles (e.g., via `vertx.deployVerticle`) and then fails—either by calling `startFuture.fail(...)` or by throwing an exception—the parent deployment is correctly failed, but any child verticles that were already started remain deployed and their `stop()` callbacks are never called. This leaves resources lingering and breaks the expected lifecycle: a failed deployment should undo its partially completed work.

Expected behaviour:
- If a verticle fails during startup, all verticles it deployed as children of that deployment must be undeployed before the failure is reported to the original deployer.
- The undeployment of children must follow the same semantics as an explicit, successful undeployment of the parent would: it should recursively undeploy any further descendants.
- The parent's own close hooks (registered via `context.addCloseHook`) are already executed on failure, but they are *not* sufficient here because they do not deal with child deployments.
- The fix must cover both asynchronous failure (via the `Future<Void>` argument to `start`) and synchronous exceptions thrown from `start()`.
```

Verticle deployment failure should undeploy its children verticle - fixes #2210


```
Title: RequestOptions silently accepts invalid HTTP header names and values

Description:
When building an HTTP request with io.vertx.core.http.RequestOptions and adding headers via addHeader, putHeader, or setHeaders, the class accepts header names that contain illegal characters (e.g., '=') and header values that contain forbidden characters (e.g., control characters like '\r'). No exception is thrown, and the invalid data is passed through to the wire.

This is inconsistent with other parts of Vert.x: the server-side HTTP implementation validates headers, and the HttpHeaders helper class provides validated header multimaps. It is also inconsistent with the expectation that RequestOptions should perform early validation to prevent malformed HTTP requests from being sent.

Expected behavior:
- Setting an invalid header name (e.g., "=") via any header-setting method of RequestOptions should throw an IllegalArgumentException.
- Setting an invalid header value (e.g., "\r") should likewise throw an IllegalArgumentException.
- The validation must respect the system property vertx.disableHttpHeadersValidation (if set to true, validation is skipped entirely).

This would align RequestOptions with the validation already present in HttpHeaders.headers() and prevent silent propagation of bad headers.
```

Fix issue 4232 - Validate header names in RequestOptions


The `HttpServerMetrics` SPI currently has `requestBegin`, `requestEnd`, `requestReset`, `responsePushed`, and `responseEnd`, but no callback that fires precisely when the server _starts_ writing the response. Add a `responseBegin` method to `HttpServerMetrics` that is invoked when the server commits the response headers (the point at which the response is "begun" and status/headers become observable). The callback must carry the corresponding request metric and the `HttpServerResponse` object.

Both HTTP/1.x and HTTP/2 response paths must invoke the new callback. The test infrastructure's `DummyVertxMetrics` and `FakeHttpServerMetrics` must be updated to satisfy the revised interface, and the fake metrics implementation should capture the response so existing lifecycle tests can assert ordering: the response must _not_ be set before `responseBegin` fires, and must be set afterward.


**Title:** Endpoint cache should re-resolve addresses when `EndpointResolver.isValid `returns false, not throw

**Description**
The `EndpointResolver` SPI includes an `isValid(D state)` method that lets a resolver signal that a previously obtained endpoint state is no longer current (e.g. due to configuration changes, health-checks, etc.). The current internal caching of resolved endpoints does not react to this signal on subsequent lookups. Instead, if a cached entry exists and its state is invalid, subsequent usage (such as selecting a server from the endpoint) throws an `IllegalStateException`. This makes the `isValid` mechanism effectively useless for cache-invalidation purposes.

**Expected behavior**
When the endpoint resolution logic looks up an address that already has a cached `ManagedEndpoint`, it should check whether the associated state is still valid by calling `EndpointResolver.isValid`. If the state is invalid, the system should transparently trigger a fresh resolution for that address instead of returning the invalid cached entry or throwing. The re-resolution must happen even when the cache entry has not yet expired by time.

**Acceptance criteria**
- Endpoint lookups that hit a cached but invalid state (according to `isValid`) must re-resolve the address without throwing any error to the caller.
- The behaviour must be correct both when the initial resolution produces an already-invalid state, and when a previously valid state later becomes invalid.
- Existing time-based expiration of cached endpoints should continue to work independently.
- Appropriate disposal/cleanup of the invalid state should be performed when it is replaced.

**Context**
- Relevant SPI: `io.vertx.core.spi.endpoint.EndpointResolver` — the `isValid` and `resolve` contracts.
- Affected area: the endpoint resolution internal caching layer (likely around `EndpointResolverImpl` and its `ManagedEndpoint` management).

This issue is about making the internal caching layer honour the invalidation signal that the SPI already exposes, thereby allowing custom resolvers to dynamically mark endpoints as stale and have them refreshed automatically.

The endpoint resolver implementation should better use the address resolver validity check.


Fix periodic timer context lifecycle: when a periodic is scheduled from a duplicated context, the periodic must not capture that duplicate, as it would artificially extend the duplicate's lifecycle and cause all periodic events to share the same duplicate context. Instead, the periodic should always capture the base (unwrapped) context, and each timer event should be dispatched on a fresh duplicate of that base context. This applies to both `Vertx.setPeriodic` and `Context.setPeriodic`. The dispatch must still run on the correct event-loop thread as before. Regular timers (`setTimer`) must remain unchanged and continue to reuse the calling context (even if it is a duplicate).

Periodic tasks will reuse any context to deliver timer events independantly of the context. Unlike timers (which reuse the current context whether it is a duplicate) a periodic should not capture duplicate context as they would extend the lifecycle of the duplicated context. Instead a periodic should duplicate the current context so each timer event will have its own independant lifecycle.


We need convenience assertion methods on `Future` that simplify testing when using Vert.x’s test runner (or any agent that listens to context exception reports).
Currently, writing a test that expects a successful or failed future requires either inspecting the `AsyncResult` manually or using `onComplete` and raising an assertion manually. That boilerplate is repetitive and error-prone when several futures are involved.

**Request**
Add two new terminal handler methods to `io.vertx.core.Future`:

1. A method that **expects the future to succeed**:
   - If the future succeeds, the supplied result handler is called with the success value.
   - If the future fails, an `AssertionError` is raised and reported via the Vert.x context’s exception handler (so that a test runner can pick it up as a test failure).
   - The original failure should be available as the cause of the `AssertionError`.

2. A method that **expects the future to fail**:
   - If the future fails, the supplied failure handler is called with the `Throwable` cause.
   - If the future succeeds, an `AssertionError` (without a cause, or with a descriptive message) is raised and reported in the same way.

**Behavioral invariants**
- Both methods are **terminal**: they consume the future’s outcome but do not transform it. The returned future reference is the same instance (fluent style), though the operation itself is terminal; subsequent handlers added are not guaranteed to be called in order.
- Any exception thrown by the user-provided handler (in the success/failure case) must also be routed through the Vert.x exception handler, consistent with how `onComplete` and other handlers work.
- The assertion error must be delivered to the exception handler of the context the future is associated with, so that tools like the Vert.x test runner can record a test failure without crashing the test thread.
- The methods should behave correctly regardless of whether the future is already completed or completes later (asynchronous completion is supported).
- The methods must work with all implementations of `Future` (e.g., context-specific futures, `Future.succeededFuture()`, `Future.failedFuture()`, etc.).

**Motivation**
These are intended to be used in unit tests where the test framework integrates with Vert.x’s `exceptionHandler` mechanism to detect assertion failures. They remove the need for explicit `if (asyncResult.failed()) { throw ... }` blocks and make test intent clearer.


**Title:** Unregistering an event-bus consumer inside its handler can skip discard-handler notifications for queued messages

**Description**
When a consumer is paused, messages are sent to its address, and the consumer is later resumed, the consumer’s internal pipeline drains any buffered messages. If the consumer is unregistered from inside its handler during that drain (i.e., while one queued message is being processed and others are still pending), the discard handler may not be called for the remaining pending messages. Instead those messages are silently lost or cause internal state corruption, leading to unpredictable behavior (for example, assertions about message delivery ordering or about the discard handler being invoked may fail).

**Expected behavior**
When a consumer is unregistered while there are still queued messages, every pending message that was not delivered to the handler should be delivered to the consumer’s discard handler. This must hold even when the unregistration happens inside the handler itself during a batch drain.

**Steps to reproduce (local event bus)**
1. Create a consumer with a message handler and a discard handler on some address.
2. Pause the consumer.
3. Send at least two messages to the address (they will be queued because the consumer is paused).
4. Resume the consumer.
5. In the message handler for the first delivered message, call `consumer.unregister()`.
6. Observe: the discard handler is not notified for the second (and any later) message; those messages disappear.

**Impact**
Applications that rely on the discard handler to release resources or handle undelivered messages will leak state or misbehave. In the worst case the internal message-channel state becomes corrupted, causing errors that are hard to diagnose. The problem is particularly dangerous because it depends on the timing of the drain operation and may pass tests under light load while failing under load or with specific high-water-mark settings.

Fix reentrant close in InboundMessageHandler


## Bug: HTTP/2 client write promises hang when stream creation fails after writes are queued

When an HTTP/2 client sends a request that queues writes (e.g., due to an asynchronous boundary before stream creation), and the underlying stream creation subsequently fails, the queued write operations do not fail. Instead, those write futures never complete, leaving callers waiting indefinitely.

**Observed symptom:**
After the server closes the connection while the client is still issuing writes to a newly created request, some `write` or `end` promises may never resolve or reject. This is most visible under concurrency and timing pressure.

**Desired behavior:**
When stream creation fails, any delayed (queued) header-write or data-write operations that had not yet been dispatched must fail with the same failure. The caller's write promise should be failed, not abandoned.

**Scope / constraints:**
- The fix should cover both header writes and data writes that are subject to asynchronous dispatch (i.e., writes that may be deferred past the point where stream creation can still fail).
- The behavior change must apply to the HTTP/2 client path.
- No change to the public API is required; this is purely a correctness fix for promise lifecycle.
- The fix must not break normal successful request/response flows.

**Test scenario (conceptual):**
Deploy multiple concurrent verticles that each send many chunked writes over HTTP/2 to a server that closes the connection shortly after accepting it. Verify that every write promise is completed (either succeeded or failed) and that no promises leak or hang.

HTTP/2 client queued writes should fail when stream creation fails.


**Title:** Expand shared data map value types and expose AsyncMap keys/values in codegen

**Background**
`LocalMap` and `AsyncMap` currently reject types that are either immutable and safe (`BigInteger`, `BigDecimal`) or can be safely deep‑copied (`Serializable`, `ClusterSerializable`). Additionally, `AsyncMap.keys()` and `AsyncMap.values()` are annotated with plain `@GenIgnore`, which completely removes them from generated language APIs (e.g., RxJava) even though the methods are perfectly usable at runtime.

**Requested changes**

### 1. Broaden the type acceptance rules for shared data maps

- Accept the following types in addition to the already-supported set (primitive wrappers, `String`, `byte[]`, `Shareable`):
  - `java.math.BigInteger`
  - `java.math.BigDecimal`
  - Any object implementing `java.io.Serializable` (unless it already implements `Shareable` or `ClusterSerializable`).
  - Any object implementing `io.vertx.core.shareddata.ClusterSerializable` (unless it already implements `Shareable`).

- Reject `null` values/keys explicitly by throwing a `NullPointerException` rather than allowing them to cause obscure failures downstream.

### 2. Define the correct copy semantics

When a value is retrieved from a shared data map, the following copy behavior must be observed:

- **Immutable types** (all primitive wrappers, `String`, `BigInteger`, `BigDecimal`): return the same object (no copy needed).
- **`byte[]`**: deep copy (as before).
- **`Shareable`**: delegate to `.copy()`.
- **`ClusterSerializable`**: copy by writing to a `Buffer` via `writeToBuffer`, then instantiating a new instance via the no‑arg constructor and reading back with `readFromBuffer`.
- **`Serializable`**: deep copy via `ObjectOutputStream`/`ObjectInputStream` round‑trip.

For the new copy paths (`ClusterSerializable` and `Serializable`), emit a **debug‑level log message** (using the Vert.x logger) recommending that the developer implement `Shareable` for better performance.

### 3. Fix `AsyncMap` codegen for `keys()` and `values()`

Change the existing `@GenIgnore` on the following methods in `io.vertx.core.shareddata.AsyncMap` to use `@GenIgnore(PERMITTED_TYPE)`:

- `Future<Set<K>> keys()`
- `void keys(Handler<AsyncResult<Set<K>>>)`
- `Future<List<V>> values()`
- `void values(Handler<AsyncResult<List<V>>>)`

This allows the code generator to include these methods in generated Rxified/other-language APIs without losing type safety.

**Safety / invariants**
- Every value stored through one of the newly accepted types must be isolated from the original object when retrieved, unless the type is inherently immutable.
- The existing type‑checking and copy logic for the already‑supported types must not change behavior.
- Local map and async map tests must still pass; no existing valid usage should break.

**Scope**
The main type‑validation and copy logic lives in the shared‑data implementation package. The `AsyncMap` changes are in the public API. No changes to `LocalMap` interface are needed—just the backing implementation.


```
Fix `TCPSSLOptions` so that the mutable collections returned by `getEnabledCipherSuites()`, `getCrlPaths()`, and `getCrlValues()` are not lost when `SSLOptions` is later created or when the options are serialized/deserialized as JSON.

Currently, the getters delegate to an `SSLOptions` object that may not exist yet, so mutations applied directly to the returned collections can be silently discarded. This also causes the JSON round-trip of Options to fail for these fields.

Make sure that any changes made via the getters survive until the `SSLOptions` object is constructed, and that the JSON representation of the options correctly reflects all modifications.
```

TCPSSLOptions should handle the lazy aspect of returning collections that can be mutated and do something about it


When a Vert.x DNS client is created with an IPv6 DNS server address — either a literal IPv6 address or a hostname that resolves to an IPv6 address — it should be possible to send and receive DNS queries over IPv6. Currently only IPv4 seems to be handled; using an IPv6 DNS server leads to a failure. Additionally, if the DNS server host cannot be resolved to any IP address, creating the client should fail immediately with a clear `IllegalArgumentException`, instead of allowing the client to be created and then failing later with a confusing low-level error.

**Acceptance criteria**

1. An `DnsClient` configured with an IPv6 server address must successfully perform DNS queries (e.g., resolve A records) without throwing an exception or binding the wrong socket type.
2. An `DnsClient` configured with a hostname that resolves to an IPv6 address must behave identically — the client must bind to an appropriate IPv6 socket and complete the query.
3. An `DnsClient` configured with a hostname that cannot be resolved to any IP address must throw an `IllegalArgumentException` at creation time, containing a message that clearly indicates the host could not be resolved.
4. IPv4-only DNS servers must continue to work unaffected.

DnsClient throws an exception when calling a Dns server over IpV6 (#2458)


When using `JsonParser` in buffering mode (either `objectValueMode` or `arrayValueMode`), floating-point numbers in the input JSON are silently dropped or cause the handler to receive an incorrect value. The parser should include any floating-point numeric values in the buffered `JsonObject` / `JsonArray`, but currently they are not handled when the internal buffering path reconstructs the object/array. The fix should make sure that tokens of type `VALUE_NUMBER_FLOAT` are properly written into the buffer so that the final value contains the expected `Double`.

Fixes #2155 JsonParser fails to handle floating point numbers in buffering mode


**Title:** `MetricsProvider.getMetrics()` should return null for cleaned `CleanableObject`s

**Description:**
When a `CleanableObject` wrapping a client (e.g., `CleanableHttpClient`, `CleanableNetClient`) is closed/cleaned, calling `getMetrics()` currently throws an `IllegalStateException` because it calls `getOrDie()` unconditionally. The `MetricsProvider` SPI contract (as seen from outside) should handle this cleanly: after cleanup the metrics are no longer available, so `null` should be returned rather than an exception.

**Expected behavior:**
- For any `CleanableObject` subclass that implements `MetricsProvider`, `getMetrics()` must return `null` when the underlying delegate has already been cleaned.
- When the delegate is still alive, behavior must remain unchanged (return the metrics as before).

**Constraints:**
- Do not change the public API of `CleanableObject` or `MetricsProvider`.
- The fix should apply to all existing client wrappers that combine `CleanableObject` with `MetricsProvider` (currently `CleanableHttpClient` and `CleanableNetClient`).
- The implementation must use the safe `get()` accessor instead of `getOrDie()` and perform the null check to decide the return value.

MetricsProvider#getMetrics() should return null on cleaned objects.


**Title:** Use `VertxHttpHeaders` for incoming HTTP requests to reduce allocations

**Background**
The server’s response path already uses `VertxHttpResponseEncoder` with `VertxHttpHeaders`, a custom header implementation that implements `MultiMap` and avoids the wrapping overhead of `HeadersAdaptor`. Incoming requests, however, still rely on Netty’s default `HttpRequestDecoder`, which stores headers as Netty’s `DefaultHttpHeaders`. Every access to request headers in `HttpServerRequestImpl.headers()` creates a `HeadersAdaptor` wrapper, adding allocation and indirection costs.

**Request**
Extend the same optimisation to request decoding:

1. Introduce a custom HTTP request decoder that uses `VertxHttpHeaders` for header storage instead of Netty’s default headers. The decoder must respect all configuration from `HttpServerOptions` (max initial line length, max header size, max chunk size, header validation setting, and initial buffer size).
2. Wire this decoder into the `HttpServerImpl` HTTP/1 pipeline in place of the standard `HttpRequestDecoder`.
3. In `HttpServerRequestImpl.headers()`, detect when the underlying `HttpHeaders` is already a `MultiMap` (i.e., a `VertxHttpHeaders` instance) and return it directly without wrapping in a `HeadersAdaptor`.

**Additional consideration**
The benchmark `HttpServerHandlerBenchmark` sets up an embedded channel that mimics the server pipeline. Ensure the benchmark continues to work correctly after the change – if the benchmark uses a raw `HttpResponseEncoder` it may need to handle `VertxHttpHeaders` directly (similar to how the production `VertxHttpResponseEncoder` encodes headers) so that request headers round-trip as expected.

Use VertxHttpHeaders in HttpRequestDecoder as well which create less objects


## HTTP/2 client connection pool races with immediate server GOAWAY after connection establishment

When an HTTP/2 client establishes a new connection, the connection pool must install an eviction handler (for connection-close tracking) and a concurrency-change handler (for stream-count tracking) on the fresh connection before the connection can be safely used. Currently, these handlers are installed asynchronously after the connection is returned from the internal connector. This creates a window where a server-initiated GOAWAY frame – which can arrive immediately after the TLS handshake – gets processed before the handlers are installed. The result is that the pool never learns about the eviction, leading to leaked connection references, incorrect active-connection counts, and potential concurrency tracking inconsistencies.

The problem manifests as misbehaving HTTP/2 client pools when servers eagerly send a graceful-shutdown GOAWAY right after accepting a new connection. The pool may keep the connection alive and attempt to use it, or fail to decrement its reference count, eventually exhausting the pool.

The fix should ensure that the pool’s eviction and concurrency-change handlers are installed *before* the new connection is handed back to the caller and exposed to any I/O events. In particular, the internal connector API must not allow the caller to observe the connection until those handlers are in place, eliminating the race with an early GOAWAY frame.

Acceptance criteria:
- Under an HTTP/2 server that sends an immediate GOAWAY after a connection is accepted, the client pool properly evicts the connection, cleanly removes it from its tracking, and does not subsequently attempt to use it.
- No change to public client APIs; the adjustment should be contained within the internal connection-establishment plumbing.

HttpChannelConnector can be called from any thread, it should therefore use a callback and not a future to ensure that there is no race when the continuation code process the connection. A connection might be received and process immediately a GO_AWAY frame before the connection is processed leading to adding the connection to the pool before the eviction handler is set on the connection.


Remove `TypeReference` overloads from `JacksonCodec` and `JsonEvent`

`JacksonCodec` currently exposes several `fromString`, `fromBuffer`, `fromValue`, and static `decodeValue` overloads that accept a Jackson `TypeReference`. Because `TypeReference` is only meaningfully supported when the full Jackson databind is available (i.e., inside `DatabindCodec`), these methods never work correctly in the base `JacksonCodec`—they either perform a lossy class erasure or simply throw an exception. The same is true of `JsonEvent.mapTo(TypeReference)`: the method delegates to `JacksonFactory.CODEC.fromValue`, which in the base codec again throws an exception. This leads to confusing failures for anyone using `JacksonCodec` directly (or any JSON SPI implementation other than databind) when those `TypeReference`-accepting methods appear to be available.

We should remove the `TypeReference`-accepting public methods from `JacksonCodec` itself (including the static `decodeValue` helpers that take `TypeReference`), from the `JsonEvent` interface, and from the default implementation. Only `DatabindCodec` should continue to offer `TypeReference`-based decoding, as it does today.

After the removal, any existing code or tests that exercise these removed entry points must be updated to call `DatabindCodec` directly (i.e., by instantiating a `DatabindCodec` instance and using its `fromString`/`fromBuffer` overloads that accept `TypeReference`), or the relevant test paths should be confined to the `DatabindCodec` subclass. The goal is that `JacksonCodec` and the JSON parser event API no longer advertise `TypeReference` support, accurately reflecting what is actually supported without databind.


`Buffer.getByteBuf()` returns a duplicate wrapped in `Unpooled.unreleasableBuffer(…)`. While this makes the duplicate safe against accidental release, the internal buffer field itself is still a standard pooled/releasable `ByteBuf`. If the internal buffer is ever exposed directly (for example, by a new accessor or through existing internal use), it can be released and lead to use-after-free. Additionally, the extra `unreleasableBuffer` wrapper adds unnecessary indirection.

Desired change:
- Change the internal heap buffer used by `BufferImpl` so that it is intrinsically unreleasable — `release()`, `retain()`, and `touch()` should be no-ops, and `refCnt()` should always remain 1. The buffer must remain unpooled.
- When `PlatformDependent.hasUnsafe()` is true, use an unsafe variant of the same unreleasable heap buffer for performance.
- Update `BufferImpl.getByteBuf()` to simply return a duplicate of the internal buffer without applying the `unreleasableBuffer` wrapper, because the internal buffer (and any derived buffer) should already be unreleasable.
- Ensure that duplicates created from such a buffer are also unreleasable and maintain independent reader/writer indices.

The fix should have no impact on existing `Buffer` behavior beyond the internal implementation detail; all existing tests for buffer slicing, reading, writing, and ref-counting behavior (where callers might call `release()/retain()`) must still pass, and accidental release attempts must remain harmless.

Vert.x heap buffer now use a ByteBuf implementation that is un-releasable, un-instrumented and un-pooled. As it is un-releasable, this avoids to wrap it with an un-releasable wrapper when used. - close #3560


**Bug: HTTP/1.x pipelined requests cause incorrect invocation of `HttpServerMetrics.requestBegin`/`requestEnd`**

When HTTP pipelining is enabled (multiple requests sent on the same connection before the first response completes) and server metrics are active (`MetricsOptions.setEnabled(true)`), the `requestBegin` and `requestEnd` callbacks on `HttpServerMetrics` become desynchronised from the application’s processing of the request.

**Observed symptoms**

1. **`requestBegin` is sometimes omitted entirely** – For some pipelined requests, the metrics infrastructure never calls `requestBegin`.
2. **`requestEnd` fires independently of application execution** – The `requestEnd` callback is triggered based purely on when the raw HTTP body is fully received on the connection, not when the application has finished reading or handling the request. This can cause `requestEnd` to fire for a request before the corresponding `requestBegin` callback, or before the application’s request handler has even been invoked.

**Expected behaviour**

The metrics events should reflect the application’s view of the request lifecycle:

- `requestBegin` must be emitted **when the application starts processing the request** (i.e., when the server dispatches the request to the configured `requestHandler`, including for every pipelined request).
- `requestEnd` must be emitted **after the application has finished receiving the request body** (i.e., after the end‑of‑body is delivered to the application, not just when the data arrives on the wire).

In particular, for a sequence of pipelined requests R1, R2:

- R1.requestBegin → R1.requestEnd (after R1 body end)
- R2.requestBegin → R2.requestEnd (after R2 body end)

should hold, and the ordering must be consistent with the application’s actual execution, not with raw I/O events.

**Impact**

Any monitoring or tracing built on top of the `HttpServerMetrics` SPI (including built‑in implementations) will produce incorrect timings, missing events, and broken parent‑child relationships for pipelined HTTP/1.x traffic, making reliable observability impossible.

**Constraint for the fix**

The timing change must apply only to the metrics and tracing event calls. The underlying request dispatching and response handling must not be altered; the request/response lifecycle visible to the application handler must remain unchanged. The fix should affect HTTP/1.x only, and must not break non‑pipelined scenarios.

HTTP server request events wrong metrics events for pipelined requests (#4091)


```
Title: Repeated calls to MessageConsumer.unregister() fail and cause double metric removal

Summary:
Calling unregister() on an already-unregistered MessageConsumer either fails to invoke the completion handler or causes incorrect side effects (e.g., handler removed from metrics twice). The unregister operation should be idempotent and safe to call multiple times.

Steps to reproduce:
1. Create an event bus consumer via vertx.eventBus().consumer("address", handler).
2. Attach a metrics implementation (or use the built-in metrics support).
3. Call consumer.unregister(ar1 -> { … }); inside that completion callback, call consumer.unregister(ar2 -> { … }) again.
4. Observe the second completion handler behavior and the state of the metrics registrations.

Observed behavior:
- The second unregister() callback may never be invoked, or it may fail with an error such as “Consumer unregistered before registration completed”.
- If metrics are enabled, the handler is removed from the metrics collection twice, which can lead to incorrect counts (e.g., negative remaining registrations) or assertion failures in test/validation setups.

Expected behavior:
- The second unregister() call should complete successfully, invoking the completion handler with a succeeded future (since the consumer is already unregistered).
- No additional side effects should occur—metrics or internal registrations should only be removed once.
- The consumer remains in a consistent “unregistered” state regardless of how many times unregister() is called.

Impact:
Applications that defensively unregister a consumer (e.g., from multiple cleanup paths) can end up with lost callbacks or broken metrics. This is especially problematic in frameworks or libraries that manage consumer lifecycles and may attempt to unregister an already-unregistered consumer.
```

MessageConsumer should unregister only once - see #3054


Make `ContextInternal` implement `java.util.concurrent.Executor`. The `execute(Runnable)` method must run the given task on the context, with the same context lifecycle as `runOnContext(Handler)`:
- The task must see `Vertx.currentContext()` set to the context.
- Any uncaught throwable from the task must be routed to the context's exception handler (if set) rather than being lost.
- On worker contexts the execution must be ordered and scheduled on the worker pool's ordered executor, respecting pool metrics.
- On event-loop contexts the task must be scheduled on the Netty event loop.
- The contract must hold for duplicated contexts as well.

The existing internal `emitAsync(E event, Handler<E> handler)` implementations effectively provide the underlying scheduling; rename and repurpose them as needed to serve `execute(Runnable)` without introducing duplicate scheduling logic. Ensure the dispatch mechanism properly wraps the runnable in begin/end-dispatch calls to maintain context thread-local state.

ContextInternal now extends Executor that executes tasks on the context


Fix a capacity accounting error in the connection pool: when the pool closes an idle connection because no waiters are queued and its capacity is fully reclaimed, the pool currently subtracts the connection’s maximum concurrency from total capacity instead of the connection’s actual capacity-in-use. This causes the pool’s capacity counter to drift and can lead to premature refusals of new connection requests.

After the fix, ensure that when the pool is completely drained and all connections are closed, the pool’s internal counters (available capacity, weight, and waiters) all return to zero.

Check invariant at the end of stress tests and fix issue where the incorrect capacity was redeemed when a connection is closed by the pool


Title: Add `HttpUtils.normalizePath` with proper RFC 3986 path normalization for general HTTP use

**Context**
The core `HttpUtils` class (in `io.vertx.core.http.impl`) already provides a `removeDots` method that resolves `"."` and `".."` segments in a URI path. However, a complete path normalization step that combines dot-segment removal with leading-slash enforcement, duplicate-slash collapsing, and unreserved percent-decoding is currently only available in vertx-web’s routing code. That normalization logic is useful for any HTTP handling, not just routing.

**Request**
Add a public static method `normalizePath(String path)` to `HttpUtils` with the following behavior:

- **Empty/null:** return `"/"`.
- **Leading slash:** always ensure the result starts with `'/'`.
- **Duplicate slashes:** collapse any run of consecutive `'/'` characters into a single `'/'`. Trailing slashes (if the normalized path logically ends with a separator) should be preserved after collapsing.
- **Percent-encoding of unreserved characters:** before dot removal, decode any percent-encoded octet that represents an *unreserved* character according to RFC 3986 (ALPHA, DIGIT, `'-'`, `'.'`, `'_'`, `'~'`). Characters outside this set (e.g., `%2F`, `%2E`) must **not** be decoded at this stage, so that they remain opaque to dot-segment resolution.
- **Invalid percent-encoding:** if a `'%'` is not followed by two hex digits, or the hex digits do not form a valid hex value, throw `IllegalArgumentException`.
- **Dot-segment removal:** apply the existing `removeDots` logic to resolve `"."` and `".."` segments. The percent-decoding step above must happen before dot removal, but only unreserved characters are decoded, so encoded dots (`%2E%2E%2F`) are left as-is and do **not** cause segment navigation.

**Note**
The implementation should rely on the already-existing `removeDots` method in `HttpUtils` to finalize dot segments after the other normalizations are applied.

Port the utils removeDots and normalizePath from web to core, as they are usefull not just to web routing but HTTP in general


**Title:** Client WebSocket exception handler not called for server protocol violations

**Description:**
When a Vert.x HTTP client upgrades to a WebSocket connection via `HttpClient.websocketStream(...)` (or similar) and the server later sends a frame that violates the WebSocket protocol, the client WebSocket's registered `exceptionHandler` is never invoked. Instead, the connection closes abruptly and only the `closeHandler` fires; the protocol error is silently dropped.

**Expected behavior:**
Any decoding or protocol error occurring on an upgraded client WebSocket must be delivered to the WebSocket's `exceptionHandler`. For example, if the server sends a control frame (opcode `0x8`, `0x9`, `0xA`) with a zero-length payload, that violates RFC 6455 and should result in a `ProtocolException` (or an appropriate `IllegalStateException`) being passed to the exception handler, before or concurrently with the close handler firing.

**Acceptance criteria:**
- When a client-side WebSocket receives a protocol-violating frame, the WebSocket's `exceptionHandler` is called with an exception describing the violation.
- The WebSocket's `closeHandler` still fires (the connection is no longer usable).
- Non-WebSocket HTTP client connections must remain unaffected: errors on plain HTTP/1.x connections should still be routed to the in-flight response stream handlers, with no regression.
- The fix should not alter the behavior of server-side WebSockets (server-side exception handling for client protocol violations already works correctly).

WebSocket client does not report server protocol violations on the WebSocket exception handler - fixes #2610


**Request:**
Vert.x supports a stateless execution mode (e.g., for virtual-thread compatibility) where all handler invocations must be dispatched through the event-loop/context machinery. The `MessageConsumer`'s discard handler is currently invoked directly via `discardHandler.handle(message)`, ignoring the context dispatch contract. This causes incorrect behaviour in stateless mode.

Make the discard handler dispatch through the consumer's context (`context.dispatch(message, discardHandler)`) like regular message handlers do.

Additionally, the event-bus test hierarchy (`EventBusTestBase`) needs to produce a `Vertx` instance that runs in stateless mode so the entire test suite exercises this path properly. Update the test base to enable stateless execution.

Update EventBus test hierarchy to use the stateless mode


**Title:** `Vertx.close()` hangs and leaks resources when called while a verticle deployment is still in progress

**Description**
When `Vertx.close()` is invoked while a verticle is still being deployed (i.e., `start()` has been called but its `Promise<Void>` has not yet been completed), the deployment future returned by `deployVerticle()` never resolves, and the shutdown process hangs indefinitely. Depending on the verticle, this can also leave resources (e.g., open sockets, timers, file handles) unreleased because the verticle’s lifecycle never reaches a terminal state.

A typical scenario:
- A verticle’s `start(Promise<Void> startPromise)` method calls `vertx.close()` before completing the start promise.
- The `deployVerticle(...).onComplete(...)` callback is never invoked.

**Expected behavior**
Calling `Vertx.close()` should immediately cause all in-progress deployments to be considered failed. This means:
1. Any pending start promise must be failed with a suitable exception (e.g., indicating the verticle was undeployed before startup finished).
2. The corresponding `deployVerticle` future must complete exceptionally, allowing the caller to observe the failure.
3. The `stop()` lifecycle method of the verticle must **not** be invoked, because the verticle never successfully transitioned to the “started” state.
4. After failing all pending deployments, the shutdown process should proceed normally without hanging.

**Impact**
This bug makes it unsafe to close a Vert.x instance from within a verticle’s `start()` method, and risks resource leaks in any scenario where `close()` races with deployment. The fix should ensure that the DeploymentManager’s shutdown sequence (`undeployAll`) properly handles verticles that are still in the deploying state, failing their start promises and cleaning up their associated contexts.

When a vertx instance is closed, there might be pending verticle in deployment which might leave resources unreleased.


Add a method to the `NodeSelector` SPI that lets the `ClusterManager` ask whether the selector wants registration updates for a given event bus address. By default the method should return `true` to preserve existing behavior. Override it in the built-in `DefaultNodeSelector` so that it returns `false` when there are no local consumer selectors for that address (i.e., no registrations that would benefit from receiving updates). This lets cluster managers avoid unnecessary backend queries when no node is interested in an address.

Give ClusterManager a chance to know if NodeSelector wants updates for an address (#3846)