172172import java .io .InputStream ;
173173import java .io .PrintStream ;
174174import java .io .PrintWriter ;
175+ import java .io .Writer ;
175176import java .lang .invoke .MethodHandle ;
176177import java .lang .ref .WeakReference ;
177178import java .net .BindException ;
179+ import java .nio .ByteBuffer ;
180+ import java .nio .channels .Channels ;
181+ import java .nio .channels .WritableByteChannel ;
178182import java .nio .charset .Charset ;
183+ import java .nio .charset .StandardCharsets ;
179184import java .nio .charset .UnsupportedCharsetException ;
180185import java .security .SecureRandom ;
181186import java .util .ArrayList ;
@@ -2848,6 +2853,25 @@ WarnCallback getRegexpWarnings() {
28482853 return regexpWarnings ;
28492854 }
28502855
2856+ public IRubyObject getStderr () {
2857+ return getGlobalVariables ().get ("$stderr" );
2858+ }
2859+
2860+ /**
2861+ * Return the original stderr with which this runtime was initialized.
2862+ *
2863+ * Used for fast-path comparisons when printing error info directly to stderr.
2864+ *
2865+ * @return the original stderr with which this runtime was initialized
2866+ */
2867+ public IRubyObject getOriginalStderr () {
2868+ return originalStderr ;
2869+ }
2870+
2871+ void setOriginalStderr (IRubyObject stderr ) {
2872+ this .originalStderr = stderr ;
2873+ }
2874+
28512875 public PrintStream getErrorStream () {
28522876 // FIXME: We can't guarantee this will always be a RubyIO...so the old code here is not safe
28532877 /*java.io.OutputStream os = ((RubyIO) getGlobalVariables().getService("$stderr")).getOutStream();
@@ -2916,36 +2940,41 @@ private static boolean isJavaPackageOrJavaClassProxyType(final RubyModule type)
29162940 return type instanceof JavaPackage || ClassUtils .isJavaClassProxyType (type );
29172941 }
29182942
2919- /** Prints an error with backtrace to the error stream.
2943+ /**
2944+ * Prints a Ruby exception with backtrace to the configured stderr stream.
29202945 *
29212946 * MRI: eval.c - error_print()
29222947 *
29232948 */
29242949 public void printError (final RubyException ex ) {
29252950 if (ex == null ) return ;
29262951
2927- PrintStream errorStream = getErrorStream ();
2928- String backtrace = config . getTraceType (). printBacktrace ( ex , ( errorStream == System . err ) && getPosix (). isatty ( FileDescriptor . err ));
2929- try {
2930- errorStream . print ( backtrace );
2931- } catch ( Exception e ) {
2932- System . err . print ( backtrace );
2933- }
2952+ boolean formatted =
2953+ getStderr () == getOriginalStderr ( ) &&
2954+ getErr () == System . err &&
2955+ getPosix (). isatty ( FileDescriptor . err );
2956+
2957+ String backtrace = config . getTraceType (). printBacktrace ( ex , formatted );
2958+ printErrorString ( backtrace );
29342959 }
29352960
2961+ /**
2962+ * Prints an exception to System.err.
2963+ *
2964+ * @param ex
2965+ */
29362966 public void printError (final Throwable ex ) {
29372967 if (ex instanceof RaiseException ) {
29382968 printError (((RaiseException ) ex ).getException ());
29392969 return ;
29402970 }
29412971
29422972 ByteArrayOutputStream baos = new ByteArrayOutputStream ();
2943- PrintStream errorStream = getErrorStream ();
29442973
29452974 ex .printStackTrace (new PrintStream (baos ));
29462975
29472976 try {
2948- errorStream . write (baos .toByteArray ());
2977+ printErrorString (baos .toByteArray ());
29492978 } catch (Exception e ) {
29502979 try {
29512980 System .err .write (baos .toByteArray ());
@@ -2956,6 +2985,50 @@ public void printError(final Throwable ex) {
29562985 }
29572986 }
29582987
2988+ /**
2989+ * Prints a string directly to the stderr channel, if default, or via dynamic dispatch otherwise.
2990+ *
2991+ * @param msg the string to print
2992+ */
2993+ public void printErrorString (String msg ) {
2994+ IRubyObject stderr = getStderr ();
2995+
2996+ WritableByteChannel writeChannel ;
2997+ if (stderr == getOriginalStderr () &&
2998+ (writeChannel = ((RubyIO ) stderr ).getOpenFile ().fd ().chWrite ) != null ) {
2999+ Writer writer = Channels .newWriter (writeChannel , "UTF-8" );
3000+ try {
3001+ writer .write (msg );
3002+ writer .flush ();
3003+ } catch (IOException ioe ) {
3004+ // ignore as in CRuby
3005+ }
3006+ } else {
3007+ getErrorStream ().print (msg );
3008+ }
3009+ }
3010+
3011+ /**
3012+ * Prints a string directly to the stderr channel, if default, or via dynamic dispatch otherwise.
3013+ *
3014+ * @param msg the string to print
3015+ */
3016+ public void printErrorString (byte [] msg ) {
3017+ IRubyObject stderr = getGlobalVariables ().get ("$stderr" );
3018+
3019+ try {
3020+ WritableByteChannel writeChannel ;
3021+ if (stderr == getOriginalStderr () &&
3022+ (writeChannel = ((RubyIO ) stderr ).getOpenFile ().fd ().chWrite ) != null ) {
3023+ writeChannel .write (ByteBuffer .wrap (msg ));
3024+ } else {
3025+ getErrorStream ().write (msg );
3026+ }
3027+ } catch (IOException ioe ) {
3028+ // ignore as in CRuby
3029+ }
3030+ }
3031+
29593032 static final String ROOT_FRAME_NAME = "(root)" ;
29603033 static long yarpTime = 0 ;
29613034 static boolean loaded = false ;
@@ -5724,6 +5797,8 @@ public void warn(String message) {
57245797 private final EnumMap <DefinedMessage , RubyString > definedMessages = new EnumMap <>(DefinedMessage .class );
57255798 private final EnumMap <RubyThread .Status , RubyString > threadStatuses = new EnumMap <>(RubyThread .Status .class );
57265799
5800+ private IRubyObject originalStderr ;
5801+
57275802 public interface ObjectSpacer {
57285803 void addToObjectSpace (Ruby runtime , boolean useObjectSpace , IRubyObject object );
57295804 }
0 commit comments