lua死循检查

内容纲要

从此向讨人厌的lua死循环说goodbye 🙂

容易查的死循环

像编译型的语言,如c、c++、c#、java一类的,有不错的IDE工具,再用debug模式编译时会把一些debug的信息也编译到执行文件中,借助强大的工具,很容易就可以找出死循环的位置,比如以VS下开发unity为例,当出现死循环时按以下步骤即可找出。
attach上unity进程,运行程序,走到死循环后会卡死,这时打开“调试/窗口/线程"菜单,然后点中断

这时线程窗口会显示执行的线程的调用栈

不好查的死循环

用lua写的程序中要是出现了死循环,一般的表现就是程序卡死或者程序未响应,而你能做的只是静静的看着它静止在哪里,或者打开任务管理器把程序给关掉。由于lua是个脚本语言,调试的工具不是太多,所以定位哪里出现死循环还是比较麻烦的。如果你凭直觉去找可能找半天也不一定能找出来。这时你会
想要是有个工具能再出现死循环的时候直接报错改多好哇!
美梦有时会成真滴,下面的代码让你从此告别死循环。
实现思路其实很简单也很朴素:统计每个循环执行的次数,如果超过一个自己设定次数比如99999次,
就自动抛错。关键是咋实现循环次数的统计呢,难到要向每个循环里都插入计数的代码吗?you are right!
有句话不知阁下听说过没

计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决
Any problem in computer science can be solved by anther layer of indirection

咱们就在读lua文件的时候加了一层,着一层的作用就是把原始的lua文件处理一下插入循环的计数代码,
用到的技术无非就是字符串查找替换,各位看官请看吧!

_G._LM = 99999
_G.LOOPDIE = function( )
    Log.fatal( '---LOOP is enter die---_LM=', _LM, debug.traceback( ) )
end
local split = function( s, delimiter )
    if ''== s then return { } end
    local t, i, j, k = { }, 1, 1, 1
    while i <= #s+1 do
        j, k = string:find(s, delimiter, i)
        j, k = j or #s+1, k or #s+1
        t[#t+1] = string:sub(s, i, j-1)
        i = k + 1
    end
    return t
end

_G.CheckLoop = function( cont, file )
    -- if not CHECKLOOP then return cont end
    local data = cont
    local replaceFor = ' local _N = 0 for '
    local replaceDo = ' do _N = _N + 1 if _N == _LM then LOOPDIE() end '
    local replaceWhile = ' local _N=1 while '
    local newdata = { }
    local t = split( data, '\n' )
    for line = 1, #t do
        local linestr = t[line]
        --for循环
        local countfor = 0
        local prefixfor = false
        local forflag = false
        local whileflag = false
        if string.find(linestr, '^for%s+') then
            countfor = countfor + 1
            prefixfor = true
        end
        for s in string.gfind( linestr, '%s+for%s+' ) do
            countfor = countfor + 1
            prefixfor = false
            if( countfor > 1 ) then
                error('please standard to for cause is one line have one for!!!!'..file.. ' line'..line)
            end
        end
        local countdo = 0
        local suffixdo = false
        if string.find(linestr, '(%s+do)$') then
            countdo = countdo + 1
            suffixdo = true
        end
        for s in string.gfind( linestr, '%s+do%s+' ) do
            countdo = countdo + 1
            suffixdo = false
            if( countdo > 1 ) then
                error('please standard to for cause is one line have one do!!!!'..file.. ' line'..line)
            end
        end

        if( countfor == 1 and countdo == 1 ) then
            local newlinestr = ''
            if prefixfor then
                newlinestr = string.gsub( linestr, '^for%s+', replaceFor)
            else
                newlinestr = string.gsub( linestr, '%s+for%s+', replaceFor )
            end
            if suffixdo then
                newlinestr = string.gsub( newlinestr, '%s+do$', replaceDo )
            else
                newlinestr = string.gsub( newlinestr, '%s+do%s+', replaceDo )
            end
            newdata[ #newdata + 1 ] = newlinestr
            forflag = true
        end

        --while循环
        local countwhile = 0
        local prefixwhile = false
        if string.find(linestr, '^while%s+') or string.find(linestr, '^%s*while%(') then
            countwhile = countwhile + 1
            prefixwhile = true
        end
        for s in string.gfind(linestr, '%s+while%s+') do
            countwhile = countwhile + 1
            prefixwhile = false
            if countwhile > 1 then
                error('please standard to while cause is one line have one while!!!'..file..' line'..line)
            end
        end

        if countfor == 1 and countwhile == 1 then
            error('please make sure for loop and while loop only one!!!!'..file..' line'..line)
        end

        if countwhile == 1 and countdo == 1 then
            local newlinestr = ''
            if prefixwhile then
                newlinestr = string.gsub(linestr, '^%s*while', replaceWhile)
            else
                newlinestr = string.gsub(linestr, '%s+while%s+', replaceWhile)
            end
            if suffixdo then
                newlinestr = string.gsub(newlinestr, '%s+do$', replaceDo)
            else
                newlinestr = string.gsub(newlinestr, '%s+do%s+', replaceDo)
            end
            newdata[ #newdata + 1 ] = newlinestr
            whileflag = true
        end
        if not forflag and not whileflag then
            newdata[ #newdata + 1 ] = linestr
        end
    end
    newdata = table.concat( newdata, '\n' )
    return newdata
end

local s = [[ 
for i = 1, 5 do 
    for j = 1, 5 do 
        print(i..j)
    end     
end 
]]

s = CheckLoop(s)
-- dump(s)

local ret, errmsg = loadstring(s, 'error_msg' )
if not ret then
    error(errmsg)
    return
end
ret()