ttrace.tcl 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. #
  2. # ttrace.tcl --
  3. #
  4. # Copyright (C) 2003 Zoran Vasiljevic, Archiware GmbH. All Rights Reserved.
  5. #
  6. # See the file "license.terms" for information on usage and redistribution of
  7. # this file, and for a DISCLAIMER OF ALL WARRANTIES.
  8. # ----------------------------------------------------------------------------
  9. #
  10. # User level commands:
  11. #
  12. # ttrace::eval top-level wrapper (ttrace-savvy eval)
  13. # ttrace::enable activates registered Tcl command traces
  14. # ttrace::disable terminates tracing of Tcl commands
  15. # ttrace::isenabled returns true if ttrace is enabled
  16. # ttrace::cleanup bring the interp to a pristine state
  17. # ttrace::update update interp to the latest trace epoch
  18. # ttrace::config setup some configuration options
  19. # ttrace::getscript returns a script for initializing interps
  20. #
  21. # Commands used for/from trace callbacks:
  22. #
  23. # ttrace::atenable register callback to be done at trace enable
  24. # ttrace::atdisable register callback to be done at trace disable
  25. # ttrace::addtrace register user-defined tracer callback
  26. # ttrace::addscript register user-defined script generator
  27. # ttrace::addresolver register user-defined command resolver
  28. # ttrace::addcleanup register user-defined cleanup procedures
  29. # ttrace::addentry adds one entry into the named trace store
  30. # ttrace::getentry returns the entry value from the named store
  31. # ttrace::delentry removes the entry from the named store
  32. # ttrace::getentries returns all entries from the named store
  33. # ttrace::preload register procedures to be preloaded always
  34. #
  35. #
  36. # Limitations:
  37. #
  38. # o. [namespace forget] is still not implemented
  39. # o. [namespace origin cmd] breaks if cmd is not already defined
  40. #
  41. # I left this deliberately. I didn't want to override the [namespace]
  42. # command in order to avoid potential slowdown.
  43. #
  44. namespace eval ttrace {
  45. # Setup some compatibility wrappers
  46. if {[info commands nsv_set] != ""} {
  47. variable tvers 0
  48. variable mutex ns_mutex
  49. variable elock [$mutex create traceepochmutex]
  50. # Import the underlying API; faster than recomputing
  51. interp alias {} [namespace current]::_array {} nsv_array
  52. interp alias {} [namespace current]::_incr {} nsv_incr
  53. interp alias {} [namespace current]::_lappend {} nsv_lappend
  54. interp alias {} [namespace current]::_names {} nsv_names
  55. interp alias {} [namespace current]::_set {} nsv_set
  56. interp alias {} [namespace current]::_unset {} nsv_unset
  57. } elseif {![catch {
  58. variable tvers [package require Thread]
  59. }]} {
  60. variable mutex thread::mutex
  61. variable elock [$mutex create]
  62. # Import the underlying API; faster than recomputing
  63. interp alias {} [namespace current]::_array {} tsv::array
  64. interp alias {} [namespace current]::_incr {} tsv::incr
  65. interp alias {} [namespace current]::_lappend {} tsv::lappend
  66. interp alias {} [namespace current]::_names {} tsv::names
  67. interp alias {} [namespace current]::_set {} tsv::set
  68. interp alias {} [namespace current]::_unset {} tsv::unset
  69. } else {
  70. error "requires NaviServer/AOLserver or Tcl threading extension"
  71. }
  72. # Keep in sync with the Thread package
  73. package provide Ttrace 2.8.7
  74. # Package variables
  75. variable resolvers "" ; # List of registered resolvers
  76. variable tracers "" ; # List of registered cmd tracers
  77. variable scripts "" ; # List of registered script makers
  78. variable enables "" ; # List of trace-enable callbacks
  79. variable disables "" ; # List of trace-disable callbacks
  80. variable preloads "" ; # List of procedure names to preload
  81. variable enabled 0 ; # True if trace is enabled
  82. variable config ; # Array with config options
  83. variable epoch -1 ; # The initialization epoch
  84. variable cleancnt 0 ; # Counter of registered cleaners
  85. # Package private namespaces
  86. namespace eval resolve "" ; # Commands for resolving commands
  87. namespace eval trace "" ; # Commands registered for tracing
  88. namespace eval enable "" ; # Commands invoked at trace enable
  89. namespace eval disable "" ; # Commands invoked at trace disable
  90. namespace eval script "" ; # Commands for generating scripts
  91. # Exported commands
  92. namespace export unknown
  93. # Initialize ttrace shared state
  94. if {[_array exists ttrace] == 0} {
  95. _set ttrace lastepoch $epoch
  96. _set ttrace epochlist ""
  97. }
  98. # Initially, allow creation of epochs
  99. set config(-doepochs) 1
  100. proc eval {cmd args} {
  101. enable
  102. set code [catch {uplevel 1 [concat $cmd $args]} result]
  103. disable
  104. if {$code == 0} {
  105. if {[llength [info commands ns_ictl]]} {
  106. ns_ictl save [getscript]
  107. } else {
  108. thread::broadcast {
  109. package require Ttrace
  110. ttrace::update
  111. }
  112. }
  113. }
  114. return -code $code \
  115. -errorinfo $::errorInfo -errorcode $::errorCode $result
  116. }
  117. proc config {args} {
  118. variable config
  119. if {[llength $args] == 0} {
  120. array get config
  121. } elseif {[llength $args] == 1} {
  122. set opt [lindex $args 0]
  123. set config($opt)
  124. } else {
  125. set opt [lindex $args 0]
  126. set val [lindex $args 1]
  127. set config($opt) $val
  128. }
  129. }
  130. proc enable {} {
  131. variable config
  132. variable tracers
  133. variable enables
  134. variable enabled
  135. incr enabled 1
  136. if {$enabled > 1} {
  137. return
  138. }
  139. if {$config(-doepochs) != 0} {
  140. variable epoch [_newepoch]
  141. }
  142. set nsp [namespace current]
  143. foreach enabler $enables {
  144. enable::_$enabler
  145. }
  146. foreach trace $tracers {
  147. if {[info commands $trace] != ""} {
  148. trace add execution $trace leave ${nsp}::trace::_$trace
  149. }
  150. }
  151. }
  152. proc disable {} {
  153. variable enabled
  154. variable tracers
  155. variable disables
  156. incr enabled -1
  157. if {$enabled > 0} {
  158. return
  159. }
  160. set nsp [namespace current]
  161. foreach disabler $disables {
  162. disable::_$disabler
  163. }
  164. foreach trace $tracers {
  165. if {[info commands $trace] != ""} {
  166. trace remove execution $trace leave ${nsp}::trace::_$trace
  167. }
  168. }
  169. }
  170. proc isenabled {} {
  171. variable enabled
  172. expr {$enabled > 0}
  173. }
  174. proc update {{from -1}} {
  175. if {$from == -1} {
  176. variable epoch [_set ttrace lastepoch]
  177. } else {
  178. if {[lsearch [_set ttrace epochlist] $from] == -1} {
  179. error "no such epoch: $from"
  180. }
  181. variable epoch $from
  182. }
  183. uplevel [getscript]
  184. }
  185. proc getscript {} {
  186. variable preloads
  187. variable epoch
  188. variable scripts
  189. append script [_serializensp] \n
  190. append script "::namespace eval [namespace current] {" \n
  191. append script "::namespace export unknown" \n
  192. append script "_useepoch $epoch" \n
  193. append script "}" \n
  194. foreach cmd $preloads {
  195. append script [_serializeproc $cmd] \n
  196. }
  197. foreach maker $scripts {
  198. append script [script::_$maker]
  199. }
  200. return $script
  201. }
  202. proc cleanup {args} {
  203. foreach cmd [info commands resolve::cleaner_*] {
  204. uplevel $cmd $args
  205. }
  206. }
  207. proc preload {cmd} {
  208. variable preloads
  209. if {[lsearch $preloads $cmd] == -1} {
  210. lappend preloads $cmd
  211. }
  212. }
  213. proc atenable {cmd arglist body} {
  214. variable enables
  215. if {[lsearch $enables $cmd] == -1} {
  216. lappend enables $cmd
  217. set cmd [namespace current]::enable::_$cmd
  218. proc $cmd $arglist $body
  219. return $cmd
  220. }
  221. }
  222. proc atdisable {cmd arglist body} {
  223. variable disables
  224. if {[lsearch $disables $cmd] == -1} {
  225. lappend disables $cmd
  226. set cmd [namespace current]::disable::_$cmd
  227. proc $cmd $arglist $body
  228. return $cmd
  229. }
  230. }
  231. proc addtrace {cmd arglist body} {
  232. variable tracers
  233. if {[lsearch $tracers $cmd] == -1} {
  234. lappend tracers $cmd
  235. set tracer [namespace current]::trace::_$cmd
  236. proc $tracer $arglist $body
  237. if {[isenabled]} {
  238. trace add execution $cmd leave $tracer
  239. }
  240. return $tracer
  241. }
  242. }
  243. proc addscript {cmd body} {
  244. variable scripts
  245. if {[lsearch $scripts $cmd] == -1} {
  246. lappend scripts $cmd
  247. set cmd [namespace current]::script::_$cmd
  248. proc $cmd args $body
  249. return $cmd
  250. }
  251. }
  252. proc addresolver {cmd arglist body} {
  253. variable resolvers
  254. if {[lsearch $resolvers $cmd] == -1} {
  255. lappend resolvers $cmd
  256. set cmd [namespace current]::resolve::$cmd
  257. proc $cmd $arglist $body
  258. return $cmd
  259. }
  260. }
  261. proc addcleanup {body} {
  262. variable cleancnt
  263. set cmd [namespace current]::resolve::cleaner_[incr cleancnt]
  264. proc $cmd args $body
  265. return $cmd
  266. }
  267. proc addentry {cmd var val} {
  268. variable epoch
  269. _set ${epoch}-$cmd $var $val
  270. }
  271. proc delentry {cmd var} {
  272. variable epoch
  273. set ei $::errorInfo
  274. set ec $::errorCode
  275. catch {_unset ${epoch}-$cmd $var}
  276. set ::errorInfo $ei
  277. set ::errorCode $ec
  278. }
  279. proc getentry {cmd var} {
  280. variable epoch
  281. set ei $::errorInfo
  282. set ec $::errorCode
  283. if {[catch {_set ${epoch}-$cmd $var} val]} {
  284. set ::errorInfo $ei
  285. set ::errorCode $ec
  286. set val ""
  287. }
  288. return $val
  289. }
  290. proc getentries {cmd {pattern *}} {
  291. variable epoch
  292. _array names ${epoch}-$cmd $pattern
  293. }
  294. proc unknown {args} {
  295. set cmd [lindex $args 0]
  296. if {[uplevel ttrace::_resolve [list $cmd]]} {
  297. set c [catch {uplevel $cmd [lrange $args 1 end]} r]
  298. } else {
  299. set c [catch {::eval ::tcl::unknown $args} r]
  300. }
  301. return -code $c -errorcode $::errorCode -errorinfo $::errorInfo $r
  302. }
  303. proc _resolve {cmd} {
  304. variable resolvers
  305. foreach resolver $resolvers {
  306. if {[uplevel [info comm resolve::$resolver] [list $cmd]]} {
  307. return 1
  308. }
  309. }
  310. return 0
  311. }
  312. proc _getthread {} {
  313. if {[info commands ns_thread] == ""} {
  314. thread::id
  315. } else {
  316. ns_thread getid
  317. }
  318. }
  319. proc _getthreads {} {
  320. if {[info commands ns_thread] == ""} {
  321. return [thread::names]
  322. } else {
  323. foreach entry [ns_info threads] {
  324. lappend threads [lindex $entry 2]
  325. }
  326. return $threads
  327. }
  328. }
  329. proc _newepoch {} {
  330. variable elock
  331. variable mutex
  332. $mutex lock $elock
  333. set old [_set ttrace lastepoch]
  334. set new [_incr ttrace lastepoch]
  335. _lappend ttrace $new [_getthread]
  336. if {$old >= 0} {
  337. _copyepoch $old $new
  338. _delepochs
  339. }
  340. _lappend ttrace epochlist $new
  341. $mutex unlock $elock
  342. return $new
  343. }
  344. proc _copyepoch {old new} {
  345. foreach var [_names $old-*] {
  346. set cmd [lindex [split $var -] 1]
  347. _array reset $new-$cmd [_array get $var]
  348. }
  349. }
  350. proc _delepochs {} {
  351. set tlist [_getthreads]
  352. set elist ""
  353. foreach epoch [_set ttrace epochlist] {
  354. if {[_dropepoch $epoch $tlist] == 0} {
  355. lappend elist $epoch
  356. } else {
  357. _unset ttrace $epoch
  358. }
  359. }
  360. _set ttrace epochlist $elist
  361. }
  362. proc _dropepoch {epoch threads} {
  363. set self [_getthread]
  364. foreach tid [_set ttrace $epoch] {
  365. if {$tid != $self && [lsearch $threads $tid] >= 0} {
  366. lappend alive $tid
  367. }
  368. }
  369. if {[info exists alive]} {
  370. _set ttrace $epoch $alive
  371. return 0
  372. } else {
  373. foreach var [_names $epoch-*] {
  374. _unset $var
  375. }
  376. return 1
  377. }
  378. }
  379. proc _useepoch {epoch} {
  380. if {$epoch >= 0} {
  381. set tid [_getthread]
  382. if {[lsearch [_set ttrace $epoch] $tid] == -1} {
  383. _lappend ttrace $epoch $tid
  384. }
  385. }
  386. }
  387. proc _serializeproc {cmd} {
  388. set dargs [info args $cmd]
  389. set pbody [info body $cmd]
  390. set pargs ""
  391. foreach arg $dargs {
  392. if {![info default $cmd $arg def]} {
  393. lappend pargs $arg
  394. } else {
  395. lappend pargs [list $arg $def]
  396. }
  397. }
  398. set nsp [namespace qual $cmd]
  399. if {$nsp == ""} {
  400. set nsp "::"
  401. }
  402. append res [list ::namespace eval $nsp] " {" \n
  403. append res [list ::proc [namespace tail $cmd] $pargs $pbody] \n
  404. append res "}" \n
  405. }
  406. proc _serializensp {{nsp ""} {result _}} {
  407. upvar $result res
  408. if {$nsp == ""} {
  409. set nsp [namespace current]
  410. }
  411. append res [list ::namespace eval $nsp] " {" \n
  412. foreach var [info vars ${nsp}::*] {
  413. set vname [namespace tail $var]
  414. if {[array exists $var] == 0} {
  415. append res [list ::variable $vname [set $var]] \n
  416. } else {
  417. append res [list ::variable $vname] \n
  418. append res [list ::array set $vname [array get $var]] \n
  419. }
  420. }
  421. foreach cmd [info procs ${nsp}::*] {
  422. append res [_serializeproc $cmd] \n
  423. }
  424. append res "}" \n
  425. foreach nn [namespace children $nsp] {
  426. _serializensp $nn res
  427. }
  428. return $res
  429. }
  430. }
  431. #
  432. # The code below is ment to be run once during the application start. It
  433. # provides implementation of tracing callbacks for some Tcl commands. Users
  434. # can supply their own tracer implementations on-the-fly.
  435. #
  436. # The code below will create traces for the following Tcl commands:
  437. # "namespace", "variable", "load", "proc" and "rename"
  438. #
  439. # Also, the Tcl object extension XOTcl 1.1.0 is handled and all XOTcl related
  440. # things, like classes and objects are traced (many thanks to Gustaf Neumann
  441. # from XOTcl for his kind help and support).
  442. #
  443. eval {
  444. #
  445. # Register the "load" trace. This will create the following key/value pair
  446. # in the "load" store:
  447. #
  448. # --- key ---- --- value ---
  449. # <path_of_loaded_image> <name_of_the_init_proc>
  450. #
  451. # We normally need only the name_of_the_init_proc for being able to load
  452. # the package in other interpreters, but we store the path to the image
  453. # file as well.
  454. #
  455. ttrace::addtrace load {cmdline code args} {
  456. if {$code != 0} {
  457. return
  458. }
  459. set image [lindex $cmdline 1]
  460. set initp [lindex $cmdline 2]
  461. if {$initp == ""} {
  462. foreach pkg [info loaded] {
  463. if {[lindex $pkg 0] == $image} {
  464. set initp [lindex $pkg 1]
  465. }
  466. }
  467. }
  468. ttrace::addentry load $image $initp
  469. }
  470. ttrace::addscript load {
  471. append res "\n"
  472. foreach entry [ttrace::getentries load] {
  473. set initp [ttrace::getentry load $entry]
  474. append res "::load {} $initp" \n
  475. }
  476. return $res
  477. }
  478. #
  479. # Register the "namespace" trace. This will create the following key/value
  480. # entry in "namespace" store:
  481. #
  482. # --- key ---- --- value ---
  483. # ::fully::qualified::namespace 1
  484. #
  485. # It will also fill the "proc" store for procedures and commands imported
  486. # in this namespace with following:
  487. #
  488. # --- key ---- --- value ---
  489. # ::fully::qualified::proc [list <ns> "" ""]
  490. #
  491. # The <ns> is the name of the namespace where the command or procedure is
  492. # imported from.
  493. #
  494. ttrace::addtrace namespace {cmdline code args} {
  495. if {$code != 0} {
  496. return
  497. }
  498. set nop [lindex $cmdline 1]
  499. set cns [uplevel namespace current]
  500. if {$cns == "::"} {
  501. set cns ""
  502. }
  503. switch -glob $nop {
  504. eva* {
  505. set nsp [lindex $cmdline 2]
  506. if {![string match "::*" $nsp]} {
  507. set nsp ${cns}::$nsp
  508. }
  509. ttrace::addentry namespace $nsp 1
  510. }
  511. imp* {
  512. # - parse import arguments (skip opt "-force")
  513. set opts [lrange $cmdline 2 end]
  514. if {[string match "-fo*" [lindex $opts 0]]} {
  515. set opts [lrange $cmdline 3 end]
  516. }
  517. # - register all imported procs and commands
  518. foreach opt $opts {
  519. if {![string match "::*" [::namespace qual $opt]]} {
  520. set opt ${cns}::$opt
  521. }
  522. # - first import procs
  523. foreach entry [ttrace::getentries proc $opt] {
  524. set cmd ${cns}::[::namespace tail $entry]
  525. set nsp [::namespace qual $entry]
  526. set done($cmd) 1
  527. set entry [list 0 $nsp "" ""]
  528. ttrace::addentry proc $cmd $entry
  529. }
  530. # - then import commands
  531. foreach entry [info commands $opt] {
  532. set cmd ${cns}::[::namespace tail $entry]
  533. set nsp [::namespace qual $entry]
  534. if {[info exists done($cmd)] == 0} {
  535. set entry [list 0 $nsp "" ""]
  536. ttrace::addentry proc $cmd $entry
  537. }
  538. }
  539. }
  540. }
  541. }
  542. }
  543. ttrace::addscript namespace {
  544. append res \n
  545. foreach entry [ttrace::getentries namespace] {
  546. append res "::namespace eval $entry {}" \n
  547. }
  548. return $res
  549. }
  550. #
  551. # Register the "variable" trace. This will create the following key/value
  552. # entry in the "variable" store:
  553. #
  554. # --- key ---- --- value ---
  555. # ::fully::qualified::variable 1
  556. #
  557. # The variable value itself is ignored at the time of
  558. # trace/collection. Instead, we take the real value at the time of script
  559. # generation.
  560. #
  561. ttrace::addtrace variable {cmdline code args} {
  562. if {$code != 0} {
  563. return
  564. }
  565. set opts [lrange $cmdline 1 end]
  566. if {[llength $opts]} {
  567. set cns [uplevel namespace current]
  568. if {$cns == "::"} {
  569. set cns ""
  570. }
  571. foreach {var val} $opts {
  572. if {![string match "::*" $var]} {
  573. set var ${cns}::$var
  574. }
  575. ttrace::addentry variable $var 1
  576. }
  577. }
  578. }
  579. ttrace::addscript variable {
  580. append res \n
  581. foreach entry [ttrace::getentries variable] {
  582. set cns [namespace qual $entry]
  583. set var [namespace tail $entry]
  584. append res "::namespace eval $cns {" \n
  585. append res "::variable $var"
  586. if {[array exists $entry]} {
  587. append res "\n::array set $var [list [array get $entry]]" \n
  588. } elseif {[info exists $entry]} {
  589. append res " [list [set $entry]]" \n
  590. } else {
  591. append res \n
  592. }
  593. append res "}" \n
  594. }
  595. return $res
  596. }
  597. #
  598. # Register the "rename" trace. It will create the following key/value pair
  599. # in "rename" store:
  600. #
  601. # --- key ---- --- value ---
  602. # ::fully::qualified::old ::fully::qualified::new
  603. #
  604. # The "new" value may be empty, for commands that have been deleted. In
  605. # such cases we also remove any traced procedure definitions.
  606. #
  607. ttrace::addtrace rename {cmdline code args} {
  608. if {$code != 0} {
  609. return
  610. }
  611. set cns [uplevel namespace current]
  612. if {$cns == "::"} {
  613. set cns ""
  614. }
  615. set old [lindex $cmdline 1]
  616. if {![string match "::*" $old]} {
  617. set old ${cns}::$old
  618. }
  619. set new [lindex $cmdline 2]
  620. if {$new != ""} {
  621. if {![string match "::*" $new]} {
  622. set new ${cns}::$new
  623. }
  624. ttrace::addentry rename $old $new
  625. } else {
  626. ttrace::delentry proc $old
  627. }
  628. }
  629. ttrace::addscript rename {
  630. append res \n
  631. foreach old [ttrace::getentries rename] {
  632. set new [ttrace::getentry rename $old]
  633. append res "::rename $old {$new}" \n
  634. }
  635. return $res
  636. }
  637. #
  638. # Register the "proc" trace. This will create the following key/value pair
  639. # in the "proc" store:
  640. #
  641. # --- key ---- --- value ---
  642. # ::fully::qualified::proc [list <epoch> <ns> <arglist> <body>]
  643. #
  644. # The <epoch> chages anytime one (re)defines a proc. The <ns> is the
  645. # namespace where the command was imported from. If empty, the <arglist>
  646. # and <body> will hold the actual procedure definition. See the
  647. # "namespace" tracer implementation also.
  648. #
  649. ttrace::addtrace proc {cmdline code args} {
  650. if {$code != 0} {
  651. return
  652. }
  653. set cns [uplevel namespace current]
  654. if {$cns == "::"} {
  655. set cns ""
  656. }
  657. set cmd [lindex $cmdline 1]
  658. if {![string match "::*" $cmd]} {
  659. set cmd ${cns}::$cmd
  660. }
  661. set dargs [info args $cmd]
  662. set pbody [info body $cmd]
  663. set pargs ""
  664. foreach arg $dargs {
  665. if {![info default $cmd $arg def]} {
  666. lappend pargs $arg
  667. } else {
  668. lappend pargs [list $arg $def]
  669. }
  670. }
  671. set pdef [ttrace::getentry proc $cmd]
  672. if {$pdef == ""} {
  673. set epoch -1 ; # never traced before
  674. } else {
  675. set epoch [lindex $pdef 0]
  676. }
  677. ttrace::addentry proc $cmd [list [incr epoch] "" $pargs $pbody]
  678. }
  679. ttrace::addscript proc {
  680. return {
  681. if {[info command ::tcl::unknown] == ""} {
  682. rename ::unknown ::tcl::unknown
  683. namespace import -force ::ttrace::unknown
  684. }
  685. if {[info command ::tcl::info] == ""} {
  686. rename ::info ::tcl::info
  687. }
  688. proc ::info args {
  689. set cmd [lindex $args 0]
  690. set hit [lsearch -glob {commands procs args default body} $cmd*]
  691. if {$hit > 1} {
  692. if {[catch {uplevel ::tcl::info $args}]} {
  693. uplevel ttrace::_resolve [list [lindex $args 1]]
  694. }
  695. return [uplevel ::tcl::info $args]
  696. }
  697. if {$hit == -1} {
  698. return [uplevel ::tcl::info $args]
  699. }
  700. set cns [uplevel namespace current]
  701. if {$cns == "::"} {
  702. set cns ""
  703. }
  704. set pat [lindex $args 1]
  705. if {![string match "::*" $pat]} {
  706. set pat ${cns}::$pat
  707. }
  708. set fns [ttrace::getentries proc $pat]
  709. if {[string match $cmd* commands]} {
  710. set fns [concat $fns [ttrace::getentries xotcl $pat]]
  711. }
  712. foreach entry $fns {
  713. if {$cns != [namespace qual $entry]} {
  714. set lazy($entry) 1
  715. } else {
  716. set lazy([namespace tail $entry]) 1
  717. }
  718. }
  719. foreach entry [uplevel ::tcl::info $args] {
  720. set lazy($entry) 1
  721. }
  722. array names lazy
  723. }
  724. }
  725. }
  726. #
  727. # Register procedure resolver. This will try to resolve the command in the
  728. # current namespace first, and if not found, in global namespace. It also
  729. # handles commands imported from other namespaces.
  730. #
  731. ttrace::addresolver resolveprocs {cmd {export 0}} {
  732. set cns [uplevel namespace current]
  733. set name [namespace tail $cmd]
  734. if {$cns == "::"} {
  735. set cns ""
  736. }
  737. if {![string match "::*" $cmd]} {
  738. set ncmd ${cns}::$cmd
  739. set gcmd ::$cmd
  740. } else {
  741. set ncmd $cmd
  742. set gcmd $cmd
  743. }
  744. set pdef [ttrace::getentry proc $ncmd]
  745. if {$pdef == ""} {
  746. set pdef [ttrace::getentry proc $gcmd]
  747. if {$pdef == ""} {
  748. return 0
  749. }
  750. set cmd $gcmd
  751. } else {
  752. set cmd $ncmd
  753. }
  754. set epoch [lindex $pdef 0]
  755. set pnsp [lindex $pdef 1]
  756. if {$pnsp != ""} {
  757. set nsp [namespace qual $cmd]
  758. if {$nsp == ""} {
  759. set nsp ::
  760. }
  761. set cmd ${pnsp}::$name
  762. if {[resolveprocs $cmd 1] == 0 && [info commands $cmd] == ""} {
  763. return 0
  764. }
  765. namespace eval $nsp "namespace import -force $cmd"
  766. } else {
  767. uplevel 0 [list ::proc $cmd [lindex $pdef 2] [lindex $pdef 3]]
  768. if {$export} {
  769. set nsp [namespace qual $cmd]
  770. if {$nsp == ""} {
  771. set nsp ::
  772. }
  773. namespace eval $nsp "namespace export $name"
  774. }
  775. }
  776. variable resolveproc
  777. set resolveproc($cmd) $epoch
  778. return 1
  779. }
  780. #
  781. # For XOTcl, the entire item introspection/tracing is delegated to XOTcl
  782. # itself. The xotcl store is filled with this:
  783. #
  784. # --- key ---- --- value ---
  785. # ::fully::qualified::item <body>
  786. #
  787. # The <body> is the script used to generate the entire item (class,
  788. # object). Note that we do not fill in this during code tracing. It is
  789. # done during the script generation. In this step, only the placeholder is
  790. # set.
  791. #
  792. # NOTE: we assume all XOTcl commands are imported in global namespace
  793. #
  794. ttrace::atenable XOTclEnabler {args} {
  795. if {[info commands ::xotcl::Class] == ""} {
  796. return
  797. }
  798. if {[info commands ::xotcl::_creator] == ""} {
  799. ::xotcl::Class create ::xotcl::_creator -instproc create {args} {
  800. set result [next]
  801. if {![string match ::xotcl::_* $result]} {
  802. ttrace::addentry xotcl $result ""
  803. }
  804. return $result
  805. }
  806. }
  807. ::xotcl::Class instmixin ::xotcl::_creator
  808. }
  809. ttrace::atdisable XOTclDisabler {args} {
  810. if { [info commands ::xotcl::Class] == ""
  811. || [info commands ::xotcl::_creator] == ""} {
  812. return
  813. }
  814. ::xotcl::Class instmixin ""
  815. ::xotcl::_creator destroy
  816. }
  817. set resolver [ttrace::addresolver resolveclasses {classname} {
  818. set cns [uplevel namespace current]
  819. set script [ttrace::getentry xotcl $classname]
  820. if {$script == ""} {
  821. set name [namespace tail $classname]
  822. if {$cns == "::"} {
  823. set script [ttrace::getentry xotcl ::$name]
  824. } else {
  825. set script [ttrace::getentry xotcl ${cns}::$name]
  826. if {$script == ""} {
  827. set script [ttrace::getentry xotcl ::$name]
  828. }
  829. }
  830. if {$script == ""} {
  831. return 0
  832. }
  833. }
  834. uplevel [list namespace eval $cns $script]
  835. return 1
  836. }]
  837. ttrace::addscript xotcl [subst -nocommands {
  838. if {![catch {Serializer new} ss]} {
  839. foreach entry [ttrace::getentries xotcl] {
  840. if {[ttrace::getentry xotcl \$entry] == ""} {
  841. ttrace::addentry xotcl \$entry [\$ss serialize \$entry]
  842. }
  843. }
  844. \$ss destroy
  845. return {::xotcl::Class proc __unknown name {$resolver \$name}}
  846. }
  847. }]
  848. #
  849. # Register callback to be called on cleanup. This will trash lazily loaded
  850. # procs which have changed since.
  851. #
  852. ttrace::addcleanup {
  853. variable resolveproc
  854. foreach cmd [array names resolveproc] {
  855. set def [ttrace::getentry proc $cmd]
  856. if {$def != ""} {
  857. set new [lindex $def 0]
  858. set old $resolveproc($cmd)
  859. if {[info command $cmd] != "" && $new != $old} {
  860. catch {rename $cmd ""}
  861. }
  862. }
  863. }
  864. }
  865. }
  866. # EOF
  867. return
  868. # Local Variables:
  869. # mode: tcl
  870. # fill-column: 78
  871. # tab-width: 8
  872. # indent-tabs-mode: nil
  873. # End: