Skip to content

Commit

Permalink
Merge branch 'master' into 9.5-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
enebo committed May 2, 2024
2 parents 9e805d5 + d7358ea commit ffecfda
Show file tree
Hide file tree
Showing 28 changed files with 382 additions and 240 deletions.
7 changes: 6 additions & 1 deletion core/src/main/java/org/jruby/RubyBinding.java
Expand Up @@ -105,7 +105,12 @@ public IRubyObject initialize(ThreadContext context) {

return this;
}


@JRubyMethod
public IRubyObject dup(ThreadContext context) {
return newBinding(context.runtime, binding.dup(context));
}

@JRubyMethod(name = "initialize_copy", visibility = Visibility.PRIVATE)
@Override
public IRubyObject initialize_copy(IRubyObject other) {
Expand Down
65 changes: 40 additions & 25 deletions core/src/main/java/org/jruby/RubyClass.java
Expand Up @@ -1380,50 +1380,56 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) {
boolean[] java_box = { false };
// re-check reifiable in case another reify call has jumped in ahead of us
if (!isReifiable(java_box)) return;
final boolean concreteExt = java_box[0];

// calculate an appropriate name, for anonymous using inspect like format e.g. "Class:0x628fad4a"
final String name = getBaseName() != null ? getName() :
( "Class_0x" + Integer.toHexString(System.identityHashCode(this)) );

final String javaName = "rubyobj." + StringSupport.replaceAll(name, "::", ".");
final String javaPath = "rubyobj/" + StringSupport.replaceAll(name, "::", "/");
final boolean concreteExt = java_box[0];

final Class<?> parentReified = superClass.getRealClass().getReifiedClass();
if (parentReified == null) {
throw typeError(getClassRuntime().getCurrentContext(), getName() + "'s parent class is not yet reified");
}

Class reifiedParent = RubyObject.class;
if (superClass.reifiedClass != null) reifiedParent = superClass.reifiedClass;

Reificator reifier;
if (concreteExt) {
reifier = new ConcreteJavaReifier(parentReified, javaName, javaPath);
} else {
reifier = new MethodReificator(reifiedParent, javaName, javaPath, null, javaPath);
}

final byte[] classBytes = reifier.reify();

final ClassDefiningClassLoader parentCL;
ClassDefiningClassLoader classLoader; // usually parent's class-loader
if (parentReified.getClassLoader() instanceof OneShotClassLoader) {
parentCL = (OneShotClassLoader) parentReified.getClassLoader();
classLoader = (OneShotClassLoader) parentReified.getClassLoader();
} else {
if (useChildLoader) {
MultiClassLoader parentLoader = new MultiClassLoader(runtime.getJRubyClassLoader());
for(Loader cLoader : runtime.getInstanceConfig().getExtraLoaders()) {
parentLoader.addClassLoader(cLoader.getClassLoader());
}
parentCL = new OneShotClassLoader(parentLoader);
classLoader = new OneShotClassLoader(parentLoader);
} else {
parentCL = runtime.getJRubyClassLoader();
classLoader = runtime.getJRubyClassLoader();
}
}

String javaName = getReifiedJavaClassName();
// *might* need to include a Class identifier in the Java class name, since a Ruby class might be dropped
// (using remove_const) and re-created in which case using the same name would cause a conflict...
if (classLoader.hasDefinedClass(javaName)) { // as Ruby class dropping is "unusual" - assume v0 to be the raw name
String versionedName; int v = 1;
// NOTE: '@' is not supported in Ruby class names thus it's safe to use as a "separator"
do {
versionedName = javaName + "@v" + (v++); // rubyobj.SomeModule.Foo@v1
} while (classLoader.hasDefinedClass(versionedName));
javaName = versionedName;
}
final String javaPath = javaName.replace('.', '/');

Reificator reifier;
if (concreteExt) {
reifier = new ConcreteJavaReifier(parentReified, javaName, javaPath);
} else {
Class<?> reifiedParent = superClass.reifiedClass;
if (reifiedParent == null) reifiedParent = RubyObject.class;
reifier = new MethodReificator(reifiedParent, javaName, javaPath, null, javaPath);
}

final byte[] classBytes = reifier.reify();

boolean nearEnd = false;
// Attempt to load the name we plan to use; skip reification if it exists already (see #1229).
try {
Class result = parentCL.defineClass(javaName, classBytes);
Class result = classLoader.defineClass(javaName, classBytes);
dumpReifiedClass(classDumpDir, javaPath, classBytes);

//Trigger initilization
Expand Down Expand Up @@ -1475,6 +1481,15 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) {
}
}

