mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-27 07:10:45 +00:00
8370102: Print method signatures in edge cases when using TraceBytecodes
Reviewed-by: coleenp, dholmes
This commit is contained in:
parent
c796ed1ad8
commit
21b9a50239
@ -40,21 +40,17 @@
|
||||
#include "utilities/align.hpp"
|
||||
|
||||
// Prints the current bytecode and its attributes using bytecode-specific information.
|
||||
// Printing bytecodes is not an idempotent operation, as this relies on modifying state.
|
||||
// An instance of this class is thus intended to only print a single bytecode. All state
|
||||
// shared between multiple bytecodes needs to be in BytecodeTracerData.
|
||||
|
||||
class BytecodePrinter {
|
||||
private:
|
||||
// %%% This field is not GC-ed, and so can contain garbage
|
||||
// between critical sections. Use only pointer-comparison
|
||||
// operations on the pointer, except within a critical section.
|
||||
// (Also, ensure that occasional false positives are benign.)
|
||||
Method* _current_method;
|
||||
bool _is_wide;
|
||||
Bytecodes::Code _code;
|
||||
address _next_pc; // current decoding position
|
||||
int _flags;
|
||||
bool _use_cp_cache;
|
||||
BytecodeTracerData* _data;
|
||||
int _flags;
|
||||
Bytecodes::Code _raw_code; // note: some methods translate this to the Java bytecode
|
||||
address _next_pc; // current decoding position, destructive
|
||||
|
||||
bool use_cp_cache() const { return _use_cp_cache; }
|
||||
void align() { _next_pc = align_up(_next_pc, sizeof(jint)); }
|
||||
int get_byte() { return *(jbyte*) _next_pc++; } // signed
|
||||
int get_index_u1() { return *(address)_next_pc++; } // returns 0x00 - 0xff as an int
|
||||
@ -65,11 +61,22 @@ class BytecodePrinter {
|
||||
int get_Java_index_u2() { int i = Bytes::get_Java_u2 (_next_pc); _next_pc += 2; return i; }
|
||||
int get_Java_index_u4() { int i = Bytes::get_Java_u4 (_next_pc); _next_pc += 4; return i; }
|
||||
int get_index_special() { return (is_wide()) ? get_Java_index_u2() : get_index_u1(); }
|
||||
Method* method() const { return _current_method; }
|
||||
bool is_wide() const { return _is_wide; }
|
||||
Bytecodes::Code raw_code() const { return Bytecodes::Code(_code); }
|
||||
ConstantPool* constants() const { return method()->constants(); }
|
||||
ConstantPoolCache* cpcache() const { assert(use_cp_cache(), "must be"); return constants()->cache(); }
|
||||
|
||||
// Warning: only do pointer comparison between critical sections.
|
||||
Method* method() const { return _data->current_method(); }
|
||||
void set_method(Method* current) { _data->set_current_method(current); }
|
||||
|
||||
// Warning: should only be used for comparison and not dereferenced.
|
||||
intptr_t* fp() const { return _data->current_fp(); }
|
||||
void set_fp(intptr_t* current) { _data->set_current_fp(current); }
|
||||
|
||||
bool is_wide() const { return _data->is_wide(); }
|
||||
void set_wide(bool wide) { _data->set_wide(wide); }
|
||||
|
||||
ConstantPool* constants() const { return method()->constants(); }
|
||||
// This may be called during linking after bytecodes are rewritten to point to the cpCache.
|
||||
bool use_cp_cache() const { return constants()->cache() != nullptr; }
|
||||
ConstantPoolCache* cpcache() const { assert(use_cp_cache(), "must be"); return constants()->cache(); }
|
||||
|
||||
void print_constant(int i, outputStream* st);
|
||||
void print_cpcache_entry(int cpc_index, outputStream* st);
|
||||
@ -81,35 +88,32 @@ class BytecodePrinter {
|
||||
void print_method_data_at(int bci, outputStream* st);
|
||||
|
||||
public:
|
||||
BytecodePrinter(int flags = 0) : _is_wide(false), _code(Bytecodes::_illegal), _flags(flags) {}
|
||||
BytecodePrinter(BytecodeTracerData* data, int flags = 0) :
|
||||
_data(data),
|
||||
_flags(flags),
|
||||
_raw_code(Bytecodes::_illegal),
|
||||
_next_pc(nullptr) {}
|
||||
|
||||
#ifndef PRODUCT
|
||||
BytecodePrinter(Method* prev_method) : BytecodePrinter(0) {
|
||||
_current_method = prev_method;
|
||||
}
|
||||
|
||||
// This method is called while executing the raw bytecodes, so none of
|
||||
// the adjustments that BytecodeStream performs applies.
|
||||
void trace(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) {
|
||||
void trace(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) {
|
||||
assert(_raw_code == Bytecodes::_illegal, "invariant");
|
||||
ResourceMark rm;
|
||||
bool method_changed = _current_method != method();
|
||||
_current_method = method();
|
||||
_use_cp_cache = method->constants()->cache() != nullptr;
|
||||
// Method changes can be another method getting called, or a self-recursive call.
|
||||
bool method_changed = (this->method() != method()) || (this->fp() != fp);
|
||||
set_method(method());
|
||||
set_fp(fp);
|
||||
assert(method->method_holder()->is_linked(),
|
||||
"this function must be called on methods that are already executing");
|
||||
|
||||
// If the method changed (new method call, return to previous method after call finishes),
|
||||
// the signature needs to be re-printed for interpretability.
|
||||
if (method_changed) {
|
||||
// Note 1: This code will not work as expected with true MT/MP.
|
||||
// Need an explicit lock or a different solution.
|
||||
// It is possible for this block to be skipped, if a garbage
|
||||
// _current_method pointer happens to have the same bits as
|
||||
// the incoming method. We could lose a line of trace output.
|
||||
// This is acceptable in a debug-only feature.
|
||||
st->cr();
|
||||
st->print("[%zu] ", Thread::current()->osthread()->thread_id_for_printing());
|
||||
method->print_name(st);
|
||||
st->cr();
|
||||
}
|
||||
|
||||
Bytecodes::Code code;
|
||||
if (is_wide()) {
|
||||
// bcp wasn't advanced if previous bytecode was _wide.
|
||||
@ -117,14 +121,13 @@ class BytecodePrinter {
|
||||
} else {
|
||||
code = Bytecodes::code_at(method(), bcp);
|
||||
}
|
||||
_code = code;
|
||||
_raw_code = code;
|
||||
_next_pc = is_wide() ? bcp+2 : bcp+1;
|
||||
|
||||
bool is_terminal = Bytecodes::is_terminal(code);
|
||||
// Trace each bytecode unless we're truncating the tracing output, then only print the first
|
||||
// bytecode in every method as well as returns/throws that pop control flow
|
||||
if (!TraceBytecodesTruncated || method_changed ||
|
||||
code == Bytecodes::_athrow ||
|
||||
code == Bytecodes::_return_register_finalizer ||
|
||||
(code >= Bytecodes::_ireturn && code <= Bytecodes::_return)) {
|
||||
// bytecode in every method as well as returns/throws that pop control flow.
|
||||
if (!TraceBytecodesTruncated || method_changed || is_terminal) {
|
||||
int bci = (int)(bcp - method->code_base());
|
||||
st->print("[%zu] ", Thread::current()->osthread()->thread_id_for_printing());
|
||||
if (Verbose) {
|
||||
@ -138,8 +141,16 @@ class BytecodePrinter {
|
||||
}
|
||||
// Set is_wide for the next one, since the caller of this doesn't skip
|
||||
// the next bytecode.
|
||||
_is_wide = (code == Bytecodes::_wide);
|
||||
_code = Bytecodes::_illegal;
|
||||
set_wide(code == Bytecodes::_wide);
|
||||
// Finished using the code, reset to illegal.
|
||||
_raw_code = Bytecodes::_illegal;
|
||||
|
||||
// Invalidate the current method to force a signature change. In some
|
||||
// rare cases, the method and frame pointers aren't enough to determine
|
||||
// a new method invocation, so this ensures the signature is re-printed.
|
||||
if (is_terminal) {
|
||||
set_method(nullptr);
|
||||
}
|
||||
|
||||
if (TraceBytecodesStopAt != 0 && BytecodeCounter::counter_value() >= TraceBytecodesStopAt) {
|
||||
TraceBytecodes = false;
|
||||
@ -150,17 +161,16 @@ class BytecodePrinter {
|
||||
// Used for Method::print_codes(). The input bcp comes from
|
||||
// BytecodeStream, which will skip wide bytecodes.
|
||||
void trace(const methodHandle& method, address bcp, outputStream* st) {
|
||||
_current_method = method();
|
||||
// This may be called during linking after bytecodes are rewritten to point to the cpCache.
|
||||
_use_cp_cache = method->constants()->cache() != nullptr;
|
||||
assert(_raw_code == Bytecodes::_illegal, "invariant");
|
||||
set_method(method());
|
||||
ResourceMark rm;
|
||||
Bytecodes::Code code = Bytecodes::code_at(method(), bcp);
|
||||
// Set is_wide
|
||||
_is_wide = (code == Bytecodes::_wide);
|
||||
set_wide(code == Bytecodes::_wide);
|
||||
if (is_wide()) {
|
||||
code = Bytecodes::code_at(method(), bcp+1);
|
||||
}
|
||||
_code = code;
|
||||
_raw_code = code;
|
||||
int bci = (int)(bcp - method->code_base());
|
||||
// Print bytecode index and name
|
||||
if (ClassPrinter::has_mode(_flags, ClassPrinter::PRINT_BYTECODE_ADDR)) {
|
||||
@ -180,26 +190,20 @@ class BytecodePrinter {
|
||||
};
|
||||
|
||||
#ifndef PRODUCT
|
||||
// We need a global instance to keep track of the method being printed so we can report that
|
||||
// the method has changed. If this method is redefined and removed, that's ok because the method passed
|
||||
// in won't match, and this will print the method passed in again. Racing threads changing this global
|
||||
// will result in reprinting the method passed in again.
|
||||
static Method* _method_currently_being_printed = nullptr;
|
||||
|
||||
void BytecodeTracer::trace_interpreter(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) {
|
||||
void BytecodeTracer::trace_interpreter(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st) {
|
||||
if (TraceBytecodes && BytecodeCounter::counter_value() >= TraceBytecodesAt) {
|
||||
BytecodePrinter printer(AtomicAccess::load_acquire(&_method_currently_being_printed));
|
||||
stringStream buf;
|
||||
printer.trace(method, bcp, tos, tos2, &buf);
|
||||
st->print("%s", buf.freeze());
|
||||
// Save method currently being printed to detect when method printing changes.
|
||||
AtomicAccess::release_store(&_method_currently_being_printed, method());
|
||||
BytecodeTracerData* data = JavaThread::current()->bytecode_tracer_data();
|
||||
BytecodePrinter printer(data);
|
||||
printer.trace(method, fp, bcp, tos, tos2, st);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void BytecodeTracer::print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered) {
|
||||
BytecodePrinter method_printer(flags);
|
||||
// Debug builds can't re-use the data in the Java Thread as that is used for tracing
|
||||
// the current bytecodes, rather than to print diagnostic information as is the
|
||||
// case here. Always stack-allocate for this printing.
|
||||
BytecodeTracerData data;
|
||||
BytecodeStream s(method);
|
||||
s.set_interval(from, to);
|
||||
|
||||
@ -207,6 +211,7 @@ void BytecodeTracer::print_method_codes(const methodHandle& method, int from, in
|
||||
stringStream ss;
|
||||
outputStream* out = buffered ? &ss : st;
|
||||
while (s.next() >= 0) {
|
||||
BytecodePrinter method_printer(&data, flags);
|
||||
method_printer.trace(method, s.bcp(), out);
|
||||
}
|
||||
if (buffered) {
|
||||
@ -345,7 +350,7 @@ void BytecodePrinter::print_bsm(int cp_index, outputStream* st) {
|
||||
|
||||
void BytecodePrinter::print_attributes(int bci, outputStream* st) {
|
||||
// Show attributes of pre-rewritten codes
|
||||
Bytecodes::Code code = Bytecodes::java_code(raw_code());
|
||||
Bytecodes::Code code = Bytecodes::java_code(_raw_code);
|
||||
// If the code doesn't have any fields there's nothing to print.
|
||||
// note this is ==1 because the tableswitch and lookupswitch are
|
||||
// zero size (for some reason) and we want to print stuff out for them.
|
||||
@ -366,7 +371,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) {
|
||||
case Bytecodes::_ldc:
|
||||
{
|
||||
int cp_index;
|
||||
if (Bytecodes::uses_cp_cache(raw_code())) {
|
||||
if (Bytecodes::uses_cp_cache(_raw_code)) {
|
||||
assert(use_cp_cache(), "fast ldc bytecode must be in linked classes");
|
||||
int obj_index = get_index_u1();
|
||||
cp_index = constants()->object_to_cp_index(obj_index);
|
||||
@ -381,7 +386,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) {
|
||||
case Bytecodes::_ldc2_w:
|
||||
{
|
||||
int cp_index;
|
||||
if (Bytecodes::uses_cp_cache(raw_code())) {
|
||||
if (Bytecodes::uses_cp_cache(_raw_code)) {
|
||||
assert(use_cp_cache(), "fast ldc bytecode must be in linked classes");
|
||||
int obj_index = get_native_index_u2();
|
||||
cp_index = constants()->object_to_cp_index(obj_index);
|
||||
@ -533,7 +538,7 @@ void BytecodePrinter::print_attributes(int bci, outputStream* st) {
|
||||
cp_index = method_entry->constant_pool_index();
|
||||
print_field_or_method(cp_index, st);
|
||||
|
||||
if (raw_code() == Bytecodes::_invokehandle &&
|
||||
if (_raw_code == Bytecodes::_invokehandle &&
|
||||
ClassPrinter::has_mode(_flags, ClassPrinter::PRINT_METHOD_HANDLE)) {
|
||||
assert(use_cp_cache(), "invokehandle is only in rewritten methods");
|
||||
method_entry->print_on(st);
|
||||
|
||||
@ -25,21 +25,54 @@
|
||||
#ifndef SHARE_INTERPRETER_BYTECODETRACER_HPP
|
||||
#define SHARE_INTERPRETER_BYTECODETRACER_HPP
|
||||
|
||||
#include "interpreter/bytecodes.hpp"
|
||||
#include "memory/allStatic.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
// The BytecodeTracer is a helper class used by the interpreter for run-time
|
||||
// bytecode tracing. If TraceBytecodes turned on, trace_interpreter() will be called
|
||||
// for each bytecode.
|
||||
|
||||
class Method;
|
||||
class methodHandle;
|
||||
class outputStream;
|
||||
|
||||
class BytecodeClosure;
|
||||
|
||||
// The BytecodeTracer is a helper class used by the interpreter for run-time
|
||||
// bytecode tracing. If TraceBytecodes is turned on, trace_interpreter() will be called
|
||||
// for each bytecode.
|
||||
class BytecodeTracer: AllStatic {
|
||||
public:
|
||||
NOT_PRODUCT(static void trace_interpreter(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st);)
|
||||
NOT_PRODUCT(static void trace_interpreter(const methodHandle& method, intptr_t* fp, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st);)
|
||||
static void print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered = true);
|
||||
};
|
||||
|
||||
// Provides tracing-centric context whose lifespan exceeds the printing of
|
||||
// a single bytecode. For instance, it is needed to determine method switches
|
||||
// in order to print the appropriate signature once a switch happens.
|
||||
class BytecodeTracerData {
|
||||
private:
|
||||
Method* _current_method; // for method switches
|
||||
intptr_t* _current_fp; // for self-recursion
|
||||
bool _is_wide; // to parse the next bytecode properly
|
||||
|
||||
public:
|
||||
BytecodeTracerData() : _current_method(nullptr),
|
||||
_current_fp(nullptr),
|
||||
_is_wide(false) {}
|
||||
|
||||
// The current method may point to a stale/garbage Method. While pointer
|
||||
// comparison is safe, it should only be dereferenced while guaranteed to
|
||||
// be valid. For example, if the current method is set to the result of a
|
||||
// methodHandle call, current_method() may be dereferenced while the handle
|
||||
// is live. It is always up to the caller to ensure that current_method()
|
||||
// is safe to dereference.
|
||||
Method* current_method() const { return _current_method; }
|
||||
void set_current_method(Method* current) { _current_method = current; }
|
||||
|
||||
// The frame pointer should only ever be used for pointer comparison and may
|
||||
// never be dereferenced.
|
||||
intptr_t* current_fp() const { return _current_fp; }
|
||||
void set_current_fp(intptr_t* current) { _current_fp = current; }
|
||||
|
||||
bool is_wide() const { return _is_wide; }
|
||||
void set_wide(bool wide) { _is_wide = wide; }
|
||||
};
|
||||
|
||||
#endif // SHARE_INTERPRETER_BYTECODETRACER_HPP
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2026, 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
|
||||
@ -418,6 +418,9 @@ class Bytecodes: AllStatic {
|
||||
static bool is_zero_const (Code code) { return (code == _aconst_null || code == _iconst_0
|
||||
|| code == _fconst_0 || code == _dconst_0); }
|
||||
static bool is_return (Code code) { return (_ireturn <= code && code <= _return); }
|
||||
static bool is_terminal (Code code) { return is_return(code) ||
|
||||
code == _return_register_finalizer ||
|
||||
code == _athrow; }
|
||||
static bool is_invoke (Code code) { return (_invokevirtual <= code && code <= _invokedynamic); }
|
||||
static bool is_field_code (Code code) { return (_getstatic <= java_code(code) && java_code(code) <= _putfield); }
|
||||
static bool has_receiver (Code code) { assert(is_invoke(code), ""); return code == _invokevirtual ||
|
||||
|
||||
@ -1517,7 +1517,9 @@ JRT_LEAF(intptr_t, InterpreterRuntime::trace_bytecode(JavaThread* current, intpt
|
||||
LastFrameAccessor last_frame(current);
|
||||
assert(last_frame.is_interpreted_frame(), "must be an interpreted frame");
|
||||
methodHandle mh(current, last_frame.method());
|
||||
BytecodeTracer::trace_interpreter(mh, last_frame.bcp(), tos, tos2, tty);
|
||||
stringStream st;
|
||||
BytecodeTracer::trace_interpreter(mh, last_frame.get_frame().real_fp(), last_frame.bcp(), tos, tos2, &st);
|
||||
tty->print("%s", st.freeze());
|
||||
return preserve_this_value;
|
||||
JRT_END
|
||||
#endif // !PRODUCT
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#include "gc/shared/oopStorage.hpp"
|
||||
#include "gc/shared/oopStorageSet.hpp"
|
||||
#include "gc/shared/tlab_globals.hpp"
|
||||
#include "interpreter/bytecodeTracer.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "jvm.h"
|
||||
#include "jvmtifiles/jvmtiEnv.hpp"
|
||||
@ -432,6 +433,8 @@ JavaThread::JavaThread(MemTag mem_tag) :
|
||||
_visited_for_critical_count(false),
|
||||
#endif
|
||||
|
||||
NOT_PRODUCT(_bytecode_tracer_data{} COMMA)
|
||||
|
||||
_terminated(_not_terminated),
|
||||
_in_deopt_handler(0),
|
||||
_doing_unsafe_access(false),
|
||||
|
||||
@ -26,6 +26,9 @@
|
||||
#ifndef SHARE_RUNTIME_JAVATHREAD_HPP
|
||||
#define SHARE_RUNTIME_JAVATHREAD_HPP
|
||||
|
||||
#ifndef PRODUCT
|
||||
#include "interpreter/bytecodeTracer.hpp"
|
||||
#endif // PRODUCT
|
||||
#include "jni.h"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "oops/oop.hpp"
|
||||
@ -291,6 +294,16 @@ class JavaThread: public Thread {
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
#ifndef PRODUCT
|
||||
private:
|
||||
BytecodeTracerData _bytecode_tracer_data;
|
||||
|
||||
public:
|
||||
BytecodeTracerData* bytecode_tracer_data() {
|
||||
return &_bytecode_tracer_data;
|
||||
}
|
||||
#endif // PRODUCT
|
||||
|
||||
// JavaThread termination support
|
||||
public:
|
||||
enum TerminatedTypes {
|
||||
|
||||
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2026, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8370102
|
||||
* @requires vm.debug
|
||||
* @library / /test/lib
|
||||
* @summary Test to ensure the signatures in -XX:+TraceBytecodes are printed in edge cases
|
||||
* @run driver TraceBytecodesSignatures
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
public class TraceBytecodesSignatures {
|
||||
|
||||
public static void main(String[] args)
|
||||
throws InterruptedException, IOException {
|
||||
if (args.length == 1 && "worker".equals(args[0])) {
|
||||
runTest();
|
||||
return;
|
||||
}
|
||||
// Enable byteocde tracing but disable compilation of the key methods
|
||||
// that we will trace.
|
||||
String[] processArgs = new String[] {
|
||||
"-XX:+TraceBytecodes",
|
||||
exclude("runTest"),
|
||||
exclude("testSameMethod"),
|
||||
exclude("testSameMethodHelper"),
|
||||
excludeConstructor(),
|
||||
exclude("testSelfRecursive"),
|
||||
exclude("testSelfRecursiveHelper"),
|
||||
klass(),
|
||||
"worker"
|
||||
};
|
||||
// Create a VM process and trace its bytecodes.
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(processArgs);
|
||||
OutputAnalyzer oa = new OutputAnalyzer(pb.start())
|
||||
.shouldHaveExitValue(0);
|
||||
analyze(oa.stdoutAsLines());
|
||||
}
|
||||
|
||||
private static void runTest() {
|
||||
testSameMethod(true);
|
||||
testSameMethod(false);
|
||||
new TestConstructor();
|
||||
new TestConstructor();
|
||||
testSelfRecursive(3);
|
||||
}
|
||||
|
||||
// Expect 3 signature prints:
|
||||
// 1. The first call
|
||||
// 2. The first call switch back after the helper is called
|
||||
// 3. The second call
|
||||
private static final int EXPECTED_SAME_METHOD = 3;
|
||||
|
||||
// Expect 2 signature prints:
|
||||
// 1. The first call of the no-arg constructor
|
||||
// 2. Switch back after calling the 1-arg constructor
|
||||
// 3. The second call of the no-arg constructor
|
||||
// 4. Switch back again
|
||||
private static final int EXPECTED_CONSTRUCTOR = 4;
|
||||
|
||||
// Expect 10 signature prints:
|
||||
// 1. The first recursive call
|
||||
// 2. The first switch back after the helper is called.
|
||||
// 3. The second recursive call
|
||||
// 4. The second switch back
|
||||
// 5. The third recursive call
|
||||
// 6. The third switch back
|
||||
// 7. The fourth recursive call: base case hit
|
||||
// 8-10. Switch back to i=1, i=2, i=3, respectively
|
||||
private static final int EXPECTED_SELF_RECURSIVE_METHOD = 10;
|
||||
|
||||
// Ensure that the expected signatures are printed the correct nr of times.
|
||||
// The signatures here need to be changed if any of the test code is changed.
|
||||
private static void analyze(List<String> lines) {
|
||||
System.out.println("Analyzing " + lines.size() + " lines");
|
||||
int testSameMethodCount = 0;
|
||||
int testConstructorCount = 0;
|
||||
int testSelfRecursiveCount = 0;
|
||||
for (String line : lines) {
|
||||
if (line.contains("static void TraceBytecodesSignatures.testSameMethod(jboolean)")) {
|
||||
testSameMethodCount++;
|
||||
} else if (line.contains("virtual void TraceBytecodesSignatures$TestConstructor.<init>()")) {
|
||||
testConstructorCount++;
|
||||
} else if (line.contains("static void TraceBytecodesSignatures.testSelfRecursive(jint)")) {
|
||||
testSelfRecursiveCount++;
|
||||
}
|
||||
}
|
||||
if (testSameMethodCount != EXPECTED_SAME_METHOD) {
|
||||
throw new RuntimeException("testSameMethod: "
|
||||
+ EXPECTED_SAME_METHOD
|
||||
+ " != "
|
||||
+ testSameMethodCount);
|
||||
}
|
||||
if (testConstructorCount != EXPECTED_CONSTRUCTOR) {
|
||||
throw new RuntimeException("testConstructor: "
|
||||
+ EXPECTED_CONSTRUCTOR
|
||||
+ " != "
|
||||
+ testConstructorCount);
|
||||
}
|
||||
if (testSelfRecursiveCount != EXPECTED_SELF_RECURSIVE_METHOD) {
|
||||
throw new RuntimeException("testSelfRecursive: "
|
||||
+ EXPECTED_SELF_RECURSIVE_METHOD
|
||||
+ " != "
|
||||
+ testSelfRecursiveCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Use a variable to do some arithmetic on to represent work.
|
||||
// This work should not produce method invocations, hence integer arithmetic.
|
||||
private static int globalState = 0;
|
||||
|
||||
private static void testSameMethod(boolean other) {
|
||||
globalState = 1;
|
||||
if (other) {
|
||||
testSameMethodHelper();
|
||||
}
|
||||
globalState = 1;
|
||||
}
|
||||
|
||||
private static void testSameMethodHelper() {
|
||||
globalState = 5;
|
||||
}
|
||||
|
||||
private static final class TestConstructor {
|
||||
// Represents some kind of mutable state, not necessarily a object attribute.
|
||||
private static String foo = "test";
|
||||
|
||||
public TestConstructor() {
|
||||
this("bar");
|
||||
}
|
||||
|
||||
public TestConstructor(String other) {
|
||||
foo = other;
|
||||
}
|
||||
}
|
||||
|
||||
private static void testSelfRecursive(int i) {
|
||||
if (i == 0) {
|
||||
return;
|
||||
}
|
||||
globalState += 2;
|
||||
// Ensure to generate another method call within the recursive method.
|
||||
// This will trigger the method switches more often.
|
||||
testSelfRecursiveHelper();
|
||||
globalState -= 2;
|
||||
testSelfRecursive(i - 1);
|
||||
}
|
||||
|
||||
private static void testSelfRecursiveHelper() {
|
||||
globalState = -1;
|
||||
}
|
||||
|
||||
// CompileCommand is accepted even if the VM does not use JIT compilation.
|
||||
private static String exclude(String methodName) {
|
||||
return "-XX:CompileCommand=exclude," + klass() + "." + methodName;
|
||||
}
|
||||
|
||||
private static String excludeConstructor() {
|
||||
return "-XX:CompileCommand=exclude," + klass() + "$TestConstructor.<init>";
|
||||
}
|
||||
|
||||
private static String klass() {
|
||||
return TraceBytecodesSignatures.class.getName();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user