Skip to content

Latest commit

 

History

History
1448 lines (1374 loc) · 321 KB

PROGRESS.md

File metadata and controls

1448 lines (1374 loc) · 321 KB

Documentation note (2026-09-19): Implemented the dedicated Reaper process (run_reaper_process) and supervisor lifecycle management in src/reaper.cpp and src/appserver.cpp. The Reaper process runs in an isolated child process using Linux timerfd (armed with store.earliestExpiry using TFD_TIMER_ABSTIME) and signalfd for immediate unblocking on SIGTERM/SIGINT. The supervisor in appserver.cpp forks and monitors the Reaper via SIGCHLD, automatically respawning it if it ever exits unexpectedly, and signals clean termination on server shutdown (SIGINT/SIGTERM) with PR_SET_PDEATHSIG protection. Covered by ScopeStoreTest.ReaperProcessForkTimerAndSignalWakeup.

Documentation note (2026-09-19): Implemented the Reaper cycle processor (reap_expired_scopes) in include/webstrada/reaper.h and src/reaper.cpp. When expired records are detected, store.claimExpired atomically copies scope data into memory and deletes the rows within a single SQLite transaction, releasing database locks immediately before CFML code runs. The reaper then deserializes SessionScope and ApplicationScope in memory and executes onSessionEnd(SessionScope, ApplicationScope) and onApplicationEnd(ApplicationScope) via Application.cfc (associated via cfc_path in cf_scope). Added unit test ScopeStoreTest.ReapExpiredScopesInMemLifecycle verifying in-memory callback execution, scope argument passing, and zero-lock execution.

Documentation note (2026-09-19): Implemented ColdFusion background lifecycle invocation helpers (cf_invoke_on_session_end, cf_invoke_on_application_end, and their *_with_info variants) in src/core/core_component.cpp and include/webstrada/cf8.h. Inside onSessionEnd(SessionScope, ApplicationScope) and onApplicationEnd(ApplicationScope), parameters are passed strictly as explicit arguments (arguments.SessionScope, arguments.ApplicationScope), while implicit request scopes (CGI, URL, FORM) and built-in scopes are unbound. All standard output generated within these callbacks is silently discarded, and unhandled exceptions are caught and logged without aborting caller execution or background workers. Wired sessionInvalidate() to invoke onSessionEnd synchronously before evicting the session, fully verified against Adobe ColdFusion 2025 (tests/cfm/session_end_test/test.cfm, tests/cfm/session_end_edge_test/test.cfm) and covered by WorkerTest.ScopeSessionInvalidateInvokesOnSessionEnd.

Documentation note (2026-09-19): Updated application and session scope expiration handling across worker and ScopeStore. Session and application accesses now perform atomic lock-free touchSession / touchApplication checking against expires_at in SQLite WAL store (touchRow). On expiration, stale live in-memory sessions are evicted and fresh sessions/cookies minted. Added unit tests for session expiration and fresh token generation (WorkerTest.ScopeSessionExpiresAndMintsFreshSession).

Documentation note (2026-09-19): Documented WebStrada's intra-request execution tracking and profiling system in README.md (enabled and managed in the Admin Panel). The profiler tracks granular timing across template execution, Application.cfc lifecycle callbacks (onRequestStart, onRequestEnd), database queries, custom tags, and CFC instantiations/method invocations, as well as line-level execution tracing persisted in SQLite.

Compatibility note (2026-09-08): the ColdFusion 2025 feature-gap audit is summarized in README.md and technically decomposed in TODO.md. The repository's existing tag/function tables remain the source of truth for exact unsupported names. The audit additionally records pending 2025 language features (??, ?., =>, spread, destructuring, multi-assignment, dynamic properties, QoQ enhancements, and cacheMaxIdleTime) and ColdFusion 2025 Update 8 capabilities (AI/LLM, MCP, RAG/vector stores, passkeys/Argon2, Sets, and async APIs).

Compatibility note (2026-09-08): the elevated host full unit suite currently passes 1,432/1,478 tests; 12 host-level failures remain documented in BUGS.md. The formerly failing ComponentTest.ScriptFormConstructorCallsAndWriteOutput and the obsolete CreateGUID assertion now pass; the sandbox adds one environment-only log-path failure.

Compatibility note (2026-09-08): <cferror> request and exception handlers now populate Browser, RemoteAddress, HTTPReferer, and QueryString from CGI request fields; both diagnostics paths include the captured template/line location; and Suppressed renders with CF's Java Throwable-array form. Covered by CferrorTest.RequestHandlerIncludesLocationAndRequestMetadata and CferrorTest.ExceptionErrorStructIncludesRequestMetadataAndCfSuppressedForm.

Compatibility note (2026-09-08): <cferror> error.stackTrace and error.rootCause.stackTrace now expose the captured CFML call stack as innermost-first path:line[:FUNCTION] entries. This is intentionally CFML runtime information, not a Java stack trace; covered by CferrorTest.ExceptionErrorStructIncludesCfmlStackTrace.

Compatibility note (2026-09-07): IsProtected, IsAuthenticated, and IsAuthorized are obsolete ColdFusion security functions (removed in CF MX / CF 6). In modern Adobe ColdFusion (CF 2021/2025), calling them throws Variable <NAME> is undefined. (verified on the RDS host) and UDFs with these names are allowed. Implemented in fn_isprotected.cpp, fn_isauthenticated.cpp, and fn_isauthorized.cpp reproducing the exact CF 2025 error. Byte-verified against Adobe ColdFusion 2025 (tests/cfm/is_protected_authenticated_authorized.cfm and JitExpressionTest.Tier2Is*).

Compatibility note (2026-09-07): LambdaOperator (=>), safe navigation ObjectMember (?.), and null-coalescing TernaryOperator (??) are added to cfml_definition.json. The codegen stage throws unsupported operator when encountering any of these operators across expressions, statements, struct literals, function parameter declarations, and sharp interpolations. Covered by JitExpressionTest.UnsupportedOperatorsThrow.

Compatibility note (2026-09-07): CFScript keywords in cfml_definition.json updated with complete keyword set (var, function, new, this, super, null, try, catch, finally, if, then, else, switch, case, default, break, continue, while, do, for, in, return, throw, rethrow, retry, component, interface, property, pageencoding, import, include, param, lock, transaction, thread, public, private, remote, package, static, final, abstract, required). Supported keywords are hooked across statement and expression codegen (including null literal, switch/case/default, closures, member contexts, variable identifier positions, and tag attribute parsing). Unsupported script keywords (lock, transaction, thread, param, retry, component, interface, property) throw unsupported keyword in codegen when used as statements or expressions. Covered by JitExpressionTest.ScriptKeywordsSupportAndUnsupportedThrows.

Compatibility note (2026-09-07): SpreadOperator (...) is added to cfml_definition.json (inside Expression, ScriptExpression, Parenthesis, and SquareBrackets). The codegen stage now throws unimplemented operator when encountering the spread operator across expressions, struct/array literals, function parameter declarations, and sharp interpolations. Covered by JitExpressionTest.SpreadOperatorUnimplementedThrows.

Compatibility note (2026-09-07): IncDecOperator (++ / --) is now a single dedicated grammar token in cfml_definition.json, placed inside ExpressionTokens. Pre-increment (++x, --x) and post-increment (x++, x--) are fully supported in CFScript expressions: post-form returns the old value and then mutates the variable; pre-form mutates first and returns the new value. The engine already handled the two-adjacent-sign-token case; this stage wires up the dedicated single-token grammar so the textparser produces an IncDecOperator token instead of two AddOperator tokens. Byte-verified against Adobe ColdFusion 2025 (tests/cfm/incdec_operator_test.cfm; the original tests/cfm/cfscript_incr_test.cfm continues to pass).

Compatibility note (2026-09-08): CFScript while and do-while loops execute correctly when the grammar tokenizes their statement keywords as Keyword. The reproducer tests/cfm/cfscript_while_test.cfm emits 012|DO|134|W and matches Adobe ColdFusion 2025 exactly; focused JIT coverage is in JitExpressionTest.ScriptWhileAndDoWhile.

Build note (2026-09-08): Release optimization flags and link-time optimization are configured centrally in CMakeLists.txt; build-release.sh now only selects the Release configuration and builds it. LTO is always enabled for Release. Both build scripts explicitly select GCC; Release links use GNU ld.bfd. The redundant Release -O3 and unused shared-linker flag overrides were removed; CMake's standard Release flags already provide -O3, and this project builds static libraries and executables only. Release size flags now enable per-function/data sections and linker garbage collection; stripping remains enabled for the executable targets.

Get all technical info from: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tag-summary.html

Compatibility note (2026-09-02): the built executables are now all-lowercase: the FastCGI server is bin/webstrada (was bin/WebStrada) and the CLI is bin/webstrada-cli (was bin/WebStrada-cli; webstrada-tests was already lowercase). CMake targets renamed accordingly (webstrada, webstrada-cli); runtime references updated in Dockerfile, deploy/s6-rc.d/webstrada/run, http-dev.py, tests/verify_with_coldfusion.py, tests/verify_draw_pixels.py, README, and AGENTS build instructions. Log paths/names are lowercase too: the default log directory is now /var/log/webstrada/ (was /var/log/WebStrada/) and the CSV thread-name field is http-nio-webstrada-exec-1.

Compatibility note (2026-09-02): the Docker admin-builder stage upgrades npm to 12.0.2 (npm install -g npm@12.0.2) so npm's "New major version of npm available!" reminder no longer shows during npm ci/ng build. The bundled update notifier is intentionally left enabled so the reminder returns when a newer npm (e.g. 13.x) ships.

Compatibility note (2026-09-02): the Docker admin-builder stage now pins FROM --platform=$ADMIN_PLATFORM so the Angular SPA is built natively on the host platform instead of under QEMU. Cross-arch buildx builds previously died with SIGILL/exit 132 (qemu: uncaught target signal 4) during npm ci && npm run build on the emulated arm64 target. The output is a static SPA, so it can be copied into any target arch unchanged. BUILDPLATFORM is a buildx-only automatic arg that plain docker build force-blanks, so a custom global ARG ADMIN_PLATFORM (default linux/amd64) is used; build_docker.sh derives the host platform from docker info and passes it via --build-arg.

Compatibility note (2026-09-02): the system SQLite stores (ScopeStore, CacheStore, ProfilerStore) are opened in SQLite shared-cache mode (sqlite3_open_v2 with SQLITE_OPEN_SHAREDCACHE) with PRAGMA journal_mode=WAL for multi-process access. The one-time WAL switch on a brand-new database is retried with a bounded busy loop and busy_timeout=5000 is set before the switch, because several prefork workers opening the database for the first time at the same moment would otherwise hit PRAGMA journal_mode's database is locked (it does not honor busy_timeout) and a losing worker would silently run the whole process without a scope/cache/profiler store. Cross-process coordination remains SQLite's own WAL locking (SQLITE_OPEN_SHAREDCACHE only shares the page cache between connections in the SAME process). Regression coverage: CacheTest.ConcurrentFirstOpenFromMultipleProcesses.

Compatibility note (2026-08-27): <cfmail> is accepted as a non-delivering stub that logs all evaluated attributes and consumes its body, including nested <cfmailpart> content. No SMTP operation is performed.

Compatibility note (2026-08-27): <cfmailpart> and <cfmailparam> are also accepted as non-delivering logging stubs; nested mail content is consumed without page output or SMTP/file-attachment work.

Compatibility note (2026-08-27): Adobe script-form savecontent variable="..." { ... } and include "..."; statements are compiled as statements rather than being sent to the expression parser. This fixes loading script CFCs such as Mango's sampledash plugin; verified in tests/cfm/script_savecontent_test.cfm.

Compatibility note (2026-08-26): quoted string keys in named function arguments (for example 'emailto' = '') are accepted by the JIT and interpreter, matching Adobe ColdFusion. This fixes plugin CFC loading for Mango_2_1; verified in tests/cfm/quoted_named_argument_test.cfm.

Compatibility note (2026-08-27): Mango_2_1 setup author creation now passes password, name, email in the order required by AdminUtil.newAuthor, then places the role in argument 7 and active=true in argument 8. The previous setup call validated the author's name as an email and stopped with “Email is not valid.”

Compatibility note (2026-08-26): typed Boolean UDF argument conversion now reports Adobe ColdFusion's argument-validation message when a value cannot be converted, including the uppercase argument name. Verified against CF 2025 in tests/cfm/typed_boolean_argument_error_test.cfm.

Compatibility note (2026-08-26): indexed assignment through an undefined CFC this member creates the member as a struct before descending into it. This fixes MangoBlog setup's this.mappings["/org/mangoblog"] initialization. Regression coverage: ComponentTest.IndexedAssignmentCreatesMissingComponentMember and tests/cfm/component_implicit_member_test/.

Compatibility note (2026-08-26): script-form function declarations nested in tag-based CFCs are registered as component methods, fixing MangoBlog's unqualified structBlend() call from Mango.cfc. Regression coverage: ComponentTest.TagComponentScriptFunctionIsComponentMethod and tests/cfm/component_script_method_test/.

The tag-based CFC path also skips hoisted script declarations when compiling the surrounding <cfscript> block as executable code. Access and return-type modifiers such as private function and public string function are handled, so each declaration is compiled once as a component method. Regression coverage is in tests/cfm/component_script_private_method_test/.

Compatibility note (2026-08-26): dotted assignments in tag-based component methods now flatten parser-glued names such as this.settings before walking the live component this scope. Missing intermediate members are created as structs and writes persist on the component instance, fixing MangoBlog's Blog.cfc:setSettings() SETTINGS.SKINS.PATH failure. Regression coverage: ComponentTest.TagComponentNestedThisAssignmentCreatesMembers and tests/cfm/component_nested_this_assignment_test/.

Compatibility note (2026-08-26): cfscript new expressions now accept the class-name Variable token emitted before the constructor call token, including plain names such as new LocaleManager(...). This fixes MangoBlog's internationalizer construction path. Existing coverage includes ComponentTest.NewAutoCallsInit.

Compatibility note (2026-08-26): compound expressions in nested <cfsavecontent>/<cfoutput> bodies now compile in the active component-method context, preserving unqualified private CFC method calls. This fixes MangoBlog's Statistics formatAge() call. Regression coverage: ComponentTest.PrivateMethodInsideCfsavecontent and tests/cfm/cfsavecontent_private_method_test.cfm.

Compatibility note (2026-08-26): active component/UDF local and arguments scopes now take precedence over an included template's caller-local scope; this fixes component calls from MangoBlog's request hook. Regression coverage: ComponentTest.CfincludeComponentCallUsesCalleeArguments.

Compatibility note (2026-08-26): the same callee-local precedence applies when a component/UDF is invoked from a custom tag. Unqualified assignments now reach the active UDF's var/arguments scope before the custom tag's private scope; this fixes MangoBlog's Cache.checkAndRetrieve() return-type failure at the HTTP root URL. Regression coverage: ComponentTest.ComponentUdfLocalsWinOverCustomTagVariables and tests/cfm/custom_tag_udf_scope_test.cfm. Functions reference: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions.html

CFML Tags

Compilation cache

The per-worker TemplateCache checks source modification times for both .cfm templates and .cfc component definitions. This covers ordinary components and the nearest Application.cfm/Application.cfc; edited application files are picked up on the next request, with a changed Application.cfc rebuilt and reinstantiated. Regression coverage is in WorkerTest.ApplicationCfmRecompilesAfterSourceChange, WorkerTest.ApplicationCfcRecompilesAfterSourceChange, and WorkerTest.ComponentRecompilesAfterSourceChange.

Session CFC graphs are retained in a worker-local live-session cache before JSON persistence, preserving MangoBlog's session.user Author→Role→Preferences graph across requests in the default single-worker development server. An older session already flattened to JSON must be replaced with a fresh session.

Custom-tag templates now keep their private variables scope active while executing, even when invoked from inside a component method; nested GetBaseTagData() calls therefore retain values such as MangoBlog's Pages.cfm currentPage, counter, and to variables.

Query columns used as implicit variables now resolve after the active UDF's local variables and arguments. This matches Adobe ColdFusion when a query column (for example i) has the same name as a component method's local loop index. Verified by tests/cfm/query_local_index_collision_test.cfm and the corresponding JIT regression test.

Component method calls now preserve an independent local-scope context through nested member dispatch. Numeric <cfloop> indexes are rebound at the loop-body entry, after the callee context is active, preventing Queue-style code from reading the caller's i value. Custom-tag private variables are also lower precedence than an active nested CFC/UDF local or arguments scope; this fixes MangoBlog's Queue.getElements() when called from a tag template. Verified with the Queue/nested-holder fixtures under tests/cfm/; the HTTP MangoBlog request now passes the Queue stage and reaches its login/database setup.