private String getReifiedJavaClassName() {
final String basePackagePrefix = "rubyobj.";
if (getBaseName() == null) { // anonymous Class instance: rubyobj.Class$0x1234abcd
return basePackagePrefix + anonymousMetaNameWithIdentifier().replace(':', '$');
}
final CharSequence name = StringSupport.replaceAll(getName(), "::", ".");
return basePackagePrefix + name; // TheFoo::Bar -> rubyobj.TheFoo.Bar
}

interface Reificator {
byte[] reify();
} // interface Reificator
Expand Down
11 changes: 5 additions & 6 deletions core/src/main/java/org/jruby/RubyHash.java
Expand Up @@ -815,11 +815,10 @@ public IRubyObject initialize(ThreadContext context, final Block block) {
public IRubyObject initialize(ThreadContext context, IRubyObject _default, final Block block) {
modify();

if (block.isGiven()) {
throw context.runtime.newArgumentError("wrong number of arguments");
} else {
ifNone = _default;
}
if (block.isGiven()) throw context.runtime.newArgumentError(1, 0);

ifNone = _default;

return this;
}

Expand Down Expand Up @@ -1601,7 +1600,7 @@ public RubyHash each_pairCommon(final ThreadContext context, final Block block)
public void visit(ThreadContext context, RubyHash self, IRubyObject key, IRubyObject value, int index, Block block) {
if (block.type == Block.Type.LAMBDA) {
block.call(context, context.runtime.newArray(key, value));
} else if (block.getSignature().arityValue() > 1) {
} else if (block.getSignature().isSpreadable()) {
block.yieldSpecific(context, key, value);
} else {
block.yield(context, context.runtime.newArray(key, value));
Expand Down
28 changes: 22 additions & 6 deletions core/src/main/java/org/jruby/RubyModule.java
Expand Up @@ -809,13 +809,10 @@ private String calculateName() {
}

private String calculateAnonymousName() {
String cachedName = this.cachedName; // re-use cachedName field since it won't be set for anonymous class
String cachedName = this.cachedName;
if (cachedName == null) {
// anonymous classes get the #<Class:0xdeadbeef> format
StringBuilder anonBase = new StringBuilder(24);
anonBase.append("#<").append(metaClass.getRealClass().getName()).append(":0x");
anonBase.append(Integer.toHexString(System.identityHashCode(this))).append('>');
cachedName = this.cachedName = anonBase.toString();
cachedName = this.cachedName = "#<" + anonymousMetaNameWithIdentifier() + '>';
}
return cachedName;
}
Expand All @@ -832,7 +829,8 @@ public IRubyObject set_temporary_name(ThreadContext context, IRubyObject arg) {
RubyString name = arg.convertToString();

if (name.length() == 0) throw context.runtime.newArgumentError("empty class/module name");
if (isValidConstantPath(name)) throw context.runtime.newArgumentError("the temporary name must not be a constant path to avoid confusion");
if (isValidConstantPath(name))
throw context.runtime.newArgumentError("the temporary name must not be a constant path to avoid confusion");

setFlag(TEMPORARY_NAME, true);

Expand All @@ -845,6 +843,10 @@ public IRubyObject set_temporary_name(ThreadContext context, IRubyObject arg) {
return this;
}

String anonymousMetaNameWithIdentifier() {
return metaClass.getRealClass().getName() + ":0x" + Integer.toHexString(System.identityHashCode(this));
}

@JRubyMethod(name = "refine", reads = SCOPE)
public IRubyObject refine(ThreadContext context, IRubyObject klass, Block block) {
if (!block.isGiven()) throw context.runtime.newArgumentError("no block given");
Expand Down Expand Up @@ -4612,13 +4614,27 @@ public IRubyObject getClassVarQuiet(String name) {
assert IdUtil.isClassVariable(name);
RubyModule module = this;
RubyModule highest = null;
RubyModule lowest = null;

do {
if (module.hasClassVariable(name)) {
highest = module;
if (lowest == null) lowest = module;
}
} while ((module = module.getSuperClass()) != null);

if (lowest != highest) {
if (!highest.isPrepended()) {
if (lowest.getOrigin().getRealModule() != highest.getOrigin().getRealModule()) {
throw getRuntime().newRuntimeError(str(getRuntime(), "class variable " + name + " of ",
lowest.getOrigin(), " is overtaken by ", highest.getOrigin()));
}

if (lowest.isClass()) lowest.removeClassVariable(name);
}

}

if (highest != null) return highest.fetchClassVariable(name);

return null;
Expand Down
21 changes: 9 additions & 12 deletions core/src/main/java/org/jruby/RubySignalException.java
Expand Up @@ -62,26 +62,23 @@ static RubyClass define(Ruby runtime, RubyClass exceptionClass) {
return signalExceptionClass;
}

@JRubyMethod(optional = 2, checkArity = false, visibility = PRIVATE)
@JRubyMethod(required = 1, optional = 2, checkArity = false, visibility = PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
int argc = Arity.checkArgumentCount(context, args, 0, 2);
int argc = Arity.checkArgumentCount(context, args, 1, 2);

final Ruby runtime = context.runtime;
int argnum = 1;
IRubyObject sig = context.nil;
long _signo;

if (argc > 0) {
sig = TypeConverter.checkToInteger(runtime, args[0], "to_int");
IRubyObject sig = TypeConverter.checkToInteger(runtime, args[0], "to_int");

if (sig.isNil()) {
sig = args[0];
} else {
argnum = 2;
}
if (sig.isNil()) {
sig = args[0];
Arity.checkArgumentCount(runtime, args, 1, argnum);
} else {
argnum = 2;
}

Arity.checkArgumentCount(runtime, args, 1, argnum);
long _signo;

if (argnum == 2) {
_signo = sig.convertToInteger().getLongValue();
Expand Down
Expand Up @@ -513,7 +513,9 @@ public static IRubyObject[] convertValueIntoArgArray(ThreadContext context, IRub
new IRubyObject[] { value };
case 0:
case 1:
return new IRubyObject[] { value };
return signature.rest() == org.jruby.runtime.Signature.Rest.ANON ?
IRBlockBody.toAry(context, value) :
new IRubyObject[] { value };
}

return IRBlockBody.toAry(context, value);
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/parser/StaticScope.java
Expand Up @@ -76,7 +76,7 @@
* will point to the previous scope of the enclosing module/class (cref).
*
*/
public class StaticScope implements Serializable {
public class StaticScope implements Serializable, Cloneable {
public static final int MAX_SPECIALIZED_SIZE = 50;
private static final long serialVersionUID = 3423852552352498148L;

Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/org/jruby/runtime/Binding.java
Expand Up @@ -316,4 +316,16 @@ public BacktraceElement getBacktrace() {
// Can't use Frame.DUMMY because of circular static init seeing it before it's assigned
new Frame(),
Visibility.PUBLIC);

/**
* Duplicate this binding and setup the proper cloned instance of the eval scope so that any previously
* captured variables still exist but are not shared with the original binding.
* @param context the current thread context
* @return the duplicated binding
*/
public Binding dup(ThreadContext context) {
Binding newBinding = new Binding(this);
newBinding.evalScope = getEvalScope(context.runtime).dupEvalScope();
return newBinding;
}
}
11 changes: 11 additions & 0 deletions core/src/main/java/org/jruby/runtime/DynamicScope.java
Expand Up @@ -32,6 +32,7 @@
import org.jruby.ir.JIT;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.scope.ManyVarsDynamicScope;

public abstract class DynamicScope implements Cloneable {
// Static scoping information for this scope
Expand Down Expand Up @@ -598,4 +599,14 @@ public DynamicScope cloneScope() {
throw new RuntimeException("BUG: failed to clone scope type " + getClass().getName());
}
}

/**
* Binding needs to clone its scope with all the current values.
* @return a duplicate of this scope with all the current values
*/
public DynamicScope dupEvalScope() {
ManyVarsDynamicScope newScope = new ManyVarsDynamicScope(staticScope.duplicate(), parent);
newScope.setVariableValues(getValues());
return newScope;
}
}
5 changes: 2 additions & 3 deletions core/src/main/java/org/jruby/runtime/IRBlockBody.java
Expand Up @@ -104,14 +104,13 @@ public IRubyObject yieldSpecific(ThreadContext context, Block block, IRubyObject
}

private IRubyObject yieldSpecificMultiArgsCommon(ThreadContext context, Block block, IRubyObject... args) {
int blockArity = signature.arityValue();
if (blockArity == 1) {
if (signature.isOneArgument()) {
args = new IRubyObject[] { RubyArray.newArrayMayCopy(context.runtime, args) };
}

if (canCallDirect()) return yieldDirect(context, block, args, null);

if (blockArity == 0) {
if (signature.arityValue() == 0) {
args = IRubyObject.NULL_ARRAY; // discard args
}
if (block.type == Block.Type.LAMBDA) signature.checkArity(context.runtime, args);
Expand Down
7 changes: 4 additions & 3 deletions core/src/main/java/org/jruby/runtime/Signature.java
Expand Up @@ -85,7 +85,7 @@ public boolean restKwargs() {
* Are there an exact (fixed) number of parameters to this signature?
*/
public boolean isFixed() {
return arityValue() >= 0;
return arityValue() >= 0 && rest != Rest.ANON;
}

/**
Expand Down Expand Up @@ -135,8 +135,9 @@ public int calculateArityValue() {
int oneForKeywords = requiredKwargs > 0 ? 1 : 0;
int fixedValue = pre() + post() + oneForKeywords;
boolean hasOptionalKeywords = kwargs - requiredKwargs > 0;
boolean optionalFromRest = rest() != Rest.NONE && rest != Rest.ANON;

if (opt() > 0 || rest() != Rest.NONE || (hasOptionalKeywords || restKwargs()) && oneForKeywords == 0) {
if (opt() > 0 || optionalFromRest || (hasOptionalKeywords || restKwargs()) && oneForKeywords == 0) {
return -1 * (fixedValue + 1);
}

Expand All @@ -153,7 +154,7 @@ public int arityValue() {
* @return true if the signature expects multiple args
*/
public boolean isSpreadable() {
return arityValue < -1 || arityValue > 1 || (opt > 0 && !restKwargs());
return arityValue < -1 || arityValue > 1 || (opt > 0 && !restKwargs()) || rest == Rest.ANON;
}


Expand Down
Expand Up @@ -50,6 +50,13 @@ private void allocate() {
if(variableValues == null) variableValues = IRubyObject.array(staticScope.getNumberOfVariables());
}

// WARNING: This is used when dup'ing an eval scope. We know the size and that it will 100% always be
// a ManyVarsDynamicScope. This should not be used by anything else. If there ever ends up being another
// use then it should be documented in this warning.
public void setVariableValues(IRubyObject[] variableValues) {
this.variableValues = variableValues;
}

public IRubyObject[] getValues() {
return variableValues;
}
Expand Down
Expand Up @@ -6,6 +6,8 @@ public interface ClassDefiningClassLoader {

Class<?> loadClass(String name) throws ClassNotFoundException;

boolean hasDefinedClass(String name);

default ClassLoader asClassLoader() { return (ClassLoader) this; }

}
Expand Up @@ -73,6 +73,11 @@ public Class<?> defineClass(String name, byte[] bytes, ProtectionDomain domain)
* @return whether it's loadable
*/
public boolean hasClass(String name) {
return definedClasses.contains(name) || super.findResource(name.replace('.', '/') + ".class") != null;
return hasDefinedClass(name) || super.findResource(name.replace('.', '/') + ".class") != null;
}

@Override
public boolean hasDefinedClass(String name) {
return definedClasses.contains(name);
}
}
5 changes: 5 additions & 0 deletions core/src/main/java/org/jruby/util/NClassClassLoader.java
Expand Up @@ -14,6 +14,11 @@ public Class<?> defineClass(String name, byte[] bytes) {
return super.defineClass(name, bytes, 0, bytes.length, ClassDefiningJRubyClassLoader.DEFAULT_DOMAIN);
}

@Override
public boolean hasDefinedClass(String name) {
return super.findLoadedClass(name) != null;
}

public boolean isFull() {
return i >= size;
}
Expand Down

0 comments on commit ffecfda

Please sign in to comment.