1 /*
2  * RCntxtUtils.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #define R_INTERNAL_FUNCTIONS
17 
18 #include <r/RCntxt.hpp>
19 #include <r/RCntxtUtils.hpp>
20 #include <r/RInterface.hpp>
21 #include <r/RExec.hpp>
22 #include <r/RUtil.hpp>
23 
24 namespace rstudio {
25 namespace r {
26 namespace context {
27 
contextVersion()28 RCntxtVersion contextVersion()
29 {
30    // cache the context version (we look this up constantly to figure out the
31    // appropriate offsets into the RCNXT struct)
32    static RCntxtVersion s_rCntxtVersion = RVersionUnknown;
33 
34    if (s_rCntxtVersion == RVersionUnknown)
35    {
36       // use current R version to divine the memory layout
37       if (r::util::hasRequiredVersion("4.0"))
38          s_rCntxtVersion = RVersion40;
39       else if (r::util::hasRequiredVersion("3.4"))
40          s_rCntxtVersion = RVersion34;
41       else if (r::util::hasRequiredVersion("3.3"))
42          s_rCntxtVersion = RVersion33;
43       else
44          s_rCntxtVersion = RVersion32;
45    }
46    return s_rCntxtVersion;
47 }
48 
globalContext()49 RCntxt globalContext()
50 {
51    return RCntxt(R_GlobalContext);
52 }
53 
firstFunctionContext()54 RCntxt firstFunctionContext()
55 {
56    RCntxt::iterator firstFunContext = RCntxt::begin();
57    while ((firstFunContext->callfun() == nullptr ||
58            firstFunContext->callfun() == R_NilValue) &&
59           firstFunContext->callflag())
60       firstFunContext++;
61    return *firstFunContext;
62 }
63 
getFunctionContext(const int depth,int * pFoundDepth,SEXP * pEnvironment)64 RCntxt getFunctionContext(const int depth,
65                           int* pFoundDepth,
66                           SEXP* pEnvironment)
67 {
68    RCntxt foundContext;
69    int currentDepth = 0;
70    int foundDepth = 0;
71    SEXP browseEnv = R_NilValue;
72    for (RCntxt::iterator ctxt = RCntxt::begin(); ctxt != RCntxt::end(); ctxt++)
73    {
74       // if looking for the actively browsed function, pick the environment
75       // evaluated by the browser on top of the stack
76       if (ctxt->callflag() & CTXT_BROWSER && browseEnv == R_NilValue)
77       {
78          browseEnv = ctxt->cloenv();
79       }
80       if (ctxt->callflag() & CTXT_FUNCTION)
81       {
82          currentDepth++;
83          if (depth == BROWSER_FUNCTION && ctxt->cloenv() == browseEnv)
84          {
85             foundDepth = currentDepth;
86             foundContext = *ctxt;
87             // continue traversing the callstack; there may be several
88             // functions eval'ing this environment and we want the "original"
89             // (here meaning oldest on the callstack)
90          }
91          else if (depth > BROWSER_FUNCTION && currentDepth >= depth)
92          {
93             foundDepth = currentDepth;
94             foundContext = *ctxt;
95             break;
96          }
97       }
98    }
99 
100    // indicate the depth at which we stopped and the environment we found at
101    // that depth, if requested
102    if (pFoundDepth)
103    {
104       *pFoundDepth = foundDepth;
105    }
106    if (pEnvironment)
107    {
108       *pEnvironment = (foundDepth == 0 || foundContext.isNull()) ?
109          R_GlobalEnv :
110          foundContext.cloenv();
111    }
112    return foundContext;
113 }
114 
115 // Return whether we're in browse context--meaning that there's a browser on
116 // the context stack and at least one function (i.e. we're not browsing at the
117 // top level).
inBrowseContext()118 bool inBrowseContext()
119 {
120    bool foundBrowser = false;
121    bool foundFunction = false;
122    for (RCntxt::iterator ctxt = RCntxt::begin(); ctxt != RCntxt::end(); ctxt++)
123    {
124       if ((ctxt->callflag() & CTXT_BROWSER) &&
125           !(ctxt->callflag() & CTXT_FUNCTION))
126       {
127          foundBrowser = true;
128       }
129       else if (ctxt->callflag() & CTXT_FUNCTION)
130       {
131          foundFunction = true;
132       }
133       if (foundBrowser && foundFunction)
134       {
135          return true;
136       }
137    }
138    return false;
139 }
140 
141 // Return whether the current context is being evaluated inside a hidden
142 // (debugger internal) function at the top level.
inDebugHiddenContext()143 bool inDebugHiddenContext()
144 {
145    for (RCntxt::iterator ctxt = RCntxt::begin(); ctxt != RCntxt::end(); ctxt++)
146    {
147       if (ctxt->callflag() & CTXT_FUNCTION)
148       {
149          // If we find a debugger internal function before any user function,
150          // hide it from the user callstack.
151          if (ctxt->isDebugHidden())
152             return true;
153 
154          // If we find a user function before we encounter a debugger internal
155          // function, don't hide the user code it invokes.
156          if (ctxt->hasSourceRefs())
157              return false;
158       }
159    }
160    return false;
161 }
162 
isByteCodeContext(const RCntxt & cntxt)163 bool isByteCodeContext(const RCntxt& cntxt)
164 {
165    return isByteCodeSrcRef(cntxt.srcref());
166 }
167 
isByteCodeSrcRef(SEXP srcref)168 bool isByteCodeSrcRef(SEXP srcref)
169 {
170    return srcref &&
171          TYPEOF(srcref) == SYMSXP &&
172          ::strcmp(CHAR(PRINTNAME(srcref)), "<in-bc-interp>") == 0;
173 }
174 
175 } // namespace context
176 } // namespace r
177 } // namespace rstudio
178 
179