Skip to content

Commit

Permalink
rework hasExternalPointer internals (#12408)
Browse files Browse the repository at this point in the history
* + hasExternalPointer()

* fix pairlistHasExternalPointer() and envHasExternalPointer()

* simplify rs_hasExternalPtr()

* misunderstood the role of nullPtr

* handle already evaluated promises

* + getWeakRefKey(), getWeakRefValue()

* tests for hasExternalPointer() covering most cases

* hunt for external pointers in CLOSXP

* move frameBindingIsActive() to where used

* update promise check so that eval.env() is local env, not global env

* add makeExternalPtr(void*, SEXP, SEXP)

* use .Call("rs_newTestExternalPointer")
  • Loading branch information
romainfrancois committed Dec 14, 2022
1 parent 5189a79 commit b735c15
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 56 deletions.
20 changes: 20 additions & 0 deletions src/cpp/r/RSexp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,11 @@ bool isPrimitiveEnvironment(SEXP object)
return TYPEOF(object) == ENVSXP;
}

bool isUserDefinedDatabase(SEXP object)
{
return OBJECT(object) && Rf_inherits(object, "UserDefinedDatabase");
}

bool isNumeric(SEXP object)
{
return Rf_isNumeric(object);
Expand Down Expand Up @@ -788,6 +793,16 @@ SEXP makeWeakRef(SEXP key, SEXP val, R_CFinalizer_t fun, Rboolean onexit)
return R_MakeWeakRefC(key, val, fun, onexit);
}

SEXP getWeakRefKey(SEXP ref)
{
return VECTOR_ELT(ref, 0);
}

SEXP getWeakRefValue(SEXP ref)
{
return VECTOR_ELT(ref, 1);
}

void registerFinalizer(SEXP s, R_CFinalizer_t fun)
{
R_RegisterCFinalizer(s, fun);
Expand All @@ -802,6 +817,11 @@ SEXP makeExternalPtr(void* ptr, R_CFinalizer_t fun, Protect* pProtect)
return s;
}

SEXP makeExternalPtr(void* ptr, SEXP prot, SEXP tag)
{
return R_MakeExternalPtr(ptr, prot, tag);
}

void* getExternalPtrAddr(SEXP extptr)
{
return R_ExternalPtrAddr(extptr);
Expand Down
4 changes: 4 additions & 0 deletions src/cpp/r/include/r/RSexp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ bool isNull(SEXP object);
bool isEnvironment(SEXP object);
bool isPrimitiveEnvironment(SEXP object);
bool isNumeric(SEXP object);
bool isUserDefinedDatabase(SEXP object);

// type coercions
std::string asString(SEXP object);
Expand All @@ -127,8 +128,11 @@ bool isExternalPointer(SEXP object);
bool isNullExternalPointer(SEXP object);

SEXP makeWeakRef(SEXP key, SEXP val, R_CFinalizer_t fun, Rboolean onexit);
SEXP getWeakRefKey(SEXP ref);
SEXP getWeakRefValue(SEXP ref);
void registerFinalizer(SEXP s, R_CFinalizer_t fun);
SEXP makeExternalPtr(void* ptr, R_CFinalizer_t fun, Protect* protect);
SEXP makeExternalPtr(void* ptr, SEXP prot, SEXP tag);
void* getExternalPtrAddr(SEXP extptr);
void clearExternalPtr(SEXP extptr);

Expand Down
7 changes: 6 additions & 1 deletion src/cpp/session/modules/SessionEnvironment.R
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@
val1
})

.rs.addFunction("hasExternalPointer", function(object, nullPtr = FALSE)
{
.Call("rs_hasExternalPointer", object, nullPtr, PACKAGE = "(embedding)")
})

.rs.addFunction("describeObject", function(env, objName, computeSize = TRUE)
{
obj <- get(objName, env)
Expand All @@ -647,7 +652,7 @@
# opt-in to checking for null pointers if required.
checkNullPtr <- .rs.readUiPref("check_null_external_pointers")
hasNullPtr <- if (identical(checkNullPtr, TRUE))
.Call("rs_hasExternalPointer", obj, TRUE, PACKAGE = "(embedding)")
.rs.hasExternalPointer(obj, TRUE)
else
FALSE

Expand Down
232 changes: 178 additions & 54 deletions src/cpp/session/modules/environment/SessionEnvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <session/SessionSourceDatabase.hpp>
#include <session/SessionPersistentState.hpp>
#include <session/prefs/UserPrefs.hpp>
#include <r/RSxpInfo.hpp>

#include "EnvironmentUtils.hpp"

Expand Down Expand Up @@ -119,95 +120,211 @@ bool handleRBrowseEnv(const core::FilePath& filePath)
}
}

