【问题标题】:Is this code which is using Switch.pm safe?这段使用 Switch.pm 的代码安全吗?
【发布时间】:2013-06-07 22:34:56
【问题描述】:

在我们公司,我们使用这个代码(在最后给出)大约 10 年,它运行良好。

前几天我们遇到了一些问题,我们不得不重新编码完整的包,我们决定用 Damian 的 Switch 模块替换这段代码(以提高代码的可读性)。

对我们来说一切都很好。

后来我在Perlmonks 上发现达米安把这个模块放在了

您不应该在生产环境中使用 Damian 模块,因为它们的用途 是探索和原型化未来的核心语言功能。

但它对我们来说很好,因为我们没有达到这个模块的限制(我猜)。

现在我请你们看看这两个实现(嵌套 if else 与 switch),让我知道在较新的实现中使用 Switch 是否很好,或者我们是否会为我们创造一些未来的问题?在下面给出的代码中使用 Switch 是否正常,或者是否有任何隐藏的错误/问题?

我已经在 CPAN 和 Perlmonks 上阅读了此模块的错误和评论,我想我们的代码距离这些错误还很远(我想是的)。

我们正在使用Perl 5.8.5

PS: 我知道 Switch 的替代方案,我们在 Perl 5.10 中有 given/when,我们可以使用 dispatch table 和指定 here 的其他解决方案,但现在我们只想比较使用 Switch 的新实现。

使用嵌套 if else

if ($command =~ /^enter$/) {
    $self->show_main_frames();
}       
elsif ($command =~ /^XYZ_MENU/i) {
    $self->show_main_menu($manual, $dbot);
}
elsif ($command =~ /^DBOT/i) {
    $dbot->process();
}
# XML is used for the reminders-history: Request 2666
elsif ($command =~ /^XML_DBOT/i) {
    $dbot->process();
}
elsif ($command =~ /^UGS/i) {
    $ugsui->process();
}
elsif ($command eq "kill") {
    my $login = $self->{COMMON_HASH}{login} || "";
    my $su_login = $self->{CONF}->get("start", "SU_LOGIN");
    if ($login eq $su_login) {      
            # usually only certain user with certain permission will be
            # able to do this. 
            $self->do_error("Daemon was killed by ".$login);
            $self->db_connection->disconnect();
            $self->{LOG}->write("User $login killed the daemon", 0);
            exit; # this 'exit' actually kill the daemon
    }
    else {
            $self->do_error("User $login tried to kill the daemon. ".
            "This incident will be reported");
            $self->{LOG}->write("User $login tried to kill the daemon", 2);
    }
}
elsif ($command eq "logout") {
    # check if we should delete the password cookie
    my $forget_me = $self->{CGI}->param("forget_me") || 0;
    if ($forget_me) {
            $self->{DB_PASSWORD_COOKIE}->delete_cookie();
    }

    $ugsui->do_logout();
    # Cliff edit remove id from logged_in
    $session->remove_session($session->login());
    # delete the session of the user
    delete $self->{SESSIONS}{$session->id()};
    if ($self->{CACHE_TO_FILE}) {
            my $session_data_path = 
                XYZ_DIR
            ."/code/cache/session_data"
        .$session->id();
            unlink($session_data_path);
    }
}
# if we just login we should create all the main frames        
elsif ($command eq "login") {
    # if extra_param holds "command*XXX" the XXX will be placed instead of
    # the command. extra_param holds pairs that are astrics-separated
    my $extra_param = $cgi->param("extra_param");
    $extra_param = "" if (!defined($extra_param));
    $extra_param =~ /command\*([^\*]+)/i;
    my $other_command = defined($1) ? $1 : "";
    if ($other_command =~ /^dbot/i) { # meanwhile - works only on dbot 
                                  # commands
        $command = $other_command;
            # now we will get the other parameters from the extra_param 
            # (actually including the command that is still in the 
            # $extra_param)         
            while ($extra_param =~ /^\*?([^\*]+)\*([^\*]+)(.*)/) {
            $extra_param = $3;
            my $name = $1;
            my $value = $2;     
            $cgi->param(-name => $name,
                 -value => $value);
            }#end while
    }#end if
    else{
    $self->show_main_frames();
    }
}#end elsif
else {
    $self->show_main_frames();
}#end outer else

使用开关

switch ($command) 
{
    case /^enter$/      { $self->show_main_frames() }
    case /^XYZ_MENU/i   { $self->show_main_menu($manual, $dbot) }
    case /^DBOT/i       { $dbot->process() }
        case /^XML_DBOT/i   { $dbot->process() }
        case /^UGS/i        { $ugsui->process() }
        case "kill"     {
                my $login = $self->{COMMON_HASH}{login} || "";
                    my $su_login = $self->{CONF}->get("start", "SU_LOGIN");
                if ($login eq $su_login) {      
                        # usually only certain user with certain permission will be
                        # able to do this. 
                        $self->do_error("Daemon was killed by ".$login);
                        $self->db_connection->disconnect();
                        $self->{LOG}->write("User $login killed the daemon", 0);
                        exit; # this 'exit' actually kill the daemon
                    }
                else    {
                        $self->do_error("User $login tried to kill the daemon. ".
                            "This incident will be reported");
                        $self->{LOG}->write("User $login tried to kill the daemon", 2);
                    }
                    }
        case "logout"       {
                    # check if we should delete the password cookie
                    my $forget_me = $self->{CGI}->param("forget_me") || 0;
                    if ($forget_me) {
                            $self->{DB_PASSWORD_COOKIE}->delete_cookie();
                    }

                $ugsui->do_logout();
                # Cliff edit remove id from logged_in
                $session->remove_session($session->login());
                # delete the session of the user
                delete $self->{SESSIONS}{$session->id()};
                    if ($self->{CACHE_TO_FILE}) {
                            my $session_data_path = 
                                XYZ_DIR
                        ."/code/cache/session_data"
                        .$session->id();
                    unlink($session_data_path);
                    }
                    }
        case "login"        {
                # if extra_param holds "command*XXX" the XXX will be placed instead of
                # the command. extra_param holds pairs that are astrics-separated
                my $extra_param = $cgi->param("extra_param");
                $extra_param = "" if (!defined($extra_param));
                $extra_param =~ /command\*([^\*]+)/i;
                my $other_command = defined($1) ? $1 : "";
                if ($other_command =~ /^dbot/i) 
                    { # meanwhile - works only on dbot 
                                       # commands
                    $command = $other_command;
                        # now we will get the other parameters from the extra_param 
                        # (actually including the command that is still in the 
                        # $extra_param)         
                        while ($extra_param =~ /^\*?([^\*]+)\*([^\*]+)(.*)/) {
                        $extra_param = $3;
                        my $name = $1;
                        my $value = $2;     
                        $cgi->param(-name => $name,
                             -value => $value);
                            }#end while
                    }#end if
                else {$self->show_main_frames();}
                }
    else            {$self->show_main_frames();}
} # end switch

