js 死循环代码debug

最近在学习vuejs设计与实现的编译器

忧虑

在代码里面有大量的while这样的状态机,一不小心就会出现死循环。这让我非常的忧虑。


我当下的问题是:

  • 如何在遇到死循环时候,及时中断
  • 如何在遇到是循环的时候,可以定位到时那个函数出问题
  • 如何在不影响原代码【逻辑】的情况下(非不新增代码,而是不影响原来代码的逻辑),中断改死循环

我在想一个问题

        能否可以捕获到【死循环】。在检测到有死循环的时候,主动中断程序,并报告当前的死循环的程序的函数的名字。

        这里的捕获是在不影响原代码的情况下,可以监听到死循环,并主动中断,并暴露死循环的函数名字。

        当时经过几次尝试,例如给程序再包裹一个函数或注入代码,我发现好像都有些不大如意的地方。

        嗯,那如果抽离这个检测死循环的代码到独立函数,且让有while的程序自己调用,是否就可以一定层度的减少心智压力以及更好的debug呢?

函数名字

首先,我们需要得到运行函数的名字,js并没有提供这样的功能,但是函数的arguments.callee却可以拿到整个函数内容的文本

例如

function a(){
//dosome
 console.log(arguments.callee.toString())
}

执行代码a()后打印出来的内容是

得到的就是字符串

'function a(){
//dosome
 console.log(arguments.callee.toString())
}'

弊端

但是这有个弊端,如果代码是匿名函数,就拿不到函数名字,例如下面这样的代码,虽然复制给了变量a,但是函数本身是匿名函数

var a=function (){
//dosome
 console.log(arguments.callee.toString())
}

因为函数是个匿名函数,得到的字符串如下,这种情况就需要用户自己传递过来了

'function(){
//dosome
 console.log(arguments.callee.toString())
}'

正则

那我们可以使用正则拿到函数名字

let str = arguments.callee.toString();

var re = /^function\s*(\w+)\(/ig;

var matches = re.exec(str);

let fnName = matches[1];



死循环的特征是什么?


一个函数一直的不断的执行!!

如何判断呢?


如果在n秒内不断的执行某个函数那就可以认为他是死循环
我们可以用两个时间的间隔来判断这个函数执行了多久,如果大于某个时间,就让他break;


分解


那确定了方案后开始,对这个抽离的函数进行分解

  • 需要维护开始时间,结束时间
  • 需要更新结束时间
  • 需要为何死亡时间
  • 需要提供判断是否时死循环的函数


实现代码如下:
 

  var deadLoopCatch = function(argument, runFnName, deadLoopTimeLimit = 5000) {
            let fnName;
            if (runFnName) {
                fnName = runFnName;
            } else {
                let str = argument.callee.toString();
                var re = /^function\s*(\w+)\(/ig;
                var matches = re.exec(str);
                if (matches && matches[1]) {
                    fnName = matches[1];
                } else {
                    //如果是匿名函数,且用户没有传递第二个参数的时候,警告
                    console.warn("When the loop is an anonymous function, please pass the second parameter for function deadLoopCatch")
                    fnName = "---UMKNOW--";
                }


            }
            //初始化开始时间和结束时间
            let startTime = endTime = new Date().getTime();
            let result = {
                fnName,
                _startTime: startTime,
                _endTime: endTime,
                _deadLoopTimeLimit: deadLoopTimeLimit,
                updateTime: () => {
                    result._endTime = new Date().getTime();

                },
                isDeadLoop: () => {
                    let isDeadLoop = result._endTime - result._startTime >= result._deadLoopTimeLimit;
                    //这代码好像有一点弊端,如果这里不执行的话,还是会死循环
                    console.log("deadLoopCatch:result._endTime - result._startTime", result._endTime - result._startTime)
                    if (isDeadLoop) {
                        console.error(`Function ${fnName} triggered an endless loop!`)
                    }
                    return isDeadLoop;

                }
            }
            return result;
        }

一般情况下while不会执行耗时操作,所以如果一个while执行超过5秒,我们认为他就是死循环了,当然这个时间可以根据自己的实际情况调整


代码使用:

var abc = function(a, b, c) {
            var i = 1;
            let deadLoopCatchInit = deadLoopCatch(arguments, 'abc', 1000);
            while (true) {
                deadLoopCatchInit.updateTime()
                if (deadLoopCatchInit.isDeadLoop()) {
                    console.warn("强制中断", deadLoopCatchInit)
                    break;
                }
                console.log("执行中....")
            }
        }
        abc()

另外一个方案

        另外个简单debug方式,就是在while第一行使用console.log(“函数名字”) 这种方式也可以定位到死循环是哪一个代码,但是却无法避免死循环。

疑问点,console

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <p>为什么去掉地46行,死循环拦截就没效果了!!<span id="txt"></span></p>
    <script>
        var deadLoopCatch = function(argument, runFnName, deadLoopTimeLimit = 5000) {
            let fnName;
            if (runFnName) {
                fnName = runFnName;
            } else {
                let str = argument.callee.toString();
                var re = /^function\s*(\w+)\(/ig;
                var matches = re.exec(str);
                if (matches && matches[1]) {
                    fnName = matches[1];
                } else {
                    //如果是匿名函数,且用户没有传递第二个参数的时候,警告
                    console.warn("When the loop is an anonymous function, please pass the second parameter for function deadLoopCatch")
                    fnName = "---UMKNOW--";
                }


            }
            //初始化开始时间和结束时间
            let startTime = endTime = new Date().getTime();
            let result = {
                fnName,
                _startTime: startTime,
                _endTime: endTime,
                _deadLoopTimeLimit: deadLoopTimeLimit,
                updateTime: () => {
                    result._endTime = new Date().getTime();

                },
                isDeadLoop: () => {
                    let isDeadLoop = result._endTime - result._startTime >= result._deadLoopTimeLimit;
                    //这代码好像有一点弊端,如果这里不执行的话,还是会死循环
                    //  console.log("deadLoopCatch:result._endTime - result._startTime", result._endTime - result._startTime)
                    if (isDeadLoop) {
                        console.error(`Function ${fnName} triggered an endless loop!`)
                    }
                    return isDeadLoop;

                }
            }
            return result;
        }
        window.onload = function() {
            const dom = document.querySelector("#txt")
            var abc = function(a, b, c) {
                var i = 1;
                let deadLoopCatchInit = deadLoopCatch(arguments, 'abc', 1000);
                while (true) {
                    deadLoopCatchInit.updateTime()
                    if (deadLoopCatchInit.isDeadLoop()) {
                        console.warn("强制中断", deadLoopCatchInit)
                        dom.innerHTML = `更新第(${i}次)后<b style='background:red;'>强制中断</b>了`
                        break;
                    }
                    i++;
                    dom.innerHTML = `更新第(${i}次)`
                    console.log("执行中....")
                }
            }
            abc()
        }
    </script>
</body>

</html>

运行一下上面的代码后会得到个奇怪的线下console.log的运行会滞后,while已经结束了,html已经输出了“强制中断”,console打印还在不断的追赶

console延后执行了



 

貌似console是个微任务的赶脚,这一定程度上也说的通,因为本来是用来调试的,不堵塞住流程代码也是应该的。


版权声明:本文为u014071104原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。