/src/r-source/src/main/debug.c
Line | Count | Source |
1 | | /* |
2 | | * R : A Computer Language for Statistical Data Analysis |
3 | | * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka |
4 | | * Copyright (C) 1998-2025 The R Core Team. |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, a copy is available at |
18 | | * https://www.R-project.org/Licenses/ |
19 | | */ |
20 | | |
21 | | #ifdef HAVE_CONFIG_H |
22 | | #include <config.h> |
23 | | #endif |
24 | | |
25 | | #define R_USE_SIGNALS 1 |
26 | | #include <Defn.h> |
27 | | #include <Internal.h> |
28 | | |
29 | | attribute_hidden SEXP do_debug(SEXP call, SEXP op, SEXP args, SEXP rho) |
30 | 0 | { |
31 | 0 | SEXP ans = R_NilValue; |
32 | |
|
33 | 0 | checkArity(op,args); |
34 | 0 | #define find_char_fun \ |
35 | 0 | if (isValidString(CAR(args))) { \ |
36 | 0 | SEXP s; \ |
37 | 0 | PROTECT(s = installTrChar(STRING_ELT(CAR(args), 0))); \ |
38 | 0 | SETCAR(args, findFun(s, rho)); \ |
39 | 0 | UNPROTECT(1); \ |
40 | 0 | } |
41 | 0 | find_char_fun |
42 | |
|
43 | 0 | if (TYPEOF(CAR(args)) != CLOSXP && |
44 | 0 | TYPEOF(CAR(args)) != SPECIALSXP && |
45 | 0 | TYPEOF(CAR(args)) != BUILTINSXP) |
46 | 0 | error(_("argument must be a function")); |
47 | 0 | switch(PRIMVAL(op)) { |
48 | 0 | case 0: // debug() |
49 | 0 | SET_RDEBUG(CAR(args), 1); |
50 | 0 | break; |
51 | 0 | case 1: // undebug() |
52 | 0 | if( RDEBUG(CAR(args)) != 1 ) |
53 | 0 | warning("argument is not being debugged"); |
54 | 0 | SET_RDEBUG(CAR(args), 0); |
55 | 0 | break; |
56 | 0 | case 2: // isdebugged() |
57 | 0 | ans = ScalarLogical(RDEBUG(CAR(args))); |
58 | 0 | break; |
59 | 0 | case 3: // debugonce() |
60 | 0 | SET_RSTEP(CAR(args), 1); |
61 | 0 | break; |
62 | 0 | } |
63 | 0 | return ans; |
64 | 0 | } |
65 | | |
66 | | /* primitives .primTrace() and .primUntrace() */ |
67 | | attribute_hidden SEXP do_trace(SEXP call, SEXP op, SEXP args, SEXP rho) |
68 | 0 | { |
69 | 0 | checkArity(op, args); |
70 | |
|
71 | 0 | find_char_fun |
72 | |
|
73 | 0 | if (TYPEOF(CAR(args)) != CLOSXP && |
74 | 0 | TYPEOF(CAR(args)) != SPECIALSXP && |
75 | 0 | TYPEOF(CAR(args)) != BUILTINSXP) |
76 | 0 | errorcall(call, _("argument must be a function")); |
77 | |
|
78 | 0 | switch(PRIMVAL(op)) { |
79 | 0 | case 0: |
80 | 0 | SET_RTRACE(CAR(args), 1); |
81 | 0 | break; |
82 | 0 | case 1: |
83 | 0 | SET_RTRACE(CAR(args), 0); |
84 | 0 | break; |
85 | 0 | } |
86 | 0 | return R_NilValue; |
87 | 0 | } |
88 | | |
89 | | |
90 | | /* maintain global trace & debug state */ |
91 | | |
92 | | static Rboolean tracing_state = TRUE, debugging_state = TRUE; |
93 | 0 | #define GET_TRACE_STATE tracing_state |
94 | 0 | #define GET_DEBUG_STATE debugging_state |
95 | 0 | #define SET_TRACE_STATE(value) tracing_state = value |
96 | 0 | #define SET_DEBUG_STATE(value) debugging_state = value |
97 | | |
98 | | attribute_hidden SEXP do_traceOnOff(SEXP call, SEXP op, SEXP args, SEXP rho) |
99 | 0 | { |
100 | 0 | checkArity(op, args); |
101 | 0 | SEXP onOff = CAR(args); |
102 | 0 | Rboolean trace = (PRIMVAL(op) == 0), |
103 | 0 | prev = trace ? GET_TRACE_STATE : GET_DEBUG_STATE; |
104 | |
|
105 | 0 | if(length(onOff) > 0) { |
106 | 0 | int _new = asLogical(onOff); |
107 | 0 | if(_new == TRUE || _new == FALSE) |
108 | 0 | if(trace) SET_TRACE_STATE((Rboolean) _new); |
109 | 0 | else SET_DEBUG_STATE((Rboolean) _new); |
110 | 0 | else |
111 | 0 | error(_("Value for '%s' must be TRUE or FALSE"), |
112 | 0 | trace ? "tracingState" : "debuggingState"); |
113 | 0 | } |
114 | 0 | return ScalarLogical(prev); |
115 | 0 | } |
116 | | |
117 | | // GUIs, packages, etc can query: |
118 | | attribute_hidden /* would need to be in an installed header if not hidden */ |
119 | 0 | Rboolean R_current_debug_state(void) { return GET_DEBUG_STATE; } |
120 | | attribute_hidden /* would need to be in an installed header if not hidden */ |
121 | 0 | Rboolean R_current_trace_state(void) { return GET_TRACE_STATE; } |
122 | | |
123 | | |
124 | | /* memory tracing */ |
125 | | /* report when a traced object is duplicated */ |
126 | | |
127 | | #ifdef R_MEMORY_PROFILING |
128 | | |
129 | | attribute_hidden SEXP do_tracemem(SEXP call, SEXP op, SEXP args, SEXP rho) |
130 | | { |
131 | | SEXP object; |
132 | | char buffer[21]; |
133 | | |
134 | | checkArity(op, args); |
135 | | check1arg(args, call, "x"); |
136 | | |
137 | | object = CAR(args); |
138 | | if (TYPEOF(object) == CLOSXP || |
139 | | TYPEOF(object) == BUILTINSXP || |
140 | | TYPEOF(object) == SPECIALSXP) |
141 | | errorcall(call, _("argument must not be a function")); |
142 | | |
143 | | if(object == R_NilValue) |
144 | | errorcall(call, _("cannot trace NULL")); |
145 | | |
146 | | if(TYPEOF(object) == ENVSXP || TYPEOF(object) == PROMSXP) |
147 | | errorcall(call, |
148 | | _("'tracemem' is not useful for promise and environment objects")); |
149 | | if(TYPEOF(object) == EXTPTRSXP || TYPEOF(object) == WEAKREFSXP) |
150 | | errorcall(call, |
151 | | _("'tracemem' is not useful for weak reference or external pointer objects")); |
152 | | |
153 | | SET_RTRACE(object, 1); |
154 | | snprintf(buffer, 21, "<%p>", (void *) object); |
155 | | return mkString(buffer); |
156 | | } |
157 | | |
158 | | attribute_hidden SEXP do_untracemem(SEXP call, SEXP op, SEXP args, SEXP rho) |
159 | | { |
160 | | SEXP object; |
161 | | |
162 | | checkArity(op, args); |
163 | | check1arg(args, call, "x"); |
164 | | |
165 | | object=CAR(args); |
166 | | if (TYPEOF(object) == CLOSXP || |
167 | | TYPEOF(object) == BUILTINSXP || |
168 | | TYPEOF(object) == SPECIALSXP) |
169 | | errorcall(call, _("argument must not be a function")); |
170 | | |
171 | | if (RTRACE(object)) |
172 | | SET_RTRACE(object, 0); |
173 | | return R_NilValue; |
174 | | } |
175 | | |
176 | | #else |
177 | | |
178 | | NORET attribute_hidden SEXP do_tracemem(SEXP call, SEXP op, SEXP args, SEXP rho) |
179 | 0 | { |
180 | 0 | checkArity(op, args); |
181 | 0 | check1arg(args, call, "x"); |
182 | 0 | errorcall(call, _("R was not compiled with support for memory profiling")); |
183 | 0 | } |
184 | | |
185 | | NORET attribute_hidden SEXP do_untracemem(SEXP call, SEXP op, SEXP args, SEXP rho) |
186 | 0 | { |
187 | 0 | checkArity(op, args); |
188 | 0 | check1arg(args, call, "x"); |
189 | 0 | errorcall(call, _("R was not compiled with support for memory profiling")); |
190 | 0 | } |
191 | | |
192 | | #endif /* R_MEMORY_PROFILING */ |
193 | | |
194 | | #ifndef R_MEMORY_PROFILING |
195 | 0 | attribute_hidden void memtrace_report(void* old, void *_new) { |
196 | 0 | return; |
197 | 0 | } |
198 | | #else |
199 | | static void memtrace_stack_dump(void) |
200 | | { |
201 | | RCNTXT *cptr; |
202 | | |
203 | | for (cptr = R_GlobalContext; cptr; cptr = cptr->nextcontext) { |
204 | | if ((cptr->callflag & (CTXT_FUNCTION | CTXT_BUILTIN)) |
205 | | && TYPEOF(cptr->call) == LANGSXP) { |
206 | | SEXP fun = CAR(cptr->call); |
207 | | Rprintf("%s ", |
208 | | TYPEOF(fun) == SYMSXP ? EncodeChar(PRINTNAME(fun)) : |
209 | | "<Anonymous>"); |
210 | | } |
211 | | } |
212 | | Rprintf("\n"); |
213 | | } |
214 | | |
215 | | void memtrace_report(void * old, void * _new) |
216 | | { |
217 | | if (!R_current_trace_state()) return; |
218 | | Rprintf("tracemem[%p -> %p]: ", (void *) old, _new); |
219 | | memtrace_stack_dump(); |
220 | | } |
221 | | |
222 | | #endif /* R_MEMORY_PROFILING */ |
223 | | |
224 | | attribute_hidden SEXP do_retracemem(SEXP call, SEXP op, SEXP args, SEXP rho) |
225 | 0 | { |
226 | | #ifdef R_MEMORY_PROFILING |
227 | | SEXP object, previous, ans, argList; |
228 | | char buffer[21]; |
229 | | static SEXP do_retracemem_formals = NULL; |
230 | | Rboolean visible; |
231 | | |
232 | | if (do_retracemem_formals == NULL) |
233 | | do_retracemem_formals = allocFormalsList2(install("x"), |
234 | | R_PreviousSymbol); |
235 | | |
236 | | PROTECT(argList = matchArgs_NR(do_retracemem_formals, args, call)); |
237 | | if(CAR(argList) == R_MissingArg) SETCAR(argList, R_NilValue); |
238 | | if(CADR(argList) == R_MissingArg) SETCAR(CDR(argList), R_NilValue); |
239 | | |
240 | | object = CAR(argList); |
241 | | if (TYPEOF(object) == CLOSXP || |
242 | | TYPEOF(object) == BUILTINSXP || |
243 | | TYPEOF(object) == SPECIALSXP) |
244 | | errorcall(call, _("argument must not be a function")); |
245 | | |
246 | | previous = CADR(argList); |
247 | | if(!isNull(previous) && (!isString(previous) || LENGTH(previous) != 1)) |
248 | | errorcall(call, _("invalid '%s' argument"), "previous"); |
249 | | |
250 | | if (RTRACE(object)) { |
251 | | snprintf(buffer, 21, "<%p>", (void *) object); |
252 | | visible = TRUE; |
253 | | ans = mkString(buffer); |
254 | | } else { |
255 | | visible = FALSE; |
256 | | ans = R_NilValue; |
257 | | } |
258 | | |
259 | | if (previous != R_NilValue){ |
260 | | SET_RTRACE(object, 1); |
261 | | if (R_current_trace_state()) { |
262 | | /* FIXME: previous will have <0x....> whereas other values are |
263 | | without the < > */ |
264 | | Rprintf("tracemem[%s -> %p]: ", |
265 | | translateChar(STRING_ELT(previous, 0)), (void *) object); |
266 | | memtrace_stack_dump(); |
267 | | } |
268 | | } |
269 | | UNPROTECT(1); |
270 | | R_Visible = visible; |
271 | | return ans; |
272 | | #else |
273 | 0 | R_Visible = FALSE; /* for consistency with other case */ |
274 | 0 | return R_NilValue; |
275 | 0 | #endif |
276 | 0 | } |