Tcl Source Code

View Ticket
Login
Ticket UUID: 67d5f75c36cbada6cf2361aaf49cffa61281143b
Title: [$interp eval $lambda] after [eval $lambda] or vice versa fails
Type: Bug Version: 8.7+
Submitter: dbohdan Created on: 2024-09-29 13:23:29
Subsystem: 20. [interp] Assigned To: nobody
Priority: 7 High Severity: Critical
Status: Closed Last Modified: 2024-12-26 18:34:11
Resolution: Fixed Closed By: sebres
    Closed on: 2024-12-26 18:34:11
Description:

The following code generates an error with a blank message in Tcl 9 but succeeds in Tcl 8.6. The error seems to only occur when:

  • You use a [list] command to create the anonymous function. It does not occur with a braced string.
  • The contents of the list is apply ... rather than, for example, puts hi.

[eval] and [$interp eval] can be called in either order; the second one fails.

Code

set lambda [list apply {{} {
    puts hi
}}]

puts [list [info patchlevel] {*}[array get tcl_platform]]

eval $lambda
set interp [interp create]
$interp eval $lambda

This is the contents of the file tests/all.tcl in the log.

Log

> tclsh8.6 tests/all.tcl
8.6.14 osVersion 6.8.0-44-generic pointerSize 8 byteOrder littleEndian threaded 1 machine x86_64 platform unix pathSeparator : os Linux engine Tcl user dbohdan wordSize 8
hi
hi

> tclsh9.0 tests/all.tcl
9.0.0 osVersion 6.8.0-44-generic pointerSize 8 byteOrder littleEndian machine x86_64 platform unix pathSeparator : os Linux engine Tcl user dbohdan wordSize 8
hi

    while executing
"apply {{} {
    puts hi
}}"
    invoked from within
"$interp eval $lambda"
    (file "tests/all.tcl" line 11)
User Comments: sebres added on 2024-12-26 18:34:11:

Merged, fixed for 8.7+, and it is covered in test-suite starting from 8.6 now. Thus close it now.


sebres added on 2024-12-26 18:16:40:

The patch [de39c09363b4fcaa] looks good to me.

As for your questions, Ashok:

1. I think yes, because it is only the difference at the moment that I see, and covers the same code as in 8.6.

2. The recompilation during TEBC evaluation is safe and also TEBCResume knows this situation (it'd yield new code version and reenter from last known position), let alone the code is protected by the refCount (so TclProcCleanupProc will be invoked by TclFreeIntRep of last reference only).

I'll merge it now for 8.7+.


apnadkarni added on 2024-12-24 01:01:22:
I don't have much to add to my earlier post and not likely to be able to delve into the innards further in the near future. I do think the possible fix I suggested would be an improvement to the current situation but am not convinced it is a complete fix.

jan.nijtmans added on 2024-12-21 16:46:40:

Ping. Any way we can do better?


dkf added on 2024-10-05 09:05:53:

Procs have an internal reference count that's incremented while code is running in them; the recompilation for the other interpreter won't be seen until the next time the lambda is invoked.

We ought to make sure that the path that can currently generate an empty error message doesn't do that any more either, as well as mending the check whether procedure records are crossing interpreters. Empty error messages are indicative of a code path that's incomplete.


apnadkarni added on 2024-10-01 17:50:19:

Possible fix at [de39c09363]. This attempts to mimic 8.6 by recompiling the lambdaon an interp mismatch. Fixes the reported bug and passes test suite.

I am however uncomfortable for a couple reasons.

  1. I am not sure this exactly or completely imitates the 8.6 implementation or behavior given the extensive refactoring in that area.

  2. More important, even with 8.6 I am not sure how all this recompilation of a lambda would with work in the middle of an execution. For example, the following code which passes a lambda in the middle of its execution to a subinterpreter which also executes the lambda.

set lambda3 [list apply {{id lambda args} {
    puts Hi:$id
    if {[llength $args]} {
        [lindex $args 0] eval $lambda child [list $lambda]
    }
    puts Bye:$id
}}]

The internal rep of a lambda stores a pointer to the interpreter it was compiled in (within a Proc struct). When the call to the child is made, the lambda is recompiled and the internal rep points to the child interpreter. When that call returns, does the lambda in the main continue with the internal rep pointing to the child interpreter ? Or is it restored to the main interpreter? If the latter, I can't figure out where. If the former, isn't that a problem (but I don't yet know how to construct a case to illustrate that it is).

A second pair of eyes would be helpful.


apnadkarni added on 2024-09-30 17:59:25:

Further detail, this is the actual commit on the tip-445 branch where the change occurred. Feels like it was intentional as the changes in tclDisassembler.c are analogous.

Assigning to @dgp hoping he can recall context from that far back!


apnadkarni added on 2024-09-30 17:49:32:

Breakage appears to be from here. The sample code is actually generating an error with an empty error message.

Basically the lambda internal rep remembers the interpreter that compiled it. In 8.6, if the internal rep was compiled in a different interpreter than the current one, it would be recompiled and life would go on. That was changed (not sure inadvertently or not) to return a NULL indicating an error instead (without setting the interp result to an error message which is another buglet).

The change happened in this TIP 445 merge. The prior commit works as in 8.6.

One fix would be to revert to 8.6 behavior and recompile on a change in interps. I'm not sure though whether the original change was intentional or not, for example to prevent constant recompilation on a lambda TclObj shared between interpreters? Perhaps the intent was such that lambda TclObjs should be duplicated instead?