Skip to content

Commit d7ae4d2

Browse files
committed
[GR-29152] [GR-16348] Load more classes related to StackOverflowError eagerly
PullRequest: truffleruby/2412
2 parents 96a124a + c11a546 commit d7ae4d2

File tree

9 files changed

+204
-85
lines changed

9 files changed

+204
-85
lines changed

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.truffleruby.core.hash.ReHashable;
4646
import org.truffleruby.core.inlined.CoreMethods;
4747
import org.truffleruby.core.kernel.AtExitManager;
48+
import org.truffleruby.core.kernel.KernelNodes;
4849
import org.truffleruby.core.kernel.TraceManager;
4950
import org.truffleruby.core.module.ModuleOperations;
5051
import org.truffleruby.core.module.RubyModule;
@@ -58,7 +59,6 @@
5859
import org.truffleruby.core.thread.ThreadManager;
5960
import org.truffleruby.core.time.GetTimeZoneNode;
6061
import org.truffleruby.debug.MetricsProfiler;
61-
import org.truffleruby.extra.ffi.Pointer;
6262
import org.truffleruby.interop.InteropManager;
6363
import org.truffleruby.language.CallStackManager;
6464
import org.truffleruby.core.string.ImmutableRubyString;
@@ -235,17 +235,13 @@ public RubyContext(RubyLanguage language, TruffleLanguage.Env env) {
235235
coverageManager = new CoverageManager(this, instrumenter);
236236
Metrics.printTime("after-instruments");
237237

238-
// Initialize RaiseException eagerly so StackOverflowError is correctly handled and does not become
239-
// NoClassDefFoundError: Could not initialize class org.truffleruby.language.control.RaiseException
240-
Pointer.UNSAFE.ensureClassInitialized(RaiseException.class);
241-
242238
Metrics.printTime("after-context-constructor");
243239
}
244240

