#! perl
# Copyright (C) 2001-2008, The Perl Foundation.
# $Id: /mirror/trunk/t/pmc/exception.t 35216 2009-01-08T02:49:58.220188Z cotto  $

use strict;
use warnings;
use lib qw( . lib ../lib ../../lib );
use Test::More;
use Parrot::Test tests => 30;

=head1 NAME

t/pmc/exception.t - Exception Handling

=head1 SYNOPSIS

    % prove t/pmc/exception.t

=head1 DESCRIPTION

Tests C<Exception> and C<ExceptionHandler> PMCs.

=cut

pasm_output_is( <<'CODE', <<'OUTPUT', "push_eh - pop_eh" );
    push_eh _handler
    print "ok 1\n"
    pop_eh
    print "ok 2\n"
    end
_handler:
    end
CODE
ok 1
ok 2
OUTPUT

pir_output_is( <<'CODE', <<'OUTPUT', "push_eh - pop_eh, PMC exception handler" );
.sub main :main
    $P0 = new ['ExceptionHandler']
    set_addr $P0, _handler
    push_eh $P0
    print "ok 1\n"
    pop_eh
    print "ok 2\n"
    end
_handler:
    print "caught it\n"
    end
.end
CODE
ok 1
ok 2
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "push_eh - throw" );
    print "main\n"
    push_eh _handler
    new P30, ['Exception']
    throw P30
    print "not reached\n"
    end
_handler:
    print "caught it\n"
    end
CODE
main
caught it
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "push_eh - throw, PMC exception handler" );
    print "main\n"
    new P20, ['ExceptionHandler']
    set_addr P20, _handler
    push_eh P20
    new P30, ['Exception']
    throw P30
    print "not reached\n"
    end
_handler:
    print "caught it\n"
    end
CODE
main
caught it
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "get_results" );
    print "main\n"
    push_eh handler
    new P1, ['Exception']
    new P2, ['String']
    set P2, "just pining"
    setattribute P1, 'message', P2
    throw P1
    print "not reached\n"
    end
handler:
    get_results "0", P0
    set S0, P0
    print "caught it\n"
    typeof S1, P0
    print S1
    print "\n"
    print S0
    print "\n"
    null P5
    end

CODE
main
caught it
Exception
just pining
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "exception attributes" );
    print "main\n"
    push_eh handler
    new P1, ['Exception']
    new P2, ['String']
    set P2, "just pining"
    setattribute P1, 'message', P2
    new P3, ['Integer']
    set P3, 5
    setattribute P1, 'severity', P3
    new P4, ['String']
    set P4, "additional payload"
    setattribute P1, 'payload', P4
    new P5, ['Array']
    set P5, 2
    set P5[0], 'backtrace line 1'
    set P5[1], 'backtrace line 2'
    setattribute P1, 'backtrace', P5

    throw P1
    print "not reached\n"
    end
handler:
    get_results "0", P0
    set S0, P0
    print "caught it\n"
    getattribute P16, P0, 'message'
    print P16
    print "\n"
    getattribute P17, P0, 'severity'
    print P17
    print "\n"
    getattribute P18, P0, 'payload'
    print P18
    print "\n"
    getattribute P19, P0, 'backtrace'
    set P20, P19[0]
    print P20
    print "\n"
    set P20, P19[1]
    print P20
    print "\n"
    null P5
    end

CODE
main
caught it
just pining
5
additional payload
backtrace line 1
backtrace line 2
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "get_results - be sure registers are ok" );
# see also #38459
    print "main\n"
    new P0, ['Integer']
    push_eh handler
    new P1, ['Exception']
    new P2, ['String']
    set P2, "just pining"
    setattribute P1, 'message', P2
    throw P1
    print "not reached\n"
    end
handler:
    get_results "0", P1
    inc P0
    print "ok\n"
    end

CODE
main
ok
OUTPUT

pir_output_is( <<'CODE', <<'OUTPUT', ".get_results() - PIR" );
.sub main :main
    print "main\n"
    push_eh _handler
    $P1 = new ['Exception']
    $P2 = new ['String']
    set $P2, "just pining"
    setattribute $P1, 'message', $P2
    throw $P1
    print "not reached\n"
    end
_handler:
    .local pmc e
    .local string s
    .get_results (e)
    s = e
    print "caught it\n"
    typeof $S1, e
    print $S1
    print "\n"
    print s
    print "\n"
    null $P5
.end
CODE
main
caught it
Exception
just pining
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "push_eh - throw - message" );
    print "main\n"
    push_eh _handler

    new P30, ['Exception']
    new P20, ['String']
    set P20, "something happened"
    setattribute P30, "message", P20
    throw P30
    print "not reached\n"
    end
_handler:
    get_results "0", P5
    set S0, P5
    print "caught it\n"
    print S0
    print "\n"
    end
CODE
main
caught it
something happened
OUTPUT

pasm_error_output_like( <<'CODE', <<'OUTPUT', "throw - no handler" );
    new P0, ['Exception']
    new P20, ['String']
    set P20, "something happened"
    setattribute P0, "message", P20
    throw P0
    print "not reached\n"
    end
