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
+35 -36
View File
@@ -563,7 +563,9 @@ function handlers.textDocument_semanticTokensFull(params)
return { data = {} } return { data = {} }
end end
-- Protected token collection
local tokens = {} local tokens = {}
local ok, err = pcall(function()
local lines = {} local lines = {}
for line in doc.content:gmatch("([^\n]*)\n") do for line in doc.content:gmatch("([^\n]*)\n") do
table.insert(lines, line) table.insert(lines, line)
@@ -571,11 +573,21 @@ function handlers.textDocument_semanticTokensFull(params)
local total_lines = #lines local total_lines = #lines
local function add_token(line, startChar, length, tokenType) local function add_token(line, startChar, length)
-- Validate line number is within bounds -- Validate all parameters
if not line or not startChar or not length then
return
end
if line < 0 or line >= total_lines then if line < 0 or line >= total_lines then
return return
end end
if startChar < 0 or length <= 0 then
return
end
if startChar + length > 10000 then
return
end
local deltaLine = line local deltaLine = line
local deltaStart = startChar local deltaStart = startChar
if #tokens >= 4 then if #tokens >= 4 then
@@ -587,6 +599,10 @@ function handlers.textDocument_semanticTokensFull(params)
else else
deltaLine = line - prevLine deltaLine = line - prevLine
end end
-- Validate deltaStart is non-negative
if deltaStart < 0 then
return
end
end end
table.insert(tokens, deltaLine) table.insert(tokens, deltaLine)
table.insert(tokens, deltaStart) table.insert(tokens, deltaStart)
@@ -601,55 +617,38 @@ function handlers.textDocument_semanticTokensFull(params)
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, "variable") add_token(lineNum, startChar - 1, #var_name)
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",
"WHILE",
"END_WHILE",
"REPEAT",
"UNTIL",
"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 for _, kw in ipairs(keywords) do
local startPos = line:find("%f[%a]" .. kw .. "%f[%A]") local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
if startPos then if startPos then
add_token(lineNum, startPos - 1, #kw, "keyword") add_token(lineNum, startPos - 1, #kw)
end end
end end
end end
end)
-- If any error occurred, return empty tokens
if not ok then
return { data = {} }
end
return { data = tokens } return { data = tokens }
end end