bool hasExternalPtrImpl(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
bool hasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited);

bool pairlistHasExternalPointer(SEXP list, bool nullPtr, std::set<SEXP>& visited)
{
while(list != R_NilValue)
{
if (hasExternalPointer(CAR(list), nullPtr, visited))
return true;

list = CDR(list);
}

return false;
}

bool attributesHasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
{
// if we've already visited this SEXP, bail
if (visited.count(obj))
return pairlistHasExternalPointer(ATTRIB(obj), nullPtr, visited);
}

bool listHasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
{
R_xlen_t n = XLENGTH(obj);
for (R_xlen_t i = 0; i < n; i++)
{
if (hasExternalPointer(VECTOR_ELT(obj, i), nullPtr, visited))
return true;
}
return false;
}

namespace {
bool frameBindingIsActive(SEXP binding)
{
static unsigned int ACTIVE_BINDING_MASK = (1<<15);
return reinterpret_cast<r::sxpinfo*>(binding)->gp & ACTIVE_BINDING_MASK;
}
}

bool frameHasExternalPointer(SEXP frame, bool nullPtr, std::set<SEXP>& visited)
{
while(frame != R_NilValue)
{
if (!frameBindingIsActive(frame) && hasExternalPointer(CAR(frame), nullPtr, visited))
return true;

frame = CDR(frame);
}

return false;
}

bool envHasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
{
SEXP hash = HASHTAB(obj);
if (hash == R_NilValue)
return frameHasExternalPointer(FRAME(obj), nullPtr, visited);

R_xlen_t n = XLENGTH(hash);
for (R_xlen_t i = 0; i < n; i++)
{
if (frameHasExternalPointer(VECTOR_ELT(hash, i), nullPtr, visited))
return true;
}
return false;
}

bool weakrefHasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
{
SEXP key = r::sexp::getWeakRefKey(obj);
if (key != R_NilValue)
{
if (hasExternalPointer(key, nullPtr, visited))
return true;

// only consider the value if the key is not NULL
if (hasExternalPointer(r::sexp::getWeakRefValue(obj), nullPtr, visited))
return true;
}

return false;
}

bool hasExternalPointer(SEXP obj, bool nullPtr, std::set<SEXP>& visited)
{
if (obj == R_NilValue || visited.count(obj))
return false;

// mark SEXP as visited
visited.insert(obj);

// list the contents of this environment
std::vector<r::sexp::Variable> vars;
r::sexp::Protect rProtect;
if (TYPEOF(obj) == S4SXP)
// check if this is an external pointer
if (r::sexp::isExternalPointer(obj))
{
// for S4 objects, list the attributes (which correspond to slots)
r::sexp::listNamedAttributes(obj, &rProtect, &vars);
// NOTE: this includes UserDefinedDatabase, aka
// external pointers to R_ObjectTable

// when nullPtr is true, only return true for null pointer xp
// otherwise only return true for non null pointer xp
if (nullPtr == (r::sexp::getExternalPtrAddr(obj) == nullptr))
return true;

if (hasExternalPointer(EXTPTR_PROT(obj), nullPtr, visited))
return true;

if (hasExternalPointer(EXTPTR_TAG(obj), nullPtr, visited))
return true;
}
else

switch(TYPEOF(obj))
{
// not S4, coerce to environment
SEXP envir = R_NilValue;
if (TYPEOF(obj) == ENVSXP)
case ENVSXP:
{
// we were given a primitive environment (ENVSXP)
envir = obj;
if (envHasExternalPointer(obj, nullPtr, visited))
return true;
break;
}
else
case VECSXP:
case EXPRSXP:
{
// convert the passed environment into a primitive environment; this is required so that
// e.g. reference objects that subclass 'environment' can be introspected below
Error error = r::sexp::asPrimitiveEnvironment(obj, &envir, &rProtect);
if (error)
if (listHasExternalPointer(obj, nullPtr, visited))
return true;
break;
}

case LISTSXP:
case LANGSXP:
{
if (pairlistHasExternalPointer(obj, nullPtr, visited))
return true;
break;
}

case WEAKREFSXP:
{
if (weakrefHasExternalPointer(obj, nullPtr, visited))
return true;

break;
}
case PROMSXP:
{
SEXP value = PRVALUE(obj);
if (value != R_UnboundValue)
{
if (hasExternalPointer(value, nullPtr, visited))
return true;
}
else
{
// can't search in here
if (hasExternalPointer(PRCODE(obj), nullPtr, visited))
return true;

if (hasExternalPointer(PRENV(obj), nullPtr, visited))
return true;

return false;
}
break;
}
case CLOSXP:
{
if (hasExternalPointer(FORMALS(obj), nullPtr, visited))
return true;

if (hasExternalPointer(BODY(obj), nullPtr, visited))
return true;

r::sexp::listEnvironment(envir,
true, // include all values
false, // don't include last dot
&vars);
if (hasExternalPointer(CLOENV(obj), nullPtr, visited))
return true;
}
default:
break;
}

// check for external pointers
for (std::vector<r::sexp::Variable>::iterator it = vars.begin(); it != vars.end(); it++)
// check attributes, this includes slots for S4 objects
if (attributesHasExternalPointer(obj, nullPtr, visited))
return true;

// altrep objects might be implemented with external pointers
if (isAltrep(obj))
{
if (r::sexp::isExternalPointer(it->second) &&
r::sexp::isNullExternalPointer(it->second) == nullPtr)
{
if (hasExternalPointer(CAR(obj), nullPtr, visited))
return true;
}

if (r::sexp::isPrimitiveEnvironment(it->second) || TYPEOF(it->second) == S4SXP)
{
// if this object is itself an environment, check it recursively for external pointers.
// (we do this only if there's sufficient recursion depth remaining)
if (hasExternalPtrImpl(it->second, nullPtr, visited))
return true;
}
}
if (hasExternalPointer(CDR(obj), nullPtr, visited))
return true;
}

