Validate that the start and end numbers of reserved and of extensions are positive and that end >= start. Change-Id: I94377f31f35a9fd15500bda93b2af1badd8efb39
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java new file mode 100644 index 0000000..a04cd27 --- /dev/null +++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator_checkIndexRangeBounds.java
@@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015 Google Inc. + * + * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse + * Public License v1.0 which accompanies this distribution, and is available at + * + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.google.eclipse.protobuf.validation; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static com.google.eclipse.protobuf.junit.core.UnitTestModule.unitTestModule; +import static com.google.eclipse.protobuf.junit.core.XtextRule.overrideRuntimeModuleWith; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.validation.ValidationMessageAcceptor; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.eclipse.protobuf.junit.core.XtextRule; +import com.google.eclipse.protobuf.protobuf.IndexRange; +import com.google.eclipse.protobuf.protobuf.ProtobufPackage; +import com.google.inject.Inject; + +public class ProtobufJavaValidator_checkIndexRangeBounds { + @Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule()); + + @Inject private ProtobufJavaValidator validator; + private ValidationMessageAcceptor messageAcceptor; + + @Before public void setUp() { + messageAcceptor = mock(ValidationMessageAcceptor.class); + validator.setMessageAcceptor(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved -2 to 2; + // } + @Test public void should_error_on_negative_bounds() { + List<IndexRange> indexRanges = xtext.findAll(IndexRange.class); + validator.checkIndexRangeBounds(indexRanges.get(0)); + verifyError( + "Extensions and reserved numbers must be positive.", + indexRanges.get(0), + ProtobufPackage.Literals.INDEX_RANGE__FROM); + verifyNoMoreInteractions(messageAcceptor); + } + + // syntax = "proto2"; + // + // message Person { + // reserved 3 to 1; + // reserved 4 to 4; + // } + @Test public void should_error_on_end_less_than_start() { + List<IndexRange> indexRanges = xtext.findAll(IndexRange.class); + validator.checkIndexRangeBounds(indexRanges.get(0)); + validator.checkIndexRangeBounds(indexRanges.get(1)); + verifyError("End number must be greater than or equal to start number.", indexRanges.get(0)); + verifyNoMoreInteractions(messageAcceptor); + } + + private void verifyError(String message, EObject errorSource) { + verifyError(message, errorSource, null); + } + + private void verifyError(String message, EObject errorSource, EStructuralFeature errorFeature) { + verify(messageAcceptor).acceptError(message, errorSource, errorFeature, -1, null); + } +}
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java index 47aee62..7eb1a56 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexRanges.java
@@ -20,6 +20,13 @@ * @author jogl@google.com (John Glassmyer) */ @Singleton public class IndexRanges { + /** + * Thrown to indicate that a range's end number is less than its start number. + */ + public static class BackwardsRangeException extends Exception { + private static final long serialVersionUID = 1L; + } + private ProtobufGrammarAccess protobufGrammarAccess; @Inject @@ -27,7 +34,10 @@ this.protobufGrammarAccess = protobufGrammarAccess; } - public Range<Long> toLongRange(IndexRange indexRange) { + /** + * @throws BackwardsRangeException if the end number is less than the start number + */ + public Range<Long> toLongRange(IndexRange indexRange) throws BackwardsRangeException { long from = indexRange.getFrom(); Range<Long> range; @@ -37,7 +47,13 @@ } else if (toString.equals(getMaxKeyword())) { range = Range.atLeast(from); } else { - range = Range.closed(from, Long.valueOf(toString)); + Long to = Long.valueOf(toString); + + if (to < from) { + throw new BackwardsRangeException(); + } + + range = Range.closed(from, to); } return range;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java index 1b995d4..f0cd083 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/model/util/IndexedElements.java
@@ -21,6 +21,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Range; +import com.google.eclipse.protobuf.model.util.IndexRanges.BackwardsRangeException; import com.google.eclipse.protobuf.protobuf.FieldOption; import com.google.eclipse.protobuf.protobuf.Group; import com.google.eclipse.protobuf.protobuf.IndexRange; @@ -67,20 +68,24 @@ private long findMaxIndex(Iterable<? extends EObject> elements) { long maxIndex = 0; - for (EObject e : elements) { - if (e instanceof OneOf) { - maxIndex = max(maxIndex, findMaxIndex(((OneOf) e).getElements())); - } else if (e instanceof IndexedElement) { - maxIndex = max(maxIndex, indexOf((IndexedElement) e)); - if (e instanceof Group) { - maxIndex = max(maxIndex, findMaxIndex(((Group) e).getElements())); + for (EObject element : elements) { + if (element instanceof OneOf) { + maxIndex = max(maxIndex, findMaxIndex(((OneOf) element).getElements())); + } else if (element instanceof IndexedElement) { + maxIndex = max(maxIndex, indexOf((IndexedElement) element)); + if (element instanceof Group) { + maxIndex = max(maxIndex, findMaxIndex(((Group) element).getElements())); } - } else if (e instanceof Reserved) { + } else if (element instanceof Reserved) { for (IndexRange indexRange : - Iterables.filter(((Reserved) e).getReservations(), IndexRange.class)) { - Range<Long> range = indexRanges.toLongRange(indexRange); - if (range.hasUpperBound()) { - maxIndex = max(maxIndex, range.upperEndpoint()); + Iterables.filter(((Reserved) element).getReservations(), IndexRange.class)) { + try { + Range<Long> range = indexRanges.toLongRange(indexRange); + if (range.hasUpperBound()) { + maxIndex = max(maxIndex, range.upperEndpoint()); + } + } catch (BackwardsRangeException e) { + // Do not consider reserved ranges that are invalid. } } }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java index 85c2f9d..bda1d9f 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.java
@@ -31,6 +31,8 @@ public static String fieldNumbersMustBePositive; public static String importingUnsupportedSyntax; public static String importNotFound; + public static String indexRangeEndLessThanStart; + public static String indexRangeNonPositive; public static String invalidMapKeyType; public static String invalidMapValueType; public static String literalNotInEnum;
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties index 15aaf6a..60934f8 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/Messages.properties
@@ -15,6 +15,8 @@ fieldNumbersMustBePositive = Field numbers must be positive integers. importingUnsupportedSyntax = Importing unsupported file (directly or indirectly.) This may cause errors related to unresolved references. importNotFound = Import \"%s\" was not found. +indexRangeEndLessThanStart = End number must be greater than or equal to start number. +indexRangeNonPositive = Extensions and reserved numbers must be positive. invalidMapKeyType = Key in a map field must be of integral or string type. invalidMapValueType = Value in a map field cannot be a map. literalNotInEnum = Enum type \"%s\" has no value named \"%s\".
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java index d37b92d..3c6e173 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/validation/ProtobufJavaValidator.java
@@ -16,6 +16,8 @@ import static com.google.eclipse.protobuf.validation.Messages.expectedFieldNumber; import static com.google.eclipse.protobuf.validation.Messages.expectedSyntaxIdentifier; import static com.google.eclipse.protobuf.validation.Messages.fieldNumbersMustBePositive; +import static com.google.eclipse.protobuf.validation.Messages.indexRangeEndLessThanStart; +import static com.google.eclipse.protobuf.validation.Messages.indexRangeNonPositive; import static com.google.eclipse.protobuf.validation.Messages.invalidMapKeyType; import static com.google.eclipse.protobuf.validation.Messages.invalidMapValueType; import static com.google.eclipse.protobuf.validation.Messages.mapWithModifier; @@ -49,6 +51,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Range; import com.google.eclipse.protobuf.model.util.IndexRanges; +import com.google.eclipse.protobuf.model.util.IndexRanges.BackwardsRangeException; import com.google.eclipse.protobuf.model.util.IndexedElements; import com.google.eclipse.protobuf.model.util.Protobufs; import com.google.eclipse.protobuf.model.util.StringLiterals; @@ -124,22 +127,44 @@ error(msg, syntax, SYNTAX__NAME, SYNTAX_IS_NOT_KNOWN_ERROR); } + @Check public void checkIndexRangeBounds(IndexRange indexRange) { + Range<Long> range; + try { + range = indexRanges.toLongRange(indexRange); + } catch (BackwardsRangeException e) { + error(indexRangeEndLessThanStart, indexRange, null); + return; + } + + if (range.lowerEndpoint() <= 0) { + error(indexRangeNonPositive, indexRange, ProtobufPackage.Literals.INDEX_RANGE__FROM); + } + } + @Check public void checkForIndexConflicts(Message message) { Multimap<EObject, Range<Long>> rangeUsages = LinkedHashMultimap.create(); for (Reserved reserved : getOwnedElements(message, Reserved.class)) { for (IndexRange indexRange : Iterables.filter(reserved.getReservations(), IndexRange.class)) { - Range<Long> range = indexRanges.toLongRange(indexRange); - errorOnConflicts(range, rangeUsages, indexRange, null); - rangeUsages.put(reserved, range); + try { + Range<Long> range = indexRanges.toLongRange(indexRange); + errorOnConflicts(range, rangeUsages, indexRange, null); + rangeUsages.put(reserved, range); + } catch (BackwardsRangeException e) { + // Do not try to find conflicts with invalid ranges. + } } } for (Extensions extensions : getOwnedElements(message, Extensions.class)) { for (IndexRange indexRange : extensions.getRanges()) { - Range<Long> range = indexRanges.toLongRange(indexRange); - errorOnConflicts(range, rangeUsages, indexRange, null); - rangeUsages.put(extensions, range); + try { + Range<Long> range = indexRanges.toLongRange(indexRange); + errorOnConflicts(range, rangeUsages, indexRange, null); + rangeUsages.put(extensions, range); + } catch (BackwardsRangeException e) { + // Do not try to find conflicts with invalid ranges. + } } }