LINUX.ORG.RU

Отдебажить где именно произошла ошибка разыменования ссылки в perl

 ,


1

2

Предположим есть такой код (добавил нумерацию строк для наглядности)

1:  use strict;
2:
3:  my %a = (
4:     a => 12,
5:     b => '',
6:     x => { z => 22 },
7:     l => 'sd'
8:  );
9:
10: my %c = (
11:    f => 'test',
12:    b => $a{b}{c},
13:    g => $a{a},
14:    d => $a{x}{z},
15: );

он выдает такую ошибку: Can't use string ("") as a HASH ref while "strict refs" in use at /tmp/t.pl line 15. Из него совершенно неочевидно где именно произошла ошибка. Если структура %c будет ещё сложнее и длиннее, то поиск ошибки вообще превратится в боль. Можно ли как-то сделать, чтобы perl явно показал, что проблема в данном случае в $a{b}{c}?

★★★

Последнее исправление: Olegymous (всего исправлений: 1)

Ответ на: комментарий от no-such-file

такие вещи

Какие такие?

такие

он выдает такую ошибку: Can’t use string ("") as a HASH ref while «strict refs» in use at /tmp/t.pl line 15. Из него совершенно неочевидно где именно произошла ошибка.

Строку, где упало, в таком простом случае питон точно показывает 😄. Не всегда очевидно, что произошло, но хоть ясно, где копать.

Virtuos86 ★★★★★
()
Ответ на: комментарий от anonymous2

Это ты к чему?

Help on built-in function round in module builtins:

round(number, ndigits=None)
    Round a number to a given precision in decimal digits.
    
    The return value is an integer if ndigits is omitted or None.  Otherwise
    the return value has the same type as the number.  ndigits may be negative.
(END)

А вообще-то округление тема непростая, чтобы наугад тыкать, знать надо, что тебе конретно нужно. Где не так? В жабоскрипте несколько функций для округления так же, из того, что я лично тыкал.

Virtuos86 ★★★★★
()

Ну тут все и так ясно в массиве а, элемент б пустая строка и взятие в ро по индексу от нее вернет пустую строку хотя даже в пхп бросит исключение. Перл хуже яваскрипт выходит

anonymous
()

Я не перловик, но поверхностная гуглёжка намекает, что просто - никак. И модуль diagnostics не поможет. Можно перед определением %c использовать print по порядочку, для каждого значения.

seiken ★★★★★
()

Закомментируй половину списка - найдешь в какой части ошибка проходит. Потом разбей эту часть на две и опять одну закомментируй . Повторяй пока не дойдешь до нужной строки.

UPDATE: Пришла еще одна идея. Добавить функцию с проверкой входных данных - isValid($row,$val) or die "WTF: on line $row"; - и добавить ее на каждую строчку в списке.

necromant ★★
()
Последнее исправление: necromant (всего исправлений: 4)
Ответ на: комментарий от mrjaggers

Там про uninitialized warning и не совсем подошло. Но что-то я подчерпнул оттуда и набросал вот такой весьма хрупкий костыль:

use strict;
use Data::Dumper;
use B::Deparse;
use PPI::Tokenizer;

$SIG{__DIE__} = sub {
    my ($err) = @_;
    my ($file,$line) = (caller(0))[1,2];
    my $subname      = (caller(1))[3];

    my ($before_code, $problem_code) = split_code( $subname, $file, $line, $err );

    my $tokenizer = PPI::Tokenizer->new( \$problem_code );

    my @problem_variables;
    my ( $start_braces, $end_braces );

    while ( my $token = $tokenizer->get_token ) {
        if ( $start_braces == $end_braces && index($token, '$') == 0 ) {
            push @problem_variables, $token;
            next;
        }

        if (index($token, '$') == 0) {
            $problem_variables[-1] .= $token;
            next;
        }

        if ( $token eq '{' ) {
            $start_braces++;
            $problem_variables[-1] .= $token;
            next;
        }

        if ( $token eq '}' ) {
            $end_braces++;
            $problem_variables[-1] .= $token;
            next;
        }

        if ( $start_braces != $end_braces ) {
            $problem_variables[-1] .= $token;
        }
    }

    for my $var (@problem_variables) {
        $before_code .= qq!\neval { my \$t = $var; 1 } or warn q~has problems with $var~, "\\n";!;
    }

    $before_code .= "}";
    eval $before_code or die $@;
};

