Commit 575584a9 by Nicola Pero Committed by Nicola Pero

In libobjc/: 2010-12-18 Nicola Pero <nicola.pero@meta-innovation.com>

In libobjc/:
2010-12-18  Nicola Pero  <nicola.pero@meta-innovation.com>

	* class.c: Tidied up comments and indentation.  No code changes.
	* error.c: Same.
	* exception.c: Same.
	* init.c: Same.
	* ivars.c: Same.
	* memory.c: Same.
	* objc-foreach.c: Same.
	* objc-sync.c: Same.
	* objects.c: Same.
	* protocols.c: Same.
	* sarray.c: Same.
	* thr.c: Same.

From-SVN: r168022
parent f21fe684
2010-12-18 Nicola Pero <nicola.pero@meta-innovation.com>
* class.c: Tidied up comments and indentation. No code changes.
* error.c: Same.
* exception.c: Same.
* init.c: Same.
* ivars.c: Same.
* memory.c: Same.
* objc-foreach.c: Same.
* objc-sync.c: Same.
* objects.c: Same.
* protocols.c: Same.
* sarray.c: Same.
* thr.c: Same.
2010-12-17 Nicola Pero <nicola.pero@meta-innovation.com>
* init.c: Include objc/runtime.h and objc-private/module-abi-8.h
......
......@@ -26,12 +26,10 @@ a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/*
The code in this file critically affects class method invocation
/* The code in this file critically affects class method invocation
speed. This long preamble comment explains why, and the issues
involved.
One of the traditional weaknesses of the GNU Objective-C runtime is
that class method invocations are slow. The reason is that when you
write
......@@ -97,11 +95,11 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#include <string.h> /* For memset */
/* We use a table which maps a class name to the corresponding class
* pointer. The first part of this file defines this table, and
* functions to do basic operations on the table. The second part of
* the file implements some higher level Objective-C functionality for
* classes by using the functions provided in the first part to manage
* the table. */
pointer. The first part of this file defines this table, and
functions to do basic operations on the table. The second part of
the file implements some higher level Objective-C functionality for
classes by using the functions provided in the first part to manage
the table. */
/**
** Class Table Internals
......@@ -176,7 +174,8 @@ class_table_setup (void)
}
/* Insert a class in the table (used when a new class is registered). */
/* Insert a class in the table (used when a new class is
registered). */
static void
class_table_insert (const char *class_name, Class class_pointer)
{
......@@ -221,18 +220,15 @@ class_table_replace (Class old_class_pointer, Class new_class_pointer)
{
hash++;
if (hash < CLASS_TABLE_SIZE)
{
node = class_table_array[hash];
}
}
else
{
Class class1 = node->pointer;
if (class1 == old_class_pointer)
{
node->pointer = new_class_pointer;
}
node = node->next;
}
}
......@@ -267,10 +263,8 @@ class_table_get_safe (const char *class_name)
for (i = 0; i < length; i++)
{
if ((node->name)[i] != class_name[i])
{
break;
}
}
if (i == length)
{
......@@ -309,9 +303,7 @@ class_table_next (struct class_table_enumerator **e)
next = class_table_array[enumerator->hash];
}
else
{
next = enumerator->node->next;
}
if (next != NULL)
{
......@@ -385,18 +377,16 @@ class_table_print_histogram (void)
{
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
{
printf ("X");
}
printf ("\n");
counter = 0;
}
}
printf ("%4d:", i + 1);
for (j = 0; j < counter; j++)
{
printf ("X");
}
printf ("\n");
}
#endif /* DEBUGGING FUNCTIONS */
......@@ -558,10 +548,8 @@ objc_getClassList (Class *returnValue, int maxNumberOfClassesToReturn)
if (count < maxNumberOfClassesToReturn)
returnValue[count] = node->pointer;
else
{
return count;
}
}
count++;
node = node->next;
}
......@@ -869,22 +857,18 @@ __objc_update_classes_with_methods (struct objc_method *method_a, struct objc_me
/* If the method is one of the ones we are looking
for, update the implementation. */
if (method == method_a)
{
sarray_at_put_safe (class->dtable,
(sidx) method_a->method_name->sel_id,
method_a->method_imp);
}
if (method == method_b)
{
if (method_b != NULL)
{
sarray_at_put_safe (class->dtable,
(sidx) method_b->method_name->sel_id,
method_b->method_imp);
}
}
}
method_list = method_list->method_next;
}
......
......@@ -28,8 +28,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
/* __USE_FIXED_PROTOTYPES__ used to be required to get prototypes for
malloc, free, etc. on some platforms. It is unclear if we still
need it, but it can't hurt.
*/
need it, but it can't hurt. */
#define __USE_FIXED_PROTOTYPES__
#include <stdlib.h>
#include <stdio.h>
......
......@@ -33,15 +33,13 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
/* This hook allows libraries to sepecify special actions when an
exception is thrown without a handler in place. This is deprecated
in favour of objc_set_uncaught_exception_handler ().
*/
in favour of objc_set_uncaught_exception_handler (). */
void (*_objc_unexpected_exception) (id exception); /* !T:SAFE */
/* 'is_kind_of_exception_matcher' is our default exception matcher -
it determines if the object 'exception' is of class 'catch_class',
or of a subclass.
*/
or of a subclass. */
static int
is_kind_of_exception_matcher (Class catch_class, id exception)
{
......@@ -49,9 +47,8 @@ is_kind_of_exception_matcher (Class catch_class, id exception)
if (catch_class == Nil)
return 1;
/* If exception is nil (eg, @throw nil;), then it can only be catched
* by a catch-all (eg, @catch (id object)).
*/
/* If exception is nil (eg, @throw nil;), then it can only be
catched by a catch-all (eg, @catch (id object)). */
if (exception != nil)
{
Class c;
......@@ -114,19 +111,18 @@ static const _Unwind_Exception_Class __objc_exception_class
/* This is the object that is passed around by the Objective C runtime
to represent the exception in flight. */
struct ObjcException
{
/* This bit is needed in order to interact with the unwind runtime. */
struct _Unwind_Exception base;
/* The actual object we want to throw. Note: must come immediately after
unwind header. */
/* The actual object we want to throw. Note: must come immediately
after unwind header. */
id value;
#ifdef __ARM_EABI_UNWINDER__
/* Note: we use the barrier cache defined in the unwind control block for
ARM EABI. */
/* Note: we use the barrier cache defined in the unwind control
block for ARM EABI. */
#else
/* Cache some internal unwind data between phase 1 and phase 2. */
_Unwind_Ptr landingPad;
......@@ -156,14 +152,16 @@ parse_lsda_header (struct _Unwind_Context *context, const unsigned char *p,
info->Start = (context ? _Unwind_GetRegionStart (context) : 0);
/* Find @LPStart, the base to which landing pad offsets are relative. */
/* Find @LPStart, the base to which landing pad offsets are
relative. */
lpstart_encoding = *p++;
if (lpstart_encoding != DW_EH_PE_omit)
p = read_encoded_value (context, lpstart_encoding, p, &info->LPStart);
else
info->LPStart = info->Start;
/* Find @TType, the base of the handler and exception spec type data. */
/* Find @TType, the base of the handler and exception spec type
data. */
info->ttype_encoding = *p++;
if (info->ttype_encoding != DW_EH_PE_omit)
{
......@@ -222,7 +220,8 @@ get_ttype_entry (struct lsda_header_info *info, _Unwind_Word i)
#endif
/* Using a different personality function name causes link failures
when trying to mix code using different exception handling models. */
when trying to mix code using different exception handling
models. */
#ifdef SJLJ_EXCEPTIONS
#define PERSONALITY_FUNCTION __gnu_objc_personality_sj0
#define __builtin_eh_return_data_regno(x) x
......@@ -294,14 +293,14 @@ PERSONALITY_FUNCTION (int version,
}
actions |= state & _US_FORCE_UNWIND;
/* TODO: Foreign exceptions need some attention (e.g. rethrowing doesn't
work). */
/* TODO: Foreign exceptions need some attention (e.g. rethrowing
doesn't work). */
foreign_exception = 0;
/* The dwarf unwinder assumes the context structure holds things like the
function and LSDA pointers. The ARM implementation caches these in
the exception header (UCB). To avoid rewriting everything we make the
virtual IP register point at the UCB. */
/* The dwarf unwinder assumes the context structure holds things
like the function and LSDA pointers. The ARM implementation
caches these in the exception header (UCB). To avoid rewriting
everything we make the virtual IP register point at the UCB. */
ip = (_Unwind_Ptr) ue_header;
_Unwind_SetGR (context, 12, ip);
......@@ -351,8 +350,8 @@ PERSONALITY_FUNCTION (int version,
#ifdef SJLJ_EXCEPTIONS
/* The given "IP" is an index into the call-site table, with two
exceptions -- -1 means no-action, and 0 means terminate. But
since we're using uleb128 values, we've not got random access
to the array. */
since we're using uleb128 values, we've not got random access to
the array. */
if ((int) ip < 0)
return _URC_CONTINUE_UNWIND;
else
......@@ -373,13 +372,15 @@ PERSONALITY_FUNCTION (int version,
goto found_something;
}
#else
/* Search the call-site table for the action associated with this IP. */
/* Search the call-site table for the action associated with this
IP. */
while (p < info.action_table)
{
_Unwind_Ptr cs_start, cs_len, cs_lp;
_uleb128_t cs_action;
/* Note that all call-site encodings are "absolute" displacements. */
/* Note that all call-site encodings are "absolute"
displacements. */
p = read_encoded_value (0, info.call_site_encoding, p, &cs_start);
p = read_encoded_value (0, info.call_site_encoding, p, &cs_len);
p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp);
......@@ -400,8 +401,8 @@ PERSONALITY_FUNCTION (int version,
#endif /* SJLJ_EXCEPTIONS */
/* If ip is not present in the table, C++ would call terminate. */
/* ??? As with Java, it's perhaps better to tweek the LSDA to
that no-action is mapped to no-entry. */
/* ??? As with Java, it's perhaps better to tweek the LSDA to that
no-action is mapped to no-entry. */
CONTINUE_UNWINDING;
found_something:
......@@ -410,8 +411,8 @@ PERSONALITY_FUNCTION (int version,
if (landing_pad == 0)
{
/* If ip is present, and has a null landing pad, there are
no cleanups or handlers to be run. */
/* If ip is present, and has a null landing pad, there are no
cleanups or handlers to be run. */
}
else if (action_record == 0)
{
......@@ -438,14 +439,14 @@ PERSONALITY_FUNCTION (int version,
}
/* During forced unwinding, we only run cleanups. With a
foreign exception class, we have no class info to match. */
foreign exception class, we have no class info to
match. */
else if ((actions & _UA_FORCE_UNWIND) || foreign_exception)
;
else if (ar_filter > 0)
{
/* Positive filter values are handlers. */
Class catch_type = get_ttype_entry (&info, ar_filter);
if ((*__objc_exception_matcher) (catch_type, xh->value))
......@@ -476,7 +477,8 @@ PERSONALITY_FUNCTION (int version,
if (!saw_handler)
CONTINUE_UNWINDING;
/* For domestic exceptions, we cache data from phase 1 for phase 2. */
/* For domestic exceptions, we cache data from phase 1 for phase
2. */
if (!foreign_exception)
{
#ifdef __ARM_EABI_UNWINDER__
......@@ -531,16 +533,14 @@ objc_exception_throw (id exception)
#endif
/* No exception handler was installed. Call the uncaught exception
handler if any is defined.
*/
handler if any is defined. */
if (__objc_uncaught_exception_handler != 0)
{
(*__objc_uncaught_exception_handler) (exception);
}
/* As a last resort support the old, deprecated way of setting an
uncaught exception handler.
*/
uncaught exception handler. */
if (_objc_unexpected_exception != 0)
{
(*_objc_unexpected_exception) (exception);
......
......@@ -47,11 +47,9 @@ class_getInstanceVariable (Class class_, const char *name)
struct objc_ivar *ivar = &(ivars->ivar_list[i]);
if (!strcmp (ivar->ivar_name, name))
{
return ivar;
}
}
}
class_ = class_getSuperclass (class_);
}
}
......@@ -83,10 +81,8 @@ object_getIndexedIvars (id object)
if (object == nil)
return NULL;
else
{
return (void *)(((char *)object)
+ object->class_pointer->instance_size);
}
}
struct objc_ivar *
......@@ -203,9 +199,7 @@ struct objc_ivar ** class_copyIvarList (Class class_, unsigned int *numberOfRetu
/* Copy the ivars. */
for (i = 0; i < count; i++)
{
returnValue[i] = &(ivar_list->ivar_list[i]);
}
returnValue[i] = NULL;
}
......@@ -243,11 +237,9 @@ class_addIvar (Class class_, const char * ivar_name, size_t size,
struct objc_ivar *ivar = &(ivars->ivar_list[i]);
if (strcmp (ivar->ivar_name, ivar_name) == 0)
{
return NO;
}
}
}
/* Ok, no direct ivars. Check superclasses. */
if (class_getInstanceVariable (objc_getClass ((char *)(class_->super_class)),
......
......@@ -24,11 +24,9 @@ a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/*
This file includes the standard functions for memory allocation and
/* This file includes the standard functions for memory allocation and
disposal. Users should use these functions in their ObjC programs
so that they work properly with garbage collectors.
*/
so that they work properly with garbage collectors. */
/* TODO: Turn these into macros or inline functions. */
......@@ -37,8 +35,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
/* __USE_FIXED_PROTOTYPES__ used to be required to get prototypes for
malloc, free, etc. on some platforms. It is unclear if we still
need it, but it can't hurt.
*/
need it, but it can't hurt. */
#define __USE_FIXED_PROTOTYPES__
#include <stdlib.h>
......@@ -163,11 +160,8 @@ objc_valloc (size_t size)
#endif /* !OBJC_WITH_GC */
/*
Hook functions for memory allocation and disposal. Deprecated
and currently unused.
*/
/* Hook functions for memory allocation and disposal. Deprecated and
currently unused. */
void *(*_objc_malloc) (size_t) = malloc;
void *(*_objc_atomic_malloc) (size_t) = malloc;
void *(*_objc_valloc) (size_t) = malloc;
......
......@@ -22,11 +22,9 @@ a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/*
This file implements objc_enumeration_mutation() and
/* This file implements objc_enumeration_mutation() and
objc_set_enumeration_mutation_handler(), the two functions required
to handle mutations during a fast enumeration.
*/
to handle mutations during a fast enumeration. */
#include "objc-private/common.h"
#include "objc-private/error.h" /* For _objc_abort() */
#include "objc/runtime.h" /* For objc_enumerationMutation() and objc_set_enumeration_mutation_handler() */
......
......@@ -22,16 +22,14 @@ a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/*
This file implements objc_sync_enter() and objc_sync_exit(), the
/* This file implements objc_sync_enter() and objc_sync_exit(), the
two functions required to support @synchronized().
objc_sync_enter(object) needs to get a recursive lock associated
with 'object', and lock it.
objc_sync_exit(object) needs to get the recursive lock associated
with 'object', and unlock it.
*/
with 'object', and unlock it. */
/* To avoid the overhead of continuously allocating and deallocating
locks, we implement a pool of locks. When a lock is needed for an
......@@ -61,18 +59,15 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
which is already held by the current thread without having to use
any protection lock or synchronization mechanism. It can so detect
recursive locks/unlocks, and transform them into no-ops that
require no actual locking or synchronization mechanisms at all.
*/
require no actual locking or synchronization mechanisms at all. */
/* You can disable the thread-local cache (most likely to benchmark
the code with and without it) by compiling with
-DSYNC_CACHE_DISABLE, or commenting out the following line.
*/
-DSYNC_CACHE_DISABLE, or commenting out the following line. */
/* #define SYNC_CACHE_DISABLE */
/* If thread-local storage is not available, automatically disable the
cache.
*/
cache. */
#ifndef HAVE_TLS
# define SYNC_CACHE_DISABLE
#endif
......@@ -85,13 +80,11 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
/* We have 32 pools of locks, each of them protected by its own
protection lock. It's tempting to increase this number to reduce
contention; but in our tests it is high enough.
*/
contention; but in our tests it is high enough. */
#define SYNC_NUMBER_OF_POOLS 32
/* Given an object, it determines which pool contains the associated
lock.
*/
lock. */
#define SYNC_OBJECT_HASH(OBJECT) ((((size_t)OBJECT >> 8) ^ (size_t)OBJECT) & (SYNC_NUMBER_OF_POOLS - 1))
/* The locks protecting each pool. */
......@@ -126,8 +119,8 @@ typedef struct lock_node
because in that case you know that node->usage_count can't get to
zero until you release the lock. It is valid to have usage_count
== 0 and object != nil; in that case, the lock is not currently
being used, but is still currently associated with the object.
*/
being used, but is still currently associated with the
object. */
id object;
/* This is a counter reserved for use by the thread currently
......@@ -143,21 +136,18 @@ typedef struct lock_node
require any synchronization with other threads, since it's
protected by the node->lock itself) instead of the usage_count
(which requires locking the pool protection lock). And it can
skip the call to objc_mutex_lock/unlock too.
*/
skip the call to objc_mutex_lock/unlock too. */
unsigned int recursive_usage_count;
} *lock_node_ptr;
/* The pools of locks. Each of them is a linked list of lock_nodes.
In the list we keep both unlocked and locked nodes.
*/
In the list we keep both unlocked and locked nodes. */
static lock_node_ptr sync_pool_array[SYNC_NUMBER_OF_POOLS];
#ifndef SYNC_CACHE_DISABLE
/* We store a cache of locks acquired by each thread in thread-local
storage.
*/
storage. */
static __thread lock_node_ptr *lock_cache = NULL;
/* This is a conservative implementation that uses a static array of
......@@ -176,8 +166,7 @@ static __thread lock_node_ptr *lock_cache = NULL;
first 8 get the speed benefits of the cache, but the cache remains
always small, fast and predictable.
SYNC_CACHE_SIZE is the size of the lock cache for each thread.
*/
SYNC_CACHE_SIZE is the size of the lock cache for each thread. */
#define SYNC_CACHE_SIZE 8
#endif /* SYNC_CACHE_DISABLE */
......@@ -217,23 +206,20 @@ objc_sync_enter (id object)
lock_node_ptr unused_node;
if (object == nil)
{
return OBJC_SYNC_SUCCESS;
}
#ifndef SYNC_CACHE_DISABLE
if (lock_cache == NULL)
{
/* Note that this calloc only happen only once per thread, the
very first time a thread does a objc_sync_enter().
*/
very first time a thread does a objc_sync_enter(). */
lock_cache = objc_calloc (SYNC_CACHE_SIZE, sizeof (lock_node_ptr));
}
/* Check the cache to see if we have a record of having already
locked the lock corresponding to this object. While doing so,
keep track of the first free cache node in case we need it later.
*/
keep track of the first free cache node in case we need it
later. */
node = NULL;
free_cache_slot = -1;
......@@ -246,10 +232,8 @@ objc_sync_enter (id object)
if (locked_node == NULL)
{
if (free_cache_slot == -1)
{
free_cache_slot = i;
}
}
else if (locked_node->object == object)
{
node = locked_node;
......@@ -261,26 +245,22 @@ objc_sync_enter (id object)
if (node != NULL)
{
/* We found the lock. Increase recursive_usage_count, which is
protected by node->lock, which we already hold.
*/
protected by node->lock, which we already hold. */
node->recursive_usage_count++;
/* There is no need to actually lock anything, since we already
hold the lock. Correspondingly, objc_sync_exit() will just
decrease recursive_usage_count and do nothing to unlock.
*/
decrease recursive_usage_count and do nothing to unlock. */
return OBJC_SYNC_SUCCESS;
}
#endif /* SYNC_CACHE_DISABLE */
/* The following is the standard lookup for the lock in the standard
pool lock. It requires a pool protection lock.
*/
pool lock. It requires a pool protection lock. */
hash = SYNC_OBJECT_HASH(object);
/* Search for an existing lock for 'object'. While searching, make
note of any unused lock if we find any.
*/
note of any unused lock if we find any. */
unused_node = NULL;
objc_mutex_lock (sync_pool_protection_locks[hash]);
......@@ -298,9 +278,7 @@ objc_sync_enter (id object)
#ifndef SYNC_CACHE_DISABLE
/* Put it in the cache. */
if (free_cache_slot != -1)
{
lock_cache[free_cache_slot] = node;
}
#endif
/* Lock it. */
......@@ -329,9 +307,7 @@ objc_sync_enter (id object)
#ifndef SYNC_CACHE_DISABLE
if (free_cache_slot != -1)
{
lock_cache[free_cache_slot] = unused_node;
}
#endif
objc_mutex_lock (unused_node->lock);
......@@ -357,9 +333,7 @@ objc_sync_enter (id object)
#ifndef SYNC_CACHE_DISABLE
if (free_cache_slot != -1)
{
lock_cache[free_cache_slot] = new_node;
}
#endif
objc_mutex_lock (new_node->lock);
......@@ -375,9 +349,7 @@ objc_sync_exit (id object)
lock_node_ptr node;
if (object == nil)
{
return OBJC_SYNC_SUCCESS;
}
#ifndef SYNC_CACHE_DISABLE
if (lock_cache != NULL)
......@@ -399,7 +371,6 @@ objc_sync_exit (id object)
/* Note that, if a node was found in the cache, the variable i
now holds the index where it was found, which will be used to
remove it from the cache. */
if (node != NULL)
{
if (node->recursive_usage_count > 0)
......@@ -413,8 +384,8 @@ objc_sync_exit (id object)
hash = SYNC_OBJECT_HASH(object);
/* TODO: If we had atomic increase/decrease operations
with memory barriers, we could avoid the lock here!
*/
with memory barriers, we could avoid the lock
here! */
objc_mutex_lock (sync_pool_protection_locks[hash]);
node->usage_count--;
/* Normally, we do not reset object to nil here. We'll
......@@ -430,8 +401,7 @@ objc_sync_exit (id object)
object from being released. In that case, we remove
it (TODO: maybe we should avoid using the garbage
collector at all ? Nothing is ever deallocated in
this file).
*/
this file). */
#if OBJC_WITH_GC
node->object = nil;
#endif
......@@ -442,8 +412,7 @@ objc_sync_exit (id object)
objc_mutex_unlock (node->lock), the pool is unlocked
so other threads may allocate this same lock to
another object (!). This is not a problem, but it is
curious.
*/
curious. */
objc_mutex_unlock (node->lock);
/* Remove the node from the cache. */
......@@ -476,9 +445,7 @@ objc_sync_exit (id object)
objc_mutex_unlock (node->lock);
/* No need to remove the node from the cache, since it
wasn't found in the cache when we looked for it!
*/
wasn't found in the cache when we looked for it! */
return OBJC_SYNC_SUCCESS;
}
......
......@@ -123,11 +123,8 @@ object_setClass (id object, Class class_)
}
}
/*
Hook functions for memory allocation and disposal. Deprecated
and currently unused.
*/
/* Hook functions for memory allocation and disposal. Deprecated and
currently unused. */
id (*_objc_object_alloc) (Class) = 0;
id (*_objc_object_dispose) (id) = 0;
id (*_objc_object_copy) (id) = 0;
......@@ -69,9 +69,7 @@ __objc_protocols_add_protocol (const char *name, Protocol *object)
Objective-C programs while trying to catch a problem that has
never been seen in practice, so we don't do it. */
if (! objc_hash_is_key_in_hash (__protocols_hashtable, name))
{
objc_hash_add (&__protocols_hashtable, name, object);
}
objc_mutex_unlock (__protocols_hashtable_lock);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment