【问题标题】:Why is `eval` worse than `str2func` to evaluate a function from a string? [closed]为什么`eval`比`str2func`更差从字符串评估函数? [关闭]
【发布时间】:2018-02-23 02:35:04
【问题描述】:

我已经展示了performance of str2func is better,但是我得到了很多 cmets 说有更基本的reasons 不使用eval。在以下情况下,哪些根本原因适用于eval 而不适用于str2func:

f='a^x+exp(b)+sin(c*x)+d'
  1. eval:

    y = eval(f)
    

    或(由rahnema1建议)

    fHandle = eval(['@(x, a, b, c, d) ' f]);
    y = fHandle(x, a, b, c, d);
    
  2. str2func:

    fHandle = str2func(['@(x, a, b, c, d) ' f]);
    y = fHandle(x, a, b, c, d);
    

除了performance reasons,为什么第一个选项比第二个差?

备注

  • 请注意,我知道最好避免这两种方法。

  • 请注意,我将eval 的输出分配给了一个变量,这样可以避免执行很多棘手的代码。

【问题讨论】:

  • 你可能想比较fHandle = str2func(['@(x, a, b, c, d) ' f]);和fHandle = eval(['@(x, a, b, c, d) ' f]);
  • 如果您没有将有效的函数字符串传递给str2func,它将发出警告(或在未来的 [current?] 版本中出现错误)。但是,如果您没有将有效函数传递给eval,它无论如何都会按照您的要求进行操作......想象一下eval('delete C:/*') 是如何获得可怕结果的示例。显然你不会故意犯这个错误,但这是夸大了在构建函数字符串时可能会出错的点(因此为什么你应该尽可能避免这两种情况)和更多的验证检查更好。
  • @Wolfie y = eval('delete my_file.m') 返回错误(并且不删除文件):Error: Unexpected MATLAB expression.。 (使用 R2016b 测试)
  • eval('!del my_file.m') 将在 windows 上删除 my_file.m
  • tldr;由于您试图忽略的原因,应尽可能避免这两种功能,因此这是一个有争议的问题。如果您必须使用str2func,请在eval 上使用它,因为它略微内置了更多保护功能。

标签: matlab eval


【解决方案1】:

不要再试图淡化eval 的安全风险。如果您不完全控制输入,则运行任意代码可能是有害的。如果您完全控制输入,几乎总是一种不涉及动态代码生成的更好方法。

你坚持一个具体的例子:

>> ls TMPFILE
Error using ls (line 35)
ls: cannot access 'TMPFILE': No such file or directory

>> y = eval('system(''touch TMPFILE'')');
>> y

y =

     0

>> ls TMPFILE
TMPFILE

touch是一个非常友好的unix命令;它创建一个空文件。想象一下rm -rf ~ 在相同的上下文中。

让我们试试str2func:

>> y = str2func('system(''touch TMPFILE2'')');
Warning: The input to STR2FUNC "system('touch TMPFILE2')" is not a valid function name. This will generate an error in a future release. 
>> ls TMPFILE2
Error using ls (line 35)
ls: cannot access 'TMPFILE2': No such file or directory

>> y

y =

  function_handle with value:

    @system('touch TMPFILE2')

>> y()
Undefined function or variable 'system('touch TMPFILE2')'.

旁注:虽然eval 的风险是真实存在的,而不仅仅是由于迷信,不是使用eval 并不一定意味着你是安全的。我最喜欢的例子,str2num 的鬼鬼祟祟:

>> x = str2num('[system(''touch TMPFILE3'')]')        

x =

     0

>> ls TMPFILE3
TMPFILE3

因此,即使您没有明确使用eval,也可能会发生您使用的便利函数确实。您应该始终确保不会发生这种情况:使用安全的str2double 而不是str2num,并使用str2func 而不是eval(因为我们在上面看到str2func 不会 执行任意代码)。

