fix(lsp): add more robust validation in semantic tokens

- Wrap token generation in pcall to catch any errors
- Add validation for all token parameters (line, startChar, length)
- Validate deltaStart is non-negative
- Return empty tokens if any error occurs
- This should prevent the 'Invalid line: out of range' errors
This commit is contained in:
2026-02-19 12:01:33 +01:00
parent 1808edc1f0
commit d94cb62e0b
+73 -74
View File
@@ -563,92 +563,91 @@ function handlers.textDocument_semanticTokensFull(params)
return { data = {} } return { data = {} }
end end
-- Protected token collection
local tokens = {} local tokens = {}
local lines = {} local ok, err = pcall(function()
for line in doc.content:gmatch("([^\n]*)\n") do local lines = {}
table.insert(lines, line) for line in doc.content:gmatch("([^\n]*)\n") do
end table.insert(lines, line)
local total_lines = #lines
local function add_token(line, startChar, length, tokenType)
-- Validate line number is within bounds
if line < 0 or line >= total_lines then
return
end end
local deltaLine = line
local deltaStart = startChar local total_lines = #lines
if #tokens >= 4 then
local prevLine = tokens[#tokens - 3] local function add_token(line, startChar, length)
local prevStart = tokens[#tokens - 2] -- Validate all parameters
if line == prevLine then if not line or not startChar or not length then
deltaLine = 0 return
deltaStart = startChar - prevStart
else
deltaLine = line - prevLine
end end
if line < 0 or line >= total_lines then
return
end
if startChar < 0 or length <= 0 then
return
end
if startChar + length > 10000 then
return
end
local deltaLine = line
local deltaStart = startChar
if #tokens >= 4 then
local prevLine = tokens[#tokens - 3]
local prevStart = tokens[#tokens - 2]
if line == prevLine then
deltaLine = 0
deltaStart = startChar - prevStart
else
deltaLine = line - prevLine
end
-- Validate deltaStart is non-negative
if deltaStart < 0 then
return
end
end
table.insert(tokens, deltaLine)
table.insert(tokens, deltaStart)
table.insert(tokens, length)
table.insert(tokens, 0)
table.insert(tokens, 0)
end end
table.insert(tokens, deltaLine)
table.insert(tokens, deltaStart)
table.insert(tokens, length)
table.insert(tokens, 0)
table.insert(tokens, 0)
end
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
local lineNum = i - 1 local lineNum = i - 1
local trimmed = line:gsub("^%s+", "") local trimmed = line:gsub("^%s+", "")
if doc.variables then if doc.variables then
for var_name, info in pairs(doc.variables) do for var_name, info in pairs(doc.variables) do
-- Validate variable line number is within bounds if info and info.line and info.line >= 0 and info.line < total_lines and info.line == lineNum then
if info.line and info.line >= 0 and info.line < total_lines and info.line == lineNum then local startChar = line:find(var_name, 1, true)
local startChar = line:find(var_name, 1, true) if startChar then
if startChar then add_token(lineNum, startChar - 1, #var_name)
add_token(lineNum, startChar - 1, #var_name, "variable") end
end end
end end
end end
end
local keywords = { local keywords = {
"IF", "IF", "THEN", "ELSIF", "ELSE", "END_IF",
"THEN", "FOR", "TO", "BY", "DO", "END_FOR",
"ELSIF", "WHILE", "END_WHILE",
"ELSE", "REPEAT", "UNTIL", "END_REPEAT",
"END_IF", "CASE", "OF", "END_CASE",
"FOR", "VAR_INPUT", "VAR_OUTPUT", "VAR_IN_OUT", "VAR_TEMP", "VAR", "END_VAR",
"TO", "BEGIN", "EXIT", "CONTINUE", "RETURN",
"BY", "TRUE", "FALSE",
"DO", }
"END_FOR", for _, kw in ipairs(keywords) do
"WHILE", local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
"END_WHILE", if startPos then
"REPEAT", add_token(lineNum, startPos - 1, #kw)
"UNTIL", end
"END_REPEAT",
"CASE",
"OF",
"END_CASE",
"VAR_INPUT",
"VAR_OUTPUT",
"VAR_IN_OUT",
"VAR_TEMP",
"VAR",
"END_VAR",
"BEGIN",
"EXIT",
"CONTINUE",
"RETURN",
"TRUE",
"FALSE",
}
for _, kw in ipairs(keywords) do
local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
if startPos then
add_token(lineNum, startPos - 1, #kw, "keyword")
end end
end end
end)
-- If any error occurred, return empty tokens
if not ok then
return { data = {} }
end end
return { data = tokens } return { data = tokens }