return false;
}


bool hasExternalPtr(SEXP obj, // environment to search for external pointers
bool nullPtr) // whether to look for NULL pointers
{
std::set<SEXP> visited;
return hasExternalPtrImpl(obj, nullPtr, visited);
return hasExternalPointer(obj, nullPtr, visited);
}

SEXP rs_hasExternalPointer(SEXP objSEXP, SEXP nullSEXP)
{
bool nullPtr = r::sexp::asLogical(nullSEXP);
r::sexp::Protect protect;
bool hasPtr = false;
if (r::sexp::isExternalPointer(objSEXP))
{
// object is an external pointer itself
hasPtr = r::sexp::isNullExternalPointer(objSEXP) == nullPtr;
}
else if (r::sexp::isPrimitiveEnvironment(objSEXP) || TYPEOF(objSEXP) == S4SXP)
{
// object is an environment; check it for external pointers
hasPtr = hasExternalPtr(objSEXP, nullPtr);
}
return r::sexp::create(hasPtr, &protect);

bool nullPtr = r::sexp::asLogical(nullSEXP);
return r::sexp::create(hasExternalPtr(objSEXP, nullPtr), &protect);
}

// Does an object contain an ALTREP anywhere? ALTREP (alternative representation) objects often
Expand All @@ -225,6 +342,12 @@ SEXP rs_isAltrep(SEXP obj)
return r::sexp::create(isAltrep(obj), &protect);
}

SEXP rs_newTestExternalPointer(SEXP nullSEXP)
{
bool nullPtr = r::sexp::asLogical(nullSEXP);
return r::sexp::makeExternalPtr(nullPtr ? nullptr : R_EmptyEnv, R_NilValue, R_NilValue);
}

// Construct a simulated source reference from a context containing a
// function being debugged, and either the context containing the current
// invocation or a string containing the last debug output from R.
Expand Down Expand Up @@ -1279,6 +1402,7 @@ Error initialize()
RS_REGISTER_CALL_METHOD(rs_hasAltrep);
RS_REGISTER_CALL_METHOD(rs_isAltrep);
RS_REGISTER_CALL_METHOD(rs_dumpContexts);
RS_REGISTER_CALL_METHOD(rs_newTestExternalPointer);

// subscribe to events
using boost::bind;
Expand Down
Loading

5 comments on commit b735c15

@netique
Copy link
Contributor

@netique netique commented on b735c15 Dec 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romainfrancois This commit causes R session to crash if you assign a "complex" object, try this from ?lm:

ctl <- c(4.17,5.58,5.18,6.11,4.50,4.61,5.17,4.53,5.33,5.14)
trt <- c(4.81,4.17,4.41,3.59,5.87,3.83,6.03,4.89,4.32,4.69)
group <- gl(2, 10, 20, labels = c("Ctl","Trt"))
weight <- c(ctl, trt)
lm.D9 <- lm(weight ~ group)

RStudio ver. 2023.03.0-daily+25 (in 2023.03.0-daily+24 there is no such issue)
R version 4.2.2 (2022-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.1

@kevinushey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing this as well.

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1)
  * frame #0: 0x000000010263a480 libR.dylib`TYPEOF(x=0x0000000000000001) at memory.c:3800:31 [opt]
    frame #1: 0x0000000100cbfc4c rsession`rstudio::r::sexp::isExternalPointer(object=<unavailable>) at RSexp.cpp:781:11 [opt]
    frame #2: 0x00000001007f2f0c rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x0000000000000001, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:215:8 [opt]
    frame #3: 0x00000001007f2ff8 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) [inlined] rstudio::session::modules::environment::(anonymous namespace)::pairlistHasExternalPointer(list=0x0000000121abcd10, nullPtr=false, visited=size=18) at SessionEnvironment.cpp:129:11 [opt]
    frame #4: 0x00000001007f2fd4 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x000000012099e4b0, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:251:14 [opt]
    frame #5: 0x00000001007f31d4 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x000000012099e478, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:305:11 [opt]
    frame #6: 0x00000001007f3198 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) [inlined] rstudio::session::modules::environment::(anonymous namespace)::pairlistHasExternalPointer(list=0x0000000120a0c168, nullPtr=false, visited=size=18) at SessionEnvironment.cpp:129:11 [opt]
    frame #7: 0x00000001007f3174 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) [inlined] rstudio::session::modules::environment::(anonymous namespace)::attributesHasExternalPointer(obj=0x00006000016904e0, nullPtr=false, visited=size=18) at SessionEnvironment.cpp:140:11 [opt]
    frame #8: 0x00000001007f316c rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x00006000016904e0, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:299:8 [opt]
    frame #9: 0x00000001007f3048 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) at SessionEnvironment.cpp:148:11 [opt]
    frame #10: 0x00000001007f3018 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x0000000121afbb98, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:243:14 [opt]
    frame #11: 0x00000001007f3128 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) [inlined] rstudio::session::modules::environment::(anonymous namespace)::frameHasExternalPointer(frame=0x0000000120a38868, nullPtr=false, visited=size=18) at SessionEnvironment.cpp:166:43 [opt]
    frame #12: 0x00000001007f30e0 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(SEXPREC*, bool, std::__1::set<SEXPREC*, std::__1::less<SEXPREC*>, std::__1::allocator<SEXPREC*> >&) at SessionEnvironment.cpp:184:11 [opt]
    frame #13: 0x00000001007f3098 rsession`rstudio::session::modules::environment::(anonymous namespace)::hasExternalPointer(obj=0x000000015784fec8, nullPtr=<unavailable>, visited=<unavailable>) at SessionEnvironment.cpp:236:14 [opt]
    frame #14: 0x00000001007e6c88 rsession`rstudio::session::modules::environment::isSuspendable() [inlined] rstudio::session::modules::environment::(anonymous namespace)::hasExternalPtr(obj=<unavailable>, nullPtr=false) at SessionEnvironment.cpp:319:11 [opt]
    frame #15: 0x00000001007e6c70 rsession`rstudio::session::modules::environment::isSuspendable() at SessionEnvironment.cpp:1376:12 [opt]
    frame #16: 0x00000001002e8b4c rsession`rstudio::session::console_input::(anonymous namespace)::canSuspend(prompt="> ") at SessionConsoleInput.cpp:106:59 [opt]
    frame #17: 0x000000010050e40c rsession`rstudio::session::suspend::addBlockingOp(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, rstudio_boost::function<bool ()> const&) [inlined] rstudio_boost::function0<bool>::operator(this=<unavailable>)() const at function_template.hpp:763:14 [opt]
    frame #18: 0x000000010050e3f0 rsession`rstudio::session::suspend::addBlockingOp(method="console_input", allowSuspend=<unavailable>)> const&) at SessionSuspend.cpp:239:9 [opt]
    frame #19: 0x00000001003ad830 rsession`rstudio::session::http_methods::waitForMethod(method="console_input", initFunction=0x000000016fb50f18, allowSuspend=0x000000016fb50f88, pRequest=0x000000016fb50fe8)> const&, rstudio_boost::function<bool ()> const&, rstudio::core::json::JsonRpcRequest*) at SessionHttpMethods.cpp:467:4 [opt]
    frame #20: 0x00000001002e70f4 rsession`rstudio::session::console_input::rConsoleRead(prompt=<unavailable>, addToHistory=true, pConsoleInput=<unavailable>) at SessionConsoleInput.cpp:335:24 [opt]
    frame #21: 0x0000000100cf2d70 rsession`rstudio::r::session::RReadConsole(char const*, unsigned char*, int, int) [inlined] rstudio_boost::function3<bool, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, rstudio::r::session::RConsoleInput*>::operator(this=<unavailable>, a0="> ", a1=<unavailable>, a2=0x000000016fb51100)(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, rstudio::r::session::RConsoleInput*) const at function_template.hpp:763:14 [opt]
    frame #22: 0x0000000100cf2d54 rsession`rstudio::r::session::RReadConsole(pmt=<unavailable>, buf="lm.D9 <- lm(weight ~ group)\n", buflen=4096, hist=1) at RStdCallbacks.cpp:321:11 [opt]
    frame #23: 0x000000010262eaf0 libR.dylib`Rf_ReplIteration(rho=0x000000015784fec8, savestack=0, browselevel=0, state=0x000000016fb512f0) at main.c:212:10 [opt]
    frame #24: 0x00000001026302ac libR.dylib`R_ReplConsole(rho=0x000000015784fec8, savestack=0, browselevel=0) at main.c:316:11 [opt]
    frame #25: 0x00000001026301e4 libR.dylib`run_Rmainloop at main.c:1139:5 [opt]
    frame #26: 0x0000000102630390 libR.dylib`Rf_mainloop at main.c:1146:5 [opt]
    frame #27: 0x0000000100d19a1c rsession`rstudio::r::session::runEmbeddedR((null)=0x000000016fb52658, (null)=0x000000016fb52de0, quiet=false, loadInitFile=true, defaultSaveAction=SA_SAVEASK, callbacks=0x000000016fb52450, pInternal=0x0000000101183d50) at REmbeddedPosix.cpp:158:4 [opt]
    frame #28: 0x0000000100cf05a8 rsession`rstudio::r::session::run(options=0x000000016fb52dd0, callbacks=<unavailable>) at RSession.cpp:424:4 [opt]
    frame #29: 0x00000001003fc634 rsession`main(argc=<unavailable>, argv=<unavailable>) at SessionMain.cpp:2445:15 [opt]
    frame #30: 0x000000019da67e50 dyld`start + 2544