【讨论】:

  • 感谢您的回答。我同意避免使用eval 是个好主意,但我不明白为什么用str2func 替换它会有什么不同。你可以用str2func做同样的事情:y = str2func('@(x) system(''touch TMPFILE'')'); y(1);
  • @m7913d 因为攻击者必须有关于你的程序内部的详细信息才能工作。如果您使用eval 或等效的,任何任意system 命令将被执行,不问任何问题。当然,足够复杂的攻击以及关于代码本身的足够信息(!)和程序员足够低质量的清理工作(!)意味着如果您使用外部输入,则很难编写防弹代码。但是str2func 将呈现Little Bobby Tables harmless。
  • @m7913d 啊,我明白你的意思了。是的,在这个确切的用例中,这两者可能同样不安全。但这并不意味着eval 的风险是没有根据的。而是暗示有时你不能完全安全。底线应该始终是尝试编写尽可能安全的代码。在这种情况下,str2func 至少与eval 一样安全。它还将使您免于格式错误(但良性)的输入,例如 str2func('@(x) 3; 4') 是一个错误,但 eval 会评估它。我的观点是默认值应该不使用eval,因为它可能非常不安全。
  • @m7913d 回答您的问题。想象一下,您有这两行代码,但不是['@(x, a, b, c, d) ' f],而是input(在这种情况下,您将使用eval)。在eval 的情况下,代码会运行。不管第二行是否错误。在str2fun 的情况下,代码不会执行。
  • @m7913d 我相当肯定我们已经为您已经毫无根据的问题提供了足够多的技术答案。没有任何合理的理由可以说服您不使用eval。我当然不会继续对着砖墙说话。您可以随意使用任何东西,代码的质量和安全性是您的全部责任。
【解决方案2】:

首先,性能(尤其是 x100 慢)应该是不使用某些东西的充分理由。

但是,您没有抓住重点。您现在在问“为什么不在评估字符串中的函数的具体示例中使用eval?”。嗯,答案是因为你有一个名为str2func 的函数来专门更快、更安全地完成这项工作。你不应该使用 eval 的原因是因为在你想使用 eval 的情况下,你的代码逻辑是有缺陷的。

eval 的唯一原因是评估任意输入,它不仅仅是字符串上的函数(你为什么要这样做,你已经证明了它有一个特定的函数)。如果你知道你在评估什么,那么你就不需要 eval,你可以为你所期望的编写代码。因此,eval 仅在您接受通用输入时才有用。但通用输入包括 rm -rf 以删除您的整个操作系统。或者在灾难性较小的情况下,代码可能会重写对算法的其余部分很重要的变量。很明显为什么你不想让你的代码运行任意输入。

动态变量呢? A terrible idea 可以使用 eval 生成。而且您可能会通过接受任意输入意外地做到这一点。

但是还有更多的东西。 eval 确实使您的代码不可读。直到运行时,您才知道代码的作用。

我已经看到在其核心功能中执行此操作的代码

model.solve_algorithm=eval(['default(',[ class(input3) ],')']);
result=eval(model.solve_algorithm);

代码有什么作用?有什么选择?没有办法知道,除非你运行它,看看它去了哪里。这使得代码变得模糊且难以阅读,当然也难以维护。在代码中显式确实对代码的可维护性有很大好处。

TLDR:在任何情况下,您可能想要使用 eval,两者之一正在发生:

  1. 有一个更好、更具体的功能可以满足您的需求
  2. 你真的不应该做你想做的事,代码/逻辑需要重组。

【讨论】:

  • 感谢您的回答,但我看不出与 str2func 有明显区别。请注意,我将eval 的输出分配给了一个变量,这样可以避免执行很多棘手的代码。
  • @m7913d 我将在此呼应安德拉斯。我的答案有足够的理由,第二段中的答案是“你没有抓住重点”。你仍然没有抓住重点。重新阅读这个问题,因为它清楚地解释了大局,而不仅仅是你试图捍卫的樱桃挑选的例子。
  • 并不是因为存在(更具体的)功能,它本身就更安全。 Matlab 可以创建一个新函数来克服特定情况下eval 的坏名声。我仍然缺少一个具体的例子。
  • @m7913d 你没抓住重点。您正在寻找您给出的具体示例在具体示例中。 eval 不安全的一个具体例子是eval(whatever_users_says)。很容易挑选出使用 eval 是安全的情况。为什么不在 eval(['a=randi(',input,')']) 中使用 eval ?它永远是安全的!但是如果你只看这个例子,你就错过了重点
  • 不是一个很好的类比,因为很难找到“很好地使用eval”的例子
【解决方案3】:

我将暂时搁置反对这两种方法的论点,尽管它们都非常有效,认真的读者应该尝试理解它们。


我可以看到两个明显的区别。对于这些示例,我们必须假设您已经使用一些可变数据为脚本中的函数构建了输入字符串,并假设它可能是 anything(无论是错误的还是其他原因)。所以我们要做好最坏情况的准备。

  1. str2func 包含一些 额外检查,以确保您传递的是有效函数,这可能会避免不必要的行为。让我们不要采取“是的,但你可以这样做来避免这种情况”的立场,并看一个例子......

    % Not assigning an output variable
    % STR2FUNC: You've harmlessly assigned ans to some junk function
    str2func('delete test.txt') 
    % EVAL: You've just deleted your super important document
    eval('delete test.txt')     
    
    % Assigning an output variable
    % STR2FUNC: You get a clear warning that this is not a valid function
    f = str2func('delete test.txt') 
    % EVAL: You can a non-descript error "Unexpected MATLAB expression"
    f = eval('delete test.txt')
    
  2. 另一个区别是大约一半的str2func documentation 的主题。它涉及变量范围,位于“检查str2func 和eval 之间的差异”标题下。

    [函数介绍]
    如果您使用匿名函数的字符向量表示,生成的函数句柄无法访问私有或本地函数。

    [检查 str2func 和 eval 之间的差异]
    当 str2func 与表示匿名函数的字符向量一起使用时,它无法访问本地函数 [...]。 eval 函数确实可以访问本地函数。


