从任意评估代码中获取返回值

时间:2013-12-27 10:15:41

标签: javascript eval

我要求用户可以提供任意语句,这些语句可以存储在函数中,稍后调用以获取返回值。一个简单的例子是userInput可能是

var x = 10;
x;

我会通过

存储
var callback = function() {
    return eval(userInput);
}

然后按预期运行callback()会返回10

但是,我还需要使用显式返回语句来支持该案例,即userInput可能

var x = 10;
return x;

在这种情况下,上面的eval方法将失败SyntaxError: return not in function。相反,我可以将回调存储为

var callback = new Function(userInput);

我的问题是我想根据规则结合这两种方法'得到显式返回值,否则得到最后执行语句的结果'。特别是,这可以通过在回调创建时分析代码来完成,因为用户可能会做一些奇怪的事情,如

if(envVar < 10)
    return a;
b * 0.5;

结合了两者。

关于如何构建callback函数的结构以适应这些可能的输入的任何想法?不幸的是,不可能在用户上强制执行一种风格或另一种风格。


更新以回答以下一些评论。

建议的一个解决方案是搜索返回令牌,并在new Functioneval之间进行选择。这对我的上一个示例不起作用,请参阅http://jsfiddle.net/ZGb6z/2/ - out4应为"no"但最终未定义,因为最后执行的语句未返回。

另一个建议是修改用户输入,以便在最后一行添加显式返回(如果找不到)。不幸的是,不可能知道最后会执行哪个语句。考虑

var x = 10;
switch(x) {
  case 10:
    100;
    break;
  default:
    200;
    break;
}

调用它时应返回100,但需要进行一些认真的分析才能确定将任意代码的返回放在何处。

4 个答案:

答案 0 :(得分:5)

只需使用try catch,操纵输入对您来说非常痛苦,try catch无法让您的代码更加迟钝点。

var failback = function () {
  try {
    return eval(userInput);
  } catch (e) {
    return Function(userInput);
  }
};

我真正建议的是投资解析器,有点像how Angular does it。这种事情会阻止你的用户做他们想做的事情,引入攻击媒介,yadda,yadda,yadda。

答案 1 :(得分:3)

管理您的期望或管理用户的期望。如果您需要在同一用户输入中混合使用显式和非显式eval语句,则new Function()return不适合您的要求。您将继续沿着这两条路线找到问题。

仅仅搜索单词return是不够的...... var returning = true;var a = 'return';/* return true */ true;都会产生误报。

管理您的期望:要做这样的事情,您需要lexer和解析器的形式,此时您可以完全取消eval并执行您自己的基于解析输入的安全功能。这是最好的方法,无论如何都必须执行用户输入,因为您可以确保不希望允许执行任何操作。如果您想要覆盖这些边缘情况并允许奇怪的用户输入,那么您必须准备好增加应用程序的大小和开发时间。我已经构建了一些执行用户生成代码的应用程序,并且总是得出结论这是正确的路线。

管理用户的期望:提供指南,告诉他们不要将显式返回与非显式返回混合在一起,这些都是奇怪的编码实践。更好的是明确告诉他们包含或省略return语句。要求您的用户关注他们并不是一件好事,特别是如果它允许您改善他们在其他地方的体验。

答案 2 :(得分:0)

在那里我以为我只会在代码高尔夫堆栈交换中看到这样的问题:)

我的解决方案在这里:http://jsfiddle.net/hKq87/1

它基本上取代了&#39; return&#39;带有前缀的特殊字符串的异常语句。如果我们看到那个字符串,我们知道我们实际上正在返回一个值,并返回它而不是重新引发异常。

我选择抛出异常而不是用函数调用替换return语句的原因是因为很难知道为return评估的JS代码到底在哪里结束。它可以分为多行,包含几个特殊字符,最后甚至可能没有可选的分号。所以我将一个字符串连接到返回的值和throw它,因为throw关键字并不要求它的参数包含在括号中。

此外,抛出异常为我提供了一种方便的方法来立即终止代码块的执行,而不会停止其他JS执行。

这是回调方法:

var callback = function(userInput) {
    var returned = undefined;
    userInput = userInput.replace(/(^|[\(\\)[\]{};,\s])return(\s*[^\s;])?/gi, function(m, b, e){ 
        return b + " throw '__RETURNED_VALUE'" +
            (e !== undefined ? "+" + e : "");
    });
    try {
        returned = eval(userInput);
    } catch (e) {
        if (e.indexOf("__RETURNED_VALUE") == 0) {
            returned = e.substring("__RETURNED_VALUE".length) || undefined; 
        }
        else {
            throw e; 
        }
    }
    return returned;
}

上面的正则表达式考虑了可能以字符串&#34; return&#34;结尾的变量,我们不想替换它,因为它不是return语句。它还允许在大括号内返回语句,而不是尾随分号或在开头/结尾。

当前方法的一个问题是您不能在return语句中使用(稀有)逗号运算符,或者期望正确返回数值。 jsfiddle中的最后一个测试用例证明了这一点。来自here的示例:

//original
return 5 * 2 + 3,  22;
//modified
 throw '__RETURNED_VALUE='+ 5 * 2 + 3,  22;
//apply * operator
 throw '__RETURNED_VALUE='+ 10 + 3,  22;
//apply + operators
 throw '__RETURNED_VALUE=103',  22;
//apply , operator
 throw 22;

通过完全删除前缀&#39; __ RETURNED_VALUE =&#39;可以避免此问题。而只是取代&#39;返回&#39;用&#39; throw&#39;。但是,这意味着所提供的代码必须在不抛出异常的情况下运行,我认为这比仅仅使返回语句变得简单(避免使用逗号运算符,非括号运算等)更难。另外,如果用户创建了一个我们无法处理当前代码的return语句,我们可以方便地为它抛出一个异常,以便我们很容易引起我们的注意。

答案 3 :(得分:0)

jsFiddle Demo

让我们假设您的用户可能比普通熊更智能一点。我们将要求他们专门提供一个初始值,然后使用该值的表达式进行回调。

这样做的主要好处是你可以避免使用eval,并且实际上有一个可重用的实现,而不是后续的重构。

这种方式还可以分离输入的来源和检查的来源。虽然提供的示例只显示整数输入,但实际上它可能是另一个调用,除了它必须符合回调逻辑之外绝对不知道值。

function expression(x,callback){
 return callback(x);
}

out1.innerHTML = expression(8,function(x){
 return x;
});
out2.innerHTML = expression(10,function(x){
 return x;
});
out3.innerHTML = expression(10,function(x){
 if(x === 10) return "yes"; "no";
});
out4.innerHTML = expression(8,function(x){
 return x === 10 ? "yes" : "no";
});