245241
public void initialize() {
246242
assert !initialized : "Already initialized";
247-
// Load the nodes
248243

244+
// Load the nodes
249245
Metrics.printTime("before-load-nodes");
250246
coreLibrary.loadCoreNodes();
251247
Metrics.printTime("after-load-nodes");
@@ -438,24 +434,36 @@ private TruffleNFIPlatform createNativePlatform() {
438434
}
439435

440436
@TruffleBoundary
441-
public static Object send(Object object, String methodName, Object... arguments) {
437+
public static Object send(Object receiver, String methodName, Object... arguments) {
442438
final InternalMethod method = ModuleOperations
443-
.lookupMethodUncached(MetaClassNode.getUncached().execute(object), methodName, null);
439+
.lookupMethodUncached(MetaClassNode.getUncached().execute(receiver), methodName, null);
444440
if (method == null || method.isUndefined()) {
445-
return null;
441+
final RubyContext context = RubyLanguage.getCurrentContext();
442+
final String message = String.format(
443+
"undefined method `%s' for %s when using RubyContext#send() which ignores #method_missing",
444+
methodName,
445+
KernelNodes.ToSNode.uncachedBasicToS(receiver));
446+
throw new RaiseException(
447+
context,
448+
context.getCoreExceptions().noMethodError(
449+
message,
450+
receiver,
451+
methodName,
452+
arguments,
453+
EncapsulatingNodeReference.getCurrent().get()));
446454
}
447455

448456
return IndirectCallNode.getUncached().call(
449457
method.getCallTarget(),
450-
RubyArguments.pack(null, null, method, null, object, Nil.INSTANCE, arguments));
458+
RubyArguments.pack(null, null, method, null, receiver, Nil.INSTANCE, arguments));
451459
}
452460

453461
@TruffleBoundary
454-
public static Object send(Node currentNode, Object object, String methodName, Object... arguments) {
462+
public static Object send(Node currentNode, Object receiver, String methodName, Object... arguments) {
455463
final EncapsulatingNodeReference callNodeRef = EncapsulatingNodeReference.getCurrent();
456464
final Node prev = callNodeRef.set(currentNode);
457465
try {
458-
return send(object, methodName, arguments);
466+
return send(receiver, methodName, arguments);
459467
} finally {
460468
callNodeRef.set(prev);
461469
}

src/main/java/org/truffleruby/core/VMPrimitiveNodes.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.io.PrintStream;
4141
import java.util.Map.Entry;
4242

43+
import com.oracle.truffle.api.TruffleStackTrace;
4344
import com.oracle.truffle.api.library.CachedLibrary;
4445
import org.jcodings.specific.ASCIIEncoding;
4546
import org.jcodings.specific.UTF8Encoding;
@@ -520,6 +521,25 @@ protected long endHash(long hash) {
520521

521522
}
522523

524+
/** Initialize RaiseException, StackOverflowError and related classes eagerly, so StackOverflowError is correctly
525+
* handled and does not become, e.g., NoClassDefFoundError: Could not initialize class SomeExceptionRelatedClass */
526+
@Primitive(name = "vm_stack_overflow_error_to_init_classes")
527+
public abstract static class InitStackOverflowClassesEagerlyNode extends PrimitiveArrayArgumentsNode {
528+
529+
private static final String MESSAGE = "initStackOverflowClassesEagerly";
530+
531+
public static boolean ignore(StackOverflowError e) {
532+
return e.getMessage() == MESSAGE;
533+
}
534+
535+
@Specialization
536+
protected Object initStackOverflowClassesEagerly() {
537+
final StackOverflowError stackOverflowError = new StackOverflowError("initStackOverflowClassesEagerly");
538+
TruffleStackTrace.fillIn(stackOverflowError);
539+
throw stackOverflowError;
540+
}
541+
}
542+
523543
@Primitive(name = "should_not_reach_here")
524544
public abstract static class ShouldNotReachHereNode extends PrimitiveArrayArgumentsNode {
525545

src/main/java/org/truffleruby/core/basicobject/BasicObjectNodes.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.truffleruby.core.basicobject.BasicObjectNodesFactory.ReferenceEqualNodeFactory;
2626
import org.truffleruby.core.cast.BooleanCastNode;
2727
import org.truffleruby.core.cast.NameToJavaStringNode;
28-
import org.truffleruby.core.exception.ExceptionOperations;
28+
import org.truffleruby.core.exception.ExceptionOperations.ExceptionFormatter;
2929
import org.truffleruby.core.exception.RubyException;
3030
import org.truffleruby.core.inlined.AlwaysInlinedMethodNode;
3131
import org.truffleruby.core.inlined.InlinedMethodNode;
@@ -512,8 +512,7 @@ private FrameAndCallNode(Frame frame, Node callNode) {
512512
}
513513

514514
@TruffleBoundary
515-
private RubyException buildMethodMissingException(Object self, Object nameObject, Object[] args,
516-
Object block) {
515+
private RubyException buildMethodMissingException(Object self, Object nameObject, Object[] args, Object block) {
517516
final String name;
518517
if (nameObject instanceof RubySymbol) {
519518
name = ((RubySymbol) nameObject).getString();
@@ -523,30 +522,37 @@ private RubyException buildMethodMissingException(Object self, Object nameObject
523522
final FrameAndCallNode relevantCallerFrame = getRelevantCallerFrame();
524523
Visibility visibility;
525524

526-
final RubyProc formatter;
527525
if (lastCallWasSuper(relevantCallerFrame)) {
528-
formatter = ExceptionOperations.getFormatter(ExceptionOperations.SUPER_METHOD_ERROR, getContext());
529-
return coreExceptions().noMethodErrorFromMethodMissing(formatter, self, name, args, this);
526+
return coreExceptions()
527+
.noMethodErrorFromMethodMissing(ExceptionFormatter.SUPER_METHOD_ERROR, self, name, args, this);
530528
} else if ((visibility = lastCallWasCallingPrivateOrProtectedMethod(
531529
self,
532530
name,
533531
relevantCallerFrame)) != null) {
534532
if (visibility == Visibility.PRIVATE) {
535-
formatter = ExceptionOperations
536-
.getFormatter(ExceptionOperations.PRIVATE_METHOD_ERROR, getContext());
537-
return coreExceptions().noMethodErrorFromMethodMissing(formatter, self, name, args, this);
533+
return coreExceptions().noMethodErrorFromMethodMissing(
534+
ExceptionFormatter.PRIVATE_METHOD_ERROR,
535+
self,
536+
name,
537+
args,
538+
this);
538539
} else {
539-
formatter = ExceptionOperations
540-
.getFormatter(ExceptionOperations.PROTECTED_METHOD_ERROR, getContext());
541-
return coreExceptions().noMethodErrorFromMethodMissing(formatter, self, name, args, this);
540+
return coreExceptions().noMethodErrorFromMethodMissing(
541+
ExceptionFormatter.PROTECTED_METHOD_ERROR,
542+
self,
543+
name,
544+
args,
545+
this);
542546
}
543547
} else if (lastCallWasVCall(relevantCallerFrame)) {
544-
formatter = ExceptionOperations
545-
.getFormatter(ExceptionOperations.NO_LOCAL_VARIABLE_OR_METHOD_ERROR, getContext());
546-
return coreExceptions().nameErrorFromMethodMissing(formatter, self, name, this);
548+
return coreExceptions().nameErrorFromMethodMissing(
549+
ExceptionFormatter.NO_LOCAL_VARIABLE_OR_METHOD_ERROR,
550+
self,
551+
name,
552+
this);
547553
} else {
548-
formatter = ExceptionOperations.getFormatter(ExceptionOperations.NO_METHOD_ERROR, getContext());
549-
return coreExceptions().noMethodErrorFromMethodMissing(formatter, self, name, args, this);
554+
return coreExceptions()
555+
.noMethodErrorFromMethodMissing(ExceptionFormatter.NO_METHOD_ERROR, self, name, args, this);
550556
}
551557
}
552558

src/main/java/org/truffleruby/core/exception/CoreExceptions.java

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static org.truffleruby.core.array.ArrayHelpers.createArray;
1313

1414
import java.io.IOException;
15+
import java.io.PrintStream;
1516
import java.util.EnumSet;
1617

1718
import com.oracle.truffle.api.interop.InvalidBufferOffsetException;
@@ -22,6 +23,7 @@
2223
import org.truffleruby.core.array.RubyArray;
2324
import org.truffleruby.core.binding.RubyBinding;
2425
import org.truffleruby.core.encoding.RubyEncoding;
26+
import org.truffleruby.core.exception.ExceptionOperations.ExceptionFormatter;
2527
import org.truffleruby.core.klass.RubyClass;
2628
import org.truffleruby.core.module.ModuleOperations;
2729
import org.truffleruby.core.module.RubyModule;
@@ -90,11 +92,16 @@ public void showExceptionIfDebug(RubyClass rubyClass, Object message, Backtrace
9092
if (backtrace != null && backtrace.getStackTrace().length > 0) {
9193
from = " at " + debugBacktraceFormatter.formatLine(backtrace.getStackTrace(), 0, null);
9294
}
93-
Object stderr = context.getCoreLibrary().getStderr();
94-
String output = "Exception `" + exceptionClass + "'" + from + " - " + message + "\n";
95-
RubyString outputString = StringOperations
96-
.createString(context, language, StringOperations.encodeRope(output, UTF8Encoding.INSTANCE));
97-
RubyContext.send(stderr, "write", outputString);
95+
final String output = "Exception `" + exceptionClass + "'" + from + " - " + message + "\n";
96+
if (context.getCoreLibrary().isLoaded()) {
97+
RubyString outputString = StringOperations
98+
.createString(context, language, StringOperations.encodeRope(output, UTF8Encoding.INSTANCE));
99+
Object stderr = context.getCoreLibrary().getStderr();
100+
RubyContext.send(stderr, "write", outputString);
101+
} else {
102+
final PrintStream printStream = BacktraceFormatter.printStreamFor(context.getEnv().err());
103+
printStream.println(output);
104+
}
98105
}
99106
}
100107

@@ -316,20 +323,17 @@ public RubyException runtimeError(String message, Backtrace backtrace) {
316323
// SystemStackError
317324

318325
@TruffleBoundary
319-
public RubyException systemStackErrorStackLevelTooDeep(Node currentNode, StackOverflowError javaThrowable) {
320-
RubyClass exceptionClass = context.getCoreLibrary().systemStackErrorClass;
321-
StackTraceElement[] stackTrace = javaThrowable.getStackTrace();
322-
String topOfTheStack = stackTrace.length > 0
326+
public RubyException systemStackErrorStackLevelTooDeep(Node currentNode, StackOverflowError javaThrowable,
327+
boolean showExceptionIfDebug) {
328+
final StackTraceElement[] stackTrace = javaThrowable.getStackTrace();
329+
final String topOfTheStack = stackTrace.length > 0
323330
? BacktraceFormatter.formatJava(stackTrace[0])
324331
: "<empty Java stacktrace>";
325332
final String message = coreStrings().STACK_LEVEL_TOO_DEEP + "\n\tfrom " + topOfTheStack;
326-
return ExceptionOperations.createRubyException(
327-
context,
328-
exceptionClass,
329-
StringOperations
330-
.createString(context, language, StringOperations.encodeRope(message, UTF8Encoding.INSTANCE)),
331-
currentNode,
332-
javaThrowable);
333+
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode, 0, javaThrowable);
334+
final RubyString messageString = StringOperations
335+
.createString(context, language, StringOperations.encodeRope(message, UTF8Encoding.INSTANCE));
336+
return ExceptionOperations.createSystemStackError(context, messageString, backtrace, showExceptionIfDebug);
333337
}
334338

335339
// NoMemoryError
@@ -794,65 +798,75 @@ public RubyNameError nameError(String message, Object receiver, String name, Nod
794798
language.getSymbol(name));
795799
}
796800

797-
public RubyNameError nameErrorFromMethodMissing(RubyProc formatter, Object receiver, String name,
801+
@TruffleBoundary
802+
public RubyNameError nameErrorFromMethodMissing(ExceptionFormatter formatter, Object receiver, String name,
798803
Node currentNode) {
799804
// omit = 1 to skip over the call to `method_missing'. MRI does not show this is the backtrace.
800805
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode, 1);
801806
final Object cause = ThreadGetExceptionNode.getLastException(context);
807+
808+
final RubyProc formatterProc = formatter.getProc(context);
809+
final String message = formatter.getMessage(formatterProc, name, receiver);
810+
802811
final RubyNameError exception = new RubyNameError(
803812
context.getCoreLibrary().nameErrorClass,
804813
language.nameErrorShape,
805-
null,
814+
message,
806815
backtrace,
807816
cause,
808817
receiver,
809818
language.getSymbol(name));
810-
exception.formatter = formatter;
819+
exception.formatter = formatterProc;
811820
showExceptionIfDebug(exception, backtrace);
812821
return exception;
813822
}
814823

815824
// NoMethodError
816825

817-
public RubyNoMethodError noMethodError(String message, Object receiver, String name, Object[] args,
818-
Node currentNode) {
819-
final RubyString messageString = StringOperations
820-
.createString(context, language, StringOperations.encodeRope(message, UTF8Encoding.INSTANCE));
826+
@TruffleBoundary
827+
public RubyNoMethodError noMethodErrorFromMethodMissing(ExceptionFormatter formatter, Object receiver, String name,
828+
Object[] args, Node currentNode) {
821829
final RubyArray argsArray = createArray(context, language, args);
822-
final RubyClass exceptionClass = context.getCoreLibrary().noMethodErrorClass;
823-
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode);
830+
831+
// omit = 1 to skip over the call to `method_missing'. MRI does not show this is the backtrace.
832+
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode, 1);
824833
final Object cause = ThreadGetExceptionNode.getLastException(context);
825-
showExceptionIfDebug(exceptionClass, messageString, backtrace);
826-
return new RubyNoMethodError(
834+
835+
final RubyProc formatterProc = formatter.getProc(context);
836+
final String message = formatter.getMessage(formatterProc, name, receiver);
837+
838+
final RubyNoMethodError exception = new RubyNoMethodError(
827839
context.getCoreLibrary().noMethodErrorClass,
828840
language.noMethodErrorShape,
829-
messageString,
841+
message,
830842
backtrace,
831843
cause,
832844
receiver,
833845
language.getSymbol(name),
834846
argsArray);
847+
exception.formatter = formatterProc;
848+
showExceptionIfDebug(exception, backtrace);
849+
return exception;
835850
}
836851

837-
public RubyNoMethodError noMethodErrorFromMethodMissing(RubyProc formatter, Object receiver, String name,
838-
Object[] args, Node currentNode) {
852+
public RubyNoMethodError noMethodError(String message, Object receiver, String name, Object[] args,
853+
Node currentNode) {
854+
final RubyString messageString = StringOperations
855+
.createString(context, language, StringOperations.encodeRope(message, UTF8Encoding.INSTANCE));
839856
final RubyArray argsArray = createArray(context, language, args);
840-
841-
// omit = 1 to skip over the call to `method_missing'. MRI does not show this is the backtrace.
842-
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode, 1);
857+
final RubyClass exceptionClass = context.getCoreLibrary().noMethodErrorClass;
858+
final Backtrace backtrace = context.getCallStack().getBacktrace(currentNode);
843859
final Object cause = ThreadGetExceptionNode.getLastException(context);
844-
final RubyNoMethodError exception = new RubyNoMethodError(
860+
showExceptionIfDebug(exceptionClass, messageString, backtrace);
861+
return new RubyNoMethodError(
845862
context.getCoreLibrary().noMethodErrorClass,
846863
language.noMethodErrorShape,
847-
null,
864+
messageString,
848865
backtrace,
849866
cause,
850867
receiver,
851868
language.getSymbol(name),
852869
argsArray);
853-
exception.formatter = formatter;
854-
showExceptionIfDebug(exception, backtrace);
855-
return exception;
856870
}
857871

858872
@TruffleBoundary

0 commit comments

Comments
 (0)