|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. |
| 2 | + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
4 | 4 | *
|
5 | 5 | * The Universal Permissive License (UPL), Version 1.0
|
|
71 | 71 | import org.junit.Assert;
|
72 | 72 | import org.junit.Test;
|
73 | 73 |
|
| 74 | +import com.oracle.truffle.api.CallTarget; |
74 | 75 | import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
| 76 | +import com.oracle.truffle.api.RootCallTarget; |
| 77 | +import com.oracle.truffle.api.Truffle; |
| 78 | +import com.oracle.truffle.api.TruffleLanguage; |
75 | 79 | import com.oracle.truffle.api.debug.Breakpoint;
|
76 | 80 | import com.oracle.truffle.api.debug.Debugger;
|
77 | 81 | import com.oracle.truffle.api.debug.DebuggerSession;
|
78 | 82 | import com.oracle.truffle.api.debug.SuspendedCallback;
|
79 | 83 | import com.oracle.truffle.api.debug.SuspendedEvent;
|
80 | 84 | import com.oracle.truffle.api.debug.SuspensionFilter;
|
| 85 | +import com.oracle.truffle.api.frame.FrameDescriptor; |
| 86 | +import com.oracle.truffle.api.frame.FrameSlotKind; |
81 | 87 | import com.oracle.truffle.api.frame.VirtualFrame;
|
82 | 88 | import com.oracle.truffle.api.instrumentation.EventContext;
|
83 | 89 | import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
|
| 90 | +import com.oracle.truffle.api.instrumentation.InstrumentableNode; |
| 91 | +import com.oracle.truffle.api.instrumentation.ProbeNode; |
84 | 92 | import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
|
85 | 93 | import com.oracle.truffle.api.instrumentation.StandardTags;
|
86 | 94 | import com.oracle.truffle.api.instrumentation.TruffleInstrument;
|
87 | 95 | import com.oracle.truffle.api.instrumentation.test.InstrumentationTestLanguage;
|
88 | 96 | import com.oracle.truffle.api.nodes.Node;
|
| 97 | +import com.oracle.truffle.api.nodes.RootNode; |
89 | 98 | import com.oracle.truffle.api.source.SourceSection;
|
90 | 99 | import com.oracle.truffle.api.test.GCUtils;
|
91 | 100 | import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
|
@@ -311,6 +320,149 @@ public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwa
|
311 | 320 | }
|
312 | 321 | }
|
313 | 322 |
|
| 323 | + @Test |
| 324 | + public void testSuspendHereFromLanguage() { |
| 325 | + String text = "suspendHereSource"; |
| 326 | + Source source = Source.create(ProxyLanguage.ID, text); |
| 327 | + try (DebuggerSession session = startSession()) { |
| 328 | + ProxyLanguage.setDelegate(new SuspendDebuggerFromLanguage(session, false)); |
| 329 | + |
| 330 | + startEval(source); |
| 331 | + expectSuspended((SuspendedEvent event) -> { |
| 332 | + Assert.assertEquals(text, event.getSourceSection().getCharacters().toString()); |
| 333 | + }); |
| 334 | + expectDone(); |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + @Test |
| 339 | + public void testSuspendHereFromLanguageInternalSource() { |
| 340 | + String text = "suspendHereSource"; |
| 341 | + Source source = Source.newBuilder(ProxyLanguage.ID, text, "name").internal(true).buildLiteral(); |
| 342 | + try (DebuggerSession session = startSession()) { |
| 343 | + ProxyLanguage.setDelegate(new SuspendDebuggerFromLanguage(session, false)); |
| 344 | + |
| 345 | + startEval(source); |
| 346 | + expectSuspended((SuspendedEvent event) -> { |
| 347 | + Assert.assertEquals(text, event.getSourceSection().getCharacters().toString()); |
| 348 | + Assert.assertTrue(event.getSourceSection().getSource().isInternal()); |
| 349 | + }); |
| 350 | + expectDone(); |
| 351 | + } |
| 352 | + } |
| 353 | + |
| 354 | + @Test |
| 355 | + public void testSuspendHereFromLanguageInternalRoot() { |
| 356 | + String text = "suspendHereSource"; |
| 357 | + Source source = Source.newBuilder(ProxyLanguage.ID, text, "name").buildLiteral(); |
| 358 | + try (DebuggerSession session = startSession()) { |
| 359 | + ProxyLanguage.setDelegate(new SuspendDebuggerFromLanguage(session, true)); |
| 360 | + |
| 361 | + startEval(source); |
| 362 | + expectSuspended((SuspendedEvent event) -> { |
| 363 | + Assert.assertEquals(text, event.getSourceSection().getCharacters().toString()); |
| 364 | + Assert.assertFalse(event.getSourceSection().getSource().isInternal()); |
| 365 | + Assert.assertTrue(Truffle.getRuntime().iterateFrames((frameInstance) -> { |
| 366 | + RootNode root = ((RootCallTarget) frameInstance.getCallTarget()).getRootNode(); |
| 367 | + return root.isInternal(); |
| 368 | + })); |
| 369 | + }); |
| 370 | + expectDone(); |
| 371 | + } |
| 372 | + } |
| 373 | + |
| 374 | + public static class SuspendDebuggerFromLanguage extends ProxyLanguage { |
| 375 | + |
| 376 | + private final DebuggerSession session; |
| 377 | + private final boolean forceRootInternal; |
| 378 | + |
| 379 | + SuspendDebuggerFromLanguage(DebuggerSession session, boolean forceRootInternal) { |
| 380 | + this.session = session; |
| 381 | + this.forceRootInternal = forceRootInternal; |
| 382 | + } |
| 383 | + |
| 384 | + @Override |
| 385 | + protected CallTarget parse(ParsingRequest request) throws Exception { |
| 386 | + return new SuspendDebuggerRoot(languageInstance, session, request.getSource(), forceRootInternal).getCallTarget(); |
| 387 | + } |
| 388 | + |
| 389 | + static class SuspendDebuggerRoot extends RootNode { |
| 390 | + |
| 391 | + private final DebuggerSession session; |
| 392 | + private final boolean forceRootInternal; |
| 393 | + @Node.Child private SuspendDebugNode statement; |
| 394 | + |
| 395 | + SuspendDebuggerRoot(TruffleLanguage<?> language, DebuggerSession session, com.oracle.truffle.api.source.Source source, boolean forceRootInternal) { |
| 396 | + super(language, createFrameDescriptor()); |
| 397 | + this.session = session; |
| 398 | + this.forceRootInternal = forceRootInternal; |
| 399 | + this.statement = new SuspendDebugNode(source.createSection(1)); |
| 400 | + } |
| 401 | + |
| 402 | + private static FrameDescriptor createFrameDescriptor() { |
| 403 | + FrameDescriptor.Builder fdb = FrameDescriptor.newBuilder(); |
| 404 | + fdb.addSlot(FrameSlotKind.Object, "session", null); |
| 405 | + return fdb.build(); |
| 406 | + } |
| 407 | + |
| 408 | + @Override |
| 409 | + public Object execute(VirtualFrame frame) { |
| 410 | + frame.setObject(0, session); |
| 411 | + return statement.execute(frame); |
| 412 | + } |
| 413 | + |
| 414 | + @Override |
| 415 | + public SourceSection getSourceSection() { |
| 416 | + return statement.getSourceSection(); |
| 417 | + } |
| 418 | + |
| 419 | + @Override |
| 420 | + public boolean isInternal() { |
| 421 | + if (!forceRootInternal) { |
| 422 | + return super.isInternal(); |
| 423 | + } else { |
| 424 | + return true; |
| 425 | + } |
| 426 | + } |
| 427 | + |
| 428 | + } |
| 429 | + |
| 430 | + static class SuspendDebugNode extends Node implements InstrumentableNode { |
| 431 | + |
| 432 | + private final SourceSection section; |
| 433 | + |
| 434 | + SuspendDebugNode(SourceSection section) { |
| 435 | + this.section = section; |
| 436 | + } |
| 437 | + |
| 438 | + @Override |
| 439 | + public SourceSection getSourceSection() { |
| 440 | + return section; |
| 441 | + } |
| 442 | + |
| 443 | + public Object execute(VirtualFrame frame) { |
| 444 | + DebuggerSession session = (DebuggerSession) frame.getObject(0); |
| 445 | + suspendHere(session); |
| 446 | + return 1; |
| 447 | + } |
| 448 | + |
| 449 | + @TruffleBoundary |
| 450 | + private void suspendHere(DebuggerSession session) { |
| 451 | + session.suspendHere(this); |
| 452 | + } |
| 453 | + |
| 454 | + @Override |
| 455 | + public boolean isInstrumentable() { |
| 456 | + return true; |
| 457 | + } |
| 458 | + |
| 459 | + @Override |
| 460 | + public WrapperNode createWrapper(ProbeNode probe) { |
| 461 | + throw new UnsupportedOperationException(); |
| 462 | + } |
| 463 | + } |
| 464 | + } |
| 465 | + |
314 | 466 | @Test
|
315 | 467 | public void testSuspendThread1() {
|
316 | 468 | Source testSource = testSource("ROOT(\n" +
|
|
0 commit comments