SUMMARY: I'm proposing that R assert that 'connection' options have not changed since first created such that R will produce the following error:
> fh <- file("a.txt", open = "w+") > cat("hello\n", file = fh) > close(fh) > fh2 <- file("b.txt", open = "w+") > cat("world\n", file = fh2) > cat("hello again\n", file = fh) Error in cat("hello again\n", file = fh) : invalid connection (non-existing 'conn_id') Note that, currently in R, the latter silently writes to 'b.txt' - not 'a.txt' (for more details, see https://github.com/HenrikBengtsson/Wishlist-for-R/issues/81). BACKGROUND: In R, connections are indexed by their (zero-based) row indices in the table of available connections. For example, > fh <- file("a.txt", open = "w") > showConnections(all = TRUE) description class mode text isopen can read can write 0 "stdin" "terminal" "r" "text" "opened" "yes" "no" 1 "stdout" "terminal" "w" "text" "opened" "no" "yes" 2 "stderr" "terminal" "w" "text" "opened" "no" "yes" 3 "a.txt" "file" "w" "text" "opened" "no" "yes" > con <- getConnection(3) > identical(con, fh) [1] TRUE ISSUE: The problem with the current design/implementation where connections are referred to by their index (only), is that (i) the table of connections changes over time and (ii) connection indices are recycled. Because a `connection` object holds the connection row index, it means that *the actual underlying connection that a `connection` object refers to may change over its lifetime*. SUGGESTION: Make use of the 'Rconn' struct field 'id', which is unique, to assert that the 'connection' object used is referring to the original/expected connection. The 'id' field is available via attribute 'conn_id' part of a 'connection' object. PATCH: See attached 'connection.patch' file (or https://github.com/HenrikBengtsson/Wishlist-for-R/issues/81#issuecomment-434210222). The patch introduces a new SEXP R_GetConnection2(SEXP sConn) function, which looks up a connection by its index *and* the 'id' field. This function is backward compatible with R_GetConnection(), which looks up a connection by its index (only). In addition, R_GetConnection2() also accepts 'sConn' of type integer, which the looks up the connection similar to how the internal getConnection() function does it. Comment: The patch is just one of many alternatives. Hopefully, it helps clarify what I'm suggesting. It passes 'make check' and I've tested it on a few packages of mine that make heavy use of different types of connections. In addition to "overridden" connections, the patch protects against invalid 'connection':s that have been serialized, e.g. > fh2 <- file("b.txt", open = "w+") > saveRDS(fh2, file = "fh2.rds") > fh3 <- readRDS("fh2.rds") > attr(fh2, "conn_id") <pointer: 0x78> > attr(fh3, "conn_id") <pointer: (nil)> #<== NIL because external pointer was lost when serialized > isOpen(fh2) [1] TRUE > isOpen(fh3) Error in isOpen(fh3) : invalid connection ('conn_id' is NULL) This is useful, when for instance 'connection':s are (incorrectly) passed to background R sessions (e.g. PSOCK cluster nodes). SEE ALSO: * More details of the above are scribbled down on https://github.com/HenrikBengtsson/Wishlist-for-R/issues/81 * R-devel post 'closeAllConnections() can really mess things up', 2016-10-30, https://stat.ethz.ch/pipermail/r-devel/2016-October/073331.html All the best, Henrik
Index: src/include/R_ext/Connections.h =================================================================== --- src/include/R_ext/Connections.h (revision 75521) +++ src/include/R_ext/Connections.h (working copy) @@ -92,6 +92,7 @@ size_t R_ReadConnection(Rconnection con, void *buf, size_t n); size_t R_WriteConnection(Rconnection con, void *buf, size_t n); Rconnection R_GetConnection(SEXP sConn); // added in R 3.3.0 +Rconnection R_GetConnection2(SEXP sConn); // added in R 3.6.0 #ifdef __cplusplus } Index: src/library/tools/src/gramRd.c =================================================================== --- src/library/tools/src/gramRd.c (revision 75521) +++ src/library/tools/src/gramRd.c (working copy) @@ -4435,9 +4435,11 @@ PushState(); - ifile = asInteger(CAR(args)); args = CDR(args); + ifile = asInteger(CAR(args)); - con = getConnection(ifile); + con = R_GetConnection2(CAR(args)); + args = CDR(args); + wasopen = con->isopen; source = CAR(args); args = CDR(args); /* encoding is unused */ Index: src/library/utils/src/io.c =================================================================== --- src/library/utils/src/io.c (revision 75521) +++ src/library/utils/src/io.c (working copy) @@ -352,7 +352,7 @@ error(_("invalid quote symbol set")); i = asInteger(file); - data.con = getConnection(i); + data.con = R_GetConnection2(file); if(i == 0) { data.ttyflag = 1; } else { @@ -852,7 +852,7 @@ data.skipNul = skipNul; i = asInteger(file); - data.con = getConnection(i); + data.con = R_GetConnection2(file); data.ttyflag = (i == 0); data.wasopen = data.con->isopen; if(!data.wasopen) { @@ -1076,7 +1076,8 @@ /* this is going to be a connection open or openable for writing */ if(!inherits(CAR(args), "connection")) error(_("'file' is not a connection")); - con = getConnection(asInteger(CAR(args))); args = CDR(args); + con = R_GetConnection2(CAR(args)); + args = CDR(args); if(!con->canwrite) error(_("cannot write to this connection")); wasopen = con->isopen; Index: src/main/builtin.c =================================================================== --- src/main/builtin.c (revision 75521) +++ src/main/builtin.c (working copy) @@ -565,7 +565,7 @@ file = CAR(args); ifile = asInteger(file); - con = getConnection(ifile); + con = R_GetConnection2(file); if(!con->canwrite) /* if it is not open, we may not know yet */ error(_("cannot write to this connection")); args = CDR(args); Index: src/main/connections.c =================================================================== --- src/main/connections.c (revision 75521) +++ src/main/connections.c (working copy) @@ -178,6 +178,21 @@ } +Rconnection getConnection2(int n, void *id) +{ + Rconnection con = NULL; + + if(n < 0 || n >= NCONNECTIONS || n == NA_INTEGER || + !(con = Connections[n])) + error(_("invalid connection")); + if (!id) + error(_("invalid connection ('conn_id' is NULL)")); + else if (con->id != id) + error(_("invalid connection (non-existing 'conn_id')")); + + return con; +} + attribute_hidden int getActiveSink(int n) { @@ -2894,7 +2909,7 @@ checkArity(op, args); if(!inherits(CAR(args), "rawConnection")) error(_("'con' is not a rawConnection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(!con->canwrite) error(_("'con' is not an output rawConnection")); this = con->private; @@ -3318,7 +3333,7 @@ checkArity(op, args); if(!inherits(CAR(args), "textConnection")) error(_("'con' is not a textConnection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(!con->canwrite) error(_("'con' is not an output textConnection")); this = con->private; @@ -3462,7 +3477,7 @@ if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); i = asInteger(CAR(args)); - con = getConnection(i); + con = R_GetConnection2(CAR(args)); if(i < 3) error(_("cannot open standard connections")); if(con->isopen) { warning(_("connection is already open")); @@ -3491,7 +3506,7 @@ int rw, res; checkArity(op, args); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); rw = asInteger(CADR(args)); res = con->isopen != FALSE; switch(rw) { @@ -3510,7 +3525,7 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); return ScalarLogical(con->incomplete != FALSE); } @@ -3521,7 +3536,7 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); return ScalarLogical(con->canseek != FALSE); } @@ -3611,7 +3626,7 @@ error(_("cannot close 'output' sink connection")); if(i == R_ErrorCon) error(_("cannot close 'message' sink connection")); - Rconnection con = getConnection(i); + Rconnection con = R_GetConnection2(CAR(args)); int status = con_close1(con); free(Connections[i]); Connections[i] = NULL; @@ -3634,7 +3649,7 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(!con->isopen) error(_("connection is not open")); where = asReal(CADR(args)); origin = asInteger(CADDR(args)); @@ -3657,7 +3672,7 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); con->truncate(con); return R_NilValue; } @@ -3669,7 +3684,7 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(con->canwrite) con->fflush(con); return R_NilValue; } @@ -3795,7 +3810,8 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - con = getConnection(asInteger(CAR(args))); args = CDR(args); + con = R_GetConnection2(CAR(args)); + args = CDR(args); n = asVecSize(CAR(args)); args = CDR(args); if(n == -999) error(_("invalid '%s' argument"), "n"); @@ -3923,7 +3939,7 @@ if(!inherits(CADR(args), "connection")) error(_("'con' is not a connection")); con_num = asInteger(CADR(args)); - con = getConnection(con_num); + con = R_GetConnection2(CADR(args)); sep = CADDR(args); if(!isString(sep)) error(_("invalid '%s' argument"), "sep"); useBytes = asLogical(CADDDR(args)); @@ -4076,7 +4092,7 @@ bytes = RAW(CAR(args)); nbytes = XLENGTH(CAR(args)); } else { - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(con->text) error(_("can only read from a binary connection")); } @@ -4335,7 +4351,7 @@ if(TYPEOF(CADR(args)) == RAWSXP) { isRaw = TRUE; } else { - con = getConnection(asInteger(CADR(args))); + con = R_GetConnection2(CADR(args)); if(con->text) error(_("can only write to a binary connection")); wasopen = con->isopen; if(!con->canwrite) error(_("cannot write to this connection")); @@ -4676,7 +4692,7 @@ bytes = RAW(CAR(args)); nbytes = LENGTH(CAR(args)); } else { - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(!con->canread) error(_("cannot read from this connection")); } @@ -4756,7 +4772,7 @@ if(TYPEOF(CADR(args)) == RAWSXP) { isRaw = TRUE; } else { - con = getConnection(asInteger(CADR(args))); + con = R_GetConnection2(CADR(args)); if(!con->canwrite) error(_("cannot write to this connection")); wasopen = con->isopen; @@ -4955,7 +4971,7 @@ stext = CAR(args); if(!isString(stext)) error(_("invalid '%s' argument"), "data"); - con = getConnection(asInteger(CADR(args))); + con = R_GetConnection2(CADR(args)); newLine = asLogical(CADDR(args)); if(newLine == NA_LOGICAL) error(_("invalid '%s' argument"), "newLine"); @@ -4994,7 +5010,7 @@ Rconnection con = NULL; checkArity(op, args); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); return ScalarInteger(con->nPushBack); } @@ -5004,7 +5020,7 @@ Rconnection con = NULL; checkArity(op, args); - con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); if(con->nPushBack > 0) { for(j = 0; j < con->nPushBack; j++) free(con->PushBack[j]); @@ -5173,12 +5189,14 @@ SEXP attribute_hidden do_getconnection(SEXP call, SEXP op, SEXP args, SEXP env) { - SEXP ans, class; + SEXP input, conn_id, ans, class; int what; + void *id; Rconnection con; checkArity(op, args); - what = asInteger(CAR(args)); + input = CAR(args); + what = asInteger(input); if (what == NA_INTEGER) error(_("there is no connection NA")); if (what < 0 || what >= NCONNECTIONS || !Connections[what]) @@ -5185,6 +5203,18 @@ error(_("there is no connection %d"), what); con = Connections[what]; + + if (what > 2) { + conn_id = getAttrib(input, R_ConnIdSymbol); + if (conn_id != R_NilValue) { + id = R_ExternalPtrAddr(conn_id); + if (!id) + error(_("invalid connection ('conn_id' is NULL)")); + else if (con->id != id) + error(_("invalid connection (non-existing 'conn_id')")); + } + } + PROTECT(ans = ScalarInteger(what)); PROTECT(class = allocVector(STRSXP, 2)); SET_STRING_ELT(class, 0, mkChar(con->class)); @@ -5203,6 +5233,7 @@ checkArity(op, args); Rcon = getConnection(asInteger(CAR(args))); + /* Rcon = R_GetConnection2(CAR(args)); */ PROTECT(ans = allocVector(VECSXP, 7)); PROTECT(names = allocVector(STRSXP, 7)); SET_STRING_ELT(names, 0, mkChar("description")); @@ -5516,6 +5547,22 @@ return getConnection(asInteger(sConn)); } +Rconnection R_GetConnection2(SEXP sConn) { + int n; + SEXP conn_id; + + if (!isInteger(sConn)) error(_("invalid connection (non-integer value)")); + + n = asInteger(sConn); + + if (!inherits(sConn, "connection")) return getConnection(n); + + conn_id = getAttrib(sConn, R_ConnIdSymbol); + if (conn_id == R_NilValue) return getConnection(n); + + return getConnection2(n, R_ExternalPtrAddr(conn_id)); +} + /* ------------------- (de)compression functions --------------------- */ /* Code for gzcon connections is modelled on gzio.c from zlib 1.2.3 */ @@ -5789,7 +5836,8 @@ checkArity(op, args); if(!inherits(CAR(args), "connection")) error(_("'con' is not a connection")); - incon = getConnection(icon = asInteger(CAR(args))); + incon = R_GetConnection2(CAR(args)); + icon = asInteger(CAR(args)); level = asInteger(CADR(args)); if(level == NA_INTEGER || level < 0 || level > 9) error(_("'level' must be one of 0 ... 9")); @@ -6045,7 +6093,7 @@ PROTECT(val = allocVector(LGLSXP, nsock)); for (i = 0; i < nsock; i++) { - Rconnection conn = getConnection(asInteger(VECTOR_ELT(insock, i))); + Rconnection conn = R_GetConnection2(VECTOR_ELT(insock, i)); Rsockconn scp = conn->private; if (strcmp(conn->class, "sockconn") != 0) error(_("not a socket connection")); Index: src/main/dcf.c =================================================================== --- src/main/dcf.c (revision 75521) +++ src/main/dcf.c (working copy) @@ -88,6 +88,7 @@ file = CAR(args); con = getConnection(asInteger(file)); + con = R_GetConnection2(file); wasopen = con->isopen; if(!wasopen) { if(!con->open(con)) error(_("cannot open the connection")); Index: src/main/deparse.c =================================================================== --- src/main/deparse.c (revision 75521) +++ src/main/deparse.c (working copy) @@ -386,6 +386,7 @@ int ifile = asInteger(CADR(args)); if (ifile != 1) { Rconnection con = getConnection(ifile); + con = R_GetConnection2(CADR(args)); RCNTXT cntxt; Rboolean wasopen = con->isopen; if(!wasopen) { @@ -472,6 +473,7 @@ } else { Rconnection con = getConnection(INTEGER(file)[0]); + con = R_GetConnection2(file); Rboolean wasopen = con->isopen; RCNTXT cntxt; if(!wasopen) { Index: src/main/saveload.c =================================================================== --- src/main/saveload.c (revision 75521) +++ src/main/saveload.c (working copy) @@ -2335,6 +2335,7 @@ list = CAR(args); con = getConnection(asInteger(CADR(args))); + con = R_GetConnection2(CADR(args)); if (TYPEOF(CADDR(args)) != LGLSXP) error(_("'ascii' must be logical")); @@ -2439,6 +2440,7 @@ checkArity(op, args); con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); wasopen = con->isopen; if(!wasopen) { Index: src/main/scan.c =================================================================== --- src/main/scan.c (revision 75521) +++ src/main/scan.c (working copy) @@ -925,6 +925,7 @@ int ii = asInteger(file); data.con = getConnection(ii); + data.con = R_GetConnection2(file); if(ii == 0) { data.atStart = FALSE; data.ttyflag = 1; Index: src/main/serialize.c =================================================================== --- src/main/serialize.c (revision 75521) +++ src/main/serialize.c (working copy) @@ -2486,6 +2486,7 @@ object = CAR(args); con = getConnection(asInteger(CADR(args))); + con = R_GetConnection2(CADR(args)); if (TYPEOF(CADDR(args)) != LGLSXP) error(_("'ascii' must be logical")); @@ -2554,6 +2555,7 @@ checkArity(op, args); con = getConnection(asInteger(CAR(args))); + con = R_GetConnection2(CAR(args)); /* Now we need to do some sanity checking of the arguments. A filename will already have been opened, so anything @@ -2649,6 +2651,7 @@ SEXP (*hook)(SEXP, SEXP); struct bconbuf_st bbs; Rconnection con = getConnection(asInteger(icon)); + con = R_GetConnection2(icon); int version; if (Sversion == R_NilValue) @@ -2842,6 +2845,7 @@ } else { Rconnection con = getConnection(asInteger(icon)); + con = R_GetConnection2(icon); R_InitConnOutPStream(&out, con, type, version, hook, fun); R_Serialize(object, &out); return R_NilValue; @@ -2869,6 +2873,7 @@ return R_Unserialize(&in); } else { Rconnection con = getConnection(asInteger(icon)); + con = R_GetConnection2(icon); R_InitConnInPStream(&in, con, R_pstream_any_format, hook, fun); return R_Unserialize(&in); } Index: src/main/source.c =================================================================== --- src/main/source.c (revision 75521) +++ src/main/source.c (working copy) @@ -221,9 +221,11 @@ R_ParseError = 0; R_ParseErrorMsg[0] = '\0'; - ifile = asInteger(CAR(args)); args = CDR(args); + ifile = asInteger(CAR(args)); - con = getConnection(ifile); + con = R_GetConnection2(CAR(args)); + args = CDR(args); + wasopen = con->isopen; num = asInteger(CAR(args)); args = CDR(args); if (num == 0)
______________________________________________ R-devel@r-project.org mailing list https://stat.ethz.ch/mailman/listinfo/r-devel