Add UTF-8 validation documentation and tests for proto2, proto3, and editions for Java Protobuf

PiperOrigin-RevId: 931326656
This commit is contained in:
Protobuf Team Bot 2026-06-12 14:13:20 -07:00 committed by Copybara-Service
parent b721c4cad5
commit bf5f97bcbb
11 changed files with 1278 additions and 0 deletions

View file

@ -0,0 +1,180 @@
package com.google.protobuf.utf8validation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Utf8ValidationDynamicMessageTest {
enum Validates {
NEVER,
ALWAYS
}
private final Case testCase;
public Utf8ValidationDynamicMessageTest(Case testCase) {
this.testCase = testCase;
}
@Parameters(name = "{0}")
public static List<Object[]> data() {
List<Object[]> p = new ArrayList<>();
Descriptor d;
d = Utf8TestProto2.getDescriptor();
Case.create(p, d, "file_unset_field_unset", Validates.NEVER);
Case.create(p, d, "file_unset_field_enforced", Validates.NEVER);
Case.create(p, d, "ext_file_unset_field_unset", Validates.NEVER);
Case.create(p, d, "ext_file_unset_field_enforced", Validates.NEVER);
d = Utf8TestProto2Unchecked.getDescriptor();
Case.create(p, d, "file_unchecked_field_unset", Validates.NEVER);
Case.create(p, d, "file_unchecked_field_enforced", Validates.NEVER);
Case.create(p, d, "ext_file_unchecked_field_unset", Validates.NEVER);
Case.create(p, d, "ext_file_unchecked_field_enforced", Validates.NEVER);
d = Utf8TestProto2Checked.getDescriptor();
Case.create(p, d, "file_checked_field_unset", Validates.ALWAYS);
Case.create(p, d, "file_checked_field_enforced", Validates.ALWAYS);
Case.create(p, d, "ext_file_checked_field_unset", Validates.ALWAYS);
Case.create(p, d, "ext_file_checked_field_enforced", Validates.ALWAYS);
d = Utf8TestProto3.getDescriptor();
Case.create(p, d, "file_unset_field_unset", Validates.ALWAYS);
Case.create(p, d, "file_unset_field_enforced", Validates.ALWAYS);
d = Utf8TestProto3Unchecked.getDescriptor();
Case.create(p, d, "file_unchecked_field_unset", Validates.ALWAYS);
Case.create(p, d, "file_unchecked_field_enforced", Validates.ALWAYS);
d = Utf8TestProto3Checked.getDescriptor();
Case.create(p, d, "file_checked_field_unset", Validates.ALWAYS);
Case.create(p, d, "file_checked_field_enforced", Validates.ALWAYS);
d = Utf8TestEdition2023.getDescriptor();
Case.create(p, d, "global_unset_java_unset", Validates.ALWAYS);
Case.create(p, d, "global_unset_java_default", Validates.ALWAYS);
Case.create(p, d, "global_unset_java_verify", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_unset", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_default", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_verify", Validates.ALWAYS);
Case.create(p, d, "global_none_java_unset", Validates.NEVER);
Case.create(p, d, "global_none_java_default", Validates.NEVER);
Case.create(p, d, "global_none_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_unset", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_default", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_unset", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_default", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_none_java_unset", Validates.NEVER);
Case.create(p, d, "ext_global_none_java_default", Validates.NEVER);
Case.create(p, d, "ext_global_none_java_verify", Validates.ALWAYS);
d = Utf8TestEdition2024.getDescriptor();
Case.create(p, d, "global_unset_java_unset", Validates.ALWAYS);
Case.create(p, d, "global_unset_java_default", Validates.ALWAYS);
Case.create(p, d, "global_unset_java_verify", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_unset", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_default", Validates.ALWAYS);
Case.create(p, d, "global_verify_java_verify", Validates.ALWAYS);
Case.create(p, d, "global_none_java_unset", Validates.NEVER);
Case.create(p, d, "global_none_java_default", Validates.NEVER);
Case.create(p, d, "global_none_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_unset", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_default", Validates.ALWAYS);
Case.create(p, d, "ext_global_unset_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_unset", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_default", Validates.ALWAYS);
Case.create(p, d, "ext_global_verify_java_verify", Validates.ALWAYS);
Case.create(p, d, "ext_global_none_java_unset", Validates.NEVER);
Case.create(p, d, "ext_global_none_java_default", Validates.NEVER);
Case.create(p, d, "ext_global_none_java_verify", Validates.ALWAYS);
return p;
}
@Test
public void test() throws Exception {
FieldDescriptor field = testCase.getFieldDescriptor();
byte[] serializedPayload = buildSerialized(field.getNumber());
ExtensionRegistry registry = ExtensionRegistry.newInstance();
if (field.isExtension()) {
registry.add(field);
}
if (testCase.expectedToValidate()) {
assertThrows(
InvalidProtocolBufferException.class,
() -> DynamicMessage.parseFrom(testCase.descriptor, serializedPayload, registry));
} else {
DynamicMessage msg =
DynamicMessage.parseFrom(testCase.descriptor, serializedPayload, registry);
assertTrue(msg.hasField(field));
assertEquals("\uFFFD\uFFFD", msg.getField(field));
}
}
private static byte[] buildSerialized(int fieldNumber) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
CodedOutputStream codedOut = CodedOutputStream.newInstance(out);
codedOut.writeByteArray(fieldNumber, new byte[] {(byte) 0xC0, (byte) 0x80});
codedOut.flush();
return out.toByteArray();
}
private static class Case {
final Descriptor descriptor;
final String fieldName;
final Validates expected;
static void create(
List<Object[]> params, Descriptor descriptor, String fieldName, Validates expected) {
params.add(new Object[] {new Case(descriptor, fieldName, expected)});
}
private Case(Descriptor descriptor, String fieldName, Validates expected) {
this.descriptor = descriptor;
this.fieldName = fieldName;
this.expected = expected;
}
@Override
public String toString() {
return String.format(
"%s#%s-->%s",
descriptor.getName(),
fieldName,
expected == Validates.ALWAYS ? "validates" : "doesNotValidate");
}
FieldDescriptor getFieldDescriptor() {
FieldDescriptor fieldDescriptor = descriptor.findFieldByName(fieldName);
if (fieldDescriptor == null) {
fieldDescriptor = descriptor.getFile().findExtensionByName(fieldName);
}
return fieldDescriptor;
}
boolean expectedToValidate() {
return expected == Validates.ALWAYS;
}
}
}

View file

@ -0,0 +1,751 @@
package com.google.protobuf.utf8validation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.ExtensionLite;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Utf8ValidationGeneratedMessageTest {
private static final boolean isLite =
true
// BEGIN FULL-RUNTIME
&& false
// END FULL-RUNTIME
;
enum Validates {
NEVER,
ALWAYS,
FULL_RUNTIME_ONLY
}
private final Case<?> testCase;
public Utf8ValidationGeneratedMessageTest(Case<?> testCase) {
this.testCase = testCase;
}
@Parameters(name = "{0}")
public static List<Case<?>> data() throws Exception {
List<Case<?>> params = new ArrayList<>();
addProto2(params);
addProto3(params);
addEditions2023(params);
addEditions2024(params);
return params;
}
private static void addProto2(List<Case<?>> params) {
// java_string_check_utf8: {unset, false, true}
// x enforce_utf8: {unset, false, true}
// x {regular, extension}
// TODO: should be 18 once our test proto is on the allowlist for
// enforce_utf8=false
final int casesToAdd = 12;
final int casesBefore = params.size();
// java_string_check_utf8: unset
Case.create(
params,
"proto2 java_string_check_utf8=unset enforce_utf8=unset",
Utf8TestProto2.FILE_UNSET_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto2::parseFrom,
Utf8TestProto2::hasFileUnsetFieldUnset,
Utf8TestProto2::getFileUnsetFieldUnset,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=unset enforce_utf8=true",
Utf8TestProto2.FILE_UNSET_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto2::parseFrom,
Utf8TestProto2::hasFileUnsetFieldEnforced,
Utf8TestProto2::getFileUnsetFieldEnforced,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=unset enforce_utf8=unset extension",
Utf8TestProto2Proto.extFileUnsetFieldUnset,
Utf8TestProto2::parseFrom,
Utf8TestProto2::hasExtension,
Utf8TestProto2::getExtension,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=unset enforce_utf8=true extension",
Utf8TestProto2Proto.extFileUnsetFieldEnforced,
Utf8TestProto2::parseFrom,
Utf8TestProto2::hasExtension,
Utf8TestProto2::getExtension,
Validates.NEVER);
// java_string_check_utf8: false
Case.create(
params,
"proto2 java_string_check_utf8=false enforce_utf8=unset",
Utf8TestProto2Unchecked.FILE_UNCHECKED_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto2Unchecked::parseFrom,
Utf8TestProto2Unchecked::hasFileUncheckedFieldUnset,
Utf8TestProto2Unchecked::getFileUncheckedFieldUnset,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=false enforce_utf8=true",
Utf8TestProto2Unchecked.FILE_UNCHECKED_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto2Unchecked::parseFrom,
Utf8TestProto2Unchecked::hasFileUncheckedFieldEnforced,
Utf8TestProto2Unchecked::getFileUncheckedFieldEnforced,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=false enforce_utf8=unset extension",
Utf8TestProto2UncheckedProto.extFileUncheckedFieldUnset,
Utf8TestProto2Unchecked::parseFrom,
Utf8TestProto2Unchecked::hasExtension,
Utf8TestProto2Unchecked::getExtension,
Validates.NEVER);
Case.create(
params,
"proto2 java_string_check_utf8=false enforce_utf8=true extension",
Utf8TestProto2UncheckedProto.extFileUncheckedFieldEnforced,
Utf8TestProto2Unchecked::parseFrom,
Utf8TestProto2Unchecked::hasExtension,
Utf8TestProto2Unchecked::getExtension,
Validates.NEVER);
// java_string_check_utf8: true
Case.create(
params,
"proto2 java_string_check_utf8=true enforce_utf8=unset",
Utf8TestProto2Checked.FILE_CHECKED_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto2Checked::parseFrom,
Utf8TestProto2Checked::hasFileCheckedFieldUnset,
Utf8TestProto2Checked::getFileCheckedFieldUnset,
Validates.ALWAYS);
Case.create(
params,
"proto2 java_string_check_utf8=true enforce_utf8=true",
Utf8TestProto2Checked.FILE_CHECKED_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto2Checked::parseFrom,
Utf8TestProto2Checked::hasFileCheckedFieldEnforced,
Utf8TestProto2Checked::getFileCheckedFieldEnforced,
Validates.ALWAYS);
Case.create(
params,
"proto2 java_string_check_utf8=true enforce_utf8=unset extension",
Utf8TestProto2CheckedProto.extFileCheckedFieldUnset,
Utf8TestProto2Checked::parseFrom,
Utf8TestProto2Checked::hasExtension,
Utf8TestProto2Checked::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"proto2 java_string_check_utf8=true enforce_utf8=true extension",
Utf8TestProto2CheckedProto.extFileCheckedFieldEnforced,
Utf8TestProto2Checked::parseFrom,
Utf8TestProto2Checked::hasExtension,
Utf8TestProto2Checked::getExtension,
Validates.FULL_RUNTIME_ONLY);
if (casesBefore + casesToAdd != params.size()) {
throw new IllegalStateException(
String.format("Expected %d cases but got %d", casesBefore + casesToAdd, params.size()));
}
}
private static void addProto3(List<Case<?>> params) {
// java_string_check_utf8: {unset, false, true}
// x enforce_utf8: {unset, false, true}
// No extensions for proto3.
// TODO: should be 9 once our test proto is on the allowlist for
// enforce_utf8=false
final int casesToAdd = 6;
final int casesBefore = params.size();
// java_string_check_utf8: unset
Case.create(
params,
"proto3 java_string_check_utf8=unset enforce_utf8=unset",
Utf8TestProto3.FILE_UNSET_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto3::parseFrom,
Utf8TestProto3::hasFileUnsetFieldUnset,
Utf8TestProto3::getFileUnsetFieldUnset,
Validates.ALWAYS);
Case.create(
params,
"proto3 java_string_check_utf8=unset enforce_utf8=true",
Utf8TestProto3.FILE_UNSET_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto3::parseFrom,
Utf8TestProto3::hasFileUnsetFieldEnforced,
Utf8TestProto3::getFileUnsetFieldEnforced,
Validates.ALWAYS);
// java_string_check_utf8: false
Case.create(
params,
"proto3 java_string_check_utf8=false enforce_utf8=unset",
Utf8TestProto3Unchecked.FILE_UNCHECKED_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto3Unchecked::parseFrom,
Utf8TestProto3Unchecked::hasFileUncheckedFieldUnset,
Utf8TestProto3Unchecked::getFileUncheckedFieldUnset,
Validates.ALWAYS);
Case.create(
params,
"proto3 java_string_check_utf8=false enforce_utf8=true",
Utf8TestProto3Unchecked.FILE_UNCHECKED_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto3Unchecked::parseFrom,
Utf8TestProto3Unchecked::hasFileUncheckedFieldEnforced,
Utf8TestProto3Unchecked::getFileUncheckedFieldEnforced,
Validates.ALWAYS);
// java_string_check_utf8: true
Case.create(
params,
"proto3 java_string_check_utf8=true enforce_utf8=unset",
Utf8TestProto3Checked.FILE_CHECKED_FIELD_UNSET_FIELD_NUMBER,
Utf8TestProto3Checked::parseFrom,
Utf8TestProto3Checked::hasFileCheckedFieldUnset,
Utf8TestProto3Checked::getFileCheckedFieldUnset,
Validates.ALWAYS);
Case.create(
params,
"proto3 java_string_check_utf8=true enforce_utf8=true",
Utf8TestProto3Checked.FILE_CHECKED_FIELD_ENFORCED_FIELD_NUMBER,
Utf8TestProto3Checked::parseFrom,
Utf8TestProto3Checked::hasFileCheckedFieldEnforced,
Utf8TestProto3Checked::getFileCheckedFieldEnforced,
Validates.ALWAYS);
if (casesBefore + casesToAdd != params.size()) {
throw new IllegalStateException(
String.format("Expected %d cases but got %d", casesBefore + casesToAdd, params.size()));
}
}
private static void addEditions2023(List<Case<?>> params) {
// features.utf8_validation: {unset, verify, none}
// x features.(pb.java).utf8_validation: {unset, default, verify}
// x {regular, extension}
final int casesToAdd = 18;
final int casesBefore = params.size();
// features.utf8_validation: unset
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=unset",
Utf8TestEdition2023.GLOBAL_UNSET_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalUnsetJavaUnset,
Utf8TestEdition2023::getGlobalUnsetJavaUnset,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2023.GLOBAL_UNSET_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalUnsetJavaDefault,
Utf8TestEdition2023::getGlobalUnsetJavaDefault,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2023.GLOBAL_UNSET_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalUnsetJavaVerify,
Utf8TestEdition2023::getGlobalUnsetJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2023Proto.extGlobalUnsetJavaUnset,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2023Proto.extGlobalUnsetJavaDefault,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2023 features.utf8_validation=unset features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2023Proto.extGlobalUnsetJavaVerify,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
// features.utf8_validation: verify
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=unset",
Utf8TestEdition2023.GLOBAL_VERIFY_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalVerifyJavaUnset,
Utf8TestEdition2023::getGlobalVerifyJavaUnset,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2023.GLOBAL_VERIFY_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalVerifyJavaDefault,
Utf8TestEdition2023::getGlobalVerifyJavaDefault,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2023.GLOBAL_VERIFY_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalVerifyJavaVerify,
Utf8TestEdition2023::getGlobalVerifyJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2023Proto.extGlobalVerifyJavaUnset,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2023Proto.extGlobalVerifyJavaDefault,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2023 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2023Proto.extGlobalVerifyJavaVerify,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
// features.utf8_validation: none
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=unset",
Utf8TestEdition2023.GLOBAL_NONE_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalNoneJavaUnset,
Utf8TestEdition2023::getGlobalNoneJavaUnset,
Validates.NEVER);
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2023.GLOBAL_NONE_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalNoneJavaDefault,
Utf8TestEdition2023::getGlobalNoneJavaDefault,
Validates.NEVER);
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2023.GLOBAL_NONE_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasGlobalNoneJavaVerify,
Utf8TestEdition2023::getGlobalNoneJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2023Proto.extGlobalNoneJavaUnset,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.NEVER);
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2023Proto.extGlobalNoneJavaDefault,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.NEVER);
Case.create(
params,
"edition2023 features.utf8_validation=NONE features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2023Proto.extGlobalNoneJavaVerify,
Utf8TestEdition2023::parseFrom,
Utf8TestEdition2023::hasExtension,
Utf8TestEdition2023::getExtension,
Validates.FULL_RUNTIME_ONLY);
if (casesBefore + casesToAdd != params.size()) {
throw new IllegalStateException(
String.format("Expected %d cases but got %d", casesBefore + casesToAdd, params.size()));
}
}
private static void addEditions2024(List<Case<?>> params) {
// features.utf8_validation: {unset, verify, none}
// x features.(pb.java).utf8_validation: {unset, default, verify}
// x {regular, extension}
final int casesToAdd = 18;
final int casesBefore = params.size();
// features.utf8_validation: unset
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=unset",
Utf8TestEdition2024.GLOBAL_UNSET_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalUnsetJavaUnset,
Utf8TestEdition2024::getGlobalUnsetJavaUnset,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2024.GLOBAL_UNSET_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalUnsetJavaDefault,
Utf8TestEdition2024::getGlobalUnsetJavaDefault,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2024.GLOBAL_UNSET_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalUnsetJavaVerify,
Utf8TestEdition2024::getGlobalUnsetJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2024Proto.extGlobalUnsetJavaUnset,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2024Proto.extGlobalUnsetJavaDefault,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2024 features.utf8_validation=unset features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2024Proto.extGlobalUnsetJavaVerify,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
// features.utf8_validation: verify
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=unset",
Utf8TestEdition2024.GLOBAL_VERIFY_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalVerifyJavaUnset,
Utf8TestEdition2024::getGlobalVerifyJavaUnset,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2024.GLOBAL_VERIFY_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalVerifyJavaDefault,
Utf8TestEdition2024::getGlobalVerifyJavaDefault,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2024.GLOBAL_VERIFY_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalVerifyJavaVerify,
Utf8TestEdition2024::getGlobalVerifyJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2024Proto.extGlobalVerifyJavaUnset,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2024Proto.extGlobalVerifyJavaDefault,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
Case.create(
params,
"edition2024 features.utf8_validation=VERIFY features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2024Proto.extGlobalVerifyJavaVerify,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
// features.utf8_validation: none
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=unset",
Utf8TestEdition2024.GLOBAL_NONE_JAVA_UNSET_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalNoneJavaUnset,
Utf8TestEdition2024::getGlobalNoneJavaUnset,
Validates.NEVER);
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=DEFAULT",
Utf8TestEdition2024.GLOBAL_NONE_JAVA_DEFAULT_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalNoneJavaDefault,
Utf8TestEdition2024::getGlobalNoneJavaDefault,
Validates.NEVER);
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=VERIFY",
Utf8TestEdition2024.GLOBAL_NONE_JAVA_VERIFY_FIELD_NUMBER,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasGlobalNoneJavaVerify,
Utf8TestEdition2024::getGlobalNoneJavaVerify,
Validates.ALWAYS);
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=unset"
+ " extension",
Utf8TestEdition2024Proto.extGlobalNoneJavaUnset,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.NEVER);
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=DEFAULT"
+ " extension",
Utf8TestEdition2024Proto.extGlobalNoneJavaDefault,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.NEVER);
Case.create(
params,
"edition2024 features.utf8_validation=NONE features.(pb.java).utf8_validation=VERIFY"
+ " extension",
Utf8TestEdition2024Proto.extGlobalNoneJavaVerify,
Utf8TestEdition2024::parseFrom,
Utf8TestEdition2024::hasExtension,
Utf8TestEdition2024::getExtension,
Validates.FULL_RUNTIME_ONLY);
if (casesBefore + casesToAdd != params.size()) {
throw new IllegalStateException(
String.format("Expected %d cases but got %d", casesBefore + casesToAdd, params.size()));
}
}
@Test
public void testGeneratedMessageUtf8Validation() throws Exception {
byte[] serializedPayload = buildSerialized(testCase.fieldNumber);
if (testCase.expectedToValidate()) {
assertThrows(InvalidProtocolBufferException.class, () -> testCase.parse(serializedPayload));
} else {
MessageLite msg = testCase.parse(serializedPayload);
assertTrue(testCase.has(msg));
assertEquals("\uFFFD\uFFFD", testCase.get(msg));
}
}
private static byte[] buildSerialized(int fieldNumber) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
CodedOutputStream codedOut = CodedOutputStream.newInstance(out);
codedOut.writeByteArray(fieldNumber, new byte[] {(byte) 0xC0, (byte) 0x80});
codedOut.flush();
return out.toByteArray();
}
private interface MessageParser<T extends MessageLite> {
T parse(byte[] data, ExtensionRegistryLite registry) throws InvalidProtocolBufferException;
}
private static class Case<T extends MessageLite> {
private final String label;
private final int fieldNumber;
private final ExtensionRegistryLite registry;
private final MessageParser<T> parser;
private final Predicate<T> hazzer;
private final Function<T, String> getter;
private final Validates expected;
static <T extends MessageLite> void create(
List<Case<?>> params,
String syntax,
int fieldNumber,
MessageParser<T> parser,
Predicate<T> hazzer,
Function<T, String> getter,
Validates expected) {
params.add(
new Case<T>(
syntax,
ExtensionRegistryLite.getEmptyRegistry(),
fieldNumber,
parser,
hazzer,
getter,
expected));
}
static <T extends MessageLite> void create(
List<Case<?>> params,
String label,
ExtensionLite<T, String> extensionDescriptor,
MessageParser<T> parser,
BiFunction<? super T, ExtensionLite<T, String>, Boolean> hazzer,
BiFunction<? super T, ExtensionLite<T, String>, String> getter,
Validates expected) {
ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
registry.add(extensionDescriptor);
params.add(
new Case<T>(
label,
registry,
extensionDescriptor.getNumber(),
parser,
(T msg) -> hazzer.apply(msg, extensionDescriptor),
(T msg) -> getter.apply(msg, extensionDescriptor),
expected));
}
private Case(
String label,
ExtensionRegistryLite registry,
int fieldNumber,
MessageParser<T> parser,
Predicate<T> hazzer,
Function<T, String> getter,
Validates expected) {
this.label = label;
this.registry = registry;
this.fieldNumber = fieldNumber;
this.parser = parser;
this.hazzer = hazzer;
this.getter = getter;
this.expected = expected;
}
@Override
public String toString() {
return String.format(
"%s-->%s", label, expectedToValidate() ? "validates" : "doesNotValidate");
}
T parse(byte[] data) throws InvalidProtocolBufferException {
return parser.parse(data, registry);
}
boolean expectedToValidate() {
return expected == Validates.ALWAYS || (!isLite && expected == Validates.FULL_RUNTIME_ONLY);
}
/**
* @param msg must be of type {@code T} or test will fail anyway.
*/
@SuppressWarnings("unchecked")
boolean has(MessageLite msg) {
return hazzer.test((T) msg);
}
/**
* @param msg must be of type {@code T} or test will fail anyway.
*/
@SuppressWarnings("unchecked")
String get(MessageLite msg) {
return getter.apply((T) msg);
}
}
}