CODE
/something happened/
OUTPUT

pasm_error_output_like( <<'CODE', <<'OUTPUT', "throw - no handler, no message" );
    push_eh _handler
    new P0, ['Exception']
    pop_eh
    throw P0
    print "not reached\n"
    end
_handler:
    end
CODE
/No exception handler and no message/
OUTPUT

pasm_error_output_like( <<'CODE', <<'OUTPUT', "throw - no handler, no message" );
    new P0, ['Exception']
    throw P0
    print "not reached\n"
    end
CODE
/No exception handler and no message/
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "2 exception handlers" );
    print "main\n"
    push_eh _handler1
    push_eh _handler2

    new P30, ['Exception']
    new P20, ['String']
    set P20, "something happened"
    setattribute P30, "message", P20
    throw P30
    print "not reached\n"
    end
_handler1:
    get_results "0", P5
    getattribute P2, P5, "message"
    print "caught it in 1\n"
    print P2
    print "\n"
    end
_handler2:
    get_results "0", P0
    getattribute P2, P0, "message"
    print "caught it in 2\n"
    print P2
    print "\n"
    end
CODE
main
caught it in 2
something happened
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "2 exception handlers, throw next" );
    print "main\n"
    push_eh _handler1
    push_eh _handler2

    new P30, ['Exception']
    new P20, ['String']
    set P20, "something happened"
    setattribute P30, "message", P20
    throw P30
    print "not reached\n"
    end
_handler1:
    get_results "0", P5
    set S0, P5
    print "caught it in 1\n"
    print S0
    print "\n"
    end
_handler2:
    get_results "0", P5
    set S0, P5
    print "caught it in 2\n"
    print S0
    print "\n"
    rethrow P5
    end
CODE
main
caught it in 2
something happened
caught it in 1
something happened
OUTPUT

pasm_output_is( <<'CODE', <<OUT, "die" );
    push_eh _handler
    die 3, 100
    print "not reached\n"
    end
_handler:
    print "caught it\n"
    end
CODE
caught it
OUT

pasm_output_is( <<'CODE', <<OUT, "die, error, severity" );
    push_eh _handler
    die 3, 100
    print "not reached\n"
    end
_handler:
    get_results "0", P5
    set S0, P5
    print "caught it\n"
    set I0, P5['severity']
    print "severity "
    print I0
    print "\n"
    end
CODE
caught it
severity 3
OUT

pasm_error_output_like( <<'CODE', <<OUT, "die - no handler" );
    die 3, 100
    print "not reached\n"
    end
_handler:
    print "caught it\n"
    end
CODE
/No exception handler and no message/
OUT

pasm_output_is( <<'CODE', '', "exit exception" );
    noop
    exit 0
    print "not reached\n"
    end
CODE

pasm_output_is( <<'CODE', <<'OUTPUT', "push_eh - throw" );
    print "main\n"
    push_eh handler
    print "ok\n"
    new P30, ['Exception']
    throw P30
    print "not reached\n"
    end
handler:
    print "caught it\n"
    end
CODE
main
ok
caught it
OUTPUT
1;

pasm_output_is( <<'CODE', <<'OUTPUT', "pushmark" );
    pushmark 10
    print "ok 1\n"
    popmark 10
    print "ok 2\n"
    end
CODE
ok 1
ok 2
OUTPUT

pasm_output_is( <<'CODE', <<'OUTPUT', "pushmark nested" );
    pushmark 10
    pushmark 11
    print "ok 1\n"
    popmark 11
    popmark 10
    print "ok 2\n"
    end
CODE
ok 1
ok 2
OUTPUT

pasm_error_output_like( <<'CODE', <<'OUTPUT', "pushmark - pop wrong one" );
    pushmark 10
    print "ok 1\n"
    popmark 500
    print "never\n"
    end
CODE
/Mark 500 not found/
OUTPUT

# stringification is handled by a vtable method, which runs in a second
# runloop. when an error in the method tries to go to a Error_Handler defined
# outside it, it winds up going to the inner runloop, giving strange results.
pir_output_is( <<'CODE', <<'OUTPUT', 'pop_eh out of context (2)', todo => 'runloop shenanigans' );
.sub main :main
        $P0 = get_hll_global ['Foo'], 'load'
        $P0()
        $P0 = new ['Foo']
        push_eh catch
        $S0 = $P0
        pop_eh
        say "huh?"
        .return()

catch:
        say "caught"
        .return()
.end

.namespace ['Foo']

.sub load
    $P0 = newclass 'Foo'
.end

.sub get_string :vtable :method
    $P0 = new ['Exception']
    throw $P0
.end
CODE
caught
OUTPUT

pir_error_output_like( <<'CODE', <<'OUTPUT', "pushaction - throw in main" );
.sub main :main
    print "main\n"
    .const 'Sub' at_exit = "exit_handler"
    pushaction at_exit
    $P0 = new ['Exception']
    throw $P0
    .return()
.end

.sub exit_handler
    .param int flag
    print "at_exit, flag = "
    say flag
.end
CODE
/^main
No exception handler/
OUTPUT

