Fix: avoid gsub (frame dependent) usage from Java

`RubyString#gsub` requires a (Ruby) frame to be present.
The method attempts to set a backref for the current caller's frame.
When the frame stack is empty there isn't really a place to set $~.

This can happen when a LogStash::Util::Loggable#logger is retrieved,
from the input worker thread while not being nested in any block.

Fixes #11874
This commit is contained in:
Karol Bucek 2020-05-06 18:11:30 +02:00
parent 1cc1ce390a
commit ecdf8715e7
3 changed files with 56 additions and 38 deletions

View file

@ -40,12 +40,21 @@ public class DeprecationLoggerExt extends RubyObject {
super(runtime, metaClass);
}
DeprecationLoggerExt(final Ruby runtime, final RubyClass metaClass, final String loggerName) {
super(runtime, metaClass);
initialize(loggerName);
}
@JRubyMethod
public DeprecationLoggerExt initialize(final ThreadContext context, final IRubyObject loggerName) {
logger = new DefaultDeprecationLogger(loggerName.asJavaString());
initialize(loggerName.asJavaString());
return this;
}
private void initialize(final String loggerName) {
logger = new DefaultDeprecationLogger(loggerName);
}
@JRubyMethod(name = "deprecated", required = 1, optional = 1)
public IRubyObject rubyDeprecated(final ThreadContext context, final IRubyObject[] args) {
if (args.length > 1) {

View file

@ -22,16 +22,16 @@ package org.logstash.log;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.InstanceVariables;
import org.logstash.RubyUtil;
import static org.logstash.RubyUtil.RUBY;
import java.util.Locale;
import static org.logstash.log.SlowLoggerExt.toLong;
@JRubyModule(name = "Loggable")
public final class LoggableExt {
@ -64,24 +64,18 @@ public final class LoggableExt {
return self.getSingletonClass().callMethod(context, "deprecation_logger");
}
private static RubyString log4jName(final ThreadContext context, final RubyModule self) {
IRubyObject name = self.name(context);
if (name.isNil()) {
final RubyClass clazz;
private static String log4jName(final RubyModule self) {
String name;
if (self.getBaseName() == null) { // anonymous module/class
RubyModule real = self;
if (self instanceof RubyClass) {
clazz = ((RubyClass) self).getRealClass();
} else {
clazz = self.getMetaClass();
}
name = clazz.name(context);
if (name.isNil()) {
name = clazz.to_s();
real = ((RubyClass) self).getRealClass();
}
name = real.getName(); // for anonymous: "#<Class:0xcafebabe>"
} else {
name = self.getName();
}
return ((RubyString) ((RubyString) name).gsub(
context, RUBY.newString("::"), RUBY.newString("."),
Block.NULL_BLOCK
)).downcase(context);
return name.replace("::", ".").toLowerCase(Locale.ENGLISH);
}
/**
@ -105,9 +99,8 @@ public final class LoggableExt {
}
IRubyObject logger = instanceVariables.getInstanceVariable("logger");
if (logger == null || logger.isNil()) {
logger = RubyUtil.LOGGER.callMethod(context, "new",
LoggableExt.log4jName(context, (RubyModule) self)
);
final String loggerName = log4jName((RubyModule) self);
logger = RubyUtil.LOGGER.callMethod(context, "new", context.runtime.newString(loggerName));
instanceVariables.setInstanceVariable("logger", logger);
}
return logger;
@ -117,18 +110,15 @@ public final class LoggableExt {
public static SlowLoggerExt slowLogger(final ThreadContext context,
final IRubyObject self, final IRubyObject[] args) {
final InstanceVariables instanceVariables = self.getInstanceVariables();
SlowLoggerExt logger =
(SlowLoggerExt) instanceVariables.getInstanceVariable("slow_logger");
IRubyObject logger = instanceVariables.getInstanceVariable("slow_logger");
if (logger == null || logger.isNil()) {
logger = new SlowLoggerExt(context.runtime, RubyUtil.SLOW_LOGGER).initialize(
context, new IRubyObject[]{
LoggableExt.log4jName(context, (RubyModule) self), args[0], args[1],
args[2], args[3]
}
final String loggerName = log4jName((RubyModule) self);
logger = new SlowLoggerExt(context.runtime, RubyUtil.SLOW_LOGGER, loggerName,
toLong(args[0]), toLong(args[1]), toLong(args[2]), toLong(args[3])
);
instanceVariables.setInstanceVariable("slow_logger", logger);
}
return logger;
return (SlowLoggerExt) logger;
}
@JRubyMethod(name = "deprecation_logger", meta = true)
@ -141,8 +131,8 @@ public final class LoggableExt {
}
IRubyObject logger = instanceVariables.getInstanceVariable("deprecation_logger");
if (logger == null || logger.isNil()) {
logger = new DeprecationLoggerExt(context.runtime, RubyUtil.DEPRECATION_LOGGER)
.initialize(context, LoggableExt.log4jName(context, (RubyModule) self));
final String loggerName = log4jName((RubyModule) self);
logger = new DeprecationLoggerExt(context.runtime, RubyUtil.DEPRECATION_LOGGER, loggerName);
instanceVariables.setInstanceVariable("deprecation_logger", logger);
}
return logger;

View file

@ -55,17 +55,36 @@ public class SlowLoggerExt extends RubyObject {
super(runtime, metaClass);
}
SlowLoggerExt(final Ruby runtime, final RubyClass metaClass, final String loggerName,
final long warnThreshold, final long infoThreshold,
final long debugThreshold, final long traceThreshold) {
super(runtime, metaClass);
initialize(loggerName, warnThreshold, infoThreshold, debugThreshold, traceThreshold);
}
@JRubyMethod(required = 5)
public SlowLoggerExt initialize(final ThreadContext context, final IRubyObject[] args) {
String loggerName = args[0].asJavaString();
slowLogger = LogManager.getLogger("slowlog." + loggerName);
warnThreshold = ((RubyNumeric) args[1]).getLongValue();
infoThreshold = ((RubyNumeric) args[2]).getLongValue();
debugThreshold = ((RubyNumeric) args[3]).getLongValue();
traceThreshold = ((RubyNumeric) args[4]).getLongValue();
initialize(args[0].asJavaString(), toLong(args[1]), toLong(args[2]), toLong(args[3]), toLong(args[4]));
return this;
}
private void initialize(final String loggerName,
final long warnThreshold, final long infoThreshold,
final long debugThreshold, final long traceThreshold) {
slowLogger = LogManager.getLogger("slowlog." + loggerName);
this.warnThreshold = warnThreshold;
this.infoThreshold = infoThreshold;
this.debugThreshold = debugThreshold;
this.traceThreshold = traceThreshold;
}
static long toLong(final IRubyObject value) {
if (!(value instanceof RubyNumeric)) {
throw RubyUtil.RUBY.newTypeError("Numeric expected, got " + value.getMetaClass());
}
return ((RubyNumeric) value).getLongValue();
}
private RubyHash asData(final ThreadContext context, final IRubyObject pluginParams,
final IRubyObject event, final IRubyObject durationNanos) {
RubyHash data = RubyHash.newHash(context.runtime);