View file

@ -0,0 +1,25 @@
# Remove code enclosed by "BEGIN FULL-RUNTIME" and "END FULL-RUNTIME" to
# create the lite-only version of a test file.
BEGIN {
in_full_runtime = 0;
}
/BEGIN FULL-RUNTIME/ {
in_full_runtime = 1;
next;
}
/END FULL-RUNTIME/ {
in_full_runtime = 0;
next;
}
in_full_runtime {
# Skip full runtime code path.
next;
}
{
print;
}

View file

@ -0,0 +1,85 @@
edition = "2023";
package utf8validation.edition2023;
import "google/protobuf/java_features.proto";
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestEdition2023Proto";
option java_multiple_files = true;
// Naming convention for test fields:
// global_[features.utf8_validation state {unset, verify,
// none}]_java_[features.(pb.java).utf8_validation state {unset, verify,
// default}]
message Utf8TestEdition2023 {
// Regular fields
string global_unset_java_unset = 1;
string global_unset_java_default = 2
[features.(pb.java).utf8_validation = DEFAULT];
string global_unset_java_verify = 3
[features.(pb.java).utf8_validation = VERIFY];
string global_verify_java_unset = 4 [features.utf8_validation = VERIFY];
string global_verify_java_default = 5 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = DEFAULT
];
string global_verify_java_verify = 6 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = VERIFY
];
string global_none_java_unset = 7 [features.utf8_validation = NONE];
string global_none_java_default = 8 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = DEFAULT
];
string global_none_java_verify = 9 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = VERIFY
];
extensions 100 to 200;
}
extend Utf8TestEdition2023 {
string ext_global_unset_java_unset = 101;
string ext_global_unset_java_default = 102
[features.(pb.java).utf8_validation = DEFAULT];
string ext_global_unset_java_verify = 103
[features.(pb.java).utf8_validation = VERIFY];
string ext_global_verify_java_unset = 104 [features.utf8_validation = VERIFY];
string ext_global_verify_java_default = 105 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = DEFAULT
];
string ext_global_verify_java_verify = 106 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = VERIFY
];
string ext_global_none_java_unset = 107 [features.utf8_validation = NONE];
string ext_global_none_java_default = 108 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = DEFAULT
];
string ext_global_none_java_verify = 109 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = VERIFY
];
}

