Skip to content

Commit ec35847

Browse files
committed
Add get_attribute() and friends to RObject
Remove footgun of `r_names()` which didn't protect
1 parent 1f42973 commit ec35847

File tree

8 files changed

+120
-119
lines changed

8 files changed

+120
-119
lines changed

crates/ark/src/srcref.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fn generate_source(
139139
}
140140

141141
// Ignore functions that already have sources
142-
if let Some(_) = old.attr("srcref") {
142+
if let Some(_) = old.get_attribute("srcref") {
143143
return Ok(None);
144144
}
145145

crates/harp/src/attrib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn zap_srcref_fn(x: SEXP) -> RObject {
2020
unsafe {
2121
let x = RObject::view(x).shallow_duplicate();
2222

23-
x.set_attr("srcref", r_null());
23+
x.set_attribute("srcref", r_null());
2424
libr::SET_BODY(x.sexp, zap_srcref(libr::R_ClosureExpr(x.sexp)).sexp);
2525

2626
x
@@ -64,7 +64,7 @@ fn zap_srcref_expr(x: SEXP) -> RObject {
6464

6565
fn zap_srcref_attrib(x: SEXP) {
6666
let x = RObject::view(x);
67-
x.set_attr("srcfile", r_null());
68-
x.set_attr("srcref", r_null());
69-
x.set_attr("wholeSrcref", r_null());
67+
x.set_attribute("srcfile", r_null());
68+
x.set_attribute("srcref", r_null());
69+
x.set_attribute("wholeSrcref", r_null());
7070
}

crates/harp/src/column_names.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::exec::RFunctionExt;
55
use crate::utils::*;
66
use crate::vector::Vector;
77
use crate::CharacterVector;
8+
use crate::RObject;
89

910
/// Column names
1011
///
@@ -17,21 +18,22 @@ pub struct ColumnNames {
1718

1819
impl ColumnNames {
1920
pub fn new(names: SEXP) -> Self {
20-
unsafe {
21-
let names = if r_typeof(names) == STRSXP {
22-
Some(CharacterVector::new_unchecked(names))
23-
} else {
24-
None
25-
};
26-
Self { names }
27-
}
21+
let names = if r_typeof(names) == STRSXP {
22+
Some(unsafe { CharacterVector::new_unchecked(names) })
23+
} else {
24+
None
25+
};
26+
Self { names }
2827
}
2928

3029
pub fn from_data_frame(x: SEXP) -> crate::Result<Self> {
3130
if !r_is_data_frame(x) {
3231
return Err(crate::anyhow!("`x` must be a data frame."));
3332
}
34-
Ok(Self::new(r_names(x)))
33+
let Some(column_names) = RObject::view(x).get_attribute_names() else {
34+
return Err(crate::anyhow!("`x` must have column names."));
35+
};
36+
Ok(Self::new(column_names.sexp))
3537
}
3638

3739
pub fn from_matrix(x: SEXP) -> crate::Result<Self> {

crates/harp/src/data_frame.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ impl DataFrame {
124124
// their own custom methods (like for the variables pane). That keeps our hot path
125125
// simpler, as we unconditionally materialize ALTREP vectors, while still providing a
126126
// way to opt out.
127-
let row_names = RObject::new(harp::r_row_names(x));
127+
let row_names = RObject::view(x).get_attribute_row_names();
128+
129+
let Some(row_names) = row_names else {
130+
return Err(crate::anyhow!("`x` must have row names"));
131+
};
128132

129133
// The row names object is typically an integer vector (possibly ALTREP compact
130134
// intrange that knows its length) or character vector, and we just take the length of
@@ -143,6 +147,7 @@ mod tests {
143147
use crate::r_alloc_integer;
144148
use crate::r_chr_poke;
145149
use crate::r_list_poke;
150+
use crate::r_null;
146151
use crate::vector::Vector;
147152
use crate::DataFrame;
148153
use crate::List;
@@ -167,7 +172,7 @@ mod tests {
167172
fn test_data_frame_no_names() {
168173
crate::r_task(|| {
169174
let df = harp::parse_eval_base("data.frame(x = 1:2, y = 3:4)").unwrap();
170-
df.set_attr("names", RObject::null().sexp);
175+
df.set_attribute("names", r_null());
171176
let df = DataFrame::new(df.sexp);
172177

173178
assert_match!(df, harp::Result::Err(err) => {
@@ -180,7 +185,7 @@ mod tests {
180185
fn test_data_frame_bad_names() {
181186
crate::r_task(|| {
182187
let df = harp::parse_eval_base("data.frame(x = 1:2, y = 3:4)").unwrap();
183-
let nms = df.attr("names").unwrap();
188+
let nms = df.get_attribute("names").unwrap();
184189
unsafe {
185190
r_chr_poke(nms.sexp, 0, libr::R_NaString);
186191
}

crates/harp/src/object.rs

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use libr::*;
1919
use crate::error::Error;
2020
use crate::exec::RFunction;
2121
use crate::exec::RFunctionExt;
22-
use crate::protect::RProtect;
2322
use crate::r_inherits;
2423
use crate::r_symbol;
2524
use crate::size::r_size;
@@ -31,7 +30,6 @@ use crate::utils::r_is_altrep;
3130
use crate::utils::r_is_null;
3231
use crate::utils::r_is_object;
3332
use crate::utils::r_is_s4;
34-
use crate::utils::r_names;
3533
use crate::utils::r_str_to_owned_utf8;
3634
use crate::utils::r_typeof;
3735

@@ -460,39 +458,64 @@ impl RObject {
460458
/// Gets a vector containing names for the object's values (from the `names`
461459
/// attribute). Returns `None` if the object's value(s) don't have names.
462460
pub fn names(&self) -> Option<Vec<Option<String>>> {
463-
let names = r_names(self.sexp);
464-
let names = RObject::from(names);
465-
match names.kind() {
466-
STRSXP => Vec::<Option<String>>::try_from(names).ok(),
467-
_ => None,
461+
match self.get_attribute_names() {
462+
Some(names) => match names.kind() {
463+
STRSXP => Vec::<Option<String>>::try_from(names).ok(),
464+
_ => None,
465+
},
466+
None => None,
468467
}
469468
}
470469

471-
/// Gets a named attribute from the object. Returns `None` if the attribute
472-
/// doesn't exist.
473-
pub fn attr(&self, name: &str) -> Option<RObject> {
474-
let val = unsafe { Rf_getAttrib(self.sexp, r_symbol!(name)) };
475-
if r_is_null(val) {
476-
None
477-
} else {
478-
Some(RObject::new(val))
479-
}
480-
}
481-
482-
pub fn set_attr(&self, name: &str, value: SEXP) {
470+
pub fn set_attribute(&self, name: &str, value: SEXP) {
483471
unsafe {
484472
Rf_protect(value);
485473
Rf_setAttrib(self.sexp, r_symbol!(name), value);
486474
Rf_unprotect(1);
487475
}
488476
}
489477

478+
/// Gets a named attribute from the object. Returns `None` if the attribute
479+
/// was `NULL`.
480+
pub fn get_attribute(&self, name: &str) -> Option<RObject> {
481+
self.get_attribute_from_symbol(unsafe { r_symbol!(name) })
482+
}
483+
484+
/// Gets the [R_NamesSymbol] attribute from the object. Returns `None` if there are no
485+
/// names.
486+
pub fn get_attribute_names(&self) -> Option<RObject> {
487+
self.get_attribute_from_symbol(unsafe { R_NamesSymbol })
488+
}
489+
490+
/// Gets the [R_RowNamesSymbol] attribute from the object. Returns `None` if there are
491+
/// no row names.
492+
///
493+
/// # Notes
494+
///
495+
/// Note that [Rf_getAttrib()] will turn compact row names of the form `c(NA, -5)`
496+
/// into ALTREP compact intrange objects. If you really need to avoid this, use
497+
/// `.row_names_info(x, 0L)` instead, which goes through `getAttrib0()`, but note that
498+
/// R core frowns on this.
499+
/// https://github.com/wch/r-source/blob/e11e04d1f9966551991569b43da2ba6ab2251f30/src/main/attrib.c#L177-L187
500+
pub fn get_attribute_row_names(&self) -> Option<RObject> {
501+
self.get_attribute_from_symbol(unsafe { R_RowNamesSymbol })
502+
}
503+
504+
fn get_attribute_from_symbol(&self, symbol: SEXP) -> Option<RObject> {
505+
let out = unsafe { Rf_getAttrib(self.sexp, symbol) };
506+
if r_is_null(out) {
507+
None
508+
} else {
509+
Some(RObject::new(out))
510+
}
511+
}
512+
490513
pub fn inherits(&self, class: &str) -> bool {
491514
return r_inherits(self.sexp, class);
492515
}
493516

494517
pub fn class(&self) -> harp::Result<Option<Vec<String>>> {
495-
let Some(class) = self.attr("class") else {
518+
let Some(class) = self.get_attribute("class") else {
496519
return Ok(None);
497520
};
498521

@@ -1050,28 +1073,26 @@ impl TryFrom<&Vec<i32>> for RObject {
10501073
impl TryFrom<RObject> for HashMap<String, String> {
10511074
type Error = crate::error::Error;
10521075
fn try_from(value: RObject) -> Result<Self, Self::Error> {
1053-
unsafe {
1054-
r_assert_type(*value, &[STRSXP, VECSXP])?;
1055-
1056-
let mut protect = RProtect::new();
1057-
let names = protect.add(r_names(*value));
1058-
r_assert_type(names, &[STRSXP])?;
1076+
r_assert_type(value.sexp, &[STRSXP, VECSXP])?;
10591077

1060-
let value = protect.add(Rf_coerceVector(*value, STRSXP));
1078+
let Some(names) = value.get_attribute_names() else {
1079+
return Err(Error::UnexpectedType(NILSXP, vec![STRSXP]));
1080+
};
10611081

1062-
let n = Rf_xlength(names);
1063-
let mut map = HashMap::<String, String>::with_capacity(n as usize);
1082+
let value = RObject::new(unsafe { Rf_coerceVector(value.sexp, STRSXP) });
10641083

1065-
for i in (0..Rf_xlength(names)).rev() {
1066-
// Translate the name and value into Rust strings.
1067-
let lhs = r_chr_get_owned_utf8(names, i)?;
1068-
let rhs = r_chr_get_owned_utf8(value, i)?;
1084+
let n = names.length();
1085+
let mut map = HashMap::<String, String>::with_capacity(n as usize);
10691086

1070-
map.insert(lhs, rhs);
1071-
}
1087+
for i in (0..n).rev() {
1088+
// Translate the name and value into Rust strings.
1089+
let lhs = r_chr_get_owned_utf8(names.sexp, i)?;
1090+
let rhs = r_chr_get_owned_utf8(value.sexp, i)?;
10721091

1073-
Ok(map)
1092+
map.insert(lhs, rhs);
10741093
}
1094+
1095+
Ok(map)
10751096
}
10761097
}
10771098

@@ -1080,28 +1101,26 @@ impl TryFrom<RObject> for HashMap<String, String> {
10801101
impl TryFrom<RObject> for HashMap<String, i32> {
10811102
type Error = crate::error::Error;
10821103
fn try_from(value: RObject) -> Result<Self, Self::Error> {
1083-
unsafe {
1084-
r_assert_type(*value, &[INTSXP, VECSXP])?;
1104+
r_assert_type(*value, &[INTSXP, VECSXP])?;
10851105

1086-
let mut protect = RProtect::new();
1087-
let names = protect.add(r_names(*value));
1088-
r_assert_type(names, &[STRSXP])?;
1106+
let Some(names) = value.get_attribute_names() else {
1107+
return Err(Error::UnexpectedType(NILSXP, vec![STRSXP]));
1108+
};
10891109

1090-
let value = protect.add(Rf_coerceVector(*value, INTSXP));
1110+
let value = RObject::new(unsafe { Rf_coerceVector(value.sexp, INTSXP) });
10911111

1092-
let n = Rf_xlength(names);
1093-
let mut map = HashMap::<String, i32>::with_capacity(n as usize);
1112+
let n = names.length();
1113+
let mut map = HashMap::<String, i32>::with_capacity(n as usize);
10941114

1095-
for i in (0..Rf_xlength(names)).rev() {
1096-
// Translate the name and value into Rust strings.
1097-
let name = r_chr_get_owned_utf8(names, i)?;
1098-
let val = r_int_get(value, i);
1115+
for i in (0..n).rev() {
1116+
// Translate the name and value into Rust strings.
1117+
let name = r_chr_get_owned_utf8(names.sexp, i)?;
1118+
let val = r_int_get(value.sexp, i);
10991119

1100-
map.insert(name, val);
1101-
}
1102-
1103-
Ok(map)
1120+
map.insert(name, val);
11041121
}
1122+
1123+
Ok(map)
11051124
}
11061125
}
11071126

@@ -1110,23 +1129,23 @@ impl TryFrom<RObject> for HashMap<String, i32> {
11101129
impl TryFrom<RObject> for HashMap<String, RObject> {
11111130
type Error = crate::error::Error;
11121131
fn try_from(value: RObject) -> Result<Self, Self::Error> {
1113-
unsafe {
1114-
let mut protect = RProtect::new();
1115-
let names = protect.add(r_names(*value));
1116-
r_assert_type(names, &[STRSXP])?;
1117-
1118-
let n = Rf_xlength(names);
1119-
let mut map = HashMap::<String, RObject>::with_capacity(n as usize);
1120-
1121-
// iterate in the reverse order to keep the first occurence of a name
1122-
for i in (0..n).rev() {
1123-
let name = r_chr_get_owned_utf8(names, i)?;
1124-
let value: RObject = RObject::new(VECTOR_ELT(*value, i));
1125-
map.insert(name, value);
1126-
}
1132+
r_assert_type(*value, &[VECSXP])?;
11271133

1128-
Ok(map)
1134+
let Some(names) = value.get_attribute_names() else {
1135+
return Err(Error::UnexpectedType(NILSXP, vec![STRSXP]));
1136+
};
1137+
1138+
let n = names.length();
1139+
let mut map = HashMap::<String, RObject>::with_capacity(n as usize);
1140+
1141+
// iterate in the reverse order to keep the first occurence of a name
1142+
for i in (0..n).rev() {
1143+
let name = r_chr_get_owned_utf8(names.sexp, i)?;
1144+
let value = RObject::new(list_get(value.sexp, i));
1145+
map.insert(name, value);
11291146
}
1147+
1148+
Ok(map)
11301149
}
11311150
}
11321151

crates/harp/src/parser/srcref.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub struct SrcFile {
4040
// attributes.
4141
impl RObject {
4242
pub fn srcrefs(&self) -> anyhow::Result<Vec<SrcRef>> {
43-
let srcref = unwrap!(self.attr("srcref"), None => {
43+
let srcref = unwrap!(self.get_attribute("srcref"), None => {
4444
return Err(anyhow!("Can't find `srcref` attribute"));
4545
});
4646

crates/harp/src/utils.rs

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -393,28 +393,6 @@ pub unsafe fn r_envir_remove(symbol: &str, envir: SEXP) {
393393
R_removeVarFromFrame(r_symbol!(symbol), envir);
394394
}
395395

396-
/// Get the row names attribute of an object
397-
///
398-
/// Raw access to the [R_RowNamesSymbol] attribute. Will return [R_NilValue] when no names
399-
/// are present.
400-
///
401-
/// Note that [Rf_getAttrib()] will turn compact row names of the form `c(NA, -5)` into
402-
/// ALTREP compact intrange objects. If you really need to avoid this, use
403-
/// `.row_names_info(x, 0L)` instead, which goes through `getAttrib0()`, but note that R
404-
/// core frowns on this.
405-
/// https://github.com/wch/r-source/blob/e11e04d1f9966551991569b43da2ba6ab2251f30/src/main/attrib.c#L177-L187
406-
pub fn r_row_names(x: SEXP) -> SEXP {
407-
unsafe { Rf_getAttrib(x, R_RowNamesSymbol) }
408-
}
409-
410-
/// Get the names attribute of an object
411-
///
412-
/// Raw access to the [R_NamesSymbol] attribute. Will return [R_NilValue] when no names
413-
/// are present.
414-
pub fn r_names(x: SEXP) -> SEXP {
415-
unsafe { Rf_getAttrib(x, R_NamesSymbol) }
416-
}
417-
418396
/// Get names of a vector
419397
///
420398
/// `r_names2()` always returns a character vector, even when the object does
@@ -427,7 +405,7 @@ pub fn r_names2(x: SEXP) -> SEXP {
427405

428406
let size = r_length(x);
429407

430-
let names = r_names(x);
408+
let names = unsafe { Rf_getAttrib(x, R_NamesSymbol) };
431409
unsafe { protect.add(names) };
432410

433411
if r_is_null(names) {

0 commit comments

Comments
 (0)