Reject empty separator in string.split
Related: #7355 #8726
Closes #8836.
PiperOrigin-RevId: 257614252
diff --git a/site/docs/skylark/backward-compatibility.md b/site/docs/skylark/backward-compatibility.md
index 6efc954..72de9de 100644
--- a/site/docs/skylark/backward-compatibility.md
+++ b/site/docs/skylark/backward-compatibility.md
@@ -17,6 +17,7 @@
* [Dictionary lookup of unhashable types](#dictionary-lookup-of-unhashable-types)
* [Dictionary concatenation](#dictionary-concatenation)
* [String escapes](#string-escapes)
+* [String.split empty separator](#string.split-empty-separator)
* [Load must appear at top of file](#load-must-appear-at-top-of-file)
* [Depset is no longer iterable](#depset-is-no-longer-iterable)
* [Depset union](#depset-union)
@@ -97,6 +98,15 @@
* Default: `false`
* Tracking issue: [#8380](https://github.com/bazelbuild/bazel/issues/8380)
+### String.split empty separator
+
+We are disallowing empty strings as separators to `string.split`. If `sep` is
+the empty string, `split` will fail.
+
+* Flag: `--incompatible_disallow_split_empty_separator`
+* Default: `false`
+* Tracking issue: [#7355](https://github.com/bazelbuild/bazel/issues/7355)
+
### Load must appear at top of file
Previously, the `load` statement could appear anywhere in a `.bzl` file so long
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
index a266ff7..13d233a 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StarlarkSemanticsOptions.java
@@ -329,6 +329,18 @@
public boolean incompatibleDisallowRuleExecutionPlatformConstraintsAllowed;
@Option(
+ name = "incompatible_disallow_split_empty_separator",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
+ effectTags = {OptionEffectTag.BUILD_FILE_SEMANTICS},
+ metadataTags = {
+ OptionMetadataTag.INCOMPATIBLE_CHANGE,
+ OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+ },
+ help = "If set to true, `string.split` will fail if `sep` is the empty string.")
+ public boolean incompatibleDisallowSplitEmptySeparator;
+
+ @Option(
name = "incompatible_string_join_requires_strings",
defaultValue = "true",
documentationCategory = OptionDocumentationCategory.STARLARK_SEMANTICS,
@@ -668,6 +680,7 @@
.incompatibleDoNotSplitLinkingCmdline(incompatibleDoNotSplitLinkingCmdline)
.incompatibleDepsetForLibrariesToLinkGetter(incompatibleDepsetForLibrariesToLinkGetter)
.incompatibleRestrictStringEscapes(incompatibleRestrictStringEscapes)
+ .incompatibleDisallowSplitEmptySeparator(incompatibleDisallowSplitEmptySeparator)
.incompatibleDisallowDictLookupUnhashableKeys(
incompatibleDisallowDictLookupUnhashableKeys)
.build();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
index 67d7b7f..048262e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkSemantics.java
@@ -204,6 +204,8 @@
public abstract boolean incompatibleRestrictStringEscapes();
+ public abstract boolean incompatibleDisallowSplitEmptySeparator();
+
public abstract boolean incompatibleDisallowDictLookupUnhashableKeys();
@Memoized
@@ -280,6 +282,7 @@
.incompatibleDoNotSplitLinkingCmdline(true)
.incompatibleDepsetForLibrariesToLinkGetter(true)
.incompatibleRestrictStringEscapes(false)
+ .incompatibleDisallowSplitEmptySeparator(false)
.incompatibleDisallowDictLookupUnhashableKeys(false)
.build();
@@ -371,6 +374,8 @@
public abstract Builder incompatibleRestrictStringEscapes(boolean value);
+ public abstract Builder incompatibleDisallowSplitEmptySeparator(boolean value);
+
public abstract Builder incompatibleDisallowDictLookupUnhashableKeys(boolean value);
public abstract StarlarkSemantics build();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
index f5f0728..4a6c6f9 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
@@ -329,6 +329,9 @@
public MutableList<String> split(
String self, String sep, Object maxSplitO, Location loc, Environment env)
throws EvalException {
+ if (env.getSemantics().incompatibleDisallowSplitEmptySeparator() && sep.isEmpty()) {
+ throw new EvalException(loc, "Empty separator");
+ }
int maxSplit =
Type.INTEGER.convertOptional(maxSplitO, "'split' argument of 'split'", /*label*/ null, -2);
// + 1 because the last result is the remainder. The default is -2 so that after +1,
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
index 6aa2045..3d50b23 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SkylarkSemanticsConsistencyTest.java
@@ -145,8 +145,9 @@
"--incompatible_disallow_legacy_javainfo=" + rand.nextBoolean(),
"--incompatible_disallow_legacy_java_provider=" + rand.nextBoolean(),
"--incompatible_disallow_old_style_args_add=" + rand.nextBoolean(),
- "--incompatible_disallow_struct_provider_syntax=" + rand.nextBoolean(),
"--incompatible_disallow_rule_execution_platform_constraints_allowed=" + rand.nextBoolean(),
+ "--incompatible_disallow_split_empty_separator=" + rand.nextBoolean(),
+ "--incompatible_disallow_struct_provider_syntax=" + rand.nextBoolean(),
"--incompatible_do_not_split_linking_cmdline=" + rand.nextBoolean(),
"--incompatible_expand_directories=" + rand.nextBoolean(),
"--incompatible_new_actions_api=" + rand.nextBoolean(),
@@ -198,8 +199,9 @@
.incompatibleDisallowLegacyJavaInfo(rand.nextBoolean())
.incompatibleDisallowLegacyJavaProvider(rand.nextBoolean())
.incompatibleDisallowOldStyleArgsAdd(rand.nextBoolean())
- .incompatibleDisallowStructProviderSyntax(rand.nextBoolean())
.incompatibleDisallowRuleExecutionPlatformConstraintsAllowed(rand.nextBoolean())
+ .incompatibleDisallowSplitEmptySeparator(rand.nextBoolean())
+ .incompatibleDisallowStructProviderSyntax(rand.nextBoolean())
.incompatibleDoNotSplitLinkingCmdline(rand.nextBoolean())
.incompatibleExpandDirectories(rand.nextBoolean())
.incompatibleNewActionsApi(rand.nextBoolean())
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index f78d647..5a913d9 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -3100,6 +3100,30 @@
}
@Test
+ public void testSplitEmptySeparatorForbidden() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_split_empty_separator=true");
+
+ scratch.file("test/extension.bzl", "y = 'abc'.split('')");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test:r");
+ assertContainsEvent("Empty separator");
+ }
+
+ @Test
+ public void testSplitEmptySeparator() throws Exception {
+ setSkylarkSemanticsOptions("--incompatible_disallow_split_empty_separator=false");
+
+ scratch.file("test/extension.bzl", "y = 'abc'.split('')");
+
+ scratch.file("test/BUILD", "load('//test:extension.bzl', 'y')", "cc_library(name = 'r')");
+
+ getConfiguredTarget("//test:r");
+ }
+
+ @Test
public void testNoOutputsError() throws Exception {
scratch.file(
"test/skylark/test_rule.bzl",
diff --git a/src/test/starlark/testdata/string_split.sky b/src/test/starlark/testdata/string_split.sky
index e8c5ba1..4cefcd6 100644
--- a/src/test/starlark/testdata/string_split.sky
+++ b/src/test/starlark/testdata/string_split.sky
@@ -4,10 +4,15 @@
assert_eq('a,e,i,o,u'.split(',', 2), ['a', 'e', 'i,o,u'])
assert_eq(' 1 2 3 '.split(' '), ['', '', '1', '', '2', '', '3', '', ''])
+---
+
# rsplit
assert_eq('abcdabef'.rsplit('ab'), ['', 'cd', 'ef'])
assert_eq('google_or_gogol'.rsplit('go'), ['', 'ogle_or_', '', 'l'])
+'abc'.rsplit("") ### Empty separator
+---
+
# rsplit regex
assert_eq('foo/bar.lisp'.rsplit('.'), ['foo/bar', 'lisp'])
assert_eq('foo/bar.?lisp'.rsplit('.?'), ['foo/bar', 'lisp'])