Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 379e8ea79f50
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e3d7c864f73f
Choose a head ref
  • 4 commits
  • 7 files changed
  • 1 contributor

Commits on Jan 5, 2017

  1. Copy the full SHA
    b0abffd View commit details
  2. [Truffle] Moved exec command processing out of the exec command, sinc…

    …e it's useful to other process spawning operations.
    nirvdrum committed Jan 5, 2017
    Copy the full SHA
    261c709 View commit details

Commits on Jan 6, 2017

  1. Improved command processing for exec/spawn.

    * Shell meta characters are now searched for to better match MRI's semantics.
    * spawn now shares the same setup logic as exec (where it can), so it uses
    the proper shell and runs through the same command processing.
    nirvdrum committed Jan 6, 2017
    Copy the full SHA
    c2a908e View commit details
  2. 1
    Copy the full SHA
    e3d7c86 View commit details
1 change: 0 additions & 1 deletion spec/truffle/tags/core/kernel/backtick_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Kernel#` raises an Errno::ENOENT if the command is not executable
slow:Kernel#` lets the standard error stream pass through to the inherited stderr
2 changes: 0 additions & 2 deletions spec/truffle/tags/core/kernel/spawn_tags.txt
Original file line number Diff line number Diff line change
@@ -4,10 +4,8 @@ fails:Kernel#spawn joins a new process group if pgroup: true
fails:Kernel#spawn joins a new process group if pgroup: 0
fails:Kernel#spawn joins the specified process group if pgroup: pgid
fails:Kernel#spawn sets the umask if given the :umask option
fails:Kernel#spawn raises an Errno::ENOENT if the command does not exist
fails:Kernel#spawn raises an Errno::EACCES when the file does not have execute permissions
fails:Kernel#spawn raises an Errno::EACCES when passed a directory
fails:Kernel#spawn with a single argument raises an ArgumentError if the command includes a null byte
fails:Kernel#spawn with multiple arguments raises an ArgumentError if an argument includes a null byte
fails:Kernel#spawn with a command array raises an ArgumentError if the Strings in the Array include a null byte
fails:Kernel#spawn when passed :chdir changes to the directory passed for :chdir
8 changes: 4 additions & 4 deletions spec/truffle/tags/core/kernel/system_tags.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
fails:Kernel#system returns nil when command execution fails
fails:Kernel#system does not write to stderr when command execution fails
fails:Kernel#system executes with `sh` if the command contains shell characters
fails:Kernel#system ignores SHELL env var and always uses `sh`
slow:Kernel#system returns true when the command exits with a zero exit status
slow:Kernel#system executes the specified command in a subprocess
slow:Kernel#system returns false when the command exits with a non-zero exit status
slow:Kernel#system expands shell variables when given a single string argument
slow:Kernel#system does not expand shell variables when given multiples arguments
slow:Kernel#system returns nil when command execution fails
slow:Kernel#system does not write to stderr when command execution fails
slow:Kernel#system executes with `sh` if the command contains shell characters
slow:Kernel#system ignores SHELL env var and always uses `sh`
Original file line number Diff line number Diff line change
@@ -682,7 +682,7 @@ protected Object writeProgramName(DynamicObject name) {
}