@romainfrancois can you take a look?

@romainfrancois
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is the ALTREP deferred string:

> .Internal(inspect(lm.D9))
@150606248 19 VECSXP g0c5 [OBJ,REF(16),ATT] (len=13, tl=0)
  @150eb2b08 14 REALSXP g0c2 [REF(6),ATT] (len=2, tl=0) 5.032,-0.371
  ATTRIB:
    @150a91260 02 LISTSXP g0c0 [REF(1)] 
      TAG: @12501d020 01 SYMSXP g1c0 [MARK,REF(54236),LCK,gp=0x4000] "names" (has value)
      @150eb23c8 16 STRSXP g0c2 [REF(65535)] (len=2, tl=0)
	@150eb2408 09 CHARSXP g0c2 [REF(11),gp=0x60,ATT] [ASCII] [cached] "(Intercept)"
	@150eb2448 09 CHARSXP g0c2 [REF(11),gp=0x60] [ASCII] [cached] "groupTrt"
  @6000020ffc20 14 REALSXP g0c7 [REF(6),ATT] (len=20, tl=0) -0.862,0.548,0.148,1.078,-0.532,...
  ATTRIB:
    @150a92f50 02 LISTSXP g0c0 [REF(1)] 
      TAG: @12501d020 01 SYMSXP g1c0 [MARK,REF(54236),LCK,gp=0x4000] "names" (has value)
      @1503552b0 16 STRSXP g0c0 [REF(65535)]   <deferred string conversion>                                                   <<<<<<< here
	@150354ec0 13 INTSXP g0c0 [REF(65535)]  1 : 20 (compact)
  @6000020ffb50 14 REALSXP g0c7 [REF(6),ATT] (len=20, tl=0) -21.6742,-0.829581,0.196516,1.12652,-0.483484,...
  ATTRIB:
    @150a92f88 02 LISTSXP g0c0 [REF(1)] 
      TAG: @12501d020 01 SYMSXP g1c0 [MARK,REF(54236),LCK,gp=0x4000] "names" (has value)
      @6000020ffa80 16 STRSXP g0c7 [REF(65535)] (len=20, tl=0)
	@150eb2408 09 CHARSXP g0c2 [REF(11),gp=0x60,ATT] [ASCII] [cached] "(Intercept)"
	@150eb2448 09 CHARSXP g0c2 [REF(11),gp=0x60] [ASCII] [cached] "groupTrt"
	@12501f838 09 CHARSXP g1c1 [MARK,REF(7),gp=0x60] [ASCII] [cached] ""
	@12501f838 09 CHARSXP g1c1 [MARK,REF(7),gp=0x60] [ASCII] [cached] ""
	@12501f838 09 CHARSXP g1c1 [MARK,REF(7),gp=0x60] [ASCII] [cached] ""
	...
  @13fd7a6d8 13 INTSXP g0c1 [REF(12)] (len=1, tl=0) 2
  @6000020ff9b0 14 REALSXP g0c7 [REF(5),ATT] (len=20, tl=0) 5.032,5.032,5.032,5.032,5.032,...
  ATTRIB:
    @150a91420 02 LISTSXP g0c0 [REF(1)] 
      TAG: @12501d020 01 SYMSXP g1c0 [MARK,REF(54236),LCK,gp=0x4000] "names" (has value)
      @1503552b0 16 STRSXP g0c0 [REF(65535)]   <deferred string conversion>
	@150354ec0 13 INTSXP g0c0 [REF(65535)]  1 : 20 (compact)
  ...
