Reproduction Steps
When a tag name contains a null character, the name is truncated:
CollectionService:AddTag(instance, "ABC\0DEF")
print(CollectionService:HasTag(instance, "ABC\0DEF")) --> false
print(CollectionService:HasTag(instance, "ABC")) --> true
To ensure correctness, an error should be thrown instead.
Furthermore, if a tag is truncated, it can be added to an instance multiple times:
CollectionService:AddTag(instance, "A\0")
CollectionService:AddTag(instance, "A\0")
CollectionService:AddTag(instance, "A\0")
print(CollectionService:GetTags(instance)
--> {
-- [1] = "A",
-- [2] = "A",
-- [3] = "A"
-- }
These duplicate tags are saved within serialized formats. When decoding tags, duplicates should be collapsed down to one.
The following script reproduces the issue:
local CollectionService = game:GetService("CollectionService")
-- Call GetTags, expect result to be equivalent to `want`.
local function passTags(inst, want)
local got = CollectionService:GetTags(inst)
if #got == #want then
local ok = true
for i, v in ipairs(want) do
if got[i] ~= v then
ok = false
break
end
end
if ok then
return
end
end
local line = debug.info(2, "l")
print(string.format("line %d: tags: {%s} ~= {%s}", line, table.concat(got,", "), table.concat(want,", ")))
end
-- Call AddTag, expect no error.
local function passAdd(inst, tag)
local ok, err = pcall(CollectionService.AddTag, CollectionService, inst, tag)
if ok then
return
end
local line = debug.info(2, "l")
print(string.format("line %d: add %q: %s", line, tag, tostring(err)))
end
-- Call AddTag, expect error.
local function failAdd(inst, tag)
local ok = pcall(CollectionService.AddTag, CollectionService, inst, tag)
if not ok then
return
end
local line = debug.info(2, "l")
print(string.format("line %d: add %q: expected error", line, tag))
end
---- Tests
local inst = Instance.new("Folder")
passTags(inst, {})
passAdd(inst, "A")
passAdd(inst, "A")
passTags(inst, {"A"})
passAdd(inst, "B")
passTags(inst, {"A", "B"})
failAdd(inst, "Z\0")
passTags(inst, {"A", "B"})
failAdd(inst, "Z\0")
passTags(inst, {"A", "B"})
failAdd(inst, "Z\0")
passTags(inst, {"A", "B"})
passAdd(inst, "C")
passTags(inst, {"A", "B", "C"})
Expected Behavior
The script prints nothing, indicating no assertions have failed.
Actual Behavior
The following failed assertions are printed:
line 51: add "Z\000": expected error
line 52: tags: {A, B, Z} ~= {A, B}
line 54: add "Z\000": expected error
line 55: tags: {A, B, Z, Z} ~= {A, B}
line 57: add "Z\000": expected error
line 58: tags: {A, B, Z, Z, Z} ~= {A, B}
line 61: tags: {A, B, Z, Z, Z, C} ~= {A, B, C}
Issue Area: Engine
Issue Type: Other
Impact: Low
Frequency: Constantly
Date First Experienced: 2022-04-06
Date Last Experienced: 2022-04-22