| 1 | /* |
| 2 | * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | * accompanied this code). |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License version |
| 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | * |
| 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 20 | * or visit www.oracle.com if you need additional information or have any |
| 21 | * questions. |
| 22 | * |
| 23 | */ |
| 24 | |
| 25 | #include "precompiled.hpp" |
| 26 | #include "classfile/javaClasses.hpp" |
| 27 | #include "classfile/javaClasses.inline.hpp" |
| 28 | #include "classfile/vmSymbols.hpp" |
| 29 | #include "logging/log.hpp" |
| 30 | #include "logging/logStream.hpp" |
| 31 | #include "memory/oopFactory.hpp" |
| 32 | #include "memory/universe.hpp" |
| 33 | #include "oops/oop.inline.hpp" |
| 34 | #include "oops/objArrayOop.inline.hpp" |
| 35 | #include "prims/stackwalk.hpp" |
| 36 | #include "runtime/globals.hpp" |
| 37 | #include "runtime/handles.inline.hpp" |
| 38 | #include "runtime/javaCalls.hpp" |
| 39 | #include "runtime/thread.inline.hpp" |
| 40 | #include "runtime/vframe.inline.hpp" |
| 41 | #include "utilities/globalDefinitions.hpp" |
| 42 | |
| 43 | // setup and cleanup actions |
| 44 | void BaseFrameStream::setup_magic_on_entry(objArrayHandle frames_array) { |
| 45 | frames_array->obj_at_put(magic_pos, _thread->threadObj()); |
| 46 | _anchor = address_value(); |
| 47 | assert(check_magic(frames_array), "invalid magic" ); |
| 48 | } |
| 49 | |
| 50 | bool BaseFrameStream::check_magic(objArrayHandle frames_array) { |
| 51 | oop m1 = frames_array->obj_at(magic_pos); |
| 52 | jlong m2 = _anchor; |
| 53 | if (oopDesc::equals(m1, _thread->threadObj()) && m2 == address_value()) return true; |
| 54 | return false; |
| 55 | } |
| 56 | |
| 57 | bool BaseFrameStream::cleanup_magic_on_exit(objArrayHandle frames_array) { |
| 58 | bool ok = check_magic(frames_array); |
| 59 | frames_array->obj_at_put(magic_pos, NULL); |
| 60 | _anchor = 0L; |
| 61 | return ok; |
| 62 | } |
| 63 | |
| 64 | JavaFrameStream::JavaFrameStream(JavaThread* thread, int mode) |
| 65 | : BaseFrameStream(thread), _vfst(thread) { |
| 66 | _need_method_info = StackWalk::need_method_info(mode); |
| 67 | } |
| 68 | |
| 69 | void JavaFrameStream::next() { _vfst.next();} |
| 70 | |
| 71 | // Returns the BaseFrameStream for the current stack being traversed. |
| 72 | // |
| 73 | // Parameters: |
| 74 | // thread Current Java thread. |
| 75 | // magic Magic value used for each stack walking |
| 76 | // frames_array User-supplied buffers. The 0th element is reserved |
| 77 | // for this BaseFrameStream to use |
| 78 | // |
| 79 | BaseFrameStream* BaseFrameStream::from_current(JavaThread* thread, jlong magic, |
| 80 | objArrayHandle frames_array) |
| 81 | { |
| 82 | assert(thread != NULL && thread->is_Java_thread(), "" ); |
| 83 | oop m1 = frames_array->obj_at(magic_pos); |
| 84 | if (!oopDesc::equals(m1, thread->threadObj())) return NULL; |
| 85 | if (magic == 0L) return NULL; |
| 86 | BaseFrameStream* stream = (BaseFrameStream*) (intptr_t) magic; |
| 87 | if (!stream->is_valid_in(thread, frames_array)) return NULL; |
| 88 | return stream; |
| 89 | } |
| 90 | |
| 91 | // Unpacks one or more frames into user-supplied buffers. |
| 92 | // Updates the end index, and returns the number of unpacked frames. |
| 93 | // Always start with the existing vfst.method and bci. |
| 94 | // Do not call vfst.next to advance over the last returned value. |
| 95 | // In other words, do not leave any stale data in the vfst. |
| 96 | // |
| 97 | // Parameters: |
| 98 | // mode Restrict which frames to be decoded. |
| 99 | // BaseFrameStream stream of frames |
| 100 | // max_nframes Maximum number of frames to be filled. |
| 101 | // start_index Start index to the user-supplied buffers. |
| 102 | // frames_array Buffer to store Class or StackFrame in, starting at start_index. |
| 103 | // frames array is a Class<?>[] array when only getting caller |
| 104 | // reference, and a StackFrameInfo[] array (or derivative) |
| 105 | // otherwise. It should never be null. |
| 106 | // end_index End index to the user-supplied buffers with unpacked frames. |
| 107 | // |
| 108 | // Returns the number of frames whose information was transferred into the buffers. |
| 109 | // |
| 110 | int StackWalk::fill_in_frames(jlong mode, BaseFrameStream& stream, |
| 111 | int max_nframes, int start_index, |
| 112 | objArrayHandle frames_array, |
| 113 | int& end_index, TRAPS) { |
| 114 | log_debug(stackwalk)("fill_in_frames limit=%d start=%d frames length=%d" , |
| 115 | max_nframes, start_index, frames_array->length()); |
| 116 | assert(max_nframes > 0, "invalid max_nframes" ); |
| 117 | assert(start_index + max_nframes <= frames_array->length(), "oob" ); |
| 118 | |
| 119 | int frames_decoded = 0; |
| 120 | for (; !stream.at_end(); stream.next()) { |
| 121 | Method* method = stream.method(); |
| 122 | |
| 123 | if (method == NULL) continue; |
| 124 | |
| 125 | // skip hidden frames for default StackWalker option (i.e. SHOW_HIDDEN_FRAMES |
| 126 | // not set) and when StackWalker::getCallerClass is called |
| 127 | if (!ShowHiddenFrames && (skip_hidden_frames(mode) || get_caller_class(mode))) { |
| 128 | if (method->is_hidden()) { |
| 129 | LogTarget(Debug, stackwalk) lt; |
| 130 | if (lt.is_enabled()) { |
| 131 | ResourceMark rm(THREAD); |
| 132 | LogStream ls(lt); |
| 133 | ls.print(" hidden method: " ); |
| 134 | method->print_short_name(&ls); |
| 135 | ls.cr(); |
| 136 | } |
| 137 | continue; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | int index = end_index++; |
| 142 | LogTarget(Debug, stackwalk) lt; |
| 143 | if (lt.is_enabled()) { |
| 144 | ResourceMark rm(THREAD); |
| 145 | LogStream ls(lt); |
| 146 | ls.print(" %d: frame method: " , index); |
| 147 | method->print_short_name(&ls); |
| 148 | ls.print_cr(" bci=%d" , stream.bci()); |
| 149 | } |
| 150 | |
| 151 | if (!need_method_info(mode) && get_caller_class(mode) && |
| 152 | index == start_index && method->caller_sensitive()) { |
| 153 | ResourceMark rm(THREAD); |
| 154 | THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), |
| 155 | err_msg("StackWalker::getCallerClass called from @CallerSensitive '%s' method" , |
| 156 | method->external_name())); |
| 157 | } |
| 158 | // fill in StackFrameInfo and initialize MemberName |
| 159 | stream.fill_frame(index, frames_array, method, CHECK_0); |
| 160 | if (++frames_decoded >= max_nframes) break; |
| 161 | } |
| 162 | return frames_decoded; |
| 163 | } |
| 164 | |
| 165 | // Fill in the LiveStackFrameInfo at the given index in frames_array |
| 166 | void LiveFrameStream::fill_frame(int index, objArrayHandle frames_array, |
| 167 | const methodHandle& method, TRAPS) { |
| 168 | HandleMark hm(THREAD); |
| 169 | Handle stackFrame(THREAD, frames_array->obj_at(index)); |
| 170 | fill_live_stackframe(stackFrame, method, CHECK); |
| 171 | } |
| 172 | |
| 173 | // Fill in the StackFrameInfo at the given index in frames_array |
| 174 | void JavaFrameStream::fill_frame(int index, objArrayHandle frames_array, |
| 175 | const methodHandle& method, TRAPS) { |
| 176 | if (_need_method_info) { |
| 177 | HandleMark hm(THREAD); |
| 178 | Handle stackFrame(THREAD, frames_array->obj_at(index)); |
| 179 | fill_stackframe(stackFrame, method, CHECK); |
| 180 | } else { |
| 181 | frames_array->obj_at_put(index, method->method_holder()->java_mirror()); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | // Create and return a LiveStackFrame.PrimitiveSlot (if needed) for the |
| 186 | // StackValue at the given index. 'type' is expected to be T_INT, T_LONG, |
| 187 | // T_OBJECT, or T_CONFLICT. |
| 188 | oop LiveFrameStream::create_primitive_slot_instance(StackValueCollection* values, |
| 189 | int i, BasicType type, TRAPS) { |
| 190 | Klass* k = SystemDictionary::resolve_or_null(vmSymbols::java_lang_LiveStackFrameInfo(), CHECK_NULL); |
| 191 | InstanceKlass* ik = InstanceKlass::cast(k); |
| 192 | |
| 193 | JavaValue result(T_OBJECT); |
| 194 | JavaCallArguments args; |
| 195 | Symbol* signature = NULL; |
| 196 | |
| 197 | // ## TODO: type is only available in LocalVariable table, if present. |
| 198 | // ## StackValue type is T_INT or T_OBJECT (or converted to T_LONG on 64-bit) |
| 199 | switch (type) { |
| 200 | case T_INT: |
| 201 | args.push_int(values->int_at(i)); |
| 202 | signature = vmSymbols::asPrimitive_int_signature(); |
| 203 | break; |
| 204 | |
| 205 | case T_LONG: |
| 206 | args.push_long(values->long_at(i)); |
| 207 | signature = vmSymbols::asPrimitive_long_signature(); |
| 208 | break; |
| 209 | |
| 210 | case T_FLOAT: |
| 211 | case T_DOUBLE: |
| 212 | case T_BYTE: |
| 213 | case T_SHORT: |
| 214 | case T_CHAR: |
| 215 | case T_BOOLEAN: |
| 216 | THROW_MSG_(vmSymbols::java_lang_InternalError(), "Unexpected StackValue type" , NULL); |
| 217 | |
| 218 | case T_OBJECT: |
| 219 | return values->obj_at(i)(); |
| 220 | |
| 221 | case T_CONFLICT: |
| 222 | // put a non-null slot |
| 223 | #ifdef _LP64 |
| 224 | args.push_long(0); |
| 225 | signature = vmSymbols::asPrimitive_long_signature(); |
| 226 | #else |
| 227 | args.push_int(0); |
| 228 | signature = vmSymbols::asPrimitive_int_signature(); |
| 229 | #endif |
| 230 | |
| 231 | break; |
| 232 | |
| 233 | default: ShouldNotReachHere(); |
| 234 | } |
| 235 | JavaCalls::call_static(&result, |
| 236 | ik, |
| 237 | vmSymbols::asPrimitive_name(), |
| 238 | signature, |
| 239 | &args, |
| 240 | CHECK_NULL); |
| 241 | return (instanceOop) result.get_jobject(); |
| 242 | } |
| 243 | |
| 244 | objArrayHandle LiveFrameStream::values_to_object_array(StackValueCollection* values, TRAPS) { |
| 245 | objArrayHandle empty; |
| 246 | int length = values->size(); |
| 247 | objArrayOop array_oop = oopFactory::new_objArray(SystemDictionary::Object_klass(), |
| 248 | length, CHECK_(empty)); |
| 249 | objArrayHandle array_h(THREAD, array_oop); |
| 250 | for (int i = 0; i < values->size(); i++) { |
| 251 | StackValue* st = values->at(i); |
| 252 | BasicType type = st->type(); |
| 253 | int index = i; |
| 254 | #ifdef _LP64 |
| 255 | if (type != T_OBJECT && type != T_CONFLICT) { |
| 256 | intptr_t ret = st->get_int(); // read full 64-bit slot |
| 257 | type = T_LONG; // treat as long |
| 258 | index--; // undo +1 in StackValueCollection::long_at |
| 259 | } |
| 260 | #endif |
| 261 | oop obj = create_primitive_slot_instance(values, index, type, CHECK_(empty)); |
| 262 | if (obj != NULL) { |
| 263 | array_h->obj_at_put(i, obj); |
| 264 | } |
| 265 | } |
| 266 | return array_h; |
| 267 | } |
| 268 | |
| 269 | objArrayHandle LiveFrameStream::monitors_to_object_array(GrowableArray<MonitorInfo*>* monitors, TRAPS) { |
| 270 | int length = monitors->length(); |
| 271 | objArrayOop array_oop = oopFactory::new_objArray(SystemDictionary::Object_klass(), |
| 272 | length, CHECK_(objArrayHandle())); |
| 273 | objArrayHandle array_h(THREAD, array_oop); |
| 274 | for (int i = 0; i < length; i++) { |
| 275 | MonitorInfo* monitor = monitors->at(i); |
| 276 | array_h->obj_at_put(i, monitor->owner()); |
| 277 | } |
| 278 | return array_h; |
| 279 | } |
| 280 | |
| 281 | // Fill StackFrameInfo with bci and initialize memberName |
| 282 | void BaseFrameStream::fill_stackframe(Handle stackFrame, const methodHandle& method, TRAPS) { |
| 283 | java_lang_StackFrameInfo::set_method_and_bci(stackFrame, method, bci(), THREAD); |
| 284 | } |
| 285 | |
| 286 | // Fill LiveStackFrameInfo with locals, monitors, and expressions |
| 287 | void LiveFrameStream::fill_live_stackframe(Handle stackFrame, |
| 288 | const methodHandle& method, TRAPS) { |
| 289 | fill_stackframe(stackFrame, method, CHECK); |
| 290 | if (_jvf != NULL) { |
| 291 | StackValueCollection* locals = _jvf->locals(); |
| 292 | StackValueCollection* expressions = _jvf->expressions(); |
| 293 | GrowableArray<MonitorInfo*>* monitors = _jvf->monitors(); |
| 294 | |
| 295 | int mode = 0; |
| 296 | if (_jvf->is_interpreted_frame()) { |
| 297 | mode = MODE_INTERPRETED; |
| 298 | } else if (_jvf->is_compiled_frame()) { |
| 299 | mode = MODE_COMPILED; |
| 300 | } |
| 301 | |
| 302 | if (!locals->is_empty()) { |
| 303 | objArrayHandle locals_h = values_to_object_array(locals, CHECK); |
| 304 | java_lang_LiveStackFrameInfo::set_locals(stackFrame(), locals_h()); |
| 305 | } |
| 306 | if (!expressions->is_empty()) { |
| 307 | objArrayHandle expressions_h = values_to_object_array(expressions, CHECK); |
| 308 | java_lang_LiveStackFrameInfo::set_operands(stackFrame(), expressions_h()); |
| 309 | } |
| 310 | if (monitors->length() > 0) { |
| 311 | objArrayHandle monitors_h = monitors_to_object_array(monitors, CHECK); |
| 312 | java_lang_LiveStackFrameInfo::set_monitors(stackFrame(), monitors_h()); |
| 313 | } |
| 314 | java_lang_LiveStackFrameInfo::set_mode(stackFrame(), mode); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | // Begins stack walking. |
| 319 | // |
| 320 | // Parameters: |
| 321 | // stackStream StackStream object |
| 322 | // mode Stack walking mode. |
| 323 | // skip_frames Number of frames to be skipped. |
| 324 | // frame_count Number of frames to be traversed. |
| 325 | // start_index Start index to the user-supplied buffers. |
| 326 | // frames_array Buffer to store StackFrame in, starting at start_index. |
| 327 | // frames array is a Class<?>[] array when only getting caller |
| 328 | // reference, and a StackFrameInfo[] array (or derivative) |
| 329 | // otherwise. It should never be null. |
| 330 | // |
| 331 | // Returns Object returned from AbstractStackWalker::doStackWalk call. |
| 332 | // |
| 333 | oop StackWalk::walk(Handle stackStream, jlong mode, |
| 334 | int skip_frames, int frame_count, int start_index, |
| 335 | objArrayHandle frames_array, |
| 336 | TRAPS) { |
| 337 | ResourceMark rm(THREAD); |
| 338 | JavaThread* jt = (JavaThread*)THREAD; |
| 339 | log_debug(stackwalk)("Start walking: mode " JLONG_FORMAT " skip %d frames batch size %d" , |
| 340 | mode, skip_frames, frame_count); |
| 341 | |
| 342 | if (frames_array.is_null()) { |
| 343 | THROW_MSG_(vmSymbols::java_lang_NullPointerException(), "frames_array is NULL" , NULL); |
| 344 | } |
| 345 | |
| 346 | // Setup traversal onto my stack. |
| 347 | if (live_frame_info(mode)) { |
| 348 | assert (use_frames_array(mode), "Bad mode for get live frame" ); |
| 349 | RegisterMap regMap(jt, true); |
| 350 | LiveFrameStream stream(jt, ®Map); |
| 351 | return fetchFirstBatch(stream, stackStream, mode, skip_frames, frame_count, |
| 352 | start_index, frames_array, THREAD); |
| 353 | } else { |
| 354 | JavaFrameStream stream(jt, mode); |
| 355 | return fetchFirstBatch(stream, stackStream, mode, skip_frames, frame_count, |
| 356 | start_index, frames_array, THREAD); |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | oop StackWalk::fetchFirstBatch(BaseFrameStream& stream, Handle stackStream, |
| 361 | jlong mode, int skip_frames, int frame_count, |
| 362 | int start_index, objArrayHandle frames_array, TRAPS) { |
| 363 | methodHandle m_doStackWalk(THREAD, Universe::do_stack_walk_method()); |
| 364 | |
| 365 | { |
| 366 | Klass* stackWalker_klass = SystemDictionary::StackWalker_klass(); |
| 367 | Klass* abstractStackWalker_klass = SystemDictionary::AbstractStackWalker_klass(); |
| 368 | while (!stream.at_end()) { |
| 369 | InstanceKlass* ik = stream.method()->method_holder(); |
| 370 | if (ik != stackWalker_klass && |
| 371 | ik != abstractStackWalker_klass && ik->super() != abstractStackWalker_klass) { |
| 372 | break; |
| 373 | } |
| 374 | |
| 375 | LogTarget(Debug, stackwalk) lt; |
| 376 | if (lt.is_enabled()) { |
| 377 | ResourceMark rm(THREAD); |
| 378 | LogStream ls(lt); |
| 379 | ls.print(" skip " ); |
| 380 | stream.method()->print_short_name(&ls); |
| 381 | ls.cr(); |
| 382 | } |
| 383 | stream.next(); |
| 384 | } |
| 385 | |
| 386 | // stack frame has been traversed individually and resume stack walk |
| 387 | // from the stack frame at depth == skip_frames. |
| 388 | for (int n=0; n < skip_frames && !stream.at_end(); stream.next(), n++) { |
| 389 | LogTarget(Debug, stackwalk) lt; |
| 390 | if (lt.is_enabled()) { |
| 391 | ResourceMark rm(THREAD); |
| 392 | LogStream ls(lt); |
| 393 | ls.print(" skip " ); |
| 394 | stream.method()->print_short_name(&ls); |
| 395 | ls.cr(); |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | int end_index = start_index; |
| 401 | int numFrames = 0; |
| 402 | if (!stream.at_end()) { |
| 403 | numFrames = fill_in_frames(mode, stream, frame_count, start_index, |
| 404 | frames_array, end_index, CHECK_NULL); |
| 405 | if (numFrames < 1) { |
| 406 | THROW_MSG_(vmSymbols::java_lang_InternalError(), "stack walk: decode failed" , NULL); |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | // JVM_CallStackWalk walks the stack and fills in stack frames, then calls to |
| 411 | // Java method java.lang.StackStreamFactory.AbstractStackWalker::doStackWalk |
| 412 | // which calls the implementation to consume the stack frames. |
| 413 | // When JVM_CallStackWalk returns, it invalidates the stack stream. |
| 414 | JavaValue result(T_OBJECT); |
| 415 | JavaCallArguments args(stackStream); |
| 416 | args.push_long(stream.address_value()); |
| 417 | args.push_int(skip_frames); |
| 418 | args.push_int(frame_count); |
| 419 | args.push_int(start_index); |
| 420 | args.push_int(end_index); |
| 421 | |
| 422 | // Link the thread and vframe stream into the callee-visible object |
| 423 | stream.setup_magic_on_entry(frames_array); |
| 424 | |
| 425 | JavaCalls::call(&result, m_doStackWalk, &args, THREAD); |
| 426 | |
| 427 | // Do this before anything else happens, to disable any lingering stream objects |
| 428 | bool ok = stream.cleanup_magic_on_exit(frames_array); |
| 429 | |
| 430 | // Throw pending exception if we must |
| 431 | (void) (CHECK_NULL); |
| 432 | |
| 433 | if (!ok) { |
| 434 | THROW_MSG_(vmSymbols::java_lang_InternalError(), "doStackWalk: corrupted buffers on exit" , NULL); |
| 435 | } |
| 436 | |
| 437 | // Return normally |
| 438 | return (oop)result.get_jobject(); |
| 439 | } |
| 440 | |
| 441 | // Walk the next batch of stack frames |
| 442 | // |
| 443 | // Parameters: |
| 444 | // stackStream StackStream object |
| 445 | // mode Stack walking mode. |
| 446 | // magic Must be valid value to continue the stack walk |
| 447 | // frame_count Number of frames to be decoded. |
| 448 | // start_index Start index to the user-supplied buffers. |
| 449 | // frames_array Buffer to store StackFrame in, starting at start_index. |
| 450 | // |
| 451 | // Returns the end index of frame filled in the buffer. |
| 452 | // |
| 453 | jint StackWalk::fetchNextBatch(Handle stackStream, jlong mode, jlong magic, |
| 454 | int frame_count, int start_index, |
| 455 | objArrayHandle frames_array, |
| 456 | TRAPS) |
| 457 | { |
| 458 | JavaThread* jt = (JavaThread*)THREAD; |
| 459 | BaseFrameStream* existing_stream = BaseFrameStream::from_current(jt, magic, frames_array); |
| 460 | if (existing_stream == NULL) { |
| 461 | THROW_MSG_(vmSymbols::java_lang_InternalError(), "doStackWalk: corrupted buffers" , 0L); |
| 462 | } |
| 463 | |
| 464 | if (frames_array.is_null()) { |
| 465 | THROW_MSG_(vmSymbols::java_lang_NullPointerException(), "frames_array is NULL" , 0L); |
| 466 | } |
| 467 | |
| 468 | log_debug(stackwalk)("StackWalk::fetchNextBatch frame_count %d existing_stream " |
| 469 | PTR_FORMAT " start %d frames %d" , |
| 470 | frame_count, p2i(existing_stream), start_index, frames_array->length()); |
| 471 | int end_index = start_index; |
| 472 | if (frame_count <= 0) { |
| 473 | return end_index; // No operation. |
| 474 | } |
| 475 | |
| 476 | int count = frame_count + start_index; |
| 477 | assert (frames_array->length() >= count, "not enough space in buffers" ); |
| 478 | |
| 479 | BaseFrameStream& stream = (*existing_stream); |
| 480 | if (!stream.at_end()) { |
| 481 | stream.next(); // advance past the last frame decoded in previous batch |
| 482 | if (!stream.at_end()) { |
| 483 | int n = fill_in_frames(mode, stream, frame_count, start_index, |
| 484 | frames_array, end_index, CHECK_0); |
| 485 | if (n < 1) { |
| 486 | THROW_MSG_(vmSymbols::java_lang_InternalError(), "doStackWalk: later decode failed" , 0L); |
| 487 | } |
| 488 | return end_index; |
| 489 | } |
| 490 | } |
| 491 | return end_index; |
| 492 | } |
| 493 | |