Skip to content

Commit 530a432

Browse files
bpamiriclaude
andcommitted
fix: avoid application-scoped array access in constraint helpers
Work exclusively with variables.routes (Mapper's internal copy) instead of reading from application[key].routes. Adobe CF returns application-scoped array elements by value, causing "dereference scalar as struct" errors on chained member access. Sync back to application scope with a single array assignment at the end. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3d931bd commit 530a432

File tree

1 file changed

+19
-43
lines changed

1 file changed

+19
-43
lines changed

vendor/wheels/mapper/matching.cfc

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -528,16 +528,12 @@ component {
528528
* Supports comma-delimited variable names to constrain multiple variables at once.
529529
*/
530530
public struct function $applyConstraintToLastRoute(required string variableName, required string pattern) {
531-
// Cache the application key and routes array reference to avoid repeated
532-
// dynamic key resolution via $appKey(). On Adobe CF, chained expressions
533-
// like application[$appKey()].routes[i] can cause unexpected runtime errors
534-
// when accessing array elements from application-scoped structs.
535-
local.appKey = "wheels";
536-
if (StructKeyExists(application, "$wheels")) {
537-
local.appKey = "$wheels";
538-
}
539-
local.appRoutes = application[local.appKey].routes;
540-
local.routeCount = ArrayLen(local.appRoutes);
531+
// Work exclusively with variables.routes (the Mapper's internal copy) to
532+
// avoid Adobe CF runtime errors. Adobe CF returns application-scoped array
533+
// elements by value, and chained expressions like
534+
// application[key].routes[i].member cause "dereference scalar as struct"
535+
// errors. After modifying routes here, we sync back to application scope.
536+
local.routeCount = ArrayLen(variables.routes);
541537
if (local.routeCount == 0) {
542538
Throw(
543539
type = "Wheels.NoRouteToConstrain",
@@ -548,7 +544,7 @@ component {
548544
// Apply constraint to the last route (and its optional-segment variants).
549545
// When optional segments are used, $match adds multiple routes. We apply the
550546
// constraint to all routes that share the same name as the last one.
551-
local.lastRoute = local.appRoutes[local.routeCount];
547+
local.lastRoute = variables.routes[local.routeCount];
552548
local.lastRouteName = StructKeyExists(local.lastRoute, "name") ? local.lastRoute.name : "";
553549

554550
local.variables = ListToArray(arguments.variableName);
@@ -557,7 +553,7 @@ component {
557553

558554
// Walk backward through routes to find all variants of the same named route.
559555
for (local.i = local.routeCount; local.i >= 1; local.i--) {
560-
local.route = local.appRoutes[local.i];
556+
local.route = variables.routes[local.i];
561557
local.routeName = StructKeyExists(local.route, "name") ? local.route.name : "";
562558

563559
// Stop if we've gone past the related routes.
@@ -575,46 +571,26 @@ component {
575571
// Recompile the regex with the new constraint.
576572
local.route.regex = $patternToRegex(local.route.pattern, local.route.constraints);
577573

578-
// Explicit write-back: on Adobe CF, array elements from the
579-
// application scope are returned by value. Write the modified
580-
// struct back directly to the application-scoped array to
581-
// ensure changes persist (not via a local reference which may
582-
// be a copy on some engines).
583-
application[local.appKey].routes[local.i] = local.route;
574+
// Write back to the internal array (Adobe CF returns array
575+
// elements by value, so the local copy must be written back).
576+
variables.routes[local.i] = local.route;
584577
}
585578

586579
// If no name, only update the very last route.
587580
if (!Len(local.lastRouteName)) {
588581
break;
589582
}
590583
}
584+
}
591585

592-
// Also update the internal routes array.
593-
local.internalCount = ArrayLen(variables.routes);
594-
for (local.i = local.internalCount; local.i >= 1; local.i--) {
595-
local.route = variables.routes[local.i];
596-
local.routeName = StructKeyExists(local.route, "name") ? local.route.name : "";
597-
598-
if (Len(local.lastRouteName) && local.routeName != local.lastRouteName && local.i < local.internalCount) {
599-
break;
600-
}
601-
602-
if (Find("[#local.varName#]", local.route.pattern)) {
603-
if (!StructKeyExists(local.route, "constraints")) {
604-
local.route.constraints = {};
605-
}
606-
local.route.constraints[local.varName] = arguments.pattern;
607-
local.route.regex = $patternToRegex(local.route.pattern, local.route.constraints);
608-
609-
// Explicit write-back for Adobe CF safety (see comment above).
610-
variables.routes[local.i] = local.route;
611-
}
612-
613-
if (!Len(local.lastRouteName)) {
614-
break;
615-
}
616-
}
586+
// Sync modified routes back to application scope. We replace the entire
587+
// array to avoid per-element access on application-scoped arrays, which
588+
// triggers Adobe CF's "dereference scalar as struct" errors.
589+
local.appKey = "wheels";
590+
if (StructKeyExists(application, "$wheels")) {
591+
local.appKey = "$wheels";
617592
}
593+
application[local.appKey].routes = variables.routes;
618594

619595
return this;
620596
}

0 commit comments

Comments
 (0)