This code looks innocent enough, but it might not be.
local tree = workspace:FindFirstChild("Tree") local branch = tree:FindFirstChild("Branch") -- We know there is a branch Part in the tree here local branchColor = branch.Color
The takeaway is this: You should be making separate folders inside an
Instance for anything that will ever be moved or destroyed without its parent. If this isn’t doable yet, at least use a
Folder for character models.
All credit goes to the original discovery by @jakedies. I made this post only to add visibility, make the recommendation obvious for beginners, highlight the most dangerous interaction, and explicitly mention that indexing is impacted.
Children of a parent are normally ordered based on when they were parented to it, with the children parented earlier coming before the ones parented later. This is why indexing
workspace.Tree gives you the tree you inserted in Studio, and not the character model of the played named “Tree”. When a parent has more than 20 children and one is moved or destroyed, it is removed through a “fast removal” roughly similar to this Lua code:
local function fastRemove(children, index) children[index] = children[#children] children[#children] = nil end
This means that a child parented to some
Instance later can be ordered before one that was parented earlier.
This applies everywhere:
- Child returned by
By default, a character model’s name is the player’s name, and it is parented to
Workspace. This issue means that if any code moves or destroys any direct child of
Workspace, future accesses to a child of
Workspace, even one with a different name, can give you a character model instead. This means that bad actors can rename themselves to the names of other
Workspace children to cause your game to error.
local firstChild = Instance.new("Model", workspace) for index = 1, 20 do Instance.new("Model", workspace) end local targetValue = Instance.new("BoolValue") targetValue.Name = "TargetValue" targetValue.Parent = workspace -- We expect to find targetValue when we do workspace:FindFirstChild("TargetValue"), not this local fakeTargetValue = Instance.new("Model") fakeTargetValue.Name = "TargetValue" fakeTargetValue.Parent = workspace -- This will cause fakeTargetValue to be swapped in child order with the first model we added firstChild:Destroy() -- Changing this to workspace.TargetValue won't help local accessedInstance = workspace:FindFirstChild("TargetValue") -- accessedInstance will be fakeTargetValue, and this will error! local value = accessedInstance.Value
“Properly” removing a child would be too expensive, so the core behavior here is unlikely to change. Roblox could eliminate the easiest to abuse case by adding a special
Folder for characters to be parented under in
Workspace, which would also have prevented the close call with the sibling instance order change.