总而言之,您可能有一些用例,其中每个函数都更可取,具体取决于您所需的错误处理和变量范围不应尽可能使用eval 或str2func,如其他答案。

【讨论】:

  • 感谢您搁置反对这两种方法的论点。我绝对同意你的观点,你应该尽可能避免使用eval 和str2func。我看到两者之间的区别,但是 - 就像 Dev-iL 的答案一样 - 首选行为取决于用户(如果你不考虑 1 - 不考虑分配输出变量)。也许您确实认为该问题只有基于意见的答案,因为在这种情况下两者几乎相同。
  • 是的,首选行为取决于用户,这就是为什么我没有说我更喜欢功能而不是其他。但这适用于几乎任何编程功能。这只是对两个非常相似的函数的不同行为的比较。这里的观点是“两者都不好”,事实就是我最后一句话之外的一切。
  • 这就是为什么我不明白为什么 Luis Mendo 对我之前的问题发表评论的原因:请在您的回答中提及eval 是出于比性能更根本的原因而不受欢迎的原因。我将这条评论解释为使用str2func 而不是eval 有更根本的原因。
  • 我认为 Luis 的实际暗示是“请在您的回答中提及 eval 是出于比性能更根本的原因而不受欢迎的原因,就像 str2func 一样,即使它更快”。虽然我不能代表他说话,但很容易解雇eval,str2func 不太常见,因此不会很快被解雇(尽管出于大多数相同的原因,但如上所述,有一些 更多保护)
  • @m7913d 是的,我的意思正是 Wolfie 在上面的评论中所说的:不要使用 eval,因为它会导致问题。我并没有真正与str2func 进行比较。我现在看到我的评论可能让您感到困惑(对不起!),因为您的问题是关于比较两者
【解决方案4】:

TL;DR - eval 可以访问本地定义的函数,而 str2func 没有。这两个函数中使用的“正确”函数取决于您的代码结构。


在不深入讨论“为什么eval 不好”的情况下,我将尝试关注MATLAB documentation 的相关部分,该部分在您的问题的背景下进行了讨论:

考虑一个有两个函数的文件:

function out = q46213509
  f='a^x+exp(b)+sin(c*x)+d';
  fHandle = {eval(['@(x, a, b, c, d) ' f]), str2func(['@(x, a, b, c, d) ' f])};
  out = [fHandle{1}(1,2,3,4,5) fHandle{2}(1,2,3,4,5)];
end

function e = exp(x)
  e = x.^0./factorial(0) - x.^1./factorial(1) + x.^2./factorial(2); % Error is deliberate
end

当你运行上面的时候,它会导致:

ans =

    8.7432   26.3287

如果您正在使用类并且定义了your own operators,这可能会失控...假设有人决定将一个文件添加到您的MATLAB path,并且方便给它您使用的某些函数的名称,或可重载运算符的名称(即mpower.m):

function out = mpower(varargin)
  % This function disregards all inputs and prints info about the calling workspace.
  disp(struct2cell(evalin('caller','whos')));
end

虽然在某些情况下,MATLAB 可以防止重新定义内置函数,但我敢打赌,上面的场景可能会让str2func 很困惑...

【讨论】:

  • 感谢您的友善、开放和中肯的回答。谢谢!但是,这可能取决于用户需要哪种行为。
  • @m7913d 我很困惑,您已经看到了一个“具体示例”,说明了它们的不同之处以及哪个可能更可取以及为什么,但这不是您要寻找的答案?
  • @Wolfie 如果这是唯一的区别,即没有其他更关键的区别,那么这确实是我正在寻找的答案。考虑到第一个例子,我不能说哪个答案显然是首选。如果我作为用户有意覆盖默认的exp 函数并将f 提供给黑盒函数,我可能会期望eval 返回的结果。
  • 再次感谢您的回答,但我会接受 Wolfie 的回答,因为它有点宽泛。
  • @m7913d 很酷,只要让这个讨论停止 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-15
  • 1970-01-01
相关资源
最近更新 更多