View file

@ -0,0 +1,83 @@
edition = "2024";
package utf8validation.edition2024;
import "google/protobuf/java_features.proto";
option java_package = "com.google.protobuf.utf8validation";
// Naming convention for test fields:
// global_[features.utf8_validation state {unset, verify,
// none}]_java_[features.(pb.java).utf8_validation state {unset, verify,
// default}]
message Utf8TestEdition2024 {
// Regular fields
string global_unset_java_unset = 1;
string global_unset_java_default = 2
[features.(pb.java).utf8_validation = DEFAULT];
string global_unset_java_verify = 3
[features.(pb.java).utf8_validation = VERIFY];
string global_verify_java_unset = 4 [features.utf8_validation = VERIFY];
string global_verify_java_default = 5 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = DEFAULT
];
string global_verify_java_verify = 6 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = VERIFY
];
string global_none_java_unset = 7 [features.utf8_validation = NONE];
string global_none_java_default = 8 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = DEFAULT
];
string global_none_java_verify = 9 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = VERIFY
];
extensions 100 to 200;
}
extend Utf8TestEdition2024 {
string ext_global_unset_java_unset = 101;
string ext_global_unset_java_default = 102
[features.(pb.java).utf8_validation = DEFAULT];
string ext_global_unset_java_verify = 103
[features.(pb.java).utf8_validation = VERIFY];
string ext_global_verify_java_unset = 104 [features.utf8_validation = VERIFY];
string ext_global_verify_java_default = 105 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = DEFAULT
];
string ext_global_verify_java_verify = 106 [
features.utf8_validation = VERIFY,
features.(pb.java).utf8_validation = VERIFY
];
string ext_global_none_java_unset = 107 [features.utf8_validation = NONE];
string ext_global_none_java_default = 108 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = DEFAULT
];
string ext_global_none_java_verify = 109 [
features.utf8_validation = NONE,
features.(pb.java).utf8_validation = VERIFY
];
}

