diff --git a/core/src/main/java/org/jruby/RubyBinding.java b/core/src/main/java/org/jruby/RubyBinding.java index b2d28105e80..ce36b1944ca 100644 --- a/core/src/main/java/org/jruby/RubyBinding.java +++ b/core/src/main/java/org/jruby/RubyBinding.java @@ -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) { diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java index 4421a30bd02..b27e6b37721 100644 --- a/core/src/main/java/org/jruby/RubyClass.java +++ b/core/src/main/java/org/jruby/RubyClass.java @@ -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 @@ -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 diff --git a/core/src/main/java/org/jruby/RubyHash.java b/core/src/main/java/org/jruby/RubyHash.java index 4c9e65b7f25..92af952d0ea 100644 --- a/core/src/main/java/org/jruby/RubyHash.java +++ b/core/src/main/java/org/jruby/RubyHash.java @@ -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; } @@ -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)); diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index 138cdce2db1..50d67167e60 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -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 # 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; } @@ -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); @@ -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"); @@ -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; diff --git a/core/src/main/java/org/jruby/RubySignalException.java b/core/src/main/java/org/jruby/RubySignalException.java index 04c9f95ebc8..fa9ed8f6e7a 100644 --- a/core/src/main/java/org/jruby/RubySignalException.java +++ b/core/src/main/java/org/jruby/RubySignalException.java @@ -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(); diff --git a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java index 39d53f776d6..64d9555f950 100644 --- a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java +++ b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java @@ -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); diff --git a/core/src/main/java/org/jruby/parser/StaticScope.java b/core/src/main/java/org/jruby/parser/StaticScope.java index 1b37fac8a3a..fb50733084d 100644 --- a/core/src/main/java/org/jruby/parser/StaticScope.java +++ b/core/src/main/java/org/jruby/parser/StaticScope.java @@ -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; diff --git a/core/src/main/java/org/jruby/runtime/Binding.java b/core/src/main/java/org/jruby/runtime/Binding.java index 2be702dd3fa..7a4b24ace7a 100644 --- a/core/src/main/java/org/jruby/runtime/Binding.java +++ b/core/src/main/java/org/jruby/runtime/Binding.java @@ -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; + } } diff --git a/core/src/main/java/org/jruby/runtime/DynamicScope.java b/core/src/main/java/org/jruby/runtime/DynamicScope.java index 01ed6d18294..30f6f43808d 100644 --- a/core/src/main/java/org/jruby/runtime/DynamicScope.java +++ b/core/src/main/java/org/jruby/runtime/DynamicScope.java @@ -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 @@ -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; + } } diff --git a/core/src/main/java/org/jruby/runtime/IRBlockBody.java b/core/src/main/java/org/jruby/runtime/IRBlockBody.java index f87ad75944f..52e3404aa4c 100644 --- a/core/src/main/java/org/jruby/runtime/IRBlockBody.java +++ b/core/src/main/java/org/jruby/runtime/IRBlockBody.java @@ -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); diff --git a/core/src/main/java/org/jruby/runtime/Signature.java b/core/src/main/java/org/jruby/runtime/Signature.java index d4725735b85..1590067cd66 100644 --- a/core/src/main/java/org/jruby/runtime/Signature.java +++ b/core/src/main/java/org/jruby/runtime/Signature.java @@ -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; } /** @@ -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); } @@ -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; } diff --git a/core/src/main/java/org/jruby/runtime/scope/ManyVarsDynamicScope.java b/core/src/main/java/org/jruby/runtime/scope/ManyVarsDynamicScope.java index ea568c26d1a..2f53433fcbe 100644 --- a/core/src/main/java/org/jruby/runtime/scope/ManyVarsDynamicScope.java +++ b/core/src/main/java/org/jruby/runtime/scope/ManyVarsDynamicScope.java @@ -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; } diff --git a/core/src/main/java/org/jruby/util/ClassDefiningClassLoader.java b/core/src/main/java/org/jruby/util/ClassDefiningClassLoader.java index f8e65293208..7d775ab54eb 100644 --- a/core/src/main/java/org/jruby/util/ClassDefiningClassLoader.java +++ b/core/src/main/java/org/jruby/util/ClassDefiningClassLoader.java @@ -6,6 +6,8 @@ public interface ClassDefiningClassLoader { Class loadClass(String name) throws ClassNotFoundException; + boolean hasDefinedClass(String name); + default ClassLoader asClassLoader() { return (ClassLoader) this; } } diff --git a/core/src/main/java/org/jruby/util/ClassDefiningJRubyClassLoader.java b/core/src/main/java/org/jruby/util/ClassDefiningJRubyClassLoader.java index 164bcc97ca0..0334c8e3ee5 100644 --- a/core/src/main/java/org/jruby/util/ClassDefiningJRubyClassLoader.java +++ b/core/src/main/java/org/jruby/util/ClassDefiningJRubyClassLoader.java @@ -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); } } diff --git a/core/src/main/java/org/jruby/util/NClassClassLoader.java b/core/src/main/java/org/jruby/util/NClassClassLoader.java index a6e63a5bcd0..cae5f7f2d8d 100644 --- a/core/src/main/java/org/jruby/util/NClassClassLoader.java +++ b/core/src/main/java/org/jruby/util/NClassClassLoader.java @@ -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; } diff --git a/core/src/main/java/org/jruby/util/OneShotClassLoader.java b/core/src/main/java/org/jruby/util/OneShotClassLoader.java index c04804b76c2..b42510ed25e 100644 --- a/core/src/main/java/org/jruby/util/OneShotClassLoader.java +++ b/core/src/main/java/org/jruby/util/OneShotClassLoader.java @@ -21,4 +21,14 @@ public Class defineClass(String name, byte[] bytes) { resolveClass(cls); return cls; } + + @Override + public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return super.loadClass(name, resolve); + } + + @Override + public boolean hasDefinedClass(String name) { + return super.findLoadedClass(name) != null; + } } diff --git a/core/src/main/java/org/jruby/util/io/PopenExecutor.java b/core/src/main/java/org/jruby/util/io/PopenExecutor.java index 6ad2117d7dd..44fe091b9c0 100644 --- a/core/src/main/java/org/jruby/util/io/PopenExecutor.java +++ b/core/src/main/java/org/jruby/util/io/PopenExecutor.java @@ -87,7 +87,7 @@ public static RubyFixnum spawn(ThreadContext context, IRubyObject[] argv) { ExecArg eargp; IRubyObject fail_str; - eargp = execargNew(context, argv, true, false); + eargp = execargNew(context, argv, context.nil, true, false); execargFixup(context, runtime, eargp); fail_str = eargp.use_shell ? eargp.command_name : eargp.command_name; @@ -110,7 +110,7 @@ public IRubyObject systemInternal(ThreadContext context, IRubyObject[] argv, Str ExecArg eargp; long pid; - eargp = execargNew(context, argv, true, true); + eargp = execargNew(context, argv, context.nil, true, true); execargFixup(context, runtime, eargp); pid = spawnProcess(context, runtime, eargp, errmsg); @@ -297,7 +297,7 @@ public static IRubyObject pipeOpen(ThreadContext context, IRubyObject prog, Stri ExecArg execArg = null; if (!isPopenFork(context.runtime, (RubyString)prog)) - execArg = execargNew(context, argv, true, false); + execArg = execargNew(context, argv, context.nil, true, false); return new PopenExecutor().pipeOpen(context, execArg, modestr, fmode, convconfig); } @@ -340,14 +340,14 @@ public static IRubyObject popen(ThreadContext context, IRubyObject[] argv, RubyC // #endif tmp = ((RubyArray)tmp).aryDup(); // RBASIC_CLEAR_CLASS(tmp); - eargp = execargNew(context, ((RubyArray)tmp).toJavaArray(), false, false); + eargp = execargNew(context, ((RubyArray)tmp).toJavaArray(), opt, false, false); ((RubyArray)tmp).clear(); } else { pname = pname.convertToString(); eargp = null; if (!isPopenFork(runtime, (RubyString)pname)) { IRubyObject[] pname_p = {pname}; - eargp = execargNew(context, pname_p, true, false); + eargp = execargNew(context, pname_p, opt, true, false); pname = pname_p[0]; } } @@ -1678,14 +1678,14 @@ static RubyArray checkExecRedirect1(Ruby runtime, RubyArray ary, IRubyObject key private static final int ST_STOP = 1; // rb_execarg_new - public static ExecArg execargNew(ThreadContext context, IRubyObject[] argv, boolean accept_shell, boolean allow_exc_opt) { + public static ExecArg execargNew(ThreadContext context, IRubyObject[] argv, IRubyObject optForChdir, boolean accept_shell, boolean allow_exc_opt) { ExecArg eargp = new ExecArg(); - execargInit(context, argv, accept_shell, eargp, allow_exc_opt); + execargInit(context, argv, optForChdir, accept_shell, eargp, allow_exc_opt); return eargp; } // rb_execarg_init - private static RubyString execargInit(ThreadContext context, IRubyObject[] argv, boolean accept_shell, ExecArg eargp, boolean allow_exc_opt) { + private static RubyString execargInit(ThreadContext context, IRubyObject[] argv, IRubyObject optForChdir, boolean accept_shell, ExecArg eargp, boolean allow_exc_opt) { RubyString prog, ret; IRubyObject[] env_opt = {context.nil, context.nil}; IRubyObject[][] argv_p = {argv}; @@ -1698,6 +1698,14 @@ private static RubyString execargInit(ThreadContext context, IRubyObject[] argv, optHash = optHash.dupFast(context); exception = optHash.delete(context, exceptionSym); } + + RubySymbol chdirSym = context.runtime.newSymbol("chdir"); + IRubyObject chdir; + if (!optForChdir.isNil() && (chdir = ((RubyHash) optForChdir).delete(chdirSym)) != null) { + eargp.chdirGiven = true; + eargp.chdir_dir = chdir.convertToString().toString(); + } + execFillarg(context, prog, argv_p[0], env_opt[0], env_opt[1], eargp); if (exception.isTrue()) { eargp.exception = true; diff --git a/core/src/main/ruby/jruby/kernel/jruby/process_util.rb b/core/src/main/ruby/jruby/kernel/jruby/process_util.rb index 7786153dcd9..b009973d390 100644 --- a/core/src/main/ruby/jruby/kernel/jruby/process_util.rb +++ b/core/src/main/ruby/jruby/kernel/jruby/process_util.rb @@ -4,7 +4,7 @@ def self.exec_args(args) env, prog, opts = nil if args.size < 1 - raise ArgumentError, 'wrong number of arguments' + raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)' end # peel off options diff --git a/spec/java_integration/reify/become_java_spec.rb b/spec/java_integration/reify/become_java_spec.rb index b42fe43f397..7789075acbb 100644 --- a/spec/java_integration/reify/become_java_spec.rb +++ b/spec/java_integration/reify/become_java_spec.rb @@ -22,21 +22,24 @@ def run it "uses the nesting of the class for its package name" do class ReifyInterfacesClass1 - class ReifyInterfacesClass2 + module Nested + class InnerClass + end end end ReifyInterfacesClass1.become_java! - ReifyInterfacesClass1::ReifyInterfacesClass2.become_java! + ReifyInterfacesClass1::Nested::InnerClass.become_java! jclass = java.lang.Class - expect(ReifyInterfacesClass1.to_java(jclass).name).to eq("rubyobj.ReifyInterfacesClass1") - expect(ReifyInterfacesClass1::ReifyInterfacesClass2.to_java(jclass).name).to eq("rubyobj.ReifyInterfacesClass1.ReifyInterfacesClass2") + outer_java_class_name = ReifyInterfacesClass1.to_java(jclass).name + expect(outer_java_class_name).to eql('rubyobj.ReifyInterfacesClass1') + expect(ReifyInterfacesClass1::Nested::InnerClass.to_java(jclass).name).to eql('rubyobj.ReifyInterfacesClass1.Nested.InnerClass') # checking that the packages are valid for Java's purposes expect do ReifyInterfacesClass1.new - ReifyInterfacesClass1::ReifyInterfacesClass2.new + ReifyInterfacesClass1::Nested::InnerClass.new end.not_to raise_error end @@ -116,176 +119,210 @@ class TopRightOfTheJStack < MiddleOfTheJStack ;def size; super + 3; end ; end expect(TopLeftOfTheJStack.new.size).to eq 0 expect(TopRightOfTheJStack.new([:a, :b]).size).to eq (2+3) end + + it "supports auto reifying a class hierarchy when class gets redefined" do + class ASubList < java.util.ArrayList + attr_reader :args + def initialize(arg1, arg2) + @args = [arg1, arg2] + super(arg1 + arg2) + end + end + class ASubSubList < ASubList; end + expect( ASubSubList.new(1, 2).args ).to eql [1, 2] + + Object.send(:remove_const, :ASubSubList) + + class ASubSubList < ASubList; end + expect( ASubSubList.new(1, 2).args ).to eql [1, 2] + + old_a_sub_list_reified_class = JRuby.reference(ASubList).reified_class + + Object.send(:remove_const, :ASubSubList) + Object.send(:remove_const, :ASubList) + + class ASubList < java.util.ArrayList + attr_reader :args + def initialize(arg1, arg2) + @args = [arg2, arg1] + super(arg1 + arg2) + end + end + class ASubSubList < ASubList; end + expect( ASubSubList.new(1, 2).args ).to eql [2, 1] + + expect(JRuby.reference(ASubList).reified_class).to_not equal(old_a_sub_list_reified_class) # new Java class generated + + Object.send(:remove_const, :ASubSubList) + Object.send(:remove_const, :ASubList) + end it "constructs in the right order using the right methods" do - class BottomOfTheCJStack < java.util.ArrayList - def initialize(lst) - lst << :bottom_start - super() - lst << :bottom_end - end - end - class MiddleLofTheCJStack < BottomOfTheCJStack - def initialize(lst) - lst << :mid1_start - super - lst << :mid1_end - end - end - class MiddleMofTheCJStack < MiddleLofTheCJStack - def initialize(lst) - lst << :mid2_error - super - lst << :mid2_end_error - end - configure_java_class ctor_name: :reconfigured - def reconfigured(lst) - lst << :mid2_good_start - super - lst << :mid2_good_end - end - end - class MiddleUofTheCJStack < MiddleMofTheCJStack - #default init - end - class TopOfTheCJStack < MiddleUofTheCJStack - def initialize(lst) - lst << :top_start - super - lst << :top_end - end - end + class BottomOfTheCJStack < java.util.ArrayList + def initialize(lst) + lst << :bottom_start + super() + lst << :bottom_end + end + end + class MiddleLofTheCJStack < BottomOfTheCJStack + def initialize(lst) + lst << :mid1_start + super + lst << :mid1_end + end + end + class MiddleMofTheCJStack < MiddleLofTheCJStack + def initialize(lst) + lst << :mid2_error + super + lst << :mid2_end_error + end + configure_java_class ctor_name: :reconfigured + def reconfigured(lst) + lst << :mid2_good_start + super + lst << :mid2_good_end + end + end + class MiddleUofTheCJStack < MiddleMofTheCJStack + #default init + end + class TopOfTheCJStack < MiddleUofTheCJStack + def initialize(lst) + lst << :top_start + super + lst << :top_end + end + end - trace = [] + trace = [] expect(TopOfTheCJStack.new(trace)).not_to be_nil expect(trace).to eql([:top_start, :mid2_good_start, :mid1_start, :bottom_start, :bottom_end, :mid1_end, :mid2_good_end, :top_end]) end it "supports reification of java classes with interfaces" do - clz = Class.new(java.lang.Exception) do - include java.util.Iterator - def initialize(array) - @array = array - super(array.inspect) - @at=0 - @loop = false - end - def hasNext - @at<@array.length - end - def next() - @array[@array.length - 1 - @at].tap{@at+=1} - end - - def remove - raise java.lang.StackOverflowError.new if @loop - @loop = true - begin - @array<< :fail1 - super - @array << :fail2 - rescue java.lang.UnsupportedOperationException => uo - @array = [:success] - rescue java.lang.StackOverflowError => so - @array << :failSO - end - @loop = false - end - end - - gotten = [] - clz.new([:a, :b, :c]).forEachRemaining { |k| gotten << k } - expect(gotten).to eql([:c,:b, :a]) - expect(clz.new([:a, :b, :c]).message).to eql("[:a, :b, :c]") - - pending "GH#6479 + reification not yet hooked up" - obj = clz.new(["fail3"]) - obj.remove - gotten = [] - obj.forEachRemaining { |k| gotten << k } - expect(gotten).to eql([:success]) - expect(gotten.length).to eql(2) + clz = Class.new(java.lang.Exception) do + include java.util.Iterator + def initialize(array) + @array = array + super(array.inspect) + @at = 0 + @loop = false + end + def hasNext + @at < @array.length + end + def next() + @array[@array.length - 1 - @at].tap{@at+=1} + end + def remove + raise java.lang.StackOverflowError.new if @loop + @loop = true + begin + @array<< :fail1 + super + @array << :fail2 + rescue java.lang.UnsupportedOperationException => uo + @array = [:success] + rescue java.lang.StackOverflowError => so + @array << :failSO + end + @loop = false + end + end + + gotten = [] + clz.new([:a, :b, :c]).forEachRemaining { |k| gotten << k } + expect(gotten).to eql([:c,:b, :a]) + expect(clz.new([:a, :b, :c]).message).to eql("[:a, :b, :c]") + + pending "GH#6479 + reification not yet hooked up" + obj = clz.new(["fail3"]) + obj.remove + gotten = [] + obj.forEachRemaining { |k| gotten << k } + expect(gotten).to eql([:success]) + expect(gotten.length).to eql(2) end it "supports reification of ruby classes with interfaces" do pending "GH#6479 + reification not yet hooked up" - clz = Class.new do - include java.util.Iterator - def initialize(array) - @array = array - @at=0 - @loop = false - end - def hasNext - @at<@array.length - end - def next() - @array[@array.length - 1 - @at].tap{@at+=1} - end - - def remove - raise java.lang.StackOverflowError.new if @loop - @loop = true - begin - @array<< :fail1 - super - @array << :fail2 - rescue java.lang.UnsupportedOperationException => uo - @array = [:success] - rescue java.lang.StackOverflowError => so - @array << :failSO - end - @loop = false - end - end - - obj = clz.new(["fail3"]) - obj.remove - gotten = [] - obj.forEachRemaining { |k| gotten << k } - expect(gotten).to eql([:success]) - expect(gotten.length).to eql(2) + clz = Class.new do + include java.util.Iterator + def initialize(array) + @array = array + @at = 0 + @loop = false + end + def hasNext + @at < @array.length + end + def next() + @array[@array.length - 1 - @at].tap{@at+=1} + end + + def remove + raise java.lang.StackOverflowError.new if @loop + @loop = true + begin + @array<< :fail1 + super + @array << :fail2 + rescue java.lang.UnsupportedOperationException => uo + @array = [:success] + rescue java.lang.StackOverflowError => so + @array << :failSO + end + @loop = false + end + end + + obj = clz.new(["fail3"]) + obj.remove + gotten = [] + obj.forEachRemaining { |k| gotten << k } + expect(gotten).to eql([:success]) + expect(gotten.length).to eql(2) end - it "supports reification of concrete classes with non-standard construction" do - - clz = Class.new(java.lang.Exception) do - def jinit(str, seq) - @seq = seq - super("foo: #{str}") - seq << :jinit - end - - def initialize(str, foo) - @seq << :init - @seq << str - @seq << foo - end - - def self.new(seq, str) - obj = allocate - seq << :new - obj.__jallocate!(str, seq) - seq << :ready - obj - end - - configure_java_class ctor_name: :jinit - end - - bclz = clz.become_java! + clz = Class.new(java.lang.Exception) do + def jinit(str, seq) + @seq = seq + super("foo: #{str}") + seq << :jinit + end + + def initialize(str, foo) + @seq << :init + @seq << str + @seq << foo + end + + def self.new(seq, str) + obj = allocate + seq << :new + obj.__jallocate!(str, seq) + seq << :ready + obj + end + + configure_java_class ctor_name: :jinit + end - lst = [] - obj = clz.new(lst, :bar) - expect(obj).not_to be_nil - expect(lst).to eq([:new, :jinit, :ready]) - expect(obj.message).to eq("foo: bar") - obj.send :initialize, :x, "y" - expect(lst).to eq([:new, :jinit, :ready, :init, :x, "y"]) - expect(bclz).to eq(clz.java_class) - expect(bclz).not_to eq(java.lang.Exception.java_class) + bclz = clz.become_java! + + lst = [] + obj = clz.new(lst, :bar) + expect(obj).not_to be_nil + expect(lst).to eq([:new, :jinit, :ready]) + expect(obj.message).to eq("foo: bar") + obj.send :initialize, :x, "y" + expect(lst).to eq([:new, :jinit, :ready, :init, :x, "y"]) + expect(bclz).to eq(clz.java_class) + expect(bclz).not_to eq(java.lang.Exception.java_class) end it "supports reification of annotations and signatures on static methods without parameters" do @@ -435,11 +472,11 @@ def ola(*args); "OLA #{args.join(' ')}" end it 'has a similar Java class name' do ReifiedSample.become_java! klass = ReifiedSample.java_class - expect( klass.getName ).to eql 'rubyobj.ReifiedSample' + expect( klass.getName ).to eql('rubyobj.ReifiedSample') klass = Class.new(ReifiedSample) hexid = klass.inspect.match(/(0x[0-9a-f]+)/)[1] klass = klass.become_java! - expect( klass.getName ).to match /^rubyobj\.Class_#{hexid}/ # rubyobj.Class_0x599f1b7 + expect( klass.getName ).to match /^rubyobj\.Class\$#{hexid}/ # rubyobj.Class$0x599f1b7 end it 'works when reflecting annotations' do diff --git a/spec/regression/GH-1229_reified_parent_and_child_have_same_name_spec.rb b/spec/regression/GH-1229_reified_parent_and_child_have_same_name_spec.rb index 1d0cf0f6672..c32f7dc276a 100644 --- a/spec/regression/GH-1229_reified_parent_and_child_have_same_name_spec.rb +++ b/spec/regression/GH-1229_reified_parent_and_child_have_same_name_spec.rb @@ -1,7 +1,7 @@ require 'jruby/core_ext' describe "A child class with the same fully-qualified name as a parent class" do - it "uses its reified parent class as its own reified class" do + it 'generates a new reified class' do module GH1229 class Foo; end end @@ -15,6 +15,8 @@ class Foo; end foo_ref = JRuby.reference(foo) foo2_ref = JRuby.reference(GH1229::Foo) - expect(foo2_ref.reified_class).to equal(foo_ref.reified_class) + expect(foo_ref.reified_class).to_not be(nil) + expect(foo2_ref.reified_class).to_not be(nil) + expect(foo2_ref.reified_class).to_not equal(foo_ref.reified_class) end end diff --git a/spec/ruby/core/binding/dup_spec.rb b/spec/ruby/core/binding/dup_spec.rb index 55fac6e3332..f30d63b708f 100644 --- a/spec/ruby/core/binding/dup_spec.rb +++ b/spec/ruby/core/binding/dup_spec.rb @@ -10,4 +10,21 @@ bind.frozen?.should == true bind.dup.frozen?.should == false end + + it "retains original binding variables but the list is distinct" do + bind1 = binding + eval "a = 1", bind1 + + bind2 = bind1.dup + eval("a = 2", bind2) + eval("a", bind1).should == 2 + eval("a", bind2).should == 2 + + eval("b = 2", bind2) + -> { eval("b", bind1) }.should raise_error(NameError) + eval("b", bind2).should == 2 + + bind1.local_variables.sort.should == [:a, :bind1, :bind2] + bind2.local_variables.sort.should == [:a, :b, :bind1, :bind2] + end end diff --git a/spec/tags/ruby/core/kernel/eval_tags.txt b/spec/tags/ruby/core/kernel/eval_tags.txt index da14ef98460..ad3754bf056 100644 --- a/spec/tags/ruby/core/kernel/eval_tags.txt +++ b/spec/tags/ruby/core/kernel/eval_tags.txt @@ -1,3 +1,3 @@ fails:Kernel#eval raises a LocalJumpError if there is no lambda-style closure in the chain fails:Kernel#eval with a magic encoding comment ignores the magic encoding comment if it is after a frozen_string_literal magic comment -fails:Kernel#eval makes flip-flop operator work correctly + diff --git a/spec/tags/ruby/core/proc/arity_tags.txt b/spec/tags/ruby/core/proc/arity_tags.txt deleted file mode 100644 index e742e2ff5e3..00000000000 --- a/spec/tags/ruby/core/proc/arity_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Proc#arity for instances created with lambda { || } returns positive values for definition '@a = lambda { |a, | }' -fails:Proc#arity for instances created with proc { || } returns positive values for definition '@a = proc { |a, | }' diff --git a/spec/tags/ruby/language/class_variable_tags.txt b/spec/tags/ruby/language/class_variable_tags.txt deleted file mode 100644 index 8960545d44f..00000000000 --- a/spec/tags/ruby/language/class_variable_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Accessing a class variable raises a RuntimeError when a class variable is overtaken in an ancestor class diff --git a/spec/tags/ruby/library/erb/result_tags.txt b/spec/tags/ruby/library/erb/result_tags.txt deleted file mode 100644 index 0607580c953..00000000000 --- a/spec/tags/ruby/library/erb/result_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ERB#result use TOPLEVEL_BINDING if binding is not passed diff --git a/spec/tags/ruby/library/erb/run_tags.txt b/spec/tags/ruby/library/erb/run_tags.txt deleted file mode 100644 index 47996d0b847..00000000000 --- a/spec/tags/ruby/library/erb/run_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ERB#run use TOPLEVEL_BINDING if binding is not passed diff --git a/test/jruby/test_higher_javasupport.rb b/test/jruby/test_higher_javasupport.rb index 7baf0674e3c..f28e31c8cbe 100644 --- a/test/jruby/test_higher_javasupport.rb +++ b/test/jruby/test_higher_javasupport.rb @@ -50,7 +50,7 @@ def self.method2; end def test_passing_a_java_class_auto_reifies assert_nil Klass2.to_java.getReifiedClass # previously TestHelper.getClassName(Klass2) returned 'org.jruby.RubyObject' - assert_equal 'rubyobj.TestHigherJavasupport.Klass2', org.jruby.test.TestHelper.getClassName(Klass2) + assert org.jruby.test.TestHelper.getClassName(Klass2).end_with?('.TestHigherJavasupport.Klass2') assert_not_nil Klass2.to_java.getReifiedClass assert_not_nil Klass1.to_java.getReifiedClass end diff --git a/test/mri/excludes/TestArity.rb b/test/mri/excludes/TestArity.rb deleted file mode 100644 index 760fd49eb42..00000000000 --- a/test/mri/excludes/TestArity.rb +++ /dev/null @@ -1,3 +0,0 @@ -exclude :test_message_change_issue_6085, "needs investigation" -exclude :test_method_err_mess, "needs investigation" -exclude :test_proc_err_mess, "needs investigation" \ No newline at end of file