# exception handlers are still run in an inferior runloop, which messes up
# nonlocal exit from within handlers.
pir_output_like(
    <<'CODE', <<'OUTPUT', "pushaction: error while handling error", todo => 'runloop shenanigans' );
.sub main :main
    push_eh h
    print "main\n"
    .const 'Sub' at_exit = "exit_handler"
    pushaction at_exit
    $P1 = new ['Exception']
    throw $P1
    print "never 1\n"
h:
    ## this is never actually reached, because exit_handler throws an unhandled
    ## exception before the handler is entered.
    print "in outer handler\n"
.end

.sub exit_handler :outer(main)
    .param int flag
    print "at_exit, flag = "
    say flag
    $P2 = new ['Exception']
    throw $P2
    print "never 2\n"
.end
CODE
/^main
at_exit, flag = 1
No exception handler/
OUTPUT

$ENV{TEST_PROG_ARGS} ||= '';
my @todo = $ENV{TEST_PROG_ARGS} =~ /-r/
    ? ( todo => '.tailcall and lexical maps not thawed from PBC, RT #60650' )
    : ();
pir_output_is( <<'CODE', <<'OUTPUT', "exit_handler via exit exception", @todo );
.sub main :main
    .local pmc a
    .lex 'a', a
    a = new ['Integer']
    a = 42
    push_eh handler
    exit 0
handler:
    .tailcall exit_handler()
.end

.sub exit_handler :outer(main)
    say "at_exit"
    .local pmc a
    a = find_lex 'a'
    print 'a = '
    say a
.end
CODE
at_exit
a = 42
OUTPUT

## Regression test for r14697.  This probably won't be needed when PDD23 is
## fully implemented.
pir_error_output_like( <<'CODE', <<'OUTPUT', "invoke handler in calling sub", todo => "deprecate rethrow" );
## This tests that error handlers are out of scope when invoked (necessary for
## rethrow) when the error is signalled in another sub.
.sub main :main
    push_eh handler
    broken()
    print "not reached.\n"
handler:
    .local pmc exception
    .get_results (exception)
    $S0 = exception
    print "in handler.\n"
    print $S0
    print "\n"
    #rethrow exception
.end

.sub broken
    $P0 = new ['Exception']
    new $P2, ['String']
    set $P2, "something broke"
    setattribute $P0, "message", $P2
    throw $P0
.end
CODE
/\Ain handler.
something broke
something broke
current inst/
OUTPUT

pir_output_is(<<'CODE', <<'OUTPUT', "taking a continuation promotes RetCs");
## This test creates a continuation in a inner sub and re-invokes it later.  The
## re-invocation signals an error, which is caught by an intermediate sub.
## Returning from the "test" sub the second time failed in r28794; invoking
## parrot with "-D80" shows clearly that the "test" context was being recycled
## prematurely.  For some reason, it is necessary to signal the error in order
## to expose the bug.
.sub main :main
    .local int redux
    .local pmc cont
    ## debug 0x80
    redux = 0
    print "calling test\n"
    cont = test()
    print "back from test\n"
    if redux goto done
    redux = 1
    print "calling cont\n"
    cont()
    print "never.\n"
done:
    print "done.\n"
.end
.sub test
    ## Push a handler around the foo() call.
    push_eh handle_errs
    print "  calling foo\n"
    .local pmc cont
    cont = foo()
    print "  returning from test.\n"
    .return (cont)
handle_errs:
    pop_eh
    print "  test:  caught error\n"
    .return (cont)
.end
.sub foo
    ## Take a continuation.
    .local pmc cont
    cont = new ['Continuation']
    set_addr cont, over_there
    print "    returning from foo\n"
    .return (cont)
over_there:
    print "    got over there.\n"
    .local pmc ex
    ex = new ['Exception']
    throw ex
.end
CODE
calling test
  calling foo
    returning from foo
  returning from test.
back from test
calling cont
    got over there.
  test:  caught error
back from test
done.
OUTPUT

pir_error_output_like( <<'CODE', <<'OUTPUT', "throw - no handler" );
.sub main :main
    push_eh try
    failure()
    pop_eh
    exit 0
  try:
    .get_results($P0)
    pop_eh
    $S1 = $P0['backtrace']
    $S1 .= "\n"
    say $S1
.end

.sub failure
    die 'what'
.end
CODE
/No such string attribute/
OUTPUT

#RT #60556
pir_output_is( <<'CODE', <<'OUTPUT', "catch ex from C-level MULTI function", todo => "broken" );
.sub main :main

.local pmc p, q

    p = new ['Integer']
    set p, "0"

    push_eh handler
    #throw an exception from a C-level MULTI function
    q = p / p
    goto end
    pop_eh
    goto end

handler:
    .local pmc exception
    .local string message
    .get_results (exception)

    message = exception['message']
    say_something(message)
end:
.end

.sub say_something
    .param string message
    #Calling this sub is enough to trigger the bug.  If execution reached this
    #point, the bug is fixed.
    say "no segfault"
end:
.end
CODE
no segfault
OUTPUT

# Local Variables:
#   mode: cperl
#   cperl-indent-level: 4
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4:
