[macruby-changes] [4004] MacRuby/trunk

source_changes at macosforge.org source_changes at macosforge.org
Sun May 2 19:13:46 PDT 2010


Revision: 4004
          http://trac.macosforge.org/projects/ruby/changeset/4004
Author:   lsansonetti at apple.com
Date:     2010-05-02 19:13:40 -0700 (Sun, 02 May 2010)
Log Message:
-----------
raising RuntimeError as in 0.5 in case we try to mutate a non-mutable collection

Modified Paths:
--------------
    MacRuby/trunk/NSArray.m
    MacRuby/trunk/NSDictionary.m
    MacRuby/trunk/NSString.m

Removed Paths:
-------------
    MacRuby/trunk/spec/macruby/tags/macruby/core/array_tags.txt
    MacRuby/trunk/spec/macruby/tags/macruby/core/hash_tags.txt
    MacRuby/trunk/spec/macruby/tags/macruby/core/string_tags.txt

Modified: MacRuby/trunk/NSArray.m
===================================================================
--- MacRuby/trunk/NSArray.m	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/NSArray.m	2010-05-03 02:13:40 UTC (rev 4004)
@@ -18,6 +18,27 @@
 VALUE rb_cNSArray;
 VALUE rb_cNSMutableArray;
 
+// Some NSArray instances actually do not even respond to mutable methods.
+// So one way to know is to check if the addObject: method exists.
+#define CHECK_MUTABLE(obj) \
+    do { \
+        if (![obj respondsToSelector:@selector(addObject:)]) { \
+	    rb_raise(rb_eRuntimeError, \
+		    "can't modify frozen/immutable array"); \
+        } \
+    } \
+    while (0)
+
+// If a given mutable operation raises an NSException error,
+// it is likely that the object is not mutable.
+#define TRY_MOP(code) \
+    @try { \
+	code; \
+    } \
+    @catch(NSException *exc) { \
+	rb_raise(rb_eRuntimeError, "can't modify frozen/immutable array"); \
+    }
+
 static id
 nsary_dup(id rcv, SEL sel)
 {
@@ -41,7 +62,8 @@
 static id
 nsary_clear(id rcv, SEL sel)
 {
-    [rcv removeAllObjects];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv removeAllObjects]);
     return rcv;
 }
 
@@ -159,21 +181,23 @@
 	rlen = RARRAY_LEN(rpl);
     }
 
+    CHECK_MUTABLE(ary);
+
     if (beg >= n) {
 	for (long i = n; i < beg; i++) {
-	    [ary addObject:[NSNull null]];
+	    TRY_MOP([ary addObject:[NSNull null]]);
 	}
 	if (rlen > 0 && rpl != Qundef) {
-	    [ary addObjectsFromArray:(id)rpl];
+	    TRY_MOP([ary addObjectsFromArray:(id)rpl]);
 	}
     }
     else {
 	if (rlen > 0 && rpl != Qundef) {
-	    [ary replaceObjectsInRange:NSMakeRange(beg, len)
-		withObjectsFromArray:(id)rpl];
+	    TRY_MOP([ary replaceObjectsInRange:NSMakeRange(beg, len)
+		withObjectsFromArray:(id)rpl]);
 	}
 	else {
-	    [ary removeObjectsInRange:NSMakeRange(beg, len)];
+	    TRY_MOP([ary removeObjectsInRange:NSMakeRange(beg, len)]);
 	}
     }
 }
@@ -189,12 +213,15 @@
 		    idx - len);
 	}
     }
+
+    CHECK_MUTABLE(ary);
+
     if (len <= idx) {
 	for (long i = len; i <= idx; i++) {
-	    [ary addObject:[NSNull null]];
+	    TRY_MOP([ary addObject:[NSNull null]]);
 	} 
     }	
-    [ary replaceObjectAtIndex:idx withObject:RB2OC(val)];
+    TRY_MOP([ary replaceObjectAtIndex:idx withObject:RB2OC(val)]);
 }
 
 static VALUE
@@ -286,8 +313,9 @@
     }
 
     if (remove) {
+	CHECK_MUTABLE(ary);
 	for (long i = 0; i < n; i++) {
-	    [ary removeObjectAtIndex:offset];
+	    TRY_MOP([ary removeObjectAtIndex:offset]);
 	}
     }
 
@@ -318,14 +346,16 @@
 nsary_concat(id rcv, SEL sel, VALUE ary)
 {
     ary = to_ary(ary);
-    [rcv addObjectsFromArray:(id)ary];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv addObjectsFromArray:(id)ary]);
     return rcv;
 }
 
 static id
 nsary_push(id rcv, SEL sel, VALUE elem)
 {
-    [rcv addObject:RB2OC(elem)];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv addObject:RB2OC(elem)]);
     return rcv;
 }
 
@@ -341,11 +371,12 @@
 static VALUE
 nsary_pop(id rcv, SEL sel, int argc, VALUE *argv)
 {
+    CHECK_MUTABLE(rcv);
     if (argc == 0) {
 	const long len = [rcv count];
 	if (len > 0) {
 	    id elem = [rcv objectAtIndex:len - 1];
-	    [rcv removeObjectAtIndex:len - 1];
+	    TRY_MOP([rcv removeObjectAtIndex:len - 1]);
 	    return OC2RB(elem);
 	}
 	return Qnil;
@@ -356,11 +387,12 @@
 static VALUE
 nsary_shift(id rcv, SEL sel, int argc, VALUE *argv)
 {
+    CHECK_MUTABLE(rcv);
     if (argc == 0) {
 	const long len = [rcv count];
 	if (len > 0) {
 	    id elem = [rcv objectAtIndex:0];
-	    [rcv removeObjectAtIndex:0];
+	    TRY_MOP([rcv removeObjectAtIndex:0]);
 	    return OC2RB(elem);
 	}
 	return Qnil;
@@ -371,8 +403,9 @@
 static id
 nsary_unshift(id rcv, SEL sel, int argc, VALUE *argv)
 {
+    CHECK_MUTABLE(rcv);
     for (int i = argc - 1; i >= 0; i--) {
-	[rcv insertObject:RB2OC(argv[i]) atIndex:0];
+	TRY_MOP([rcv insertObject:RB2OC(argv[i]) atIndex:0]);
     }
     return rcv;
 }
@@ -380,6 +413,7 @@
 static void
 nsary_insert(id rcv, long idx, VALUE elem)
 {
+    CHECK_MUTABLE(rcv);
     const long len = [rcv count];
     if (idx < 0) {
 	idx += len;
@@ -389,15 +423,16 @@
     }
     if (idx > len) {
 	for (long i = len; i < idx; i++) {
-	    [rcv addObject:[NSNull null]];
+	    TRY_MOP([rcv addObject:[NSNull null]]);
 	} 	
     }
-    [rcv insertObject:RB2OC(elem) atIndex:idx];
+    TRY_MOP([rcv insertObject:RB2OC(elem) atIndex:idx]);
 }
 
 static id
 nsary_insert_m(id rcv, SEL sel, int argc, VALUE *argv)
 {
+    CHECK_MUTABLE(rcv);
     if (argc < 1) {
 	rb_raise(rb_eArgError, "wrong number of arguments (at least 1)");
     }
@@ -539,8 +574,9 @@
 static id
 nsary_reverse_bang(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     for (long i = 0, count = [rcv count]; i < (count / 2); i++) {
-	[rcv exchangeObjectAtIndex:i withObjectAtIndex:count - i - 1];
+	TRY_MOP([rcv exchangeObjectAtIndex:i withObjectAtIndex:count - i - 1]);
     }
     return rcv;
 }
@@ -563,12 +599,13 @@
 static id
 nsary_sort_bang(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     if ([rcv count] > 1) {
 	if (rb_block_given_p()) {
-	    [rcv sortUsingFunction:sort_block context:NULL];
+	    TRY_MOP([rcv sortUsingFunction:sort_block context:NULL]);
 	}
 	else {
-	    [rcv sortUsingSelector:@selector(compare:)];
+	    TRY_MOP([rcv sortUsingSelector:@selector(compare:)]);
 	}
     }
     return rcv;
@@ -583,11 +620,12 @@
 static VALUE
 collect(id rcv)
 {
+    CHECK_MUTABLE(rcv);
     for (long i = 0, count = [rcv count]; i < count; i++) {
 	id elem = [rcv objectAtIndex:i];
 	id newval = RB2OC(rb_yield(OC2RB(elem)));
 	RETURN_IF_BROKEN();
-	[rcv replaceObjectAtIndex:i withObject:newval];
+	TRY_MOP([rcv replaceObjectAtIndex:i withObject:newval]);
     }
     return (VALUE)rcv;
 }
@@ -653,8 +691,9 @@
     NSRange range = NSMakeRange(0, len);
     NSUInteger index;
     bool changed = false;
+    CHECK_MUTABLE(rcv);
     while ((index = [rcv indexOfObject:ocelem inRange:range]) != NSNotFound) {
-	[rcv removeObjectAtIndex:index];
+	TRY_MOP([rcv removeObjectAtIndex:index]);
 	range.location = index;
 	range.length = --len - index;
 	changed = true;
@@ -677,6 +716,7 @@
 static VALUE
 nsary_delete_at(id rcv, SEL sel, VALUE pos)
 {
+    CHECK_MUTABLE(rcv);
     long index = NUM2LONG(pos);
     const long len = [rcv count];
     if (index >= len) {
@@ -689,20 +729,21 @@
 	}
     }
     VALUE elem = OC2RB([rcv objectAtIndex:index]);
-    [rcv removeObjectAtIndex:index]; 
+    TRY_MOP([rcv removeObjectAtIndex:index]); 
     return elem;
 }
 
 static VALUE
 reject(id rcv)
 {
+    CHECK_MUTABLE(rcv);
     bool changed = false;
     for (long i = 0, n = [rcv count]; i < n; i++) {
 	VALUE elem = OC2RB([rcv objectAtIndex:i]);
 	VALUE test = rb_yield(elem);
 	RETURN_IF_BROKEN();
 	if (RTEST(test)) {
-	    [rcv removeObjectAtIndex:i];
+	    TRY_MOP([rcv removeObjectAtIndex:i]);
 	    n--;
 	    i--;
 	    changed = true;
@@ -736,7 +777,8 @@
 nsary_replace(id rcv, SEL sel, VALUE other)
 {
     other = to_ary(other);
-    [rcv setArray:(id)other];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv setArray:(id)other]);
     return rcv;
 }
 
@@ -828,6 +870,7 @@
 static VALUE
 nsary_uniq_bang(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     long len = [rcv count];
     bool changed = false;
     for (long i = 0; i < len; i++) {
@@ -835,7 +878,7 @@
 	NSRange range = NSMakeRange(i + 1, len - i - 1);
 	NSUInteger index;
 	while ((index = [rcv indexOfObject:elem inRange:range]) != NSNotFound) {
-	    [rcv removeObjectAtIndex:index];
+	    TRY_MOP([rcv removeObjectAtIndex:index]);
 	    range.location = index;
 	    range.length = --len - index;
 	    changed = true;
@@ -919,10 +962,11 @@
 static id
 nsary_shuffle_bang(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     long i = [rcv count];
     while (i > 0) {
 	const long j = rb_genrand_real() * i--;
-	[rcv exchangeObjectAtIndex:i withObjectAtIndex:j];
+	TRY_MOP([rcv exchangeObjectAtIndex:i withObjectAtIndex:j]);
     }
     return rcv;
 }

Modified: MacRuby/trunk/NSDictionary.m
===================================================================
--- MacRuby/trunk/NSDictionary.m	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/NSDictionary.m	2010-05-03 02:13:40 UTC (rev 4004)
@@ -18,6 +18,27 @@
 VALUE rb_cNSHash;
 VALUE rb_cNSMutableHash;
 
+// Some NSDictionary instances actually do not even respond to mutable methods.
+// So one way to know is to check if the setObject:forKey: method exists.
+#define CHECK_MUTABLE(obj) \
+    do { \
+        if (![obj respondsToSelector:@selector(setObject:forKey:)]) { \
+	    rb_raise(rb_eRuntimeError, \
+		    "can't modify frozen/immutable hash"); \
+        } \
+    } \
+    while (0)
+
+// If a given mutable operation raises an NSException error,
+// it is likely that the object is not mutable.
+#define TRY_MOP(code) \
+    @try { \
+	code; \
+    } \
+    @catch(NSException *exc) { \
+	rb_raise(rb_eRuntimeError, "can't modify frozen/immutable hash"); \
+    }
+
 static id
 to_hash(id hash)
 {
@@ -47,12 +68,14 @@
 static id
 nshash_rehash(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     NSArray *keys = [rcv allKeys];
     NSArray *values = [rcv allValues];
     assert([keys count] == [values count]);
-    [rcv removeAllObjects];
+    TRY_MOP([rcv removeAllObjects]);
     for (unsigned i = 0, count = [keys count]; i < count; i++) {
-	[rcv setObject:[values objectAtIndex:i] forKey:[keys objectAtIndex:i]];
+	TRY_MOP([rcv setObject:[values objectAtIndex:i]
+		forKey:[keys objectAtIndex:i]]);
     }
     return rcv;
 }
@@ -113,7 +136,8 @@
 static VALUE
 nshash_aset(id rcv, SEL sel, VALUE key, VALUE val)
 {
-    [rcv setObject:RB2OC(val) forKey:RB2OC(key)];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv setObject:RB2OC(val) forKey:RB2OC(key)]);
     return val;
 }
 
@@ -251,11 +275,12 @@
 static VALUE
 nshash_shift(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     if ([rcv count] > 0) {
 	id key = [[rcv keyEnumerator] nextObject];
 	assert(key != NULL);
 	id value = [rcv objectForKey:key];
-	[rcv removeObjectForKey:key];
+	TRY_MOP([rcv removeObjectForKey:key]);
 	return rb_assoc_new(OC2RB(key), OC2RB(value));
     }
     return nshash_default(rcv, 0, 0, NULL);
@@ -264,10 +289,11 @@
 static VALUE
 nshash_delete(id rcv, SEL sel, VALUE key)
 {
+    CHECK_MUTABLE(rcv);
     id ockey = RB2OC(key);
     id value = [rcv objectForKey:ockey];
     if (value != nil) {
-	[rcv removeObjectForKey:ockey];
+	TRY_MOP([rcv removeObjectForKey:ockey]);
 	return OC2RB(value);
     }
     if (rb_block_given_p()) {
@@ -279,6 +305,7 @@
 static VALUE
 nshash_delete_if(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     RETURN_ENUMERATOR(rcv, 0, 0);
     NSMutableArray *ary = [NSMutableArray new];
     for (id key in rcv) {
@@ -288,19 +315,20 @@
 	}
 	RETURN_IF_BROKEN();
     }
-    [rcv removeObjectsForKeys:ary];
+    TRY_MOP([rcv removeObjectsForKeys:ary]);
     return (VALUE)rcv;
 }
 
 static VALUE
 nshash_select(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     RETURN_ENUMERATOR(rcv, 0, 0);
     NSMutableDictionary *dict = [NSMutableDictionary new];
     for (id key in rcv) {
 	id value = [rcv objectForKey:key];
 	if (RTEST(rb_yield_values(2, OC2RB(key), OC2RB(value)))) {
-	    [dict setObject:value forKey:key];
+	    TRY_MOP([dict setObject:value forKey:key]);
 	}
 	RETURN_IF_BROKEN();
     }
@@ -334,6 +362,7 @@
 nshash_update(id rcv, SEL sel, id hash)
 {
     hash = to_hash(hash);
+    CHECK_MUTABLE(rcv);
     if (rb_block_given_p()) {
 	for (id key in hash) {
 	    id value = [hash objectForKey:key];
@@ -342,13 +371,13 @@
 		value = RB2OC(rb_yield_values(3, OC2RB(key), OC2RB(old_value),
 			    OC2RB(value)));
 	    }
-	    [rcv setObject:value forKey:key];
+	    TRY_MOP([rcv setObject:value forKey:key]);
 	}
     }
     else {
 	for (id key in hash) {
 	    id value = [hash objectForKey:key];
-	    [rcv setObject:value forKey:key];
+	    TRY_MOP([rcv setObject:value forKey:key]);
 	}
     }    
     return rcv;
@@ -364,7 +393,8 @@
 nshash_replace(id rcv, SEL sel, id hash)
 {
     hash = to_hash(hash);
-    [rcv setDictionary:hash];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv setDictionary:hash]);
     return rcv;
 }
 

Modified: MacRuby/trunk/NSString.m
===================================================================
--- MacRuby/trunk/NSString.m	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/NSString.m	2010-05-03 02:13:40 UTC (rev 4004)
@@ -18,6 +18,27 @@
 VALUE rb_cNSString;
 VALUE rb_cNSMutableString;
 
+// Some NSString instances actually do not even respond to mutable methods.
+// So one way to know is to check if the setString: method exists.
+#define CHECK_MUTABLE(obj) \
+    do { \
+        if (![obj respondsToSelector:@selector(setString:)]) { \
+	    rb_raise(rb_eRuntimeError, \
+		    "can't modify frozen/immutable string"); \
+        } \
+    } \
+    while (0)
+
+// If a given mutable operation raises an NSException error,
+// it is likely that the object is not mutable.
+#define TRY_MOP(code) \
+    @try { \
+	code; \
+    } \
+    @catch(NSException *exc) { \
+	rb_raise(rb_eRuntimeError, "can't modify frozen/immutable string"); \
+    }
+
 static inline VALUE
 to_str(VALUE str)
 {
@@ -59,16 +80,18 @@
 static id
 nsstr_replace(id rcv, SEL sel, VALUE other)
 {
-    [rcv setString:(id)to_str(other)];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv setString:(id)to_str(other)]);
     return rcv;
 }
 
 static id
 nsstr_clear(id rcv, SEL sel)
 {
+    CHECK_MUTABLE(rcv);
     const long len = [rcv length];
     if (len > 0) {
-	[rcv deleteCharactersInRange:NSMakeRange(0, len)];
+	TRY_MOP([rcv deleteCharactersInRange:NSMakeRange(0, len)]);
     }
     return rcv;
 }
@@ -95,7 +118,8 @@
 static id
 nsstr_concat(id rcv, SEL sel, VALUE other)
 {
-    [rcv appendString:(id)to_str(other)];
+    CHECK_MUTABLE(rcv);
+    TRY_MOP([rcv appendString:(id)to_str(other)]);
     return rcv;
 }
 
@@ -180,10 +204,11 @@
 static VALUE
 nsstr_forward_bang(id rcv, SEL sel, int argc, VALUE *argv)
 {
+    CHECK_MUTABLE(rcv);
     VALUE rcv_rstr = nsstr_to_rstr(rcv);
     VALUE ret = rb_vm_call_with_cache2(rb_vm_get_call_cache(sel),
 	    rb_vm_current_block(), rcv_rstr, 0, sel, argc, argv);
-    [rcv setString:(id)rcv_rstr];
+    TRY_MOP([rcv setString:(id)rcv_rstr]);
     return ret;
 }
 

Deleted: MacRuby/trunk/spec/macruby/tags/macruby/core/array_tags.txt
===================================================================
--- MacRuby/trunk/spec/macruby/tags/macruby/core/array_tags.txt	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/spec/macruby/tags/macruby/core/array_tags.txt	2010-05-03 02:13:40 UTC (rev 4004)
@@ -1 +0,0 @@
-critical:An NSArray object is immutable

Deleted: MacRuby/trunk/spec/macruby/tags/macruby/core/hash_tags.txt
===================================================================
--- MacRuby/trunk/spec/macruby/tags/macruby/core/hash_tags.txt	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/spec/macruby/tags/macruby/core/hash_tags.txt	2010-05-03 02:13:40 UTC (rev 4004)
@@ -1 +0,0 @@
-critical:An NSDictionary object is immutable

Deleted: MacRuby/trunk/spec/macruby/tags/macruby/core/string_tags.txt
===================================================================
--- MacRuby/trunk/spec/macruby/tags/macruby/core/string_tags.txt	2010-05-03 01:23:40 UTC (rev 4003)
+++ MacRuby/trunk/spec/macruby/tags/macruby/core/string_tags.txt	2010-05-03 02:13:40 UTC (rev 4004)
@@ -1 +0,0 @@
-critical:An NSString object is immutable
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20100502/9624ab39/attachment-0001.html>


More information about the macruby-changes mailing list