Skip to content

Commit db47dcd

Browse files
dfa1claude
andcommitted
fix: reject control characters in EncodingId and LayoutId
A control-char ID would write a file PostscriptParser crashes on. Both Custom constructors now scan for ISO control characters, mirroring ColumnName.violation(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ab8bc55 commit db47dcd

5 files changed

Lines changed: 45 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Fixed
1515

16+
- `EncodingId.Custom` and `LayoutId.Custom` now reject wire strings containing ISO control characters, mirroring `ColumnName`. A control-char id previously wrote a corrupt file that `PostscriptParser` crashes on at read time. (internal)
1617
- Parquet files with duplicate column names now compare correctly. The oracle header de-duplicates names with the Rust Vortex writer's algorithm (first occurrence unmodified, Nth repetition gets ` [N]` suffix) and all row access uses column index rather than name. ([#256](https://github.com/dfa1/vortex-java/issues/256))
1718
- Blank column names are now accepted on both the read and write paths. The Rust reference legitimately produces them (e.g. `uci-electricityloaddiagrams20112014` has one at field index 0). `ColumnName` now enforces a symmetric policy: non-null and free of control characters (`\n`, `\t`, U+0000, …); blank names such as `""` or `" "` pass through on both paths. ([#255](https://github.com/dfa1/vortex-java/issues/255))
1819
- `VarBinArray.DictMode` reads dict-value offsets in a ptype-uniform loop: the I32 fast path eliminates the per-row `switch(dictValOffPType)` that blocked C2 vectorization (introduced by #215). ([#243](https://github.com/dfa1/vortex-java/issues/243))

core/src/main/java/io/github/dfa1/vortex/core/model/EncodingId.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,29 @@ public String toString() {
140140

141141
/// A third-party encoding id whose wire string is not part of the [WellKnown] set.
142142
///
143-
/// @param id the wire string of this encoding id; must be non-blank and must not collide
144-
/// with a [WellKnown] wire string
143+
/// @param id the wire string of this encoding id; must be non-blank, free of ISO control
144+
/// characters, and must not collide with a [WellKnown] wire string
145145
record Custom(String id) implements EncodingId {
146146

147-
/// Validates that `id` is a usable custom encoding id.
147+
/// Validates that `id` is a usable custom encoding id. A control character would write a
148+
/// file that [io.github.dfa1.vortex.core.error.VortexException]-raising parsers crash on,
149+
/// so the same policy as [ColumnName#violation(String)] applies here.
148150
///
149151
/// @param id the wire string of this encoding id
150152
/// @throws NullPointerException if `id` is `null`
151-
/// @throws IllegalArgumentException if `id` is blank or matches a [WellKnown] wire string
153+
/// @throws IllegalArgumentException if `id` is blank, contains an ISO control character, or
154+
/// matches a [WellKnown] wire string
152155
public Custom {
153156
Objects.requireNonNull(id, "id");
154157
if (id.isBlank()) {
155158
throw new IllegalArgumentException("encoding id must not be blank");
156159
}
160+
for (int i = 0; i < id.length(); i++) {
161+
if (Character.isISOControl(id.charAt(i))) {
162+
throw new IllegalArgumentException(
163+
"encoding id contains control character U+%04X".formatted((int) id.charAt(i)));
164+
}
165+
}
157166
WellKnown wellKnown = WellKnown.byId(id);
158167
if (wellKnown != null) {
159168
throw new IllegalArgumentException(

core/src/main/java/io/github/dfa1/vortex/core/model/LayoutId.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,29 @@ public String toString() {
8282

8383
/// A third-party layout id whose wire string is not part of the [WellKnown] set.
8484
///
85-
/// @param id the wire string of this layout id; must be non-blank and must not collide
86-
/// with a [WellKnown] wire string
85+
/// @param id the wire string of this layout id; must be non-blank, free of ISO control
86+
/// characters, and must not collide with a [WellKnown] wire string
8787
record Custom(String id) implements LayoutId {
8888

89-
/// Validates that `id` is a usable custom layout id.
89+
/// Validates that `id` is a usable custom layout id. A control character would write a
90+
/// file that [io.github.dfa1.vortex.core.error.VortexException]-raising parsers crash on,
91+
/// so the same policy as [ColumnName#violation(String)] applies here.
9092
///
9193
/// @param id the wire string of this layout id
9294
/// @throws NullPointerException if `id` is `null`
93-
/// @throws IllegalArgumentException if `id` is blank or matches a [WellKnown] wire string
95+
/// @throws IllegalArgumentException if `id` is blank, contains an ISO control character, or
96+
/// matches a [WellKnown] wire string
9497
public Custom {
9598
Objects.requireNonNull(id, "id");
9699
if (id.isBlank()) {
97100
throw new IllegalArgumentException("layout id must not be blank");
98101
}
102+
for (int i = 0; i < id.length(); i++) {
103+
if (Character.isISOControl(id.charAt(i))) {
104+
throw new IllegalArgumentException(
105+
"layout id contains control character U+%04X".formatted((int) id.charAt(i)));
106+
}
107+
}
99108
WellKnown wellKnown = WellKnown.byId(id);
100109
if (wellKnown != null) {
101110
throw new IllegalArgumentException(

core/src/test/java/io/github/dfa1/vortex/core/model/EncodingIdTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ void construct_wellKnownId_throwsIllegalArgumentException() {
7070
.isInstanceOf(IllegalArgumentException.class)
7171
.hasMessageContaining("VORTEX_PRIMITIVE");
7272
}
73+
74+
@ParameterizedTest
75+
@ValueSource(strings = {"a\nb", "tab\there"})
76+
void custom_controlCharId_throwsIllegalArgumentException(String id) {
77+
// Given / When / Then — a control char would write a file PostscriptParser crashes on,
78+
// so Custom rejects it just as ColumnName does
79+
assertThatThrownBy(() -> new EncodingId.Custom(id))
80+
.isInstanceOf(IllegalArgumentException.class);
81+
}
7382
}
7483

7584
@Nested

core/src/test/java/io/github/dfa1/vortex/core/model/LayoutIdTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ void construct_wellKnownId_throwsIllegalArgumentException() {
7070
.isInstanceOf(IllegalArgumentException.class)
7171
.hasMessageContaining("FLAT");
7272
}
73+
74+
@ParameterizedTest
75+
@ValueSource(strings = {"a\nb", "tab\there"})
76+
void custom_controlCharId_throwsIllegalArgumentException(String id) {
77+
// Given / When / Then — a control char would write a file PostscriptParser crashes on,
78+
// so Custom rejects it just as ColumnName does
79+
assertThatThrownBy(() -> new LayoutId.Custom(id))
80+
.isInstanceOf(IllegalArgumentException.class);
81+
}
7382
}
7483

7584
@Nested

0 commit comments

Comments
 (0)