blob: 036b2e38be3c4bbd65652031fb17ec402e577ca4 [file] [log] [blame]
# File src/library/methods/R/trace.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2016 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
## some temporary (!) hooks to trace the tracing code
.doTraceTrace <- function(on) {
.assignOverBinding(".traceTraceState", on,
environment(.doTraceTrace), FALSE)
on
}
.traceTraceState <- FALSE
## the internal functions in the evaluator. These are all prohibited,
## although some of them could just barely be accomodated, with some
## specially designed new definitions (not using ..., for example).
## The gain does not seem worth the inconsistencies; and "if" can
## never be traced, since it has to be used to determine if tracing is
## on. (see .doTrace())
## The remaining invalid functions create miscellaneous bugs, maybe
## related to the use of "..." as the introduced arguments. Aside from
## .Call, tracing them seems of marginal value.
.InvalidTracedFunctions <- c("if", "where", "for", "repeat", "(", "{",
"next", "break", ".Call", ".Internal", ".Primitive")
.TraceWithMethods <- function(what, tracer = NULL, exit = NULL, at = numeric(),
print = TRUE, signature = NULL,
where = .GlobalEnv, edit = FALSE, from = NULL,
untrace = FALSE, classMethod = FALSE) {
fromPackage <-
if(is.function(where)) {
## start from the function's environment: important for
## tracing from a namespace
where <- if(is(where, "genericFunction"))
parent.env(environment(where))
else
environment(where)
getPackageName(where)
} else ""
doEdit <- !isFALSE(edit)
whereF <- NULL
pname <- character()
def <- NULL
tracingWhere <- "in package"
refCase <- isS4(where) && (is(where, "envRefClass") ||
is(where, "refClassRepresentation"))
if(refCase) {
## some error checking
if(!is.null(signature))
stop("argument 'signature' is not meaningful for tracing reference methods")
.where <- where # to avoid substituting where in the eval() below
## A reference class object or its class or its generator
if(is(.where, "refGeneratorSlot") && !classMethod)
.where <- .where$def # should now be the refClassRepresentation
if(is(.where, "refClassRepresentation")) {
pname <- .where@className
.where <- .where@refMethods
tracingWhere <- "for class"
}
else {
tracingWhere <- "for object from class"
pname <- class(.where)
}
## interpret as tracing .where$what
def <- eval(substitute(.dollarForEnvRefClass(.where, what)))
if(!is(def, "refMethodDef")) {
thisName <- substitute(what)
stop(gettextf("%s is not a method for reference class %s",
sQuote(as.character(if(is.symbol(thisName)) thisName
else what)),
dQuote(class(where))),
domain = NA)
}
what <- def@name
whereF <- .where
}
else if(is.function(what)) {
def <- what
if(is(def, "genericFunction")) {
what <- def@generic
whereF <- .genEnv(what, where)
pname <- def@package
}
else {
fname <- substitute(what)
if(is.name(fname)) {
what <- as.character(fname)
temp <- .findFunEnvAndName(what, where)
whereF <- temp$whereF
pname <- temp$pname
}
else if(is.call(fname) && identical(fname[[1L]], as.name("::"))) {
whereF <- as.character(fname[[2L]])
require(whereF, character.only = TRUE)
whereF <- as.environment(paste0("package:", whereF))
pname <- fname[[2L]]
what <- as.character(fname[[3L]])
}
else if(is.call(fname) && identical(fname[[1L]], as.name(":::"))) {
pname <- paste(fname[[2L]], "(not-exported)")
whereF <- loadNamespace(as.character(fname[[2L]]))
what <- as.character(fname[[3L]])
}
else
stop("argument 'what' should be the name of a function")
}
}
else {
what <- as(what, "character")
if(length(what) != 1) {
for(f in what) {
if(nargs() == 1)
trace(f)
else
Recall(f, tracer, exit, at, print, signature, where, edit, from, untrace)
}
return(what)
}
temp <- .findFunEnvAndName(what, where, signature)
whereF <- temp$whereF
pname <- temp$pname
fname <- what
}
if(what %in% .InvalidTracedFunctions)
stop(gettextf("tracing the internal function %s is not allowed",
sQuote(what)), domain = NA)
if(.traceTraceState) {
message(".TraceWithMethods: after computing what, whereF", domain = NA)
browser()
}
if(nargs() == 1) # for back compatibility
return(if(untrace) .primUntrace(what) else .primTrace(what))
## FIXME: for trace(stats:::.....) we really want -- how can this be solved
## return(if(untrace) .primUntrace(frame) else .primTrace(fname))
if(is.null(whereF)) {
allWhere <- findFunction(what, where = where)
if(length(allWhere)==0)
stop(gettextf("no function definition for %s found", sQuote(what)),
domain = NA)
whereF <- as.environment(allWhere[[1L]])
}
## detect use with no action specified (old-style R trace())
if(is.null(tracer) && is.null(exit) && isFALSE(edit))
tracer <- quote({})
if(is.null(def))
def <- getFunction(what, where = whereF)
if(is(def, "traceable") && isFALSE(edit) && !untrace)
def <- .untracedFunction(def)
if(!is.null(signature)) {
fdef <- if (!is(def, "genericFunction"))
getGeneric(as.character(fname), TRUE, where)
else def
def <- selectMethod(what, signature, fdef = fdef, optional = TRUE)
if(isRematched(def)) {
expr <- substitute(trace(.local, tracer = tr, at = at,
exit = ex, print = pr,
edit = ed,
where = sys.frame(sys.nframe())),
list( tr = substitute(tracer),
ex = exit, at = at, pr = print,
ed = edit))
at <- 3L
tracer <- expr
print <- FALSE
}
if(is.null(def)) {
warning(gettextf("cannot untrace method for %s; no method defined for this signature: %s",
sQuote(what),
paste(signature, collapse = ", ")),
domain = NA)
return(def)
}
## pick up signature with package slot from selectMethod
signature <- def@target
}
if(untrace) {
if(.traceTraceState) {
message(".TraceWithMethods: untrace case", domain = NA)
browser()
}
if(is.null(signature)) {
## ensure that the version to assign is untraced
if(is(def, "traceable")) {
newFun <- .untracedFunction(def)
}
else {
.primUntrace(what) # to be safe--no way to know if it's traced or not
### or sometimes rather _FIXME_ ?
### .primUntrace(fname) # (rather than 'what')
return(what)
}
}
else {
if(is(def, "traceable"))
newFun <- .untracedFunction(def)
else {
warning(gettextf("the method for %s for this signature was not being traced",
sQuote(what)),
domain = NA)
return(what)
}
}
}
else {
if(!is.null(exit)) {
if(is.function(exit)) {
tname <- substitute(exit)
if(is.name(tname))
exit <- tname
exit <- substitute(TRACE(), list(TRACE=exit))
}
}
if(!is.null(tracer)) {
if(is.function(tracer)) {
tname <- substitute(tracer)
if(is.name(tname))
tracer <- tname
tracer <- substitute(TRACE(), list(TRACE=tracer))
}
}
original <- .untracedFunction(def)
traceClass <- .traceClassName(class(original))
if(is.null(getClassDef(traceClass)))
traceClass <- .makeTraceClass(traceClass, class(original))
if(doEdit && is.environment(edit)) {
## trace with the version found in the edit environment
def <- .findNewDefForTrace(what, signature, edit, fromPackage)
environment(def) <- environment(original)
if(is.null(c(tracer, exit))) {
newFun <- new(traceClass, original)
newFun@.Data <- def
}
else {
newFun <- new(traceClass, def = def, tracer = tracer, exit = exit, at = at, print = print, doEdit = FALSE)
newFun@original <- original # left as def by initialize method
}
newFun@source <- edit
}
else
newFun <- new(traceClass,
def = if(doEdit) def else original, tracer = tracer, exit = exit, at = at,
print = print, doEdit = edit)
}
global <- identical(whereF, .GlobalEnv)
if(.traceTraceState) {
message(".TraceWithMethods: about to assign or setMethod", domain = NA)
browser()
}
if(is.null(signature)) {
if(bindingIsLocked(what, whereF))
.assignOverBinding(what, newFun, whereF, global)
else
assign(what, newFun, whereF)
if (length(pname) != 0) {
## update the function also in "imports:" environments of already
## loaded packages that import package pname
spname <- sub("^namespace:", "", pname)
# catching error in case when spname is not a name of a namespace, but
# e.g. a reference class
ipkgs <- tryCatch(getNamespaceUsers(spname), error=function(e){c()})
for(importingPkg in ipkgs) {
.updateInImportsEnv(what, newFun, importingPkg)
}
}
if(length(grep("[^.]+[.][^.]+", what)) > 0) { #possible S3 method
## check for a registered version of the object
S3MTableName <- ".__S3MethodsTable__."
if(!is.null(tbl <- get0(S3MTableName, envir = whereF, inherits = FALSE))) {
if(exists(what, envir = tbl, inherits = FALSE)) {
tracedFun <- get(what, envir = whereF, inherits = TRUE)
assign(what, tracedFun, envir = tbl)
}
}
}
}
else {
if(untrace && is(newFun, "MethodDefinition") &&
!identical(newFun@target, newFun@defined))
## we promoted an inherited method for tracing, now we have
## to remove that method. Assertion is that there was no directly
## specified method, or else defined, target would be identical
newFun <- NULL
## arrange for setMethod to put the new method in the generic
## but NOT to assign the methods list object (binding is ignored)
setMethod(fdef, signature, newFun, where = baseenv())
}
if(!global) {
action <- if(untrace)"Untracing" else "Tracing"
nameSpaceCase <- FALSE
location <- if(.identC(fromPackage, "")) {
if(length(pname)==0 && !is.null(whereF))
pname <- getPackageName(whereF)
nameSpaceCase <- isNamespace(whereF) &&
!is.na(match(pname, loadedNamespaces())) &&
identical(whereF, getNamespace(pname))
if(length(pname)==0) # but not possible from getPackagename ?
""
else {
if(nameSpaceCase)
paste0(" in environment <namespace:", pname, ">")
else
paste0(" ", tracingWhere, " \"", pname, "\"")
}
}
else paste0(" as seen from package \"", fromPackage, "\"")
object <- if(refCase) "reference method"
else if(is.null(signature)) "function"
else "specified method for function"
object <- paste0(" ", object, " \"", what, "\" ")
.message(action, object, location)
## tracing methods (signature not null) works without setting where
if(nameSpaceCase && !untrace && is.null(signature) && exists(what, envir = .GlobalEnv)) {
untcall <- paste0("untrace(\"", what,
"\", where = getNamespace(\"", pname, "\"))")
.message("Warning: Tracing only in the namespace; to untrace you will need:\n ",
untcall, "\n")
}
}
what
}
.makeTracedFunction <- function(def, tracer, exit, at, print, doEdit) {
switch(typeof(def),
builtin = {
fBody <- substitute({.prim <- DEF; .prim(...)},
list(DEF = def))
def <- eval(function(...)NULL)
body(def, envir = .GlobalEnv) <- fBody
},
special = {
fBody <- substitute({do.call(DEF, list(...))},
list(DEF = def))
def <- eval(function(...)NULL)
body(def, envir = .GlobalEnv) <- fBody
warning("making a traced version of a special; arguments may be altered")
}
)
if(!isFALSE(doEdit)) {
if(is.character(doEdit) || is.function(doEdit)) {
editor <- doEdit
doEdit <- TRUE
}
else
editor <- getOption("editor")
}
## look for a request to edit the definition
if(doEdit) {
if(is(def, "traceable"))
def <- as(def, "function") # retain previous tracing if editing
if(is(editor, "character") && !is.na(match(editor, c("emacs","xemacs")))) {
## cater to the usual emacs modes for editing R functions
file <- tempfile("emacs")
file <- sub('..$', ".R", file)
}
else
file <- ""
## insert any requested automatic tracing expressions before editing
if(!(is.null(tracer) && is.null(exit) && length(at)==0))
def <- Recall(def, tracer, exit, at, print, FALSE)
def2 <- utils::edit(def, editor = editor, file = file)
if(!is.function(def2))
stop(gettextf("the editing in trace() can only change the body of the function; got an object of class %s",
dQuote(class(def2))),
domain = NA)
if(!identical(args(def), args(def2)))
stop("the editing in trace() can only change the body of the function, not the arguments or defaults")
fBody <- body(def2)
}
else {
def <- .untracedFunction(def) # throw away earlier tracing
fBody <- body(def)
if(length(at) > 0) {
if(is.null(tracer))
stop("cannot use 'at' argument without a trace expression")
else if(!inherits(fBody, "{"))
stop("cannot use 'at' argument unless the function body has the form '{ ... }'")
for(i in at) {
fBody[[i]] <-
if(print)
substitute({.doTrace(TRACE, MSG); EXPR},
list(TRACE = tracer,
MSG = paste("step",paste(i, collapse=",")),
EXPR = fBody[[i]]))
else
substitute({.doTrace(TRACE); EXPR},
list(TRACE=tracer, EXPR = fBody[[i]]))
}
}
else if(!is.null(tracer)){
fBody <-
if(print)
substitute({.doTrace(TRACE, MSG); EXPR},
list(TRACE = tracer, MSG = paste("on entry"), EXPR = fBody))
else
substitute({.doTrace(TRACE); EXPR},
list(TRACE=tracer, EXPR = fBody))
}
if(!is.null(exit)) {
exit <-
if(print)
substitute(.doTrace(EXPR, MSG),
list(EXPR = exit, MSG = paste("on exit")))
else
substitute(.doTrace(EXPR),
list(EXPR = exit))
fBody <- substitute({on.exit(TRACE); BODY},
list(TRACE=exit, BODY=fBody))
}
}
body(def, envir = environment(def)) <- fBody
def
}
## return the untraced version of f
.untracedFunction <- function(f) {
while(is(f, "traceable"))
f <- f@original
f
}
.InitTraceFunctions <- function(envir) {
setClass("traceable", representation(original = "PossibleMethod", source = "environment"), contains = "VIRTUAL",
where = envir); clList <- "traceable"
## create the traceable classes
for(cl in c("function", "MethodDefinition", "MethodWithNext", "genericFunction",
"standardGeneric", "nonstandardGeneric", "groupGenericFunction",
"derivedDefaultMethod")) {
.makeTraceClass(.traceClassName(cl), cl, FALSE)
clList <- c(clList, .traceClassName(cl))
}
setClass("sourceEnvironment", contains = "environment",
representation(packageName = "character", dateCreated = "POSIXt", sourceFile = "character"),
prototype = prototype( packageName = "", dateCreated = Sys.time(), sourceFile = ""))
clList <- c(clList, "sourceEnvironment")
assign(".SealedClasses", c(get(".SealedClasses", envir), clList), envir)
setMethod("initialize", "traceable",
function(.Object, ...) .initTraceable(.Object, ...),
where = envir)
if(!isGeneric("show", envir))
setGeneric("show", where = envir, simpleInheritanceOnly = TRUE)
setMethod("show", "traceable", .showTraceable, where = envir)
setMethod("show", "sourceEnvironment", .showSource, where = envir)
}
## allow control over whether methods & classes are cached when assigned
## to a particular environment. defaults to TRUE
cacheOnAssign <- function(env) is.null(env$.cacheOnAssign) || env$.cacheOnAssign
setCacheOnAssign <- function(env, onOff = cacheOnAssign(env))
env$.cacheOnAssign <- if(onOff) TRUE else FALSE
.showTraceable <- function(object) {
if(identical(object@source, emptyenv())) {
cat("Object with tracing code, class \"", class(object),
"\"\nOriginal definition: \n", sep="")
callGeneric(object@original)
cat("\n## (to see the tracing code, look at body(object))\n")
}
else {
cat("Object of class \"", class(object),
"\", from source\n", sep = "")
callGeneric(object@.Data)
cat("\n## (to see original from package, look at object@original)\n")
}
}
.initTraceable <- function(.Object, def, tracer, exit, at, print, doEdit) {
.Object@source <- emptyenv()
if(missing(def))
return(.Object)
oldClass <- class(def)
oldClassDef <- getClass(oldClass)
if(!is.null(oldClassDef) && length(oldClassDef@slots) > 0)
as(.Object, oldClass) <- def # to get other slots in def
.Object@original <- def
if(nargs() > 2) {
if(!is.null(elNamed(getSlots(getClass(class(def))), ".Data")))
def <- def@.Data
.Object@.Data <- .makeTracedFunction(def, tracer, exit, at, print, doEdit)
}
.Object
}
.showSource <- function(object) {
cat("Object of class \"", class(object), "\"\n", sep = "")
cat("Source environment created ", format(object@dateCreated), "\n")
if(nzchar(object@packageName))
cat("For package \"",object@packageName, "\"\n", sep = "")
if(nzchar(object@sourceFile))
cat("From source file \"", object@sourceFile, "\"\n", sep = "")
}
.doTracePrint <- function(msg = "") {
call <- deparse(sys.call(sys.parent(1)))
if(length(call)>1)
call <- paste(call[[1L]], "....")
cat("Tracing", call, msg, "\n")
}
.traceClassName <- function(className) {
className[] <- paste0(className, "WithTrace")
className
}
.assignOverBinding <- function(what, value, where, verbose = TRUE) {
if(verbose) {
pname <- getPackageName(where)
msg <-
gettextf("assigning over the binding of symbol %s in environment/package %s",
sQuote(what), sQuote(pname))
message(strwrap(msg), domain = NA)
}
warnOpt <- options(warn= -1) # kill the obsolete warning from R_LockBinding
on.exit(options(warnOpt))
if(is.function(value)) {
## assign in the namespace for the function as well
fenv <- environment(value)
if(is.null(fenv)) # primitives
fenv <- baseenv()
if(!identical(fenv, where) && exists(what, envir = fenv, inherits = FALSE #?
) && bindingIsLocked(what, fenv)) {
unlockBinding(what, fenv)
assign(what, value, fenv)
lockBinding(what, fenv)
}
}
if(exists(what, envir = where, inherits = FALSE) && bindingIsLocked(what, where)) {
unlockBinding(what, where)
assign(what, value, where)
lockBinding(what, where)
}
else
assign(what, value, where)
}
.setMethodOverBinding <- function(what, signature, method, where, verbose = TRUE) {
if(verbose)
warning(gettextf("setting a method over the binding of symbol %s in environment/package %s",
sQuote(what),
sQuote(getPackageName(where))),
domain = NA)
if(exists(what, envir = where, inherits = FALSE)) {
fdef <- get(what, envir = where)
hasFunction <- is(fdef, "genericFunction")
}
hasFunction <- FALSE
if(hasFunction) {
## find the generic in the corresponding namespace
where2 <- findFunction(what, where = environment(fdef))[[1L]] # must find it?
unlockBinding(what, where)
setMethod(what, signature, method, where = where)
lockBinding(what, where)
## assign in the package namespace as well
unlockBinding(what, where2)
setMethod(what, signature, method, where = where2)
lockBinding(what, where2)
}
else {
setMethod(what, signature, method, where = where)
}
}
.getImportsEnv <- function(pkg) {
iname = paste0("imports:", pkg)
empty = emptyenv()
env = asNamespace(pkg)
while(!identical(env, empty)) {
if (identical(attr(env, "name"), iname))
return(env)
env = parent.env(env)
}
NULL
}
.updateInImportsEnv <- function(what, newFun, importingPkg) {
where = .getImportsEnv(importingPkg)
if (!is.null(where) && (what %in% names(where))) {
.assignOverBinding(what, newFun, where, FALSE)
}
}
### finding the package name for a loaded namespace
.searchNamespaceNames <- function(env)
paste0("namespace:", getNamespaceName(env))
.findFunEnvAndName <- function(what, where, signature = NULL) {
pname <- character()
if(is.null(signature)) {
whereF <- findFunction(what, where = where)
if(length(whereF)>0)
whereF <- whereF[[1L]]
else return(list(pname = pname, whereF = baseenv()))
} else
whereF <- .genEnv(what, where)
## avoid partial matches to "names"
if("name" %in% names(attributes(whereF)))
pname <- gsub("^.*:", "", attr(whereF, "name"))
else if(isNamespace(whereF))
pname <- .searchNamespaceNames(whereF)
list(pname = pname, whereF = whereF)
}
.makeTraceClass <- function(traceClassName, className, verbose = TRUE) {
## called because the traceClassName not a class
## first check whether it may exist but not in the same package
if(isClass(as.character(traceClassName)))
return(as.character(traceClassName))
if(verbose)
message(sprintf("Constructing traceable class %s", dQuote(traceClassName)),
domain = NA)
env <- .classEnv(className)
if(environmentIsLocked(env)) {
message(gettextf("Environment of class %s is locked; using global environment for new class",
dQuote(className)),
domain = NA)
env <- .GlobalEnv
packageSlot(traceClassName) <- NULL
}
setClass(traceClassName,
contains = c(className, "traceable"), where = env)
if(existsMethod("show", className, env)) # override it for traceClassName
setMethod("show", traceClassName, .showTraceable)
traceClassName
}
utils::globalVariables("fdef")
.dummySetMethod <- function(f, signature = character(), definition,
where = topenv(parent.frame()), valueClass = NULL,
sealed = FALSE)
{
if(is.function(f) && is(f, "genericFunction"))
f <- fdef@generic
else if(is.function(f)) {
if(is.primitive(f))
f <- .primname(f)
else
stop("a function for argument 'f' must be a generic function")
} else
f <- switch(f, "as.double" = "as.numeric", f)
assign(.dummyMethodName(f, signature), definition, envir = where)
}
.functionsOverriden <- c("setClass", "setClassUnion", "setGeneric", "setIs", "setMethod", "setValidity")
.setEnvForSource <- function(env) {
doNothing <- function(x, ...)x
## establish some dummy definitions & a special setMethod()
for(f in .functionsOverriden)
assign(f, switch(f, setMethod = .dummySetMethod, doNothing),
envir = env)
env
}
.dummyMethodName <- function(f, signature)
paste(c(f,signature), collapse="#")
.guessPackageName <- function(env) {
allObjects <- names(env)
allObjects <- allObjects[is.na(match(allObjects, .functionsOverriden))]
## counts of packaages containing objects; objects not found don't count
possible <- sort(table(unlist(lapply(allObjects, utils::find))),
decreasing = TRUE)
## message <- ""
if(length(possible) == 0)
stop("none of the objects in the source code could be found: need to attach or specify the package")
else if(length(possible) > 1L) {
global <- match(".GlobalEnv", names(possible), 0)
if(global > 0) {
possible <- possible[-global] # even if it's the most common
}
if(length(possible) > 1L)
warning(gettextf("objects found in multiple packages: using %s and ignoring %s",
sQuote(names(possible[[1L]])),
paste(sQuote(names(possible[-1L])),
collapse = ", ")),
domain = NA)
}
.rmpkg(names(possible[1L])) # the package name, or .GlobalEnv
}
## extract the new definitions from the source file
evalSource <- function(source, package = "", lock = TRUE, cache = FALSE) {
if(!nzchar(package))
envp <- .GlobalEnv # will look for the package after evaluating source
else {
pstring <- paste0("package:",package)
packageIsVisible <- pstring %in% search()
if(packageIsVisible) {
envp <- as.environment(pstring)
## envns <- tryCatch(asNamespace(package), error = function(cond) NULL)
}
else {
envp <- tryCatch(asNamespace(package), error = function(cond) NULL)
## envns <- envp
}
if(is.null(envp))
stop(gettextf("package %s is not attached and no namespace found for it",
sQuote(package)),
domain = NA)
}
env <- new("sourceEnvironment", new.env(parent = envp),
packageName = package,
sourceFile = (if(is.character(source)) source else ""))
env$.packageName <- package # Fixme: should be done by an initialize method
setCacheOnAssign(env, cache)
if(is(source, "character"))
for(text in source) sys.source(text, envir = env)
else if(is(source, "connection")) sys.source(source, envir = env)
else if(!is(source, "environment"))
stop(gettextf("invalid 'source' argument: expected file names or a connection but got an object of class %s",
dQuote(class(source)[[1L]])),
domain = NA)
if(lock)
lockEnvironment(env, bindings = TRUE) # no further changes allowed
env
}
insertSource <- function(source, package = "",
functions = allPlainObjects(),
methods = (if(missing(functions)) allMethodTables() else NULL)
## ,classes = (if(missing(functions)) allClassDefs() else NULL)
, force = missing(functions) & missing(methods)
){
MPattern <- .TableMetaPattern()
CPattern <- .ClassMetaPattern()
allPlainObjects <- function()
allObjects[!(grepl(MPattern, allObjects) | grepl(CPattern, allObjects) | ".cacheOnAssign" == allObjects)]
allMethodTables <- function()
allObjects[grepl(MPattern, allObjects)]
## allClassDefs <- function()
## allObjects[grepl(CPattern, allObjects)]
differs <- function(f1, f2)
!(identical(body(f1), body(f2)) && identical(args(f1), args(f2)))
if(is.environment(source) && !nzchar(package)) {
if(is(source, "sourceEnvironment"))
package <- source@packageName
else if(exists(".packageName", envir = source, inherits = FALSE))
package <- get(".packageName", envir =source)
}
if(is(source, "environment"))
env <- source
else
env <- evalSource(source, package, FALSE) # sourceEnvironment, unlocked
envPackage <- getPackageName(env, FALSE)
## identify an environment and (if possible) namespace for the package
envp <- parent.env(env)
if(identical(envp, .GlobalEnv) || !nzchar(envPackage)) { # no package name in the eval, guess one
if(!nzchar(package))
package <- .guessPackageName(env) # use find() on objects in env
if(identical(package, ".GlobalEnv"))
envns <- NULL
else {
pname <- paste0("package:", package)
envp <- tryCatch(as.environment(pname), error = function(cond)NULL)
if(is.null(envp)) {
envp <- tryCatch(as.environment(pname), error = function(cond)NULL)
if(is.null(envp))
stop(gettextf(
"cannot find an environment corresponding to package name \'%s\"",
package), domain = NA)
}
envns <- tryCatch(asNamespace(package), error = function(cond)NULL)
}
if(nzchar(package))
assign(".packageName", package, envir = env)
}
else {
if(isNamespace(envp))
envns <- envp
else
envns <- tryCatch(asNamespace(package), error = function(cond)NULL)
}
if(nzchar(envPackage) && envPackage != package)
warning(gettextf("supplied package, %s, differs from package inferred from source, %s",
sQuote(package), sQuote(envPackage)),
domain = NA)
packageSlot(env) <- package
## at this point, envp is the target environment (package or other)
## and envns is the corresponding namespace if any, or NULL
allObjects <- names(env)
## Figure out what to trace.
if(!missing(functions)) {
notThere <- is.na(match(functions, allObjects))
if(any(notThere)) {
warning(gettextf("cannot insert these (not found in source): %s",
paste0('"', functions[notThere], '"',
collapse = ", ")),
domain = NA)
}
}
.mnames <- allMethodTables()
if(length(methods) > 0) {
notThere <- vapply(methods, function(fname)
length(grep(fname, .mnames, fixed = TRUE)) == 0, NA)
if(any(notThere)) {
warning(gettextf("cannot insert methods for these functions (methods table not found in source): %s",
paste0('"', methods[notThere], '"',
collapse = ", ")),
domain = NA)
methods <- methods[!notThere]
}
methodNames <- vapply(methods, function(fname)
.mnames[[grep(fname, .mnames, fixed = TRUE)[[1]]]], "")
}
else {
methodNames <- .mnames
methods <- sub(.TableMetaPrefix(), "", methodNames)
methods <- sub(":.*","",methods)
}
## if(!missing(classes)) {
## .mnames <- allMethodNames()
## notThere <- sapply(classes,
## function(fname) length(grep(fname, .mnames, fixed = TRUE) == 0)
## )
## if(any(notThere)) {
## warning(gettextf("Can't insert these classes (class definition not found in source): %s",
## paste('"',classes[notThere],'"',
## sep = "", collapse = ", ")),
## domain = NA)
## classes <- classes[!notThere]
## }
## }
notTraceable <- newObjects <- objectsDone <- character()
for(i in seq_along(functions)) {
this <- functions[[i]]
thisWhere <- NULL
if(is.null(envns) ||
exists(this, envir = envp, inherits = FALSE)) {
envwhere <- envp
thisWhere <- get(this, envir = envp)
}
else {
envwhere <- envns
if(is.environment(envns) &&
exists(this, envir = envns, inherits = FALSE))
thisWhere <- get(this, envir = envns)
}
thisObj <- get(this, envir = env)
if(is.function(thisObj) && is.function(thisWhere)
&& differs(thisObj, thisWhere)) {
suppressMessages(
.TraceWithMethods(this, where = envwhere, edit = env))
objectsDone <- c(objectsDone, this)
}
else if(force)
assign(this, thisObj, envir = envwhere)
else if(!is.function(thisObj))
notTraceable <- c(notTraceable, this)
else if(is.null(thisWhere))
newObjects <- c(newObjects, this)
}
if(length(notTraceable) > 0)
message(gettextf("Non-function objects are not currently inserted (not traceable): %s",
paste(notTraceable, collapse = ", ")), domain = NA)
if(length(newObjects) > 0)
message(gettextf("New functions are not currently inserted (not untraceable): %s",
paste(newObjects, collapse = ", ")), domain = NA)
if(length(objectsDone) > 0)
message(gettextf("Modified functions inserted through trace(): %s",
paste(objectsDone, collapse = ", ")), domain = NA)
for(i in seq_along(methods)) {
.copyMethods(methods[[i]], methodNames[[i]], env, envp)
}
## for(class in classes) {
## .copyClass(class, env, envwhere)
## }
## return the environment, after cleaning up the dummy functions and
## adding a time stamp, if the source was parssed on this call
if(!is.environment(source)) {
lockEnvironment(env, bindings = TRUE) # no further changes allowed
invisible(env)
}
else
invisible(source)
}
.copyMethods <- function(f, tableName, env, envwhere) {
differs <- function(o1, o2)
!(is.function(o2) && # o2 can be NULL
identical(body(o2), body(o2)) && identical(args(o1), args(o2)))
table <- get(tableName, envir=env)
fdef <- getGeneric(f, where = envwhere)
if(!is(fdef, "genericFunction")) {
message(gettextf("%s() is not a generic function in the target environment -- methods will not be inserted",
f), domain = NA)
return(NULL)
}
curTable <- getMethodsForDispatch(fdef)
allObjects <- sort(names(table))
if(length(allObjects) > 0) {
methodsInserted <- as.character(Filter(function(this) {
def <- get(this, envir = table)
curdef <- curTable[[this]]
if(differs(def, curdef)) {
suppressMessages(
.TraceWithMethods(f, signature = this, where = envwhere,
edit = env))
TRUE
} else
FALSE
}, allObjects))
if(length(methodsInserted) > 0)
message(gettextf("Methods inserted for function %s(): %s",
f, paste(methodsInserted, collapse =", ")),
domain = NA)
}
}
.copyClass <- function(class, env, envwhere) {
message("Pretend we inserted class ", class, domain = NA)
}
.findNewDefForTrace <- function(what, signature, env, package) {
if(is.null(signature)) {
if(exists(what, envir = env, inherits = FALSE))
newObject <- get(what, envir = env)
else
stop(gettextf("no definition for object %s found in tracing environment",
sQuote(what), source),
domain = NA)
}
else {
## we don't know the package for the generic (which may not
## be active), so we search for the string w/o package
table <- .TableMetaName(what, "")
allObjects <- sort(names(env))
i <- grep(table, allObjects, fixed = TRUE)
if(length(i) == 1)
table <- env[[allObjects[[i]]]]
else if(length(i) >1) {
table <- allObjects[[i[[1]]]]
warning(gettextf("multiple generics match pattern, using table %s", table)
, domain = NA)
table <- env[[table]]
}
else
stop(gettextf("does not seem to be a method table for generic %s in tracing environment",
sQuote(what)),
domain = NA)
if(exists(signature, envir = table, inherits = FALSE))
newObject <- get(signature, envir = table)
else
stop(gettextf("no method in methods table for %s for signature %s",
sQuote(what),
sQuote(signature)),
domain = NA)
}
newObject
}