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 = {} }
end
-- Protected token collection
local tokens = {}
local lines = {}
for line in doc.content:gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
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
local ok, err = pcall(function()
local lines = {}
for line in doc.content:gmatch("([^\n]*)\n") do
table.insert(lines, line)
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
local total_lines = #lines
local function add_token(line, startChar, length)
-- Validate all parameters
if not line or not startChar or not length then
return
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
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
local lineNum = i - 1
local trimmed = line:gsub("^%s+", "")
for i, line in ipairs(lines) do
local lineNum = i - 1
local trimmed = line:gsub("^%s+", "")
if doc.variables then
for var_name, info in pairs(doc.variables) do
-- Validate variable line number is within bounds
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)
if startChar then
add_token(lineNum, startChar - 1, #var_name, "variable")
if doc.variables then
for var_name, info in pairs(doc.variables) do
if info and info.line and info.line >= 0 and info.line < total_lines and info.line == lineNum then
local startChar = line:find(var_name, 1, true)
if startChar then
add_token(lineNum, startChar - 1, #var_name)
end
end
end
end
end
local keywords = {
"IF",
"THEN",
"ELSIF",
"ELSE",
"END_IF",
"FOR",
"TO",
"BY",
"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
local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
if startPos then
add_token(lineNum, startPos - 1, #kw, "keyword")
local keywords = {
"IF", "THEN", "ELSIF", "ELSE", "END_IF",
"FOR", "TO", "BY", "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
local startPos = line:find("%f[%a]" .. kw .. "%f[%A]")
if startPos then
add_token(lineNum, startPos - 1, #kw)
end
end
end
end)
-- If any error occurred, return empty tokens
if not ok then
return { data = {} }
end
return { data = tokens }