src/share/vm/classfile/loaderConstraints.cpp

Thu, 27 Jan 2011 16:11:27 -0800

author
coleenp
date
Thu, 27 Jan 2011 16:11:27 -0800
changeset 2497
3582bf76420e
parent 2314
f95d63e2154a
child 2551
4f26f535a225
permissions
-rw-r--r--

6990754: Use native memory and reference counting to implement SymbolTable
Summary: move symbols from permgen into C heap and reference count them
Reviewed-by: never, acorn, jmasa, stefank

duke@435 1 /*
stefank@2314 2 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
duke@435 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
duke@435 4 *
duke@435 5 * This code is free software; you can redistribute it and/or modify it
duke@435 6 * under the terms of the GNU General Public License version 2 only, as
duke@435 7 * published by the Free Software Foundation.
duke@435 8 *
duke@435 9 * This code is distributed in the hope that it will be useful, but WITHOUT
duke@435 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
duke@435 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
duke@435 12 * version 2 for more details (a copy is included in the LICENSE file that
duke@435 13 * accompanied this code).
duke@435 14 *
duke@435 15 * You should have received a copy of the GNU General Public License version
duke@435 16 * 2 along with this work; if not, write to the Free Software Foundation,
duke@435 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
duke@435 18 *
trims@1907 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
trims@1907 20 * or visit www.oracle.com if you need additional information or have any
trims@1907 21 * questions.
duke@435 22 *
duke@435 23 */
duke@435 24
stefank@2314 25 #include "precompiled.hpp"
stefank@2314 26 #include "classfile/loaderConstraints.hpp"
stefank@2314 27 #include "memory/resourceArea.hpp"
stefank@2314 28 #include "oops/oop.inline.hpp"
stefank@2314 29 #include "runtime/handles.inline.hpp"
stefank@2314 30 #include "runtime/safepoint.hpp"
stefank@2314 31 #include "utilities/hashtable.inline.hpp"
duke@435 32
duke@435 33 LoaderConstraintTable::LoaderConstraintTable(int nof_buckets)
coleenp@2497 34 : Hashtable<klassOop>(nof_buckets, sizeof(LoaderConstraintEntry)) {};
duke@435 35
duke@435 36
duke@435 37 LoaderConstraintEntry* LoaderConstraintTable::new_entry(
coleenp@2497 38 unsigned int hash, Symbol* name,
duke@435 39 klassOop klass, int num_loaders,
duke@435 40 int max_loaders) {
duke@435 41 LoaderConstraintEntry* entry;
coleenp@2497 42 entry = (LoaderConstraintEntry*)Hashtable<klassOop>::new_entry(hash, klass);
duke@435 43 entry->set_name(name);
duke@435 44 entry->set_num_loaders(num_loaders);
duke@435 45 entry->set_max_loaders(max_loaders);
duke@435 46 return entry;
duke@435 47 }
duke@435 48
coleenp@2497 49 void LoaderConstraintTable::free_entry(LoaderConstraintEntry *entry) {
coleenp@2497 50 // decrement name refcount before freeing
coleenp@2497 51 entry->name()->decrement_refcount();
coleenp@2497 52 Hashtable<klassOop>::free_entry(entry);
coleenp@2497 53 }
coleenp@2497 54
duke@435 55
duke@435 56 void LoaderConstraintTable::oops_do(OopClosure* f) {
duke@435 57 for (int index = 0; index < table_size(); index++) {
duke@435 58 for (LoaderConstraintEntry* probe = bucket(index);
duke@435 59 probe != NULL;
duke@435 60 probe = probe->next()) {
duke@435 61 if (probe->klass() != NULL) {
duke@435 62 f->do_oop((oop*)probe->klass_addr());
duke@435 63 }
duke@435 64 for (int n = 0; n < probe->num_loaders(); n++) {
duke@435 65 if (probe->loader(n) != NULL) {
duke@435 66 f->do_oop(probe->loader_addr(n));
duke@435 67 }
duke@435 68 }
duke@435 69 }
duke@435 70 }
duke@435 71 }
duke@435 72
duke@435 73
duke@435 74 // The loaderConstraintTable must always be accessed with the
duke@435 75 // SystemDictionary lock held. This is true even for readers as
duke@435 76 // entries in the table could be being dynamically resized.
duke@435 77
duke@435 78 LoaderConstraintEntry** LoaderConstraintTable::find_loader_constraint(
coleenp@2497 79 Symbol* name, Handle loader) {
duke@435 80
duke@435 81 unsigned int hash = compute_hash(name);
duke@435 82 int index = hash_to_index(hash);
duke@435 83 LoaderConstraintEntry** pp = bucket_addr(index);
duke@435 84 while (*pp) {
duke@435 85 LoaderConstraintEntry* p = *pp;
duke@435 86 if (p->hash() == hash) {
coleenp@2497 87 if (p->name() == name) {
duke@435 88 for (int i = p->num_loaders() - 1; i >= 0; i--) {
duke@435 89 if (p->loader(i) == loader()) {
duke@435 90 return pp;
duke@435 91 }
duke@435 92 }
duke@435 93 }
duke@435 94 }
duke@435 95 pp = p->next_addr();
duke@435 96 }
duke@435 97 return pp;
duke@435 98 }
duke@435 99
duke@435 100
duke@435 101 void LoaderConstraintTable::purge_loader_constraints(BoolObjectClosure* is_alive) {
jcoomes@1844 102 assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
duke@435 103 // Remove unloaded entries from constraint table
duke@435 104 for (int index = 0; index < table_size(); index++) {
duke@435 105 LoaderConstraintEntry** p = bucket_addr(index);
duke@435 106 while(*p) {
duke@435 107 LoaderConstraintEntry* probe = *p;
duke@435 108 klassOop klass = probe->klass();
duke@435 109 // Remove klass that is no longer alive
duke@435 110 if (klass != NULL && !is_alive->do_object_b(klass)) {
duke@435 111 probe->set_klass(NULL);
duke@435 112 if (TraceLoaderConstraints) {
duke@435 113 ResourceMark rm;
duke@435 114 tty->print_cr("[Purging class object from constraint for name %s,"
duke@435 115 " loader list:",
duke@435 116 probe->name()->as_C_string());
duke@435 117 for (int i = 0; i < probe->num_loaders(); i++) {
duke@435 118 tty->print_cr("[ [%d]: %s", i,
duke@435 119 SystemDictionary::loader_name(probe->loader(i)));
duke@435 120 }
duke@435 121 }
duke@435 122 }
duke@435 123 // Remove entries no longer alive from loader array
duke@435 124 int n = 0;
duke@435 125 while (n < probe->num_loaders()) {
duke@435 126 if (probe->loader(n) != NULL) {
duke@435 127 if (!is_alive->do_object_b(probe->loader(n))) {
duke@435 128 if (TraceLoaderConstraints) {
duke@435 129 ResourceMark rm;
duke@435 130 tty->print_cr("[Purging loader %s from constraint for name %s",
duke@435 131 SystemDictionary::loader_name(probe->loader(n)),
duke@435 132 probe->name()->as_C_string()
duke@435 133 );
duke@435 134 }
duke@435 135
duke@435 136 // Compact array
duke@435 137 int num = probe->num_loaders() - 1;
duke@435 138 probe->set_num_loaders(num);
duke@435 139 probe->set_loader(n, probe->loader(num));
duke@435 140 probe->set_loader(num, NULL);
duke@435 141
duke@435 142 if (TraceLoaderConstraints) {
duke@435 143 ResourceMark rm;
duke@435 144 tty->print_cr("[New loader list:");
duke@435 145 for (int i = 0; i < probe->num_loaders(); i++) {
duke@435 146 tty->print_cr("[ [%d]: %s", i,
duke@435 147 SystemDictionary::loader_name(probe->loader(i)));
duke@435 148 }
duke@435 149 }
duke@435 150
duke@435 151 continue; // current element replaced, so restart without
duke@435 152 // incrementing n
duke@435 153 }
duke@435 154 }
duke@435 155 n++;
duke@435 156 }
duke@435 157 // Check whether entry should be purged
duke@435 158 if (probe->num_loaders() < 2) {
duke@435 159 if (TraceLoaderConstraints) {
duke@435 160 ResourceMark rm;
duke@435 161 tty->print("[Purging complete constraint for name %s\n",
duke@435 162 probe->name()->as_C_string());
duke@435 163 }
duke@435 164
duke@435 165 // Purge entry
duke@435 166 *p = probe->next();
duke@435 167 FREE_C_HEAP_ARRAY(oop, probe->loaders());
duke@435 168 free_entry(probe);
duke@435 169 } else {
duke@435 170 #ifdef ASSERT
duke@435 171 if (probe->klass() != NULL) {
duke@435 172 assert(is_alive->do_object_b(probe->klass()), "klass should be live");
duke@435 173 }
duke@435 174 for (n = 0; n < probe->num_loaders(); n++) {
duke@435 175 if (probe->loader(n) != NULL) {
duke@435 176 assert(is_alive->do_object_b(probe->loader(n)), "loader should be live");
duke@435 177 }
duke@435 178 }
duke@435 179 #endif
duke@435 180 // Go to next entry
duke@435 181 p = probe->next_addr();
duke@435 182 }
duke@435 183 }
duke@435 184 }
duke@435 185 }
duke@435 186
coleenp@2497 187 bool LoaderConstraintTable::add_entry(Symbol* class_name,
duke@435 188 klassOop klass1, Handle class_loader1,
duke@435 189 klassOop klass2, Handle class_loader2) {
duke@435 190 int failure_code = 0; // encode different reasons for failing
duke@435 191
duke@435 192 if (klass1 != NULL && klass2 != NULL && klass1 != klass2) {
duke@435 193 failure_code = 1;
duke@435 194 } else {
duke@435 195 klassOop klass = klass1 != NULL ? klass1 : klass2;
duke@435 196
duke@435 197 LoaderConstraintEntry** pp1 = find_loader_constraint(class_name,
duke@435 198 class_loader1);
duke@435 199 if (*pp1 != NULL && (*pp1)->klass() != NULL) {
duke@435 200 if (klass != NULL) {
duke@435 201 if (klass != (*pp1)->klass()) {
duke@435 202 failure_code = 2;
duke@435 203 }
duke@435 204 } else {
duke@435 205 klass = (*pp1)->klass();
duke@435 206 }
duke@435 207 }
duke@435 208
duke@435 209 LoaderConstraintEntry** pp2 = find_loader_constraint(class_name,
duke@435 210 class_loader2);
duke@435 211 if (*pp2 != NULL && (*pp2)->klass() != NULL) {
duke@435 212 if (klass != NULL) {
duke@435 213 if (klass != (*pp2)->klass()) {
duke@435 214 failure_code = 3;
duke@435 215 }
duke@435 216 } else {
duke@435 217 klass = (*pp2)->klass();
duke@435 218 }
duke@435 219 }
duke@435 220
duke@435 221 if (failure_code == 0) {
duke@435 222 if (*pp1 == NULL && *pp2 == NULL) {
duke@435 223 unsigned int hash = compute_hash(class_name);
duke@435 224 int index = hash_to_index(hash);
duke@435 225 LoaderConstraintEntry* p;
coleenp@2497 226 p = new_entry(hash, class_name, klass, 2, 2);
duke@435 227 p->set_loaders(NEW_C_HEAP_ARRAY(oop, 2));
duke@435 228 p->set_loader(0, class_loader1());
duke@435 229 p->set_loader(1, class_loader2());
duke@435 230 p->set_klass(klass);
duke@435 231 p->set_next(bucket(index));
duke@435 232 set_entry(index, p);
duke@435 233 if (TraceLoaderConstraints) {
duke@435 234 ResourceMark rm;
duke@435 235 tty->print("[Adding new constraint for name: %s, loader[0]: %s,"
duke@435 236 " loader[1]: %s ]\n",
coleenp@2497 237 class_name->as_C_string(),
duke@435 238 SystemDictionary::loader_name(class_loader1()),
duke@435 239 SystemDictionary::loader_name(class_loader2())
duke@435 240 );
duke@435 241 }
duke@435 242 } else if (*pp1 == *pp2) {
duke@435 243 /* constraint already imposed */
duke@435 244 if ((*pp1)->klass() == NULL) {
duke@435 245 (*pp1)->set_klass(klass);
duke@435 246 if (TraceLoaderConstraints) {
duke@435 247 ResourceMark rm;
duke@435 248 tty->print("[Setting class object in existing constraint for"
duke@435 249 " name: %s and loader %s ]\n",
coleenp@2497 250 class_name->as_C_string(),
duke@435 251 SystemDictionary::loader_name(class_loader1())
duke@435 252 );
duke@435 253 }
duke@435 254 } else {
duke@435 255 assert((*pp1)->klass() == klass, "loader constraints corrupted");
duke@435 256 }
duke@435 257 } else if (*pp1 == NULL) {
duke@435 258 extend_loader_constraint(*pp2, class_loader1, klass);
duke@435 259 } else if (*pp2 == NULL) {
duke@435 260 extend_loader_constraint(*pp1, class_loader2, klass);
duke@435 261 } else {
duke@435 262 merge_loader_constraints(pp1, pp2, klass);
duke@435 263 }
duke@435 264 }
duke@435 265 }
duke@435 266
duke@435 267 if (failure_code != 0 && TraceLoaderConstraints) {
duke@435 268 ResourceMark rm;
duke@435 269 const char* reason = "";
duke@435 270 switch(failure_code) {
duke@435 271 case 1: reason = "the class objects presented by loader[0] and loader[1]"
duke@435 272 " are different"; break;
duke@435 273 case 2: reason = "the class object presented by loader[0] does not match"
duke@435 274 " the stored class object in the constraint"; break;
duke@435 275 case 3: reason = "the class object presented by loader[1] does not match"
duke@435 276 " the stored class object in the constraint"; break;
duke@435 277 default: reason = "unknown reason code";
duke@435 278 }
duke@435 279 tty->print("[Failed to add constraint for name: %s, loader[0]: %s,"
duke@435 280 " loader[1]: %s, Reason: %s ]\n",
coleenp@2497 281 class_name->as_C_string(),
duke@435 282 SystemDictionary::loader_name(class_loader1()),
duke@435 283 SystemDictionary::loader_name(class_loader2()),
duke@435 284 reason
duke@435 285 );
duke@435 286 }
duke@435 287
duke@435 288 return failure_code == 0;
duke@435 289 }
duke@435 290
duke@435 291
duke@435 292 // return true if the constraint was updated, false if the constraint is
duke@435 293 // violated
duke@435 294 bool LoaderConstraintTable::check_or_update(instanceKlassHandle k,
duke@435 295 Handle loader,
coleenp@2497 296 Symbol* name) {
duke@435 297 LoaderConstraintEntry* p = *(find_loader_constraint(name, loader));
duke@435 298 if (p && p->klass() != NULL && p->klass() != k()) {
duke@435 299 if (TraceLoaderConstraints) {
duke@435 300 ResourceMark rm;
duke@435 301 tty->print("[Constraint check failed for name %s, loader %s: "
duke@435 302 "the presented class object differs from that stored ]\n",
coleenp@2497 303 name->as_C_string(),
duke@435 304 SystemDictionary::loader_name(loader()));
duke@435 305 }
duke@435 306 return false;
duke@435 307 } else {
duke@435 308 if (p && p->klass() == NULL) {
duke@435 309 p->set_klass(k());
duke@435 310 if (TraceLoaderConstraints) {
duke@435 311 ResourceMark rm;
duke@435 312 tty->print("[Updating constraint for name %s, loader %s, "
duke@435 313 "by setting class object ]\n",
coleenp@2497 314 name->as_C_string(),
duke@435 315 SystemDictionary::loader_name(loader()));
duke@435 316 }
duke@435 317 }
duke@435 318 return true;
duke@435 319 }
duke@435 320 }
duke@435 321
coleenp@2497 322 klassOop LoaderConstraintTable::find_constrained_klass(Symbol* name,
duke@435 323 Handle loader) {
duke@435 324 LoaderConstraintEntry *p = *(find_loader_constraint(name, loader));
duke@435 325 if (p != NULL && p->klass() != NULL)
duke@435 326 return p->klass();
duke@435 327
duke@435 328 // No constraints, or else no klass loaded yet.
duke@435 329 return NULL;
duke@435 330 }
duke@435 331
duke@435 332 void LoaderConstraintTable::ensure_loader_constraint_capacity(
duke@435 333 LoaderConstraintEntry *p,
duke@435 334 int nfree) {
duke@435 335 if (p->max_loaders() - p->num_loaders() < nfree) {
duke@435 336 int n = nfree + p->num_loaders();
duke@435 337 oop* new_loaders = NEW_C_HEAP_ARRAY(oop, n);
duke@435 338 memcpy(new_loaders, p->loaders(), sizeof(oop) * p->num_loaders());
duke@435 339 p->set_max_loaders(n);
duke@435 340 FREE_C_HEAP_ARRAY(oop, p->loaders());
duke@435 341 p->set_loaders(new_loaders);
duke@435 342 }
duke@435 343 }
duke@435 344
duke@435 345
duke@435 346 void LoaderConstraintTable::extend_loader_constraint(LoaderConstraintEntry* p,
duke@435 347 Handle loader,
duke@435 348 klassOop klass) {
duke@435 349 ensure_loader_constraint_capacity(p, 1);
duke@435 350 int num = p->num_loaders();
duke@435 351 p->set_loader(num, loader());
duke@435 352 p->set_num_loaders(num + 1);
duke@435 353 if (TraceLoaderConstraints) {
duke@435 354 ResourceMark rm;
duke@435 355 tty->print("[Extending constraint for name %s by adding loader[%d]: %s %s",
duke@435 356 p->name()->as_C_string(),
duke@435 357 num,
duke@435 358 SystemDictionary::loader_name(loader()),
duke@435 359 (p->klass() == NULL ? " and setting class object ]\n" : " ]\n")
duke@435 360 );
duke@435 361 }
duke@435 362 if (p->klass() == NULL) {
duke@435 363 p->set_klass(klass);
duke@435 364 } else {
duke@435 365 assert(klass == NULL || p->klass() == klass, "constraints corrupted");
duke@435 366 }
duke@435 367 }
duke@435 368
duke@435 369
duke@435 370 void LoaderConstraintTable::merge_loader_constraints(
duke@435 371 LoaderConstraintEntry** pp1,
duke@435 372 LoaderConstraintEntry** pp2,
duke@435 373 klassOop klass) {
duke@435 374 // make sure *pp1 has higher capacity
duke@435 375 if ((*pp1)->max_loaders() < (*pp2)->max_loaders()) {
duke@435 376 LoaderConstraintEntry** tmp = pp2;
duke@435 377 pp2 = pp1;
duke@435 378 pp1 = tmp;
duke@435 379 }
duke@435 380
duke@435 381 LoaderConstraintEntry* p1 = *pp1;
duke@435 382 LoaderConstraintEntry* p2 = *pp2;
duke@435 383
duke@435 384 ensure_loader_constraint_capacity(p1, p2->num_loaders());
duke@435 385
duke@435 386 for (int i = 0; i < p2->num_loaders(); i++) {
duke@435 387 int num = p1->num_loaders();
duke@435 388 p1->set_loader(num, p2->loader(i));
duke@435 389 p1->set_num_loaders(num + 1);
duke@435 390 }
duke@435 391
duke@435 392 if (TraceLoaderConstraints) {
duke@435 393 ResourceMark rm;
duke@435 394 tty->print_cr("[Merged constraints for name %s, new loader list:",
duke@435 395 p1->name()->as_C_string()
duke@435 396 );
duke@435 397
duke@435 398 for (int i = 0; i < p1->num_loaders(); i++) {
duke@435 399 tty->print_cr("[ [%d]: %s", i,
duke@435 400 SystemDictionary::loader_name(p1->loader(i)));
duke@435 401 }
duke@435 402 if (p1->klass() == NULL) {
duke@435 403 tty->print_cr("[... and setting class object]");
duke@435 404 }
duke@435 405 }
duke@435 406
duke@435 407 // p1->klass() will hold NULL if klass, p2->klass(), and old
duke@435 408 // p1->klass() are all NULL. In addition, all three must have
duke@435 409 // matching non-NULL values, otherwise either the constraints would
duke@435 410 // have been violated, or the constraints had been corrupted (and an
duke@435 411 // assertion would fail).
duke@435 412 if (p2->klass() != NULL) {
duke@435 413 assert(p2->klass() == klass, "constraints corrupted");
duke@435 414 }
duke@435 415 if (p1->klass() == NULL) {
duke@435 416 p1->set_klass(klass);
duke@435 417 } else {
duke@435 418 assert(p1->klass() == klass, "constraints corrupted");
duke@435 419 }
duke@435 420
duke@435 421 *pp2 = p2->next();
duke@435 422 FREE_C_HEAP_ARRAY(oop, p2->loaders());
duke@435 423 free_entry(p2);
duke@435 424 return;
duke@435 425 }
duke@435 426
duke@435 427
tonyp@1693 428 void LoaderConstraintTable::verify(Dictionary* dictionary,
tonyp@1693 429 PlaceholderTable* placeholders) {
duke@435 430 Thread *thread = Thread::current();
duke@435 431 for (int cindex = 0; cindex < _loader_constraint_size; cindex++) {
duke@435 432 for (LoaderConstraintEntry* probe = bucket(cindex);
duke@435 433 probe != NULL;
duke@435 434 probe = probe->next()) {
duke@435 435 if (probe->klass() != NULL) {
duke@435 436 instanceKlass* ik = instanceKlass::cast(probe->klass());
duke@435 437 guarantee(ik->name() == probe->name(), "name should match");
coleenp@2497 438 Symbol* name = ik->name();
duke@435 439 Handle loader(thread, ik->class_loader());
duke@435 440 unsigned int d_hash = dictionary->compute_hash(name, loader);
duke@435 441 int d_index = dictionary->hash_to_index(d_hash);
duke@435 442 klassOop k = dictionary->find_class(d_index, d_hash, name, loader);
tonyp@1693 443 if (k != NULL) {
tonyp@1693 444 // We found the class in the system dictionary, so we should
tonyp@1693 445 // make sure that the klassOop matches what we already have.
tonyp@1693 446 guarantee(k == probe->klass(), "klass should be in dictionary");
tonyp@1693 447 } else {
tonyp@1693 448 // If we don't find the class in the system dictionary, it
tonyp@1693 449 // has to be in the placeholders table.
tonyp@1693 450 unsigned int p_hash = placeholders->compute_hash(name, loader);
tonyp@1693 451 int p_index = placeholders->hash_to_index(p_hash);
tonyp@1693 452 PlaceholderEntry* entry = placeholders->get_entry(p_index, p_hash,
tonyp@1693 453 name, loader);
tonyp@1693 454
tonyp@1693 455 // The instanceKlass might not be on the entry, so the only
tonyp@1693 456 // thing we can check here is whether we were successful in
tonyp@1693 457 // finding the class in the placeholders table.
tonyp@1693 458 guarantee(entry != NULL, "klass should be in the placeholders");
tonyp@1693 459 }
duke@435 460 }
duke@435 461 for (int n = 0; n< probe->num_loaders(); n++) {
duke@435 462 guarantee(probe->loader(n)->is_oop_or_null(), "should be oop");
duke@435 463 }
duke@435 464 }
duke@435 465 }
duke@435 466 }
duke@435 467
duke@435 468 #ifndef PRODUCT
duke@435 469
duke@435 470 // Called with the system dictionary lock held
duke@435 471 void LoaderConstraintTable::print() {
duke@435 472 ResourceMark rm;
duke@435 473
duke@435 474 assert_locked_or_safepoint(SystemDictionary_lock);
duke@435 475 tty->print_cr("Java loader constraints (entries=%d)", _loader_constraint_size);
duke@435 476 for (int cindex = 0; cindex < _loader_constraint_size; cindex++) {
duke@435 477 for (LoaderConstraintEntry* probe = bucket(cindex);
duke@435 478 probe != NULL;
duke@435 479 probe = probe->next()) {
duke@435 480 tty->print("%4d: ", cindex);
duke@435 481 probe->name()->print();
duke@435 482 tty->print(" , loaders:");
duke@435 483 for (int n = 0; n < probe->num_loaders(); n++) {
duke@435 484 probe->loader(n)->print_value();
duke@435 485 tty->print(", ");
duke@435 486 }
duke@435 487 tty->cr();
duke@435 488 }
duke@435 489 }
duke@435 490 }
duke@435 491 #endif

mercurial