在取消设置跟踪变量时触发错误
Triggering an error on unsetting a traced variable
我正在尝试创建一些只读变量,以与在安全中断中评估的代码一起使用。使用 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
%
的确,我最终注意到 documentation 甚至顺便说了一句:
Any errors in unset traces are ignored.
尝试使用不同的 return
代码,包括自定义号码,它们 所有 似乎都被忽略了。它也不会触发 interp bgerror
处理程序。是否有任何其他方法可以在尝试取消设置特定变量时引发错误?
真的没有。关键问题是,有时 Tcl 将取消设置变量,而该变量 实际上 将被删除,因为它的包含结构(命名空间、堆栈框架或对象,最终口译员)也被删除。变量在那一点上注定失败,用户代码无法阻止它(当然,永远不会从跟踪返回的可怕方法除外,它无限地推迟死亡并使一切都处于奇怪的状态;不要那样做)。根本没有地方可以将变量复活到。命令删除痕迹也有同样的问题;他们也可以开火,因为他们的存储正在消失。 (TclOO 析构函数对此有更多的保护;他们尽量不丢失错误——甚至将它们投入 interp bgerror
作为最后的手段——但在某些边缘情况下仍然可以。)
此外,目前 API 中没有任何内容允许错误消息从删除命名空间或调用框架的过程中冒出来。我认为这是可以修复的(它需要更改一些 public APIs)但是出于充分的理由我认为删除仍然必须发生,特别是栈帧。此外,我不确定当您删除包含两个未设置跟踪变量的名称空间时会发生什么,这两个变量的跟踪都报告错误。错误应该是什么?我真的不知道。 (我知道最终结果必须是命名空间仍然消失,FWIW,但细节很重要,我不知道它们应该是什么。)
I'm trying to create some read-only variables to use with code evaluated
Schelte 和 Donal 已经提供了及时而深入的反馈。因此,出现的意思是一个不起眼的补充。既然知道变量跟踪是在 事后执行的,下面是我用来模仿只读的方式(或者更确切地说 keep-re_setting-to-a-one-time-value) 使用跟踪的变量(注意:正如 Donal 所解释的,这不会扩展到 proc-local 变量)。
下面的实现允许:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
受 variable
启发,但仅适用于一个变量值对。
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-local 变量;
环绕variable
;
[namespace exists ...]
:当给定的父名称空间当前正在删除(namespace delete ::ns1
,或子 interp 删除)时,这些守卫保护免受操作;
在unset
的情况下,处理程序脚本会在重新创建的变量上重新添加跟踪(否则,将不再捕获任何后续写入。);
[trace info variable ...]
:有助于避免添加多余的痕迹;
[namespace which -variable]
:确保使用完全限定的变量名;
一些最后的评论:
Ooo, maybe I can substitute the normal unset for a custom version and
do the checking in it instead of relying on trace
当然是一个选项,但它不会让您涵盖取消设置变量的各种(间接)路径。
[...] in a safe interp.
您可能希望 interp alias
在您的安全插入中的 variable
与父插入中的上述 readOnly
之间?
我正在尝试创建一些只读变量,以与在安全中断中评估的代码一起使用。使用 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
%
的确,我最终注意到 documentation 甚至顺便说了一句:
Any errors in unset traces are ignored.
尝试使用不同的 return
代码,包括自定义号码,它们 所有 似乎都被忽略了。它也不会触发 interp bgerror
处理程序。是否有任何其他方法可以在尝试取消设置特定变量时引发错误?
真的没有。关键问题是,有时 Tcl 将取消设置变量,而该变量 实际上 将被删除,因为它的包含结构(命名空间、堆栈框架或对象,最终口译员)也被删除。变量在那一点上注定失败,用户代码无法阻止它(当然,永远不会从跟踪返回的可怕方法除外,它无限地推迟死亡并使一切都处于奇怪的状态;不要那样做)。根本没有地方可以将变量复活到。命令删除痕迹也有同样的问题;他们也可以开火,因为他们的存储正在消失。 (TclOO 析构函数对此有更多的保护;他们尽量不丢失错误——甚至将它们投入 interp bgerror
作为最后的手段——但在某些边缘情况下仍然可以。)
此外,目前 API 中没有任何内容允许错误消息从删除命名空间或调用框架的过程中冒出来。我认为这是可以修复的(它需要更改一些 public APIs)但是出于充分的理由我认为删除仍然必须发生,特别是栈帧。此外,我不确定当您删除包含两个未设置跟踪变量的名称空间时会发生什么,这两个变量的跟踪都报告错误。错误应该是什么?我真的不知道。 (我知道最终结果必须是命名空间仍然消失,FWIW,但细节很重要,我不知道它们应该是什么。)
I'm trying to create some read-only variables to use with code evaluated
Schelte 和 Donal 已经提供了及时而深入的反馈。因此,出现的意思是一个不起眼的补充。既然知道变量跟踪是在 事后执行的,下面是我用来模仿只读的方式(或者更确切地说 keep-re_setting-to-a-one-time-value) 使用跟踪的变量(注意:正如 Donal 所解释的,这不会扩展到 proc-local 变量)。
下面的实现允许:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
受 variable
启发,但仅适用于一个变量值对。
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-local 变量;
环绕
variable
;[namespace exists ...]
:当给定的父名称空间当前正在删除(namespace delete ::ns1
,或子 interp 删除)时,这些守卫保护免受操作;在
unset
的情况下,处理程序脚本会在重新创建的变量上重新添加跟踪(否则,将不再捕获任何后续写入。);[trace info variable ...]
:有助于避免添加多余的痕迹;[namespace which -variable]
:确保使用完全限定的变量名;
一些最后的评论:
Ooo, maybe I can substitute the normal unset for a custom version and do the checking in it instead of relying on trace
当然是一个选项,但它不会让您涵盖取消设置变量的各种(间接)路径。
[...] in a safe interp.
您可能希望 interp alias
在您的安全插入中的 variable
与父插入中的上述 readOnly
之间?