【问题讨论】:

  • 你为什么要使用这么古老的 Perl 版本。当前版本是 5.16.2,因此您错过了 5.10、5.12、5.14 和 5.16 系列中的改进和错误修复。 Perl 5.8.5 于 2004 年 7 月发布。那是很久以前的事了。
  • 我知道,但没办法。我个人使用 5.12,但在公司中我必须使用 5.8.5。顺便说一句,我们将在下一个实现(我们产品的新版本)中使用 5.12。
  • @JonathanLeffler 这是 RHEL 5 提供的版本,它仍然被广泛使用。 Red Hat 实际上会在 2017 年之前支持该版本的 perl(带有付费扩展支持)。
  • 取决于您所说的“支持”是什么意思。其中有很多 RH 无法修复的错误。例如它甚至不是该版本的最新版本(5.8.9 是)。
  • RHEL 5 提供 5.8.8。至少,您应该对此进行更新。

标签: perl if-statement switch-statement


【解决方案1】:

Switch 自己解析源代码。这可能导致难以诊断直接使用它的代码中的错误。 Switch 产生的问题不是间歇性的,因此如果您的代码正常工作,您无需担心。

但实际上,它并没有增加多少。

带开关:

switch ($command) {
    case /^enter$/      { $self->show_main_frames() }
    case /^XYZ_MENU/i   { $self->show_main_menu($manual, $dbot) }
    case /^DBOT/i       { $dbot->process() }
    case /^XML_DBOT/i   { $dbot->process() }
    case /^UGS/i        { $ugsui->process() }
    case "kill"         {
        my $login = $self->{COMMON_HASH}{login} || "";

无开关:

for ($command) {
    if    (/^enter$/)      { $self->show_main_frames() }
    elsif (/^XYZ_MENU/i)   { $self->show_main_menu($manual, $dbot) }
    elsif (/^DBOT/i)       { $dbot->process() }
    elsif (/^XML_DBOT/i)   { $dbot->process() }
    elsif (/^UGS/i)        { $ugsui->process() }
    elsif ($_ eq "kill")   {
        my $login = $self->{COMMON_HASH}{login} || "";

elsif (/^kill\z/) 也可以。)

【讨论】:

  • 我见过的无开关for 的另一个变体是/^enter$/ && do { ...; next; };
【解决方案2】:

其实Switch模块并没有为你提供任何“杀手级功能”; elsif 语句也可以做到这一点,该语句安全、稳定并且没有 Switch 的缺点。这是我在项目中遇到的 Switch 问题(我不再使用它):

通过 Perl 过滤器进行切换。这种技术有以下限制:

  • 您的源代码实际上是在运行中重写并替换为 随后的 elsif 语句。
  • 一些 Perl 错误报告会引用错误的行;其中一些显示了您的源代码中没有的代码(自动生成的代码)。

不是过滤器限制,而是模块本身的限制:

  • 如果您调用 use Swtich 的文件(.pl 或 .pm)超过 1Mbyte 大小,这可能会导致“神秘错误”(如 doc 中所述)。我可以确认这些错误不会导致 Switch 模块并且完全不明显,因此您可以在数周的编码/文档之后进行艰苦的调试。

我建议使用自 Perl 5.10 起可用的 elsifgiven..when 语句。所以如果你使用 perl 5.8.x - 使用 elsif。

您还可以阅读Switch 文档的“限制”段落。

【讨论】:

  • 附注:布莱恩本人 suggests to use for instead of given
  • 如果您调用use Switch 的文件大小超过1MB ...您遇到与您使用use Switch 无关的问题:-)(除此之外,我同意。)
【解决方案3】:

因为 Switch 有自己的源代码解析,所以在某些情况下根本不起作用。例如,它不可能与 mod_perl 一起使用。

但是,如果您有 Perl 5.10 或更高版本,则可以使用相同的功能更好地替换:given/when

use v5.10;

given ($var) {
when (/^abc/) { $abc = 1 }
when (/^def/) { $def = 1 }
when (/^xyz/) { $xyz = 1 }
default       { $nothing = 1 }
}

given 受到 Perl 核心的支持(并且可以在任何地方使用,包括 mod_perl) - 您只需 use v5.10; 即可立即使用。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 2010-10-29
  • 2013-02-20
  • 1970-01-01
  • 2023-04-10
  • 2011-05-18
相关资源
最近更新 更多