src/share/vm/classfile/loaderConstraints.cpp

changeset 435
a61af66fc99e
child 1693
38836cf1d8d2
child 1771
0c3f888b7636
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/share/vm/classfile/loaderConstraints.cpp	Sat Dec 01 00:00:00 2007 +0000
     1.3 @@ -0,0 +1,507 @@
     1.4 +/*
     1.5 + * Copyright 2003-2006 Sun Microsystems, Inc.  All Rights Reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.
    1.11 + *
    1.12 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.14 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.15 + * version 2 for more details (a copy is included in the LICENSE file that
    1.16 + * accompanied this code).
    1.17 + *
    1.18 + * You should have received a copy of the GNU General Public License version
    1.19 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.21 + *
    1.22 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    1.23 + * CA 95054 USA or visit www.sun.com if you need additional information or
    1.24 + * have any questions.
    1.25 + *
    1.26 + */
    1.27 +
    1.28 +# include "incls/_precompiled.incl"
    1.29 +# include "incls/_loaderConstraints.cpp.incl"
    1.30 +
    1.31 +LoaderConstraintTable::LoaderConstraintTable(int nof_buckets)
    1.32 +  : Hashtable(nof_buckets, sizeof(LoaderConstraintEntry)) {};
    1.33 +
    1.34 +
    1.35 +LoaderConstraintEntry* LoaderConstraintTable::new_entry(
    1.36 +                                 unsigned int hash, symbolOop name,
    1.37 +                                 klassOop klass, int num_loaders,
    1.38 +                                 int max_loaders) {
    1.39 +  LoaderConstraintEntry* entry;
    1.40 +  entry = (LoaderConstraintEntry*)Hashtable::new_entry(hash, klass);
    1.41 +  entry->set_name(name);
    1.42 +  entry->set_num_loaders(num_loaders);
    1.43 +  entry->set_max_loaders(max_loaders);
    1.44 +  return entry;
    1.45 +}
    1.46 +
    1.47 +
    1.48 +void LoaderConstraintTable::oops_do(OopClosure* f) {
    1.49 +  for (int index = 0; index < table_size(); index++) {
    1.50 +    for (LoaderConstraintEntry* probe = bucket(index);
    1.51 +                                probe != NULL;
    1.52 +                                probe = probe->next()) {
    1.53 +      f->do_oop((oop*)(probe->name_addr()));
    1.54 +      if (probe->klass() != NULL) {
    1.55 +        f->do_oop((oop*)probe->klass_addr());
    1.56 +      }
    1.57 +      for (int n = 0; n < probe->num_loaders(); n++) {
    1.58 +        if (probe->loader(n) != NULL) {
    1.59 +          f->do_oop(probe->loader_addr(n));
    1.60 +        }
    1.61 +      }
    1.62 +    }
    1.63 +  }
    1.64 +}
    1.65 +
    1.66 +// We must keep the symbolOop used in the name alive.  We'll use the
    1.67 +// loaders to decide if a particular entry can be purged.
    1.68 +void LoaderConstraintTable::always_strong_classes_do(OopClosure* blk) {
    1.69 +  // We must keep the symbolOop used in the name alive.
    1.70 +  for (int cindex = 0; cindex < table_size(); cindex++) {
    1.71 +    for (LoaderConstraintEntry* lc_probe = bucket(cindex);
    1.72 +                                lc_probe != NULL;
    1.73 +                                lc_probe = lc_probe->next()) {
    1.74 +      assert (lc_probe->name() != NULL,  "corrupted loader constraint table");
    1.75 +      blk->do_oop((oop*)lc_probe->name_addr());
    1.76 +    }
    1.77 +  }
    1.78 +}
    1.79 +
    1.80 +
    1.81 +// The loaderConstraintTable must always be accessed with the
    1.82 +// SystemDictionary lock held. This is true even for readers as
    1.83 +// entries in the table could be being dynamically resized.
    1.84 +
    1.85 +LoaderConstraintEntry** LoaderConstraintTable::find_loader_constraint(
    1.86 +                                    symbolHandle name, Handle loader) {
    1.87 +
    1.88 +  unsigned int hash = compute_hash(name);
    1.89 +  int index = hash_to_index(hash);
    1.90 +  LoaderConstraintEntry** pp = bucket_addr(index);
    1.91 +  while (*pp) {
    1.92 +    LoaderConstraintEntry* p = *pp;
    1.93 +    if (p->hash() == hash) {
    1.94 +      if (p->name() == name()) {
    1.95 +        for (int i = p->num_loaders() - 1; i >= 0; i--) {
    1.96 +          if (p->loader(i) == loader()) {
    1.97 +            return pp;
    1.98 +          }
    1.99 +        }
   1.100 +      }
   1.101 +    }
   1.102 +    pp = p->next_addr();
   1.103 +  }
   1.104 +  return pp;
   1.105 +}
   1.106 +
   1.107 +
   1.108 +void LoaderConstraintTable::purge_loader_constraints(BoolObjectClosure* is_alive) {
   1.109 +  assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint")
   1.110 +  // Remove unloaded entries from constraint table
   1.111 +  for (int index = 0; index < table_size(); index++) {
   1.112 +    LoaderConstraintEntry** p = bucket_addr(index);
   1.113 +    while(*p) {
   1.114 +      LoaderConstraintEntry* probe = *p;
   1.115 +      klassOop klass = probe->klass();
   1.116 +      // Remove klass that is no longer alive
   1.117 +      if (klass != NULL && !is_alive->do_object_b(klass)) {
   1.118 +        probe->set_klass(NULL);
   1.119 +        if (TraceLoaderConstraints) {
   1.120 +          ResourceMark rm;
   1.121 +          tty->print_cr("[Purging class object from constraint for name %s,"
   1.122 +                     " loader list:",
   1.123 +                     probe->name()->as_C_string());
   1.124 +          for (int i = 0; i < probe->num_loaders(); i++) {
   1.125 +            tty->print_cr("[   [%d]: %s", i,
   1.126 +                          SystemDictionary::loader_name(probe->loader(i)));
   1.127 +          }
   1.128 +        }
   1.129 +      }
   1.130 +      // Remove entries no longer alive from loader array
   1.131 +      int n = 0;
   1.132 +      while (n < probe->num_loaders()) {
   1.133 +        if (probe->loader(n) != NULL) {
   1.134 +          if (!is_alive->do_object_b(probe->loader(n))) {
   1.135 +            if (TraceLoaderConstraints) {
   1.136 +              ResourceMark rm;
   1.137 +              tty->print_cr("[Purging loader %s from constraint for name %s",
   1.138 +                            SystemDictionary::loader_name(probe->loader(n)),
   1.139 +                            probe->name()->as_C_string()
   1.140 +                            );
   1.141 +            }
   1.142 +
   1.143 +            // Compact array
   1.144 +            int num = probe->num_loaders() - 1;
   1.145 +            probe->set_num_loaders(num);
   1.146 +            probe->set_loader(n, probe->loader(num));
   1.147 +            probe->set_loader(num, NULL);
   1.148 +
   1.149 +            if (TraceLoaderConstraints) {
   1.150 +              ResourceMark rm;
   1.151 +              tty->print_cr("[New loader list:");
   1.152 +              for (int i = 0; i < probe->num_loaders(); i++) {
   1.153 +                tty->print_cr("[   [%d]: %s", i,
   1.154 +                              SystemDictionary::loader_name(probe->loader(i)));
   1.155 +              }
   1.156 +            }
   1.157 +
   1.158 +            continue;  // current element replaced, so restart without
   1.159 +                       // incrementing n
   1.160 +          }
   1.161 +        }
   1.162 +        n++;
   1.163 +      }
   1.164 +      // Check whether entry should be purged
   1.165 +      if (probe->num_loaders() < 2) {
   1.166 +            if (TraceLoaderConstraints) {
   1.167 +              ResourceMark rm;
   1.168 +              tty->print("[Purging complete constraint for name %s\n",
   1.169 +                         probe->name()->as_C_string());
   1.170 +            }
   1.171 +
   1.172 +        // Purge entry
   1.173 +        *p = probe->next();
   1.174 +        FREE_C_HEAP_ARRAY(oop, probe->loaders());
   1.175 +        free_entry(probe);
   1.176 +      } else {
   1.177 +#ifdef ASSERT
   1.178 +        assert(is_alive->do_object_b(probe->name()), "name should be live");
   1.179 +        if (probe->klass() != NULL) {
   1.180 +          assert(is_alive->do_object_b(probe->klass()), "klass should be live");
   1.181 +        }
   1.182 +        for (n = 0; n < probe->num_loaders(); n++) {
   1.183 +          if (probe->loader(n) != NULL) {
   1.184 +            assert(is_alive->do_object_b(probe->loader(n)), "loader should be live");
   1.185 +          }
   1.186 +        }
   1.187 +#endif
   1.188 +        // Go to next entry
   1.189 +        p = probe->next_addr();
   1.190 +      }
   1.191 +    }
   1.192 +  }
   1.193 +}
   1.194 +
   1.195 +bool LoaderConstraintTable::add_entry(symbolHandle class_name,
   1.196 +                                      klassOop klass1, Handle class_loader1,
   1.197 +                                      klassOop klass2, Handle class_loader2) {
   1.198 +  int failure_code = 0; // encode different reasons for failing
   1.199 +
   1.200 +  if (klass1 != NULL && klass2 != NULL && klass1 != klass2) {
   1.201 +    failure_code = 1;
   1.202 +  } else {
   1.203 +    klassOop klass = klass1 != NULL ? klass1 : klass2;
   1.204 +
   1.205 +    LoaderConstraintEntry** pp1 = find_loader_constraint(class_name,
   1.206 +                                                         class_loader1);
   1.207 +    if (*pp1 != NULL && (*pp1)->klass() != NULL) {
   1.208 +      if (klass != NULL) {
   1.209 +        if (klass != (*pp1)->klass()) {
   1.210 +          failure_code = 2;
   1.211 +        }
   1.212 +      } else {
   1.213 +        klass = (*pp1)->klass();
   1.214 +      }
   1.215 +    }
   1.216 +
   1.217 +    LoaderConstraintEntry** pp2 = find_loader_constraint(class_name,
   1.218 +                                                         class_loader2);
   1.219 +    if (*pp2 != NULL && (*pp2)->klass() != NULL) {
   1.220 +      if (klass != NULL) {
   1.221 +        if (klass != (*pp2)->klass()) {
   1.222 +          failure_code = 3;
   1.223 +        }
   1.224 +      } else {
   1.225 +        klass = (*pp2)->klass();
   1.226 +      }
   1.227 +    }
   1.228 +
   1.229 +    if (failure_code == 0) {
   1.230 +      if (*pp1 == NULL && *pp2 == NULL) {
   1.231 +        unsigned int hash = compute_hash(class_name);
   1.232 +        int index = hash_to_index(hash);
   1.233 +        LoaderConstraintEntry* p;
   1.234 +        p = new_entry(hash, class_name(), klass, 2, 2);
   1.235 +        p->set_loaders(NEW_C_HEAP_ARRAY(oop, 2));
   1.236 +        p->set_loader(0, class_loader1());
   1.237 +        p->set_loader(1, class_loader2());
   1.238 +        p->set_klass(klass);
   1.239 +        p->set_next(bucket(index));
   1.240 +        set_entry(index, p);
   1.241 +        if (TraceLoaderConstraints) {
   1.242 +          ResourceMark rm;
   1.243 +          tty->print("[Adding new constraint for name: %s, loader[0]: %s,"
   1.244 +                     " loader[1]: %s ]\n",
   1.245 +                     class_name()->as_C_string(),
   1.246 +                     SystemDictionary::loader_name(class_loader1()),
   1.247 +                     SystemDictionary::loader_name(class_loader2())
   1.248 +                     );
   1.249 +        }
   1.250 +      } else if (*pp1 == *pp2) {
   1.251 +        /* constraint already imposed */
   1.252 +        if ((*pp1)->klass() == NULL) {
   1.253 +          (*pp1)->set_klass(klass);
   1.254 +          if (TraceLoaderConstraints) {
   1.255 +            ResourceMark rm;
   1.256 +            tty->print("[Setting class object in existing constraint for"
   1.257 +                       " name: %s and loader %s ]\n",
   1.258 +                       class_name()->as_C_string(),
   1.259 +                       SystemDictionary::loader_name(class_loader1())
   1.260 +                       );
   1.261 +          }
   1.262 +        } else {
   1.263 +          assert((*pp1)->klass() == klass, "loader constraints corrupted");
   1.264 +        }
   1.265 +      } else if (*pp1 == NULL) {
   1.266 +        extend_loader_constraint(*pp2, class_loader1, klass);
   1.267 +      } else if (*pp2 == NULL) {
   1.268 +        extend_loader_constraint(*pp1, class_loader2, klass);
   1.269 +      } else {
   1.270 +        merge_loader_constraints(pp1, pp2, klass);
   1.271 +      }
   1.272 +    }
   1.273 +  }
   1.274 +
   1.275 +  if (failure_code != 0 && TraceLoaderConstraints) {
   1.276 +    ResourceMark rm;
   1.277 +    const char* reason = "";
   1.278 +    switch(failure_code) {
   1.279 +    case 1: reason = "the class objects presented by loader[0] and loader[1]"
   1.280 +              " are different"; break;
   1.281 +    case 2: reason = "the class object presented by loader[0] does not match"
   1.282 +              " the stored class object in the constraint"; break;
   1.283 +    case 3: reason = "the class object presented by loader[1] does not match"
   1.284 +              " the stored class object in the constraint"; break;
   1.285 +    default: reason = "unknown reason code";
   1.286 +    }
   1.287 +    tty->print("[Failed to add constraint for name: %s, loader[0]: %s,"
   1.288 +               " loader[1]: %s, Reason: %s ]\n",
   1.289 +               class_name()->as_C_string(),
   1.290 +               SystemDictionary::loader_name(class_loader1()),
   1.291 +               SystemDictionary::loader_name(class_loader2()),
   1.292 +               reason
   1.293 +               );
   1.294 +  }
   1.295 +
   1.296 +  return failure_code == 0;
   1.297 +}
   1.298 +
   1.299 +
   1.300 +// return true if the constraint was updated, false if the constraint is
   1.301 +// violated
   1.302 +bool LoaderConstraintTable::check_or_update(instanceKlassHandle k,
   1.303 +                                                   Handle loader,
   1.304 +                                                   symbolHandle name) {
   1.305 +  LoaderConstraintEntry* p = *(find_loader_constraint(name, loader));
   1.306 +  if (p && p->klass() != NULL && p->klass() != k()) {
   1.307 +    if (TraceLoaderConstraints) {
   1.308 +      ResourceMark rm;
   1.309 +      tty->print("[Constraint check failed for name %s, loader %s: "
   1.310 +                 "the presented class object differs from that stored ]\n",
   1.311 +                 name()->as_C_string(),
   1.312 +                 SystemDictionary::loader_name(loader()));
   1.313 +    }
   1.314 +    return false;
   1.315 +  } else {
   1.316 +    if (p && p->klass() == NULL) {
   1.317 +      p->set_klass(k());
   1.318 +      if (TraceLoaderConstraints) {
   1.319 +        ResourceMark rm;
   1.320 +        tty->print("[Updating constraint for name %s, loader %s, "
   1.321 +                   "by setting class object ]\n",
   1.322 +                   name()->as_C_string(),
   1.323 +                   SystemDictionary::loader_name(loader()));
   1.324 +      }
   1.325 +    }
   1.326 +    return true;
   1.327 +  }
   1.328 +}
   1.329 +
   1.330 +klassOop LoaderConstraintTable::find_constrained_klass(symbolHandle name,
   1.331 +                                                       Handle loader) {
   1.332 +  LoaderConstraintEntry *p = *(find_loader_constraint(name, loader));
   1.333 +  if (p != NULL && p->klass() != NULL)
   1.334 +    return p->klass();
   1.335 +
   1.336 +  // No constraints, or else no klass loaded yet.
   1.337 +  return NULL;
   1.338 +}
   1.339 +
   1.340 +
   1.341 +klassOop LoaderConstraintTable::find_constrained_elem_klass(symbolHandle name,
   1.342 +                                                            symbolHandle elem_name,
   1.343 +                                                            Handle loader,
   1.344 +                                                            TRAPS) {
   1.345 +  LoaderConstraintEntry *p = *(find_loader_constraint(name, loader));
   1.346 +  if (p != NULL) {
   1.347 +    assert(p->klass() == NULL, "Expecting null array klass");
   1.348 +
   1.349 +    // The array name has a constraint, but it will not have a class. Check
   1.350 +    // each loader for an associated elem
   1.351 +    for (int i = 0; i < p->num_loaders(); i++) {
   1.352 +      Handle no_protection_domain;
   1.353 +
   1.354 +      klassOop k = SystemDictionary::find(elem_name, p->loader(i), no_protection_domain, THREAD);
   1.355 +      if (k != NULL) {
   1.356 +        // Return the first elem klass found.
   1.357 +        return k;
   1.358 +      }
   1.359 +    }
   1.360 +  }
   1.361 +
   1.362 +  // No constraints, or else no klass loaded yet.
   1.363 +  return NULL;
   1.364 +}
   1.365 +
   1.366 +
   1.367 +void LoaderConstraintTable::ensure_loader_constraint_capacity(
   1.368 +                                                     LoaderConstraintEntry *p,
   1.369 +                                                    int nfree) {
   1.370 +    if (p->max_loaders() - p->num_loaders() < nfree) {
   1.371 +        int n = nfree + p->num_loaders();
   1.372 +        oop* new_loaders = NEW_C_HEAP_ARRAY(oop, n);
   1.373 +        memcpy(new_loaders, p->loaders(), sizeof(oop) * p->num_loaders());
   1.374 +        p->set_max_loaders(n);
   1.375 +        FREE_C_HEAP_ARRAY(oop, p->loaders());
   1.376 +        p->set_loaders(new_loaders);
   1.377 +    }
   1.378 +}
   1.379 +
   1.380 +
   1.381 +void LoaderConstraintTable::extend_loader_constraint(LoaderConstraintEntry* p,
   1.382 +                                                     Handle loader,
   1.383 +                                                     klassOop klass) {
   1.384 +  ensure_loader_constraint_capacity(p, 1);
   1.385 +  int num = p->num_loaders();
   1.386 +  p->set_loader(num, loader());
   1.387 +  p->set_num_loaders(num + 1);
   1.388 +  if (TraceLoaderConstraints) {
   1.389 +    ResourceMark rm;
   1.390 +    tty->print("[Extending constraint for name %s by adding loader[%d]: %s %s",
   1.391 +               p->name()->as_C_string(),
   1.392 +               num,
   1.393 +               SystemDictionary::loader_name(loader()),
   1.394 +               (p->klass() == NULL ? " and setting class object ]\n" : " ]\n")
   1.395 +               );
   1.396 +  }
   1.397 +  if (p->klass() == NULL) {
   1.398 +    p->set_klass(klass);
   1.399 +  } else {
   1.400 +    assert(klass == NULL || p->klass() == klass, "constraints corrupted");
   1.401 +  }
   1.402 +}
   1.403 +
   1.404 +
   1.405 +void LoaderConstraintTable::merge_loader_constraints(
   1.406 +                                                   LoaderConstraintEntry** pp1,
   1.407 +                                                   LoaderConstraintEntry** pp2,
   1.408 +                                                   klassOop klass) {
   1.409 +  // make sure *pp1 has higher capacity
   1.410 +  if ((*pp1)->max_loaders() < (*pp2)->max_loaders()) {
   1.411 +    LoaderConstraintEntry** tmp = pp2;
   1.412 +    pp2 = pp1;
   1.413 +    pp1 = tmp;
   1.414 +  }
   1.415 +
   1.416 +  LoaderConstraintEntry* p1 = *pp1;
   1.417 +  LoaderConstraintEntry* p2 = *pp2;
   1.418 +
   1.419 +  ensure_loader_constraint_capacity(p1, p2->num_loaders());
   1.420 +
   1.421 +  for (int i = 0; i < p2->num_loaders(); i++) {
   1.422 +    int num = p1->num_loaders();
   1.423 +    p1->set_loader(num, p2->loader(i));
   1.424 +    p1->set_num_loaders(num + 1);
   1.425 +  }
   1.426 +
   1.427 +  if (TraceLoaderConstraints) {
   1.428 +    ResourceMark rm;
   1.429 +    tty->print_cr("[Merged constraints for name %s, new loader list:",
   1.430 +                  p1->name()->as_C_string()
   1.431 +                  );
   1.432 +
   1.433 +    for (int i = 0; i < p1->num_loaders(); i++) {
   1.434 +      tty->print_cr("[   [%d]: %s", i,
   1.435 +                    SystemDictionary::loader_name(p1->loader(i)));
   1.436 +    }
   1.437 +    if (p1->klass() == NULL) {
   1.438 +      tty->print_cr("[... and setting class object]");
   1.439 +    }
   1.440 +  }
   1.441 +
   1.442 +  // p1->klass() will hold NULL if klass, p2->klass(), and old
   1.443 +  // p1->klass() are all NULL.  In addition, all three must have
   1.444 +  // matching non-NULL values, otherwise either the constraints would
   1.445 +  // have been violated, or the constraints had been corrupted (and an
   1.446 +  // assertion would fail).
   1.447 +  if (p2->klass() != NULL) {
   1.448 +    assert(p2->klass() == klass, "constraints corrupted");
   1.449 +  }
   1.450 +  if (p1->klass() == NULL) {
   1.451 +    p1->set_klass(klass);
   1.452 +  } else {
   1.453 +    assert(p1->klass() == klass, "constraints corrupted");
   1.454 +  }
   1.455 +
   1.456 +  *pp2 = p2->next();
   1.457 +  FREE_C_HEAP_ARRAY(oop, p2->loaders());
   1.458 +  free_entry(p2);
   1.459 +  return;
   1.460 +}
   1.461 +
   1.462 +
   1.463 +void LoaderConstraintTable::verify(Dictionary* dictionary) {
   1.464 +  Thread *thread = Thread::current();
   1.465 +  for (int cindex = 0; cindex < _loader_constraint_size; cindex++) {
   1.466 +    for (LoaderConstraintEntry* probe = bucket(cindex);
   1.467 +                                probe != NULL;
   1.468 +                                probe = probe->next()) {
   1.469 +      guarantee(probe->name()->is_symbol(), "should be symbol");
   1.470 +      if (probe->klass() != NULL) {
   1.471 +        instanceKlass* ik = instanceKlass::cast(probe->klass());
   1.472 +        guarantee(ik->name() == probe->name(), "name should match");
   1.473 +        symbolHandle name (thread, ik->name());
   1.474 +        Handle loader(thread, ik->class_loader());
   1.475 +        unsigned int d_hash = dictionary->compute_hash(name, loader);
   1.476 +        int d_index = dictionary->hash_to_index(d_hash);
   1.477 +        klassOop k = dictionary->find_class(d_index, d_hash, name, loader);
   1.478 +        guarantee(k == probe->klass(), "klass should be in dictionary");
   1.479 +      }
   1.480 +      for (int n = 0; n< probe->num_loaders(); n++) {
   1.481 +        guarantee(probe->loader(n)->is_oop_or_null(), "should be oop");
   1.482 +      }
   1.483 +    }
   1.484 +  }
   1.485 +}
   1.486 +
   1.487 +#ifndef PRODUCT
   1.488 +
   1.489 +// Called with the system dictionary lock held
   1.490 +void LoaderConstraintTable::print() {
   1.491 +  ResourceMark rm;
   1.492 +
   1.493 +  assert_locked_or_safepoint(SystemDictionary_lock);
   1.494 +  tty->print_cr("Java loader constraints (entries=%d)", _loader_constraint_size);
   1.495 +  for (int cindex = 0; cindex < _loader_constraint_size; cindex++) {
   1.496 +    for (LoaderConstraintEntry* probe = bucket(cindex);
   1.497 +                                probe != NULL;
   1.498 +                                probe = probe->next()) {
   1.499 +      tty->print("%4d: ", cindex);
   1.500 +      probe->name()->print();
   1.501 +      tty->print(" , loaders:");
   1.502 +      for (int n = 0; n < probe->num_loaders(); n++) {
   1.503 +        probe->loader(n)->print_value();
   1.504 +        tty->print(", ");
   1.505 +      }
   1.506 +      tty->cr();
   1.507 +    }
   1.508 +  }
   1.509 +}
   1.510 +#endif

mercurial