我要求用户可以提供任意语句,这些语句可以存储在函数中,稍后调用以获取返回值。一个简单的例子是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 Function
和eval
之间进行选择。这对我的上一个示例不起作用,请参阅http://jsfiddle.net/ZGb6z/2/ - out4应为"no"
但最终未定义,因为最后执行的语句未返回。
另一个建议是修改用户输入,以便在最后一行添加显式返回(如果找不到)。不幸的是,不可能知道最后会执行哪个语句。考虑
var x = 10;
switch(x) {
case 10:
100;
break;
default:
200;
break;
}
调用它时应返回100
,但需要进行一些认真的分析才能确定将任意代码的返回放在何处。
答案 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";
});