Tcl Source Code

View Ticket
Login
Ticket UUID: 9bdd5788e370acc24ddc8a57e5291832aada8e6b
Title: TCL 8.6.8 does not work with Tcl_SetCommandInfo on some commands
Type: Bug Version: 8.6.8
Submitter: sebastianb Created on: 2018-11-19 09:16:33
Subsystem: 50. Embedding Support Assigned To: nobody
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2018-11-19 21:15:05
Resolution: Invalid Closed By: dgp
    Closed on: 2018-11-19 21:15:05
Description:
I'm trying to do something non-standard with TCL 8.6.8 that was working just fine with 8.5.12.

Actually i want to rewrite proc/objProc for *every* command in every namespace. Since tracing seems to be slower, this solution was used in production code without issues. Basically I'm using Tcl_SetCommandInfo with proc/objProc pointing to my functions that do proxy call to original functions.

Bu it was broken in 8.6.8. Some functions, that are (I'm guessing now) native byte code implemented, does not work at all as expected. Instead of calling my proc/objProc I'm getting original behavior of command, my functions are not called at all. Also, touching objClientData field generates assertion (broken byte code).

Since this seems fatal for me, and I cannot find any sane solution, there is minimal example that generates assertion in byte code interpreter:

#include "tcl.h"

#include <string>
#include <iostream>
#include <assert.h>

int
objProxy( ClientData clientData, Tcl_Interp *interp, int objc, struct Tcl_Obj *const *objv )
{
    assert( !"not executed at all" );

    return TCL_OK;
}

int
proxy( ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[] )
{
    assert( !"not executed at all" );

    return TCL_OK;
}

void
doProxy( std::string const& _name, Tcl_Interp* _interp )
{
    Tcl_CmdInfo commandInfo;
    int a;
    commandInfo.isNativeObjectProc = 0;
    commandInfo.objClientData = &a;  /// <---- important to trigger assertion
    commandInfo.objProc = objProxy;
    commandInfo.clientData = nullptr;
    commandInfo.proc = proxy;
    commandInfo.deleteData = nullptr;
    commandInfo.deleteProc = nullptr;

    Tcl_SetCommandInfo( _interp, _name.c_str(), &commandInfo );
}

int main()
{
    std::string command( "::tcl::clock::seconds" );
    auto interp = Tcl_CreateInterp();
    doProxy( command, interp );
    Tcl_RecordAndEval( interp, command.c_str(), TCL_EVAL_GLOBAL );
    std::cout << Tcl_GetStringResult( interp ) << std::endl;
    Tcl_DeleteInterp( interp );
    return 0;
}

Example exits with assertion:

"clockRead instruction with unknown clock#", debugging tcl internals shows garbage instead of proper byte code.

In case commandInfo.objClientData is nullptr original command works just fine, my functions are not called at all.

It works with ::tcl::clock::seconds in mysterious way. Other "normal" commands works fine, like "puts" and many others. I'm not quite sure how many other commands behave this way, but seems that majority of commands just working fine with above code (except it is missing calling original proc).

PS. No, changing between TCL_EVAL_GLOBAL/TCL_EVAL_DIRECT does not work. Also Tcl_RecordAndEval is critical, using standard eval works just fine (but I expect this is just luck with memory garbage). I'm using VS 2015 with TCL compiled from sources.
User Comments: dgp added on 2018-11-19 21:15:05:
For the example you provide, the change from Tcl 8.5 to Tcl 8.6 that is most likely at work is that [tcl::clock::seconds] is compiled and executed as bytecode in 8.6. There is no dispatch to a command procedure, so CmdInfo customizations have no effect.

Yet your comment suggests that you understand that, so I'm left not understanding what your question is.

The Tcl_RecordAndEval() routine screens out any TCL_EVAL_DIRECT flag, so using it leaves you with no power to override the decision to compile to bytecode. Maybe that's a poor design, but it raises the question of why you are using it.  What reason do you have for calling Tcl_RecordAndEval() over the many better evaluation options?

For what it's worth, modern core developers would also be very very happy to see you stop all use of the Tcl_CmdInfo struct and associated routines. The need to continue supporting them is a drag on further improvements to Tcl command evaluation.

If there's some functionality this offers that is otherwise not available, can you help us understand what it is so we can consider offering an alternative approach? Evaluation tracing needs ought to be covered with other interfaces.

sebres added on 2018-11-19 10:23:54:

Firstly the usage of Tcl_SetCommandInfo may be insufficient for the internal tcl commands (e. g. because they may be compiled, etc). With this routine you can alter the command/proc you've created at binary/script level. The usage for tcl-internal commands is not really expected.

Secondly Tcl 8.6 has more compiled command as 8.5, additionally Tcl 8.6 gets yet another processor called NRE to provide stackless evaluation (see wiki::NRE or NRE manual pagefor details).
Both compiled and NRE (if available) are preferred evaluations inside the script.

Thus to create a proper wrapper for a tcl-internal commands you should:
- either rename the original and create a proxy-wrapper command (that can call "renamed" command internally), or delete/replace the original if unwanted to invoke it from proxy;
- or use internal structure-type "Command" (from tclInt.h) instead of "Tcl_CmdInfo" to overwrite ALL proc-handler. For example set of nreProc/compileProc to NULL will force Tcl to process command invocation using objProc method.

If you just want to trace the execution, the Tcl_TraceCommand or Tcl_CreateTrace/Tcl_CreateObjTrace are better candidates for purposes like this.

Also note that tracing of the interpreter as well as non-compiled execution will do slowing down the tcl scripts, so the performance degrades significantly.