/src/r-source/src/main/inspect.c
Line | Count | Source |
1 | | /* |
2 | | * R : A Computer Language for Statistical Data Analysis |
3 | | * Copyright (C) 2009-2025 The R Core Team. |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of the GNU General Public License as published by |
7 | | * the Free Software Foundation; either version 2 of the License, or |
8 | | * (at your option) any later version. |
9 | | * |
10 | | * This program is distributed in the hope that it will be useful, |
11 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | * GNU General Public License for more details. |
14 | | * |
15 | | * You should have received a copy of the GNU General Public License |
16 | | * along with this program; if not, a copy is available at |
17 | | * https://www.R-project.org/Licenses/ |
18 | | */ |
19 | | |
20 | | /* This is an experimental facility for printing low-level information |
21 | | about R objects. It is not intended to be exposed at the top level |
22 | | but rather used as a debugging/inspection facility. It is not |
23 | | necessarily complete - feel free to add missing pieces. */ |
24 | | |
25 | | #define USE_RINTERNALS |
26 | | |
27 | | #ifdef HAVE_CONFIG_H |
28 | | #include <config.h> |
29 | | #endif |
30 | | |
31 | | #include <Defn.h> |
32 | | #include <Internal.h> |
33 | | #include <R_ext/Print.h> |
34 | | |
35 | | /* FIXME: envir.c keeps this private - it should probably go to Defn.h */ |
36 | 0 | #define FRAME_LOCK_MASK (1<<14) |
37 | 0 | #define FRAME_IS_LOCKED(e) (ENVFLAGS(e) & FRAME_LOCK_MASK) |
38 | 0 | #define GLOBAL_FRAME_MASK (1<<15) |
39 | 0 | #define IS_GLOBAL_FRAME(e) (ENVFLAGS(e) & GLOBAL_FRAME_MASK) |
40 | | |
41 | | /* based on EncodeEnvironment in printutils.c */ |
42 | | static void PrintEnvironment(SEXP x) |
43 | 0 | { |
44 | 0 | const void *vmax = vmaxget(); |
45 | 0 | if (x == R_GlobalEnv) |
46 | 0 | Rprintf("<R_GlobalEnv>"); |
47 | 0 | else if (x == R_BaseEnv) |
48 | 0 | Rprintf("<base>"); |
49 | 0 | else if (x == R_EmptyEnv) |
50 | 0 | Rprintf("<R_EmptyEnv>"); |
51 | 0 | else if (R_IsPackageEnv(x)) |
52 | 0 | Rprintf("<%s>", |
53 | 0 | translateChar(STRING_ELT(R_PackageEnvName(x), 0))); |
54 | 0 | else if (R_IsNamespaceEnv(x)) |
55 | 0 | Rprintf("<namespace:%s>", |
56 | 0 | translateChar(STRING_ELT(R_NamespaceEnvSpec(x), 0))); |
57 | 0 | else Rprintf("<%p>", (void *)x); |
58 | 0 | vmaxset(vmax); |
59 | 0 | } |
60 | | |
61 | | /* print prefix */ |
62 | 0 | static void pp(int pre) { |
63 | | /* this is sort of silly, I know, but it saves at least some output |
64 | | calls (and we can replace \t by spaces if desired) ... */ |
65 | 0 | while (pre >= 8) { Rprintf("\t"); pre -= 8; } |
66 | 0 | while (pre-- > 0) Rprintf(" "); |
67 | 0 | } |
68 | | |
69 | 0 | static const char *typename(SEXP v) { |
70 | 0 | if(TYPEOF(v) == OBJSXP && IS_S4_OBJECT(v)) |
71 | 0 | return "S4SXP"; |
72 | 0 | return sexptype2char(TYPEOF(v)); // -> memory.c |
73 | 0 | } |
74 | | |
75 | | static void inspect_tree(int, SEXP, int, int); |
76 | | static void inspect_subtree(SEXP x, int pre, int deep, int pvec) |
77 | 0 | { |
78 | 0 | inspect_tree(pre + 2, x, deep - 1, pvec); |
79 | 0 | } |
80 | | |
81 | | /* pre is the prefix, v is the object to inspect, deep specifies |
82 | | the recursion behavior (0 = no recursion, -1 = [sort of] unlimited |
83 | | recursion, positive numbers define the maximum recursion depth) |
84 | | and pvec is the max. number of vector elements to show */ |
85 | 0 | static void inspect_tree(int pre, SEXP v, int deep, int pvec) { |
86 | 0 | int a = 0; |
87 | 0 | pp(pre); |
88 | | /* the use of %lx is deliberate because I hate the output of %p, |
89 | | but if this causes portability issues, it could be changed. |
90 | | SU |
91 | | |
92 | | It is invalid on 64-bit Windows. |
93 | | */ |
94 | | #ifdef _WIN64 |
95 | | Rprintf("@%p %02d %s g%dc%d [", (void *)v, TYPEOF(v), typename(v), |
96 | | v->sxpinfo.gcgen, v->sxpinfo.gccls); |
97 | | #else |
98 | 0 | Rprintf("@%lx %02d %s g%dc%d [", (long) v, TYPEOF(v), typename(v), |
99 | 0 | v->sxpinfo.gcgen, v->sxpinfo.gccls); |
100 | 0 | #endif |
101 | 0 | if (OBJECT(v)) { a = 1; Rprintf("OBJ"); } |
102 | 0 | if (MARK(v)) { if (a) Rprintf(","); Rprintf("MARK"); a = 1; } |
103 | | #ifndef SWITCH_TO_REFCNT |
104 | | if (NAMED(v)) { if (a) Rprintf(","); Rprintf("NAM(%d)",NAMED(v)); a = 1; } |
105 | | #endif |
106 | 0 | if (REFCNT(v)) { if (a) Rprintf(","); Rprintf("REF(%d)",REFCNT(v)); a = 1; } |
107 | 0 | if (RDEBUG(v)) { if (a) Rprintf(","); Rprintf("DBG"); a = 1; } |
108 | 0 | if (RTRACE(v)) { if (a) Rprintf(","); Rprintf("TR"); a = 1; } |
109 | 0 | if (RSTEP(v)) { if (a) Rprintf(","); Rprintf("STP"); a = 1; } |
110 | 0 | if (IS_S4_OBJECT(v)) { if (a) Rprintf(","); Rprintf("S4"); a = 1; } |
111 | 0 | if (TYPEOF(v) == SYMSXP || TYPEOF(v) == LISTSXP) { |
112 | 0 | if (IS_ACTIVE_BINDING(v)) { if (a) Rprintf(","); Rprintf("AB"); a = 1; } |
113 | 0 | if (BINDING_IS_LOCKED(v)) { if (a) Rprintf(","); Rprintf("LCK"); a = 1; } |
114 | 0 | } |
115 | 0 | if (TYPEOF(v) == ENVSXP) { |
116 | 0 | if (FRAME_IS_LOCKED(v)) { if (a) Rprintf(","); Rprintf("LCK"); a = 1; } |
117 | 0 | if (IS_GLOBAL_FRAME(v)) { if (a) Rprintf(","); Rprintf("GL"); a = 1; } |
118 | 0 | } |
119 | 0 | if (TYPEOF(v) == PROMSXP) { |
120 | 0 | if (PROMISE_IS_EVALUATED(v)) { if (a) Rprintf(","); Rprintf("VAL"); a = 1; } |
121 | 0 | } |
122 | 0 | if (LEVELS(v)) { if (a) Rprintf(","); Rprintf("gp=0x%x", LEVELS(v)); a = 1; } |
123 | 0 | if (ATTRIB(v) && ATTRIB(v) != R_NilValue) { if (a) Rprintf(","); Rprintf("ATT"); a = 1; } |
124 | 0 | Rprintf("] "); |
125 | |
|
126 | 0 | if (ALTREP(v) && ALTREP_INSPECT(v, pre, deep, pvec, inspect_subtree)) { |
127 | 0 | if (ATTRIB(v) && ATTRIB(v) != R_NilValue && TYPEOF(v) != CHARSXP) { |
128 | 0 | pp(pre); |
129 | 0 | Rprintf("ATTRIB:\n"); |
130 | 0 | inspect_tree(pre+2, ATTRIB(v), deep, pvec); |
131 | 0 | } |
132 | 0 | return; |
133 | 0 | } |
134 | | |
135 | 0 | switch (TYPEOF(v)) { |
136 | 0 | case VECSXP: case STRSXP: case LGLSXP: case INTSXP: case RAWSXP: |
137 | 0 | case REALSXP: case CPLXSXP: case EXPRSXP: |
138 | 0 | if (GROWABLE_BIT_SET(v)) |
139 | 0 | Rprintf("(len=%lld, tl=%lld, gr)", (long long)XLENGTH(v), (long long)XTRUELENGTH(v)); |
140 | 0 | else |
141 | 0 | Rprintf("(len=%lld, tl=%lld)", (long long)XLENGTH(v), (long long)XTRUELENGTH(v)); |
142 | 0 | } |
143 | 0 | if (TYPEOF(v) == ENVSXP) /* NOTE: this is not a trivial OP since it involves looking up things |
144 | | in the environment, so for a low-level debugging we may want to |
145 | | avoid it .. */ |
146 | 0 | PrintEnvironment(v); |
147 | 0 | if (TYPEOF(v) == CHARSXP) { |
148 | 0 | if (IS_BYTES(v)) Rprintf("[bytes] "); |
149 | 0 | if (IS_LATIN1(v)) Rprintf("[latin1] "); |
150 | 0 | if (IS_UTF8(v)) Rprintf("[UTF8] "); |
151 | 0 | if (IS_ASCII(v)) Rprintf("[ASCII] "); |
152 | 0 | if (IS_CACHED(v)) Rprintf("[cached] "); |
153 | 0 | Rprintf("\"%s\"", CHAR(v)); |
154 | 0 | if (v == R_NaString) Rprintf(" [NA]"); |
155 | 0 | } |
156 | 0 | if (TYPEOF(v) == SYMSXP) { |
157 | 0 | if (v == R_UnboundValue) |
158 | 0 | Rprintf("[unbound value]"); |
159 | 0 | else if (v == R_MissingArg) |
160 | 0 | Rprintf("[missing argument]"); |
161 | 0 | else if (v == R_RestartToken) |
162 | 0 | Rprintf("[restart token]"); |
163 | 0 | else |
164 | 0 | Rprintf("\"%s\"%s", EncodeChar(PRINTNAME(v)), (SYMVALUE(v) == R_UnboundValue) ? "" : " (has value)"); |
165 | 0 | } |
166 | 0 | if (TYPEOF(v) == EXTPTRSXP) |
167 | 0 | Rprintf("<%p>", R_ExternalPtrAddr(v)); |
168 | 0 | switch (TYPEOF(v)) { /* for native vectors print the first elements in-line */ |
169 | 0 | case LGLSXP: |
170 | 0 | if (XLENGTH(v) > 0) { |
171 | 0 | unsigned int i = 0; |
172 | 0 | while (i < XLENGTH(v) && i < pvec) { |
173 | 0 | Rprintf("%s%d", (i > 0) ? "," : " ", |
174 | 0 | (int) LOGICAL_ELT(v, i)); |
175 | 0 | i++; |
176 | 0 | } |
177 | 0 | if (i < XLENGTH(v)) Rprintf(",..."); |
178 | 0 | } |
179 | 0 | break; |
180 | 0 | case INTSXP: |
181 | 0 | if (XLENGTH(v) > 0) { |
182 | 0 | unsigned int i = 0; |
183 | 0 | while (i < XLENGTH(v) && i < pvec) { |
184 | 0 | Rprintf("%s%d", (i > 0) ? "," : " ", INTEGER_ELT(v, i)); |
185 | 0 | i++; |
186 | 0 | } |
187 | 0 | if (i < XLENGTH(v)) Rprintf(",..."); |
188 | 0 | } |
189 | 0 | break; |
190 | 0 | case RAWSXP: |
191 | 0 | if (XLENGTH(v) > 0) { |
192 | 0 | unsigned int i = 0; |
193 | 0 | while (i < XLENGTH(v) && i < pvec) { |
194 | 0 | Rprintf("%s%02x", (i > 0) ? "," : " ", (int) ((unsigned char) RAW(v)[i])); |
195 | 0 | i++; |
196 | 0 | } |
197 | 0 | if (i < XLENGTH(v)) Rprintf(",..."); |
198 | 0 | } |
199 | 0 | break; |
200 | 0 | case REALSXP: |
201 | 0 | if (XLENGTH(v) > 0) { |
202 | 0 | unsigned int i = 0; |
203 | 0 | while (i < XLENGTH(v) && i < pvec) { |
204 | 0 | Rprintf("%s%g", (i > 0) ? "," : " ", REAL_ELT(v, i)); |
205 | 0 | i++; |
206 | 0 | } |
207 | 0 | if (i < XLENGTH(v)) Rprintf(",..."); |
208 | 0 | } |
209 | 0 | break; |
210 | 0 | } |
211 | 0 | Rprintf("\n"); |
212 | 0 | if (deep) switch (TYPEOF(v)) { |
213 | 0 | case VECSXP: case EXPRSXP: |
214 | 0 | { |
215 | 0 | unsigned int i = 0; |
216 | 0 | while (i < XLENGTH(v) && i < pvec) { |
217 | 0 | inspect_tree(pre+2, VECTOR_ELT(v, i), deep - 1, pvec); |
218 | 0 | i++; |
219 | 0 | } |
220 | 0 | if (i < XLENGTH(v)) { pp(pre+2); Rprintf("...\n"); } |
221 | 0 | } |
222 | 0 | break; |
223 | 0 | case STRSXP: |
224 | 0 | { |
225 | 0 | unsigned int i = 0; |
226 | 0 | while (i < XLENGTH(v) && i < pvec) { |
227 | 0 | inspect_tree(pre+2, STRING_ELT(v, i), deep - 1, pvec); |
228 | 0 | i++; |
229 | 0 | } |
230 | 0 | if (i < XLENGTH(v)) { pp(pre+2); Rprintf("...\n"); } |
231 | 0 | } |
232 | 0 | break; |
233 | 0 | case LISTSXP: case LANGSXP: |
234 | 0 | { |
235 | 0 | SEXP lc = v; |
236 | 0 | while (lc != R_NilValue) { |
237 | 0 | if (TYPEOF(lc) != LISTSXP && TYPEOF(lc) != LANGSXP) { |
238 | | /* a dotted pair */ |
239 | 0 | pp(pre + 2); |
240 | 0 | Rprintf(".\n"); |
241 | 0 | inspect_tree(pre + 2, lc, deep - 1, pvec); |
242 | 0 | break; |
243 | 0 | } |
244 | 0 | if (TAG(lc) && TAG(lc) != R_NilValue) { |
245 | 0 | pp(pre + 2); |
246 | 0 | Rprintf("TAG: "); /* TAG should be a one-liner since it's a symbol so we don't put it on an extra line*/ |
247 | 0 | inspect_tree(0, TAG(lc), deep - 1, pvec); |
248 | 0 | } |
249 | 0 | if (BNDCELL_TAG(lc)) { |
250 | 0 | int type = BNDCELL_TAG(lc); |
251 | 0 | pp(pre + 2); |
252 | 0 | Rprintf("immediate %s: ", sexptype2char(type)); |
253 | 0 | switch(type) { |
254 | 0 | case REALSXP: |
255 | 0 | Rprintf("%g\n", BNDCELL_DVAL(lc)); |
256 | 0 | break; |
257 | 0 | case INTSXP: |
258 | 0 | if (BNDCELL_IVAL(lc) == NA_INTEGER) |
259 | 0 | Rprintf("NA\n"); |
260 | 0 | else |
261 | 0 | Rprintf("%d\n", BNDCELL_IVAL(lc)); |
262 | 0 | break; |
263 | 0 | case LGLSXP: |
264 | 0 | if (BNDCELL_LVAL(lc) == NA_INTEGER) |
265 | 0 | Rprintf("NA\n"); |
266 | 0 | else if (BNDCELL_LVAL(lc)) |
267 | 0 | Rprintf("TRUE\n"); |
268 | 0 | else |
269 | 0 | Rprintf("FALSE\n"); |
270 | 0 | break; |
271 | 0 | default: error("unknown immediate binding type"); |
272 | 0 | } |
273 | 0 | } |
274 | 0 | else |
275 | 0 | inspect_tree(pre + 2, CAR(lc), deep - 1, pvec); |
276 | 0 | lc = CDR(lc); |
277 | 0 | } |
278 | 0 | } |
279 | 0 | break; |
280 | 0 | case ENVSXP: |
281 | 0 | if (FRAME(v) != R_NilValue) { |
282 | 0 | pp(pre); Rprintf("FRAME:\n"); |
283 | 0 | inspect_tree(pre+2, FRAME(v), deep - 1, pvec); |
284 | 0 | } |
285 | 0 | pp(pre); Rprintf("ENCLOS:\n"); |
286 | 0 | inspect_tree(pre+2, ENCLOS(v), 0, pvec); |
287 | 0 | if (HASHTAB(v) != R_NilValue) { |
288 | 0 | pp(pre); Rprintf("HASHTAB:\n"); |
289 | 0 | inspect_tree(pre+2, HASHTAB(v), deep - 1, pvec); |
290 | 0 | } |
291 | 0 | break; |
292 | | |
293 | 0 | case CLOSXP: |
294 | 0 | pp(pre); Rprintf("FORMALS:\n"); |
295 | 0 | inspect_tree(pre+2, FORMALS(v), deep - 1, pvec); |
296 | 0 | pp(pre); Rprintf("BODY:\n"); |
297 | 0 | inspect_tree(pre+2, BODY(v), deep - 1, pvec); |
298 | 0 | pp(pre); Rprintf("CLOENV:\n"); |
299 | 0 | inspect_tree(pre+2, CLOENV(v), 0, pvec); |
300 | 0 | break; |
301 | 0 | case EXTPTRSXP: |
302 | 0 | { |
303 | 0 | SEXP prot = R_ExternalPtrProtected(v); |
304 | 0 | SEXP tag = R_ExternalPtrTag(v); |
305 | 0 | if (prot != R_NilValue) { |
306 | 0 | pp(pre); Rprintf("PROTECTED:\n"); |
307 | 0 | inspect_tree(pre+2, prot, deep - 1, pvec); |
308 | 0 | } |
309 | 0 | if (tag != R_NilValue) { |
310 | 0 | pp(pre); Rprintf("TAG:\n"); |
311 | 0 | inspect_tree(pre+2, tag, deep - 1, pvec); |
312 | 0 | } |
313 | 0 | } |
314 | 0 | break; |
315 | 0 | } |
316 | | |
317 | 0 | if (ATTRIB(v) && ATTRIB(v) != R_NilValue && TYPEOF(v) != CHARSXP) { |
318 | 0 | pp(pre); Rprintf("ATTRIB:\n"); inspect_tree(pre+2, ATTRIB(v), deep, pvec); |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | | /* internal API - takes one mandatory argument (object to inspect) and |
323 | | two optional arguments (deep and pvec - see above), positional argument |
324 | | matching only */ |
325 | 0 | attribute_hidden SEXP do_inspect(SEXP call, SEXP op, SEXP args, SEXP env) { |
326 | 0 | checkArity(op, args); |
327 | 0 | SEXP obj = CAR(args); |
328 | 0 | int deep = -1; |
329 | 0 | int pvec = 5; |
330 | 0 | if (CDR(args) != R_NilValue) { |
331 | 0 | deep = asInteger(CADR(args)); |
332 | 0 | if (CDDR(args) != R_NilValue) |
333 | 0 | pvec = asInteger(CADDR(args)); |
334 | 0 | } |
335 | |
|
336 | 0 | inspect_tree(0, CAR(args), deep, pvec); |
337 | 0 | return obj; |
338 | 0 | } |
339 | | |
340 | | attribute_hidden SEXP do_address(SEXP call, SEXP op, SEXP args, SEXP rho) |
341 | 0 | { |
342 | 0 | checkArity(op, args); |
343 | 0 | return R_MakeExternalPtr((void *) CAR(args), R_NilValue, R_NilValue); |
344 | 0 | } |
345 | | |
346 | | attribute_hidden SEXP do_named(SEXP call, SEXP op, SEXP args, SEXP rho) |
347 | 0 | { |
348 | 0 | checkArity(op, args); |
349 | 0 | return ScalarInteger(NAMED(CAR(args))); |
350 | 0 | } |
351 | | |
352 | | attribute_hidden SEXP do_refcnt(SEXP call, SEXP op, SEXP args, SEXP rho) |
353 | 0 | { |
354 | 0 | checkArity(op, args); |
355 | 0 | return ScalarInteger(REFCNT(CAR(args))); |
356 | 0 | } |
357 | | |
358 | | /* the following functions can be use internally and for debugging purposes - |
359 | | so far they are not used in any actual code */ |
360 | 0 | attribute_hidden SEXP R_inspect(SEXP x) { |
361 | 0 | inspect_tree(0, x, -1, 5); |
362 | 0 | return x; |
363 | 0 | } |
364 | | |
365 | 0 | attribute_hidden SEXP R_inspect3(SEXP x, int deep, int pvec) { |
366 | 0 | inspect_tree(0, x, deep, pvec); |
367 | 0 | return x; |
368 | 0 | } |