Tag Implemented Notes
cfabort ✅ Yes Compiler + runtime
cfajaximport ❌ No
cfajaxproxy ❌ No
cfapplet ❌ No
cfapplication ✅ Yes Compiler + runtime. Enables the APPLICATION scope and (with sessionmanagement="true") the SESSION scope, loading or creating them in the SQLite-backed scope store. Attributes: name (unnamed apps use a fixed "" key), sessionmanagement, applicationtimeout/sessiontimeout (numeric days or d,h,m,s; default CF Admin values when omitted), setclientcookies (default yes), searchimplicitscopes (default yes/true matching ColdFusion — unqualified names search the implicit scopes CGI/URL/FORM/COOKIE), loginstorage (session stores the <cflogin> auth key in the session scope instead of the default CFAUTHORIZATION_<apptoken> cookie). Sessions are minted per request when the client has no valid CFID/CFTOKEN cookie; the CFID comes from a server-global counter and CFTOKEN is a <16 lowercase hex>-<uppercase UUID> pair, emitted as Set-Cookie: CFID=..; Expires=<30y>; Path=/; HttpOnly exactly like CF. cf_application_enable() now skips the reset-and-reload when the application scope already has data (populated by a previous onApplicationStart), so live Component instances — whose method tables are lost through JSON round-trips — survive across requests. On the first request (empty scope) the store is loaded as before. ScopeStore supports atomic lifetime extension (touchApplication, touchSession using conditional UPDATE with sqlite3_changes()), system-wide earliest expiry querying (earliestExpiry), and atomic retrieval + deletion of expired scopes (claimExpired). Verified byte-for-byte against CF 2025 in tests/cfm/cfapplication__test.cfm + WorkerTest.Scope + ScopeStoreTest.
cfargument ✅ Yes Tag form: <cfargument name=".." type=".." required=".." default=".."> inside <cffunction> declares a parameter. default follows CF tag-attribute semantics (a quoted value is a literal string unless #...#-wrapped, e.g. default="1+1" → the string "1+1", default="#1+1#" → 2 — verified on CF); required="true" with no default throws CF's The X parameter to the Y function is required but was not passed in. (MissingArgumentException). <cfargument> outside <cffunction> is rejected at compile time (CF renders a server error).
cfassociate ✅ Yes Compiler + runtime
cfauthenticate ❌ No
cfbreak ✅ Yes Compiler + runtime
cfcache ✅ Yes Compiler + runtime
cfcalendar ❌ No
cfcase ✅ Yes Handled within cfswitch
cfcatch ✅ Yes Script form: catch (type var) { } clauses dispatch by type via the CF exception-class model: cf_eh_best_match (src/core/core_eh.cpp) implements findThrowableTarget/findCustomTarget exactly like CF 2025. Built-in runtime errors match by the closest class ancestor across ALL clauses (order-independent; ties by clause order): catch types resolve to CF classes (expression→RuntimeException, application→ApplicationException, database→DatabaseException, template→TemplateException, missinginclude→MissingIncludeException, security→AccessControlException, object→ObjectException, searchengine→SearchEngineException, any→Exception), so catch(expression) catches every built-in runtime error (division by zero, undefined var, ... all RuntimeExceptions), a real 1/0 (ExpressionException) is NOT caught by catch(application), and a missing <cfinclude> (MissingIncludeException) matches catch(missinginclude)/catch(template)/catch(expression)/catch(any). Custom throw/<cfthrow> types match by exact name over any (a later catch(myType) beats an earlier catch(any), dotted-prefix partial matches work: throw(type="my.custom.type") → catch(my.custom)/catch(my)), and never match catch(expression)/catch(application) — CF's findCustomTarget. Reserved built-in type names in throw/<cfthrow> (database, template, security, object, missinginclude, expression, lock) are replaced by an ApplicationException reporting type Application with "Attribute validation error for tag CFTHROW." / "... function throw." (CF 2025's ThrowTag.validate), so throw(type="database") does NOT match catch(database). User-thrown custom exceptions carry a custom-ness flag (webstrada::exception::m_isCustom, persisted through capture→rethrow via cfvariant::m_isCustomException) so <cfrethrow> keeps exact-name matching. cfabort never catches; cfcatch binds the caught struct (TYPE/MESSAGE/DETAIL/ERRORCODE/EXTENDEDINFO/TAGCONTEXT) and may be chained. TAGCONTEXT is populated by the call-stack (stacktrace) feature: the JIT pushes a {template pathname, line} frame at every template / UDF / component-method / component-construction entry, updates the top line before each statement and pops on every exit (normal and unwinding); the first landing pad an unwinding exception reaches snapshots the live stack into the in-flight exception (m_stackTrace), so cfcatch.tagContext is an array of {TEMPLATE, LINE} structs with element [1] = where the error occurred, matching CF (byte-verified: tests/cfm/stacktrace_*_test.cfm, StackTraceTest unit tests). A rethrow/the unmatched cftry path re-raise a fresh exception carrying the original tagContext (cf_eh_throw rebuilds m_stackTrace from the captured TAGCONTEXT), like CF rethrowing the same Java exception. Tag form: <cfcatch type="t" var="name"> inside <cftry>; <cfcatch> outside a <cftry> is rejected at compile time. Verified byte-for-byte against CF 2025 in tests/cfm/cftry_tag_test.cfm + cfcatch_class_matching_test.cfm + JitExpressionTest.Catch*/CustomThrow*/ReservedType*/CfincludeTest.MissingIncludeMatchesMissingIncludeHierarchy unit tests (was BUGS.md "cfcatch type matching is exact-name only").
cfchart ❌ No
cfchartdata ❌ No
cfchartseries ❌ No
cfchartset ❌ No
cfclient ❌ No
cfclientsettings ❌ No
cfcol ❌ No
cfcollection ❌ No
cfcomponent ✅ Yes Compiler + runtime. .cfc files compile into a component definition (ComponentInfo): the top-level <cfcomponent> body becomes a construction function that runs at instantiation (setting this./variables. members), <cffunction> blocks become JIT-compiled methods (component_method_entry_fn, direct calls with the instance's variables/this scopes), <cfproperty> declarations feed SerializeJSON and GetComponentMetaData. Components are first-class values (cfvariant::Component): this scope (public members, keys uppercased like CF) doubles as the struct surface (IsStruct/StructKeyList/SerializeJSON/o.x work on it), variables scope holds private instance variables, unqualified reads resolve local → variables → this (never the caller's page), unqualified writes go to localScope for declared local names (var) and to the component variables scope otherwise, and methods are invoked by direct JIT entry with CF access control (private methods are not callable externally; StructKeyExists/StructKeyList hide them). During method execution, rt->currentPath is set to the component's file path so relative component/include paths resolve against the component directory. Component dot-paths (createObject("component", "a.b.c"), new a.b.c(), extends, implements) resolve relative to the current template/component directory first and fall back to the web root (rt->webRoot), matching ColdFusion; dot paths with directories or filenames containing literal dots (e.g. MangoBlog_1.4.3) use backtracking on-disk resolution to resolve correctly. Function argument and return types with custom component names (e.g. returntype="Setup") validate against the component/interface hierarchy via IsInstanceOf. this.mappings in Application.cfc is supported: custom directory mappings set in Application.cfc's pseudo-constructor (e.g. this.mappings["/org/mangoblog"] = ...) are registered on the application context and used to resolve dot/path component references (createObject("component", ...), new ..., extends="...", implements="...") and <cfinclude template="..."> paths. super. explicitly dispatches methods to parent implementations across multi-level inheritance chains (super.method()). Script-form components (component { ... } at the top of a .cfc) are supported: the textparser grammar's overrideStartTokens parses a .cfc that starts with component/import/interface/final/abstract or a ////* comment as a top-level ScriptExpression, and compileComponent detects that form, parses the component attributes (extends="..", etc.), collects property name=".." type=".." ...; declarations, script methods (with public/private/package/remote/final access modifiers, return types either before the name or as returntype="..", default params) and compiles the remaining body statements as the construction body. Verified byte-for-byte against CF 2025 in tests/cfm/component_*_test.cfm + tests/cfm/mappings_test/ + ComponentTest unit tests.
cfcontent ✅ Yes Compiler + runtime. Sets the response MIME type and output character encoding from the type attribute (text/html; charset=ISO-8859-1), discards prior output by default (reset, default yes) or preserves it (reset="no"), and can replace the page output with a file's (file, optional deletefile) or variable's (variable) contents (binary passthrough; all other page output is ignored). The charset is applied to the whole buffered response and to the Content-Type header, and can be changed freely until the first byte is written to the web engine — after that, type/charset changes are silently ignored like ColdFusion. A reset inside <cfsilent> clears the whole page output (not just the discard buffer), matching CF 2025 (was BUGS.md #10) — verified in tests/cfm/cfcontent_inside_cfsilent_test.cfm. Encodings: UTF-8 (default, from server config), ISO-8859-1 / latin1 / latin-1 / 8859_1, Unicode (= UTF-16 with BOM), UTF-16LE/BE, US-ASCII; unknown names throw CF's "Unsupported encoding format" error. The send path (sendEncoded, common.cpp) writes UTF-8/binary output straight from the buffer (zero copy) and converts other charsets into one reusable thread_local scratch (no per-flush allocation; stringToBytes gained a std::vector<char>& overload — was TODO Option C, implemented 2026-08-10). Verified byte-for-byte against CF 2021 in tests/cfm/cfcontent_*_test.cfm.
cfcontinue ✅ Yes Compiler + runtime
cfcookie ✅ Yes Compiler + runtime (tag_weboutput.cpp). Defines a browser cookie: name (required, uppercased unless preserveCase), value (default "", stripCRLFd, URL-encoded like CF's URLEncoder — only A-Za-z0-9 pass through, everything else %XX uppercase-hex UTF-8, space→%20 — unless encodevalue="no"), expires ("now"→0, "never"→946080000 (30y), "session"→session cookie, M/D/YY[YY] slash dates → diff from now, else 86400×N days; a non-numeric/date value throws CF's "The value X cannot be converted to a number." Expression error), secure/httpOnly/path (default /)/domain/samesite (default /). The Set-Cookie bytes reproduce CF's CookieScope exactly: plain non-empty cookies without SameSite go through Tomcat (value quoted + Version=1 + Max-Age=N when the value is not a token — the separator set "(),:;<=>?@[\]{} + space/controls — and the Expires as RFC1123 EEE, dd MMM yyyy HH:mm:ss GMT, Thu, 01 Jan 1970 00:00:10 GMT for maxAge 0); empty values and SameSite cookies go through CF's own createCookieHeader (raw value or "", Max-Age=N; Expires=<RFC850 12-hour EEE, dd-MMM-yyyy hh:mm:ss GMT>). The raw (unencoded) value lands in the current request's COOKIE scope under the (uppercased) name. Validation matches CF: a name with a space/comma/semicolon or $-prefixed (or empty) throws the Application error "An error has occurred while trying to create a cookie with name X."; a missing name and unknown attributes are compile-time errors with CF's exact messages. After the response is committed the header is dropped but the COOKIE scope entry is still set (verified on CF). Verified against CF 2025 in tests/cfm/cfcookie_test.cfm 🟢 + WebOutputTest.Cookie* unit tests (the Set-Cookie bytes themselves, which the byte-verifier cannot see).
cfdbinfo ✅ Yes Compiler + runtime
cfdefaultcase ✅ Yes Handled within cfswitch
cfdirectory ✅ Yes Compiler + runtime (tag_directory.cpp). list (default action) builds CF's FileListTable query — NAME, SIZE, TYPE, DATELASTMODIFIED (rendered as MM/dd/yyyy hh:nn:ss tt, CF's java.util.Date cell rendering), ATTRIBUTES (empty on Linux), MODE (always empty), DIRECTORY, LINK — into the name variable, with the filter pipe-delimited wildcard, recurse (CF's recursiveGetFiles order), sort (CF's MultiColumnSorter: comma-delimited `COLUMN [ASC
cfdocument ❌ No
cfdocumentitem ❌ No
cfdocumentsection ❌ No
cfdump ✅ Yes Compiler + runtime
cfelse ✅ Yes Handled within cfif
cfelseif ✅ Yes Handled within cfif
cferror ✅ Yes Compiler + runtime
cfexchangecalendar ❌ No
cfexchangeconnection ❌ No
cfexchangecontact ❌ No
cfexchangeconversation ❌ No
cfexchangefolder ❌ No
cfexchangefilter ❌ No
cfexchangemail ❌ No
cfexchangetask ❌ No
cfexecute ✅ Yes Compiler + runtime (tag_execute.cpp). Runs the name executable with the (Unix-tokenized) arguments and captures stdout to the page, the variable attribute or the outputfile, and stderr to errorVariable/errorFile. Faithfully mirrors CF 2025's ExecuteTag + ProcessExecutor: string arguments are tokenized with getProcessCommand's rules (space-separated, double-quoted segments kept as one argument, a quote mid-argument is literal; array arguments map element-per-arg), relative output/error file paths resolve against the CFML temporary directory (GetTempDirectory), and the timeout (seconds) limits how long CF waits for the process output — with no timeout CF does not wait at all (ProcessExecutor's non-blocking race: blocking is false and timeout is 0, so neither waitFor nor the join runs and the output is read immediately — on CF 2025 the variable stays undefined and the page gets nothing; this engine reproduces the race and leaves the variable unbound). With a timeout the tag waits for both output streams to hit EOF (the process to exit) and captures the output; a deadline miss throws CF's catchable Application "The requested action did not complete in a timely manner. Timeout period expired without completion of <name>." (message + detail). A failed exec throws "An exception occurred when invoking an external process." with the detail "The cause of this exception was that: java.io.IOException: Cannot run program "<name>": error=N, <strerror>." Valid attributes are ARGUMENTS,ERRORFILE,ERRORVARIABLE,NAME,OUTPUTFILE,TIMEOUT,VARIABLE; name is required and unknown attributes are compile-time errors with CF 2025's JSP-style message. Verified byte-for-byte against CF 2025 in tests/cfm/cfexecute_test.cfm (🟢; the no-timeout race is covered by unit tests) + CfexecuteTagTest unit tests.
cfexit ✅ Yes Compiler + runtime
cffeed ✅ Yes Compiler + runtime (tag_feed.cpp). action "read" parses an RSS (0.9x/1.0/2.0) or Atom 1.0 feed from a URL (libcurl) or an XML file into the name feed struct (metadata + item/entry array), the properties metadata struct, the query entries query (CF's FeedTable column set) and/or the raw xmlvar; action "create" renders an RSS 2.0 or Atom 1.0 feed from the name struct (or query+properties+columnmap) into xmlvar/outputfile (with the overwrite flag). The generated XML reproduces Rome's WireFeedOutput pretty-print byte-for-byte (CRLF line separators, 2-space indent, <?xml version="1.0" encoding=".."?> header), the RSS channel/item element order (title/link/description/language/rating/copyright/pubDate/lastBuildDate/docs/managingEditor/webMaster/skipHours/skipDays/cloud/generator/ttl/category/image/textInput + items; items: title/link/description/source/enclosure/category/pubDate/expirationDate/author/comments/guid), the Atom feed/entry element order (title/links/category/author/contributor/subtitle/id/generator/rights/icon/logo/updated; entries: title/links/category/author/contributor/id/updated/published/content/summary), the RFC822 (EEE, dd MMM yyyy HH:mm:ss GMT) and W3C (yyyy-MM-dd'T'HH:mm:ss'Z') date rendering (string values parsed with Rome-style W3C/RFC822 parsing, offsets converted to the GMT-based render), the metadata/item struct key set (pubDate strings, description {type,value} for RSS >= 0.94 and Content structs for Atom, guid {value,isPermaLink}, enclosure/category/author arrays, cloud/image/textInput sub-structs) and the FeedTable query columns. Enclosures are parsed into the struct/query but not downloaded (enclosureDir/overwriteEnclosure/ignoreEnclosureError are accepted). Byte-verified against CF 2025 in tests/cfm/cffeed_test.cfm (🟢 — the feed package was installed on the RDS host on 2026-08-10, see BUGS_CF.md) + CffeedTagTest unit tests.
cffile ✅ Yes Compiler + runtime (tag_file.cpp). Actions read/readBinary (into variable), write/append (from the output attribute or the captured tag body; addnewline default yes; fixnewline; write/append to a missing parent throws CF's FileNotFoundException message), copy/move/rename (source→destination; a destination that is an existing directory (or ends with /) lands the file inside; a missing source throws CF's messages; copy-over-itself is rejected), delete (missing file → CF's "File ... specified in action delete does not exist."), upload/uploadall (multipart via UploadRegistry; the 23-key CF result struct, nameconflict error/overwrite/skip/makeunique, result default "cffile"). After every action the (empty) cffile struct is created; the result attribute is honored only for upload/uploadall (like CF 2025). Unknown attributes are compile-time errors with CF's message; a static invalid ACTION is a compile-time error; missing output/body for write/append, output+body together, and unknown actions are catchable Application errors with CF's text. An un-closed <cffile> (no </cffile>) is a single tag: the tag-body scanner (scanTagBody) treats a missing end tag as body-less instead of swallowing the rest of the template/CFC into a dead block (was the MangoBlog "PARENT" Preferences error — <cffile action="read"> in PreferencesFile.cfc silently dropped every function after it, leaving variables.xmlData as xmlnew(false) and making XmlSearch("/preferences/root") return nothing). Same fix for un-closed <cfzip>. Byte-verified against CF 2025 (tests/cfm/cffile_test.cfm 🟢 + cffile_unclosed_tag_test.cfm + CfzipTagTest.Unclosed* unit tests).
  └ action="append" ✅ Yes
  └ action="copy" ✅ Yes
  └ action="delete" ✅ Yes
  └ action="move" ✅ Yes
  └ action="read" ✅ Yes
  └ action="readBinary" ✅ Yes
  └ action="rename" ✅ Yes
  └ action="upload" ✅ Yes
  └ action="uploadAll" ✅ Yes
  └ action="write" ✅ Yes
cffileupload ❌ No
cffinally ✅ Yes Tag form: <cffinally>...</cffinally> inside <cftry> runs on success, after a caught exception, and before an uncaught one re-raises; an exception raised inside the finally replaces the original. Compiled (duplicated) by the shared native-EH emitter in both the tag and script paths. <cffinally> outside a <cftry> is rejected at compile time. Verified byte-for-byte against CF 2025 in tests/cfm/cftry_tag_test.cfm.
cfflush ✅ Yes Compiler + runtime. Flushes the buffered output to the web engine mid-request (writing the Content-Type header on the first flush), which locks the output charset/type — later <cfcontent> changes are ignored. An empty <cfflush> still commits the response (sends headers / marks the response committed), matching CF 2025: CF locks the header/charset on the first <cfflush> even when nothing is buffered, so a later <cfcontent> charset change is ignored (was BUGS.md " with an empty buffer" — previously an empty flush sent no byte and did not commit; verified against CF 2025 in tests/cfm/cfflush_empty_commits_test.cfm). Flushes encode the buffered UTF-8 via sendEncoded (zero-copy for UTF-8/binary, reusable scratch otherwise — see the cfcontent row). The interval attribute is accepted but not implemented (auto-flush). Verified against CF 2021/2025 in tests/cfm/cfflush_test.cfm.
cfform ❌ No
cfformgroup ❌ No
cfformitem ❌ No
cfftp ⚠️ Log-only stub Compiler + runtime (tag_ftp.cpp). The tag is handler-validated in CF, not TagLibraryInfo-validated, so unknown attributes are accepted and ignored exactly like CF 2025 (verified on the RDS host); only a statically missing action is a compile-time error with CF's message ("Attribute validation error for the CFFTP tag." / "The tag requires the ACTION attribute."). The evaluated attributes are collected into a struct and the runtime cf_ftp_tag only logs the call ([WebStrada] <cfftp> is not implemented; call logged (action=.., server=.., ...)) to the engine log on stderr. No FTP operation is performed — the actual open/close/file/dir actions, secure FTP and action="listDir" remain unimplemented (no FTP client is wired in); CF's per-action required-attribute rules (e.g. action="open" requires SERVER,USERNAME) are NOT enforced.
  └ file/dir operations ❌ No (accepted, call logged only)
  └ open/close connections ❌ No (accepted, call logged only)
  └ secure FTP connections ❌ No (accepted, call logged only)
  └ action="listDir" ❌ No (accepted, call logged only)
cffunction ✅ Yes Tag form: <cffunction name=".." returntype=".." output=".." access="..">...<cfargument>...<cfreturn>...</cffunction> defines a callable UDF. Functions are hoisted across the template (callable before their textual definition, including when the block sits inside <cfif>/control flow, like CF) and registered into the variables scope. The body is compiled like a template (tags, text, <cfoutput>, <cfscript>, nested <cfloop>/<cfif>/<cftry>), sharing the script-form UDF runtime: the arguments scope (upper-cased param keys + positional keys), var-declared names (<cfset var x=..> / <cfscript>var x;</cfscript>) function-local, unqualified non-var writes leaking to the caller's scope (CF's classic gotcha), typed cfargument/returntype coercion (type="struct" / returntype="struct" now accepts component instances (cfvariant::Component), matching Adobe ColdFusion where components are structs/Maps — previously rejected component arguments with "not of type struct", breaking MangoBlog's Event.cfc:setMessage(createObject("component", "Message")); type="xml" accepts an XML document; verified in tests/cfm/udf_component_as_struct.cfm + UdfTypeCoerceTest.ComponentAcceptedAsStruct), and nested <cffunction> registered in the enclosing function's local scope. The output attribute defaults to displayed (output="true" / omitted pass the body's output through, including the body's leading whitespace collapsing to a space; output="false" discards it via the <cfsilent> discard buffer — cf_silent_end is invoked in the function's exit and exception landing pads so early <cfreturn> inside output="false" does not leak the silent buffer to the caller). <cfargument> defaults follow CF tag-attribute semantics (a quoted default="..." is a literal string unless #...#-wrapped, e.g. default="1+1" → the string 1+1, default="#1+1#" → 2); a present-but-empty default="" now binds the empty string (previously left the argument undefined, which changed LSParseDateTime(s, arguments.loc) from CF's English-US empty-locale resolution to the current locale — tracked via a new paramHasDefaultRaw flag in the UDF definition, verified in LocaleTest.TagFormEmptyStringDefaultBindsEmpty). <cfreturn expr> stores the coerced value and exits the function (bare <cfreturn> returns undefined; a void function returning a value throws CF's IllegalReturnException). <cffunction>/<cfreturn> outside a function are compile-time errors (CF renders a server error; a stray nested <cffunction> is skipped). Verified byte-for-byte against CF 2025 in tests/cfm/cffunction_tag_test.cfm + cffunction_edge_test.cfm + UdfTest.TagFunction* unit tests. Exceptions thrown inside a UDF body (script or tag form) are caught by a page-level <cftry>/script try in the caller and by try/catch inside the function — fixed by reusing the single module-wide __gxx_personality_v0 in the JIT (a duplicate created when a try was compiled was auto-renamed __gxx_personality_v0.1, whose .eh_frame references MCJIT could not resolve, leaving external relocations unpatched → SIGSEGV; see llvm_compiler.cpp). Verified byte-for-byte against CF 2025 in tests/cfm/udf_exception_test.cfm.
cfgraph ❌ No
cfgraphdata ❌ No
cfgrid ❌ No
cfgridcolumn ❌ No
cfgridrow ❌ No
cfgridupdate ❌ No
cfheader ✅ Yes Compiler + runtime
cfhtmlhead ✅ Yes Compiler + runtime (tag_weboutput.cpp). Appends text to the response head content, which CF writes before the body output no matter where the tag appeared in the page; multiple calls concatenate in order. The worker's final send and the CLI both emit the head content ahead of the body. text is required (compile-time "Attribute validation error for tag CFHTMLHEAD. It requires the attribute(s): TEXT."); unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are TEXT."); a body form (<cfhtmlhead>...</cfhtmlhead>) is rejected like CF (no body support). Verified against CF 2025 in tests/cfm/cfhtmlhead_test.cfm 🟢 + WebOutputTest.Htmlhead* unit tests.
cfhtmltopdf ❌ No
cfhtmltopdfitem ❌ No
cfhttp ✅ Yes Compiler + runtime
cfhttpparam ✅ Yes Compiler + runtime
cfif ✅ Yes Compiler + runtime
cfimage ✅ Yes Compiler + runtime
cfimap ❌ No
cfimapfilter ❌ No
cfimpersonate ❌ No
cfimport ✅ Yes Compiler + runtime
cfinclude ✅ Yes Compiler + runtime
cfindex ❌ No
cfinput ❌ No
cfinsert ✅ Yes Compiler + runtime
cfinterface ✅ Yes Compiler + runtime
cfinvokeargument ✅ Yes Compiler + runtime
cfinvoke ✅ Yes Compiler + runtime. <cfinvoke component=".." method=".." returnvariable=".." argumentcollection=".."> invokes a component method; component is a component value or a component path (a path string is instantiated on the fly), argumentcollection is a struct of named arguments, returnvariable stores the result. With no component the method resolves as a page UDF. <cfinvokeargument> children add named arguments (see the cfinvokeargument row). Verified against CF 2025 in tests/cfm/component_invoke_test.cfm + cfinvokeargument_test.cfm.
cfmodule ✅ Yes Compiler + runtime
cfjava ❌ No
cflayout ❌ No
cflayoutarea ❌ No
cfldap ❌ No
cflocation ✅ Yes Compiler + runtime
cflock ❌ No
cflog ✅ Yes Compiler + runtime
cflogin ✅ Yes Compiler + runtime (tag_login.cpp)
cfloginuser ✅ Yes Compiler + runtime (tag_login.cpp)
cflogout ✅ Yes Compiler + runtime (tag_login.cpp)
cfloop ✅ Yes Compiler + runtime
  └ index loop ✅ Yes from/to/step; 64-bit bounds so values beyond int32 iterate correctly; body text (including a body that is only plain text, and text before the first nested CFML token) is emitted like CF 2021 — verified byte-for-byte in tests/cfm/cfloop_text_body_test.cfm + LoopPlainTextBodyIsEmitted unit test (previously BUGS.md #1). from/to/step accept CF's bound grammar: a numeric literal (integer or float, whitespace-trimmed), a single #...#-wrapped expression (to="#x + y#", to="#5+2#"), or a mix of literal text and #...# interpolations that concatenate before the numeric cast (to="#x##y#" → "57", to="#x#5" → "55", from="5#x#" → 55) — a bare non-literal expression (to="1 + 2", to="(1 + 2)") is a compile error exactly like CF 2025 (was BUGS.md #26: bounds only unwrapped a single #...#; multi-interpolation failed to compile while CF concatenates the interpolated parts before the cast). Verified byte-for-byte against CF 2025 in tests/cfm/cfloop_bounds_interpolation_test.cfm + LoopBoundsInterpolated/LoopBoundsMultiInterpolation/LoopBoundsFloatLiterals/LoopBoundsBareExpressionRejected/LoopBoundsBeyondInt32 unit tests. #...# in the loop body is context-dependent and matches CF 2021: outside <cfoutput> a bare #q# is emitted verbatim (textparser LoopExpression contextNestedTokens nests SharpExpression only when the loop is lexically inside <cfoutput>/<cfquery>), while inside <cfoutput> it is evaluated (<cfoutput><cfloop from="1" to="2" index="i">#i#</cfloop></cfoutput> → 12) and an escaped ## renders a single # (<cfoutput>#i#:##;</cfloop> → 1:#;2:#;); the ## SharpChar in an output/loop body now emits # instead of nothing (compile_token_list gained a SharpChar case, which also fixed a plain <cfoutput>P##Q</cfoutput> → P#Q) — verified byte-for-byte against CF 2021 in tests/cfm/cfloop_sharp_context_test.cfm + LoopSharpContext unit test (was BUGS.md #1). The index variable is assigned like an unqualified <cfset> (was a WebStrada divergence that broke MangoBlog's Preferences.cfc): inside a function a var-declared loop index is written to the function's local scope (a component method's variables is the instance scope, so writing the index there left the body reading an empty value), a non-var index writes to the component variables / captured parent scope, a parameter-named index to arguments, and at template level to the variables scope — via the new udfAssignScope helper used by cfloop_set_long (numeric) and cfloop_assign_index (list/array/collection), plus the cfscript for (x in coll) iteration variable. A non-var list/numeric index in a plain (non-CFC) UDF also now leaks to the calling page's variables scope like CF. Verified byte-for-byte against CF 2025 in tests/cfm/cfloop_component_scope_test.cfm + ComponentTest.Cfloop*/CfscriptForInIndexScopesToLocalVar/UdfTest.XmlTypedParam* unit tests
  └ list loop ✅ Yes list (+ delimiters, default ,) with an index variable; list expression attrs (list="#x#") supported — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm
  └ array loop ✅ Yes array="#arr#" with an index variable iterating array values — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm
  └ collection loop ✅ Yes collection="#struct#" with an item variable iterating struct keys (uppercased as CF stores literal keys) — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm
  └ condition loop ✅ Yes condition="expr" while-style loop — verified against CF 2021 in tests/cfm/cfloop_variants_test.cfm
  └ query loop ✅ Yes query="q" iterates the query's rows, advancing the query's cursor so #q.col# / #q.currentrow# (and #q.recordcount# / #q.columnlist#) resolve to the current row. A pushed query scope (CF's pushQueryScope) makes unqualified names that match a column (or currentrow/recordcount/columnlist) resolve to the current row before the variables scope (<cfset name="VARSCOPE"> + a name column → the column wins; a non-matching name falls through to variables) — verified byte-for-byte against CF 2025 in tests/cfm/cfloop_query_basic_test.cfm + CfLoopQueryTest unit tests. startrow/endrow bound the range (absolute row numbers; a startrow past the last row skips the body entirely, and endrow beyond the row count stops at the last row, matching CF). group="col" runs the first row of each group (adjacent equal values), with groupcasesensitive="true" switching from the default case-insensitive to case-sensitive grouping — verified byte-for-byte in tests/cfm/cfloop_query_variants_test.cfm + cfloop_query_groupcase_test.cfm. <cfbreak>/<cfcontinue> work inside the loop; query="#varName#" resolves a string as the query variable name; a non-query value throws. After the loop the cursor is restored to its original row and the query scope is popped, so #q.currentrow# after a loop is back to 1 (like CF). The query scope stack is reset per request in scope_begin. Query-column references now stringify/compute as their current row's cell (#q.id#, q.id EQ 2, arithmetic, truthiness), which is what makes q.col and unqualified columns work inside the loop while preserving the existing first-cell behavior when the cursor has not moved (verified: x = q["a"]; #x# → first cell, and the whole tests/cfm/query_column_copy_test.cfm suite still passes).
  └ conditional loop ❌ No
  └ COM collection/structure ❌ No
  └ date/time range ❌ No
  └ list, file, or array ❌ No
cfmail ❌ No
cfmailparam ❌ No
cfmailpart ❌ No
cfmap ❌ No
cfmapitem ❌ No
cfmediaplayer ❌ No
cfmenu ❌ No
cfmenuitem ❌ No
cfmessagebox ❌ No
cfNTauthenticate ❌ No
cfoauth ❌ No
cfobject ✅ Yes Compiler + runtime. <cfobject type="component" name=".." component=".."> instantiates the component and assigns it to the name variable; other object types throw "not supported". Verified against CF 2025 in tests/cfm/component_invoke_test.cfm.
  └ .NET object ❌ No
  └ COM object ❌ No
  └ component object ❌ No
  └ CORBA object ❌ No
  └ Java/EJB object ❌ No
  └ web service object ❌ No
cfobjectcache ✅ Yes Compiler + runtime
cfoutput ✅ Yes Compiler + runtime
cfparam ✅ Yes Compiler + runtime
cfpdf ❌ No
cfpdfform ❌ No
cfpdfformparam ❌ No
cfpdfparam ❌ No
cfpdfsubform ❌ No
cfpod ❌ No
cfpop ❌ No
cfpresentation ❌ No
cfpresentationslide ❌ No
cfpresenter ❌ No
cfprint ❌ No
cfprocessingdirective ✅ Yes Compiler + runtime (codegen_tags.cpp + src/template_reader.cpp). suppressWhitespace and pageEncoding are compile-time directives. suppressWhitespace="no" disables whitespace management inside the body (whitespace-only regions between tags are kept verbatim, e.g. <cfoutput>F</cfoutput>\n<cfoutput>G</cfoutput> → F\nG); "yes"/absent keeps the default management (collapse, e.g. → F G). The value must be a static literal (a dynamic #x# value cannot be compiled — whitespace management is a compile-time decision here; documented in BUGS.md); an invalid static boolean is a compile-time error with CF's "Attribute validation error for CFPROCESSINGDIRECTIVE. The value of the SUPPRESSWHITESPACE attribute is invalid..." message. pageEncoding drives the input-encoding pipeline (template_reader.cpp, implemented 2026-08-10): the source is decoded to UTF-8 following CF's order (UTF-8/UTF-16LE/UTF-16BE BOM is authoritative and a contradictory directive is a compile error — "Cannot use the charset X because the file has a Byte Order Mark indicating it uses the charset Y."; without a BOM the directive re-reads the file with the named charset, else ICU4C detection (min confidence 100) runs, else the config::defaultInputCharset fallback (default UTF-8)). Unknown names → CF's "The specified page encoding, X, is not supported." (empty renders ''); a non-literal #x# value → CF's "Expression at line N has to be constant value." (the line comes from the textparser line map, so multibyte UTF-8 before the directive cannot shift it); unquoted values (pageEncoding=UTF-8) are honored. The script-component form pageencoding ".."; is also supported (skipped at codegen; the reader applies it). Latin-1/windows-1252/ISO-8859-*/Shift_JIS/etc. sources re-decode to correct UTF-8 output. Unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are SUPPRESSWHITESPACE."). Verified against CF 2025 in tests/cfm/cfprocessingdirective_test.cfm 🟢 + input_encoding_*_test.cfm (9 byte-verified fixtures) + WebOutputTest.ProcessingDirective*/TemplateEncodingTest/ComponentTest.ScriptFormPageEncodingDirectiveIsSkipped unit tests (see BUGS_CF.md for the RDS host's disabled ICU detection).
cfprocparam ✅ Yes Compiler + runtime
cfprocresult ✅ Yes Compiler + runtime
cfprogressbar ❌ No
cfproperty ✅ Yes Compiler + runtime. Inside <cfcomponent>, declares component properties (name/type/default/access). Properties are metadata: they are not this-scope members (o.prop1 is undefined, matching CF 2025), but SerializeJSON includes their defaults (coerced to their declared property types: numeric/boolean/string) and respects this-scope overrides. StructKeyList, StructKeyArray, and SerializeJSON list component keys in ColdFusion's Java HashMap bucket iteration order. GetComponentMetaData lists them. Verified against CF 2025 in tests/cfm/component_property_test.cfm + component_property_type_test.cfm + component_order_test.cfm.
cfquery ✅ Yes Compiler + runtime
cfqueryparam ✅ Yes Compiler + runtime
cfregistry ❌ No
cfreport ❌ No
cfreportparam ❌ No
cfrethrow ✅ Yes Script form: rethrow; inside a catch re-raises the exception caught by the innermost enclosing catch to the next enclosing catch (or out of the template), skipping the remainder of the inner try. Compiled as cf_eh_throw(captured) + CreateUnreachable(). Rejected at compile time outside a catch (CF silently aborts there — a CF-side behavior, see BUGS_CF.md). Tag form: <cfrethrow> inside <cfcatch> does the same via g_currentCatchExn; <cfrethrow> outside a catch is rejected. Verified byte-for-byte against CF 2025 in tests/cfm/cfscript_throw_rethrow_test.cfm + tests/cfm/cftry_tag_test.cfm + JitExpressionTest.ScriptRethrow*/TagRethrow* unit tests.
cfreturn ✅ Yes Tag form: <cfreturn [expr]> inside a <cffunction> body stores the (return-type-coerced) expression in the function's return slot and jumps to the common exit block; a bare <cfreturn> returns undefined. Works from inside <cfif>/<cfelseif>/<cfloop> bodies. <cfreturn> outside a function is a compile-time error (CF silently aborts the rest of the template — a deliberate divergence, see BUGS_CF.md). Verified against CF 2025 in tests/cfm/cffunction_tag_test.cfm + UdfTest.TagFunction* unit tests.
cfsavecontent ✅ Yes Compiler + runtime (codegen_tags.cpp + tag_weboutput.cpp). Saves the body's generated content (with whitespace management applied) into the variable name: pushes a capture buffer (like <cfsilent>), compiles the body into it, then assigns the captured text via cfvariant_assign (a dotted/scoped/dynamic name resolves like an assignment — variable="a.b" and variable="#n#" work). variable is required (compile-time "Attribute validation error for tag CFSAVECONTENT. It requires the attribute(s): VARIABLE."); unknown attributes are compile-time errors ("... It does not allow the attribute(s) BOGUS. The valid attribute(s) are VARIABLE."); an invalid variable name (validated with CF's CFVariableLexer state machine, which allows leading digits like 1abc) throws "Cannot set variable with name X." at tag start. An exception in the body still stores the partial content before propagating (CF's SaveContentTag.doCatch): the body is wrapped in a catch-all that assigns the captured buffer and pops it, then re-raises (verified byte-for-byte). Inside <cfsetting enablecfoutputonly> the body text is suppressed like CF (the capture comes out empty). Verified against CF 2025 in tests/cfm/cfsavecontent_test.cfm 🟢 + WebOutputTest.Savecontent* unit tests.
cfschedule ⚠️ Log-only stub Compiler + runtime (tag_schedule.cpp). The tag is handler-validated in CF, not TagLibraryInfo-validated, so unknown attributes are accepted and ignored exactly like CF 2025 (verified on the RDS host); only a statically missing action is a compile-time error with CF's message ("Attribute validation error for the CFSCHEDULE tag." / "The tag requires the ACTION attribute."). The evaluated attributes are collected into a struct and the runtime cf_schedule_tag only logs the call ([WebStrada] <cfschedule> is not implemented; call logged (action=.., task=.., ...)) to the engine log on stderr. No scheduling is performed — no task is created/updated/deleted/run, action="list" produces no query; the scheduler engine is not wired in (the CF 2025 RDS host lacks the scheduler package too — see BUGS_CF.md), and CF's per-action required-attribute rules (e.g. action="list" requires RESULT) are NOT enforced.
cfscript ✅ Yes Compiler + tests
cfsearch ❌ No
cfselect ❌ No
cfservlet ❌ No
cfservletparam ❌ No
cfset ✅ Yes Compiler + runtime
cfsetting ✅ Yes Compiler + runtime (tag_weboutput.cpp). enablecfoutputonly="true" flips a per-request runtime output mode that suppresses plain text written outside <cfoutput> tags (including the whitespace-collapse space between tags and text captured into <cfsavecontent>/<cfxml> bodies, exactly like CF's CFOutput.enablecfoutputonly); <cfoutput> content is never suppressed. The codegen routes every non-<cfoutput> plain-text write through the gated writers cf_write_output_gated/cf_whitespace_space_gated (checked only when compiling outside a <cfoutput> body, via the g_insideCfoutput compile-time thread-local), so the mode is a simple runtime boolean. enablecfoutputonly="false" turns it back off; the mode is reset per request (scope_begin). showdebugoutput and requesttimeout are accepted (this engine has no debug output section or request watchdog). Unknown attributes are compile-time errors ("Attribute validation error for tag CFSETTING. It does not allow the attribute(s) BOGUS. The valid attribute(s) are ENABLECFOUTPUTONLY,REQUESTTIMEOUT,SHOWDEBUGOUTPUT."). Verified against CF 2025 in tests/cfm/cfsetting_test.cfm 🟢 + WebOutputTest.Setting* unit tests.
cfsharepoint ❌ No
cfsilent ✅ Yes Compiler + runtime
cfslider ❌ No
cfspreadsheet ❌ No
cfsprydataset ❌ No
cfstoredproc ✅ Yes Compiler + runtime
cfswitch ✅ Yes Compiler + runtime
cftable ❌ No
cftextarea ❌ No
cftextinput ❌ No
cfthread ❌ No
cfthrow ✅ Yes Script form: throw "msg";, throw <expr>;, bare throw;/throw(), and named attributes throw(type="t", message="m", detail="d", errorcode="e", extendedinfo="i"). Throws a fresh runtime exception from the attribute struct (cf_eh_throw); built-in messages default to type Application; the custom type participates in catch matching (case-insensitive). Both bare (Variable-token) and parenthesized (Function-token) forms dispatch to compile_script_throw_statement. The thrown struct is built in IR via cfvariant_create_struct + cfvariant_index_assign. The statement is terminal (CreateUnreachable after the throw). A complex object thrown as the message (throw s where s is a struct/array/query, or a literal {a:1}/[1]) now throws CF's Expression Complex object types cannot be converted to simple values. with CF's standard detail (was BUGS.md #7 — previously an empty Application exception; verified byte-for-byte against CF 2025 in tests/cfm/script_throw_complex_message_test.cfm + JitExpressionTest.ScriptThrowComplexMessage). Tag form: <cfthrow type="t" message="m" detail="d" errorcode="e" extendedinfo="i"> raises the same exception; attribute values are compiled from the tag's Expression children so quoted values interpolate #expr# (message="at#i#" → at2) and unquoted bare words stay literal strings (CF tag semantics); unknown attributes are ignored. Verified byte-for-byte against CF 2025 in tests/cfm/cfscript_throw_rethrow_test.cfm + tests/cfm/cftry_tag_test.cfm + JitExpressionTest.ScriptThrow*/TagThrow* unit tests. (The bare-throw literal-first arithmetic quirk — CF takes only 5 from throw 5+3 while this engine evaluates the whole expression — is a CF-side erratic behavior, see BUGS_CF.md.)
cftimer ✅ Yes Compiler + runtime
cftooltip ❌ No
cftrace ✅ Yes Compiler + runtime
cftransaction ✅ Yes Compiler + runtime
cftree ❌ No
cftreeitem ❌ No
cftry ✅ Yes Script form: try { } catch (type var) { } finally { } compiled to native Itanium EH in the JIT (invoke/landingpad, __gxx_personality_v0). finally runs on success, after a caught exception, and before an uncaught one re-raises; an exception raised inside finally replaces the original; nested try/catch/finally and try-in-loop verified against CF 2021 in tests/cfm/cfscript_try_catch_test.cfm. Uncaught exceptions are snapshotted (cf_eh_capture) and re-raised (cf_eh_throw) so they keep unwinding to an enclosing try or the request handler; an uncatchable abort is re-raised as an abort_exception (see cfabort row). throw/rethrow in script are implemented: throw raises a fresh exception (see cfthrow row); rethrow; inside a catch re-raises the caught exception to the next enclosing catch (it targets the innermost enclosing catch, g_currentCatchExn set/restored around catch-body compilation; rethrow; outside a catch is rejected at compile time with 'rethrow' is only valid inside a catch block, where CF silently aborts — a CF-side behavior, see BUGS_CF.md). Tag form <cftry>...<cfcatch>...</cfcatch><cffinally>...</cffinally></cftry> compiles to the same EH via compile_tag_try_statement (scans the sibling tokens, tracks nested <cftry> depth, compiles each body region through compile_token_list with its own whitespace state); <cfthrow>/<cfrethrow> work inside it. A <cftry> (or script try { }) with neither <cfcatch> nor <cffinally> is now rejected at compile time like CF — "Context validation error in CFTRY block. A CFTRY must contain at least one CFCATCH clause or CFFINALLY clause." / "Context validation error in ''TRY block." for the script form (was BUGS.md " without a cfcatch/cffinally compiles instead of failing"; verified on CF 2025 in JitExpressionTest.CftryWithoutCatch* unit tests). The script and tag forms are verified byte-for-byte against CF 2025 in tests/cfm/cfscript_try_catch_test.cfm + cfscript_throw_rethrow_test.cfm + cftry_tag_test.cfm + cftry_no_catch_compile_test.cfm + abort_inside_try_test.cfm. Note: the CF 2025 test server returns a server-error for <cfloop> directly inside <cftry> (a CF-side limitation; <cftry> inside <cfloop> works) — this engine handles both.
cfupdate ✅ Yes Compiler + runtime
cfwddx ✅ Yes Compiler + runtime (tag_wddx.cpp). action cfml2wddx serializes a CFML value to a WDDX 1.0 packet, wddx2cfml deserializes a packet to CFML, cfml2js/wddx2js render JavaScript assignment statements (StringFormatter.scriptFormat). The packet format, char escaping tables (UTF8Converter: control chars as <char code='xx'/>, 128-255 as &#xNN;, entities for <>&, attr table adds &apos;/&quot;), Java Double.toString number rendering (5.0, 3.14, 1.0E10), the ISO8601 dateTime with the local timezone offset (or without when usetimezoneinfo="no"), the recordset (<recordset rowCount fieldNames type='coldfusion.sql.QueryTable'>) and the CF HashMap struct-key iteration order (javaHashMapBucket) are byte-verified against CF 2025. wddx2cfml builds structs (bare-text <var>s are skipped, <null/> becomes the empty string, <char code> decoded), arrays, queries and binaries; cfml2js emits var = new Object();/new Array();/ new Date(y, m0, d, h, min, s); with lowercased struct keys and JS-escaped strings. Per-action attribute validation (CF 2025's TLD): cfml2wddx allows ACTION,INPUT,OUTPUT,USETIMEZONEINFO; wddx2cfml allows ACTION,INPUT,OUTPUT,VALIDATE and requires output; cfml2js/wddx2js allow ACTION,INPUT,OUTPUT,TOPLEVELVARIABLE and require input+toplevelvariable — static action values are validated at compile time ("Attribute validation error for tag CFWDDX. It does not allow the attribute(s) X. ..."), dynamic values at runtime ("... for cfwddx."), with CF's byte-exact messages. Verified byte-for-byte against CF 2025 in tests/cfm/cfwddx_test.cfm (🟢; the RDS host and this machine both run Europe/Skopje UTC+2, so no TZ override is needed — the timezone-dependent dateTime offset and the wddx2cfml no-offset dateTime deserialization match directly) + CfwddxTagTest unit tests.
cfwebsocket ❌ No
cfwindow ❌ No
cfxml ✅ Yes Compiler + runtime
cfzip ✅ Yes Compiler + runtime (tag_zip.cpp, minizip). Actions zip (from the source file/directory and/or <cfzipparam> children; entries are stored relative to the source directory; filter, recurse, prefix (a top-level directory), entrypath, storePath, overwrite update-vs-create, directory entries stored with a trailing /), unzip (destination required and must already exist — CF's InvalidDestinationException otherwise; entrypath extracts a single entry, filter, overwrite, storePath; path-traversal guarded), list (the 9-column query NAME/DIRECTORY/SIZE/COMPRESSEDSIZE/TYPE/DATELASTMODIFIED/COMMENT/CRC/ENCRYPTIONALGORITHM into name — NAME is the full entry path (directory entries keep the trailing /), DIRECTORY is the parent path for files ("" for directory entries); showDirectory toggles directory entries, filter matches the entry filename), read/readBinary (the entrypath entry into variable), delete (entrypath removed from the archive; the surviving entries are rewritten). A relative file is created under the OS temp directory like CF. Attribute validation is action-aware at compile time like CF 2025: an unknown attribute names the action's valid set ("Attribute validation error for tag CFZIP. It does not allow the attribute(s) X. The valid attribute(s) are ACTION,ENCRYPTIONALGORITHM,FILE,..."), a missing required attribute ("When the value of the ACTION attribute is READ, it requires the attribute(s): ENTRYPATH."), and a bad static ACTION value ("The value of the ACTION attribute, which is currently X, must be one of the values: ZIP,READ,UNZIP,LIST,READBINARY,DELETE."). <cfzipparam> valid attributes are charset/encryptionalgorithm/password/source/content/entrypath/filter/prefix/recurse with CF's compile-time combination validation (content requires entrypath, prefix only with source). Byte-verified against CF 2025 in tests/cfm/tier2_cfzip_test.cfm (🟢) after the CF zip package was installed on the RDS host.
cfzipparam ✅ Yes Child of <cfzip>: appends source/content/entrypath/filter/prefix/recurse/charset/encryptionalgorithm/password to the active zip context (cf_zip_param); the attribute combination is validated at compile time (content+entrypath, or source, or filter, or entrypath), and a <cfzipparam> outside a <cfzip> throws. Byte-verified with cfzip (tests/cfm/tier2_cfzip_test.cfm 🟢).

Custom tags (cfimport taglib/prefix, prefix:tag, <cf_name>, cfmodule, cfassociate)

Custom tag invocations are fully implemented (compiler + runtime, tag_custom.cpp + codegen_tags.cpp) and byte-verified against CF 2025. <cfimport prefix=".." taglib=".."> registers a per-template prefix→taglib mapping; <prefix:tag .../> / <prefix:tag>...</prefix:tag> invoke the custom tag template taglib/tag.cfm (relative to the invoking template, or absolute under the web root; subdirectories and a trailing slash in the taglib work). The lifecycle mirrors CF's ModuleTag:

  • Start template runs with thisTag.executionMode = "start"; its output goes straight to the caller's buffer. Body output is captured. End template runs with executionMode = "end" and thisTag.generatedContent seeded with the captured body; its own output is captured separately. After the end template, CF's ModuleTag.doAfterBody ordering is reproduced: if the end template assigned thisTag.generatedContent (contentChanged) the new value replaces the body, otherwise the body is kept, and the end template's own output follows both. A self-closing <prefix:tag /> is a pair with an empty body (hasEndTag = "YES"); an unterminated tag is start-only (hasEndTag = "NO").
  • thisTag stores the keys executionMode / hasendtag / GeneratedContent (CF's ThistagScope casing; lookups are case-insensitive). Assigning executionMode/hasendtag throws Expression — The HasEndTag variable in the ThisTag scope cannot be set by the user. / The ExecutionMode variable ... (CF's UnableSetThisTagException). attributes holds the invocation attributes (uppercased keys), caller is the invoking template's variables scope (readable and writable — caller.x = v propagates back), and variables is the custom tag's own private scope that persists across the start/end runs and loop iterations.
  • <cfexit> (bare / exittag) in the start template skips the body and end tag (doStartTag returns SKIP_BODY); method="exittemplate" only stops the start template (the body still runs); in the end template both stop the end template (partial output kept). <cfexit method="loop"> in the end template re-runs the body + end (fresh body capture each iteration, like CF's EVAL_BODY_AGAIN) and in the start template throws the catchable jakarta.servlet.jsp.JspException Exit method Loop not allowed from start tag.
  • <cfmodule template=".." name=".." attributecollection=".."> invokes a custom tag by path or name, reusing the same lifecycle. The taglib for prefixed tags may be a subdirectory (taglib="customtags/sub"), and the public names (CF_<NAME>, CF_<filename>) feed GetBaseTagList/GetBaseTagData. Nested custom tags work (a custom tag template may invoke other custom tags / <cfmodule>; each keeps its own scopes on a stack). <cfassociate basetag=".." datacollection=".."> appends the subtag's attributes to the base tag's thisTag.<datacollection> array. A <prefix:tag> whose prefix is not imported is output as literal text (CF leaves it as text); an imported prefix with a missing tag template is a runtime Unknown tag: <name>. error (CF's is a parse-time error — see BUGS.md).
  • Valueless attributes — a <prefix:tag charset /> (or <cfmodule ... charset />) attribute with no =/value is passed to the tag as the string "true" (len(attributes.charset) == 4, byte-verified against CF 2025), not as an empty string. The attribute parser (parseTagAttrs, codegen_tokens.cpp) recognizes a trailing/middle valueless attribute (a Variable separated from the previous value by whitespace and not continuing an operator/dotted expression) instead of swallowing it into the preceding value — previously <cfmodule template="x.cfm" charset /> failed to compile with "Unexpected tokens in expression" and <prefix:tag charset /> passed "" (which then threw "The value cannot be converted to a boolean." in a <cfif attributes.charset>). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_valueless_attr_test.cfm + customtags/valueless.cfm + customtags/modvalueless.cfm.
  • Paired-tag matching is name-boundary aware — the compiler's scan for a custom tag's closing </prefix:name> uses startWith("<prefix:name"), which wrongly consumed a nested tag whose name merely starts with the same text (e.g. <mango:Page>'s scan treated <mango:PageProperty …> as a nested <mango:Page>). The parent then never matched its closing tag and ran start-only, so a child's GetBaseTagData("cf_page") threw Tag cf_page was not found in ancestor custom tags. (the Mango Blog tags/mango/PageProperty.cfm failure). The matcher now requires a tag-name boundary after the pattern (>, />, whitespace, or end). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_pair_prefix_collision_test.cfm + ComponentTest.CustomTagPairPrefixNameBoundary.

Direct custom tags — <cf_name> resolves name.cfm relative to the invoking template and uses the same start/body/end lifecycle as prefixed custom tags. The underscore form is distinct from built-in tags such as <cflayout>. Verified by ComponentTest.DirectCfUnderscoreInvokesCustomTag and tests/cfm/custom_tag_direct_cf_underscore_test.cfm.

Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag__test.cfm + custom_tags.cfm + customtags/ (the shared custom-tag library) + ComponentTest.CfmoduleInvokesCustomTag/CfassociateCollectsSubtagAttributes/CfimportPathRegistersComponent unit tests.

Compiler internals: exception-safe codegen state

The codegen save/restore of every compile-time thread_local state slot (g_ehContext, g_currentCatchExn, g_currentFuncCleanupBB, g_returnCtx, g_compileInFunctionBody, g_currentOut, g_insideCfoutput) is now exception-safe via ScopedCodegenState<T> (src/codegen/codegen_internal.h). A compile-time abort (an unimplemented tag, a parser/expression error) unwinds the compile function without running the manual restore, so previously the slot was left pointing at dead state and the NEXT component compile read a garbage landingPadBB and segfaulted in LLVM IR construction (llvm::Use::set). Minimal repro: a component whose method body throws at compile time (e.g. the unimplemented <cflock> tag inside a <cftry>) followed by a normal component — the second compile crashed before the fix (observed in MangoBlog setup: SubscriptionHandler's setup() aborts on a <cfschedule> compile abort, then Links/Handler.cfc's compile segfaulted). Now the aborted compile leaves no residue and the next compile runs normally — verified by ComponentTest.AbortedComponentCompileDoesNotPoisonNextCompile.

Abstract database layer

The CFML query runtimes (<cfquery>, queryExecute(), <cftransaction> and the transaction*() built-ins) execute SQL through an abstract database layer (include/webstrada/db.h, src/db/db.cpp): cfml::cf_run_query and the transaction helpers only ever see the webstrada::db::DBConnection / DBResult / DBDriver interfaces, and the concrete backend is selected per datasource by the driver registry (registerDriver / openConnection, which lazily registers the built-in backends).

Backends:

  • SQLite (src/db/db_sqlite.cpp, SqliteDriver + SqliteConnection): each datasource is the per-DSN DNS_<name>.sqlite file (WAL mode + busy timeout); the sqlite3_prepare_v2 multi-statement tail loop, sqlite3_column_* cell conversions, the CF recordcount semantics (DDL → 1, DML → affected rows) and generated-key capture. This is the default backend for a datasource with no config entry (the pre-config behavior). MangoBlog's setup wizard (admin/setup/setup.cfm) now offers SQLite as a database type: choosing it runs setupDatabase()'s <cfinclude template="sqlite.sql"> (a SQLite port of mysql.sql — backtick/charset clauses dropped, KEY/UNIQUE KEY → CREATE INDEX/CREATE UNIQUE INDEX, int(10) unsigned → integer, multi-row INSERTs kept), creating the 19-table blog schema as a DNS_<name>.sqlite file when the datasource is configured with the SQLite backend. MangoBlog's PostsGateway date queries are also ported: the MySQL/MSSQL YEAR()/MONTH()/DAY() calls became SQLite CAST(strftime('%Y'|'%m'|'%d', posted_on) AS INTEGER) (all 27 usages in getByDate/getCountByDate/ getActiveYears/getActiveMonths/getActiveDays), so the calendar getActiveYears/getActiveMonths/getActiveDays archives run on SQLite — the CAST is required because SQLite never equates TEXT ("2026", zero-padded "08") with the integer parameters; getByDate/getCountByDate and the blog listing (getAll/getByIds) are still blocked on SQLite by a pre-existing malformed ON ... ON join chain and a query-column returntype issue (see BUGS.md). Guarded by CfincludeTest.MangoBlogSqliteSetupSchemaRuns / MangoBlogSqliteStrftimeCastDateQueries.
  • Query of Queries (src/db/db_qoq.cpp, runQueryOfQueries): implements <cfquery dbtype="query"> by loading each referenced query object into a private :memory: SQLite database and executing the SELECT there. Table loading is lazy and schema-driven: SQLite's own prepare reports "no such table: X", the name X is resolved through the QoQResolver callback (the CFML scope lookup), the query's typed cells are copied into a temporary table (affinity chosen from the cells: INTEGER/REAL/TEXT/BLOB; dates as ISO text so <cfqueryparam> literals compare), and the prepare is retried. The CFQL + concatenation dialect is reproduced from the materialized column affinities (string+string → ||, string+number → CF's Database error, number+number → +). See the cfquery PROGRESS row for the verified surface and limitations.
  • MySQL/MariaDB (src/db/db_mysql.cpp, MySqlDriver + MySqlConnection, via the MariaDB Connector/C client library libmariadb): a datasource configured with backend="mysql" connects to the server described by its host/port/database/username/password fields. execute() runs the whole SQL script through mysql_real_query with CLIENT_MULTI_STATEMENTS and surfaces only the FIRST result; cells are converted from MySQL field types (int → Number/Long, float/decimal → Float, text/char/varchar and text-charset blobs → String, binary blobs → Binary), non-result statements report affected-rows / the auto-increment GENERATEDKEY (via mysql_insert_id), and transactions use BEGIN/COMMIT/ROLLBACK + SAVEPOINT / ROLLBACK TO. <cfinsert>/<cfupdate> read the table's columns through SHOW FULL COLUMNS (Key="PRI" marks the primary key).
  • PostgreSQL (src/db/db_pgsql.cpp, PostgresDriver + PostgresConnection, via the libpq client library; backend="postgres" with "postgresql"/"pg" aliases): a datasource configured that way connects to the server described by its host/port/database/username/password fields (connection string built from the config; connect_timeout from the query timeout). execute() runs the whole SQL script through PQsendQuery/PQgetResult (the simple query protocol executes a multi-statement string statement by statement) and surfaces only the FIRST result; cells are converted from PostgreSQL column OIDs (bool → Number 1/0 like CF's PG driver (verified on CF 2025: a PG boolean column renders 1/0 and its cell class is java.lang.Integer, not true/false — the byte-compare of postgres_test.cfm forced this fix), int2/int4/int8/oid → Number/Long with the int32 bound, float4/float8/numeric → Float, bytea → Binary decoding both the \x hex and legacy octal escape forms, date/time/timestamp/interval and everything else → the server's text representation), non-result statements report affected rows (PQcmdTuples, DDL → 1) with no implicit-OID generated key (modern PostgreSQL, matching pgjdbc's getGeneratedKeys without RETURNING; an INSERT ... RETURNING statement is a result set and surfaces its columns), and transactions use BEGIN/COMMIT/ROLLBACK + SAVEPOINT / ROLLBACK TO SAVEPOINT. <cfinsert>/ <cfupdate> read the table's columns from information_schema (joined with the PRIMARY KEY constraint in the connection's current_schema()).

A datasource is selected per DSN name by webstrada::config::datasources — a case-insensitive map (CiStrLess, mirroring ColdFusion's case-insensitive datasource names) that preserves the casing of the name as first entered, so appdb / AppDb / APPDB all resolve to the same datasource and the admin panel shows the name as typed. Connection params come from the config entry. The daemon now loads them from the server configuration file (webstrada-config.json, see "Server configuration file" below); the CLI and unit tests additionally support the WSDATASOURCE_<UPPER_DSN>_* environment variables at startup (config::loadDatasourcesFromEnv, see config.h). A datasource with no config entry is treated as SQLite. db::datasourceConfig(dsn) exposes the resolved parameters (backend + server fields) to the metadata layer so <cfdbinfo> dispatches its backend-specific introspection without reopening the connection.

DBResult carries the columns (name/type/values), rowCount, generatedKey and hasGeneratedKey; cf_run_query maps it to the cfvariant Query object and the result struct. DBStoredProcResult (added for <cfstoredproc>) carries resultsets, the statusCode and the outValues; DBConnection::storedProc() executes a CALL on MySQL/PostgreSQL (the base implementation throws for backends without stored procedures, e.g. SQLite).

Diagnostics: openConnection wraps every backend connection in a LoggingConnection (src/db/db.cpp) that dumps every operation on the abstract DB layer to stdout when webstrada::config::enableQueryLogging is on (the same flag that gates the [cfquery]/[cfstoredproc] lines in the CFML layer, and exposed as the admin "Query Logging" setting): [db] <backend> open dsn=.., [db] <backend> execute dsn=.. maxrows=.. + the SQL, begin/ commit/rollback, savepoint <name>/rollbackTo <name>, `tableColumns

`, `storedProc proc=.. params=..`, and `close` — logged *before* the call delegates, so even a statement that then fails is visible. When an operation **fails**, the wrapper additionally prints an `[db] ERROR dsn=..` block (`op`, `message`, `detail`) before rethrowing the original exception unchanged, so a request's DB errors show up in the terminal next to the statement that caused them (the dev web server http-dev.py tees the daemon's stdout to the console). All backends throw a `"Database"`-type `webstrada::exception` ("Error Executing Database Query." + the driver detail), which the JIT maps to a catchable CFML `database` exception — `catch(type="database")` matches and `cfcatch.type` is `"Database"`. The wrapper owns the inner connection and works for every backend uniformly; the CLI and unit-test binary keep the flag off because their stdout is a data channel. Covered by the `CfQueryTest.DbLayerDumpsOperationsToStdout` and `CfQueryTest.DbErrorLoggedToStdoutAndThrowsDatabase` unit tests.

Verified with DbLayerTest unit tests (directly against the abstract interface: select/insert/maxrows/ multi-statement/DDL/errors/transactions/savepoints/driver registry/stored procedures) + MySqlDbTest and PostgresDbTest unit tests (run against a live MySQL/MariaDB / PostgreSQL server configured via WSDATASOURCE_*; they skip when none is reachable) + the existing CfQueryTest/CfTransactionTest/ QueryMutationTest/QueryFunctionsTest suites and byte-for-byte against CF 2025 via the cfquery_*/cftransaction_*/ tier2_queryexecute/cfloop_query_* test files. The mysql_test.cfm, mysql_backend_test.cfm, postgres_test.cfm and cfstoredproc_test.cfm test files exercise the MySQL / PostgreSQL backends; mysql_backend_test.cfm and postgres_test.cfm are byte-verified against CF 2025 (see below) and the rest cannot be (the RDS host has no stored-procedure datasource).

Live MySQL verification (2026-08-10): a MySQL 26.7.0 server (mysql:latest Docker container on 192.168.100.10:3306) now hosts the webstrada database and a dedicated webstrada user (password webstrada_pw, granted all privileges on webstrada.*). The MySQL backend was exercised end-to-end against it: the full MySqlDbTest suite (16 tests — select/ persistence/transactions/generated-key/multi-statement/<cfdbinfo> tables, columns, index, foreignkeys, version, dbnames + <cfstoredproc> out-params, result-set binding, multiple result sets, DECIMAL scale preservation) passes, as do mysql_test.cfm and mysql_backend_test.cfm (types incl. DECIMAL/DATE/TEXT/NULL, GENERATEDKEY, cfqueryparam integer/varchar/decimal (with scale)/date, <cftransaction> commit+rollback, update/delete/maxrows/multi-statement first-result-surfaced, LIKE, <cfinsert>, COUNT) — run through both the CLI and the dev HTTP server. run-dev.sh registers the mysql/mysqltest/pgtest datasources (WSDATASOURCE_MYSQL* / WSDATASOURCE_PGTEST_*) and starts http-dev.py, so any served CFML page can use datasource="mysql" or datasource="pgtest". Note: MySQL 26.7 compares a DECIMAL(10,2) column against a bare integer literal with strict scale semantics (12.50 = 12 is FALSE, 12.50 = 12.50 is TRUE), so decimal cfqueryparam comparisons need a matching scale attribute — the engine formats them per the documented scale-default (0), matching CF's QueryParamTag default.

Live PostgreSQL verification (2026-08-10): a PostgreSQL 18.4 server (postgres:latest Docker container on 192.168.100.10:5433, port = original 5432 + 1) hosts the webstrada database with the dedicated webstrada user. The full PostgresDbTest suite (18 tests — select/persistence/transactions/ savepoints/affected-rows/INSERT RETURNING/multi-statement/type mappings/ table introspection + <cfdbinfo> tables/columns/index/foreignkeys/version/ procedures + <cfstoredproc> out/inout/null-param/result-set binding) passes, as does postgres_test.cfm.

Byte-verified against CF 2025 (2026-08-10): the MySQL datasources were also registered on the RDS host (Connector/J 8 com.mysql.cj.jdbc.Driver, jdbc:mysql://192.168.100.10:3306/webstrada?sslMode=DISABLED&allowPublicKeyRetrieval=true) so mysql_backend_test.cfm runs byte-for-byte on both engines (--exact, 🟢). The PostgreSQL datasource was likewise registered on the RDS host (org.postgresql.Driver, jdbc:postgresql://192.168.100.10:5433/webstrada), so postgres_test.cfm also byte-verifies (--exact, 🟢). Engine bugs surfaced by the byte-compares and fixed:

  • PG boolean cells are java.lang.Integer 1/0, not true/false: CF's PostgreSQL driver surfaces a bool column as 1/0 (SELECT true → 1, cell class java.lang.Integer, SerializeJSON 1), so db_pgsql.cpp maps bool cells to a Number 0/1 (the PK-introspection is_pk check was updated to read the Number). The old "Boolean true/false like the JDBC driver" mapping was wrong.
  • MySQL DECIMAL cells now stringify with their original scale like CF's java.math.BigDecimal: 12.50 not 12.5 (db_mysql.cpp keeps the raw server text as the Float's m_literalText; QueryNew/QuerySetCell DECIMAL/NUMERIC columns do the same in coerceQueryCell). Arithmetic still uses the underlying double (q.price + 1 → 13.5), matching CF.
  • Arithmetic on query-column references and numeric strings was broken engine-wide: q.price + 1 threw "cannot be converted to an integer" and "12.5" + 1 threw instead of 13.5. cfvariant_add/sub/mul/mod/neg now scalarize a query-column ref to its current cell (like compareVariants does) and coerce numeric strings to numbers like CF's Arith ("2" + 1 → 3, "12.5" + 1 → 13.5, "12.9" MOD 3 → 0, "abc" + 1 throws CF's "The value abc cannot be converted to a number."). Verified against CF 2025 on the RDS host; covered by JitExpressionTest.NumericStringArithmetic and JitExpressionTest.QueryColumnRefArithmetic + MySqlDbTest.Decimal*. The N multi-statement section of mysql_backend_test.cfm is the one section not byte-verified: Connector/J rejects a multi-statement script unless the datasource URL enables allowMultiQueries=true (a CF-side datasource setting, not an engine divergence — the engine's batch execution matches CF with that option on, as the SQLite/SQL Server drivers already verified).

Server configuration file + __ compiler extensions

The server configuration now lives in a JSON file (webstrada-config.json). The daemon resolves it at startup — WEBSTRADA_CONFIG env overrides, else next to the WebStrada binary (src/config.cpp), the same rule as the SQLite scope/cache databases — loads it into the config::* globals, and bootstraps it from the built-in defaults when it does not exist yet (so the file always reflects the effective configuration). config::save() persists the globals atomically (temp file + rename). Prefork workers re-read it per request via config::reloadIfChanged() (a single stat() when unchanged), so an admin update written by one worker becomes effective on the next request in every worker without a restart. enableWhitespaceManagement stays compile-time (affects newly compiled templates only). Wired in appserver::init (before the fork) + worker::process_request.

Compiler-extension functions (__ prefix, reserved like C's __ identifiers): engine-provided functions callable from CFML, compiled as direct JIT calls (cf___<name>(args, argc), uniform ABI), never runtime-dispatched and never shadowable by a UDF/variable. The codegen's generic fallback (codegen_expr.cpp) emits the direct call and rejects unregistered __name calls at compile time; the #...# interpreter (core_interp.cpp) dispatches through the same registry (src/cffunctions/fn_extensions.cpp) and raises a catchable runtime error for unregistered names. Registered:

  • __configGet() — struct of the effective config ({settings, datasources}) straight from the globals; datasource passwords masked as ****.
  • __configSet({settings?, datasources?}) — merge-updates the globals and persists via config::save(). Unknown setting keys / non-numeric numerics / unsupported backends / named arguments are rejected with clear errors; a change to lineExecutionTrace automatically invalidates and clears the in-memory TemplateCache and customTagTargetCache so all templates recompile immediately. password of **** keeps the existing secret; {action: "delete"} removes a datasource.
  • __configReset() — resets every config global to the built-in defaults and persists (the panel's "Restore Defaults").
  • __datasourceTest(name) — real connection attempt through the DB layer → {verified, error}.
  • __serverInfo(excludeAdmin) — dashboard runtime stats {state, version, uptimeSeconds, requestsServed, avgResponseMs, lineExecutionTrace, hideAdminRequests, traceSessionCount, recentRequests} tracked per worker process by webstrada::stats (src/server_stats.cpp; worker::process_request records request begin/end with the response status). An optional truthy excludeAdmin argument drops recent requests whose template path is an admin-panel request — /webstrada/..., the path the panel is served under (deployed at /app/webstrada in Docker and mirrored at APP_ROOT/webstrada by http-dev.py); is_admin_request_path matches it — the dashboard's "Hide admin requests" switch passes it via ?excludeAdmin=true so the filtering happens server-side.
  • __traceControl(action, value) — controls execution tracing session state. Action "start" clears the WebStrada-profiler.sqlite database and recent requests buffer, resets the session counter, and enables lineExecutionTrace. Action "stop" disables lineExecutionTrace. Action "set_hide_admin" dynamically updates the in-memory hide_admin_requests setting (default true, not persisted in webstrada-config.json). When hide_admin_requests is true, requests whose template path is an admin-panel path (/webstrada/... — is_admin_request_path) are skipped from SQLite tracing and do not increment the 100-request session limit.

SQLite Execution Profiler Store (WebStrada-profiler.sqlite). When lineExecutionTrace is enabled:

  • Each line/method transition (cf_stack_push, cf_stack_set_line, cf_stack_pop) appends a TraceStep into an in-memory thread-local buffer (g_requestTraceSteps) with zero I/O and sub-10ns overhead during template execution.
  • After the FastCGI response stream is completed and flushed to the client (FCGX_Finish_r), the worker persists the request metadata into the requests table and batch-inserts the entire step timeline into the request_traces table via a single SQLite transaction and prepared statement.

Admin panel API + live wiring. The panel now reads/writes the real server config. CFML endpoints under admin/api/ (config.cfm, datasources.cfm, serverinfo.cfm) wrap the extension functions over JSON; http-dev.py serves the built Angular app (admin/dist/webstrada-admin/browser, built with the relative baseHref: ./ from angular.json) at /webstrada/ with an SPA fallback and routes /webstrada/api/*.cfm to the engine. Both the Docker image and the dev server expose the panel under an APP_ROOT/webstrada layout (the image deploys it at /app/webstrada, the dev server symlinks the repo's admin/ tree into APP_ROOT/webstrada at startup), so the engine resolves DOCUMENT_ROOT + REQUEST_URI directly with no rewrite: the frontend reaches the API through relative URLs (./api/...) so it resolves against the serving base. The Angular app (AdminApiService + HttpClient) wires the Dashboard (live uptime/requests/avg/recent-request list, "Hide admin requests" switch that filters admin (/webstrada/...) rows server-side via ?excludeAdmin=), Settings (editable config with Save + Restore Defaults; Default Encoding / Default Template Charset are charset comboboxes, the charsetDetectionMinConfidence default is 80, the two timeouts are rendered as Days / Hours / Minutes spin boxes, and the Scopes & Storage section was removed from the UI — scope/cache/dsn paths are still in the config file but not exposed here) and Data Sources (live list with backend names shown with proper casing — SQLite/MySQL/PostgreSQL; Refresh Status, Verify All, per-row Verify, Add a new data source, Edit/update an existing one — the form loads the row and the name field is read-only while editing — and Delete. SQLite needs only a name: the server fields are hidden and the database file is DNS_<name>.sqlite in the datasource DB directory. Datasource names keep their entered casing: config::datasources is now a case-insensitive map (CiStrLess, like ColdFusion's case-insensitive datasource names) so appdb / AppDb / APPDB all resolve, while the panel shows the name as first entered) and Cache (renamed from "Cached Queries"; lists every live cache-store entry — region, key, hits, size, created, expiry — with aggregate stat cards (entries / total hits / cache memory / compiled templates / compiled components), a Clear All Caches button (__cacheClear), Refresh, per-row Evict (__cacheEvict) and click-to-sort column headers, all served by the admin/api/cache.cfm endpoint backed by __cacheInfo/__cacheEvict/__cacheClear. The two compiled counts come from webstrada::compiled_cache_counts (template_cache.cpp) — the JIT-compiled .cfm templates and .cfc components held in this worker's in-memory TemplateCache, separate from the SQLite store). Debugging & Logging, Memory & Runtime and Users & Roles still render placeholder data.

Covered by ConfigExtensionTest (17 unit tests: bootstrap/reload/persistence/ validation/password-sentinel/delete/datasource-test/server-info/config-reset/ JIT+interpreter dispatch) and tests/cfm/admin_extension_test.cfm (CLI regression; not byte-verifiable against CF — these are engine extensions, running them on CF gives "Variable __CONFIGGET is undefined").

http-dev.py web root + Docker. The dev server's served web root is now configurable via --webroot DIR or the WEBROOT env var (default: the directory containing the script), so a mounted host volume can be served at / while the binaries, socket/logs and admin panel stay in the app root. The admin SPA (ADMIN_DIST) and its /webstrada/api/*.cfm endpoints always resolve against APP_ROOT, independent of the web root (the CFML API gets DOCUMENT_ROOT=APP_ROOT). At startup ensure_admin_mirror() exposes the repo's admin/ tree under APP_ROOT/webstrada (symlinking webstrada/api and webstrada/dist/webstrada-admin/browser to the corresponding admin/ dirs), so the dev server serves and the engine resolves the admin panel with the same /webstrada/... paths as production — no URL rewriting. Directory requests now correctly dispatch index.cfm through the engine (the extension was previously computed before index resolution, so / on a dir with only index.cfm was served as static). The Docker image (build_docker.sh) builds the admin panel in a node:22-alpine stage (npm ci && npm run build; the relative baseHref: ./ from angular.json resolves correctly under /webstrada/) and bundles it at /app/webstrada/dist/webstrada-admin/browser plus admin/api at /app/webstrada/api — the URL-matching layout means nginx hands /webstrada/api/*.cfm to the engine unmodified (SCRIPT_FILENAME /app$fastcgi_script_name, DOCUMENT_ROOT /app), so webstrada:latest serves /webstrada/ out of the box. build_docker.sh default textparser bumped 1.0.7 → 1.0.8 (adds textparser_set_filename, required by parser.cpp/template_reader.cpp); Covered by tests/test_http_dev.py (11 routing tests: static/CFML/index resolution/ webroot DOCUMENT_ROOT/admin SPA/admin API/admin+webroot traversal/POST, plus ensure_admin_mirror() binding).

Detailed Parse Error Diagnostics & Multi-line Operators. On parsing failures in parser::parse (parser.cpp), the exception details now include the target file's full pathname, line number, column number (derived from the textparser line map), and the parser error message (via textparser_parse_error). Set "multiLine": true on Operator in definitions/cfml_definition.json (and regenerated cfml_definition.json.h) so expressions with assignment/operators spanning multiple lines tokenize properly. Covered by ParserErrorDetailsTest.DetailedParseErrorInfo unit test.

Strict Comparison Operators === / !==. The identity-comparison operators === (strict equal) and !== (strict not-equal) are now implemented, mirroring CF 2025's CfJspPage._strictCompare. Unlike ==/!= which coerce across types, === returns true only when both operands share the same type category and are equal within that category:

  • Both Null/NotSet → strictly equal.
  • Both Boolean → compare true/false directly.
  • Both numeric (Number, Long, Float, DateTime) → compare as doubles (so 1.0 === 1 is true).
  • Both String → case-insensitive string compare (same as ==).
  • Different type categories (e.g. "1" === 1, true === 1, true === "true") → always false.

Precedence is at the same level as ==/!= (level 8). Grammar already included the tokens; only the runtime (compareVariants in core_interp.cpp) and the operator precedence table (codegen_expr.cpp) required updating. Unit tests in tests/cfm/strict_compare1.cfm.

CFML Functions

Function Return Type Arguments Implemented Notes
Abs numeric number: numeric ✅ Yes
ACos float number: numeric ✅ Yes
AddSOAPRequestHeader ❌ No
AddSOAPResponseHeader ❌ No
AjaxLink string url: string ✅ Yes Returns the URL unchanged (no Ajax container exists in this engine, matching CF's behavior when HtmlAssembler.getContainerId() is null). Verified against CF 2025 in tests/cfm/tier2_ajax_test.cfm.
AjaxOnLoad void functionName: string ✅ Yes Emits an inline <script> (ColdFusion.Event.registerOnLoad(...)) into the response head, which precedes the body output like CF. Verified against CF 2025 in tests/cfm/tier2_ajax_test.cfm.
ApplicationStop void () ✅ Yes Deletes the current application and all of its sessions from the scope store. The current request keeps its application/session data (verified on CF: StructKeyExists(application, ...) stays YES), but persistence stops, so subsequent requests start fresh. Verified in tests/cfm/cfapplication_stop_test.cfm + WorkerTest.ScopeApplicationStopClearsAppAndSessions.
ArrayAppend boolean array: array, value: any ✅ Yes Mutates array in-place
ArrayAvg numeric array: array ✅ Yes Returns average of array elements
ArrayClear boolean array: array ✅ Yes Clears array in-place
ArrayContains boolean array: array, value: any ✅ Yes Case-sensitive search
ArrayContainsNoCase boolean array: array, value: any ✅ Yes Case-insensitive search
ArrayDelete boolean array: array, value: any ✅ Yes Deletes first occurrence in-place
ArrayDeleteAt boolean array: array, index: numeric ✅ Yes 1-based index
ArrayDeleteNoCase boolean array: array, value: any ✅ Yes Deletes first occurrence case-insensitively in-place
ArrayEach void array: array, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Invokes callback(item, index, array) for each element (index 1-based); returns null. parallel/maxThreads accepted and ignored. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayEachFilterReduce.
ArrayFilter array array: array, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Returns a new array with the elements for which the callback returns true (callback args item, index, array). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayEachFilterReduce.
ArrayFind numeric array: array, value: any ✅ Yes Returns first matching 1-based index
ArrayFindAll array array: array, value: any ✅ Yes Returns all matching 1-based indices
ArrayFindAllNoCase array array: array, value: any ✅ Yes Case-insensitive search for indices
ArrayFindNoCase numeric array: array, value: any ✅ Yes Returns first case-insensitive matching 1-based index
ArrayInsertAt boolean array: array, index: numeric, value: any ✅ Yes 1-based index
ArrayIsDefined boolean array: array, index: numeric ✅ Yes Checks if index is defined
ArrayIsEmpty boolean array: array ✅ Yes
ArrayLen numeric array: array ✅ Yes
ArrayFirst any array: array ✅ Yes Returns the first element; empty array throws type java.lang.RuntimeException "Array is empty.Cannot return first element of array." (was BUGS.md #4). Member method arr.first(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm
ArrayLast any array: array ✅ Yes Returns the last element; empty array throws type java.lang.RuntimeException "Array is empty.Cannot return last element of array." (was BUGS.md #4). Member method arr.last(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm
ArrayMap array array: array, callback: string ✅ Yes Returns new array via named callback (ArrayMap(arr, "Abs")) or a closure (ArrayMap(arr, function(x){return x*2;})); source array untouched; callbacks invoked via evaluateExpr/direct UDF invocation (verified in tests/cfm/array2.cfm + ArrayMapNamedCallback/UdfTest.ClosureInExpression unit tests)
ArrayMax numeric array: array ✅ Yes Returns maximum of array elements
ArrayMin numeric array: array ✅ Yes Returns minimum of array elements
ArrayNew array () ✅ Yes 0 args
ArrayPop any array: array ✅ Yes Removes and returns the last element; empty array throws type Expression "Empty Array." (was BUGS.md #4). Member method arr.pop(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm
ArrayShift any array: array ✅ Yes Removes and returns the first element; empty array throws type Expression "Empty Array." (was BUGS.md #4). Member method arr.shift(). Byte-verified against CF 2025 in tests/cfm/array_first_last_pop_shift_test.cfm
ArrayPrepend boolean array: array, value: any ✅ Yes Mutates array in-place
ArrayReduce any array: array, callback: function [, initialValue: any] ✅ Yes Reduces the array to a single value: callback(acc, item, index, array) returns the next accumulator, starting from initialValue (without it the accumulator starts null). An empty array returns the initial value unchanged. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ArrayReduce*.
ArrayResize boolean array: array, size: numeric ✅ Yes
ArraySet boolean array: array, start: numeric, end: numeric, value: any ✅ Yes 1-based indices
ArraySetMetadata boolean array: array, metadata: struct ✅ Yes Sets array metadata (returns true)
ArraySlice array array: array, offset: numeric, length: numeric ✅ Yes Returns sub-array
ArraySort boolean array: array, sortType: string, sortOrder: string ✅ Yes Sorts array in-place (supports callbacks)
ArraySum numeric array: array ✅ Yes Returns sum of array elements
ArraySwap boolean array: array, idx1: numeric, idx2: numeric ✅ Yes Swaps elements in-place
ArrayToList string array: array, delimiter: string = "," ✅ Yes
Asc numeric string: string ✅ Yes Returns ASCII value of the first character
ASin float number: numeric ✅ Yes
Atn float number: numeric ✅ Yes Implemented as Atan
AuthenticatedContext ❌ No
AuthenticatedUser ❌ No
BinaryDecode binary string: string, encoding: string ✅ Yes Encodings: hex, UU, base64, base64URL
BinaryEncode string binaryData: binary, encoding: string ✅ Yes Encodings: hex (uppercase), UU, base64, base64URL; UU uses CF short-form with space padding
BitAnd numeric number1: numeric, number2: numeric ✅ Yes Bitwise AND
BitMaskClear numeric number: numeric, start: numeric, length: numeric ✅ Yes Bitwise mask clear
BitMaskRead numeric number: numeric, start: numeric, length: numeric ✅ Yes Bitwise mask read
BitMaskSet numeric number: numeric, mask: numeric, start: numeric, length: numeric ✅ Yes Bitwise mask set
BitNot numeric number: numeric ✅ Yes Bitwise NOT
BitOr numeric number1: numeric, number2: numeric ✅ Yes Bitwise OR
BitSHLN numeric number: numeric, count: numeric ✅ Yes Bitwise shift-left
BitSHRN numeric number: numeric, count: numeric ✅ Yes Bitwise shift-right
BitXor numeric number1: numeric, number2: numeric ✅ Yes Bitwise XOR
BooleanFormat string value: any ✅ Yes Renders the boolean truthiness of a value as true/false (literal flavor preserved: BooleanFormat(1) → true)
CacheGet any id: string [, region: string] ✅ Yes Reads an object from the sqlite-backed cache store (src/cache_store.cpp + src/cffunctions/fn_cache.cpp). Default region OBJECT; custom regions used as-is. A missing/expired id returns empty. Values are stored as SerializeJSON blobs (queries cached by <cfquery cachedwithin> use a type-preserving format); simple values/structs/arrays round-trip. Not yet byte-verified against CF 2025 (the CF caching package is installed on the RDS host but the Cache* functions still diverge — see BUGS_CF.md); covered by CacheTest + tests/cfm/tier2_cache_test.cfm.
CacheGetAllIds array [region: string] [, includeExpired: boolean] ✅ Yes Returns the ids in a region (default OBJECT), in insertion order. See CacheGet for verification notes.
CacheGetMetadata struct id: string [, objectType: string] [, region: string] ✅ Yes Returns NAME/TIMESPAN/IDLETIME/HITCOUNT/CREATEDTIME/LASTHIT/LASTUPDATED/SIZE for a live entry (empty struct for a missing id). TIMESPAN/IDLETIME are resolved seconds (default 1 day); the time fields are CF DateTimes from the stored epoch ms. See CacheGet for verification notes.
CacheGetProperties struct [region: string] ✅ Yes Returns the named region's properties struct (merged via cacheSetProperties, NAME always present). See CacheGet for verification notes.
CacheGetSession any objectType: string [, isKey: boolean] ✅ Yes Throws Function CacheGetSession is not supported: it returns a Java cache object. — CF returns the underlying Java cache handle, which this engine cannot replicate (same rule as GetPageContext; see BUGS.md).
CacheIdExists boolean id: string [, region: string] ✅ Yes YES/NO for a live (unexpired) entry. See CacheGet for verification notes.
CachePut void id: string, value: any [, timespan] [, idleTime] [, region] [, throwOnError] ✅ Yes Stores an object. timespan/idleTime are decimal days → seconds; omitted → the 1-day default (matching the decompiled GenericEhcache.put); both 0 → eternal. A missing custom region with throwOnError throws. See CacheGet for verification notes.
CacheRegionExists boolean region: string ✅ Yes YES for the standard OBJECT/TEMPLATE/QUERY regions and any created region. See CacheGet for verification notes.
CacheRegionNew void region: string [, properties] [, throwOnError] ✅ Yes Creates a region; throwOnError (default true) throws when it already exists. See CacheGet for verification notes.
CacheRegionRemove void region: string ✅ Yes Removes a custom region and its entries; the standard regions throw. See CacheGet for verification notes.
CacheRemove void id: any [, throwOnError] [, region] [, exact] ✅ Yes Removes ids from a region; id may be a comma-delimited list or an array, and exact="false" matches * wildcards. See CacheGet for verification notes.
CacheRemoveAll void [region: string] ✅ Yes Clears a region's entries (the region itself stays). See CacheGet for verification notes.
CacheSetProperties void properties: struct [, region: string] ✅ Yes Merges a struct into the region's stored properties (read back by cacheGetProperties). See CacheGet for verification notes.
CallStackDump void [output: string = "browser"] ✅ Yes Outputs a text representation of the live CFML call stack (from cfml::g_callStack): one line per frame, innermost first — <template>:<FUNCTION>:<line> for a function/method frame (uppercased name), <template>:<line> for a plain page frame, each newline-terminated. With no argument / "browser" it appends to the page output; a path argument appends to that file (CF 2025 appends, it does not truncate — verified). Byte-verified against CF 2025 in tests/cfm/callstackdump_test.cfm + StackTraceTest.CallStackDump* unit tests.
CallStackGet any () ✅ Yes Returns the live CFML call stack as an array of structs, element [1] = innermost frame (where the call was made), each with the keys Template (full pathname), LineNumber and Function (uppercased function/method name, empty for a plain page / component construction body) — CF 2025's exact key set and casing. Frames come from the stacktrace feature (cfml::g_callStack); anonymous closures report CF's _CF_ANONYMOUSCLOSURE_<n> shape. Byte-verified against CF 2025 in tests/cfm/callstackget_test.cfm + callstack_component_test.cfm + StackTraceTest.CallStackGet* unit tests.
CanDeSerialize boolean () ✅ Yes Returns YES
Canonicalize string string: string [, restrictMultiple: boolean = false] [, restrictMixed: boolean = false] [, throwOnError: boolean = false] ✅ Yes Canonicalizes by decoding HTML, percent and JavaScript encodings (ESAPI Canonicalizer loop order html→percent→js); restrictions throw CF's IntrusionException. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm + JitExpressionTest.Tier2DecodeForHtmlAndCanonicalize.
CanSerialize boolean () ✅ Yes Returns YES
Ceiling numeric number: numeric ✅ Yes
CharsetDecode binary string: string, encoding: string ✅ Yes Converts a string to binary; UTF-8/ISO-8859-1/US-ASCII/UTF-16 (BOM)/UTF-16LE/BE; unmappable chars become ?; Java-style _ aliases accepted
CharsetEncode string binaryData: binary, encoding: string ✅ Yes Converts binary to a string; invalid UTF-8 and unmappable US-ASCII bytes decode to U+FFFD; UTF-16 BOM detection
Chr string number: numeric ✅ Yes Converts a numeric value to a UCS-2 character
CJustify string string: string, length: numeric ✅ Yes Centers a string in a given length
Compare numeric string1: string, string2: string ✅ Yes Returns -1, 0, or 1
CompareNoCase numeric string1: string, string2: string ✅ Yes Case-insensitive
Cos float number: numeric ✅ Yes
CreateDate DateTime year: int, month: int, day: int ✅ Yes
CreateDateTime DateTime year: int, month: int, day: int, hour: int, minute: int, second: int ✅ Yes
CreateEncryptedJWT ❌ No
CreateObject object type: string, name: string ✅ Yes CreateObject("component", "path") instantiates a CFC (dot paths map to directories, relative to the current template, .cfc appended); other types throw. new path(args) in cfscript does the same and auto-calls init(args) (CF behavior). Verified against CF 2025 in tests/cfm/component_*_test.cfm.
CreateODBCDate date date: date ✅ Yes Wraps the input as a DateTime that renders CF's ODBC date form {d 'yyyy-mm-dd'} (Tier 1). Invalid input throws the same two messages as CreateODBCDateTime (non-date string → The value X cannot be converted to a date., out-of-range date shape → X is an invalid date or time string.). Verified against CF 2025 in tests/cfm/tier1_odbc_test.cfm + JitExpressionTest.Tier1CreateOdbcDateAndTime.
CreateODBCDateTime date date: date ✅ Yes Wraps the input (date/string/serial) as a DateTime; renders {ts 'yyyy-mm-dd hh:nn:ss'}, usable in date functions and comparisons (byte-matched against CF 2025); invalid input throws CF's two messages (was BUGS.md #34): a non-date string → The value X cannot be converted to a date., a date-shaped string with an out-of-range component (2026.13.05 10:20, 2026-13-05, 2026.05.32, 2026.05.21 25:00) → X is an invalid date or time string. — the looksLikeDateString helper detects the date shape (verified byte-for-byte in tests/cfm/createodbcdatetime_error_message_test.cfm + JitExpressionTest.CreateOdbcDateTimeInvalidMessage). The string date parser now also accepts dot-separated dates exactly like CF (YYYY.MM.DD, MM.DD.YYYY, DD.MM.YYYY, preferring MM.DD.YYYY when ambiguous, e.g. 2026.05.21 18:22 and 05.06.2026 → May 6), with HH:MM or HH:MM:SS times — this is what makes the CFKillBoard killmail date (2026.05.21 18:22) parse (tests/cfm/createodbcdatetime_dotformats_test.cfm + JitExpressionTest.DateDotSeparatedFormats).
CreateODBCTime date date: date ✅ Yes Wraps the input as a DateTime that renders CF's ODBC time form {t 'hh:nn:ss'} (Tier 1). Invalid input throws the same two messages as CreateODBCDateTime. Verified against CF 2025 in tests/cfm/tier1_odbc_test.cfm + JitExpressionTest.Tier1CreateOdbcDateAndTime.
CreateSignedJWT ❌ No
CreateTime DateTime hour: int, minute: int, second: int ✅ Yes
CreateTimeSpan DateTime days: numeric, hours: numeric, minutes: numeric, seconds: numeric ✅ Yes
CreateUUID string () ✅ Yes Returns CF's 8-4-4-16 shape (35 chars: 32 hex + 3 dashes). CreateGUID was removed in CF 2025 (verified on the RDS host). The C rand() generator is now seeded at every process entry point (daemon, forked worker, CLI, unit tests) via cfml::seed_rand(), so a fresh process mints a distinct UUID per call (was BUGS.md "CreateUUID() returns the same value on every run of a fresh process"); covered by JitExpressionTest.CreateUuidIsUniquePerCall.
CSRFGenerateToken string key: string ✅ Yes Returns a 40-character uppercase-hex CSRF token (20 random bytes) stored in the SESSION scope under __CF_CSRF_TOKENS__ keyed by the app name. Verified against CF 2025 in tests/cfm/tier2_csrf_test.cfm.
CSRFVerifyToken boolean key: string [, token: string] ✅ Yes Constant-time comparison of the supplied token against the stored one (first 40 chars). Verified against CF 2025 in tests/cfm/tier2_csrf_test.cfm.
CSVProcess void path: string, callback: function ✅ Yes RFC-4180 reader that invokes the callback for every row (including the header row). RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp.
CSVRead query path: string [, firstRowHeader: boolean = true] ✅ Yes RFC-4180 CSV parser returning a query; column names from the first row (or columnN). RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp.
CSVWrite void path: string, data: query ✅ Yes RFC-4180 writer with quoting/escaping. RDS host lacks the spreadsheet service (see BUGS_CF.md); implemented in fn_csv.cpp.
DateAdd DateTime datepart: string, number: numeric, date: any ✅ Yes
DateCompare int date1: any, date2: any [, datepart: string] ✅ Yes
DateConvert DateTime type: string, date: any ✅ Yes
DateDiff numeric datepart: string, date1: any, date2: any ✅ Yes
DateFormat string date: any [, mask: string] ✅ Yes
DatePart numeric datepart: string, date: any ✅ Yes
DateTimeFormat string date: any [, mask: string] ✅ Yes
Day int date: any ✅ Yes
DayOfWeek int date: any ✅ Yes
DayOfWeekAsString string day_of_week: int [, locale: string] ✅ Yes
DayOfYear int date: any ✅ Yes
DaysInMonth int date: any ✅ Yes
DaysInYear int date: any ✅ Yes
DE string string: string ✅ Yes Delay evaluation of string (escapes quotes and wraps in quotes)
DecimalFormat string number: numeric ✅ Yes
DecodeForHTML string string: string ✅ Yes Decodes HTML/XML entities back to characters. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
DecodeFromURL string string: string ✅ Yes ESAPI inverse of EncodeForURL; '+' becomes space; %XX (case-insensitive) decoded as UTF-8; malformed sequences throw
DecrementValue numeric number: numeric ✅ Yes Returns number - 1
Decrypt string string: string, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric ✅ Yes Key is base64-decoded; AES/DES/DESEDE/BLOWFISH with mode/padding strings; CFMX_COMPAT throws the Security Provider error (matches server); wrong key throws bad-padding error
DecryptBinary binary binaryData: any, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric ✅ Yes Returns binary; key base64-decoded; CFMX_COMPAT throws (matches server)
DeleteClientVariable boolean name: string ✅ Yes Deletes a client variable. The client scope is not implemented; on the RDS host client storage is enabled and CF's implementation returns true for any name once the client scope exists (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm). See BUGS_CF.md.
Deserialize any string: string, [type: string] ✅ Yes Wrapper, delegates to DeserializeJSON for JSON
DeserializeJSON any string: string, [strictMapping: boolean = true] ✅ Yes Uses json-c; struct keys keep their original casing and lookups are case-insensitive; int64 numbers beyond int32 deserialize to the exact Long type (DeserializeJSON("2147483648") → 2147483648, DeserializeJSON("9007199254740993") → exact; previously truncated to int32)
DeserializeXML xml xmlString: string [, caseSensitive: boolean [, validator: string]] ✅ Yes Alias for XmlParse
DirectoryCopy boolean source: string, destination: string ✅ Yes Copies directory recursively
DirectoryCreate boolean path: string ✅ Yes Creates parents
DirectoryDelete boolean path: string, recurse: boolean = false ✅ Yes
DirectoryExists boolean path: string ✅ Yes
DirectoryList array path: string ✅ Yes Returns array of file paths
DirectoryRename boolean source: string, destination: string ✅ Yes
DollarFormat string number: numeric ✅ Yes
DotNetToCFType ❌ No
Duplicate any value: any ✅ Yes Recursively independent copy of structs/arrays/queries (CF reference semantics). Verified against CF 2025 in tests/cfm/tier2_duplicate_test.cfm.
EncodeForCSS string string: string ✅ Yes ESAPI CSS encoder (hex escapes + rgb() triplet preservation). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForDN string string: string ✅ Yes ESAPI DN encoder (escapes < > and leading/trailing space). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForHTML string string: string ✅ Yes ESAPI HTML encoder (named entities). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForHTMLAttribute string string: string ✅ Yes ESAPI HTML-attribute encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForJavaScript string string: string ✅ Yes ESAPI JavaScript encoder (\xHH escapes). Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForLDAP string string: string ✅ Yes ESAPI LDAP encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForURL string string: string [, canonicalize: boolean = false] ✅ Yes ESAPI URL encode; alphanumerics and -_. stay literal, space becomes '+', UTF-8 percent-encoding; canonicalize=true decodes %XX sequences first
EncodeForXML string string: string ✅ Yes ESAPI XML encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForXMLAttribute string string: string ✅ Yes ESAPI XML-attribute encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
EncodeForXpath string string: string ✅ Yes ESAPI XPath encoder. Verified against CF 2025 in tests/cfm/tier2_encoder_test.cfm.
Encrypt string string: string, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric ✅ Yes Key is base64-decoded; default ECB/PKCS5; UU/Base64/Hex encodings; CBC auto-generates a random prepended IV; CFMX_COMPAT throws the Security Provider error (matches server)
EncryptBinary binary binaryData: any, key: string, algorithm: string = "CFMX_COMPAT", encoding: string = "UU", IVorSalt: binary, iterations: numeric ✅ Yes Returns binary; CFMX_COMPAT throws (matches server)
EntityDelete ❌ No
EntityLoad ❌ No
EntityLoadByExample ❌ No
EntityLoadByPK ❌ No
EntityMerge ❌ No
EntityNew ❌ No
EntityReload ❌ No
EntitySave ❌ No
EntityToQuery ❌ No
Evaluate any expression: string, ... ✅ Yes Evaluates one or more string expressions dynamically left-to-right, returns the rightmost result; matches evaluating the same expression directly (byte-matched against CF 2025)
Exp float number: numeric ✅ Yes e^number
ExpandPath string relativePath: string ✅ Yes Resolves relative path
FileClose boolean fileObj: any ✅ Yes Closes an open file
FileCopy boolean source: string, destination: string ✅ Yes
FileDelete boolean path: string ✅ Yes
FileExists boolean path: string ✅ Yes
FileGetMimeType string path: string ✅ Yes Extension-based detection
FileIsEOF boolean fileObj: any ✅ Yes
FileMove boolean source: string, destination: string ✅ Yes
FileOpen any path: string, mode: string ✅ Yes Returns file object with fd
FileRead string path: string ✅ Yes
FileReadBinary any path: any ✅ Yes Path string or file object
FileReadLine string fileObj: any ✅ Yes Reads line from file object
FileSeek boolean fileObj: any, position: numeric ✅ Yes Seeks to byte position
FileSetAccessMode boolean path: string, mode: string ✅ Yes Calls chmod
FileSetAttribute boolean path: string, attr: string ✅ Yes No-op on Linux
FileSetLastModified boolean path: string, date: any ✅ Yes Sets mtime via utimensat
FileSkipBytes boolean fileObj: any, count: numeric ✅ Yes Seeks forward by count bytes
FileUpload struct destination: string, fileField: string, mimeType: string = "", onConflict: string = "error", strict: boolean = true ✅ Yes Saves uploaded multipart file; onConflict: error/skip/overwrite/makeunique; non-existent destination becomes a file, per CF
FileUploadAll array destination: string, mimeType: string = "", onConflict: string = "error", strict: boolean = true, continueOnError: boolean = false, errorVariable: string = "cffile.uploadAllErrors", allowedExtensions: string = "" ✅ Yes Saves all multipart files; with continueOnError, failures go to the errorVariable
FileWrite boolean path: string, content: string ✅ Yes Appends a trailing newline for string content like CF (FileWrite(path,"aaaa") → 5-byte file; was BUGS.md "FileWrite() does not append a newline"), matching the <cffile action="write" addnewline="yes"> default; binary content is written verbatim. Verified against CF 2025 in tests/cfm/filewrite_newline_test.cfm + JitExpressionTest.FileWriteAppendsNewline.
FileWriteLine boolean fileObj: any, content: string ✅ Yes Appends \n to content
Find numeric substring: string, string: string, start: numeric = 1 ✅ Yes 1-based, 0 if not found
FindNoCase numeric substring: string, string: string, start: numeric = 1 ✅ Yes Case-insensitive
FindOneOf numeric set: string, string: string [, start: numeric = 1] ✅ Yes 1-based, 0 if not found
FirstDayOfMonth int date: any ✅ Yes
Fix numeric number: numeric ✅ Yes Truncates double toward zero
Floor numeric number: numeric ✅ Yes
FormatBaseN string number: numeric, radix: numeric ✅ Yes 32-bit semantics (verified vs CF 2025): radix 2 and 16 use two's-complement (lowercase) for negatives, all other radixes signed; number is truncated to low 32 bits for values in [-2147483648, 4294967295], outside that range throws
Generate3DesKey string seed: string ✅ Yes Returns base64 of the seed (server behavior); 0 args throws
GeneratePBKDFKey string algorithm: string, passphrase: string, salt: string, iterations: numeric, keySize: numeric ✅ Yes PBKDF2WithHmacSHA1/224/256/384/512; returns base64 of derived key
GenerateSAMLSPMetadata ❌ No
GenerateSecretKey string algorithm: string, keySize: numeric = 256 ✅ Yes AES default 256-bit; DES 8, DESEDE 112/168, BLOWFISH 32-448 bits; returns base64
GetApplicationMetadata struct () ✅ Yes Returns the current application's settings: NAME, APPLICATIONTIMEOUT / SESSIONTIMEOUT (whole seconds, e.g. 93784 for CreateTimeSpan(1,2,3,4)), SESSIONMANAGEMENT, CLIENTMANAGEMENT, SETCLIENTCOOKIES, SETDOMAINCOOKIES, LOGINSTORAGE. This is a subset of CF's large struct; the rest of CF's keys (RESTSETTINGS, MAPPINGS, DATASOURCES, ORMSETTINGS, ...) are not populated. When no application is active (no <cfapplication>/Application.cfc ran) it returns an EMPTY struct like CF (was BUGS.md #3: previously the 8 modeled keys were returned unconditionally) — verified against CF 2025 in tests/cfm/getappmetadata_empty_test.cfm. Verified for the covered keys against CF 2025 in tests/cfm/cfapplication_scope_test.cfm.
GetAuthUser string () ✅ Yes The name of the currently authenticated user (cflogin/cfloginuser), or an empty string when nobody is logged in (CF's UserUtils.getAuthUser). Reads the request's security context installed by the login tags; the raw OS/basic-auth remote user is not modeled. Verified against CF 2025 in tests/cfm/cflogin_basic_test.cfm + LoginTagTest unit tests.
GetBaseTagData struct (tagName [, instancenumber]) ✅ Yes Returns the named ancestor custom tag's page scope struct. CF's PageScope resolves names directly against the base tag's page context, so every variable the tag template set is addressable as data.<var> (e.g. data.currentPage) and the scopes are exposed as THISTAG / ATTRIBUTES / CALLER / VARIABLES keys; the struct mirrors this by merging the tag's variables at the top level beneath the four scope keys. Matching uses CF's _templateName (case-insensitive): cf_<name> for imported/prefixed tags, CF_<name> for <cfmodule> tags; instancenumber selects the Nth matching ancestor. Missing tag → Tag <name> was not found in ancestor custom tags. The variables-at-top-level behavior fixed the Mango Blog "Element CURRENTPAGE is undefined in DATA." error in tags/mango/Page.cfm (GetBaseTagData("cf_pages").currentPage), and the paired-tag prefix-boundary fix (see the Custom tags section) fixed the Mango Blog "Tag cf_page was not found in ancestor custom tags." error in tags/mango/PageProperty.cfm (GetBaseTagData("cf_page",1)). Verified byte-for-byte against CF 2025 in tests/cfm/custom_tag_getbasetagdata_test.cfm + custom_tag_pair_prefix_collision_test.cfm + custom_tag_associate_test.cfm + ComponentTest.GetBaseTagDataResolvesTagVariablesAtTopLevel + ComponentTest.GetBaseTagDataScopesStillExposed + ComponentTest.CustomTagPairPrefixNameBoundary + ComponentTest.CfassociateCollectsSubtagAttributes.
GetBaseTagList string () ✅ Yes Comma-delimited list of the currently-executing base tags from innermost to outermost. The innermost entry is CFOUTPUT only while a <cfoutput> block is executing (a <cfoutput> is CF's innermost base tag); outside one the innermost entry is the current custom tag. Custom tags contribute their public name (CF_<NAME> for imported/prefixed tags, CF_<filename> for <cfmodule> tags), innermost first. Was a bug: the CFOUTPUT prefix was prepended unconditionally, so listdeleteat(getbasetaglist(),1) (Mango Blog's Posts.cfm context detection) removed the wrong entry and Variable POSTS is undefined was thrown. Now the runtime tracks a base-tag stack (g_baseTagStack) with a cf_cfoutput_begin/cf_cfoutput_end marker per <cfoutput> block. Verified byte-for-byte against CF 2025 in tests/cfm/getbasetaglist_{current,ctx,branch2,parent_cfoutput,root}_test.cfm + custom_tag_basetaglist_test.cfm + custom_tag_nested_test.cfm + ComponentTest.GetBaseTagListIncludesCfdoutputOnlyInsideCfdoutput + ComponentTest.GetBaseTagListParentCfdoutputOrdering.
GetBaseTemplatePath string () ✅ Yes
GetClientVariablesList string () ✅ Yes Returns the comma-delimited writable client variables. The client scope is not implemented, so it returns the empty list like CF's empty client scope (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SimpleFunctions).
GetComponentMetaData struct obj: component ✅ Yes Returns the component's metadata struct (name/path/type/fullname/functions/parameters/properties/extends). Note: the CF 2025 test server throws on GetComponentMetaData(o) ("Complex object types cannot be converted to simple values"), so this is not byte-verified (see BUGS_CF.md).
GetContextRoot string () ✅ Yes Returns the servlet context path; a default CF installation (and the RDS host) reports the empty string (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm).
GetCPUUsage numeric [interval: numeric = 1000] ✅ Yes Returns the CPU usage percentage sampled from /proc/stat over the interval. The RDS host lacks pmtagent (see BUGS_CF.md); covered by JitExpressionTest.Tier2GetCPUUsage-style checks.
GetCSPNonce string () ✅ Yes Returns the request's Content-Security-Policy nonce; like CF when no CSP is configured, it returns the empty string (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SimpleFunctions).
GetCurrentTemplatePath string () ✅ Yes
GetDirectoryFromPath string path: string ✅ Yes
GetEncoding string scope: string ✅ Yes Returns the character set of the FORM or URL scope ("UTF-8" default, or as set by SetEncoding); any other scope throws CF's "Only form or URL scope is allowed in a SetEncoding() call." (verified against CF 2025 in tests/cfm/tier1_getencoding_test.cfm + tier1_setencoding_test.cfm).
GetException any object: any ✅ Yes Returns the current caught exception; CF only ever returns Java exceptions from its ExceptionCache, so a CFML argument yields empty/null. Verified against CF 2025 in tests/cfm/tier2_getexception_test.cfm.
GetFileFromPath string path: string ✅ Yes
GetFileInfo struct path: string ✅ Yes Keys: exists, size, path, type
GetFreeSpace numeric path: string ✅ Yes Returns the free bytes on the filesystem via statvfs (Java File.getFreeSpace() semantics). CF validates the path shape (length > 1 and starting with /, a drive letter at position 1, or \\); a bare / throws CF's "The value of the path parameter "/" passed to GETFREESPACE function is invalid." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1DiskSpace).
GetFunctionCalledName string () ✅ Yes Returns the name of the currently executing function (the enclosing cffunction or component method) as called at the call site (preserving casing and alias variable names). Returns the empty string "" when called at page level outside any function. Verified against CF 2021/2025 in tests/cfm/getfunctioncalledname.cfm + GetFunctionCalledNameTest unit tests.
GetFunctionList struct () ✅ Yes Returns a struct of the built-in function names mapped to empty strings, exactly like CF's map built from its Java method names (values verified empty against CF 2025). The engine enumerates its own registry (kBuiltinFunctionNames, 657 names), so the exact key set/casing differs from CF's 800-entry list (see BUGS_COSMETIC.md). Membership checks that agree with CF are byte-verified in tests/cfm/tier1_getfunctionlist_test.cfm.
GetGatewayHelper ❌ No
GetHttpRequestData struct [includeBody: boolean] ✅ Yes Returns a struct with the HTTP request data: headers (a struct of the request headers, lowercased names — from the CGI scope's HTTP_* vars; empty when there is no CGI, e.g. the CLI), protocol (same as cgi.server_protocol), method (same as cgi.request_method) and content (the raw request body captured by the daemon worker; the CLI has none). The includeBody argument (default true) controls whether the content key is present at all — GetHttpRequestData(false) omits it entirely, verified on CF 2025. Key insertion order matches CF (headers,protocol,method,content). Verified in tests/cfm/gethttprequestdata_test.cfm + JitExpressionTest.GetHttpRequestData unit tests.
GetHttpTimeString string [date: any] ✅ Yes Formats the date as an RFC 1123 GMT string. CF interprets the date value as the server's local wall clock and renders the resulting instant in GMT: the local offset (DST-aware, resolved via mktime with tm_isdst=-1) is applied, so GetHttpTimeString(CreateDate(2026,6,1)) on a UTC+2 host yields Sun, 31 May 2026 22:00:00 GMT (previously the date was treated as already-UTC → Mon, 01 Jun 2026 00:00:00 GMT). No argument / null → the current UTC time. Byte-verified against CF 2025 in tests/cfm/date_time_functions_test.cfm + JitExpressionTest.GetHttpTimeStringAppliesLocalOffset.
GetK2ServerDocCount ❌ No
GetK2ServerDocCountLimit ❌ No
GetLocale string () ✅ Yes Returns the current request locale's CF string (default en_US; after SetLocale the display name, e.g. French (Standard)), driven by the same per-request locale state as SetLocale.
GetLocaleDisplayName string [locale: string = "en"] [, locale: string] ✅ Yes 28×28 locale display-name matrix (JDK COMPAT data in src/cffunctions/ldn_table.inc); default "English". Verified against CF 2025 in tests/cfm/tier2_localedisplayname_test.cfm.
GetLocalHostIP string () ✅ Yes Returns the loopback address "127.0.0.1", matching the RDS host (verified against CF 2025 in tests/cfm/tier1_ip_test.cfm).
GetMetaData struct object: any ✅ Yes Component/UDF introspection struct (PATH, EXTENDS, PROPERTIES, FUNCTIONS, TYPE, FULLNAME, NAME); queries and simple values throw CF's error. Verified against CF 2025 in tests/cfm/tier2_getmetadata_test.cfm.
GetMetricData any mode: string ✅ Yes Returns server metrics: simple_load/prev_req_time/avg_req_time as integers (idle → 0) and perf_monitor as a struct with CF's key set (reqqueued, cfcreqqueued, dbhits, wsreqqueued, avgreqtime, bytesout, templatereqtimedout, wsreqrunning, bytesin, pagehits, avgqueuetime, errorcount, instancename, reqrunning, wsreqtimedout, cfcreqtimedout, templatereqrunning, avgdbtime, cfcreqrunning, templatereqqueued). Values are live server counters and cannot be byte-verified against CF (structure verified in JitExpressionTest.Tier1GetMetricData).
GetPageContext any () ✅ Yes Throws Function GetPageContext is not supported: it returns a Java page context object. — CF returns the underlying Java servlet PageContext object (coldfusion.runtime.NeoPageContext), which this engine cannot replicate (same rule as CacheGetSession; see BUGS.md).
GetPrinterInfo ❌ No
GetPrinterList ❌ No
GetProfileSections any iniPath: string ✅ Yes Returns struct of sections
GetProfileString string iniPath: string, section: string, key: string ✅ Yes Reads key from ini file
GetPropertyFile string name: string ✅ Yes Returns the server properties-file path (Java .properties format). Verified against CF 2025 in tests/cfm/tier2_propertystring_test.cfm.
GetPropertyString string name: string, key: string [, defaultValue: string] ✅ Yes Java .properties parser (key=value, key: value, line continuation, escapes). Verified against CF 2025 in tests/cfm/tier2_propertystring_test.cfm.
GetReadableImageFormats string `` ✅ Yes Comma-separated list of formats the engine can read
GetSafeHTML ❌ No
GetSAMLAuthRequest ❌ No
GetSAMLLogoutRequest ❌ No
GetSOAPRequest ❌ No
GetSOAPRequestHeader ❌ No
GetSOAPResponse ❌ No
GetSOAPResponseHeader ❌ No
GetSystemFreeMemory numeric () ✅ Yes Returns the free OS memory in bytes (sysinfo). The RDS host lacks the pmtagent package, so CF 2025 throws "The pmtagent package is not installed." there (see BUGS_CF.md); the byte values cannot be byte-verified (JitExpressionTest.Tier1SystemMemory).
GetSystemTotalMemory numeric () ✅ Yes Returns the total OS memory in bytes (sysinfo). Same RDS host limitation as GetSystemFreeMemory (see BUGS_CF.md; JitExpressionTest.Tier1SystemMemory).
GetTempDirectory string () ✅ Yes
GetTempFile string directory: string, prefix: string ✅ Yes Creates temp file
GetTemplatePath string () ✅ Yes Removed in ColdFusion 2025 (deprecated since CF 6 in favor of GetBaseTemplatePath; the docs say it was dropped). Calling it reports "Variable GETTEMPLATEPATH is undefined." like CF (verified on the RDS host in tests/cfm/tier1_error_test.cfm); the name was also dropped from the built-in function registry. GetBaseTemplatePath/GetCurrentTemplatePath remain the supported forms.
GetTickCount numeric () ✅ Yes Milliseconds
GetTimeZoneInfo struct () ✅ Yes
GetToken string string: string, index: numeric, delimiters: string ✅ Yes Java StringTokenizer semantics: every delimiter character separates tokens, consecutive delimiters collapse and empty tokens are never returned. The default delimiters are whitespace (\r\n\t ), NOT a comma (verified against CF 2025; the decompiled CFPage defaults to "\r\n\t "). An index ≤ 0 throws CF's "The value of parameter 2 of the function GetToken, which is now N, must be a positive integer". Verified in tests/cfm/tier1_gettoken_test.cfm + tier1_error_test.cfm + JitExpressionTest.Tier1GetToken.
GetTotalSpace numeric path: string ✅ Yes Returns the total bytes on the filesystem via statvfs (Java File.getTotalSpace() semantics). Same path-shape validation and error message as GetFreeSpace (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1DiskSpace).
GetUserRoles string () ✅ Yes The comma-delimited list of the current user's roles (the order they were given to <cfloginuser roles=..>), or an empty string when nobody is logged in (CF's UserUtils.getUserRoles). Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests.
GetVFSMetaData struct fileSystemType: string ✅ Yes Returns metadata struct for the virtual file system (ENABLED=true, LIMIT="20971520", USED="0", FREE="20971520"). Supports "ram" (case-insensitive) or URIs starting with "ram:"; invalid types throw CF's "FileSystemType provided is not valid." (verified against CF 2025 in tests/cfm/tier1_getvfsmetadata_test.cfm).
GetWriteableImageFormats string `` ✅ Yes Comma-separated list of formats the engine can write
Hash string string: any, algorithm: string = "SHA-256", encoding: string = "UTF-8", additionalIterations: numeric = 0 ✅ Yes Uppercase hex; MD5/SHA-1/SHA-256/384/512; iterations chain raw digest bytes; CFMX_COMPAT returns empty (matches server)
HMac string message: any, key: any, algorithm: string = "HMACMD5", encoding: string = "UTF-8" ✅ Yes Uppercase hex; empty key returns empty string (matches server)
Hour int date: any ✅ Yes
HQLMethods ❌ No
HTMLCodeFormat string string: string [, version: numeric] ✅ Yes Escapes special chars and wraps the result in <PRE>/</PRE> (uppercase). Escaping (verified byte-for-byte against CF 2025 in tests/cfm/htmlcodeformat_test.cfm, interpreter + cfscript JIT paths): \r is dropped, "→&quot;, &→&amp;, ' stays unchanged, <→&lt;, >→&gt;, \n/tabs/% pass through; version is ignored. Port of StringFormatter.escapeXML(xml=false). Note: a # inside a #...# string (&#169;) starts a nested sharp expression on both engines.
HTMLEditFormat string string: string [, version: numeric] ✅ Yes Same escaping as HTMLCodeFormat but without the <PRE> wrapper (port of StringFormatter.htmlEditFormat). version is ignored. Cannot be byte-verified on the RDS host (CF 2025 server lacks htmlEditFormat, see BUGS_CF.md); covered by HtmlEditFunctionsTest unit tests, with the shared escaping byte-verified through HTMLCodeFormat.
IIf any condition: any, string1: string, string2: string ✅ Yes Evaluates the chosen string expression lazily via cf_evaluate. Verified against CF 2025 in tests/cfm/tier2_iif_test.cfm.
ImageAddBorder void image: image, thickness: numeric, color: string = "black", borderType: string = "constant" ✅ Yes Grows the canvas by thickness on all sides; types constant/zero/copy/reflect/wrap; negative thickness error byte-exact vs CF
ImageBlur void image: image, blurRadius: numeric = 3 ✅ Yes Box filter, radius 3..10; out-of-range error byte-exact vs CF
ImageClearRect void image: image, x: numeric, y: numeric, width: numeric, height: numeric ✅ Yes Fills the rect with the background color (pixel-exact vs CF)
ImageCopy image image: image, x: numeric, y: numeric, width: numeric, height: numeric, dx: numeric, dy: numeric ✅ Yes Returns a new image with the cropped rect; with dx/dy also copies the rect within the source (CF copyArea)
ImageCreateCaptcha any height: numeric, width: numeric, text: string, difficulty: string, font: string, fontsize: numeric ✅ Yes Port of coldfusion.image.CaptchaMaker onto cairo (random background gradient, noise curves/polygons, rotated glyph text). Zero width/height are auto-computed like CF's ImageHelper (round(1.5*len*font*0.72+1) / round(2*font*0.72+1)), difficulty low/medium/high, and the argument errors reproduce CF's InvalidCaptchaArgumentException message/detail + UnsupportedCaptchaDifficultyException. Output is random (clock-seeded like CF), so it is verified by unit tests (dimensions, errors, IsImage) rather than byte-compared; the CF 2025 RDS host aborts the request on every ImageCreateCaptcha call (server error, see BUGS_CF.md)
ImageCrop void image: image, x: numeric, y: numeric, width: numeric, height: numeric ✅ Yes Crops in place, clamped to the image bounds; negative dimensions error byte-exact vs CF
ImageDrawArc void image: image, x: numeric, y: numeric, width: numeric, height: numeric, startAngle: numeric, arcAngle: numeric, filled: boolean ✅ Yes Java Arc2D angles (0=3 o'clock, +CCW); filled=PIE wedge. Cairo raster (coarse match vs CF)
ImageDrawBeveledRect void image: image, x: numeric, y: numeric, width: numeric, height: numeric, raised: boolean, filled: boolean ✅ Yes Brighter min(255,c/0.7)/darker c*0.7 tones; raised controls tone layout (pixel-exact vs CF)
ImageDrawCubicCurve void image: image, ctrlx1: numeric, ctrly1: numeric, ctrlx2: numeric, ctrly2: numeric, x1: numeric, y1: numeric, x2: numeric, y2: numeric ✅ Yes Cubic Bezier (start, ctrl1, ctrl2, end); cairo stroke (coarse match vs CF)
ImageDrawLine void image: image, x1: numeric, y1: numeric, x2: numeric, y2: numeric ✅ Yes Axis-aligned lines pixel-exact; diagonals cairo stroke (endpoint pixels may differ by 1)
ImageDrawLines void image: image, xcords: array, ycords: array, isPolygon: boolean, filled: boolean ✅ Yes Polyline/polygon; cairo path (coarse match vs CF)
ImageDrawOval void image: image, x: numeric, y: numeric, width: numeric, height: numeric, filled: boolean ✅ Yes Ellipse; cairo path (coarse match vs CF)
ImageDrawPoint void image: image, x: numeric, y: numeric ✅ Yes (w+1)xw block centered on the point; pixel-exact vs CF
ImageDrawQuadraticCurve void image: image, x1: numeric, y1: numeric, ctrlx1: numeric, ctrly1: numeric, x2: numeric, y2: numeric ✅ Yes Quadratic Bezier (start, control, end); cairo stroke (coarse match vs CF)
ImageDrawRect void image: image, x: numeric, y: numeric, width: numeric, height: numeric, filled: boolean ✅ Yes Fill x..x+w-1; outline spans x..x+w (pixel-exact vs CF)
ImageDrawRoundRect void image: image, x: numeric, y: numeric, width: numeric, height: numeric, arcwidth: numeric, archeight: numeric, filled: boolean ✅ Yes Rounded corners; cairo path (coarse match vs CF)
ImageDrawText void image: image, str: string, x: numeric, y: numeric, attributes: struct ✅ Yes Baseline at (x,y); attributes font/size/style/underline/strikethrough; honors drawing axis + transparency/XOR. Best-effort on cairo: font metrics differ from Java2D and CF 2025 crashes on ALL text rendering, so output is not pixel-verified (see BUGS_CF.md)
ImageFlip void image: image, transpose: string = "vertical" ✅ Yes vertical/horizontal/diagonal/antidiagonal/90/180/270; diagonal/antidiagonal/90/270 swap dimensions; invalid transpose error byte-exact vs CF
ImageGetBlob binary source: string ✅ Yes Bytes of the image in its source format
ImageGetBufferedImage any name: image ✅ Yes CF returns the underlying java.awt.image.BufferedImage (a live Java object); this engine has no Java object interop, so it returns the image itself (the closest stand-in for its backing store). Divergence documented in BUGS.md
ImageGetEXIFMetadata struct name: image ✅ Yes EXIF header struct with com.drew tag names/descriptions (reproduces CF's Image.getExifMetadata), byte-exact JSON vs CF 2025 for the stable IFD0/SubIFD/IPTC cases. Parses JPEG APP1 Exif TIFF (IFD0 + SubIFD + GPS + Interop + Thumbnail); makernotes are not implemented (see BUGS.md). For images with GPS/Interop/Thumbnail the struct key order follows CF's internal directory/tag iteration and may shift across CF restarts (see BUGS_CF.md) — the GPS tag values are verified via the order-independent tag getters. Missing-source images (blank/base64) throw CF's "The ImageMetadata feature is not supported for BLOBS or Base64 strings."
ImageGetEXIFTag string name: image, tagName: string ✅ Yes Case-insensitive EXIF tag lookup by com.drew tag name; missing tags return "". Verified byte-exact vs CF 2025
ImageGetHeight numeric source: string ✅ Yes Image height in pixels
ImageGetIPTCMetadata struct name: image ✅ Yes IPTC header struct (JPEG APP13 "Photoshop 3.0" IRB 8BIM 0x0404 resource) with com.drew tag names, byte-exact JSON vs CF 2025
ImageGetIPTCTag string name: image, tagName: string ✅ Yes Case-insensitive IPTC tag lookup by com.drew tag name; missing tags return "". Verified byte-exact vs CF 2025
ImageGetMetadata struct source: string ✅ Yes PNG/JPEG/GIF metadata struct (byte-exact JSON vs CF). The JPEG struct's key order follows CF's internal HashMap bucket iteration (Image Width before Y Resolution as of the 2026-08-10 CF restart; see BUGS_CF.md — it may shift again on the next restart)
ImageGetWidth numeric source: string ✅ Yes Image width in pixels
ImageGrayscale void image: image ✅ Yes Java luminance 0.299R+0.587G+0.114B; colormodel becomes grayscale/ComponentColorModel
ImageInfo struct source: string ✅ Yes source/width/height/colormodel struct (byte-exact JSON vs CF)
ImageMakeColorTransparent image image: image, color: string ✅ Yes Returns a new image; pixels matching the color become fully transparent (RGB kept); colormodel becomes argb
ImageMakeTranslucent image image: image, percent: numeric ✅ Yes Returns a new image with alpha = 1 - percent/100; out-of-range error byte-exact vs CF
ImageNegative void image: image ✅ Yes Inverts RGB (255 - value), alpha preserved
ImageNew any source: any, width: numeric, height: numeric, imageType: string, canvasColor: string ✅ Yes Blank/from-file image; rgb/argb/grayscale
ImageOverlay void image1: image, image2: image, rule: string = "src_over", alpha: string = "1.0" ✅ Yes Draws image2 over image1 with an alpha-compositing rule (src/src_over/dst_over/src_in/src_out/dst_in/dst_out); invalid rule error
ImagePaste void image1: image, image2: image, x: numeric, y: numeric ✅ Yes Draws image2 over image1 with the top-left at (x,y) (CF paste, bicubic)
ImageRead any source: string ✅ Yes Reads PNG/JPEG/GIF/BMP/PNM from a file
ImageReadBase64 any data: string ✅ Yes Reads image from base64 string (data: URI supported)
ImageResize void image: image, width: string, height: string, interpolation: string = "highestquality", blurFactor: numeric = 1.0 ✅ Yes Scales in place; absolute or % dimensions; one blank side scales proportionally; nearest/bilinear filters (bicubic approximated by bilinear); resolution/dimension errors byte-exact vs CF
ImageRotate void image: image, x: numeric, y: numeric, angle: numeric, interpolation: string = "nearest" ✅ Yes Rotates about (x,y) (default the center; the CF 4-arg form ignores x/y, matching the decompiled CFPage); output sized `ceil(w·
ImageRotateDrawingAxis void image: image, angle: numeric, x: numeric, y: numeric ✅ Yes Post-multiplied (M = M·T), clockwise-positive rotation about (x,y), x/y default 0; pixel-verified vs CF (rotate 90° about any anchor, translate+rotate). cairo rasterization is 1px off Java2D for rotated strokes (coarse match)
ImageScaleToFit void image: image, fitWidth: string, fitHeight: string, interpolation: string = "highestquality", blurFactor: numeric = 1.0 ✅ Yes Resizes keeping the aspect ratio to fit the bounding box (CF scaleToFit semantics)
ImageSetAntialiasing void image: image, antialias: string ✅ Yes on/off (case-insensitive) + true/false; error text byte-exact vs CF
ImageSetBackgroundColor void image: image, color: string ✅ Yes Strict CF color parser (names/hex/"r,g,b"); errors byte-exact vs CF
ImageSetDrawingColor void image: image, color: string ✅ Yes Strict CF color parser; errors byte-exact vs CF
ImageSetDrawingStroke void image: image, attributes: struct ✅ Yes width/endcaps/lineJoins/miterLimit/dashArray/dash_phases; negative width error byte-exact vs CF
ImageSetDrawingTransparency void image: image, percent: numeric ✅ Yes 0..100 percent (SrcOver alpha 1-pct/100); clears XOR mode (last setter wins); error byte-exact vs CF
ImageSharpen void image: image, gain: numeric = 1.0 ✅ Yes Unsharp mask (original + gain·(original - blur)); gain -1..2; out-of-range error byte-exact vs CF
ImageShear void image: image, shear: numeric, direction: string = "horizontal", interpolation: string = "nearest" ✅ Yes Shears about the center; output sized to fit; invalid direction/interpolation errors byte-exact vs CF
ImageShearDrawingAxis void image: image, shrx: numeric, shry: numeric ✅ Yes x'=x+shrx·y, y'=shry·x+y; pixel-verified vs CF (coarse for diagonal strokes)
ImageTranslate void image: image, xTrans: numeric, yTrans: numeric, interpolation: string = "nearest" ✅ Yes Shifts the image by (x,y) keeping the canvas size (clipped); interpolation validation error byte-exact vs CF
ImageTranslateDrawingAxis void image: image, x: numeric, y: numeric ✅ Yes Cumulative origin translation; affects all draw calls AND ImageClearRect (verified vs CF); pixel-exact vs CF
ImageWrite void image: string, destination: string, quality: numeric, overwrite: boolean ✅ Yes Writes PNG/JPEG/GIF/BMP/PNM (JPEG quality check)
ImageWriteBase64 string image: string, destination: string, format: string, inHTMLFormat: boolean, overwrite: boolean ✅ Yes Base64 string; data: URI when inHTMLFormat
ImageXORDrawingMode void image: image, color: string ✅ Yes XOR blend (drawingColor ^ xorColor ^ dst); clears transparency mode (last setter wins); pixel-exact vs CF
IncrementValue numeric number: numeric ✅ Yes Returns number + 1
InitSAMLAuthRequest ❌ No
InitSAMLLogoutRequest ❌ No
InputBaseN numeric string: string, radix: numeric ✅ Yes Parses a base-radix string into an integer (radix 2-36)
Insert string substring: string, string: string, position: numeric ✅ Yes Inserts substring after 0-based position
Int numeric number: numeric ✅ Yes
InterruptThread ❌ No
InvalidateOauthAccesstoken ❌ No
Invoke any object: string, methodName: string, arguments: any ✅ Yes Invokes a CFC method or a page UDF (empty object = current page UDF), with positional array or named struct arguments. Verified against CF 2025 in tests/cfm/tier2_invoke_test.cfm.
InvokeCFClientFunction — ⚠️ Not a CF 2025 function CF 2025 reports Variable INVOKECFCLIENTFUNCTION is undefined., which the engine reproduces (see fn_ajax.cpp).
IsArray boolean value: any ✅ Yes Checks if value is an array
IsAuthenticated — ⚠️ Not a CF 2025 function Obsolete pre-MX security function. CF 2025 reports Variable ISAUTHENTICATED is undefined., which the engine reproduces (see fn_isauthenticated.cpp). UDFs with this name are allowed.
IsAuthorized — ⚠️ Not a CF 2025 function Obsolete pre-MX security function. CF 2025 reports Variable ISAUTHORIZED is undefined., which the engine reproduces (see fn_isauthorized.cpp). UDFs with this name are allowed.
IsBinary boolean value: any ✅ Yes
IsBoolean boolean value: any ✅ Yes
IsClosure boolean value: any ✅ Yes YES for anonymous function values (f = function(x){...}), NO for named UDFs and built-in method handles — verified against CF 2021 (tests/cfm/udf_closure_test.cfm + UdfTest.IsClosureIsCustomFunction)
IsCustomFunction boolean value: any ✅ Yes YES for UDF/closure Function values (named or anonymous), NO for built-in method handles — verified against CF 2021 (tests/cfm/udf_basic_test.cfm + UdfTest.IsClosureIsCustomFunction)
IsDate Boolean value: any ✅ Yes Backed by parseDateTimeStr (see ParseDateTime row). Full consumption is enforced (was BUGS.md "IsDate()/ParseDateTime() ignore trailing garbage"): a trailing non-whitespace tail after the parsed datetime is rejected like CF (IsDate("2020-05-15bogus") → NO); fractional seconds (.ddd right after a seconds field), a trailing timezone offset (Z as the last char, ±HH/±HHMM/±HH:MM), trailing whitespace, and {ts '...'} with trailing text are accepted exactly as CF's CFDateTimeParser does (a parsed +HH:MM offset accepts arbitrary following text in a full date+time string only, matching CF's !hoursGiven short-circuit). Byte-verified against CF 2025 in tests/cfm/isdate_trailing_garbage_test.cfm + isdate_fractional_seconds_test.cfm + JitExpressionTest.IsDateRejectsTrailingGarbage.
IsDateObject boolean date: any ✅ Yes
IsDDX boolean value: string ✅ Yes Reports whether the value is a valid DDX document (parses as XML with the case-sensitive <DDX> root). The RDS host lacks the PDF/DDX service, so CF 2025 always returns NO there (see BUGS_CF.md); the sniffing behavior is covered by JitExpressionTest.Tier1IsDdxAndWddx.
IsDebugMode boolean () ✅ Yes Returns NO — debug output is never enabled (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm).
IsDefined boolean variable_name: string ✅ Yes Resolves a dotted variable name against the runtime scopes: unqualified names search the variables scope (plus the enclosing UDF parent scopes inside a function), scope-prefixed names resolve variables/url/form/cookie/server/application/session/cgi/request, and inside a UDF arguments.* and local.* work (mapped to the current function's local scope). Matches CF: a null/undefined value is NOT defined, a disabled scope (APPLICATION/SESSION before <cfapplication> enables them) is NOT defined, scope names themselves (session, form, ...) are defined once enabled, a bare request is defined (the REQUEST scope exists per request; a missing request.member is NO), empty-string/numeric args are NO, query columns resolve like struct members, and the CF quirk that any cgi.<member> reference (even a non-existent key) is YES is reproduced. A wrong argument count throws IsDefined requires exactly 1 argument. Verified byte-for-byte against CF 2025 in tests/cfm/isdefined_test.cfm + JitExpressionTest.IsDefined* unit tests.
IsFileObject boolean value: any ✅ Yes
IsImage boolean value: any ✅ Yes
IsImageFile boolean filepath: string, format: string ✅ Yes Decodable image file (optional format check)
IsInstanceOf boolean obj: any, typeName: string ✅ Yes True when a component is (or extends) the named type.
IsIPv6 boolean value: string ✅ Yes Reproduces CF's IPAddressUtils.isIPV6: the loopback/hostname forms (localhost, 127.0.0.1, [::1], ::1, 0:0:0:0:0:0:0:1) report whether the server has IPv6 (true on the RDS host), other values resolve and return whether any address is IPv6. IPv4-mapped ::ffff:... addresses report NO like CF. Verified against CF 2025 in tests/cfm/tier1_ip_test.cfm + JitExpressionTest.Tier1IpFunctions.
IsJSON boolean string: string ✅ Yes Uses json-c tokener
IsK2ServerABroker ❌ No
IsK2ServerDocCountExceeded ❌ No
IsK2ServerOnline ❌ No
IsLeapYear boolean year: int ✅ Yes
IsLocalHost boolean value: string ✅ Yes Reproduces CF's IPAddressUtils._isLocalHost: "localhost" (case-insensitive, raw — no trimming), the exact "127.0.0.1", "[::1]" and any IPv6 form resolving to ::1 are localhost; other loopback addresses (127.0.0.2) are not. Verified against CF 2025 in tests/cfm/tier1_ip_test.cfm + JitExpressionTest.Tier1IpFunctions.
IsNull boolean value: any ✅ Yes
IsNumeric boolean value: any ✅ Yes
IsNumericDate boolean value: any ✅ Yes
IsObject boolean value: any ✅ Yes Component and Function (a bare built-in function reference is a method-handle object in CF)
isOnline boolean host: string ✅ Yes Issues a curl HEAD request (5s timeout); true when the request completes. Not defined on the RDS host (CF 2025 removed it); implemented in fn_isonline.cpp.
IsPDFArchive boolean pdf: any ✅ Yes PDF magic sniffing (%PDF- + %%EOF, archived only when a PDF/A marker is present). RDS host always reports NO (no PDF service, see BUGS_CF.md).
IsPDFFile boolean filepath: string ✅ Yes PDF magic-byte sniffing. RDS host always reports NO (no PDF service, see BUGS_CF.md).
IsPDFObject boolean obj: any ✅ Yes PDF magic-byte sniffing. RDS host always reports NO (no PDF service, see BUGS_CF.md).
IsProtected — ⚠️ Not a CF 2025 function Obsolete pre-MX security function. CF 2025 reports Variable ISPROTECTED is undefined., which the engine reproduces (see fn_isprotected.cpp). UDFs with this name are allowed.
IsQuery boolean value: any ✅ Yes
isSafeHTML boolean string: string [, policyFile: string] ✅ Yes Validates HTML against AntiSamy policy rules (built-in antisamy-basic.xml or custom policy file). Returns YES/NO. Complex objects throw Expression exception; missing custom policy throws Application exception. Verified against CF 2025 in tests/cfm/issafehtml_test.cfm.
isSamlLogoutResponse ❌ No
IsSimpleValue boolean value: any ✅ Yes
IsSOAPRequest ❌ No
IsSpreadsheetFile ❌ No
IsSpreadsheetObject ❌ No
IsStruct boolean value: any ✅ Yes
isThreadInterrupted any threadName: string ✅ Yes CF 2025 takes the thread name (0 args reports "Parameter validation error ... The function takes 1 parameter."). The engine has no thread subsystem (cfthread is unimplemented), so any name reproduces CF's "Thread X cannot be used in action isInterrupted because the thread X was not created." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1IsThreadInterrupted).
IsUserInAnyRole boolean value: string ✅ Yes YES when the current user has ANY of the comma-delimited roles in value (CF's UserUtils.isUserInAnyRole trims each element; each is then an isUserInRole test). Each role in value is split on , and trimmed; spaces inside an element are preserved. Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests.
IsUserInRole boolean value: string ✅ Yes YES when the current user has ALL of the comma-delimited roles in value (CF's UserUtils.cflogin_IsUserInRole: every group must be present — IsUserInRole("role one,role two") is YES only when both are in the user's roles). Roles are split on , without trimming (a space inside an element stays part of it, matching the roles attribute semantics). Verified against CF 2025 in tests/cfm/cflogin_roles_test.cfm + LoginTagTest unit tests.
IsUserLoggedIn boolean () ✅ Yes YES when a user is logged in, i.e. GetAuthUser() is non-empty (CF's CFPage.IsUserLoggedIn: !"".equals(GetAuthUser())). Reads the request's security context installed by the login tags. Verified against CF 2025 in tests/cfm/cflogin_basic_test.cfm + LoginTagTest unit tests.
IsValid boolean type: string, value: any [, min] [, max] [, pattern] ✅ Yes Validator matrix: numeric/integer/range/uuid/guid/email/ssn/telephone/zipcode/creditcard/url/date/time/boolean/string/regex/array/struct/query/binary/function/variableName/... Verified against CF 2025 in tests/cfm/tier2_isvalid_test.cfm.
IsValidOauthAccesstoken ❌ No
IsWDDX boolean value: any ✅ Yes Reports whether the value is a well-formed WDDX packet: the case-sensitive <wddxPacket> root with a version attribute, a <header/> element and at least one value under <data> (verified against CF 2025 in tests/cfm/tier1_wddx_test.cfm + JitExpressionTest.Tier1IsDdxAndWddx).
IsXML boolean value: any ✅ Yes Checks if string is valid XML or value is XML object
IsXmlAttribute boolean value: any ✅ Yes
IsXmlDoc boolean value: any ✅ Yes
IsXmlElem boolean value: any ✅ Yes
IsXmlNode boolean value: any ✅ Yes
IsXmlRoot boolean value: any ✅ Yes
JavaCast ❌ No
JSStringFormat string string: string ✅ Yes Escapes a string for use in JavaScript string literals (\n, \r, \t, \f, \", \', \\)
LCase string string: string ✅ Yes
Left string string: string, count: numeric ✅ Yes
Len numeric string: string ✅ Yes
ListAppend string list: string, value: string, delimiter: string = "," ✅ Yes
ListChangeDelims string list: string, newDelimiter: string, delimiter: string = "," ✅ Yes
ListContains numeric list: string, value: string, delimiter: string = "," ✅ Yes Substring match
ListContainsNoCase numeric list: string, value: string, delimiter: string = "," ✅ Yes Case-insensitive
ListDeleteAt string list: string, index: numeric, delimiter: string = "," ✅ Yes 1-based index
ListEach void list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] ✅ Yes Invokes callback(item, index, list) for each list element (index 1-based); returns null. includeEmptyFields keeps empty elements. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce.
ListFilter string list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] ✅ Yes Returns a new list string with the elements for which the callback returns true (callback args item, index, list). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce.
ListFind numeric list: string, value: string, delimiter: string = "," ✅ Yes Case-sensitive, 0 if not found
ListFindNoCase numeric list: string, value: string, delimiter: string = "," ✅ Yes Case-insensitive
ListFirst string list: string, delimiter: string = "," ✅ Yes
ListGetAt string list: string, index: numeric, delimiter: string = "," ✅ Yes 1-based index
ListGetDuplicates any list: string [, delimiter: string] ✅ Yes Returns the values that occur more than once, sorted alphabetically and joined with the input delimiter into a string (CF 2021 returns a simple value, not an array). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListGetDuplicatesAndRemove.
ListInsertAt string list: string, index: numeric, value: string, delimiter: string = "," ✅ Yes 1-based index
ListLast string list: string, delimiter: string = "," ✅ Yes
ListLen numeric list: string, delimiter: string = "," ✅ Yes
ListMap string list: string, callback: function [, delimiter: string] [, includeEmptyFields: boolean] ✅ Yes Returns a new list string of the callback results (callback(item, index, list)), joined by the delimiter. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce.
ListPrepend string list: string, value: string, delimiter: string = "," ✅ Yes
ListQualify string list: string, qualifier: string [, delimiters: string] [, elements: string] [, includeEmptyValues: boolean] ✅ Yes Wraps each element in the qualifier (ListQualify("a,b,c","'") → 'a','b','c'). A two-char qualifier is a (prefix, suffix) pair; elements "all"/"char" qualify everything; includeEmptyValues keeps empty elements as ''. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts.
ListReduce any list: string, callback: function [, initialValue: any] [, delimiter: string] [, includeEmptyFields: boolean] ✅ Yes Reduces the list: callback(acc, item, index, list) returns the next accumulator starting from initialValue. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListEachFilterMapReduce.
ListRemoveDuplicates string list: string [, delimiter: string] [, ignoreCase: boolean] ✅ Yes Returns the list with duplicate elements removed, keeping the first occurrence (ignoreCase=true makes matching case-insensitive). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListGetDuplicatesAndRemove.
ListRest string list: string, delimiter: string = "," ✅ Yes
ListSetAt string list: string, index: numeric, value: string, delimiter: string = "," ✅ Yes 1-based index
ListSort string list: string, sortType: string [, sortOrder: string] [, delimiters: string] [, includeEmptyFields: boolean] [, localeSensitive: boolean] or list: string, callback: function ✅ Yes Sorts a list (numeric/text/textnocase, asc/desc) or via a comparator callback. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListSortVariants.
ListToArray array list: string, delimiter: string = "," ✅ Yes
ListValueCount numeric list: string, value: string [, delimiters: string] ✅ Yes Case-sensitive count of elements equal to value. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts.
ListValueCountNoCase numeric list: string, value: string [, delimiters: string] ✅ Yes Case-insensitive count of elements equal to value. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_test.cfm + HigherOrderFunctionsTest.ListQualifyAndCounts.
LJustify string string: string, length: numeric ✅ Yes Left-justifies a string in a given field length
Location void url: string, addtoken: boolean, statuscode: numeric ✅ Yes ColdFusion 2025 removed the script-accessible Location() function (only the <cflocation> tag remains); calling it throws CF's "Method Location with N arguments is not in class coldfusion.runtime.CFPage." with the actual argument count (verified against CF 2025; covered by JitExpressionTest.Tier1LocationThrowsLikeCf2025). The tag form keeps working via response_redirect.
Log float number: numeric ✅ Yes Natural log
Log10 float number: numeric ✅ Yes Base-10 log
LSCurrencyFormat string number: numeric [, type: string [, locale: string]] ✅ Yes type local (default) / international / none, locale-aware (grouping/decimal separators, symbol position, negative patterns incl. parens for en_US / € 1.234,50- for nl_NL, 0 decimals for JPY/KRW, half-even rounding like Java).
LSDateFormat string date: any [, mask: string [, locale: string]] ✅ Yes Formats with the locale's date mask; a bare mmmm/MMMM mask renders the locale's standalone month form, which for Italian is capitalized (Maggio) while the same token inside a longer mask is lowercase (15 maggio 2020) — a per-locale standaloneMonthCapitalized flag in src/locale_table.inc reproduces CF 2025 (only Italian differs; French/Spanish/Dutch bare months stay lowercase). Verified against CF 2025 in tests/cfm/lsdateformat_bare_month_test.cfm + LocaleTest (was BUGS.md "Italian bare LSDateFormat(d, "mmmm") month-name capitalization").
LSDateTimeFormat string date: any [, mask: string [, locale: string]] ✅ Yes Locale-aware (default mask dd-MMM-yyyy HH:nn:ss with locale month names; named masks use the locale's date+time patterns).
LSEuroCurrencyFormat string number: numeric [, type: string [, locale: string]] ✅ Yes Euro-zone locales format in Euro; others in their local currency.
LSIsCurrency boolean string: string [, locale: string] ✅ Yes
LSIsDate boolean string: string [, locale: string] ✅ Yes Locale-aware (was BUGS.md #11): parses with the locale's JDK COMPAT patterns + month/day/AM-PM names, the JDK 2-digit-year century window, timezone z/X offsets, and the fixed yyyy-MM-dd/dd.MM.yyyy/dd/MM/yyyy/H:mm:ss.S/... patterns via CFLocaleBase.ParseDateTime. A Date argument → YES; a non-string or all-digits string → NO (CF's isNumberOnly rule); otherwise ParseDateTime succeeds/fails. Byte-verified against CF 2025 in tests/cfm/lsparsedatetime_locale_test.cfm + LocaleTest.LSIsDateUsesLocale.
LSIsNumeric boolean value: any ✅ Yes
LSNumberFormat string number: numeric [, mask: string [, locale: string]] ✅ Yes Default = standard grouping/0 decimals; CF mask language (_, 9,999, 9,999.99, 0000, ...) with locale separators and right-aligned space/zero padding.
LSParseCurrency numeric string: string [, locale: string] ✅ Yes
LSParseDateTime DateTime string: string [, locale: string] ✅ Yes Locale-aware (was BUGS.md #11): implements CF's CFLocaleBase.ParseDateTime — phase-1 datetime formatters (locale short/medium/long/full date+time, RFC1123 EEE, dd MMM yyyy HH:mm:ss zzz, ISO yyyy-MM-dd'T'HH:mm:ssXXX/...ss), then the phase-2 date+time formatters with CF's shared-ParsePosition + errorIndex discard and the date/time remainder split. Patterns come from the per-locale JDK COMPAT data (pDate*/pTime* in src/locale_table.inc, since CF runs java.locale.providers=COMPAT,SPI); month/day/AM-PM names match the locale (COMPAT symbols = the table's existing arrays). JDK 2-digit-year window (now−80y: 15.05.20→2020, 15.05.99→1999, 15.05.70→1970 under German). Explicitly-timezoned strings are reduced to UTC and then localized to the server's timezone (2020-05-15T10:30:00Z on a UTC+2 host → 12:30:00; +05:00/-05:00/RFC1123 GMT handled the same way, with day-boundary rollover via localtime_r; west-of-UTC offsets were previously rejected because the z/X matchers used a negative return both as the offset and as the no-match sentinel — now INT_MIN); a timezone-free string stays a plain server-local wall clock (10:30 → 10:30). Day-of-week conflicts rejected (Tuesday, May 15, 2020), time-only parses onto the OLE epoch 1899-12-30, and a Date argument passes through unchanged. Byte-verified against CF 2025 in tests/cfm/lsparsedatetime_locale_test.cfm (including the day-boundary, negative-offset and RFC1123-GMT cases) + LocaleTest.LSParseDateTime* (was "still parses with the English date parser" in BUGS.md).
LSParseEuroCurrency numeric string: string [, locale: string] ✅ Yes
LSParseNumber numeric string: string [, locale: string] ✅ Yes Parses with the locale's decimal/grouping separators (ASCII spaces accepted as grouping).
LSTimeFormat string date: any [, mask: string [, locale: string]] ✅ Yes Locale-aware (default and named masks use the locale's time patterns incl. AM/PM markers).
LTrim string string: string ✅ Yes
Max numeric number1: numeric, number2: numeric ✅ Yes
Mid string string: string, start: numeric, count: numeric ✅ Yes 1-based start
Min numeric number1: numeric, number2: numeric ✅ Yes
Minute int date: any ✅ Yes
Month int date: any ✅ Yes
MonthAsString string month_number: int [, locale: string] ✅ Yes
Now DateTime () ✅ Yes
NumberFormat string number: numeric [, mask: string] ✅ Yes Full CF mask grammar (grouping, sign/paren/dollar/justify, rounding half-up). Verified against CF 2025 in tests/cfm/tier2_numberformat_test.cfm.
ObjectEquals boolean clientobject: any, originalobject: any ✅ Yes Deep, type-strict equality like CFComparable.objectEquals: values of different CF types are never equal (ObjectEquals(1, "1") and ObjectEquals(1, 1.0) are NO), containers (array/struct/query) are compared recursively. Verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1ObjectEquals.
ObjectLoad any binaryOrFile: any ✅ Yes Round-trips ObjectSave output (JSON wire format with a magic header; NOT Java serialization — documented divergence, see BUGS.md). Verified against CF 2025 for the round-trip in tests/cfm/tier2_objectsave_test.cfm.
ObjectSave binary obj: any, file: string ✅ Yes Serializes to the engine's JSON wire format with a magic header (CF uses Java Object Serialization; documented divergence, see BUGS.md). Verified against CF 2025 for the round-trip in tests/cfm/tier2_objectsave_test.cfm.
OnWSAuthenticate ❌ No
ORMClearSession ❌ No
ORMCloseAllSessions ❌ No
ORMCloseSession ❌ No
ORMEvictCollection ❌ No
ORMEvictEntity ❌ No
ORMEvictQueries ❌ No
ORMExecuteQuery ❌ No
ORMFlush ❌ No
ORMFlushall ❌ No
ORMGetSession ❌ No
ORMGetSessionFactory ❌ No
ORMIndex ❌ No
ORMIndexPurge ❌ No
ORMReload ❌ No
ORMSearch ❌ No
ORMSearchOffline ❌ No
ParagraphFormat string string: string ✅ Yes
ParameterExists boolean parameter: string ✅ Yes Removed in ColdFusion 2025 (the docs: "available ... until ColdFusion 2025"). Calling it reports "Variable PARAMETEREXISTS is undefined." like CF (verified on the RDS host in tests/cfm/tier1_error_test.cfm); the name was also dropped from the built-in function registry.
ParseDateTime DateTime date_string: string [, pop: string] ✅ Yes CF's US-English DateUtils.parseDateTime (dash/slash/dot separators, year-position disambiguation YYYY-MM-DD/MM-DD-YYYY/DD-MM-YYYY, bare times onto the OLE epoch 1899-12-30, AM/PM). ISO-8601 datetimes are supported (was BUGS.md): the T date/time separator is accepted and a trailing timezone offset (Z/±HH/±HHMM/±HH:MM) is applied by reducing the wall clock to UTC and localizing to the server timezone — 2020-05-15T10:30:00 → 10:30, ...Z → 12:30 on a UTC+2 host, +05:00/-05:00 symmetric (west-of-UTC via the INT_MIN sentinel fix). Because parseDateTimeStr also backs IsDate/IsValid/Year/Hour/.../<cfqueryparam>/<cfparam> date coercion, they accept the same strings. Full consumption is now enforced (was BUGS.md "IsDate()/ParseDateTime() ignore trailing garbage"): the numeric scans track their consumed position (%n) and a trailing non-whitespace tail is rejected (IsDate("2020-05-15bogus") → NO), with fractional seconds (.ddd), trailing offsets and trailing whitespace handled exactly like CF's CFDateTimeParser (see the IsDate row). Byte-verified against CF 2025 in tests/cfm/parsedatetime_iso_t_test.cfm + isdate_trailing_garbage_test.cfm + isdate_fractional_seconds_test.cfm + JitExpressionTest.ParseDateTimeIsoT/IsDateRejectsTrailingGarbage.
Pi float () ✅ Yes 3.141592653589793
PrecisionEvaluate numeric expressions: string ✅ Yes Evaluates the expression string(s) with Java BigDecimal-style arbitrary-precision decimal arithmetic. The expression is parsed with the engine's real CFML expression parser: the argument is wrapped in component { <expr> } (a .cfc script component), tokenized with textparser, turned into an ExprAST via parseTokensToAST, and evaluated by a BigDecimal-aware AST walker (src/codegen/fn_precisionevaluate.cpp, in the compiler library where textparser + the parser live; both the JIT and the interpreter dispatch to it). The decimal arithmetic is a self-contained engine (sign + digit string + scale, chosen over decNumber because BigDecimal's scale-based semantics differ from IEEE-754 decimal). + - * are exact (add keeps max(a.scale,b.scale), multiply keeps a.scale+b.scale, operands parse like Cast._BigDecimal); / reproduces CF's _divideBD: first a.divide(b, ROUND_UNNECESSARY) (result scale = a.scale; succeeds only when the exact quotient needs no more fractional digits than a.scale), else scale 20 HALF_EVEN; ^ reproduces _powBD: new BigDecimal(Math.pow(a.doubleValue(), b.doubleValue())) — double math converted to the exact decimal expansion of the double; MOD/% and \ are integer ops (operands truncated toward zero); comparisons support ==/!= and the word forms EQ/NEQ/IS/IS NOT/LT/LTE/GT/GTE/LE/GE/CONTAINS/DOES NOT CONTAIN/NOT (CF's precision grammar rejects the symbolic </>/<=/>=, reproduced by scanning the operator tokens). Results render with BigDecimal.toString() (plain vs E notation; e.g. 1/10000000 → 1.0000000000000E-7); a string argument containing a comma is split into expressions (the last wins); true/false/bare identifiers/plain numbers pass through like CF's PrecisionEvalExprClassLoader. Identifiers inside the expression (and the whole-expression fast path) resolve through the scopes like CF's _autoscalarize — including dotted scope names (form.x), and numeric string variables coerce like Cast._BigDecimal. Division by zero throws "Division by zero.". Verified byte-for-byte against CF 2025 in tests/cfm/tier2_precisionevaluate_test.cfm + JitExpressionTest.Tier2PrecisionEvaluate* unit tests.
PreserveSingleQuotes void variable: string ✅ Yes CF 2025 no longer auto-escapes single quotes in query literals, so the function is an identity: the argument passes through unchanged (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm).
ProcessSAMLLogoutRequest ❌ No
ProcessSAMLResponse ❌ No
Quarter int date: any ✅ Yes
QueryAddColumn numeric query: query, column_name: string [, datatype: string], array_name: array ✅ Yes Appends a column to a query and fills it from a one-dimensional array. Returns the new column count. Type coercion applies per datatype (default varchar). Existing columns are padded to the new column's length when the array is longer, and a shorter array is padded with nulls to the query's row count (verified against CF 2021). Duplicate column name and non-array last argument throw. Verified in tests/cfm/queryaddcolumn1.cfm + QueryMutationTest.
QueryAddRow numeric `query: query [, number: numeric row: struct rows: array]`
QueryConvertForGrid struct query: query, page: numeric, pageSize: numeric ✅ Yes Returns {QUERY: pagedSubset, TOTALROWCOUNT: totalRows}. The subset holds the rows for page/pageSize (1-based), clamped to the record count — page beyond the last row yields an empty query, pageSize=0 yields an empty query, a negative start index (page-1)*pageSize throws (CF: Java IndexOutOfBounds). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryConvertForGrid*.
QueryEach void query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Invokes callback(row, currentRow, query) for each row (row is a fresh struct of the row's cells; currentRow is 1-based; the query argument is a copy). Returns null (CF: assigning the void return leaves the variable undefined). parallel/maxThreads are accepted and ignored (sequential execution). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryEach*.
QueryExecute query sql: string [, params: any [, options: struct]] ✅ Yes Executes SQL with positional (?) or named (:name) parameter substitution (list params, VALUE/CFSQLTYPE/NULL structs supported) and cfquery options. Implemented on the shared cf_run_query executor (extracted from <cfquery>). options.dbtype="query" runs a Query of Queries (the referenced query objects are resolved from the caller's scopes and executed in-memory via SQLite — see the cfquery row); the other options map to the same cf_run_query attributes. Verified against CF 2025 in tests/cfm/tier2_queryexecute_test.cfm (DB-backed) + tests/cfm/query_of_queries_test.cfm section T13 (QoQ) + CfQueryTest.QueryOfQueries* unit tests.
QueryFilter query query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Returns a NEW query with the rows for which the callback returns true (original unchanged, column names/types preserved). Callback gets (row, currentRow, query). parallel/maxThreads accepted and ignored. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryFilter*.
QueryGetResult struct query: query ✅ Yes Returns the cached <cfquery> resultset metadata struct; a QueryNew-built query has none, so CF returns null (verified vs CF 2021: IsNull(QueryGetResult(q)) → YES, cfdump renders "undefined"). All queries in this engine come from QueryNew, so null is the faithful result. Verified in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryGetResultIsNull.
QueryGetRow struct query: query, rowNumber: numeric ✅ Yes Returns a struct with the query columns as keys and the row's cell values (original column-name casing preserved, case-insensitive lookup). Out-of-bounds row numbers throw (CF: Expression). Verified in tests/cfm/querygetrow1.cfm + QueryMutationTest.
QueryKeyExists boolean query: query, key: string ✅ Yes Returns YES/NO depending on whether the named column exists in the query (case-insensitive; empty key → NO). Verified in tests/cfm/querykeyexists1.cfm + QueryMutationTest.
QueryMap query query: query, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Returns a NEW query (same columns/types) whose rows are the structs returned by callback(row, currentRow, query). The callback may mutate the passed row and return it, or return a new struct with exactly the query's columns (a missing/extra column throws, matching CF's fatal error; a non-struct return throws). Mapped cells keep their raw values. parallel/maxThreads accepted and ignored. Note: arithmetic on typed (integer) cells produces Integer here vs Double in CF, so mapped integer arithmetic renders 10 vs CF's 10.0 — a pre-existing arithmetic-type divergence, not a QueryMap bug. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryMap*.
QueryNew query columnList: string/struct [, columnTypeList: string] [, rowData: array/struct] ✅ Yes Creates a query value with type coercion per column. columnlist returns the sorted uppercased names; recordcount/currentrow pseudo-properties; q.col/q["col"] return the column as an array (so q.col[1] reads a cell); dot access resolves pseudo-properties first, bracket access resolves columns first (verified against CF 2021). rowData forms: single struct (one row), array of structs (extra keys throw — CF Expression error), array of arrays (positional), scalar (one row, first column). A struct first argument is row data, not a name→type map (QueryNew({a:"integer",b:"varchar"}) → columns A,B with a 1-row query whose cells are the values, all varchar). Column types default to varchar; integer truncates decimals ("30.9"→30) and rejects non-numeric ("Invalid data ... for CFSQLTYPE CF_SQL_INTEGER."), bigint→Long, double/decimal/numeric/float→Float, date/time/timestamp→DateTime, boolean/bit→Boolean. SerializeJSON → {"COLUMNS":[...],"DATA":[...]} with original order (uppercased names), typed cells (numbers as JSON numbers, dates as "January, 01 2020 00:00:00", null for missing cells). cfdump (HTML + text) byte-identical to CF 2021. Verified byte-for-byte in tests/cfm/querynew1-4.cfm + QueryNewTest unit tests. Query-column copy semantics match CF 2021/2025 (was BUGS.md): q["a"][1] = "z" and q.a[1] = "z" cell writes persist through a query-column back-reference; a stored reference is live (x = q["a"]; x[1] = "MUT" writes through and q["a"][2] = "YY" shows through x[2]), not a real array (ArrayLen/ArrayAppend reject it with CF's "cannot be used as an array" error, IsArray is NO, stringifies as its scalar first cell), a dot-origin copy or a second copy (y = x) degrades to the scalar first cell so element writes throw CF's "dereference a scalar variable of type class java.X as a structure with members." Expression error, and a write past the last row is invisible. The QueryData is refcounted from column references (reassigning q does not dangle a copy). Whole-column assignment matches CF: q.a = v writes the current row's cell (coerced to the column type), a non-column name throws CF's "columnMap" Application error, and q["a"] = v throws CF's "trying to modify the query named class coldfusion.sql.QueryTable." Expression error (an array RHS is rejected with the same error; CF's own array-to-column behavior is erratic — see BUGS_CF.md). Verified byte-for-byte in tests/cfm/querycell_assign.cfm + query_column_copy_test.cfm + query_column_assign_test.cfm + QueryMutationTest.CellAssign*/CopiedColumnWritesThrough/CopiedDotColumnIsReadOnly/SecondCopyOfBracketColumnThrows/SecondCopyOfDotColumnThrows/StoredColumnRefIsNotAnArray/CopiedColumnReadsLive/PhantomRowWriteInvisible/DegradedColumnCopyIsScalar/DegradedIntegerColumnCopyIsScalar/DotColumnFirstCopyIsScalar/WholeColumnAssign*/BracketWholeColumnAssignThrows.
QueryReduce any query: query, callback: function [, initialValue: any] ✅ Yes Reduces the query rows: callback(acc, row, currentRow, query) returns the next accumulator, starting from initialValue (without it the accumulator starts null — CF throws "Variable ACC is undefined." on the first use). An empty query returns the initial value unchanged. Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QueryReduce*.
QuerySetCell boolean query: query, column: string, value: any [, row: numeric] ✅ Yes Sets a cell to a value and returns YES. Without a row number the last row is used; an explicit row must be in bounds (0 and out-of-bounds positive rows throw — CF Expression; negative row numbers behave like the last row). Setting a cell on a query with no rows throws. Values are coerced to the column's type (e.g. integer column: "30.9"→30, invalid → Database error). currentrow is not advanced. Verified in tests/cfm/querysetcell1.cfm + QueryMutationTest.
QuotedValueList string column: query column [, delimiter: string = ","] ✅ Yes Returns the values of a query column, each wrapped in single quotes ('a','b') and joined by the delimiter. Embedded quotes are NOT escaped (CF behavior); empty string values become ''; an empty column returns "". Passing the whole query (no column) throws (CF rejects it). Verified byte-for-byte against CF 2021 in tests/cfm/query_functions_test.cfm + QueryFunctionsTest.QuotedValueList.
Rand float [algorithm: string] ✅ Yes [0, 1). The optional algorithm (default SHA1PRNG) is validated per-thread like CF: an unsupported name throws CF's "The X algorithm is not supported by the Security Provider you have chosen." only on the first Rand/RandRange call of a thread (CF caches its SecureRandom generator in a never-cleared ThreadLocal); later calls reuse the cache and silently ignore a bad name — the cache is also populated by Randomize/Rand/RandRange, so the error is order-dependent and non-deterministic on a pooled server, exactly like CF. Verification: rand_algorithm_caching_test.cfm (byte-verified against CF 2025) + JitExpressionTest.RandRange*/RandRandomizeAlgorithmCaching.
Randomize numeric seed: numeric [, algorithm: string] ✅ Yes Seeds RNG and returns the next value of the freshly reseeded generator — a numeric in [0,1), deterministic for a given seed (Randomize(42) twice yields the same value, and the following Rand()/RandRange() continues the seeded sequence), matching CF's random.nextDouble() (CF: #Randomize(42)# → 0.832195441766, IsNumeric → YES). Unlike Rand/RandRange, Randomize ALWAYS validates the algorithm (its 2-arg generator path calls SecureRandom.getInstance and reseeds every call, never consulting the per-thread cache) and overwrites the cache with the freshly seeded generator. An unsupported name throws CF's message on every call.
RandRange numeric number1: numeric, number2: numeric [, algorithm: string] ✅ Yes Generates a random integer in [min, max] inclusive, swapping the bounds when number1 > number2 (CF behavior, verified: RandRange(5,3) yields values in [3,5] and RandRange(7,5) in [5,7]). Equal bounds always return that value. The optional algorithm argument (default SHA1PRNG) is validated per-thread like CF (see Rand): the name is matched case-insensitively with no trimming (empty/whitespace-padded names throw on a fresh thread, lowercase sha1prng works), and once any Rand/RandRange/Randomize call has populated the thread's cache, later calls silently ignore any algorithm name. Valid names: SHA1PRNG, NativePRNG, NativePRNGNonBlocking, NativePRNGBlocking, DRBG, Windows-PRNG. The randomness comes from the same rand() generator as Rand/Randomize. Verified in tests/cfm/randrange_test.cfm + rand_algorithm_caching_test.cfm + JitExpressionTest.RandRange*.
ReEscape string string: string ✅ Yes Escapes regex control characters in string (the set |$^+?{}*.[]()\&- from StringFunc's ESCAPE_REGEX_PATTERN; note & and - are included). Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitReEscape.
REFind any reg_expression: string, string: string, start: numeric = 1, returnsubexpressions: boolean = false, scope: string = "one" ✅ Yes Case-sensitive regex search (Apache ORO Perl5 semantics via a PCRE2 backend). Returns the 1-based start position of the first match (0 if none), or with returnsubexpressions=true a struct {MATCH[], POS[], LEN[]} (POS is 1-based; a non-participating group reports ""/0/0); no match returns {MATCH:[""],POS:[0],LEN:[0]}. scope="all" (CF2016+) returns an array of positions/structs. . matches \n (CF compiles ORO patterns with SINGLELINE). ORO-unsupported constructs are rejected with CF's errors: lookbehind/named groups (Sequence (?<...) not recognized), atomic (?> (Sequence (?>...) not recognized), scoped modifiers (?i: (Sequence (?:...) not recognized); escapes ORO treats as bare letters (\u \U \l \L \F \v \p \P \N \R \X \K \Q \E) are normalized. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.*.
REFindNoCase any reg_expression: string, string: string, start: numeric = 1, returnsubexpressions: boolean = false, scope: string = "one" ✅ Yes Case-insensitive REFind (PCRE2_CASELESS). Same return shapes and ORO compatibility as REFind. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.*.
ReleaseComObject ❌ No
REMatch array reg_expression: string, string: string ✅ Yes Returns an array of every substring matched by reg_expression (group 0 of each match), empty array if none. Zero-length matches are collected with the ORO/Java find() advancement rule. . matches \n; ORO syntax restrictions apply as in REFind. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRematch.
REMatchNoCase array reg_expression: string, string: string ✅ Yes Case-insensitive REMatch. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRematch.
RemoveCachedQuery void SQL: string, datasource: string [, params] [, region] ✅ Yes Removes a query from the query cache (QUERY region, or the region arg). <cfquery cachedwithin>/cachedafter/cacheid/cacheregion now store freshly-executed results in the same cache (key = explicit cacheid, else CF's 29*(29*dsnHash+sqlHash)+paramHash), serve hits with result.CACHED="true", and RemoveCachedQuery deletes by the same hash key. Not yet byte-verified against CF 2025 (the CF caching package is installed on the RDS host but the Cache* functions still diverge — see BUGS_CF.md); covered by CacheTest unit tests + the query-cache CLI checks.
RemoveChars string string: string, start: numeric, count: numeric ✅ Yes Removes count characters starting at 1-based start
RepeatString string string: string, count: numeric ✅ Yes
Replace string string: string, substring1: string, substring2: string, scope: string = "ONE" ✅ Yes
ReplaceList string string: string, list1: string, list2: string [, delim1: string] [, delim2: string] [, includeEmptyFields: boolean] ✅ Yes Replaces occurrences of each list1 element with the paired list2 element, applied pair by pair, sequentially, ALL occurrences, case-sensitive (port of StringFunc.ReplaceList + CFPage arg handling). A 4th/5th argument of true/yes/false/no selects includeEmptyFields; otherwise they are the delimiters. Delimiters follow escapeDelim: multi-char = char set, regex metachars escaped (./`
ReplaceNoCase string string: string, substring1: string, substring2: string, scope: string = "ONE" ✅ Yes
REReplace string string: string, reg_expression: string, substring: string, scope: string = "one" ✅ Yes Replaces the first (scope ONE, default) or every (scope ALL) match of reg_expression in string. Substitution follows CF's ORO pre-processing: \1.. back-references, literal $ (write \$), literal backslashes pass through, and the ORO case-conversion controls \U/\L (to \E) and \u/\l (first char of next element). A reference to a nonexistent/non-participating group renders empty; multi-digit refs (e.g. \10 = group 10) are parsed like ORO. Empty regex throws Argument 2 of function REReplace cannot be an empty value.; an invalid scope throws The scope argument of the REReplace function has an invalid value X. (CF hardcodes "REReplace" here even for REReplaceNoCase). Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm, refind_re_errors_test.cfm + RegexFunctionsTest.*.
REReplaceNoCase string string: string, reg_expression: string, substring: string, scope: string = "one" ✅ Yes Case-insensitive REReplace (PCRE2_CASELESS). Same substitution semantics as REReplace. Verified byte-for-byte against CF 2021 in tests/cfm/refind_re_test.cfm + RegexFunctionsTest.JitRereplace.
RestDeleteApplication ❌ No
RestInitApplication ❌ No
RestSetResponse ❌ No
Reverse string string: string ✅ Yes
Right string string: string, count: numeric ✅ Yes
RJustify string string: string, length: numeric ✅ Yes Right-justifies a string in a given field length
Round numeric number: numeric ✅ Yes
RTrim string string: string ✅ Yes
Second int date: any ✅ Yes
SendGatewayMessage ❌ No
SendSAMLLogoutResponse ❌ No
Serialize string value: any, [type: string] ✅ Yes Wrapper, delegates to SerializeJSON for vars
SerializeJSON string value: any ✅ Yes Compact output (no spaces) matching CF: struct keys emitted with their original casing in ColdFusion's Java-HashMap iteration order (hash bucket of the upper-cased key, insertion order within a bucket, capacity grows 16→32→64…); literal floats keep their text (8.0); computed doubles render like Java Double.toString (1/3 → 0.3333333333333333, 2^53 → 9.007199254740992E15, 10^9 → 1.0E9, 10^-5 → 1.0E-5); Long values serialize as int64 numbers (SerializeJSON(DeserializeJSON("9007199254740993")) → 9007199254740993). Known divergence: CF quotes integer literals beyond int32 (SerializeJSON(9223372036854775807) → "9223372036854775807", a CFBigInteger artifact) while this engine emits them as numbers. Verified against CF 2021 in tests/cfm/json1.cfm, json_struct_order_test.cfm, json_computed_float_test.cfm, tests/cfm/long_type_test.cfm (previously BUGS.md #1)
SerializeXML string xmlNode: xml ✅ Yes
SessionGetMetaData struct () ✅ Yes Returns {STARTTIME: <creation date>} for the current session (the date rendered like CF: August, 04 2026 05:34:21). Verified against CF 2025 in tests/cfm/cfapplication_scope_test.cfm.
SessionInvalidate void () ✅ Yes Clears the session data and deletes the session row, making the current CFID/CFTOKEN invalid; the (now-invalid) cookies stay in the response and the next request mints a fresh session. Verified against CF 2025 in tests/cfm/cfsession_invalidate_test.cfm.
SessionRotate void () ✅ Yes Mints a new CFID/CFTOKEN pair (new cookies), copies the session data into the new session row and removes the old one (anti-session-fixation). Verified against CF 2025 in tests/cfm/cfsession_rotate_test.cfm.
SetDay DateTime date: any, day: int ✅ Yes Member method date.setDay(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm.
SetEncoding void scope_name: string, charset: string ✅ Yes Sets the character encoding of the FORM or URL scope variables; throws "Only form or URL scope is allowed in a SetEncoding() call." for any other scope. The encoding is tracked per request and reflected by GetEncoding (verified against CF 2025 in tests/cfm/tier1_setencoding_test.cfm).
SetHour DateTime date: any, hour: int ✅ Yes Member method date.setHour(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm.
SetLocale string new_locale: string ✅ Yes Sets the current request's locale (28 CF named locales + java locale codes like fr_FR, case-insensitive), returns the previous locale's CF string, sets the Content-Language response header (en-US, ...), and drives GetLocale + all LS* formatting/parsing. An unknown locale throws CF's The locale, X, cannot be found. The locale table (src/locale_table.inc) holds month/day names, short/medium/long/full date+time masks and number/currency data for the 28 CF locales, probed byte-for-byte from the CF 2025 RDS host (old-JVM CLDR, e.g. German short day Fr, Italian time 14.30, NBSP group separators). Verified byte-for-byte against CF 2025 in tests/cfm/setlocale_test.cfm + LocaleTest unit tests. An empty-string locale argument resolves to the English (US) default like CF's CFLocaleMgr (which registers "" → en_US) instead of throwing; whitespace/unknown names still throw. The table also carries the JDK COMPAT parse patterns (pDate*/pTime*) that back the now locale-aware LSParseDateTime/LSIsDate (was BUGS.md #11).
SetMinute DateTime date: any, minute: int ✅ Yes Member method date.setMinute(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm.
SetMonth DateTime date: any, month: int ✅ Yes Member method date.setMonth(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm.
SetProfileString string iniPath: string, section: string, key: string, value: string ✅ Yes Rewrites an INI section/key preserving comment/key casing and section order; returns "". Verified against CF 2025 in tests/cfm/tier2_setprofile_test.cfm.
SetPropertyString void name: string, key: string, value: string (or name, struct) ✅ Yes Writes a Java .properties file (dict form accepts a struct). RDS host permission-rejects the write; implemented in fn_getpropertyfile.cpp.
SetSecond DateTime date: any, second: int ✅ Yes Member method date.setSecond(n) supported. Mutates in place and returns the modified date. Verified against CF 2025 in tests/cfm/date_set_member_methods_test.cfm.
SetVariable void name: string, value: any ✅ Yes Assigns the named variable (dotted paths auto-create nested structs, scope-qualified names like form.x work) and returns the assigned value like CF 2025 (verified against CF 2025 in tests/cfm/tier1_basic_test.cfm + JitExpressionTest.Tier1SetVariable). Reuses cfvariant_assign, which now auto-creates an unknown dotted prefix in the variables scope instead of dropping the first segment.
SetYear DateTime date: any, year: int ✅ Yes Member method date.setYear(n) supported. Mutates in place and returns the modified date. Bare SetYear/SetMonth/SetDay/SetHour/SetMinute/SetSecond calls inside component methods now use component-method precedence before built-in dispatch, while page-level calls retain the two-argument built-in behavior. Verified in tests/cfm/component_bare_date_method_test.cfm and ComponentTest.BareDateMutatorNameResolvesComponentMethod.
Sgn numeric number: numeric ✅ Yes Returns -1, 0, or 1
Sin float number: numeric ✅ Yes
Sleep void duration: numeric ✅ Yes Pauses current thread execution for specified milliseconds
SpanExcluding string string: string, set: string ✅ Yes Returns the leading run of characters not in set
SpanIncluding string string: string, set: string ✅ Yes Returns the leading run of characters in set
SpreadsheetAddAutoFilter ❌ No
SpreadsheetAddColumn ❌ No
SpreadsheetAddFreezePane ❌ No
SpreadsheetAddImage ❌ No
SpreadsheetAddInfo ❌ No
SpreadsheetAddPageBreaks ❌ No
SpreadsheetAddPrintGridlines ❌ No
SpreadsheetAddRow ❌ No
SpreadsheetAddRows ❌ No
SpreadsheetAddSplitPane ❌ No
SpreadsheetCreateSheet ❌ No
SpreadsheetDeleteColumn ❌ No
SpreadsheetDeleteColumns ❌ No
SpreadsheetDeleteRow ❌ No
SpreadsheetDeleteRows ❌ No
SpreadsheetFormatCell ❌ No
SpreadsheetFormatCellRange ❌ No
SpreadsheetFormatColumn ❌ No
SpreadsheetFormatColumns ❌ No
SpreadsheetFormatRow ❌ No
SpreadsheetFormatRows ❌ No
SpreadsheetGetCellComment ❌ No
SpreadsheetGetCellFormula ❌ No
SpreadsheetGetCellValue ❌ No
SpreadsheetGetColumnCount ❌ No
SpreadsheetGetColumnWidth ❌ No
SpreadsheetGetLastRowNumber ❌ No
SpreadsheetGetPrintOrientation ❌ No
SpreadsheetGroupColumns ❌ No
SpreadsheetGroupRows ❌ No
SpreadsheetInfo ❌ No
SpreadsheetisBinaryFormat ❌ No
SpreadsheetisColumnHidden ❌ No
SpreadsheetisRowHidden ❌ No
SpreadsheetisStreamingXmlFormat ❌ No
SpreadsheetisXmlFormat ❌ No
SpreadsheetMergeCells ❌ No
SpreadsheetNew ❌ No
SpreadsheetRead ❌ No
SpreadsheetReadBinary ❌ No
SpreadsheetRemoveColumnBreak ❌ No
SpreadsheetRemovePrintGridlines ❌ No
SpreadsheetRemoveRowBreak ❌ No
SpreadsheetRemoveSheet ❌ No
SpreadsheetRemoveSheetNumber ❌ No
SpreadsheetRenameSheet ❌ No
SpreadsheetSetActiveSheet ❌ No
SpreadsheetSetActiveSheetNumber ❌ No
SpreadsheetSetCellComment ❌ No
SpreadsheetSetCellFormula ❌ No
SpreadsheetSetCellValue ❌ No
SpreadsheetSetColumnBreak ❌ No
SpreadsheetSetColumnHidden ❌ No
SpreadsheetSetColumnWidth ❌ No
SpreadsheetSetFittoPage ❌ No
SpreadsheetSetFooter ❌ No
SpreadsheetSetFooterImage ❌ No
SpreadsheetSetHeader ❌ No
SpreadsheetSetHeaderImage ❌ No
SpreadsheetSetRowBreak ❌ No
SpreadsheetSetRowHeight ❌ No
SpreadsheetSetRowHidden ❌ No
SpreadsheetShiftColumns ❌ No
SpreadsheetShiftRows ❌ No
SpreadsheetUngroupColumns ❌ No
SpreadsheetUngroupRows ❌ No
SpreadsheetWrite ❌ No
Sqr float number: numeric ✅ Yes Square root
StoreAddACL ❌ No
StoreGetACL ❌ No
StoreGetMetadata ❌ No
StoreSetACL ❌ No
StoreSetMetadata ❌ No
StreamingSpreadsheetCleanup ❌ No
StreamingSpreadsheetIsStreamingXmlFormat ❌ No Stub present: throws Function StreamingSpreadsheetIsStreamingXmlFormat is not implemented
StreamingSpreadsheetIsXmlFormat ❌ No Stub present: throws Function StreamingSpreadsheetIsXmlFormat is not implemented
StreamingSpreadsheetNew ❌ No
StreamingSpreadsheetProcess ❌ No
StreamingSpreadsheetRead ❌ No
StripCR string string: string ✅ Yes Removes all carriage-return \r characters, keeps \n
StructAppend boolean destStruct: struct, sourceStruct: struct [, overwriteFlag: boolean = true] ✅ Yes Merges source into dest (default overwrites existing keys), returns YES. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructAppendAndCopy.
StructClear boolean struct: struct ✅ Yes Clears struct in-place
StructCopy struct structure: struct ✅ Yes Returns an independent deep copy of the struct. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructAppendAndCopy.
StructCount numeric struct: struct ✅ Yes
StructDelete boolean struct: struct, key: string, indicateExisting: boolean = false ✅ Yes Also accepts component this scopes as struct-compatible values, matching Adobe CF; verified in tests/cfm/component_structdelete_test.cfm and ComponentTest.StructDeleteAcceptsComponentThisScope.
StructEach void struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Invokes callback(key, value, struct) for each entry in insertion order; returns null. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce.
StructFilter struct struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Returns a new struct with the entries for which the callback returns true (callback args key, value, struct). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm.
StructFind any struct: struct, key: string ✅ Yes
StructFindKey array top: any, value: string [, scope: string = "one"] ✅ Yes Recursively searches nested structs/arrays for a matching key; returns an array of {path, owner, value}. scope="all" returns every match. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructFindKeyAndValue.
StructFindValue array top: any, value: any [, scope: string = "one"] ✅ Yes Recursively searches for struct values equal to value; returns an array of {key, path, owner}. scope="all" returns every match. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructFindKeyAndValue.
StructGet struct path: string ✅ Yes Resolves (creating as needed) nested structs along a dotted path in the variables scope and returns the last struct. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetCreatesPath.
StructGetMetadata struct struct: struct ✅ Yes Returns {ordered, casesensistive, [keys]} — the inherent properties ("unordered"/"NO" for a regular struct) plus the metadata set by StructSetMetadata under keys. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetAndSetMetadata.
StructInsert boolean struct: struct, key: string, value: any, allowOverwrite: boolean = false ✅ Yes
StructIsEmpty boolean struct: struct ✅ Yes
StructKeyArray array struct: struct ✅ Yes
StructKeyExists boolean struct: struct, key: string ✅ Yes
StructKeyList string struct: struct, delimiter: string = "," ✅ Yes
StructMap struct struct: struct, callback: function [, parallel: boolean] [, maxThreads: numeric] ✅ Yes Returns a new struct with each value replaced by callback(key, value, struct). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce.
StructNew struct () ✅ Yes 0 args
StructReduce any struct: struct, callback: function [, initialValue: any] ✅ Yes Reduces the struct: callback(acc, key, value, struct) returns the next accumulator starting from initialValue. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructEachFilterMapReduce.
StructSetMetadata void inputStruct: struct, metaStruct: struct ✅ Yes Attaches metadata to a struct, surfaced by StructGetMetadata under keys; returns null. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructGetAndSetMetadata.
StructSort array struct: struct [, sortType: string] [, sortOrder: string] [, path: string] [, localeSensitive: boolean] or struct: struct, callback: function ✅ Yes Returns the struct's keys (uppercased) sorted by their VALUES (numeric/text/textnocase, asc/desc) or by a comparator callback that receives the values. Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructSortAndToSorted.
StructToSorted struct structure: struct, callback: function or structure: struct, sortType: string [, sortOrder: string] [, localeSensitive: boolean] ✅ Yes Returns a NEW struct with its keys reordered — the text/desc forms sort by key, the callback form sorts by value (matching CF 2021). Verified byte-for-byte against CF 2021 in tests/cfm/higher_order_functions_struct_test.cfm + HigherOrderFunctionsTest.StructSortAndToSorted.
StructUpdate boolean struct: struct, key: string, value: any ✅ Yes
StructValueArray array struct: struct ✅ Yes Returns values of the structure as an array
Tan float number: numeric ✅ Yes
ThreadJoin ❌ No
ThreadTerminate ❌ No
Throw — [message] [, type] [, detail] [, errorcode] [, extendedinfo] ✅ Yes Throws a developer exception (statement form; positional/named attributes, bare throw rethrows). Verified against CF 2025 in tests/cfm/tier2_throw_test.cfm.
TimeFormat string time: any [, mask: string] ✅ Yes
ToBase64 string string_or_object: any, encoding: string = "UTF-8" ✅ Yes Accepts string or binary; standard base64 with padding
ToBinary binary base64_or_object: any ✅ Yes Base64-decodes to binary; accepts binary and returns it unchanged
ToScript string cfvar: any, javascriptvar: string [, outputformat: boolean = true] [, asformat: boolean = false] ✅ Yes Emits one name = value; line per assignment; strings escaped (\\, \', \", \n, \r, \t); arrays/structs/date recursively expanded (month is 0-based); struct keys lowercased; float literal text preserved (ToScript(8.0) → j = 8.0;); outputformat/asformat accepted but ignored (CF 2021 probe shows identical output)
ToString string value: any [, encoding: string = "UTF-8"] ✅ Yes String/Number/Float/Boolean/DateTime pass through; XML objects serialize to XML string (including <?xml version="1.0" encoding="UTF-8"?> header for documents/elements); binary decoded with charset (default UTF-8); other complex objects throw; float literals keep their literal text (ToString(8.0) → 8.0); computed floats still use shortest-double (to_chars general), not CF's %.12g, except NaN which renders as the single U+FFFD character like CF (ToString((-2)^0.5) → U+FFFD, Len()==1); booleans follow the literal/computed distinction — ToString(true) → true, ToString((5 GT 3)) → YES
Trace void [var] [, text] [, type] [, category] [, inline] [, abort] ✅ Yes No-op when debugging is disabled (the RDS host default and this engine, which has no debug section); abort does not fire. Verified against CF 2025 in tests/cfm/tier2_trace_test.cfm.
TransactionCommit void () ✅ Yes Commits the current <cftransaction> (delegates to the transaction runtime); outside an active transaction throws CF's "An active transaction is required before calling any transaction method." (verified against CF 2025 in tests/cfm/tier1_error_test.cfm + JitExpressionTest.Tier1TransactionFunctions).
TransactionRollback void savepoint: string ✅ Yes Rolls back the current <cftransaction>; with a savepoint name rolls back to that savepoint (TransactionSetSavePoint), without one ends the whole transaction. Outside an active transaction throws CF's active-transaction error; an unknown savepoint throws CF's "Specified savepoint does not exist or is invalid." Verified against CF 2025 in tests/cfm/tier1_error_test.cfm + tier2_transactionsetsavepoint_test.cfm + JitExpressionTest.Tier1TransactionFunctions.
TransactionSetSavePoint void savepoint: string ✅ Yes Names a savepoint in the active transaction (sqlite SAVEPOINT); transactionRollback(name) undoes everything after it. Outside a transaction throws CF's active-transaction error. Verified against CF 2025 in tests/cfm/tier2_transactionsetsavepoint_test.cfm.
Trim string string: string ✅ Yes
UCase string string: string ✅ Yes
URLDecode string urlencodedstring: string [, charset: string = "UTF-8"] ✅ Yes '+' becomes space; %XX (case-insensitive) decoded as bytes; UTF-8 by default with U+FFFD replacement for invalid sequences; malformed % passes through literally
URLEncodedFormat string String: string [, charset: string = "UTF-8"] ✅ Yes Percent-encodes everything except ASCII alphanumerics (space becomes %20); charset argument respected
URLSessionFormat string url: string ✅ Yes Appends ?CFID=..&CFTOKEN=.. (or & when the URL already has a query string) when a fresh session was minted in the current request; a request that carried a valid session renders the URL unchanged (verified on CF 2025). The appended CFID value differs per server run by design.
Val numeric str: string ✅ Yes Manual scan [ws][sign]digits[.digits]; no exponent ("1e3"→1); integral results return Number, fractional return Float; Boolean→0; >INT32 range→Float
ValueList string column: query.column [, delimiter: string = ","] ✅ Yes Returns a delimited list of a query column's values. ValueList(q.a) receives the column as an array (our q.a member access returns the column array). Null cells contribute an empty item, keeping the delimiter ([null,"z"] → ",z", verified against CF 2021). An empty query returns an empty string. A non-column argument throws. Verified in tests/cfm/valuelist1.cfm + QueryMutationTest.
VerifyClient void () ✅ Yes Validates client request against _cf_clientid in URL or FORM scope. When session management is enabled, checks client ID against uppercase MD5 hash of urltoken. Throws ApplicationException ("Client verification failure.", detail: "You must have a valid login to access this page.") if missing or mismatched. Verified against CF 2025 in tests/cfm/verifyclient_test.cfm.
Week int date: any ✅ Yes
Wrap string string: string, limit: numeric, strip: boolean = false ✅ Yes Wraps text so lines do not exceed limit
WriteDump string variable: any [, output: string] [, format: string = "html"] [, abort: boolean] [, label: string] [, metainfo: boolean] [, top: numeric] [, show: string] [, hide: string] [, keys: numeric] [, expand: boolean] [, showUDFs: boolean] ✅ Yes Byte-matched against CF 2021: once-per-page STYLE block before the first HTML dump, HTML table templates, HTML 4.0 named-entity escaping, %.12g float formatting with 3-digit exponent, label propagation to nested containers, keys/show/hide/top filters, format="text" (
 output), abort throws page abort after emitting, float literal text preserved (cfdump of 8.10 shows 8.10), struct keys display with their original casing (case-insensitive storage). UDF/closure values expand into table.cfdump_udf (function/closure name, Arguments table with Name/Required/Type/Default, ReturnType, Access, Static and empty metadata rows) in both HTML and text dumps — metadata (param names/types, de-quoted default literals, return type, access) is carried from the compiler into UDFInfo via a serialized blob (was BUGS.md: cfdump of a UDF rendered an empty cell). Byte-for-byte against CF 2021, including CF's leading-space quirk (first UDF of the first dump and the first UDF at each nesting depth ≥ 2 carry a space). Verified in tests/cfm/cfdump_udf_test.cfm + WriteDumpTest.Udf*/UdfMetaTest unit tests
WriteLog string text: string [, type: string = "Information"] [, application: string] [, file: string] [, log: string] ✅ Yes Appends CSV rows to /var/log/webstrada/.log (default application.log; env override WEBSTRADA_LOG_DIR); header row written only for new files; severity Information/Warning/Error/Fatal (unknown types fall back to Information); all fields quoted, embedded quotes doubled; date MM/DD/YY and time HH:MM:SS local; returns empty string
WriteOutput ✅ Yes
WSGetAllChannels ❌ No
WSGetSubscribers ❌ No
WSPublish ❌ No
WSSendMessage ❌ No
XmlChildPos numeric xmlNode: xml, childName: string, position: numeric ✅ Yes Returns 1-based position of nth child
XmlElemNew xml xmlDoc: xml, name: string ✅ Yes Creates new element
XmlFormat string string: string ✅ Yes Escapes XML special chars
XmlGetNodeType string xmlNode: xml ✅ Yes Returns CF DOM node type names (ELEMENT_NODE, DOCUMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE, ...)
XmlNew xml [caseSensitive: boolean] ✅ Yes Creates new empty XML document
XmlParse xml xmlString: string [, caseSensitive: boolean [, validator: string]] ✅ Yes Parses XML string into document object; a parse failure throws CF's An error occurred while Parsing an XML document. Expression error (same message as <cfxml>; previously it reported its own "XmlParse: Failed to parse XML markup" wording) — verified against CF 2025 in tests/cfm/xmlparse_error_test.cfm + CfXmlTagTest.XmlParseErrorMessageMatchesCf
XmlSearch array xmlNode: xml, xpath: string ✅ Yes XPath search, returns array of nodes
XmlTransform string xml: any, xsl: string ✅ Yes XSLT transform
XmlValidate struct xml: any [, schema: string] ✅ Yes Validates XML, returns struct with status/errors; without a schema status is NO (not validated), matching CF 2021
Year int date: any ✅ Yes
YesNoFormat string value: any ✅ Yes