diff --git a/java/core/src/main/java/com/google/protobuf/Descriptors.java b/java/core/src/main/java/com/google/protobuf/Descriptors.java index 19fd96b438..dc5e45a9f0 100644 --- a/java/core/src/main/java/com/google/protobuf/Descriptors.java +++ b/java/core/src/main/java/com/google/protobuf/Descriptors.java @@ -1616,6 +1616,15 @@ public final class Descriptors { private final Descriptor extensionScope; private final boolean isProto3Optional; + private enum Sensitivity { + UNKNOWN, + SENSITIVE, + NOT_SENSITIVE + } + + // Caches the result of isSensitive() for performance reasons. + private volatile Sensitivity sensitivity = Sensitivity.UNKNOWN; + // Possibly initialized during cross-linking. private Type type; private Descriptor containingType; @@ -1788,6 +1797,67 @@ public final class Descriptors { file.pool.addSymbol(this); } + @SuppressWarnings("unchecked") // List guaranteed by protobuf runtime. + private boolean isOptionSensitive(FieldDescriptor field, Object value) { + if (field.getType() == Descriptors.FieldDescriptor.Type.ENUM) { + if (field.isRepeated()) { + for (EnumValueDescriptor v : (List) value) { + if (v.getOptions().getDebugRedact()) { + return true; + } + } + } else { + if (((EnumValueDescriptor) value).getOptions().getDebugRedact()) { + return true; + } + } + } else if (field.getJavaType() == Descriptors.FieldDescriptor.JavaType.MESSAGE) { + if (field.isRepeated()) { + for (Message m : (List) value) { + for (Map.Entry entry : m.getAllFields().entrySet()) { + if (isOptionSensitive(entry.getKey(), entry.getValue())) { + return true; + } + } + } + } else { + for (Map.Entry entry : + ((Message) value).getAllFields().entrySet()) { + if (isOptionSensitive(entry.getKey(), entry.getValue())) { + return true; + } + } + } + } + return false; + } + + // Lazily calculates if the field is marked as sensitive. Is only called upon the first + // access of the isSensitive() method. + boolean isSensitive() { + if (sensitivity == Sensitivity.UNKNOWN) { + // If the field is directly marked with debug_redact=true, then it is sensitive. + synchronized (this) { + if (sensitivity == Sensitivity.UNKNOWN) { + boolean isSensitive = proto.getOptions().getDebugRedact(); + if (!isSensitive) { + // Check if the FieldOptions contain any enums that are marked as debug_redact=true, + // either directly or indirectly via a message option. + for (Map.Entry entry : + proto.getOptions().getAllFields().entrySet()) { + if (isOptionSensitive(entry.getKey(), entry.getValue())) { + isSensitive = true; + break; + } + } + } + sensitivity = isSensitive ? Sensitivity.SENSITIVE : Sensitivity.NOT_SENSITIVE; + } + } + } + return sensitivity == Sensitivity.SENSITIVE; + } + /** See {@link FileDescriptor#resolveAllFeatures}. */ private void resolveAllFeatures() throws DescriptorValidationException { resolveFeatures(proto.getOptions().getFeatures()); diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index af9b4d109a..a5c0136ef2 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -568,51 +568,17 @@ public final class TextFormat { } } - private boolean shouldRedactOptionValue(EnumValueDescriptor optionValue) { - if (optionValue.getOptions().hasDebugRedact()) { - return optionValue.getOptions().getDebugRedact(); - } - return false; - } - // The criteria for redacting a field is as follows: 1) The enablingSafeDebugFormat printer - // option - // must be on. 2) The field must be marked by a debug_redact=true option, or is marked by an - // option with an enum value that is marked by a debug_redact=true option. - @SuppressWarnings("unchecked") // List guaranteed by protobuf runtime. + // option must be on. 2) The field must be considered "sensitive". A sensitive field can be + // marked as sensitive via two methods: a) via a direct debug_redact=true annotation on the + // field, b) via an enum field marked with debug_redact=true that is within the proto's + // FieldOptions, either directly or indirectly via a message option. private boolean shouldRedact(final FieldDescriptor field, TextGenerator generator) { // Skip checking if it's sensitive and potentially reporting it if we don't care about either. if (!shouldReport(generator.fieldReporterLevel) && !enablingSafeDebugFormat) { return false; } - boolean isSensitive = false; - if (field.getOptions().hasDebugRedact() && field.getOptions().getDebugRedact()) { - isSensitive = true; - } else { - // Iterate through every option; if it's an enum, we check each enum value for debug_redact. - for (Map.Entry entry : - field.getOptions().getAllFields().entrySet()) { - Descriptors.FieldDescriptor option = entry.getKey(); - if (option.getType() != Descriptors.FieldDescriptor.Type.ENUM) { - continue; - } - if (option.isRepeated()) { - for (EnumValueDescriptor value : (List) entry.getValue()) { - if (shouldRedactOptionValue(value)) { - isSensitive = true; - break; - } - } - } else { - EnumValueDescriptor optionValue = (EnumValueDescriptor) entry.getValue(); - if (shouldRedactOptionValue(optionValue)) { - isSensitive = true; - break; - } - } - } - } - return isSensitive && enablingSafeDebugFormat; + return field.isSensitive() && enablingSafeDebugFormat; } private boolean shouldReport(FieldReporterLevel level) {