View file

@ -0,0 +1,31 @@
syntax = "proto2";
package utf8validation.proto2;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto2Proto";
option java_multiple_files = true;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto2 {
optional string file_unset_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_unset_field_unenforced = 2 [enforce_utf8 = false];
optional string file_unset_field_enforced = 3 [enforce_utf8 = true];
extensions 100 to 200;
}
extend Utf8TestProto2 {
optional string ext_file_unset_field_unset = 100;
// TODO: Enable once allowlist entry is built into protoc.
// optional string ext_file_unset_field_unenforced = 101 [enforce_utf8 =
// false];
optional string ext_file_unset_field_enforced = 102 [enforce_utf8 = true];
}

View file

@ -0,0 +1,32 @@
syntax = "proto2";
package utf8validation.proto2;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto2CheckedProto";
option java_multiple_files = true;
option java_string_check_utf8 = true;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto2Checked {
optional string file_checked_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_checked_field_unenforced = 2 [enforce_utf8 = false];
optional string file_checked_field_enforced = 3 [enforce_utf8 = true];
extensions 100 to 200;
}
extend Utf8TestProto2Checked {
optional string ext_file_checked_field_unset = 100;
// TODO: Enable once allowlist entry is built into protoc.
// optional string ext_file_checked_field_unenforced = 101 [enforce_utf8 =
// false];
optional string ext_file_checked_field_enforced = 102 [enforce_utf8 = true];
}

