Tcl 在取消设置跟踪变量时触发错误
我试图创建一些只读变量,用于在安全interp中计算代码。使用Tcl 在取消设置跟踪变量时触发错误,tcl,Tcl,我试图创建一些只读变量,用于在安全interp中计算代码。使用trace,我可以在尝试设置它们时生成错误,但在使用unset时不会: % set foo bar bar % trace add variable foo {unset write} {apply {{var _ op} { error "$var $op trace triggered" }}} % set foo bar can't set "foo": foo write trace t
trace
,我可以在尝试设置它们时生成错误,但在使用unset
时不会:
% set foo bar
bar
% trace add variable foo {unset write} {apply {{var _ op} { error "$var $op trace triggered" }}}
% set foo bar
can't set "foo": foo write trace triggered
% unset foo
%
事实上,我最终注意到,甚至还顺便说:
将忽略未设置跟踪中的任何错误
使用不同的
返回
代码,包括自定义数字,它们似乎都被忽略了。它也不会触发interp bg错误
处理程序。如果试图取消设置某个特定变量,是否有其他方法引发错误?实际上没有。关键问题是,有时Tcl会取消设置一个变量,而该变量实际上会被删除,因为它的包含结构(名称空间、堆栈帧或对象,最终还有解释器)也会被删除。变量在那一点上就注定要失败,用户代码无法阻止它(当然,除了从跟踪中永不返回的可怕方法,这会无限地推迟死亡,并将一切置于一种奇怪的状态;不要这样做)。没有任何地方可以恢复变量。命令删除跟踪也有同样的问题;它们也可以开火,因为它们的储存正在消失。(TclOO析构函数对此有更大的保护;它们尽量不丢失错误-作为最后手段,甚至可以将它们放入interp bg error
,但在某些边缘情况下仍然可以。)
此外,API中目前没有允许在删除命名空间或调用帧的过程中冒泡出错误消息的内容。我认为这是可以修复的(需要更改一些公共API),但出于充分的理由,我认为删除仍然必须发生,特别是对于堆栈帧。此外,我不确定删除包含两个未设置跟踪变量的名称空间时会发生什么,这两个变量的跟踪都会报告错误。错误应该是什么?我真的不知道。(我知道最终的结果是名称空间仍然不存在,FWIW,但是细节很重要,我不知道它们应该是什么。)
我正在尝试创建一些只读变量,用于计算代码
Schelte和Donal已经提供了及时和深入的反馈。因此,这意味着一个谦逊的补充。现在,我们知道有变量跟踪是在事后执行的,下面是我如何使用跟踪来模拟只读(或者更确切地说是保持设置为一次性值)变量(注意:正如Donal解释的,这并不扩展到proc局部变量)
以下实施允许以下情况:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
受变量
启发,但仅适用于一个变量-值对
proc ::readOnly {var val} {
uplevel [list variable $var $val]
if {![string match "::*" $var]} {
set var [uplevel [list namespace which -variable $var]]
}
# only proceed iff namespace is not under deletion!
if {[namespace exists [namespace qualifiers $var]]} {
set readOnlyHandler {{var val _ _ op} {
if {[namespace exists [namespace qualifiers $var]]} {
if {$op eq "unset"} {
::readOnly $var $val
} else {
set $var $val
}
# optional: use stderr as err-signalling channel?
puts stderr [list $var is read-only]
}
}}
set handlerScript [list apply $readOnlyHandler $var $val]
set traces [trace info variable $var]
set varTrace [list {write unset} $handlerScript]
if {![llength $traces] || $varTrace ni $traces} {
trace add variable $var {*}$varTrace
}
}
}
一些注意事项:
- 这意味着只适用于全局变量或其他名称空间变量,而不适用于proc局部变量
- 它环绕
变量
:当给定的父名称空间当前正在删除时(名称空间删除::ns1,或子interp删除),这些防护可以防止操作[名称空间存在…]
- 在
情况下,处理程序脚本在重新创建好的变量上重新添加跟踪(否则,将不再捕获任何后续写入)unset
:帮助避免添加冗余跟踪[跟踪信息变量…]
:确保使用完全限定的变量名[名称空间which-variable]
最后几点意见:
哦,也许我可以用普通的unset替换自定义版本,然后 执行签入操作,而不是依赖跟踪 当然是一种选择,但它并没有覆盖取消设置变量的各种(间接)路径 […]在安全的环境中
您可能希望将安全interp中的
变量
与父interp中的上述只读
之间的interp别名
。请注意,即使写入跟踪引发错误,变量仍设置为新值。因此,这也不适用于实现只读变量。@SchelteBron因此出现错误。但它不是只读变量,只是更复杂的写入变量。问题是,包含变量的命名空间可能会消失,因为其父命名空间也在消失,一些其他部分可能已经被删除,我们不想在删除过程中不得不遍历两次,这样可能会有机会让变量拒绝这个过程。我怀疑这与道德上的问题是一样的:C++中如何不在析构函数中抛出异常。其中一个在超出范围时被解释器自动清理,但我猜这两种情况被跟踪框架?Ooo处理得一样,也许我可以用普通的unset
替换自定义版本,并在其中进行检查,而不是依赖trace
。@shawn我更新了代码片段,以避免重复设置
。