ATTRIB:
  @13fb581a0 02 LISTSXP g0c0 [REF(1)] 
    TAG: @12501d020 01 SYMSXP g1c0 [MARK,REF(54236),LCK,gp=0x4000] "names" (has value)
    @1506062f8 16 STRSXP g0c5 [REF(65535)] (len=13, tl=0)
      @126cc5fc8 09 CHARSXP g1c2 [MARK,REF(57),gp=0x61] [ASCII] [cached] "coefficients"
      @126d756c8 09 CHARSXP g1c2 [MARK,REF(129),gp=0x61] [ASCII] [cached] "residuals"
      @125dd7a08 09 CHARSXP g1c1 [MARK,REF(76),gp=0x61,ATT] [ASCII] [cached] "effects"
      @12682e1f8 09 CHARSXP g1c1 [MARK,REF(272),gp=0x61] [ASCII] [cached] "rank"
      @126cab088 09 CHARSXP g1c2 [MARK,REF(46),gp=0x61] [ASCII] [cached] "fitted.values"
      ...
    TAG: @12501d5d0 01 SYMSXP g1c0 [MARK,REF(65535),LCK,gp=0x4000] "class" (has value)
    @13fd7aa90 16 STRSXP g0c1 [REF(65535)] (len=1, tl=0)
      @126066940 09 CHARSXP g1c1 [MARK,REF(425),gp=0x61] [ASCII] [cached] "lm"

and I can reproduce the crash with the simpler:

y <- as.character(1:10)

@romainfrancois
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm pretty sure it's this, from https://github.com/r-devel/r-svn/blob/b7a3736e38919613e8a1ba44977ebca7a640938b/src/main/altclasses.c#L622-L638

aka some SEXP are null pointer.

static SEXP R_OutDecSym = NULL;

static R_INLINE const char *DEFERRED_STRING_OUTDEC(SEXP x)
{
    /* The default value of OutDec at startup is ".". If it is
       something different at the time the deferred string conversion
       is created then the current value is stored as an attribute. */
    if (R_OutDecSym == NULL)
	R_OutDecSym = install("OutDec");
    SEXP info = DEFERRED_STRING_INFO(x);
    if (ATTRIB(info) != R_NilValue) {
	SEXP outdecattr = getAttrib(info, R_OutDecSym);
	if (TYPEOF(outdecattr) == STRSXP && XLENGTH(outdecattr) == 1)
	    return CHAR(STRING_ELT(outdecattr, 0));
    }
    return ".";
}

@romainfrancois
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there was more to this story; but I think this fixes it: #12445

Please sign in to comment.