sub split_code {
    my ($name, $file, $line, $err) =@_;

    my $subref = \&{$name};

    my $subbody = B::Deparse->new('-q','-l','-x0')->coderef2text($subref);

    my $start = "#line \Q$line\E \"\Q$file\E\"\n";
    my $end   = "\n(#line|})";

    my ($before, $problem) =
      $subbody =~ m/^(.+?)$start\s+(.*?);$end/s;

    return ($before, $problem);
}

sub test {
    my %a = (
        a => 12,
        b => '',
        x => { z => 22 },
        l => 'sd'
    );

    my %c = (
        f => 'test',
        b => $a{b}{c},
        g => $a{a},
        d => $a{x}{z},
    );
}

test();

Результат теперь такой:

has problems with $a{'b'}{'c'}
Can't use string ("") as a HASH ref while "strict refs" in use at /tmp/t.pl line 83.

В целом это уже можно улучшать и развивать.

Olegymous ★★★
() автор топика
Последнее исправление: Olegymous (всего исправлений: 1)

Какой-то искусственный, высосанный из пальца пример. Скорее всего, ты что-то делаешь не так.

Например:

Если структура %c будет ещё сложнее и длиннее, то поиск ошибки вообще превратится в боль.

При чём тут структура хеш %c, если ошибка в %a? Ты заполнил %a неправильно — вот где проблема. Нарисуй функцию, которая валидирует %a, и вызывай её по мере надобности.

Типа isValid(12,$a{b}{c})? Оно же уже на этом моменте упадёт

Ну так тебе голова на то дана, чтобы ей думать, а не только шапку носить. Во-первых, валидировать нужно весь хеш %a, а не отдельные значения. Во-вторых, при валидации %a сначала валидируешь $a{b}, и только потом $a{b}{c} — тогда ничего не упадёт. Элементарно же.

debugger ★★★★★
()
Ответ на: комментарий от Olegymous

c eval тогда поиграться может. Все это переусложнит код. Архитектурно прежде чем передавать куда-то данные они должны быть проверены на валидность.

die "Bad reference for hash" unless ref($a{b}) eq HASH;
necromant ★★
()
Ответ на: комментарий от Virtuos86

Я где-то утверждал, что перл лучше? Это же «пыхоплеяда», они все конченные. В перле всё плохо с диагностикой, да. В том числе поэтому я и топлю за то, что не надо его применять нигде кроме однострочников коротких скриптов. А питон вообще нигде не надо применять.

bread
()
Ответ на: комментарий от bread

Я где-то утверждал, что перл лучше? Это же «пыхоплеяда», они все конченные. В перле всё плохо с диагностикой, да. В том числе поэтому я и топлю за то, что не надо его применять нигде кромеоднострочниковкоротких скриптов. А питон вообще нигде не надо применять.

Если внимательно прочитать твое сообщение, ты даже здесь умудрился между строк утвердить, что перл лучше питона 😁. Потому что на перле можно писать хотя бы «короткие скрипты», а «питон вообще нигде не надо применять». Сектанты они такие — не хотят, всё равно проговариваются 😂.

Virtuos86 ★★★★★
()

Если структура %c будет ещё сложнее и длиннее, то поиск ошибки вообще превратится в боль.

Так не усложняйте и не удлиняйте.

my %c;
$c{f} = 'test';
$c{b} = $a{b}{c};
$c{g} = $a{a};
$c{d} = $a{x}{z};
# ...
annulen ★★★★★
()
Ответ на: комментарий от debugger

в ссылке, что я кидал (perlmonks за 2017) прямо сказано, что Perl не умеет указывать на конкретную неинициализированную сущность, когда она в хэш-рефе. Если просто переменная - пожалуйста.

так что хоть пример действительно искусственный (сознательное ССЗБ), но проблема-то реальная.

mrjaggers
()
Ответ на: комментарий от mrjaggers

сознательное ССЗБ

В реальности код был подобного рода

my $hashref = {...};

for my $var ( $arrayref_of_hashrefs ) {
    my %hash = (
        # тут пару десятков ключей заполняется как из $hashref, так и из $var
        # обращаясь к разным уровням вложенности $hashref и $var
    );
}

Была получена ошибка Not a hash reference. Учитывая обилие ключей в %hash и обращение к разному уровню вложенности при обращении к двум переменным было совершенно неочевидно где именно проблема.

По итогу выяснилось, что автор забыл разыменовать $arrayref_of_hashrefs.

for my $var ( @$arrayref_of_hashrefs ) {
Olegymous ★★★
() автор топика
Последнее исправление: Olegymous (всего исправлений: 2)