@Primitive(name = "vm_exec", needsSelf = false)
public abstract static class VMSExecNode extends PrimitiveArrayArgumentsNode {
public abstract static class VMExecNode extends PrimitiveArrayArgumentsNode {

@TruffleBoundary
@Specialization(guards = { "isRubyString(path)", "isRubyArray(args)", "isRubyArray(env)" })
126 changes: 0 additions & 126 deletions truffle/src/main/java/org/jruby/truffle/core/kernel/KernelNodes.java
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.ImportStatic;
@@ -65,15 +64,12 @@
import org.jruby.truffle.core.cast.NameToSymbolOrStringNodeGen;
import org.jruby.truffle.core.cast.TaintResultNode;
import org.jruby.truffle.core.cast.ToPathNodeGen;
import org.jruby.truffle.core.cast.ToStrNode;
import org.jruby.truffle.core.cast.ToStrNodeGen;
import org.jruby.truffle.core.format.BytesResult;
import org.jruby.truffle.core.format.FormatExceptionTranslator;
import org.jruby.truffle.core.format.exceptions.FormatException;
import org.jruby.truffle.core.format.exceptions.InvalidFormatException;
import org.jruby.truffle.core.format.printf.PrintfCompiler;
import org.jruby.truffle.core.hash.HashOperations;
import org.jruby.truffle.core.hash.KeyValue;
import org.jruby.truffle.core.kernel.KernelNodesFactory.CopyNodeFactory;
import org.jruby.truffle.core.kernel.KernelNodesFactory.SameOrEqualNodeFactory;
import org.jruby.truffle.core.kernel.KernelNodesFactory.SingletonMethodsNodeFactory;
@@ -94,7 +90,6 @@
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.RubyRootNode;
import org.jruby.truffle.language.SnippetNode;
import org.jruby.truffle.language.Visibility;
import org.jruby.truffle.language.arguments.RubyArguments;
import org.jruby.truffle.language.backtrace.Activation;
@@ -147,137 +142,16 @@
import org.jruby.truffle.platform.UnsafeGroup;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ProcessBuilder.Redirect;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

@CoreClass("Kernel")
public abstract class KernelNodes {

@CoreMethod(names = "`", isModuleFunction = true, required = 1, unsafe = { UnsafeGroup.IO, UnsafeGroup.PROCESSES })
public abstract static class BacktickNode extends CoreMethodArrayArgumentsNode {

private static class ExecuteResult {

private final DynamicObject output;
private final int pid;
private final int code;

public ExecuteResult(DynamicObject output, int pid, int code) {
this.output = output;
this.pid = pid;
this.code = code;
}

public DynamicObject getOutput() {
return output;
}

public int getPid() {
return pid;
}

public int getCode() {
return code;
}
}

@Child private CallDispatchHeadNode toHashNode;
@Child private ToStrNode toStrNode;
@Child private SnippetNode setStatusNode = new SnippetNode();

@Specialization(guards = "!isRubyString(command)")
public DynamicObject backtickCoerce(VirtualFrame frame, DynamicObject command) {
// TODO BJF Aug 4, 2016 Needs SafeStringValue here
if (toStrNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toStrNode = insert(ToStrNodeGen.create(null));
}
return backtick(frame, toStrNode.executeToStr(frame, command));
}

@Specialization(guards = "isRubyString(command)")
public DynamicObject backtick(VirtualFrame frame, DynamicObject command) {
// Command is lexically a string interoplation, so variables will already have been expanded

if (toHashNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toHashNode = insert(DispatchHeadNodeFactory.createMethodCall());
}

final DynamicObject env = getContext().getCoreLibrary().getENV();
final DynamicObject envAsHash = (DynamicObject) toHashNode.call(frame, env, "to_hash");

final ExecuteResult result = spawnAndCaptureOutput(command, envAsHash);

setStatusNode.execute(frame,
"Rubinius::Mirror::Process.set_status_global Process::Status.new(pid, code)",
"pid", result.getPid(),
"code", result.getCode());

return result.getOutput();
}

@TruffleBoundary
private ExecuteResult spawnAndCaptureOutput(DynamicObject command, final DynamicObject envAsHash) {
if (TruffleOptions.AOT) {
throw new UnsupportedOperationException("ProcessEnvironment.environment not supported with AOT");
}

// We need to run via bash to get the variable and other expansion we expect
String[] cmdArray = new String[] { "bash", "-c", command.toString() };

ProcessBuilder builder = new ProcessBuilder(cmdArray).redirectError(Redirect.INHERIT);

Map<String, String> env = builder.environment();
env.clear();
for (KeyValue keyValue : HashOperations.iterableKeyValues(envAsHash)) {
// TODO(CS): toString
env.put(keyValue.getKey().toString(), keyValue.getValue().toString());
}

final Process process;
try {
process = builder.start();
} catch (IOException e) {
throw new JavaException(e);
}

final InputStream stdout = process.getInputStream();

final ByteArrayOutputStream baos = new ByteArrayOutputStream();

final int bufferSize = 1024;
final byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
try {
while ((bytesRead = stdout.read(buffer, 0, bufferSize)) != -1) {
baos.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
throw new JavaException(e);
}

final int code = getContext().getThreadManager().runUntilResult(this, () -> process.waitFor());
final DynamicObject output = createString(baos.toByteArray(), getContext().getEncodingManager().getDefaultExternalEncoding());

// TODO CS 30-Oct-16 how to get the PID? JRuby does some gymnastics with reflection. I think we
// should probably reimplement this in Ruby using spawn, which starts processes with JNR and so
// has proper access to things like the PID.

final int pid = 0;

return new ExecuteResult(output, pid, code);
}

}

/**
* Check if operands are the same object or call #==.
* Known as rb_equal() in MRI. The fact Kernel#=== uses this is pure coincidence.
11 changes: 11 additions & 0 deletions truffle/src/main/ruby/core/kernel.rb
Original file line number Diff line number Diff line change
@@ -182,6 +182,17 @@ def StringValue(obj)
end
module_function :StringValue

def `(str) #`
str = StringValue(str) unless str.kind_of?(String)

io = IO.popen(str)
output = io.read
io.close

Rubinius::Type.external_string output
end
module_function :` # `

def =~(other)
nil
end
Loading