diff --git a/src/hotspot/share/classfile/classPrinter.cpp b/src/hotspot/share/classfile/classPrinter.cpp index c4b6a024242..3ed0a5e9840 100644 --- a/src/hotspot/share/classfile/classPrinter.cpp +++ b/src/hotspot/share/classfile/classPrinter.cpp @@ -28,9 +28,10 @@ #include "memory/iterator.hpp" #include "memory/resourceArea.hpp" #include "oops/instanceKlass.hpp" -#include "oops/klass.hpp" +#include "oops/klass.inline.hpp" #include "oops/method.hpp" #include "oops/symbol.hpp" +#include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" class ClassPrinter::KlassPrintClosure : public LockedClassesDo { @@ -42,16 +43,15 @@ class ClassPrinter::KlassPrintClosure : public LockedClassesDo { outputStream* _st; int _num; bool _has_printed_methods; + GrowableArray _klasses; + public: KlassPrintClosure(const char* class_name_pattern, const char* method_name_pattern, const char* method_signature_pattern, bool always_print_class_name, int flags, outputStream* st) - : _class_name_pattern(class_name_pattern), - _method_name_pattern(method_name_pattern), - _method_signature_pattern(method_signature_pattern), - _always_print_class_name(always_print_class_name), + : _always_print_class_name(always_print_class_name), _flags(flags), _st(st), _num(0), _has_printed_methods(false) { if (has_mode(_flags, PRINT_METHOD_HANDLE)) { @@ -66,70 +66,150 @@ public: if (has_mode(_flags, PRINT_BYTECODE)) { _flags |= (PRINT_METHOD_NAME); } + + if (has_mode(_flags, PRINT_CLASS_DETAILS)) { + _always_print_class_name = true; + } + + _class_name_pattern = copy_pattern(class_name_pattern); + _method_name_pattern = copy_pattern(method_name_pattern); + _method_signature_pattern = copy_pattern(method_signature_pattern); + } + + static const char* copy_pattern(const char* pattern) { + if (pattern == nullptr) { + return nullptr; + } + char* copy = ResourceArea::strdup(pattern); + for (char* p = copy; *p; p++) { + if (*p == '.') { + *p = '/'; + } + } + return copy; } virtual void do_klass(Klass* k) { if (!k->is_instance_klass()) { return; } - print_instance_klass(InstanceKlass::cast(k)); + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->is_loaded() && ik->name()->is_star_match(_class_name_pattern)) { + _klasses.append(ik); + } } + void print() { + _klasses.sort(compare_klasses_alphabetically); + for (int i = 0; i < _klasses.length(); i++) { + print_instance_klass(_klasses.at(i)); + } + } + + static bool match(const char* pattern, Symbol* sym) { return (pattern == nullptr || sym->is_star_match(pattern)); } + static int compare_klasses_alphabetically(InstanceKlass** a, InstanceKlass** b) { + return compare_symbols_alphabetically((*a)->name(), (*b)->name()); + } + + static int compare_methods_alphabetically(const void* a, const void* b) { + Method* ma = *(Method**)a; + Method* mb = *(Method**)b; + int n = compare_symbols_alphabetically(ma->name(), mb->name()); + if (n == 0) { + n = compare_symbols_alphabetically(ma->signature(), mb->signature()); + } + return n; + } + + static int compare_symbols_alphabetically(Symbol* a, Symbol *b) { + if (a == b) { + return 0; + } + if (a != nullptr && b == nullptr) { + return 1; + } + if (a == nullptr && b != nullptr) { + return -1; + } + + return strcmp(a->as_C_string(), b->as_C_string()); + } + void print_klass_name(InstanceKlass* ik) { - _st->print("[%3d] " INTPTR_FORMAT " class %s ", _num++, p2i(ik), ik->name()->as_C_string()); + _st->print("[%3d] " INTPTR_FORMAT " class: %s mirror: " INTPTR_FORMAT " ", _num++, + p2i(ik), ik->name()->as_C_string(), p2i(ik->java_mirror())); ik->class_loader_data()->print_value_on(_st); _st->cr(); } void print_instance_klass(InstanceKlass* ik) { - if (ik->is_loaded() && ik->name()->is_star_match(_class_name_pattern)) { - ResourceMark rm; - if (_has_printed_methods) { - // We have printed some methods in the previous class. - // Print a new line to separate the two classes - _st->cr(); + ResourceMark rm; + if (_has_printed_methods) { + // We have printed some methods in the previous class. + // Print a new line to separate the two classes + _st->cr(); + } + _has_printed_methods = false; + if (_always_print_class_name) { + print_klass_name(ik); + } + + if (has_mode(_flags, ClassPrinter::PRINT_CLASS_DETAILS)) { + _st->print("InstanceKlass: "); + ik->print_on(_st); + oop mirror = ik->java_mirror(); + if (mirror != nullptr) { + _st->print("\nJava mirror oop for %s: ", ik->name()->as_C_string()); + mirror->print_on(_st); } - _has_printed_methods = false; - if (_always_print_class_name) { - print_klass_name(ik); + } + + if (has_mode(_flags, ClassPrinter::PRINT_METHOD_NAME)) { + bool print_codes = has_mode(_flags, ClassPrinter::PRINT_BYTECODE); + int len = ik->methods()->length(); + int num_methods_printed = 0; + + Method** sorted_methods = NEW_RESOURCE_ARRAY(Method*, len); + for (int index = 0; index < len; index++) { + sorted_methods[index] = ik->methods()->at(index); } - if (has_mode(_flags, ClassPrinter::PRINT_METHOD_NAME)) { - bool print_codes = has_mode(_flags, ClassPrinter::PRINT_BYTECODE); - int len = ik->methods()->length(); - int num_methods_printed = 0; + qsort(sorted_methods, len, sizeof(Method*), compare_methods_alphabetically); - for (int index = 0; index < len; index++) { - Method* m = ik->methods()->at(index); - if (match(_method_name_pattern, m->name()) && - match(_method_signature_pattern, m->signature())) { - if (print_codes && num_methods_printed++ > 0) { - _st->cr(); - } - - if (_has_printed_methods == false) { - if (!_always_print_class_name) { - print_klass_name(ik); - } - _has_printed_methods = true; - } - print_method(m); + for (int index = 0; index < len; index++) { + Method* m = sorted_methods[index]; + if (match(_method_name_pattern, m->name()) && + match(_method_signature_pattern, m->signature())) { + if (print_codes && num_methods_printed++ > 0) { + _st->cr(); } + + if (_has_printed_methods == false) { + if (!_always_print_class_name) { + print_klass_name(ik); + } + _has_printed_methods = true; + } + print_method(m); } } } } void print_method(Method* m) { - bool print_codes = has_mode(_flags, ClassPrinter::PRINT_BYTECODE); _st->print_cr(INTPTR_FORMAT " %smethod %s : %s", p2i(m), m->is_static() ? "static " : "", m->name()->as_C_string(), m->signature()->as_C_string()); - if (print_codes) { + + if (has_mode(_flags, ClassPrinter::PRINT_METHOD_DETAILS)) { + m->print_on(_st); + } + + if (has_mode(_flags, ClassPrinter::PRINT_BYTECODE)) { m->print_codes_on(_st, _flags); } } @@ -142,12 +222,16 @@ void ClassPrinter::print_flags_help(outputStream* os) { os->print_cr(" 0x%02x - print the address of bytecodes", PRINT_BYTECODE_ADDR); os->print_cr(" 0x%02x - print info for invokedynamic", PRINT_DYNAMIC); os->print_cr(" 0x%02x - print info for invokehandle", PRINT_METHOD_HANDLE); + os->print_cr(" 0x%02x - print details of the C++ and Java objects that represent classes", PRINT_CLASS_DETAILS); + os->print_cr(" 0x%02x - print details of the C++ objects that represent methods", PRINT_METHOD_DETAILS); os->cr(); } void ClassPrinter::print_classes(const char* class_name_pattern, int flags, outputStream* os) { + ResourceMark rm; KlassPrintClosure closure(class_name_pattern, nullptr, nullptr, true, flags, os); ClassLoaderDataGraph::classes_do(&closure); + closure.print(); } void ClassPrinter::print_methods(const char* class_name_pattern, @@ -174,4 +258,5 @@ void ClassPrinter::print_methods(const char* class_name_pattern, KlassPrintClosure closure(class_name_pattern, method_name_pattern, method_signature_pattern, false, flags | PRINT_METHOD_NAME, os); ClassLoaderDataGraph::classes_do(&closure); + closure.print(); } diff --git a/src/hotspot/share/classfile/classPrinter.hpp b/src/hotspot/share/classfile/classPrinter.hpp index 0fff372c302..470e82ddc0e 100644 --- a/src/hotspot/share/classfile/classPrinter.hpp +++ b/src/hotspot/share/classfile/classPrinter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,6 +52,8 @@ public: PRINT_BYTECODE_ADDR = 1 << 2, PRINT_DYNAMIC = 1 << 3, // extra information for invokedynamic (and dynamic constant ...) PRINT_METHOD_HANDLE = 1 << 4, // extra information for invokehandle + PRINT_CLASS_DETAILS = 1 << 5, // print details of the C++ and Java objects that represent classes + PRINT_METHOD_DETAILS = 1 << 6, // print details of the C++ objects that represent methods }; static bool has_mode(int flags, Mode mode) { return (flags & static_cast(mode)) != 0; diff --git a/test/hotspot/gtest/runtime/test_classPrinter.cpp b/test/hotspot/gtest/runtime/test_classPrinter.cpp index b13713d7232..9b6d47f09bc 100644 --- a/test/hotspot/gtest/runtime/test_classPrinter.cpp +++ b/test/hotspot/gtest/runtime/test_classPrinter.cpp @@ -28,6 +28,7 @@ #include "utilities/ostream.hpp" #include "unittest.hpp" +using testing::ContainsRegex; using testing::HasSubstr; TEST_VM(ClassPrinter, print_classes) { @@ -35,13 +36,31 @@ TEST_VM(ClassPrinter, print_classes) { ThreadInVMfromNative invm(THREAD); ResourceMark rm; - stringStream ss; - ClassPrinter::print_classes("java/lang/Object", 0x03, &ss); - const char* output = ss.freeze(); + stringStream s1; + ClassPrinter::print_classes("java/lang/Object", 0x03, &s1); + const char* o1 = s1.freeze(); - ASSERT_THAT(output, HasSubstr("class java/lang/Object loader data:")) << "must find java/lang/Object"; - ASSERT_THAT(output, HasSubstr("method wait : (J)V")) << "must find java/lang/Object::wait"; - ASSERT_THAT(output, HasSubstr("method finalize : ()V\n 0 return")) << "must find java/lang/Object::finalize and disasm"; + ASSERT_THAT(o1, HasSubstr("class: java/lang/Object mirror:")) << "must find java/lang/Object"; + ASSERT_THAT(o1, HasSubstr("method wait : (J)V")) << "must find java/lang/Object::wait"; + ASSERT_THAT(o1, HasSubstr("method finalize : ()V\n 0 return")) << "must find java/lang/Object::finalize and disasm"; + + // "." should also work as separator in class name + stringStream s2; + ClassPrinter::print_classes("java.lang.Object", 0x03, &s2); + const char* o2 = s2.freeze(); + ASSERT_THAT(o2, HasSubstr("class: java/lang/Object mirror:")) << "must find java/lang/Object"; + + // 0x20 is PRINT_CLASS_DETAILS + stringStream s3; + ClassPrinter::print_classes("java.lang.Integer", 0x20, &s3); + const char* o3 = s3.freeze(); + ASSERT_THAT(o3, HasSubstr("class: java/lang/Integer mirror:")) << "must find java/lang/Integer"; + ASSERT_THAT(o3, HasSubstr("InstanceKlass: java.lang.Integer {0x")) << "must print InstanceKlass"; + ASSERT_THAT(o3, HasSubstr("Java mirror oop for java/lang/Integer:")) << "must print mirror oop"; +#if GTEST_USES_POSIX_RE + // Complex regex not available on Windows + ASSERT_THAT(o3, ContainsRegex("public static final 'MIN_VALUE' 'I'.* -2147483648 [(]0x80000000[)]")) << "must print static fields"; +#endif } TEST_VM(ClassPrinter, print_methods) { @@ -52,7 +71,7 @@ TEST_VM(ClassPrinter, print_methods) { stringStream s1; ClassPrinter::print_methods("*ang/Object*", "wait", 0x1, &s1); const char* o1 = s1.freeze(); - ASSERT_THAT(o1, HasSubstr("class java/lang/Object loader data:")) << "must find java/lang/Object"; + ASSERT_THAT(o1, HasSubstr("class: java/lang/Object mirror:")) << "must find java/lang/Object"; ASSERT_THAT(o1, HasSubstr("method wait : (J)V")) << "must find java/lang/Object::wait(long)"; ASSERT_THAT(o1, HasSubstr("method wait : ()V")) << "must find java/lang/Object::wait()"; ASSERT_THAT(o1, Not(HasSubstr("method finalize : ()V"))) << "must not find java/lang/Object::finalize"; @@ -60,8 +79,34 @@ TEST_VM(ClassPrinter, print_methods) { stringStream s2; ClassPrinter::print_methods("j*ang/Object*", "wait:(*J*)V", 0x1, &s2); const char* o2 = s2.freeze(); - ASSERT_THAT(o2, HasSubstr("class java/lang/Object loader data:")) << "must find java/lang/Object"; + ASSERT_THAT(o2, HasSubstr("class: java/lang/Object mirror:")) << "must find java/lang/Object"; ASSERT_THAT(o2, HasSubstr("method wait : (J)V")) << "must find java/lang/Object::wait(long)"; ASSERT_THAT(o2, HasSubstr("method wait : (JI)V")) << "must find java/lang/Object::wait(long,int)"; ASSERT_THAT(o2, Not(HasSubstr("method wait : ()V"))) << "must not find java/lang/Object::wait()"; + + // 0x02 is PRINT_BYTECODE + // 0x04 is PRINT_BYTECODE_ADDRESS + // 0x40 is PRINT_METHOD_DETAILS + stringStream s3; + ClassPrinter::print_methods("java.lang.Object", "wait:()V", 0x46, &s3); + const char* o3 = s3.freeze(); + ASSERT_THAT(o3, HasSubstr("method wait : ()V")) << "must find java/lang/Object::wait()"; + +#ifndef PRODUCT + // PRINT_METHOD_DETAILS -- available only in debug builds + ASSERT_THAT(o3, HasSubstr("{method}")) << "must print Method metadata"; +#if GTEST_USES_POSIX_RE + // Complex regex not available on Windows + ASSERT_THAT(o3, ContainsRegex("method holder:.*'java/lang/Object'")) << "must print Method metadata details"; + ASSERT_THAT(o3, ContainsRegex("name: *'wait'")) << "must print Method metadata details"; +#endif +#endif + +#if GTEST_USES_POSIX_RE + // Bytecodes: we should have at least one 'return' bytecide for Object.wait() + // The print out should look like this: + // 0x000000004adf73ad 5 return + ASSERT_THAT(o3, ContainsRegex("0x[0-9a-f]+ +[0-9]+ +return")) << "must print return bytecode"; +#endif + }