Skip to content

Commit 9e7bf40

Browse files
bpamiriclaude
andcommitted
fix: use Duplicate() for safe route access in constraint helpers
Deep-copy the routes array via Duplicate() before traversal to create a fully independent local copy. This avoids both the Adobe CF "dereference scalar as struct" error on application-scoped array access AND potential mixin scope issues where variables.routes may not reference the Mapper's internal routes array. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 530a432 commit 9e7bf40

File tree

1 file changed

+22
-19
lines changed

1 file changed

+22
-19
lines changed

vendor/wheels/mapper/matching.cfc

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -528,12 +528,19 @@ 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-
// 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);
531+
// Resolve the application key for the Wheels scope.
532+
local.appKey = "wheels";
533+
if (StructKeyExists(application, "$wheels")) {
534+
local.appKey = "$wheels";
535+
}
536+
537+
// Deep-copy the routes array into a fully independent local variable.
538+
// On Adobe CF, accessing application-scoped array elements by chained
539+
// bracket/dot notation (e.g., application[key].routes[i].member) causes
540+
// "dereference scalar as struct" errors. Duplicate() creates a plain local
541+
// copy that is safe to traverse on all CFML engines.
542+
local.routes = Duplicate(application[local.appKey].routes);
543+
local.routeCount = ArrayLen(local.routes);
537544
if (local.routeCount == 0) {
538545
Throw(
539546
type = "Wheels.NoRouteToConstrain",
@@ -544,7 +551,7 @@ component {
544551
// Apply constraint to the last route (and its optional-segment variants).
545552
// When optional segments are used, $match adds multiple routes. We apply the
546553
// constraint to all routes that share the same name as the last one.
547-
local.lastRoute = variables.routes[local.routeCount];
554+
local.lastRoute = local.routes[local.routeCount];
548555
local.lastRouteName = StructKeyExists(local.lastRoute, "name") ? local.lastRoute.name : "";
549556

550557
local.variables = ListToArray(arguments.variableName);
@@ -553,7 +560,7 @@ component {
553560

554561
// Walk backward through routes to find all variants of the same named route.
555562
for (local.i = local.routeCount; local.i >= 1; local.i--) {
556-
local.route = variables.routes[local.i];
563+
local.route = local.routes[local.i];
557564
local.routeName = StructKeyExists(local.route, "name") ? local.route.name : "";
558565

559566
// Stop if we've gone past the related routes.
@@ -571,9 +578,8 @@ component {
571578
// Recompile the regex with the new constraint.
572579
local.route.regex = $patternToRegex(local.route.pattern, local.route.constraints);
573580

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;
581+
// Write back to the local array copy.
582+
local.routes[local.i] = local.route;
577583
}
578584

579585
// If no name, only update the very last route.
@@ -583,14 +589,11 @@ component {
583589
}
584590
}
585591

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";
592-
}
593-
application[local.appKey].routes = variables.routes;
592+
// Replace both the application-scoped and internal routes arrays with
593+
// the modified copy. A single array assignment avoids per-element
594+
// application-scope access that triggers Adobe CF runtime errors.
595+
application[local.appKey].routes = local.routes;
596+
variables.routes = local.routes;
594597

595598
return this;
596599
}

0 commit comments

Comments
 (0)