Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: fix java_binary.jvm_flags escaping #7314

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/rules/java:java-rules",
"//src/main/java/com/google/devtools/build/lib/rules/objc",
"//src/main/java/com/google/devtools/build/lib/rules/platform",
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/android",
"//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/apple",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.rules.cpp.CcInfo;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
Expand All @@ -66,6 +67,7 @@
import com.google.devtools.build.lib.rules.java.JavaToolchainProvider;
import com.google.devtools.build.lib.rules.java.JavaUtil;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.OS;
Expand Down Expand Up @@ -265,7 +267,7 @@ public Artifact createStubAction(
List<String> jvmFlags,
Artifact executable,
String javaStartClass,
String javaExecutable) {
String javaExecutable) throws RuleErrorException {
return createStubAction(
ruleContext,
javaCommon,
Expand All @@ -288,7 +290,7 @@ public Artifact createStubAction(
String coverageStartClass,
NestedSetBuilder<Artifact> filesBuilder,
String javaExecutable,
boolean createCoverageMetadataJar) {
boolean createCoverageMetadataJar) throws RuleErrorException {
Preconditions.checkState(ruleContext.getConfiguration().hasFragment(JavaConfiguration.class));

Preconditions.checkNotNull(jvmFlags);
Expand Down Expand Up @@ -399,12 +401,25 @@ public Artifact createStubAction(
arguments.add(Substitution.ofSpaceSeparatedList("%jvm_flags%", jvmFlagsList));

if (OS.getCurrent() == OS.WINDOWS) {
// Bash-tokenize the JVM flags. On Linux/macOS tokenization is achieved by embedding the
// 'jvmFlags' into the java_binary stub script (which is a Bash script) and letting Bash
// tokenize them.
List<String> tokenizedJvmFlags = new ArrayList<>(jvmFlagsList.size());
for (String s : jvmFlags) {
try {
ShellUtils.tokenize(tokenizedJvmFlags, s);
} catch (ShellUtils.TokenizationException e) {
ruleContext.attributeError(
"jvm_flags", "Could not Bash-tokenize \"" + s + "\": " + e.getMessage());
throw new RuleErrorException();
}
}
return createWindowsExeLauncher(
ruleContext,
javaExecutable,
classpath,
javaStartClass,
jvmFlagsList,
tokenizedJvmFlags,
executable);
}

Expand All @@ -418,7 +433,7 @@ private static Artifact createWindowsExeLauncher(
String javaExecutable,
NestedSet<Artifact> classpath,
String javaStartClass,
ImmutableList<String> jvmFlags,
List<String> bashTokenizedJvmFlags,
Artifact javaLauncher) {

LaunchInfo launchInfo =
Expand All @@ -440,7 +455,13 @@ private static Artifact createWindowsExeLauncher(
"classpath",
";",
Iterables.transform(classpath, Artifact.ROOT_RELATIVE_PATH_STRING))
.addJoinedValues("jvm_flags", " ", jvmFlags)
// TODO(laszlocsomor): joining flags on \t means no flag may contain this character,
// otherwise the launcher would split it into two flags. This solution is good enough
// because flags typically don't contain tabs, but we can't rule out the possibility
// that they do. Let's find a better way to embed the JVM flags to the launcher, e.g.
// by using one jvm_flags entry per flag instead of joining all flags as one jvm_flags
// value.
.addJoinedValues("jvm_flags", "\t", bashTokenizedJvmFlags)
.build();

LauncherFileWriteAction.createAndRegister(ruleContext, javaLauncher, launchInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ Artifact createStubAction(
Artifact executable,
String javaStartClass,
String javaExecutable)
throws InterruptedException;
throws InterruptedException, RuleErrorException;

/**
* Same as {@link #createStubAction(RuleContext, JavaCommon, List, Artifact, String, String)}.
Expand All @@ -326,7 +326,7 @@ public Artifact createStubAction(
NestedSetBuilder<Artifact> filesBuilder,
String javaExecutable,
boolean createCoverageMetadataJar)
throws InterruptedException;
throws InterruptedException, RuleErrorException;

/**
* Returns true if {@code createStubAction} considers {@code javaExecutable} as a substitution.
Expand Down
104 changes: 98 additions & 6 deletions src/test/py/bazel/launcher_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,10 @@ def _buildAndCheckArgumentPassing(self, package, target_name):
bin_suffix))))

arguments = ['a', 'a b', '"b"', 'C:\\a\\b\\', '"C:\\a b\\c\\"']
parenthesized_arguments = ['(%s)' % a for a in arguments]
exit_code, stdout, stderr = self.RunProgram([bin1] + arguments)
self.AssertExitCode(exit_code, 0, stderr)
self.assertEqual(stdout, arguments)
self.assertEqual(stdout, parenthesized_arguments)

def testJavaBinaryLauncher(self):
self.ScratchFile('WORKSPACE')
Expand Down Expand Up @@ -248,7 +249,7 @@ def testJavaBinaryArgumentPassing(self):
'public class Main {',
' public static void main(String[] args) {'
' for (String arg : args) {',
' System.out.println(arg);',
' System.out.printf("(%s)%n", arg);',
' }'
' }',
'}',
Expand Down Expand Up @@ -316,7 +317,7 @@ def testShBinaryArgumentPassing(self):
'N=${#args[@]}',
'# Echo each argument',
'for (( i=0;i<$N;i++)); do',
' echo ${args[${i}]}',
' echo "(${args[${i}]})"',
'done',
])
os.chmod(foo_sh, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
Expand Down Expand Up @@ -389,7 +390,7 @@ def testPyBinaryArgumentPassing(self):
self.ScratchFile('foo/bin.py', [
'import sys',
'for arg in sys.argv[1:]:',
' print(arg)',
' print("(%s)" % arg)',
])

self._buildAndCheckArgumentPassing('foo', 'bin')
Expand All @@ -409,7 +410,7 @@ def testWindowsJavaExeLauncher(self):
])
self.ScratchFile('foo/Main.java', [
'public class Main {',
' public static void main(String[] args) {'
' public static void main(String[] args) {',
' System.out.println("helloworld");',
' }',
'}',
Expand Down Expand Up @@ -502,7 +503,7 @@ def testWindowsJavaExeLauncher(self):
self.AssertExitCode(exit_code, 0, stderr)
self.assertIn('local_jdk/bin/java.exe', ''.join(stdout))

my_tmp_dir = self.ScratchDir('my/temp/dir')
my_tmp_dir = self.ScratchDir('my/temp/dir').replace('\\', '/')
exit_code, stdout, stderr = self.RunProgram(
[binary, print_cmd], env_add={'TEST_TMPDIR': my_tmp_dir})
self.AssertExitCode(exit_code, 0, stderr)
Expand Down Expand Up @@ -651,6 +652,97 @@ def AssertRunfilesManifestContains(self, manifest, entry):
return
self.fail('Runfiles manifest "%s" did not contain "%s"' % (manifest, entry))

def testJvmFlagsFromBuildFile(self):
self.ScratchFile('WORKSPACE')
self.ScratchFile('BUILD', [r"""
java_binary(
name = "foo",
srcs = ["Main.java"],
main_class = "Main",
jvm_flags = [
'-Darg0=A',
'-Darg1="A"',
'-Darg2=\'"a"\'',
'-Darg3=\'B\'',
'-Darg4="\'b\'"',
'-Darg5="\\"b\\""',
'-Darg6=\'D E\'',
'-Darg7=\'"d e"\'',
'-Darg8=\'F"G\'',
'-Darg9=\'"F"G"\'',
'-Darg10=\'C:\\H I\'',
'-Darg11=\'"C:\\h i"\'',
'-Darg12=\'C:\\J"K\'',
'-Darg13=\'"C:\\j"k"\'',
'-Darg14=\'C:\\L\\"M\'',
'-Darg15=\'"C:\\l\\"m"\'',
'-Darg16=\'C:\\N O \'',
'-Darg17=\'"C:\\n o "\'',
'-Darg18=\'C:\\P Q\\\'',
'-Darg19=\'"C:\\p q\\"\'',
'-Darg20=\'C:\\R S\\ \'',
'-Darg21=\'"C:\\r s\\ "\'',
'-Darg22=\'C:\\T\\U\\\'',
'-Darg23=\'"C:\\t\\u\\"\'',
'-Darg24=\'C:\\V W\\X\\\'',
'-Darg25=\'"C:\\v w\\x\\"\'',
],
)"""])
self.ScratchFile('Main.java', ["""
public class Main {
public static void main(String[] args) {
for (int i = 0; ; ++i) {
String value = System.getProperty("arg" + i);
if (value == null) {
break;
} else {
System.out.printf("arg%d=(%s)%n", i, value);
}
}
}
}"""])
exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin'])
self.AssertExitCode(exit_code, 0, stderr)
bazel_bin = stdout[0]

exit_code, _, stderr = self.RunBazel(['build', '//:foo'])
self.AssertExitCode(exit_code, 0, stderr)

if self.IsWindows():
foo_path = os.path.abspath(os.path.join(bazel_bin, 'foo.exe'))
else:
foo_path = os.path.abspath(os.path.join(bazel_bin, 'foo'))
exit_code, stdout, stderr = self.RunProgram([foo_path])
self.AssertExitCode(exit_code, 0, stderr)
self.assertListEqual([
"arg0=(A)",
"arg1=(A)",
"arg2=(\"a\")",
"arg3=(B)",
"arg4=('b')",
"arg5=(\"b\")",
"arg6=(D E)",
"arg7=(\"d e\")",
"arg8=(F\"G)",
"arg9=(\"F\"G\")",
"arg10=(C:\\H I)",
"arg11=(\"C:\\h i\")",
"arg12=(C:\\J\"K)",
"arg13=(\"C:\\j\"k\")",
"arg14=(C:\\L\\\"M)",
"arg15=(\"C:\\l\\\"m\")",
"arg16=(C:\\N O )",
"arg17=(\"C:\\n o \")",
"arg18=(C:\\P Q\\)",
"arg19=(\"C:\\p q\\\")",
"arg20=(C:\\R S\\ )",
"arg21=(\"C:\\r s\\ \")",
"arg22=(C:\\T\\U\\)",
"arg23=(\"C:\\t\\u\\\")",
"arg24=(C:\\V W\\X\\)",
"arg25=(\"C:\\v w\\x\\\")"], stdout)



if __name__ == '__main__':
unittest.main()
9 changes: 3 additions & 6 deletions src/tools/launcher/bash_launcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,15 @@ ExitCode BashBinaryLauncher::Launch() {
vector<wstring> origin_args = this->GetCommandlineArguments();
wostringstream bash_command;
wstring bash_main_file = GetBinaryPathWithoutExtension(origin_args[0]);
bash_command << GetEscapedArgument(bash_main_file,
/*escape_backslash = */ true);
bash_command << BashEscapeArg(bash_main_file);
for (int i = 1; i < origin_args.size(); i++) {
bash_command << L' ';
bash_command << GetEscapedArgument(origin_args[i],
/*escape_backslash = */ true);
bash_command << BashEscapeArg(origin_args[i]);
}

vector<wstring> args;
args.push_back(L"-c");
args.push_back(
GetEscapedArgument(bash_command.str(), /*escape_backslash = */ true));
args.push_back(BashEscapeArg(bash_command.str()));
return this->LaunchProcess(bash_binary, args);
}

Expand Down
5 changes: 2 additions & 3 deletions src/tools/launcher/java_launcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ ExitCode JavaBinaryLauncher::Launch() {
jvm_flags.push_back(flag);
}
wstringstream jvm_flags_launch_info_ss(this->GetLaunchInfoByKey(JVM_FLAGS));
while (getline(jvm_flags_launch_info_ss, flag, L' ')) {
while (getline(jvm_flags_launch_info_ss, flag, L'\t')) {
jvm_flags.push_back(flag);
}

Expand Down Expand Up @@ -411,8 +411,7 @@ ExitCode JavaBinaryLauncher::Launch() {
vector<wstring> escaped_arguments;
// Quote the arguments if having spaces
for (const auto& arg : arguments) {
escaped_arguments.push_back(
GetEscapedArgument(arg, /*escape_backslash = */ false));
escaped_arguments.push_back(CreateProcessEscapeArg(arg));
}

ExitCode exit_code = this->LaunchProcess(java_bin, escaped_arguments);
Expand Down
2 changes: 1 addition & 1 deletion src/tools/launcher/python_launcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ExitCode PythonBinaryLauncher::Launch() {

// Escape arguments that has spaces
for (int i = 1; i < args.size(); i++) {
args[i] = GetEscapedArgument(args[i], /*escape_backslash = */ false);
args[i] = CreateProcessEscapeArg(args[i]);
}

return this->LaunchProcess(python_binary, args);
Expand Down
Loading