View file

@ -0,0 +1,32 @@
syntax = "proto2";
package utf8validation.proto2;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto2UncheckedProto";
option java_multiple_files = true;
option java_string_check_utf8 = false;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto2Unchecked {
optional string file_unchecked_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_unchecked_field_unenforced = 2 [enforce_utf8 = false];
optional string file_unchecked_field_enforced = 3 [enforce_utf8 = true];
extensions 100 to 200;
}
extend Utf8TestProto2Unchecked {
optional string ext_file_unchecked_field_unset = 100;
// TODO: Enable once allowlist entry is built into protoc.
// optional string ext_file_unchecked_field_unenforced = 101 [enforce_utf8 =
// false];
optional string ext_file_unchecked_field_enforced = 102 [enforce_utf8 = true];
}

View file

@ -0,0 +1,19 @@
syntax = "proto3";
package utf8validation.proto3;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto3Proto";
option java_multiple_files = true;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto3 {
optional string file_unset_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_unset_field_unenforced = 2 [enforce_utf8 = false];
optional string file_unset_field_enforced = 3 [enforce_utf8 = true];
}

View file

@ -0,0 +1,20 @@
syntax = "proto3";
package utf8validation.proto3;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto3CheckedProto";
option java_multiple_files = true;
option java_string_check_utf8 = true;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto3Checked {
optional string file_checked_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_checked_field_unenforced = 2 [enforce_utf8 = false];
optional string file_checked_field_enforced = 3 [enforce_utf8 = true];
}

View file

@ -0,0 +1,20 @@
syntax = "proto3";
package utf8validation.proto3;
option java_package = "com.google.protobuf.utf8validation";
option java_outer_classname = "Utf8TestProto3UncheckedProto";
option java_multiple_files = true;
option java_string_check_utf8 = false;
// Naming convention for test fields:
// file_[java_string_check_utf8 state {unset, false, true}]_field_[enforce_utf8
// state {unset, enforced}]
message Utf8TestProto3Unchecked {
optional string file_unchecked_field_unset = 1;
// TODO: Enable once allowlist entry is built into protoc.
// optional string file_unchecked_field_unenforced = 2 [enforce_utf8 = false];
optional string file_unchecked_field_enforced = 3 [enforce_utf8 = true];
}