Ticket #1550 (closed bug: invalid)

Opened 4 years ago

Last modified 4 years ago

lexical lost when cloning a Sub

Reported by: fperrad Owned by:
Priority: normal Milestone:
Component: core Version: 2.2.0
Severity: medium Keywords:
Cc: Language:
Patch status: Platform: all

Description

when cloning a Sub, we lose lexical variables

.sub 'main' :main
    .const 'Sub' f = 'func'
    $P0 = clone f
    $P0()
.end

.sub 'func' :lex
    say "func"
    $P0 = box 'STATE'
    .lex 'VAR', $P0
    inner()
.end

.sub 'inner' :lex :outer('func')
    $P0 = find_lex 'VAR'
    say $P0
.end

fails with this output:

func
Null PMC in say
current instr.: 'inner' pc 35 (clone_lex.pir:17)
called from Sub 'func' pc 25 (clone_lex.pir:11)
called from Sub 'main' pc 8 (clone_lex.pir:4)

Attachments

clone_lex.pir Download (266 bytes) - added by fperrad 4 years ago.
t_op_lexicals_t_failure.txt Download (4.4 KB) - added by jkeenan 4 years ago.
t/op/lexicals.t: Failures observed at r45635, Linux/i386

Change History

Changed 4 years ago by fperrad

Changed 4 years ago by fperrad

in r45629, added as todoed test

Changed 4 years ago by jkeenan

t/op/lexicals.t: Failures observed at r45635, Linux/i386

Changed 4 years ago by bacek

Hello.

Quick investigation:

1. Sub.invoke uses Sub.outer_sub pointer for lexical autoclose. 2. Cloned Sub has different pointer (of course).

I'm not sure about autoclose semantics and if it can be remade based on call chain and sub_id instead of pointers.

-- Bacek

Changed 4 years ago by kjs

If you invoke f() before doing the clone, it works. I'm not at all familiar with closures etc. but apparently there's something to do with the invocation of f() that triggers the lexical being stored; this doesn't happen when f() is not called before cloning it. Therefore, it seems to me it's not a problem with cloning the sub.

Just a thought. kjs

Changed 4 years ago by pmichaud

On Mon, Apr 12, 2010 at 06:45:18AM -0000, Parrot wrote:
> #1550: lexical lost when cloning a Sub
>  when cloning a Sub, we lose lexical variables
> 
>  {{{
>  .sub 'main' :main
>      .const 'Sub' f = 'func'
>      $P0 = clone f
>      $P0()
>  .end
> 
>  .sub 'func' :lex
>      say "func"
>      $P0 = box 'STATE'
>      .lex 'VAR', $P0
>      inner()
>  .end
> 
>  .sub 'inner' :lex :outer('func')
>      $P0 = find_lex 'VAR'
>      say $P0
>  .end
>  }}}

In Parrot, one cannot "clone a Sub" and then invoke
it and expect its lexically nested subs to work, 
because the :outer attribute of the lexically nested
subs will still be pointing to the original Sub
(not the cloned copy).

Rephrased using the above, when cloning 'func',
'inner' still references 'func' as its outer sub, 
and not its clone.

You probably want to be using one of the capture_lex
and/or newclosure opcodes somewhere to properly capture
the lexical variables.  Or, if you really do need to
be attaching subs to a cloned outer, then you'll want
the ."set_outer" method.

Pm

Changed 4 years ago by pmichaud

On Tue, Apr 13, 2010 at 09:50:23PM -0000, Parrot wrote:
> #1550: lexical lost when cloning a Sub
>  Hello.
> 
>  Quick investigation:
> 
>  1. Sub.invoke uses Sub.outer_sub pointer for lexical autoclose.
>  2. Cloned Sub has different pointer (of course).
> 
>  I'm not sure about autoclose semantics and if it can be remade based on
>  call chain and sub_id instead of pointers.

The original lexicals code (pre November 2008) attempted to do 
autoclose based on caller chain and was a complete mess, so I
strongly advise against this approach.  (Be sure you review
the comments to RT #56398 and understand them, at the least! :-)

Personally I think this ticket can be closed as "not a bug".

Pm

Changed 4 years ago by fperrad

  • status changed from new to closed
  • resolution set to invalid

I'd forgot that a subroutine must be cloned by newclosure instead of clone.

So, newclosure and set_outer do the job.

.sub 'main' :main
    .const 'Sub' f = 'func'
    $P0 = newclosure f
    $P1 = get_global 'inner'
    $P1.'set_outer'($P0)
    $P0()
.end

.sub 'func' :lex
    say "func"
    $P0 = box 'STATE'
    .lex 'VAR', $P0
    inner()
.end

.sub 'inner' :lex :outer('func')
    $P0 = find_lex 'VAR'
    say $P0
.end
Note: See TracTickets for help on using tickets.